Skip to content

Latest commit

 

History

History
315 lines (246 loc) · 7.96 KB

File metadata and controls

315 lines (246 loc) · 7.96 KB

Property-Based Testing Examples & Expected Output

Running Your First Test

Command

PROPTEST_CASES=10 cargo test test_fee_property --lib -- --nocapture

Expected Output

running 14 tests
test test_fee_property::prop_percentage_fee_never_negative ... ok
test test_fee_property::prop_fee_never_exceeds_amount ... ok
test test_fee_property::prop_fee_calculation_deterministic ... ok
test test_fee_property::prop_zero_amount_rejected ... ok
test test_fee_property::prop_negative_amount_rejected ... ok
test test_fee_property::prop_fee_scales_with_amount ... ok
test test_fee_property::prop_breakdown_arithmetic_valid ... ok
test test_fee_property::prop_breakdown_no_negative_components ... ok
test test_fee_property::prop_no_panic_on_extremes ... ok
test test_fee_property::prop_overflow_handled_gracefully ... ok
test test_fee_property::prop_large_amounts_handled ... ok
test test_fee_property::prop_minimum_amounts_valid ... ok
test test_fee_property::prop_boundary_amounts_valid ... ok
test test_fee_property::prop_fee_monotonic_increase ... ok
test test_fee_property::_property_testing_guide ... ok

test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Standard Testing Run

Command

cargo test test_fee_property --lib -- --nocapture

What Happens (Internally)

Each test property runs with 100 random cases:

Test: prop_percentage_fee_never_negative

Generated inputs (sample cases):
├─ Case 1: amount = 523,456,789, fee_bps = 250 → fee = 1,308,642 ✓
├─ Case 2: amount = 100, fee_bps = 0 → fee = 0 ✓
├─ Case 3: amount = 999,999,999, fee_bps = 10000 → fee = 999,999,999 ✓
├─ Case 4: amount = 1,500,000, fee_bps = 500 → fee = 7,500 ✓
├─ Case 5: amount = 100,000, fee_bps = 1 → fee = 10 ✓
... (95 more cases)
└─ All 100 cases PASSED ✓

Test: prop_fee_never_exceeds_amount

Generated inputs (sample cases):
├─ Case 1: amount = 1,000,000, fee_bps = 250 → fee = 2,500 ≤ 1,000,000 ✓
├─ Case 2: amount = 500,000,000, fee_bps = 100 → fee = 5,000,000 ≤ 500,000,000 ✓
├─ Case 3: amount = 100, fee_bps = 50 → fee = 0 (MIN_FEE) ≤ 100 ✓
... (97 more cases)
└─ All 100 cases PASSED ✓

Intensive Fuzzing Run

Command

PROPTEST_CASES=1000 cargo test test_fee_property --lib

Expected Statistics

  • Total test properties: 14
  • Cases per property: 1000
  • Total cases: 14,000
  • Estimated runtime: 4-6 minutes
  • Memory usage: ~200-400 MB

Sample Output

test result: ok. 14 passed; 0 failed; 0 ignored; 14,000 shrunk cases

Seed: 1234567890  # Reproducible seed for failures

Testing Overflow Scenarios

Test: prop_overflow_handled_gracefully

What it does:

  • Generates amounts from i128::MAX / 2 to i128::MAX
  • Tests fee calculation with these extreme values
  • Expects either:
    • Valid result (fee ≥ 0 and fee ≤ amount)
    • Error: ContractError::Overflow

Sample Cases:

// Case 1: Near max but valid
amount = 9,223,372,036,854,775,800
fee_bps = 250
Result: Ok(fee = 23,058,430,092,136,939)// Case 2: Would overflow
amount = i128::MAX
fee_bps = 10000
Result: Err(Overflow)// Case 3: Large but safe
amount = 1,000,000,000,000,000
fee_bps = 500
Result: Ok(fee = 5,000,000,000,000)

Testing Determinism

Test: prop_fee_calculation_deterministic

What it validates:

  • Same input always produces same output
  • Important for auditability and reproducibility

Example:

Run 1: calculate_platform_fee(500,000, None) = Ok(1250)
Run 2: calculate_platform_fee(500,000, None) = Ok(1250)
Run 3: calculate_platform_fee(500,000, None) = Ok(1250)
Result: ✓ PASS - Deterministic

Testing Fee Breakdown Consistency

Test: prop_breakdown_arithmetic_valid

Formula Validated:

