
Driving student programs - tekknolagi
https://bernsteinbear.com/blog/driving-student-programs/
======
repsilat
Sounds like a job for bash...

    
    
        timeout 1 program < in > student_out
        diff student_out real_out
    

Or something?

Edit: I don't know how standard `timeout` is, but I guess you could also do
something like

    
    
        program < in | tee student_out | sleep 1

~~~
tekknolagi
The problem is that they can't handle normal piping well and we don't want the
extraneous output in our logs & tests.

~~~
repsilat
Hmm, I don't quite understand, sorry. As far as I can tell, the code in TFA
takes the pipe output up to one second and then kills the student code if it
hasn't exited by itself. That's what my examples do too. (Happy to be
corrected on either point.)

Another solution: give students some simple test cases and the test
environment.

~~~
kd5bjo
The article's code leaves the input stream open, which causes reads to block
forever instead of exiting with an end-of-file marker. As the student projects
are for an introductory course, making them handle that particular detail
would only be a distraction from the desired learning outcomes.

~~~
tekknolagi
Precisely!

------
orf
Seems simple enough in Python:

    
    
         p = subprocess.Popen(stdin=PIPE, stdout=PIPE)
         stdout, stderr = p.communicate(timeout=5)
         p.kill()
    

A lot easier to read to boot.

------
kd5bjo
If you're not filtering or monitoring stdout and stderr, there's no need to
proxy them via pipes. Instead, you can let the child process inherit them and
it will send its output directly to the shell without a round-trip through the
parent's userspace.

~~~
tekknolagi
Now that's something I hadn't considered. Can you point me to some
documentation?

~~~
kd5bjo
Unfortunately, I don't know of a good write-up of how the kernel actually
deals with file descriptors, aside from the man pages for the various syscalls
you're using.

From the kernel's perspective, there's nothing special about file descriptors
0, 1, and 2. The shell did the same pipe/fork/dup2/exec procedure as your code
when it launched your program, and hooked the other end of the pipes to the
terminal.

When you call fork(), both processes keep all of their open file descriptors,
which is why you have to close the ones you're not going to use with either
close() or dup2(). If stdout or stderr is already attached to the correct
destination when fork() is called, it's perfectly acceptable to leave them
alone.

Read-mode file descriptors (like stdin) are more problematic if they're used
concurrently by multiple processes. Only one of the processes will read any
given piece of data that was written into the pipe, so you need to coordinate
the reads between the processes somehow.

