Building Tests¶
Every test in clintest is assembled from a few building blocks. Here, you will learn what they are and how to combine them, building on the example from Getting Started.
Tests and outcomes¶
A test is an instance of Test.
The simplest one is Assert, which checks a property of the models of a program.
from clintest.test import Assert
from clintest.quantifier import All
from clintest.assertion import Contains
test = Assert(All(), Contains("a"))
This test checks that all models contain the atom a.
Every test keeps track of its own outcome, which is one of the following:
F?: possibly falseT?: possibly trueF!: certainly falseT!: certainly true
An uncertain outcome means that the test is not complete yet. Once the outcome is certain, it never changes again. You can query the outcome at any time:
The test has not been run yet, so its outcome is still uncertain.
Since no model so far lacks the atom a, it is possibly true.
Assert combines an assertion (here: Contains("a")) with a quantifier (here: All()) into a test.
The next two sections introduce both in turn.
Assertions¶
An Assertion is a statement about a single model.
clintest provides the following assertions:
| Assertion | Holds if the model … |
|---|---|
Contains(symbol) |
contains the atom symbol |
Equals(symbols) |
shows exactly the symbols in symbols |
SubsetOf(symbols) |
shows only symbols in symbols |
SupersetOf(symbols) |
shows all symbols in symbols |
Optimal() |
is proven to be optimal |
Contains checks all atoms of a model, while Equals, SubsetOf, and SupersetOf only look at the shown symbols.
This matters as soon as your program uses #show.
Symbols can be given as strings, such as "p(1)", or as clingo.Symbol objects.
Assertions can be combined with Not, And, Or, Implies, and Equivalent.
For example, the following assertion holds for every model that either lacks b or contains a:
Quantifiers¶
A Quantifier decides for how many models the assertion must hold.
clintest provides the following quantifiers:
| Quantifier | The assertion must hold for … |
|---|---|
All() |
all models |
Any() |
at least one model |
First() |
the first model |
Last() |
the last model |
Exact(target) |
exactly target models |
Less(supremum) |
fewer than supremum models |
LessEqual(maximum) |
at most maximum models |
Greater(infimum) |
more than infimum models |
GreaterEqual(minimum) |
at least minimum models |
The quantifier also determines when the outcome becomes certain.
For example, All is certainly false as soon as one model violates the assertion, and Any is certainly true as soon as one model satisfies it.
Otherwise, the outcome only becomes certain once the solver has finished.
Quantifiers can be combined with Not, And, and Or.
For example, the following quantifier demands that the assertion holds for two to five models, so it is certainly false as soon as the assertion holds for a sixth one:
from clintest.quantifier import And, GreaterEqual, LessEqual
quantifier = And(GreaterEqual(2), LessEqual(5))
Combinations also let you decide what happens if a program has no models.
Without models, All is true and Any is false.
If you need it the other way around, And(All(), Any()) behaves like All but is false without models, and Or(Any(), All()) behaves like Any but is true without models.
Quantifiers are stateful, so each test needs its own instance.
The same goes for the operands of And and Or: in And(q, Not(q)), the quantifier q would count every model twice.
Solvers¶
A Solver runs a test.
Most of the time, you will use Clingo:
Its constructor takes three optional arguments:
arguments: a list of command-line options for clingo. Here,"0"asks clingo for all models. Without it, clingo stops after the first model.program: the program as a string.files: a list of paths to files with additional program parts.
Calling solve runs the test and updates its outcome:
A test is updated in place and cannot be reset. To run the same check again, create a new test.
If you want to test against a fixed list of models instead of running clingo, use Iterate.
Combining tests¶
Real-world programs need more than one check.
Instead of running each check separately, you can combine tests with And, Or, and Not:
from clintest.test import Assert, And, Not
from clintest.quantifier import All, Any
from clintest.assertion import Contains
test = And(
Assert(All(), Contains("a")),
Not(Assert(Any(), Contains("c"))),
)
This test checks that all models contain a and that no model contains c.
All operands see the same models, so clingo only has to solve the program once.
Compound tests also decide when to stop.
And is certainly false as soon as one operand is certainly false, and Or is certainly true as soon as one operand is certainly true.
From then on, the remaining operands no longer matter, so clintest stops the solver.
This is why, in Getting Started, the last assertion was never completed.
You can turn this behavior off with short_circuit=False.
The remaining operands then keep running until their own outcomes are certain.