amount = platform_fee + protocol_fee + net_amount

Example Case:

amount            = 1,000,000
platform_fee      = 2,500  (0.25%)
protocol_fee      = 0      (for simplicity)
net_amount        = 997,500

Verify: 2,500 + 0 + 997,500 = 1,000,000 ✓
FeeBreakdown::validate() = Ok(()) ✓

When a Test Fails (Hypothetical)

Failure Scenario

Imagine a bug where fees sometimes go negative:

thread 'test_fee_property::prop_percentage_fee_never_negative' panicked at 
'assertion failed: fee >= 0, 
  Fee -100 must be non-negative'

Proptest has shrunk the failing input to:
  amount = 500, fee_bps = 250

Seed: 0x1234abcd5678def0

This can be reproduced with:
  PROPTEST_REGRESSIONS=proptest-regressions/fee_property.txt \
  cargo test prop_percentage_fee_never_negative --lib

How to debug:

  1. Review the shrunk input (smallest failing case)
  2. Test manually: calculate_platform_fee(500, 250) should not return negative
  3. Review fee calculation logic
  4. Fix the bug
  5. Rerun the test - proptest will re-verify the previously failing case

Performance Metrics

Compilation Time (First Run)

Initial: 45-60 seconds (includes Soroban SDK)
Cached:  5-10 seconds (incremental builds)

Test Execution Time by Case Count

PROPTEST_CASES=10  → 2-3 seconds
PROPTEST_CASES=100 → 20-30 seconds  (default)
PROPTEST_CASES=500 → 2-3 minutes
PROPTEST_CASES=1000 → 4-5 minutes

Verbose Output Example

Command

PROPTEST_VERBOSE=1 cargo test prop_percentage_fee_never_negative --lib

Sample Output

proptest: Run set to execute with PROPTEST_VERBOSE=1

[1/100] Running: amount = 523456789, fee_bps = 250
  → Fee calculated: 1308642 ✓
  → Assert: 1308642 >= 0 ✓

[2/100] Running: amount = 100, fee_bps = 0
  → Fee calculated: 0 ✓
  → Assert: 0 >= 0 ✓

[3/100] Running: amount = 999999999, fee_bps = 10000
  → Fee calculated: 999999999 ✓
  → Assert: 999999999 >= 0 ✓

... (97 more cases)

[100/100] Running: amount = 1000000, fee_bps = 500
  → Fee calculated: 5000 ✓
  → Assert: 5000 >= 0 ✓

test result: ok. All 100 cases passed.

Edge Case Examples

Boundary Testing

// Tier boundary: 1000 * 10^7 = 10,000,000,000
test_amount_at_tier_boundary() {
    // Below boundary (Tier 1)
    amount = 9,999,999,999
    expected_bps = full_bps ✓
    
    // At boundary (Tier 2)
    amount = 10,000,000,000
    expected_bps = full_bps * 0.8// Well above boundary (Tier 3)
    amount = 100,000,000,000
    expected_bps = full_bps * 0.6}

Minimum Fee Testing

// When calculated fee is very small
amount = 100
bps = 1  (0.01%)
calculated_fee = 100 * 1 / 10000 = 0
applied_fee = max(0, MIN_FEE) = 1 ✓

Regression Testing

If a test fails, proptest saves the failing case:

File: proptest-regressions/fee_property.txt

# Regression test for prop_percentage_fee_never_negative
# Generated from version 1.0 at 2026-04-27T10:30:00Z
# Case 1: FAILED
prop_percentage_fee_never_negative(
    amount: 523456789,
    fee_bps: 250,
)

Run regression tests:

cargo test test_fee_property --lib
# Automatically runs all previously failed cases first

CI/CD Integration Example

GitHub Actions Workflow

name: Property-Based Fee Tests

on: [push, pull_request]

jobs:
  property-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
      
      - name: Run property-based fee tests
        run: |
          PROPTEST_CASES=500 \
          cargo test test_fee_property --lib -- --nocapture
      
      - name: Check for regressions
        if: failure()
        run: git diff proptest-regressions/

Summary

With property-based testing, you get:

450+ test cases automatically generated from strategies
Edge cases discovered that manual tests would miss
Deterministic failure reproduction via seeds
Regression prevention with saved failing cases
Confidence in overflows being handled correctly
Audit trail showing invariants validated

Next step: Run PROPTEST_CASES=10 cargo test test_fee_property --lib to see it in action!