OpenSysML uses a multi-layer test contract to keep parsing, semantic analysis, and execution correct and to catch regressions in each.
internal/core/
├── parser/
│ ├── golden_test.go # Golden AST snapshots
│ ├── negative_test.go # Malformed input handling
│ └── testdata/parse/ # Test fixtures + goldens
├── runtime/
│ ├── conformance_test.go # Execution outcome verification, under every policy
│ ├── trace_test.go # Execution ordering/scheduling
│ ├── explore_test.go # Every linearization within a budget
│ ├── robustness_test.go # Failure mode handling
│ └── testdata/conformance/ # Behavioral test cases
└── libs/
└── stdlib_conformance_test.go # Standard library gate
New grammar features require a four-layer test contract:
Purpose: Ensure standard library continues to parse cleanly
- Test:
TestStdlibConformance(internal/core/libs/) - Coverage: 99/99 bundled library files — the 94 official SysML v2 standard library files and five non-normative OpenSysML extensions
- Acceptance: All stdlib files parse without errors
- Allowlist:
testdata/stdlib_known_failures.txt(currently empty)
go test -v -run TestStdlibConformance ./internal/core/libsPurpose: Verify AST structure matches expected output
- Test:
TestGolden(internal/core/parser/) - Fixtures:
testdata/parse/*.sysmland*.kerml(one representative file per construct) - Goldens:
testdata/parse/*.golden(AST dumps) - Acceptance: Parse output matches golden file
Update goldens after intentional changes:
go test -run TestGolden -update ./internal/core/parserCoverage includes:
- Package/namespace declarations
- Part/attribute definitions and usages
- Connections and relationships
- Requirements and constraints
- State machines and transitions
- Actions (control flow, parameters, nested)
- Calculations and expressions
- Enumerations and metadata
Status: Explicitly deferred (no faithful SysML printer exists)
Future work: If SysML printer added, verify parse(print(parse(input))) == parse(input)
Purpose: Verify parser rejects malformed input gracefully
- Test:
TestNegative(internal/core/parser/) - Coverage: one subtest per malformed input — count in the measured counts
- Acceptance: Each case produces diagnostics (never panics)
Examples:
- Unclosed blocks
- Unexpected tokens
- Invalid syntax
- Incomplete behavioral members
New behavioral features (actions, states, calc, constraints, requirements) require a four-layer test contract:
Purpose: Lock in parse structure before execution changes
- Location:
internal/core/parser/testdata/parse/(behavioral fixtures) - Coverage: the behavioral fixtures among the whole set — count in the measured counts
- Acceptance:
TestGoldenpasses, AST dumps match expectations
Behavioral fixtures:
action_control_flow.sysml,action_if_branch_body.sysml,action_mixed_params.sysml,action_send_port.sysmlstate_full.sysml,state_transition_variants.sysml,state_call_trigger.sysml,state_def_region_pseudostate.sysml,state_defer.sysml,state_fork_join.sysml,state_history.sysml,state_timed_triggers.sysml,state.sysmlcalc.sysml,calc_defaults_and_invocation.sysml,calc_return.sysml,calc_return_parameter.sysmlconstraint_assert_assume.sysmlrequirement.sysml,requirement_members.sysml
Purpose: Verify behavioral execution produces expected outcomes
- Test:
TestExecutionConformance(internal/core/runtime/) - Format:
.sysml+.expected.jsonpairs - Schema:
internal/core/runtime/testdata/conformance/README.md - Allowlist:
known_failures.txt(currently empty) - Admissible outcomes: a case whose model admits several complete results lists them under
outcomes, each with anadmissiblecitation into the semantic oracle deriving why the library leaves the order open; the observed run must match exactly one. The case is also explored (explorepolicy): every listed outcome must be reached, nothing unlisted may be, and the exploration must complete within the case'sexploreBudget - Policy sweep:
TestExecutionConformanceUnderPoliciesruns the whole suite underdeclaredandseed:1. A case with noschedulepin was recorded under the default and must hold under any policy; one that differs was pinning a scheduling artefact and fails rather than being skipped. Pinningschedule: "reverse"declares the result one linearization, kept only until its admissible set is derived or the bug it pins is fixed - Design: scheduling policies, choice points and exploration
Coverage (all passing, by fixture prefix; counts in the measured counts):
- Calc: parameter binding, return values, defaults, inherited parameters, unary ops, qualified names, type coercion, body-local usages, statement bodies, nested and from-constraint invocation
- Action: token flow, outputs, nested invocation, send/accept, port communication,
performreference and shorthand, accept...then, flows, loops and decisions - State: simple, do behavior, concurrent do, transition effect, choice/junction/fork-join pseudostates, orthogonal regions and region pseudostates, shallow/deep history, deferred/undeferred events, call and timed triggers, signal discrimination/unmatched, self signal
- Requirement: require, subject, actor, assume, nested
- Instance, unit and quantity, constraint assert/assume/negation, satisfy, variation, redefinition, variant, feature chains, string operations, nested behaviors, element filters, ball-and-chain, and one each of attribute, connector, cubesat and view
go test -v -run TestExecutionConformance ./internal/core/runtimePurpose: verify how execution proceeds (ordering, scheduling), not only the final result
- Test:
TestExecutionTrace(internal/core/runtime/) - Format:
.trace.goldenfiles, one linearization each; a case withoutcomesowns a<case>.<policy>.trace.goldenper sweep policy beside the default one - Order constraints: a
.trace.orderfile states the partial order a trace must respect, onea < bper line — the first trace entry mentioningabefore the first mentioningb; labels the same entry first mentions are unordered, and a label no entry mentions fails. It is checked beside a golden, or instead of one where every linearization is admissible;TestTraceOrderViolationFailsproves a violated constraint fails - Determinism: Token sorting by ID, fixed event queue tie-breaking; each
choicethe run made is a trace line naming the alternatives and the one taken - Coverage:
.trace.goldenfiles for action, calc, state, constraint, accept and string execution
Trace format examples:
- Action:
step 1: token T1@node1, token T2@node2(sorted) - State:
entry: StateName [hasEntryAction],transition: From -> To [event],exit: StateName [hasExitAction] - Choice:
choice step 2: tokens 3@left, 4@right (unordered; took 4@right first)
Generate traces:
go test -run TestExecutionTrace -update-traces ./internal/core/runtimePurpose: Verify malformed/pathological behaviors fail gracefully
- Test:
TestRuntimeRobustness(internal/core/runtime/) - Coverage: one subtest per failure mode — count in the measured counts
- Acceptance: Typed errors, never panic, 60s timeout guard
Failure modes:
- Deadlocked action (join starvation)
- Decision with no satisfied guard
- State machine with dangling transition
- Sourceless accept...then written first in its body or after a member that is not a state
- Calc with unbound parameter, surplus or unknown-named arguments, no result, non-calc target, direct or mutual recursion
- Constraint referencing missing feature
- Step budget exceeded
- Fork/join misuse (branches sharing a region, join with one incoming branch)
- Region pseudostate with no satisfied guard, or a cycle
- Non-numeric time trigger
- Send that reaches only its addressee, accept of an unsent type, send through an unconnected port
- History outside a composite state, or without a record or default
- Defer of a non-deferrable trigger
- Non-terminating do behavior
- Call of an unhandled operation, call argument of the wrong type
performof a missing action,performreference cycle
go test -v -run TestRuntimeRobustness -timeout 60s ./internal/core/runtime- Unit tests: Per-package coverage (lexer, parser, semantics, runtime)
- Integration tests: End-to-end workspace/REPL scenarios (internal/core/model/)
- Test fixtures:
testdata/*.sysml,testdata/*.kerml - Golden files: Expected parse/resolve/diagnostic outputs
Run all tests:
go test ./...Run tests with coverage:
go test -cover ./internal/core/...When adding parser support for new SysML v2 constructs:
- ✅ Add representative example to
testdata/parse/*.sysml - ✅ Run
go test -run TestGolden -updateto generate golden - ✅ Verify
TestStdlibConformancestill passes (no regressions) - ✅ Add negative test case if construct has error conditions
When adding execution support for actions, states, calc, constraints, requirements:
- ✅ Add golden AST fixture to
internal/core/parser/testdata/parse/(if not already covered) - ✅ Implement semantics in
internal/core/runtime/(executor or evaluator) - ✅ Add conformance case:
.sysml+.expected.jsonininternal/core/runtime/testdata/conformance/ - ✅ Add golden trace case:
.trace.goldenfor ordering-sensitive features - ✅ Add robustness test for failure modes (deadlock, unbound params, missing refs)
- ✅ Update
docs/project/spec-compliance.mdwith semantic rule → implementation → test → status - ✅ Verify all tests pass:
go test ./internal/core/parser/ ./internal/core/runtime/
- Parser: Golden ASTs + negative tests + stdlib conformance
- Behavioral execution: Conformance + traces + robustness
- Semantics: Unit tests for resolution, type system, validation
- No coverage target: Quality over percentage (each feature has explicit test contract)
Rationale: Test contracts (what must pass) > coverage metrics (% lines hit). Each feature has defined acceptance criteria.