summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xpython/engine.py71
-rwxr-xr-xpython/runner/interpreter.py8
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='')