Testing Strategy¶
OpenSysML uses a multi-layer test contract to keep parsing, semantic analysis, and execution correct and to catch regressions in each.
Test Organization¶
tests/
├── parser/
│ ├── golden_test.go # Golden AST snapshots
│ ├── negative_test.go # Malformed input handling
│ └── testdata/parse/ # Test fixtures + goldens
├── wasm/
│ ├── wasm_build_test.go # Both WebAssembly targets compile, vet and link
│ └── wasm_run_test.go # The linked modules run and do real work
internal/
├── exec/
│ └── 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
└── workspace/
└── libs/
└── stdlib_conformance_test.go # Standard library gate
Parser Test Contract¶
New grammar features require a four-layer test contract:
1. Conformance Gate¶
Purpose: Ensure standard library continues to parse cleanly
- Test:
TestStdlibConformance(internal/workspace/libs/) - Coverage: 106/106 bundled library files — the 94 official SysML v2 standard library files and twelve non-normative OpenSysML extensions
- Acceptance: All stdlib files parse without errors
- Allowlist:
testdata/stdlib_known_failures.txt(currently empty)
2. Golden AST Snapshots¶
Purpose: Verify AST structure matches expected output
- Test:
TestGolden(tests/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:
Coverage 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
3. Round-Trip Serialization¶
Status: Explicitly deferred (no faithful SysML printer exists)
Future work: If SysML printer added, verify parse(print(parse(input))) == parse(input)
4. Negative Test Suite¶
Purpose: Verify parser rejects malformed input gracefully
- Test:
TestNegative(tests/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
Behavioral Test Contract¶
New behavioral features (actions, states, calc, constraints, requirements) require a four-layer test contract:
1. Golden AST Fixtures¶
Purpose: Lock in parse structure before execution changes
- Location:
tests/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.sysml
- state_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.sysml
- calc.sysml, calc_defaults_and_invocation.sysml, calc_return.sysml, calc_return_parameter.sysml
- constraint_assert_assume.sysml
- requirement.sysml, requirement_members.sysml
2. Execution Conformance Gate¶
Purpose: Verify behavioral execution produces expected outcomes
- Test:
TestExecutionConformance(internal/exec/runtime/) - Format:
.sysml+.expected.jsonpairs - Schema:
internal/exec/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, perform reference 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
3. Golden Execution Traces¶
Purpose: verify how execution proceeds (ordering, scheduling), not only the final result
- Test:
TestExecutionTrace(internal/exec/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:
4. Runtime Robustness Tests¶
Purpose: Verify malformed/pathological behaviors fail gracefully
- Test:
TestRuntimeRobustness(internal/exec/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
- perform of a missing action, perform reference cycle
Unit & Integration Tests¶
- Unit tests: Per-package coverage (lexer, parser, semantics, runtime)
- Integration tests: End-to-end workspace/REPL scenarios (internal/workspace/model/)
- Test fixtures:
testdata/*.sysml,testdata/*.kerml - Golden files: Expected parse/resolve/diagnostic outputs
- WebAssembly gate:
tests/wasmcompiles and vets the whole tree for both WebAssembly targets, links each command and runs it under Node —make wasm-check, whereOPENSYSML_REQUIRE_WASM=1(which both CI systems set) turns an absent runtime into a failure rather than a skip
Run all tests:
Run tests with coverage:
Contributing New Features¶
Grammar Features¶
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
Behavioral Features¶
When adding execution support for actions, states, calc, constraints, requirements:
- ✅ Add golden AST fixture to
tests/parser/testdata/parse/(if not already covered) - ✅ Implement semantics in
internal/exec/runtime/(executor or evaluator) - ✅ Add conformance case:
.sysml+.expected.jsonininternal/exec/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/syntax/parser/ ./internal/exec/runtime/
Test Coverage Policy¶
- 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.