Skip to main content

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:

  1. Create a .mo or .sr9 file in an appropriate test/viper/ directory with the expected suffix
  2. Run ./update_goldens.sh test/viper/<dir>/your_test.mo to generate golden files
  3. Run ./test-viper.sh to 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:

SuffixMeaning
_satVerification must succeed
_unsatVerification must fail
_rejectTranslation to Viper must fail
_nopermMust 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
};
}

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/:

FileContent
my_new_test_sat.vprGenerated Viper code
my_new_test_sat.vpr.stderrTranslator diagnostics
my_new_test_sat.siliconVerification result (JSON)
my_new_test_sat.silicon.retReturn 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​

  1. Check the suffix matches your intent (_sat vs _unsat)
  2. Run with --counterexample to see failing inputs
  3. Add intermediate assert statements 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​

  1. One concept per test - Test a single verification feature
  2. Include both SAT and UNSAT variants - Prove both success and failure detection
  3. Use descriptive names - array_bounds_check_sat.mo not test1.mo
  4. Document the test - Headers explain purpose and expected behavior
  5. Keep tests minimal - Smallest code that exercises the feature
  6. 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.sh to generate expected outputs
  • Run ./test-viper.sh to verify all tests pass
  • Include header comments documenting test purpose
  • Create both _sat and _unsat variants for thorough coverage