Stateful Property Testing a CRDT with jetCheck
Background
Property-based testing is a powerful technique for making general claims about the behavior of a system and verifying that those claims hold true. This relies on combinatorics to drive the tests themselves, generating randomized, valid inputs to the system and asserting against the resulting state. The technique was arguably popularized by the Haskell QuickCheck library.
A presentation by Benjamin C. Pierce titled "Principles of Synchronization" (see the CERN CDS record for the video) and the corresponding paper titled "Mysteries of Dropbox" (located at the University of Pennsylvania site) can offer an idea of how effective the technique can be to discovering problematic behavior in a real-world system.
JetBrains developed a property-based testing Java library named jetCheck (the repository can be found on GitHub) as part of the IntelliJ IDE project. Crucially, it supports verifying stateful systems by inputing a set of custom commands that interact with the system. A random sequence of those commands is executed against the system with assertions that certain properties hold true. If the custom commands are deterministic, jetCheck can shrink the total sequence of commands down to a minimal subsequence that reproduces a verification failure.
Example
To help demonstrate the jetCheck library by example, the system under test is an arbitrary state-based Add-Wins set CRDT (Conflict-free Replicated Data Type) implementation. Details such as the mechanism for establishing causality are abstracted away for simplicity. Its interface is in Listing 1.
Listing 1 - Interface of the Add-Wins set.
The property-based test is written in Groovy (with Spock) and is contained in Listing 2. If an exception is thrown, jetCheck assumes that a property was violated and verification fails. Since in Groovy assertions are always executed and throw an exception on failure, the test makes use of the “assert” keyword.
Should a test iteration fail, a descriptive error message is printed and describes shrunk (if possible) steps that lead to the failure and outputs a seed number to re-run the same sequence if required for debugging:
The test itself declares four commands: Add, Remove, Merge and Add-Wins. first three commands are self-explanatory and correspond directly to the set operations defined by the interface. On the other hand, the Add-Wins command exercises a specific property of the set: that on merge, where there is a concurrent add and remove of an element, the add prevails, keeping the element in the set.
Listing 2 - Property-based test that harnesses the jetCheck library.
Tuning
It is possible to customize parameters of the test by invoking the PropertyChecker#customized
method, for example:
Here the iteration count is bumped to 500 from the default value of 100 and print all values generated for testing.