/
interactive_runner.py
132 lines (122 loc) · 5.23 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
123
124
125
126
127
128
129
130
131
132
# 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>
#
#
# ////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////
# AEREN's
# python3 interactive_runner.py python3 local_testing_tool.py 0 -- ./a
# python3 interactive_runner.py python3 local_testing_tool.py 1 -- ./a
# python3 interactive_runner.py python3 local_testing_tool.py 2 -- ./a
# ////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////
#
# For example, if you have a local_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 local_testing_tool.py 0 -- python3 my_solution.py
# 2. python interactive_runner.py python3 local_testing_tool.py 0 -- ./my_solution
# 3. python interactive_runner.py python3 local_testing_tool.py 0 -- java solution
# 4. python interactive_runner.py python3 local_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.")