Adding a custom checker#
Prerequisite
This page continues the story from First steps. If you haven't gone through it yet, start there — we pick up right where it left off.
In First steps we built a problem that asks for the sum of N integers.
That problem has a single correct answer, so rbx's default checker does the job: wcmp
compares the participant's output token-by-token against the model answer.
But not every problem has a unique answer. Let's mutate our problem into one that doesn't, and see why we need a checker.
A problem with many answers#
Let's change the problem to:
Given an integer
N(2 ≤ N ≤ 10^9), print any two integersaandbsuch thata + b = Nand1 ≤ a, b.
Now there are many correct answers. For N = 10, both 1 9 and 5 5 are valid. This breaks
token comparison: if your solution prints 1 9 but the model solution printed 5 5, wcmp
would wrongly flag a wrong answer even though 1 9 is correct.
We need a checker that verifies the property (a + b = N) instead of comparing strings.
The solutions#
Here's an accepted solution and a wrong answer solution. As in step 1, the
filename prefix (wa-) tells rbx the expected outcome.
What about the validator, generator and statement?
Switching problems also means updating the input validator, the generator and the
statement. The input is now a single integer N, and the mechanics are the ones from
First steps, so we keep the spotlight on the checker instead of
re-walking them.
Writing the checker#
A testlib checker is a small program that receives three files:
<input_file>— the test input (here, the integerN).<output_file>— the participant's output (here, the paira b).<answer_file>— the model solution's output.
testlib exposes these as the streams inf, ouf and ans respectively. For our problem,
we only need inf and ouf: any pair that sums to N is correct, so we never have to look at
the model answer.
-
registerTestlibCmdwires up the three streams (inf,ouf,ans) from the command line arguments. Every checker starts with this call. -
Reading with bounds is your first line of defense.
ouf.readInt(1, n - 1, "a")reads an integer namedaand fails with a wrong answer if it is missing or outside[1, n - 1], with no extra code. -
quitf(_wa, ...)ends the checker with a wrong answer and a custom message. Notice you can useprintf-style format specifiers, so the report says what went wrong. -
quitf(_ok, ...)ends the checker with an accepted verdict.
When you do need the model answer
Some problems (e.g. "find the shortest path") can only be checked by comparing against
the jury's solution via the ans stream. For that pattern, see the
Checkers feature guide and its output + answer example.
Wiring it into problem.rbx.yml#
The pre-initialized preset uses the built-in wcmp checker. Point the checker field at our
new file instead:
Running it#
Now run rbx run again. Two things change compared to step 1:
- Your
main.cpppasses on every test, even when it prints a different pair than the model solution, because the checker verifies the property rather than the tokens. wa-offbyone.cppfails, and instead of an opaque token diff you get the checker's custom message, e.g.a + b = 11, expected 10.
Testing the checker with rbx unit#
A buggy checker can silently let wrong solutions through, or reject correct ones. rbx lets you unit test your checker before you rely on it.
Declare the expected outcomes in problem.rbx.yml:
Each unit test is a triple of files sharing a name prefix: <name>.in (input), <name>.out
(participant output) and an optional <name>.ans (model answer). Our checker ignores the
answer, so we only provide .in and .out.
ac_BASIC should be accepted (4 + 6 = 10) and wa_BAD_SUM should be
wrong answer (2 + 9 = 11 ≠ 10). Run them with:
For many small cases, test plans avoid one file per test. See the Unit tests feature guide.
Next steps#
-
Stress-test your solutions
Continue the track: hunt for inputs that break a solution your checker would otherwise pass.
-
Checker reference
The full testlib checker guide: built-in checkers, the
ansstream, andJUDGE_FAILED.