Skip to content

Add property/fuzz/invariant tests; fix 2 fuzz-found bugs; restore Makefile (#152)#152

Merged
cep21 merged 2 commits intomasterfrom
property-fuzz-tests
Feb 28, 2026
Merged

Add property/fuzz/invariant tests; fix 2 fuzz-found bugs; restore Makefile (#152)#152
cep21 merged 2 commits intomasterfrom
property-fuzz-tests

Conversation

@cep21
Copy link
Copy Markdown
Owner

@cep21 cep21 commented Feb 28, 2026

Summary

Three layers of new test coverage, two bug fixes they surfaced, and build tooling.

Test infrastructure

  • circuit_property_test.go — invariant-asserting stress tests + deterministic state-machine property test (testing/quick + MockClock + trivial reference model). Verified to catch the PR Fix 8 bugs: TOCTOU races, panics, and missed cases from PR #150 #151 TOCTOU bugs by temporarily reverting the CAS fix.
  • faststats/fuzz_test.go — 5 Go-native fuzz targets + naiveRollingCounter differential oracle. Seed corpora run as unit tests in ~5ms; active fuzzing via make fuzz.
  • Tightened existing stress testsTestCircuitStateTransitionRace and friends were t.Logf-only; now assert real invariants (closed ≤ opened ≤ closed+1, counter balance). Deleted 2 permanently-skipped tests.

Bug fixes (found by fuzzers in ~10 seconds)

  • Percentile(NaN) panicked — NaN falls through both p<=0 and p>=100 guards (NaN comparisons are always false), then int(math.Floor(NaN)) = INT64_MIN on amd64 → index-out-of-range. Now returns -1.
  • RollingCounter.UnmarshalJSON accepted hostile JSON with NumBuckets > len(Buckets), then GetBuckets panicked. Now rejected at unmarshal time with a descriptive error.

Build tooling

  • Makefile restored (deleted Jan 2024). make = fast dev loop; make ci = what GitHub Actions runs (build + -race -count 10 + lint); make fuzz FUZZTIME=30s; make help for the rest.
  • .github/workflows/fuzz.yml — daily scheduled fuzzing, 5min/target, uploads crash inputs as artifacts on failure. Manual dispatch enabled.
  • build.yml now calls make build / make test-race (same commands, now DRY).
  • README Development section updated; fixed dead Makefile link.

Test plan

  • make ci → all pass, ~61s (CI was ~60s before — negligible overhead)
  • golangci-lint run → 0 issues
  • Verified TestCircuit_TransitionAlternation_Concurrent catches the pre-PR#151 CAS/TOCTOU bug by reverting the fix locally
  • Ran each fuzzer 10s → clean after fixes applied
  • Existing TestRollingCounter_UnmarshalJSON_RoundTrip still passes (new validation doesn't break valid Marshal output)

…efile

## Test infrastructure

- circuit_property_test.go: invariant-asserting stress tests + deterministic
  state-machine property test (testing/quick + MockClock + reference model).
  Verified catches the TOCTOU bugs from PR #151 by temporarily reverting CAS.
    - TestCircuit_RunMetricsExactlyOnce_Concurrent: verifies the exactly-once
      RunMetrics contract under 100K concurrent Execute calls
    - TestCircuit_TransitionAlternation_Concurrent: verifies Opened/Closed
      alternate correctly (closed ≤ opened ≤ closed+1) under contention
    - TestCircuit_FallbackMetricsExactlyOnce_Concurrent: same for fallback path
    - TestCircuit_StateMachineProperty: 500 random op sequences replayed
      against real circuit + trivial reference model, 6 invariants checked
      per step, fully deterministic via MockClock

- faststats/fuzz_test.go: 5 Go-native fuzz targets + naiveRollingCounter
  differential oracle. Seeds run as unit tests in ~5ms.
    - FuzzRollingCounterOps: Inc/RollingSumAt/GetBuckets vs naive oracle
    - FuzzRollingCounterJSON: round-trip + hostile-input safety
    - FuzzSortedDurationsPercentile: bounds + monotonicity
    - FuzzRollingBucketAdvance: index bounds + LastAbsIndex monotonicity
    - FuzzTimedCheckJSON: round-trip + hostile-input safety

- Tightened existing stress tests (were assertion-free, just t.Logf):
    - circuit_stress_test.go: TestCircuitStateTransitionRace now verifies
      Opened/Closed alternation + ConcurrentCommands balance
    - faststats/rolling_stress_test.go: deleted 2 permanent-skip tests,
      added real TestTimedCheckConcurrency, added RollingSum≤TotalSum check

## Bug fixes (found by fuzzers in ~10s)

- faststats/rolling_percentile.go: Percentile(NaN) panicked — NaN falls
  through both p<=0 and p>=100 guards, int(math.Floor(NaN)) is INT64_MIN
  on amd64, index-out-of-range. Now returns -1.

- faststats/rolling_counter.go: UnmarshalJSON accepted hostile JSON with
  NumBuckets > len(Buckets), then GetBuckets panicked. Now rejected at
  unmarshal time with a descriptive error.

## Build tooling

- Makefile: restored after Jan 2024 deletion. make (fast dev loop),
  make ci (pre-PR gate mirroring GitHub Actions), make fuzz, make bench,
  make cover, make fix, make help.

- .github/workflows/build.yml: Build/Test steps now call make build /
  make test-race (same commands, now DRY).

- .github/workflows/fuzz.yml: daily scheduled fuzzing, 5min per target,
  uploads crash inputs as artifacts on failure. Manual dispatch enabled.

- README.md: Development section points to make targets. Fixed dead
  Makefile link.

- .gitignore: added /coverage.html (from make cover).
@cep21 cep21 enabled auto-merge (squash) February 28, 2026 02:35
TestRollingCounterBucketRolloverRace had a rollingSum < 0 check inside
the hot concurrent loop since PR #139/#150. This check was always wrong
for the lock-free RollingCounter design (documented at rolling_bucket.go):

  Thread A (Inc):      buckets[idx].Add(1)
  Thread B (rollover): Swap(0) returns the incremented value,
                       rollingSum.Add(-toDec) — briefly over-decrements
  Thread A (Inc cont): rollingSum.Add(1) — corrects

A reader between B and A's rollingSum write observes a transient negative.
The value is correct once writers quiesce.

Moved the < 0 check to after wg.Wait(). The rollingSum <= TotalSum check
stays in the hot loop (safe: reads are correctly ordered).
@cep21 cep21 merged commit 4333a6a into master Feb 28, 2026
6 checks passed
@cep21 cep21 deleted the property-fuzz-tests branch February 28, 2026 02:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant