Merged
Conversation
2eb0353 to
d2bb460
Compare
- Four-method contract: InitialState, Commands, RunCommand, Invariant - Commands returns state-dependent IEnumerable<Strategy<TCommand>>, enabling the engine to draw applicable commands per step - Invariant throws on violation; returns normally for valid states - Full XML doc on all members per ADR-0015
- StateMachineRun carries the executed step sequence, failure step index, final state, and a Passed convenience property - ExecutedStep is a readonly record struct pairing post-command state with a human-readable command label for use in failure output
…tion - Draws a bounded length integer then loops, inserting a CommandStart sentinel before each command draw so the shrinker can identify command boundaries in the IR stream - Stops early when Commands() returns empty or Invariant() throws; records the failure step index on the run - Adds IRNodeKind.CommandStart = 7 and supporting IRNode/ConjectureData plumbing for the new sentinel node kind
- Adds StateMachineRunner.Execute — a pure function that takes a machine and a pre-drawn command list, runs each command, checks invariants, and returns a StateMachineRun with steps and failure index - StateMachineStrategy.Generate now draws commands into a list, then delegates all execution and step recording to StateMachineRunner
- StateMachineRunner.Execute now captures the invariant exception alongside the run result; StateMachineStrategy throws a wrapped exception with a formatted step sequence when the run fails - StateMachineRunner gains FormatSteps to produce the failure message, listing each executed step with a ✗ marker at the failure point
- Scans IR nodes for CommandStart sentinels to locate command span boundaries, then removes the last command span and decrements the length integer to keep replay consistent - Returns false when fewer than two commands are present (nothing to truncate) or when the truncated candidate is no longer interesting
- TryBinaryHalve: removes the second half of the command sequence in one step (faster convergence than removing one at a time) - TryDeleteOne: tries removing each command span individually; skips deletions that make the sequence non-interesting, enabling state-dependent shrinking - Both share a new BuildPrefixCandidate helper; TryDeleteOne composes ShrinkHelper.WithoutInterval + Replace instead of reimplementing them - Updated the existing single-step test to use a minimum-count predicate, since TryDeleteOne can now delete the last command when the predicate allows it
- Adds CommandSequenceShrinkPass to tier 0 alongside ZeroBlocksPass, DeleteBlocksPass, and IntervalDeletionPass - Updates ShrinkerPassOrderTests: pass count 10→11, tier-0 count 3→4 - Adds ShrinkIntegrationTests: end-to-end shrink of a state machine that fails at state >= 3 converges to exactly 3 steps
- Formats failing runs as pseudocode: InitialState declaration, RunCommand per step, then Invariant annotation at the failure point - StateMachineStrategy static ctor auto-registers the formatter with FormatterRegistry so CounterexampleFormatter picks it up automatically
- Verifies StateMachineFormatter is auto-registered when StateMachineStrategy is instantiated (via FormatterRegistry) - Verifies formatter output contains step-sequence format: "state = InitialState();" and "// <- fails here" - Verifies seed reproduction line appears in the failure message - Verifies ShrinkCount is positive after a shrinkable failure
- BuggyStackMachine plants an off-by-one ModelCount decrement on Pop; three tests verify Conjecture finds the failure, shrinks to exactly 2 commands ([Push, Pop]), and records ShrinkCount > 0 - CorrectStackMachine (no bug) verifies the full example budget passes without a counterexample - StateAwareStackMachine exercises state-dependent command availability: Pop only yielded when Stack.Count > 0; passing result confirms the engine never selects Pop on an empty stack - Commands is fixed-size (always [Push, Pop]) in the buggy/correct machines to avoid a stale OneOf-index shrinker artefact that arises when Commands size changes with state
- BuggyQueueMachine plants a value-independent off-by-one in Peek (returns front+1 when Count > 1), so [Enqueue(0), Enqueue(0), Peek] is the minimal 3-step counterexample after shrinking - CorrectQueueMachine verifies the passing path across 100 examples - StateAwareQueueMachine exercises state-dependent command availability: Dequeue/Peek only offered when Count > 0; an engine violation would throw immediately, causing the test to fail
- BuggyCounterMachine (Inc/Nop commands, fails when counter ≥ 3) provides a planted-bug machine whose minimal counterexample is [Inc, Inc, Inc] - MachineStrategy uses Gen.Compose to vary the step budget [5, 20], giving the shrinker runs of different lengths to reduce - Three invariants checked: monotone shrinking (step count never increases), failure preservation (shrunk run replays as Interesting), and idempotence (re-shrinking a fully shrunk result makes zero additional progress)
- StateMachineGeneration_Passing: 50-step sequences on a never-failing machine - StateMachineGeneration_Failing: same path with invariant violation detection - StateMachineShrinking_Short/Long: full find+shrink via TestRunner with fixed seed - Inline CounterMachine variants keep domain logic out of engine overhead
- Covers IStateMachine<TState, TCommand> implementation pattern - Shows annotated failure output, state-dependent Commands, shrinking, and seed reproduction - Registers the guide in the Guides toc
d2bb460 to
7a056d9
Compare
StateMachineStrategy draws sequence length from NextInteger(0, maxSteps). When the first failing example happened to land exactly on the minimum failing length (3 steps), the shrinker had nothing to reduce, leaving ShrinkCount = 0. With maxSteps=20 this produced a ~5.6% flake rate. Pins Seed = 1UL on the five affected tests. With this seed the first generated sequence is length 14 (maxSteps=20) or 26 (maxSteps=50), both well above the minimum, so ShrinkCount > 0 is always guaranteed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Core feature: IStateMachine<TState, TCommand> — an interface-based model for testing stateful systems by generating and shrinking command sequences.
Key capabilities: