Specification testing allows you to define requirements for a state machine (a protocol, rules, or regulations) and verify them. You define a specification using Spec.From(initialState) and then chain several types of definitions:
Action: Defines a transition. Includes a predicate for when the action is valid, the transition logic, and an optional weight for sampling.Rule: Defines a requirement that must hold. Uses when (the condition) and then (the expected outcome).Response: Defines a temporal requirement. It specifies a trigger condition and a response that must occur within a certain interval, checked per a specific action (e.g., per: "Tick").Never: Defines a negative requirement (e.g., "this should never happen").Reachable: Defines a goal state that must be reachable from the initial state.
Once defined, you can run the specification using different modes:
Exhaustive: Proves the requirements for the entire reachable state space (breadth-first).Sample: Performs random walks for large models.Faults: Injects defects to see if your requirements actually catch them (mutation testing for specs).Conform: Drives a real implementation through the same traces used by the specification to ensure the implementation matches the model.
Spec.From(State.Connected)
.Action("Recv", Inbound, (s, _) => s.Status != Disconnected, (s, m) => s.Inbound(m), weight: 30)
.Action("Tick", s => s.Status != Disconnected, s => s.Tick(), weight: 20)
.Rule("SEQ-TOO-LOW-FATAL",
"MsgSeqNum lower than expected without PossDupFlag set to Y is a fatal error: send a Logout and terminate.",
when: (b, a) => b.Up && a.RecvSeq == Seq.TooLow,
then: (b, a) => a.Put(Out.Logout) && a.Status == Disconnected)
.Response("LOGOUT-COMPLETES",
"The initiator of a Logout waits for the confirming Logout, and terminates anyway if it does not arrive.",
trigger: (b, a) => a.Status == LogoutSent && b.Status != LogoutSent,
response: (b, a) => a.Status == Disconnected,
within: Interval + 1, per: "Tick")
.Never("DISCONNECTED-SILENT", "No message is sent on a terminated connection.",
(b, a) => b.Status == Disconnected && a.Sent != Out.None)
.Reachable("CAN-LOG-ON", "A session can reach the logged on state at all.",
s => s.Status == LoggedOn);