Validators#
Validator is a concept introduced by testlib to verify whether the tests you generate for a problem are in the format you really expect.
Think of the frustrating scenarios where you've written in the statement that the graph should be connected, or a tree, or a DAG, but there was a test in your testset that contradicted this. Even experienced setters make these mistakes, and it's important to have extra guards to catch them.
Similar to Codeforces, rbx offers built-in support for testlib validators (and also encourages you to use it), but also provides the flexibility for you to write your own.
Motivational problem#
For the next sections, let's assume we have a problem that asks you to find a path between two
vertices 1 and N
in a connected graph with N
vertices numbered from 1 to N
, where N
is between 2 and 1000 and M
is between 1 and N * (N - 1) / 2
.
Let's assume the input is given like this:
In the first line, we have the number of vertices N
and the number of edges M
, separated by a single space.
In the next M
lines, we have the edges of the graph, represented by two integers u
and v
separated by a single space,
indicating that there is an undirected edge between vertex u
and vertex v
. Then, the file ends.
Let's write a validator to verify that our testset does not violate these constraints.
Using testlib validators#
You can read more about testlib validators in the Codeforces documentation.
To use a testlib validator, you need to specify the path to the validator in the validator
field.
testlib validators are always written in C++ and should include the testlib.h
header. rbx treats
this header especially, and will automatically place it along your validator when compiling it.
Let's write a simple validator that checks the input format above.
-
We read the number of vertices
N
and check that it is an integer between 2 and 1000. Notice we also set a variable nameN
.This is used for testlib to print useful error messages when an issue is found.
-
We read the number of edges
M
and check that it is between 1 andN * (N - 1) / 2
.Notice how we re-use the variable
n
we read before. -
We read the end of the file.
Notice we're super strict about spaces, end-of-lines and end-of-file here. That's the purpose of the validator component.
Of course, we still have to check that the graph is connected, but let's do this in a minute.
Let's first talk about variables. As explained in the Variables section, we can use variables to refer to constraints in the input.
At the moment, we're hard coding the lower and upper bounds for N
in the validator. If we change the problem statement to, let's say,
allow N
between 3 and 500 instead, we'd have to remember to modify the validator. This is a dangerous
practice, as it's super easy to forget to do so.
Let's do the following modifications to our problem to make it safer:
rbx will automatically generate an rbx.h
header file for you, which will include the variables
you defined in your problem.rbx.yml
file, that you can access in your validator with the getVar<>()
function.
To read more about variables, check the Variables section.
Now, let's finally check that the graph is connected.
Tip
You can always manually call a validator on a custom input with rbx validate
.
Using custom validators#
Let's say you want to build a custom Python3 validator. You can do that similarly by specifying a Python
validator in the validator
field.
Warning
We strongly recommend using testlib validators.
They're not only easier to write, but also provides a set of tested utilites to read and stricly check parts of the input, something you would've to do manually otherwise.
Defining additional validators#
rbx provides a couple ways of defining additional validators for a problem or testset.
The first one is by using the extraValidators
field in the problem.rbx.yml
file.
This allows you to, for instance, define validators that check for different properties of the input separately.
validator:
path: 'validator.cpp'
extraValidators:
- path: 'connected-validator.cpp'
- path: 'bipartite-validator.cpp'
Or define validators that check for common properties of the input file that you'd rather keep off of the main validator.
validator:
path: 'validator.cpp'
extraValidators:
- path: 'only-printable-ascii.py'
- path: 'no-tabs.py'
- path: 'no-consecutive-spaces.py'
Another way of additional validators it to specify validators (or extra validators) for a specific test group in your problem.
This is often useful for problems that have multiple subtasks with different constraints, but can also be useful for ICPC-style contests where you use the grouping feature to separate tests you've generated with a specific purpose in mind.
Considering the problem above one more time, let's say we have a specific testplan focused on tests that contain a straight path from 1 to N
, because we know that this
is the largest solution a participant can get. We might want to have a validator to ensure tests coming from this testplan
really have this property.
# ... rest of the problem.rbx.yml ...
validator:
path: 'validator.cpp'
testcases:
- name: samples
testcaseGlob: manual_tests/samples/*.in
- name: general
generatorScript:
path: testplan/general.txt
- name: straight
generatorScript:
path: testplan/straight.txt
extraValidators:
- path: 'straight-validator.cpp'