diff options
-rwxr-xr-x | python/engine.py | 71 | ||||
-rwxr-xr-x | python/runner/interpreter.py | 8 |
2 files changed, 60 insertions, 19 deletions
diff --git a/python/engine.py b/python/engine.py index 4a12443..51c08b4 100755 --- a/python/engine.py +++ b/python/engine.py @@ -1,30 +1,63 @@ #!/usr/bin/python3 -# This module submits Python code to the python-tester. - +from fcntl import fcntl, F_GETFL, F_SETFL import http.client import json +import os +import subprocess -address, port = 'localhost', 3031 # TODO put this somewhere sane -def request(path, args=None): - headers = {'Content-Type': 'application/json;charset=utf-8'} - body = json.dumps(args) - try: - conn = http.client.HTTPConnection(address, port, timeout=30) - conn.request('GET', path, body=body, headers=headers) - response = conn.getresponse() - if response.status != http.client.OK: - raise Exception('server returned {}'.format(response.status)) - reply = json.loads(response.read().decode('utf-8')) - return reply - finally: - conn.close() - -# Inputs are given as a list of pairs (expression, stdin). Receives and returns -# a list of answers given as tuples (result, stdout, stderr, exception). +# For each input, load [code] into a new environment and run it using a Python +# runner daemon (so we can get structured results instead of parsing stdout). +# Inputs are given as a list of pairs (expression, stdin). Return a list of +# answers as tuples (result, stdout, stderr, exception). If the code times out +# on some input, 'timed out' is returned instead at the corresponding position +# in the answer list. def run(code=None, inputs=None, timeout=1.0): + address, port = 'localhost', 3031 # TODO put this somewhere sane + def request(path, args=None): + headers = {'Content-Type': 'application/json;charset=utf-8'} + body = json.dumps(args) + try: + conn = http.client.HTTPConnection(address, port, timeout=30) + conn.request('GET', path, body=body, headers=headers) + response = conn.getresponse() + if response.status != http.client.OK: + raise Exception('server returned {}'.format(response.status)) + reply = json.loads(response.read().decode('utf-8')) + return reply + finally: + conn.close() return request('/run', {'code': code, 'inputs': inputs, 'timeout': timeout}) +# Start and return a new Python interpreter process. +def create(): + directory = os.path.dirname(os.path.realpath(__file__)) + script = os.path.join(directory, 'runner', 'interpreter.py') + p = subprocess.Popen([script], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + # Set the non-blocking flag for stdout. + flags = fcntl(p.stdout, F_GETFL) + fcntl(p.stdout, F_SETFL, flags | os.O_NONBLOCK) + return p + +# Read any available bytes from the interpreter's stdout. +def pull(interpreter): + stdout = interpreter.stdout.read() + if stdout: + stdout = stdout.decode('utf-8') + return stdout + +# Push a string to the interpreter's stdin. +def push(interpreter, stdin): + interpreter.stdin.write(stdin.encode('utf-8')) + interpreter.stdin.flush() + +# Kill an interpreter process. +def destroy(interpreter): + interpreter.kill() + # Basic sanity check. if __name__ == '__main__': diff --git a/python/runner/interpreter.py b/python/runner/interpreter.py new file mode 100755 index 0000000..5fa320a --- /dev/null +++ b/python/runner/interpreter.py @@ -0,0 +1,8 @@ +#!/usr/bin/python3 + +# Apparently there is no (working) way to get a non-blocking stdout if we call +# the Python interpreter directly with subprocess.Popen. For some reason, this +# works. + +import code +code.interact(banner='') |