Adding New Tests
This guide walks through creating verification tests for Sector9, from writing the test file to integrating with the test suite.
Quick Start
To add a new verification test:
- Create a
.moor.sr9file in an appropriatetest/viper/directory with the expected suffix - Run
./update_goldens.sh test/viper/<dir>/your_test.moto generate golden files - Run
./test-viper.shto verify the test passes against suffix expectations and goldens
Test File Location
Tests are organized by complexity level and feature area:
test/viper/
├── level1/ # Basic: arithmetic, arrays, simple contracts
├── level2/ # Actors, modules, imports
├── level3/ # Actor invariants and recursive data shapes
├── level4/ # Loop control-flow coverage
├── level5/ # Higher-order functions, type aliases
├── level8/ # Fixed-width numerics
├── level16/ # Quantifiers
├── level18/ # Collections, await semantics
├── quantifiers/ # forall, exists, triggers
├── async/ # Async/await verification
├── bugs/ # Regression tests
└── ...
Choose a directory that matches your test's complexity. When in doubt, use the lowest level that includes the features you need.
Naming Your Test
The filename suffix determines the expected outcome:
| Suffix | Meaning |
|---|---|
_sat | Verification must succeed |
_unsat | Verification must fail |
_reject | Translation to Viper must fail |
_noperm | Must fail with permission error |
See Test Naming Conventions for details.
Writing the Test File
Minimal Structure
A verification test requires a persistent actor with contracts:
persistent actor {
public func example(n : Nat) : async Nat
ensures result == n + 1;
{
n + 1
};
}
Recommended Header
Include a header documenting the test purpose:
// ============================================================================
// Sector9 Verification Test
// ============================================================================
//
// Test Name: [Descriptive name]
// Level: [level1, level2, etc.]
// Expected Outcome: [SAT, UNSAT, REJECT]
//
// Purpose:
// [What this test verifies]
//
// Why This Should [Verify/Fail]:
// [Brief explanation]
//
// ============================================================================
persistent actor {
// ... test code
}
Test Configuration
Control test behavior with inline directives:
// viper-test: verify --cores 1 # Override verification flags for this file
// viper-test: viper --deterministic # Override Viper generation flags
// viper-test: slow-threshold 60 # Mark as slow only after 60s
// viper-test: serial # Avoid parallel suite scheduling
The test runner always performs both translation and verification for normal
tests. Inline directives add target-specific flags; they should not include
--verify, --viper, --json, --toon, or output-path flags because the
runner owns those.
Generating Golden Files
Golden files capture expected outputs for regression detection.
First-Time Setup
Generate golden files for a new test:
./update_goldens.sh test/viper/level1/my_new_test_sat.mo
This creates files in test/viper/level1/ok/:
| File | Content |
|---|---|
my_new_test_sat.vpr | Generated Viper code |
my_new_test_sat.vpr.stderr | Translator diagnostics |
my_new_test_sat.silicon | Verification result (JSON) |
my_new_test_sat.silicon.ret | Return code |
Batch Update
Update all golden files after intentional changes:
./update_goldens.sh
Verify Against Goldens
Check that outputs match expected values:
./test-viper.sh
Complete Example: SAT Test
Create test/viper/level1/bounded_increment_sat.mo:
// ============================================================================
// Sector9 Verification Test
// ============================================================================
//
// Test Name: Bounded increment
// Level: level1
// Expected Outcome: SAT
//
// Purpose:
// Verify increment with upper bound precondition.
//
// Why This Should Verify:
// Precondition ensures no overflow; postcondition matches implementation.
//
// ============================================================================
persistent actor BoundedIncrement {
public func inc(n : Nat) : async Nat
entry_requires n < 1000;
requires n < 1000;
ensures result == n + 1;
ensures result <= 1000;
{
n + 1
};
}
Generate golden files:
./update_goldens.sh test/viper/level1/bounded_increment_sat.mo
Verify:
cd test/viper/level1 && ../../run.sh -v bounded_increment_sat.mo
Complete Example: UNSAT Test
Create test/viper/level1/bounded_increment_unsat.mo:
// ============================================================================
// Sector9 Verification Test
// ============================================================================
//
// Test Name: Bounded increment (wrong bound)
// Level: level1
// Expected Outcome: UNSAT
//
// Purpose:
// Demonstrate postcondition failure with wrong bound.
//
// Why This Should Fail:
// Claims result < 1000, but n=999 produces result=1000.
//
// ============================================================================
persistent actor BoundedIncrementUnsat {
public func inc(n : Nat) : async Nat
entry_requires n < 1000;
requires n < 1000;
ensures result == n + 1;
ensures result < 1000; // Wrong: fails when n = 999
{
n + 1
};
}
Complete Example: REJECT Test
Create test/viper/level3/pure_old_reject.mo:
// ============================================================================
// Sector9 Verification Test
// ============================================================================
//
// Test Name: Pure function with old (rejected)
// Level: level3
// Expected Outcome: REJECT
//
// Purpose:
// Verify that old() is rejected in pure function contracts.
//
// Why This Should Be Rejected:
// Pure functions cannot use old() - they have no state context.
//
// ============================================================================
module {
var state : Nat = 0;
public pure func bad(x : Nat) : Nat
ensures result == old(state); // Error: old() not allowed in pure
{
x
};
}
Running Individual Tests
Verification Only
XDG_CACHE_HOME=/tmp/sector9 ./bin/sector9 --verify test/viper/level1/my_test_sat.mo
Generate Viper Code
XDG_CACHE_HOME=/tmp/sector9 ./bin/sector9 --viper test/viper/level1/my_test_sat.mo > output.vpr
With Counterexamples
XDG_CACHE_HOME=/tmp/sector9 ./bin/sector9 --verify --counterexample test/viper/level1/my_test_unsat.mo
Counterexample output requires the Silicon/Viper server JAR to be available.
If the local VIPER_SERVER path is missing, plain --check and --viper can
still help narrow frontend and translation issues.
Test Report
After running ./test-viper.sh, check test/viper_test.txt for results:
=== level1 ===
✅ [m] bounded_increment_sat.mo - verified OK
✅ [m] bounded_increment_unsat.mo - postcondition might not hold...
Columns:
- Semantic status:
✅pass,❌fail - Golden status:
[m]match,[!]mismatch,[ ]missing - Filename and outcome summary
Troubleshooting
Test fails unexpectedly
- Check the suffix matches your intent (
_satvs_unsat) - Run with
--counterexampleto see failing inputs - Add intermediate
assertstatements to locate the issue
Golden files out of date
./update_goldens.sh test/viper/<dir>/your_test.mo
Permission errors
Ensure reads/modifies clauses cover all accessed fields. See
Permission Errors.
Test times out
Default timeout is 120 seconds. For complex tests:
VERIFY_TIMEOUT_MS=300000 ./test-viper.sh
Best Practices
- One concept per test - Test a single verification feature
- Include both SAT and UNSAT variants - Prove both success and failure detection
- Use descriptive names -
array_bounds_check_sat.monottest1.mo - Document the test - Headers explain purpose and expected behavior
- Keep tests minimal - Smallest code that exercises the feature
- Use appropriate level - Match test complexity to directory
Summary
- Place tests in the appropriate
test/viper/directory with an expected-outcome suffix - Use
./update_goldens.shto generate expected outputs - Run
./test-viper.shto verify all tests pass - Include header comments documenting test purpose
- Create both
_satand_unsatvariants for thorough coverage