0

To give some context, I have to grade my students' C codes. This is the folder structure:

    Root
      |_____ID#1
        |____task1.c
        |____task2.c
        |____task3.c
      |_____ID#2
        |____task1.c
        |____task2.c
        |____task3.c
        ...

It is to be noted that not every student answered all the questions, nor is the naming convention uniform. So, I have no way of knowing which file contains which task. All of the tasks require some sort of user input, but since I don't know which file contains which task, I cannot hardcode the input by figuring out the file name.

I want to be able to save:

a. the inputs to the processes, which I must be able to give to them in an interactive way (consider the following screenshot for what I mean by interactivity, after "Code compiles"),

enter image description here

b. their output after processing the input,

c. their errors (if any)

to a json or even a text file for now.

This is what I have so far:

import os
import subprocess
from zipfile import ZipFile

student_folders_root = "/home/mahieyin/Documents/Code/Python/CSE115/Mid #1-20200403T150847Z-001/Mid #1/"

for idx, (directory, _, zipfiles) in enumerate(os.walk(student_folders_root)):
    result = ""
    student_id = directory.rsplit(os.sep, 1)[-1]

    if idx == 0:
        continue
    elif len(zipfiles) == 0:        
        result += f"{student_id} did not attend the mid term exam."
        print(result)
    else:
        zipfiles = list(filter(lambda x: x.endswith(".zip"), zipfiles))

        path_to_zip = os.path.join(directory, *zipfiles)

        if path_to_zip.endswith('.zip'):
            with ZipFile(path_to_zip, 'r') as zip_object:
                zip_object.extractall(directory)

        print(f"\033[1;33;40mProcessing student: {student_id}...")

        for _, _, files in os.walk(directory):
            files = list(filter(lambda x: x.endswith('.c'), files))
            files = list(map(lambda x: os.path.join(directory, x), files))

            executable_name = "a.out"
            exec_path = os.path.join(directory, executable_name)

            for c_file in files:
                try:                
                    print(f"\n\n\033[1;35;40mProcessing file: {c_file}", end="\n\n")
                    compile_command = f"gcc \"{c_file}\" -o \"{exec_path}\""
                    run_command = f"\"{exec_path}\""
                    # command = f"gcc \"{c_file}\" -o \"{exec_path}\""

                    # subprocess.call(command, shell=True)
                    compile_result = subprocess.check_output(compile_command, shell=True)
                    compile_result = compile_result.decode('utf-8').strip()

                    if len(compile_result) == 0:
                        print("Code compiles.")                        
                        run_process = subprocess.run(run_command, shell=True)
                    else:
                        print("Compilation error.")
                        print("\033[1;31;40m", compile_result)
                except subprocess.CalledProcessError:
                    pass

This works as expected. I can see the compilation errors if the source file fails to compile. I can provide input to the spawned processes and see the output. However I am unable to wrap my head around the way in which I can obtain the outputs while preserving the interactability.

Basically, whenever I try to set the stdout and stderr options in either subprocess.Popen() or subprocess.call(), I lose the interactability with the spawned process. The prompts of the spawned processes don't show (e.g. "Please enter the value: ") until after I have provided the input, which isn't really elegant to look at.

Thanks in advance.

2 Answers 2

1

Have a look at pexpect:

Pexpect is a pure Python module for spawning child applications; controlling them; and responding to expected patterns in their output.

Sign up to request clarification or add additional context in comments.

Comments

0

Following Roland Smith's advice, I ended up using a solution that is not elegant but saves my time. I replaced the second subprocess call with pexpect.

import pexpect
import subprocess
import os
from zipfile import ZipFile

student_folders_root = "/home/mahieyin/Documents/Code/Python/CSE115/Mid #1-20200403T150847Z-001/Mid #1/"

for idx, (directory, _, zipfiles) in enumerate(os.walk(student_folders_root)):
    result = ""
    student_id = directory.rsplit(os.sep, 1)[-1]

    if (idx == 0):
        continue
    elif len(zipfiles) == 0:        
        result += f"{student_id} did not attend the mid term exam."
        print(result)
    else:
        zipfiles = list(filter(lambda x: x.endswith(".zip"), zipfiles))

        path_to_zip = os.path.join(directory, *zipfiles)

        if (path_to_zip.endswith('.zip')):
            with ZipFile(path_to_zip, 'r') as zip_object:
                zip_object.extractall(directory)

        print(f"Processing student: {student_id}...")

        for _, _, files in os.walk(directory):
            files = list(filter(lambda x: x.endswith('.c'), files))
            files = list(map(lambda x: os.path.join(directory, x), files))

            executable_name = "a.out"
            exec_path = os.path.join(directory, executable_name)

            for c_file in files:
                try:
                    print(f"\n\nProcessing file: {c_file}", end="\n\n")
                    compile_command = f"gcc \"{c_file}\" -o \"{exec_path}\""
                    run_command = f"\"{exec_path}\""
                    # command = f"gcc \"{c_file}\" -o \"{exec_path}\""

                    # subprocess.call(command, shell=True)
                    compile_result = subprocess.check_output(compile_command, shell=True)
                    compile_result = compile_result.decode('utf-8').strip()

                    if len(compile_result) == 0:
                        print("Code compiles.")
                        # the change
                        c_process = pexpect.spawn(run_command)
                        c_process.interact()
                    else:
                        print("Compilation error.")
                        print(compile_result)
                except subprocess.CalledProcessError:
                    pass

And for redirecting the output to a log file, I use:

python GradeStudents.py 2>&1 | tee logfile.txt

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.