-
Notifications
You must be signed in to change notification settings - Fork 0
/
interactive_runner.py
122 lines (112 loc) · 4.69 KB
/
interactive_runner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# This code can be run as python2 or python3 in most systems.
#
# This is a small program that runs two processes, connecting the stdin of each
# one to the stdout of the other.
# It doesn't perform a lot of checking, so many errors may
# be caught internally by Python (e.g., if your command line has incorrect
# syntax) or not caught at all (e.g., if the judge or solution hangs).
#
# Run this as:
# python interactive_runner.py <cmd_line_judge> -- <cmd_line_solution>
#
# For example, if you have a testing_tool.py in python3 (that takes a single
# integer as a command line parameter) to use as judge -- like one
# downloaded from a problem statement -- and you would run your solution
# in a standalone using one of the following:
# 1. python3 my_solution.py
# 2. ./my_solution
# 3. java Solution
# 4. my_solution.exe
# Then you could run the judge and solution together, using this, as:
# 1. python interactive_runner.py python3 testing_tool.py 0 -- python3 my_solution.py
# 2. python interactive_runner.py python3 testing_tool.py 0 -- ./my_solution
# 3. python interactive_runner.py python3 testing_tool.py 0 -- java solution
# 4. python interactive_runner.py python3 testing_tool.py 0 -- my_solution.exe
# Notice that the solution in cases 2, 3 and 4 would usually have a
# compilation step before running, which you should run in your usual way
# before using this tool.
#
# This is only intended as a convenient tool to help contestants test solutions
# locally. In particular, it is not identical to the implementation on our
# server, which is more complex.
#
# The standard streams are handled the following way:
# - judge's stdin is connected to the solution's stdout;
# - judge's stdout is connected to the solution's stdin;
# - stderrs of both judge and solution are piped to standard error stream, with
# lines prepended by "judge: " or "sol: " respectively (note, no
# synchronization is done so it's possible for the messages from both programs
# to overlap with each other).
from __future__ import print_function
import sys, subprocess, threading
class SubprocessThread(threading.Thread):
def __init__(self,
args,
stdin_pipe=subprocess.PIPE,
stdout_pipe=subprocess.PIPE,
stderr_prefix=None):
threading.Thread.__init__(self)
self.stderr_prefix = stderr_prefix
self.p = subprocess.Popen(
args, stdin=stdin_pipe, stdout=stdout_pipe, stderr=subprocess.PIPE)
def run(self):
try:
self.pipeToStdErr(self.p.stderr)
self.return_code = self.p.wait()
self.error_message = None
except (SystemError, OSError):
self.return_code = -1
self.error_message = "The process crashed or produced too much output."
# Reads bytes from the stream and writes them to sys.stderr prepending lines
# with self.stderr_prefix.
# We are not reading by lines to guard against the case when EOL is never
# found in the stream.
def pipeToStdErr(self, stream):
new_line = True
while True:
chunk = stream.readline(1024)
if not chunk:
return
chunk = chunk.decode("UTF-8")
if new_line and self.stderr_prefix:
chunk = self.stderr_prefix + chunk
new_line = False
sys.stderr.write(chunk)
if chunk.endswith("\n"):
new_line = True
sys.stderr.flush()
assert sys.argv.count("--") == 1, (
"There should be exactly one instance of '--' in the command line.")
sep_index = sys.argv.index("--")
judge_args = sys.argv[1:sep_index]
sol_args = sys.argv[sep_index + 1:]
t_sol = SubprocessThread(sol_args, stderr_prefix=" sol: ")
t_judge = SubprocessThread(
judge_args,
stdin_pipe=t_sol.p.stdout,
stdout_pipe=t_sol.p.stdin,
stderr_prefix="judge: ")
t_sol.start()
t_judge.start()
t_sol.join()
t_judge.join()
# Print an empty line to handle the case when stderr doesn't print EOL.
print()
print("Judge return code:", t_judge.return_code)
if t_judge.error_message:
print("Judge error message:", t_judge.error_message)
print("Solution return code:", t_sol.return_code)
if t_sol.error_message:
print("Solution error message:", t_sol.error_message)
if t_sol.return_code:
print("A solution finishing with exit code other than 0 (without exceeding "
"time or memory limits) would be interpreted as a Runtime Error "
"in the system.")
elif t_judge.return_code:
print("A solution finishing with exit code 0 (without exceeding time or "
"memory limits) and a judge finishing with exit code other than 0 "
"would be interpreted as a Wrong Answer in the system.")
else:
print("A solution and judge both finishing with exit code 0 (without "
"exceeding time or memory limits) would be interpreted as Correct "
"in the system.")