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 😊

result

⚠️ **GitHub.com Fallback** ⚠️