Files
sanderling/internal/ltl/evaluator_test.go
T

116 lines
3.1 KiB
Go

package ltl
import (
"strings"
"testing"
"time"
)
func observe(formula Formula, count int) []Verdict {
evaluator := NewEvaluator(formula)
verdicts := make([]Verdict, 0, count)
for range count {
verdicts = append(verdicts, evaluator.Observe())
}
return verdicts
}
func TestPure_HoldsThenStays(t *testing.T) {
got := observe(Always(Pure(true)), 3)
for index, verdict := range got {
if verdict != VerdictHolds {
t.Errorf("step %d: got %v, want holds", index, verdict)
}
}
}
func TestPure_FalseImmediatelyViolates(t *testing.T) {
got := observe(Always(Pure(false)), 3)
for index, verdict := range got {
if verdict != VerdictViolated {
t.Errorf("step %d: got %v, want violated", index, verdict)
}
}
}
func TestThunk_TransitionFromHoldToViolate(t *testing.T) {
values := []bool{true, true, false, true, true}
step := 0
evaluator := NewEvaluator(Always(Thunk(func() (bool, error) {
current := values[step]
step++
return current, nil
})))
wantSequence := []Verdict{
VerdictHolds, // true
VerdictHolds, // true
VerdictViolated, // false — latches
VerdictViolated, // true after violation — still violated
VerdictViolated, // true after violation — still violated
}
for index, want := range wantSequence {
got := evaluator.Observe()
if got != want {
t.Errorf("step %d: got %v, want %v", index, got, want)
}
}
}
func TestEvaluator_StickinessAfterViolation(t *testing.T) {
state := true
evaluator := NewEvaluator(Always(Thunk(func() (bool, error) { return state, nil })))
if got := evaluator.Observe(); got != VerdictHolds {
t.Fatalf("step 1: got %v, want holds", got)
}
state = false
if got := evaluator.Observe(); got != VerdictViolated {
t.Fatalf("step 2: got %v, want violated", got)
}
state = true
if got := evaluator.Observe(); got != VerdictViolated {
t.Fatalf("step 3 (recovered state): violation should latch, got %v", got)
}
}
func TestEvaluator_TopLevelPureCountedAtEachStep(t *testing.T) {
got := observe(Pure(true), 2)
if got[0] != VerdictHolds || got[1] != VerdictHolds {
t.Errorf("bare Pure(true): %v", got)
}
}
func TestEvaluator_TopLevelThunkRespectsObservation(t *testing.T) {
state := true
evaluator := NewEvaluator(Thunk(func() (bool, error) { return state, nil }))
if got := evaluator.Observe(); got != VerdictHolds {
t.Errorf("expected holds, got %v", got)
}
state = false
if got := evaluator.Observe(); got != VerdictViolated {
t.Errorf("expected violated, got %v", got)
}
}
func TestDescribe(t *testing.T) {
formula := Always(Pure(true))
if got := Describe(formula); !strings.Contains(got, "Always") || !strings.Contains(got, "Pure(true)") {
t.Errorf("Describe wrong: %q", got)
}
thunk := Always(Thunk(func() (bool, error) { return true, nil }))
if got := Describe(thunk); !strings.Contains(got, "Thunk") {
t.Errorf("Describe(thunk) wrong: %q", got)
}
}
func TestObserve_PanicsOnUnknownFormulaType(t *testing.T) {
type unsupportedFormula struct{ Formula }
defer func() {
if recovered := recover(); recovered == nil {
t.Errorf("expected panic on unsupported formula type")
}
}()
reduce(unsupportedFormula{}, time.Now())
}