Testing Tool For GCJ - YessineJallouli/Competitive-Programming GitHub Wiki
We will use Twisty Little Passages problem as an exemple. We need 3 files, main.cpp is the main solution, interactive-runner.py and testing-tool.py are our testing tools.
interactive-runner.py
# 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.")
testing-tool.py
# "Twisty Little Passages" local testing tool.
#
# Usage: `python3 local_testing_tool.py`
import sys
import random
NUM_CASES = 100
N = 100000
K = 8000
NEED_CORRECT = 90
class Error(Exception):
pass
class WrongAnswer(Exception):
pass
WRONG_NUM_TOKENS_ERROR = ("Wrong number of tokens: expected {}, found {}.".format)
NOT_INTEGER_ERROR = "Not an integer: {}.".format
INVALID_LINE_ERROR = "Couldn't read a valid line."
ADDITIONAL_INPUT_ERROR = "Additional input after all cases finish: {}.".format
OUT_OF_BOUNDS_ERROR = "Request out of bounds: {}.".format
TOO_FEW_CORRECT_ERROR = "Too few correct answers: {}.".format
INVALID_COMMAND_ERROR = "couldn't understand player command: {}.".format
DID_NOT_GIVE_AN_ESTIMATE_ERROR = "Player did not give an estimate after K rounds."
def ReadValues(line):
t = line.split()
return t
def ConvertToInt(token, min, max):
try:
v = int(token)
except:
raise Error(NOT_INTEGER_ERROR(token[:100]))
if v < min or v > max:
raise Error(OUT_OF_BOUNDS_ERROR(v))
return v
def ConvertToAnyInt(token):
try:
v = int(token)
except:
raise Error(NOT_INTEGER_ERROR(token[:100]))
return v
def Input():
try:
return input()
except EOFError:
raise
except:
raise Error(INVALID_LINE_ERROR)
def Output(line):
try:
print(line)
sys.stdout.flush()
except:
try:
sys.stdout.close()
except:
pass
def RunCases():
Output("{}".format(NUM_CASES))
correct = 0
for case_number in range(NUM_CASES):
Output("{} {}".format(N, K))
# Construct a graph in adj.
adj = [[] for _ in range(N)]
correct_total_edges = 0
order = [i for i in range(N)]
random.shuffle(order)
for i in range(0, N, 2):
v1 = order[i]
v2 = order[i+1]
adj[v1].append(v2)
adj[v2].append(v1)
correct_total_edges += 1
add = random.randint(0, 4*N)
add = random.randint(0, add)
for j in range(add):
v1 = random.randint(0,N-1)
v2 = random.randint(0,N-1)
if v1 != v2 and v2 not in adj[v1] and len(adj[v1])<N-2 and len(adj[v2])<N-2:
adj[v1].append(v2)
adj[v2].append(v1)
correct_total_edges += 1
complement = random.choice([False, True])
if complement:
correct_total_edges = (N*(N-1))//2 - correct_total_edges
# Play the game.
where = random.randint(0,N-1)
for move_number in range(K+1):
# Output current room number (1-based) and number of adjacent passages.
if complement:
Output("{} {}".format(where+1, N-1-len(adj[where])))
else:
Output("{} {}".format(where+1, len(adj[where])))
# Get the user's move.
try:
move = ReadValues(Input())
except EOFError:
raise Error(INVALID_LINE_ERROR)
except Error as error:
raise error
if len(move) == 0:
raise Error(INVALID_LINE_ERROR)
if move[0] == "E":
# The user provided an estimate.
if len(move) != 2:
raise Error(WRONG_NUM_TOKENS_ERROR(2,len(move)))
estimate = ConvertToAnyInt(move[1])
lo = (correct_total_edges * 2 + 2) // 3
hi = (correct_total_edges * 4) // 3
if lo <= estimate and estimate <= hi:
print(f"Case #{case_number + 1}: Correct -- got {estimate}; exact answer is {correct_total_edges}.", file=sys.stderr)
correct += 1
else:
print(f"Case #{case_number + 1}: Wrong -- got {estimate}; exact answer is {correct_total_edges}; acceptable range is [{lo}, {hi}].", file=sys.stderr)
# Go to the next test case.
break
elif move_number == K:
# The cave is now closed!
raise Error(DID_NOT_GIVE_AN_ESTIMATE_ERROR)
elif move[0] == "W":
# The user took a random exit.
if len(move) != 1:
raise Error(WRONG_NUM_TOKENS_ERROR(1,len(move)))
if complement:
while True:
next = random.randint(0,N-1)
# NOTE: The check for (next != where) was not present at the
# beginning of contest. This would, in rare occasions, introduce
# self-loops. This bug was never present in the real judge.
if next != where and next not in adj[where]:
where = next
break
else:
l = adj[where]
where = l[random.randint(0,len(l)-1)]
elif move[0] == "T":
# The user teleported to a room.
if len(move) != 2:
raise Error(WRONG_NUM_TOKENS_ERROR(1,len(move)))
where = ConvertToInt(move[1], 1, N)
where -= 1
else:
raise Error(INVALID_COMMAND_ERROR(move[0][:1000]))
# Check there is no extraneous input from the user.
try:
extra_input = Input()
raise Error(ADDITIONAL_INPUT_ERROR(extra_input[:100]))
except EOFError:
pass
# Finished.
print(f"User got {correct} cases correct.", file=sys.stderr)
if correct < NEED_CORRECT:
raise WrongAnswer(TOO_FEW_CORRECT_ERROR(correct))
def main():
if len(sys.argv) == 2 and int(sys.argv[1]) < 0:
sys.exit(0)
random.seed(12345)
try:
RunCases()
except Error as error:
print(error, file=sys.stderr)
sys.exit(1)
except WrongAnswer as error:
print(error, file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
main.cpp
#include <bits/stdc++.h>
#define ll long long
#define SaveTime ios_base::sync_with_stdio(false), cin.tie(0);
using namespace std;
const int N = 1001;
int fun[N];
int MX = 0;
mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count());
void solve() {
int n, k; cin >> n >> k;
int a1,b1; cin >> a1 >> b1;
ll nbExperience = 5;
ll h = 0;
for (int i = 0; i < nbExperience; i++) {
vector<int> v;
for (int j = 1; j <= n; j++) {
v.push_back(j);
}
shuffle(v.begin(), v.end(), mt);
ll nb = 0;
for (int j = 0; j < k/nbExperience; j++) {
cout << "T " << v[j] << endl;
int a,b; cin >> a >> b;
nb+= b;
}
nb*= nbExperience*n/k;
h+= nb;
}
cout << "E " << h/(nbExperience*2) << endl;
}
int main() {
int T; cin >> T;
for (int ii = 1; ii <= T; ii++) {
solve();
}
}
Put those three files in the same directory and then create executable file of main.cpp using this command :
g++ main.cpp -o main
Now, execute this command to launch the testing tool :
python3 interactive-runner.py python3 testing-tool.py -- ./main
Everything is fine 😊