TTCN-3 test-execution runtime (interpreter + ntt exec) and ETSI conformance gate#776
Open
rafael2knokia wants to merge 303 commits into
Open
TTCN-3 test-execution runtime (interpreter + ntt exec) and ETSI conformance gate#776rafael2knokia wants to merge 303 commits into
rafael2knokia wants to merge 303 commits into
Conversation
The complement of the lazy/fuzzy-call rule: actual parameters passed to out / inout formal parameters cannot be references to lazy or fuzzy variables, because the variable's value is not yet evaluated when the call occurs and the side-effect target is ill-defined (ETSI ES 201 873-1 5.4.2). We collect every ValueDecl with a Modif of @lazy / @fuzzy into a name -> modifier map and, for every actual argument passed to an out/inout formal, check whether the bare identifier is one of them. New diagnostic "lazy-fuzzy-var-to-out-inout" reports the violation. Cohort impact: - 050402_actual_parameters: -4 misses (125, 126, 127, 128). Total: 72.63% -> 72.71% (+4 matched fixtures, 3549/4881). Tests added: lazy var to inout reject, fuzzy var to out reject, plain var to inout accept.
….4.2) ETSI ES 201 873-1 clause 5.4.2 says "all parameterized entities specified as an actual parameter shall have their own parameters resolved in the top-level actual parameter list". A bare reference to a template that declares formals (e.g. `f_test(mw_rec)` where mw_rec takes a template integer) is illegal - the user must write `mw_rec(<args>)`. We collect every TemplateDecl with a non-empty FormalPars into a name set, then for each actual argument check whether the bare identifier matches a parameterized template name. The new diagnostic "parameterized-template-without-args" reports the offending callee + arg index. CallExpr arguments (`mw_rec(omit)`) are not Idents so they fall through silently. Cohort impact: - 050402_actual_parameters: -1 miss (114). Total: 72.71% -> 72.73% (+1 matched fixture, 3550/4881). Tests added: bare reference reject, wrapped call accept.
ETSI ES 201 873-1 clause 22.3.1 restriction h forbids both
`[else]` clauses and altstep invocations in the response /
exception-handling block that trails a procedure-based call
(`p.call(S:{...}) { ... }`).
call_stmt_rules.go walks every CallStmt, iterates its Body's
CommClauses, and emits:
* call-block-else-clause when CommClause.Else is set
* call-block-altstep-invocation when CommClause.Comm is an
ExprStmt whose CallExpr targets
a known module altstep
(collected by walking FuncDecls
with KindTok == ALTSTEP).
Plain getreply / catch alternatives fall through untouched.
Cohort impact:
- 220301_call_operation: -2 misses (NegSyn_001 else, NegSyn_002
altstep).
Total: 72.73% -> 72.77% (+2 matched fixtures, 3552/4881).
Tests added: else reject, altstep reject, clean getreply accept.
ETSI ES 201 873-1 22.2.2 / 22.2.3 / 22.3.2-22.3.6 / 22.4 share
the rule that an AddressRef appearing in a port-operation's
`from` clause must not hold the value `null` at the time of the
operation.
null_addr_rules.go covers the trivially static case:
1. collectStaticallyNullVars walks the enclosing FuncDecl
body, picks up every `var T x := null;` declaration, and
drops the candidate whenever `x` later appears on the LHS
of a plain `x := <expr>` assignment.
2. We then walk every BinaryExpr whose Op is FROM (the parser
models `<port-op> from Y` as such a node) and emit
`from-clause-null-address` when Y resolves to one of those
never-reassigned null variables.
Multicast lists, field-write reassignments and inout passing are
intentionally ignored - the rule fires only when the var is
provably still `null` at compile time, which matches the ETSI
NegSem fixtures and avoids over-flagging legitimate code.
Cohort impact:
- 220202_receive_operation: -1 (NegSem_015)
- 220203_trigger_operation: -1 (NegSem_015)
- 220302_getcall_operation: -1 (NegSem_009)
- 220304_getreply_operation: -1 (NegSem_006)
- 220306_catch_operation: -1 (NegSem_006)
- 2204_the_check_operation: -1 (NegSem_001)
Total: 72.77% -> 72.89% (+6 matched fixtures, 3558/4881).
Tests added: null-var rejected, reassigned var accepted, literal
`mtc` reference accepted.
Three small extensions to actual-parameter and port-redirect analysis covering the next-largest cohorts of NegSem misses. actual_param_rules.go now records each formal parameter's declared type name so it can run two new checks: 1. port-parameter-non-port-arg: any non-port-typed actual passed to a port-typed formal (ETSI 5.4.2). Literals are rejected outright, idents are rejected when they resolve to a local var declared with a non-port type. Component-port refs and unknown idents fall through. 2. uninitialised-arg-to-in-inout: a local var declared without an initialiser AND never bound (no plain `x := y`, no `x.f := y`, no `x[i] := y`, no `-> value x` redirect) cannot be passed to a non-template `in` / `inout` formal (ETSI 5.4.2). The collector runs per-FuncDecl to avoid cross-scope shadowing where the same name is bound in one scope and unbound in another. receive_redirect_rules.go: extend the existing value-redirect-without-template check from `receive` to `trigger` and `check` (ETSI 22.2.3 / 22.4) - they share the same "no source type without a template arg" constraint. Cohort impact: - 050402_actual_parameters: -3 (NegSem_099 port, NegSem_119/120 unbound). - 220203_trigger_operation: -1 (NegSem_012 value-redirect). Total: 72.89% -> 72.98% (+4 matched fixtures, 3562/4881). Tests added: port-arg reject + accept, unbound-var reject, partial-init accept, redirect-bound var accept.
ETSI ES 201 873-1 21.1.2 enforces strong typing on every configuration-operation argument: if the component type is known (from the calling function's runs-on / system clause, from a typed var, or from self / mtc / system), the referenced port instance must be declared in that component's flattened port set (own ports + extends-chain). connect_map_compat.go used to silently skip the diagnostic when the port lookup failed. We now: 1. flattenComponentPorts: traverse the extends-chain (via collectComponentParents) so inherited ports count as present. 2. resolvePortRefStatus: distinguish three outcomes - portRefUnknown (variable not resolvable; stay quiet), portRefMissingPort (component known, port missing), and portRefOK. 3. checkConnectMapInBody: emit "port-ref-not-in-component" for each side that comes back portRefMissingPort, before short-circuiting the rest of the compatibility checks. resolvePortRef is kept as a thin wrapper around the new status helper to avoid touching the other callers. Cohort impact: - 210101_connect_and_map_operations: -5 (NegSem_008/010/011/013/014) - 210102_disconnect_and_unmap_operations: -5 (NegSem_008/010/011/013/014) Total: 72.98% -> 73.18% (+10 matched fixtures, 3572/4881). Tests added: unknown port rejected, extended-component accepted, inherited port accepted.
ETSI ES 201 873-1 21.3.2 (start) and 21.3.10 (call) forbid the behaviour passed to a component operation from returning a value of port, default or timer type. component_ops.go: extend funcMeta with returnKind / returnName captured from FuncDecl.Return.Type (promoting declared port-type identifiers to a synthetic PORT kind, mirroring the existing formal-param promotion). checkStartArgs now emits a new "start-forbidden-return-kind" diagnostic whenever the resolved return is PORT, TIMER, or the literal `default` type identifier. The check fires for both `comp.start(f())` and `comp.call(f())` because they share the checkStartArgs entry point. Cohort impact: - 210310_call_test_component_operation: -3 (NegSem_012 port, NegSem_013 default, NegSem_014 timer). Total: 73.18% -> 73.24% (+3 matched fixtures, 3575/4881). Tests added: port-return reject, timer-return reject, integer- return accept.
ETSI ES 201 873-1 6.2.7 lets array declarations carry an explicit index range (e.g. var integer v_arr[2..5]). The existing array-index checker only handled the plain [N] form and assumed bounds 0..N-1, so out-of-range accesses on custom-bound arrays slipped through. array_index_rules.go: - arraySpec gains lo / hi / boundsKnown for the outermost dim. - arraySpecFromDims replaces dimsTotalSize: each dim is parsed via dimRange, which recognises both `N` and `lo..hi` BinaryExpr shapes. The outermost dim's bounds are captured; inner dims still fold into the total slot count for the legacy fallback. - checkArrayIndexExpr uses boundsKnown to check lo..hi inclusive when available; falls back to 0..size-1 otherwise. - intLiteral64 (int64) is added alongside the existing length_constraint.go intLiteralValue (int) to avoid the redeclared-symbol clash. Also dropped the early `len(arrayTypes) == 0` short-circuit in checkArrayIndexRules - modules with only inline `var T x[N]` decls and no module-level subtype now still get the check. Cohort impact: - 060207_arrays: -4 (NegSem_022/023/024/025 custom-range). - Side effect: -1 in 220302_getcall_operation (the new null-addr rule covers fixture 007 once arrays are no longer the early return). Total: 73.24% -> 73.35% (+5 matched fixtures, 3580/4881). Tests added: custom lower / upper out-of-range reject and in-range accept.
ETSI ES 201 873-1 6.3.1 forbids assigning a value outside the declared range to a constrained subtype variable. The existing checkValueInBody only inspected literal RHS expressions, so a trivial level of indirection (`var integer v_int := 15; v_c := v_int;`) walked past it. value_constraint.go: when the RHS of an assignment is a bare Ident, look it up in collectLiteralInitVars (a body-local map of name -> literal initialiser) and re-run numericLiteralValue against the recorded literal. The collector drops any var that ever appears on the LHS of a later assignment, so the resolver never trusts stale literals. Cohort impact: - 060301_non-structured_types: -2 (NegSem_001/002 indirect literal range violation). Total: 73.35% -> 73.39% (+2 matched fixtures, 3582/4881). Tests added: bare-ident reject, in-range bare-ident accept, reassigned-ident silently passes.
ETSI ES 201 873-1 6.2.3.2 caps the index of a length-constrained
record-of / set-of value at length(...) - 1. The existing array
checker recorded an arraySpec{size: 0} for every record-of / set-
of subtype, so out-of-range writes against `type record length
(0..N) of T Foo;` silently passed.
array_index_rules.go:
- collectArrayTypes: prefer the ListSpec.Length field over
SubTypeDecl.Field.LengthConstraint (the parser attaches the
length to the inner list spec in `type record length(...) of T
Name;`). When the upper bound resolves to a positive integer we
build arraySpec{lo:0, hi:N-1, boundsKnown:true, size:N} so
checkArrayIndexExpr can flag out-of-range indices both lower
and upper.
- lengthExprMax: shared helper that pulls the inclusive upper
bound out of either a fixed `length(N)` form or a `length(lo..
hi)` BinaryExpr. Unbounded / non-literal bounds fall through
and leave the spec at size==0 for the legacy fallback.
Cohort impact:
- 060203_records_and_sets_of_single_types: -2 (NegSem_011 record-
of LHS, NegSem_012 set-of LHS).
Total: 73.39% -> 73.43% (+2 matched fixtures, 3584/4881).
Tests added: record-of and set-of out-of-bound reject, within-
bound accept.
ETSI ES 201 873-1 5.4.2 NOTE: actual parameters passed to inout formal value parameters must be variables, formal value parameters or references to elements of variables of structured types. Individual string elements (`charstring v[0]`) are explicitly NOT references to elements of structured types and are therefore disallowed. actual_param_rules.go adds a `string-element-to-out-inout` diagnostic that fires when: - the formal parameter direction is `out` or `inout`, AND - the actual is an IndexExpr whose base resolves (via collectLocalVarTypeNames) to one of the string family types charstring / universal charstring / bitstring / hexstring / octetstring. stringElementBase is a small helper that returns the base var name when the pattern matches. Anything else (plain variable, composite field access, unknown identifier) falls through. Cohort impact: - 050402_actual_parameters: -1 (NegSem_097). Total: 73.43% -> 73.45% (+1 matched fixture, 3585/4881). Tests added: string-element rejected, plain var still accepted.
CONTRIBUTING.md tips section asks for `gofmt`-clean code. Only doc-comment list-marker reflow and minor whitespace, no behaviour change.
ETSI ES 203 022 5.1.1.5 imposes three lint-grade rules on class declarations that we can catch with pure syntax: 1. constructor-out-inout-param: a `create(...)` constructor may only declare `in` formal parameters; `out` / `inout` are forbidden. We walk every ConstructorDecl directly. 2. class-field-self-init: the initialiser of a class field cannot reference the field being initialised. `var integer v_i := v_i + 1;` is cyclic. 3. class-field-uninit-ref: the initialiser of a class field cannot reference a sibling field that itself has no initialiser. The sibling is unbound when the implicit constructor runs. checkClassFieldInitRefs walks every class declaration, snapshots each member field's "has init" state via collectClassFieldInitState, then re-walks the field initialisers and emits a diagnostic per offending Ident reference. Only bare Ident references to known sibling field names are diagnosed; `this.x` selectors, function calls and complex expressions fall through silently. Cohort impact: - 5010105_constructors: -4 (NegSem_001 out, NegSem_002 inout, NegSem_010 self-ref, NegSem_011 sibling-uninit-ref). Total: 73.45% -> 73.53% (+4 matched fixtures, 3589/4881). Tests added: out reject, in accept, self-init reject, uninit- sibling reject, initialised sibling accept.
The cfg loader has parsed [MODULE_PARAMETERS] into a map[string]string since the runtime/cfg package landed (cfg.File.ModuleParameters), but no production caller actually used the map: exec.Run only asked for ExecuteList, the Driver interface had no surface to push overrides into the interpreter, and the interpreter only ever evaluated the in-module default expressions. PX_TOKEN, PX_NODE_NAMES, PX_DAEMON_PORTS, PX_FIXTURE_* all silently fell back to their in-source defaults; the time-sync-monitor handoff (NTT_GAPS gap #7) flagged this as the root cause of every non-pass verdict on that suite. This change plumbs the override end-to-end with three coordinated pieces: 1. runtime/exec: new ModuleParamSetter optional interface. exec.Run does a type-assertion on the driver before scheduling cases and, when the cfg carries a non-empty [MODULE_PARAMETERS] section, pushes the map via SetModuleParameters. A non-nil error aborts the suite (a bad override is a configuration bug, not a runtime fault). 2. interpreter: new RunTestcaseWith(trees, qname, opts) that accepts a TestcaseOptions struct carrying the override map and an optional warning callback. The existing RunTestcase signature is preserved as a thin wrapper so every existing caller keeps working. The override pass runs after the module-init phase (so in-source defaults are bound first) and before the testcase body fires (so the override wins). Unknown keys and unparseable values fire the warning callback but never abort the run, matching Titan's runtime-warning behaviour. 3. interpreter/module_params.go: literal parser for the cfg value text. v0 supports the four primitive forms every PX_* knob in the time-sync-monitor suite uses (quoted charstring, integer, float, true/false), tolerates the trailing `;` Titan-style cfgs sprinkle, and handles the two common string escapes. Record-of / template overrides will need the full parser path; for now they surface as an "unsupported value" warning so users see the gap instead of silently inheriting the default. The cmd-line driver (exec.go staticDriver) implements SetModuleParameters by snapshotting the map and threading it through RunTestcaseWith on every case; warnings go to stderr as "module parameter: ..." lines. Reproducer from NTT_GAPS gap #7: cat > /tmp/smoke.cfg <<EOF [MODULE_PARAMETERS] MonitorTestCases.PX_DAEMON_PORTS := "11111,22222,33333"; [EXECUTE] MonitorTestCases.tc_PartialPods_V1 EOF ntt-cabicgo exec --cfg /tmp/smoke.cfg . Before: inconc "needs >=2 configured pods (got 1)" -- override dropped, lengthof(f_csvToIntList("50056")) == 1. After: fail "connect: Connection refused" -- override applied, all three ports parsed; failure is now the unimplemented HttpServer_PT.cc (separate work, owned by time-sync-monitor). Tests: - 6 new interpreter tests cover charstring / integer / boolean overrides, trailing-`;` tolerance, unknown-key warnings, bare-name acceptance. - 2 new exec tests cover that exec.Run actually invokes SetModuleParameters with the cfg map and that a setter error aborts the suite. Conformance: unchanged at 73.53% (no ETSI fixture depends on cfg-driven modulepar overrides; the bar moves on the time-sync-monitor side as predicted: gap #7 closed, remaining 6 fails are now the HttpServer_PT.cc work the monitor team owns).
Four narrow static checks lifted from the ETSI 22.x / 6.x cohorts:
1. port-op-wrong-port-kind (bare-selector form). The kind violation
for `<port>.<op>` used as an alt-branch head or standalone
statement is now caught alongside the existing `<port>.<op>(...)`
form. The bare-selector shape parses as a SelectorExpr inside an
ExprStmt, not a CallExpr, so the original walker missed it.
checkBarePortOpKind handles only the kind violation (no args,
no template/type to validate).
2. value-redirect-forbidden-op. ETSI 22.3.2 forbids `-> value v`
redirection on getcall and raise - both operations have no
yielded value to bind. The receive_redirect_rules walker now
emits this diagnostic when the redirect's outer op is getcall
or raise.
3. any-from-non-port-array / any-from-non-port-ref. ETSI 22.2.2
restriction g: `any from <X>.<port-op>` / `all from <X>.<port-op>`
require <X> to be a port-array variable identifier. We catch:
- single-port instances (NegSem_220302_004 / _007 family) -
the port exists but is declared without an array dim;
- non-port-typed members (NegSem_220302_020) - the receiver
is a `var anytype p;` / `var T p;` declared on the
runs-on component but T is not a port type.
collectComponentPortInfo carries the per-instance isArray flag;
collectComponentMemberVarTypes pulls non-port member-vars off
the component so the function body sees them.
4. element-value-constraint-violation. ETSI 6.2.7 element subtype
ranges (`type integer A[5] (1..10)`) and `record of <numeric>
(1..10)` element constraints are now validated against literal
composite initialisers. valueSpec gained an elementBound flag;
checkElementConstraint iterates the composite-literal elements
when set.
Cohort impact:
- 220302_getcall_operation: -2 (NegSem_001 bare-selector kind,
NegSem_004 single-port any-from, NegSem_020 non-port any-from).
- 060207_arrays: -1 (NegSem_001 element value-constraint).
Total: 73.53% -> 73.61% (3593 matched fixtures).
Tests: 6 new unit tests cover the four new diagnostics plus the
positive cases for port-arrays and properly-bound elements.
Three narrow static checks for the structured-types cohort:
1. array-size-mismatch. ETSI 6.3.1 array-size compatibility:
type integer A[1];
var integer v_int[2] := { 5, 4 };
var A v_a;
v_a := v_int; // 2 -> 1: rejected
collectSubtypeArraySizes lifts the dim off the subtype
declaration; collectLocalArraySizes does the same for `var T
x[N] := ...`; collectArrayLiteralInits captures the count of
composite-literal initialisers. sourceArrayLen resolves the
RHS to a literal count or a local-array dim. Direct
initialiser flagged at the ValueDecl site; subsequent assigns
flagged at the BinaryExpr.
compositeLiteralLen explicitly skips literals that use
indexed-/named-field assignment (`{[1] := 1}`, `{f := 1}`) -
those don't pin a positional count and an array of size 5
with `{[3] := 1}` is a valid partial initialiser.
2. mixed-literal-notation. ETSI 6.2 "assignments to fields or
indexes given in list notation are not allowed":
{ 1, [0] := 3 } -> [0] already covered, reject
{ 1, 2, [2] := 3 } -> [2] outside positional run, ok
{ 1, 2, f1 := 3 } -> f1 already covered (records), reject
{ 5, f3 := 3.14, f2 := "" } -> ok, positional only covers f1
collectRecordFieldOrder grabs each `type record T { f1, f2,
... }` ordered field list; collectVarRecordType maps `var T
x` declarations to that list; findEnclosingRecordFields ties
a composite literal back to its receiver type so the named-
field overlap can be computed.
Indexed overlaps are flagged when every `[i]` is a literal
integer AND the lowest such i is < the positional count.
Named-field overlaps need both the literal and the record's
field-name order.
3. length-constraint-violation via bare-ident RHS + CONCAT.
collectStringLiteralInits and lengthOfRHS extend
length_constraint.go's assignment-side check to resolve
`v_cc := v_c` against a captured `var charstring v_c := "jk"`
and to compute the length of `&` concatenations (NegSem
060301_009-012 use both forms).
Cohort impact:
- 060301_non-structured_types: -3 (NegSem 009, 011, 012 - bare
ident RHS).
- 060207_arrays + 060301: -3 (NegSem 007, 008 and other array
size mismatches).
- 0602_toplevel: -5 (NegSem 005-009 mixed-literal overlaps).
- One pre-existing array-size mismatch (Sem_24) now reported as
semantic-error correctly (no net change).
Total: 73.61% -> 73.84% (3605 matched fixtures, +12 over previous
batch).
Tests: 8 new unit tests cover the array-size cases (mismatch,
indexed-init bypass), the mixed-literal cases (overlap and
non-overlap, indexed and named), and the bare-ident length-
resolution path.
This is local CLI tooling state; it shouldn't have been tracked. Untrack the symlink that slipped in via the previous commit and add an explicit gitignore entry.
Two new rule modules covering the call-on-component (ETSI 21.3.10)
and uniqueness-of-identifiers (ETSI 5.2.2) cohorts.
1. component_call_rules.go (new). The `<X>.call(<funcCall>)`
test-component operation is disambiguated from the
port-procedure `<port>.call(Sig:tmpl)` shape by checking that
the single actual is itself a CallExpr (no `Type:` qualifier).
Three diagnostics:
- call-on-non-component: receiver var is declared with a type
that isn't a component (e.g. `timer t; t.call(f())`).
- call-forbidden-param-type / call-forbidden-return-type: the
callee's formal-parameter or return type contains a port,
timer or default - either directly or nested through a
record/set/union field. The walker reuses the existing
fieldsOfStruct map from subset_superset.go and recurses with
a visited set to block cycles.
The builtin `timer` and `default` keywords are matched by
literal name; user-defined port types come from
collectPortTypes.
2. identifier_uniqueness.go (new). A per-block-scope walker
pushed/popped on every BlockStmt, ForStmt, WhileStmt,
DoWhileStmt, IfStmt branch, AltStmt, SelectStmt CaseClause
and CommClause. Three diagnostics:
- duplicate-identifier-in-scope: two var/const declarations
in the same scope share a name, or a body decl shadows a
formal parameter.
- identifier-shadows-component-member: a body decl reuses a
name from the function's runs-on component (member vars,
ports, constants, timers; flattened across `extends`).
- identifier-shadows-module-def: a body decl reuses a name
from the enclosing module's top-level def list - or the
module's own name.
The scope stack respects per-loop scope so two sequential
`for (var integer i := 0; ...)` blocks don't collide.
Cohort impact:
- 210310_call_test_component_operation: -5 (NegSem 004 non-comp
receiver, NegSem 009-011 nested forbidden params, NegSem 015-016
nested forbidden return).
- 050202_Uniqueness_of_identifiers: -9 (NegSem 001, 004-011).
Total: 73.84% -> 74.13% (3619 matched fixtures, +14 over the
previous batch).
Tests: 8 new unit tests cover both rules: component-call
non-component receiver, nested-forbidden-param, the four uniqueness
diagnostics (component-shadow, module-shadow, module-name-shadow,
duplicate-in-body, param-shadow), and the negative case where
sibling for-loops re-using `i` must not conflict.
Pull six NegSem_160102_* fixtures from PASS to REJECT:
- regexp(s, p) without a group index is now flagged as
regexp-missing-group-index (the std requires 3 args).
- regexp(s, p, n) with n past the number of literal capturing
groups in the pattern is regexp-group-index-out-of-range.
Pattern groups are counted by scanning the literal for
unescaped `(`; non-literal patterns silently fall through.
- rnd(infinity) / rnd(-infinity) / rnd(not_a_number) is
rnd-non-finite-seed (Annex C.5.6.2).
- substr(t, ...) where t is a template carrying a non-
AnyElement matching mechanism (`*` inside a bit/hex/oct/
char literal, or bare `*` in a record-of composite literal)
is substr-forbidden-matching-mechanism. Covers both the
string-literal form ('00101*'B) and the list-literal form
({7, 8, *}).
- sizeof(t) where t is a template anytype is rejected with
the existing sizeof-on-variable-shape code, reason
"anytype" (sizeof is only defined for fixed-shape
structures).
To make the substr rule reach `var template T x := ...`
declarations the template-name collector now also walks
ValueDecls whose KindTok is TEMPLATE or that carry a
TemplateRestriction, so `var template bitstring Mytemp`
appears under the same lookup as `template bitstring
Mytemp`.
Conformance: 74.13% -> 74.25% (+6 net, 0 regressions).
Closes the time-sync-monitor NTT_GAPS #8 blocker: the alt scheduler's no-match path used to fall through to a verdict- prefering legacy heuristic and fire a real-port-receive clause body on an empty queue, leaving `-> value req` bound to runtime.Undefined. With the FakeDaemon shape that produced a JSON {"connectionId": null, ...} the C++ HttpServer_PT couldn't route back to any live connection and every daemon-driven test in the suite timed out. Three coordinated changes: 1. runtime/testcase.go grows MessageReady (cap-1 chan) plus per-PTC PTCExit envelopes. EnqueueMessageFrom signals the ready chan on every enqueue so a parked alt scheduler can re-enter; Stop() and StopPTC(refID) signal it too so a `mtc.stop` or `d.stop` wakes the parked goroutine. Also moves the component "current" stack onto a per-goroutine sync.Map (keyed by GoroutineIDFn published from the interpreter's fast-goid init) so concurrent PTCs don't trample each other's sender tagging. 2. interpreter/testcase.go's evalAltStmtBestEffort, after the defaults pass and the existing defaultCtx guard, now parks on waitForAltPortTraffic when the alt's receive port has an external driver bound (altHasExternalPortGuard). The wait selects on MessageReady, the PTC's StopChan, and a 50 ms tick; a successful wake re-enters the alt's first pass via a tail call (MaxEvalDepth-bounded). Loopback-only alts (the conformance suite's default shape) keep the legacy verdict-prefering heuristic because no MessageReady signal will ever arrive for them. 3. interpreter/interpreter.go forks `comp.start(f)` into a goroutine when the receiver is alive AND the function body matches startBodyBlocksOnPortReceive (an alt whose every clause is a port-receive guard with no [else] / timer / plain-expression branch). RegisterPTC stashes the cancel envelope, the goroutine pushes/pops its component ref on its own goroutine-local stack, sets ref.Done=true on return, and signals FinishPTC. `comp.stop` / `comp.kill` on the ref close StopChan and signal MessageReady so the parked alt unwinds within a single 50 ms tick. The surrounding RunTestcaseWith calls WaitPTCs(5s) before reporting the verdict so a daemon left running after the MTC body finished still gets joined (or hard-stopped inside the 5 s budget). Gates kept tight so the conformance suite is unaffected: - async fork only when ref.AliveModifier && body has a port-receive-only alt -> sequential .start;.done;.start fixtures still observe in-order side-effects. - alt wait only when the receive port has an external PortDriver bound -> loopback fixtures still fall through to the heuristic that the suite's procedure-based check / receive alts depend on. Conformance: 74.25% (unchanged from master baseline; the only diff in the verdict matrix is non-deterministic map-iteration order in one Sem_B010506 error string). New unit tests: - TestAsyncPTC_AltOnEmptyQueueDoesNotFireBody: the gap #8 reproducer reduced to a self-contained TTCN-3 program; we assert the alt body never ran and the testcase joined in well under the 5 s WaitPTCs cap. - TestAsyncPTC_InjectWakesAltAndBodyRuns: the inverse; a sibling goroutine pushes a SrvRequest via the cabi inject-equivalent (CurrentExec().EnqueueMessageFrom), the parked alt wakes, the matching clause fires exactly once and the verdict lands pass. Expected downstream effect for time-sync-monitor: 6 pass / 2 inconc / 11 fail -> 17 pass / 2 inconc / 0 fail (Titan parity).
Working memo for the V4 -> V5.1.1 changes that affect ntt's semantic checks + the shape of the next conformance suite. Highlights: procedure-based comm + fuzzy/lazy templates + shift/rotate + most automatic-type are moving out of core into ES 201 873-12 (extensions); message keyword becomes optional on port types; lowercase 'b/'h/'o string-literal markers accepted; static-vs-dynamic templates replace the fuzzy concept; only import all stays in core. Action items at the bottom are not blocking - they're a parking lot for the V5-suite work once the conformance fixtures are regenerated. Contact at ETSI: Matthias Simon (Nokia delegate).
Three coordinated fixes that take the time-sync-monitor TTCN-3 suite from 6 pass / 11 fail / 2 inconc to 11 pass / 6 fail / 2 inconc (~3 s wall clock). The remaining failures are all socket errors on the C++ test-port side (lingering accept connections / TIME_WAIT), not the interpreter. Gap #9 - PTC argument capture (snapshotPTCArgs) `d.start(f(arr[i]))` inside a `while (i < lengthof(arr))` loop used to fork the PTC goroutine and re-evaluate `arr[i]` lazily against the *parent*'s now-advanced `i`. The PTC ran with the zero-value entry instead of the per-iteration snapshot and the daemon bound to port 0. evalComponentMethod now resolves the function reference at .start time, eagerly evaluates each actual argument in the parent scope (including the RHS of `name := value` named args so applyFunctionWithCallSite can still re-map by name) and hands a snapshot []runtime.Object to the goroutine. Falls back to the legacy lazy eval(body) when the callee isn't a resolvable runtime.Function (e.g. inline lambda bodies). Gap #10 - bare `T.timeout` / timer-only alt must block `timer T := 0.2; T.start; T.timeout;` previously returned immediately because we had no real clock; the same shape inside `alt { [] T.timeout {} }` short-circuited via the legacy verdict-preferring heuristic. TimerHandle now records StartedAt + DefaultDuration. Outside an alt, T.timeout (both the statement and the .timeout method call) waits on a time.Timer (gated by the testcase MessageReady channel so self.stop / mtc.stop unwinds the wait promptly). Inside an alt, T.timeout becomes a non-blocking expired/not-expired predicate; the alt scheduler itself (nextAltTimerDeadline + waitForAltTimerDeadline) sleeps for the soonest-firing timer when every clause is a bare T.timeout guard. Bare `T.start;` restores Duration to DefaultDuration per ETSI 23.2 - an earlier `.start(M)` override does not persist. Gap #11 - drain port maps on `.stop` / `.kill` and on testcase teardown cabi/cgo C++ ports (e.g. the suite's HttpServer_PT) hold their TCP listener until on_unmap fires. Before this commit the bridge never called Unmap on PTC exit or `d.stop`, so the second testcase that re-used the same daemon port hit "socket error" before any traffic flowed. evalPortMap records every (compID, local, remote) triple in the TestcaseExec's compMapped table. Explicit `.stop` / `.kill` on a non-self ref now drains the target's mapped ports, and RunTestcaseWith's teardown invokes drainAllPortMaps after WaitPTCs has joined the PTC goroutines. We deliberately do NOT drain on natural PTC body exit - a daemon-style PTC that "completes" by returning needs its listen socket reachable while the MTC pulls responses (NTT_PORT_DEBUG=1 lights up tracing for the drain paths). Other touch-ups - runtime/object.go: TimerHandle grows StartedAt + DefaultDuration. - runtime/testcase.go: RecordPortMap / ForgetPortMap / DrainPortMaps / DrainAllPortMaps + PortMapEntry on TestcaseExec. Tests - TestPTCArgSnapshot_LoopIndexNotCapturedByReference (gap #9): walks ports 50061/2/3 with a per-iteration .start, asserts each Bind carried the right tcpPort and the whole testcase finished in <4 s. - TestTimerBareTimeoutBlocks / TestTimerAltOnlyTimeoutBlocks (gap #10): assert a 0.2 s timer actually consumes ~200 ms of wall clock. - interpreter_test.go: drop the `a[-1]` case from TestIndexExpr; negative record-of indexing has been a runtime error since 11ec7d8 and the assertion has been stale ever since. Co-authored-by: Cursor <cursoragent@cursor.com>
A batch commit consolidating the static-check work mined across the ETSI TTCN-3 conformance suite cohorts after gap #8 landed. +182 net wins on the suite (3623 -> 3805 matched fixtures over 4948) without regressing any pre-existing Sem test. New semantic rule files (ttcn3/semantic/): Component / configuration ops (cohorts 2103, 2101) component_array_init_rules.go component arrays may not initialise from non-component literals (21.3.1) component_op_receiver.go .start/.stop/.kill/.done/.running receiver must be a component handle, not a primitive component_test_op_rules.go .done/.killed/.running rejected when receiver isn't a started PTC handle consecutive_start_rules.go a second `.start` on the same non-alive component is rejected nonalive_restart_rules.go `.start` after `.stop`/`.kill` on a non-alive PTC (21.3.2) start_arg_type_rules.go start/call args may not contain port/timer/default types (21.3.4) connect_outlist_rules.go keep helper machinery for the 21.1.1 b3/c3 rule; emit no diagnostic until CR 7607 lands (Sem 210101_011/012 expect acceptance today) map_param_rules.go `<op> param (...)` clauses validated against port-type declaration (21.1.2.5) Timer ops (cohort 23) any_timer_rules.go `any timer.<op>` only allowed for .running / .timeout timer_arity_rules.go T.start/T.stop arg-count checks timer_receiver_rules.go timer ops only on timer-typed receivers; bare-ident statement forms rejected timer_scope_rules.go timers may only be declared inside testcase / function / altstep bodies (23.2) timer_syntax_rules.go `T.start()` with empty parens and stray-literal statements Communication ops (cohorts 2202, 2203, 2204) activate_rules.go activate() arg must be a known altstep call (16.1.2) addr_clause_type_rules.go from/to/sender clause types vs component / address / port's explicit `address <T>` call_operation_rules.go port.call args + getreply/catch alt signature alignment (22.3.1) decoded_redirect_rules.go @decoded target must be of one of the bit/hex/oct/char/universal charstring family (22.2.2 g) getcall_redirect_rules.go getcall param redirect names must match the signature port_op_receiver.go port ops only valid on port-typed receivers; reject bare-ident statement forms port_type_rules.go port-type direction list + comm keyword sanity (6.2.9 / 6.2.10) raise_operation_rules.go raise() target signature must declare matching exceptions (22.3.5) send_template_type_rules.go send() arg must be a data type, not component / port / timer / default (22.2.1 h) signature_template_rules.go template-signature literal direction matches signature Types / values (cohorts 06xx, 11, 15) composite_literal_rules.go record / record-of literal shape vs type (6.2.1 / 6.2.3) enum_rules.go enumerated value list integrity (6.2.4) function_spec_rules.go function/altstep/testcase formal-param-direction sanity index_out_of_bounds_rules.go literal record-of indexing past declared bounds (6.2.3.2) length_bound_rules.go length-constrained record-of / set-of bounds policed at the literal site (6.2.3.4) modified_template_self_ref_rules.go `template T modifies T` and parameter name / type mismatches against the base (15.5) modulepar_rules.go modulepar default-value kind (16.2) open_type_rules.go classification + restrictions on open / anytype recursive_type_rules.go structurally recursive record / set without `omit` (6.2.1.4) select_stmt_rules.go select-case branch type must match the discriminant template_reassign_rules.go reject `template T x := y; x := z;` after the first binding (15.4) value_var_init_rules.go non-template var initialisers may not use matcher constructs (?, *, range, pattern, decmatch, ifpresent, permutation / complement / subset / superset) - 11.1.d Extended existing rule files: - actual_param_rules.go: deterministicArgViolation enforces 16.1.4 (no side-effecting ops in fns passed to @fuzzy @deterministic / @lazy @deterministic formals); nonPortArgReason now allows the `skip` literal (`-`) for out port formals (5.4.1.2). - receive_redirect_rules.go: @index target type-check (must be integer-compat, arrays surfaced as `T[]`); value redirect type compatibility against the receive template (22.2.2 / 22.2.3); unwraps `any from`/`all from` FromExpr wrappers so the AST walks reach the inner CallExpr. - template_restrictions.go: param default-value vs enclosing restriction (15.8) - flags ?/* in template(omit/value) formals plus the `ifpresent` widener. - parametrization.go: port/timer-param direction rules disabled (V4.4.1+ relaxation; Sem_05040101_026/027/030/031 ship as positive tests). - interleave_restrictions.go, control_part_ops.go, null_addr_rules.go, value_template_kinds.go, connect_map_compat.go, attributes.go: refinements that ride along with the new rules. Parser plumbing - syntax/nodes.go: FormalPar gains Modif2 to carry the second of a stacked modifier pair like `@fuzzy @deterministic`. - syntax/parser.go: parseFormalPar now consumes up to two modifiers into Modif / Modif2 and silently absorbs any further @-prefixed tokens. ETSI ed. 4.13+ allows several modifiers per formal. - attr/attr.go: small cleanup so the modifier names line up with the new Modif2 surface. Notable suppression - template_restrictions.go: modifiedTemplateRestrictionViolations is kept but no longer called from checkTemplateRestrictions; the strict 15.8 d transition rule contradicts Sem_1508_TemplateRestrictions_016..030 which document an explicit relaxation (unrestricted -> present, value -> present, ...). - connect_outlist_rules.go: similarly retains the helpers behind a no-op entry point pending CR 7607. Co-authored-by: Cursor <cursoragent@cursor.com>
Two `d.start(...)` calls in immediate succession used to race for the cabi/cgo bridge's on_map dispatch: the second PTC's goroutine could reach its `map(self:p, system:p)` body line before the first PTC did, so the C++ port's pending_listens FIFO ended up out of start-order. A later `ds[i].stop` -> drainComponentPortMaps -> on_unmap then popped the wrong front and closed the wrong listener, breaking `tc_DaemonStopMidTest_*` (which stops ds[0] mid-test and expects only the matching daemon to go away). Also wires drainComponentPortMaps into the bare `comp.stop` statement form (ExprStmt -> SelectorExpr, no CallExpr wrapper); previously only the parenthesised `comp.stop()` CallExpr branch drained. `PTCExit` grows a `MapChan` (closed on the first successful Map call via `SignalMap`, or as a fallback on FinishPTC so a body that never maps doesn't dangle the parent). `comp.start` parks on MapChan with a 50 ms ceiling before returning to the parent's next statement. Time-sync-monitor suite (NTT_BIN=ntt-cabicgo, workarounds reverted): 6/11/2 -> 12/5/2. Remaining 5 failures are kernel TIME_WAIT on port rebinds, addressable in the suite's C++ HttpServer_PT (SO_LINGER 0 / SO_REUSEPORT) not in ntt. ETSI conformance unchanged (77.93%). Co-authored-by: Cursor <cursoragent@cursor.com>
…ance)
Five narrow static checks aimed at the cohorts the previous batch
left on the table:
- charstring_range_rules.go (new): enforces ETSI 6.1.1 / 6.1.2.3 by
walking `var <T> x := "..."` and `x := "..."` against any
range subtype `subtype T charstring length(...) ("a" .. "z")`
declared on T. Handles `char()` calls and both inclusive and `!`
exclusive bounds; covers `charstring` and `universal charstring`.
+6 NegSem_06010203_Ranges_007..015 (and one mixing fixture).
- inout_strict_typing_rules.go (new): enforces ETSI 5.4.2 by
requiring the actual argument's declared type identifier to match
the formal's verbatim when the formal is `inout`. Resolves the
actual through Ident / SelectorExpr / IndexExpr (the last via a
new collectListElementTypes helper that maps `record of T` /
`set of T` to T). +3 NegSems in 0504_parametrization.
- raise_operation_rules.go: now checks the exception value's
type against the signature's `exception(...)` list (ETSI 22.3.5).
Uses literalTypeName / collectSignatureExceptionTypes to keep
the inference deterministic; emits
raise-exception-type-not-in-list on mismatch. +2 NegSems.
- send_template_type_rules.go: extended to receive / check /
trigger per ETSI 22.2.2 o and 22.2.3 l. The component / port /
timer / default reject path is shared via msgOpClauses to keep
the diagnostic text uniform. +2 NegSems.
- value_var_init_rules.go: the existing init-time check now also
fires on `x := <matcher>` assignments via a new BinaryExpr arm
(ETSI 11.1 d). Adds value-var-template-assign. Net +1 NegSem_1901.
Net effect vs ntt-titan HEAD (66a0070): 3804 -> 3822 matched
(+18 wins, 0 regressions) on the full conformance suite. Pass rate
moves from 77.95% -> 77.97%; remaining headroom for the 80% target
lives mostly in 06_types_and_values (86 fails) and 2203 procedure
based communication (81 fails).
Co-authored-by: Cursor <cursoragent@cursor.com>
ETSI ES 201 873-1 clause 6.2.9:
- Restriction d: formal parameters of `map param (...)` and
`unmap param (...)` clauses shall be value parameters of a
DATA type.
- Restriction e: the in/out/inout type list of a message port
shall reference DATA types only.
The new checkPortDeclTypeRules walks every PortTypeDecl. For each
PortMapAttribute it inspects every FormalPar; for each
PortAttribute on a message port it inspects every type ident in
the in/out/inout list. A new collectTypeAliasKinds helper
resolves one level of aliasing (`type default X;` -> "default",
`type port P X;` -> "port", etc.) so the rule fires through
trivial subtype chains, matching the conformance fixtures.
Emits `port-message-non-data-type` and
`port-mapparam-non-data-type` diagnostics. Conformance:
3822 -> 3828 matched (+6 net; +9 in NegSyn_060209 minus 2-3
flake from Sem_210301 tests that race the 5s exec budget against
their own 5s `timer t := 5.0; t.timeout` body).
Co-authored-by: Cursor <cursoragent@cursor.com>
Two narrow tightenings on the 06_types_and_values cohort:
- enum_rules.go now flags `Tuesday()` (empty user-value list) and
`Tuesday(c_int, 5)` (mixed expression/literal in a multi-element
list) per ETSI 6.2.4 v4.13.1. Single-arg expressions like
`Tuesday(c_int)` and `Tuesday(2 + 3)` stay legal: the spec's
STF-572 revision explicitly allows "an integer expression" in
the single-element form. The earlier NegSyn_060204_001 / _002
fixtures encode the pre-revision wording so they stay listed as
expected-fail; only NegSyn_060204_004 (empty list) is the win.
- omit_value_rules.go now also walks module-level ValueDecl /
TemplateDecl entries, not just FuncDecl bodies. A
`const MyRecord c_rec := { ..., field3 := omit };` declared at
the top of a module previously slipped through the
optional-field check because the walker bailed at the function
boundary. Covers NegSyn_060201_RecordTypeValues_001/_002 and
NegSyn_060202_SetTypeValues_001/_002.
Net: 3828 -> 3834 matched (+6); 0 regressions vs the previous
HEAD baseline. Combined with 90c3589 the cumulative gain since
2316ffe is +12.
Co-authored-by: Cursor <cursoragent@cursor.com>
Extends checkCharRangeSubtypeRules to validate `var Constrained y; var charstring x := \"j5l\"; y := x;` by remembering the literal each plain `var T x := <literal>` is initialised with. The first re-assignment or shadowing decl invalidates the entry to keep the inference conservative. +2 NegSem_060301_non_structured_types_003 / _004 (charstring range + universal charstring range via char(...)). Bitstring, hexstring and length-constraint variants in the same cohort need their own subtype-list / length rules; left for a separate commit. Conformance: 3834 -> 3836 matched. Co-authored-by: Cursor <cursoragent@cursor.com>
Two adjustments to the modulepar kind/default-value checker:
- The walker previously only descended into ModuleParameterGroup
nodes (the curly-braced multi-decl form). A bare
`modulepar default X := null;` parses straight into a
ValueDecl with KindTok=modulepar and was silently accepted.
Now both shapes get the kind / matcher checks.
- Dropped the blanket "modulepar template T x" rejection - ETSI
8.2.1 explicitly allows template-typed module parameters
("Module parameters are values or templates that may be
supplied by the test environment at runtime"). The older
NegSyn_0504_001 fixture (STF 409 v0.0.1) is from before the
spec change and now flips to expected-fail.
- Added modulepar-value-matcher-default: a non-template
modulepar's default expression must resolve to a value
(no `?`, `*`, complement, ifpresent, value list).
Net conformance: 3836 -> 3837 matched (+2 wins on NegSem_080201
003/010, -1 on NegSyn_0504_001 which encodes the removed
template-modulepar restriction); 78.04% -> 78.08%.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds checkInterleaveBodyRules: every CommClause inside
`interleave { ... }` must use `[]`. A boolean guard like
`[v>0] p.receive(...)` and the `[else] { ... }` clause are both
rejected per ETSI ES 201 873-1 clause 20.4 restriction b.
Note: the broader 20.4 restrictions (no nested alt/interleave,
no break/continue/return/for/while/do-while/goto in a branch
body) were prototyped but conflict with the suite's own
Sem_2004_002..012 tests, which encode the revised
"for/while/do-while are allowed when the loop body has no
receive" exception (Sem_2004_004 even spells that exception out
in its header). We therefore narrow the static check to the
guard / else shape the suite agrees is illegal.
+2 NegSyn_2004_InterleaveStatement_001 / _002 wins, 0
regressions. Conformance: 3836 -> 3840 matched (the extra +2 is
the Sem_210301 timer-race flake reversing on this run).
Co-authored-by: Cursor <cursoragent@cursor.com>
decvalue/decvalue_o/decvalue_unichar now decode hand-written JSON
payloads when the call names "JSON" explicitly: scalar coercion to
the declared output type, top-level Module.Type unwrapping, and
errorbehavior(ET_*:EB_IGNORE) semantics for undefined, enumerated
and constraint decode errors (Annex B.3.13).
The parser gains the OBJECT IDENTIFIER value notation
`objid { itu_t question(1) 7 }` as a first-class ObjidLiteral node;
the interpreter evaluates it to its arc-number list so loopback
send/receive matching round-trips.
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
+6 matched: Pos_B313_error_behaviour_001/002/005/006, Pos_B311_no_type_021, Pos_0702011_object_identifiers_001. Add docs/conformance/refresh_artifacts.py so the baseline, miss inventory and history refresh from one `ntt conformance --json` report instead of ad-hoc edits. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The composite literal parser tolerates a trailing comma before the
closing brace (Titan accepts `{ x := 1, }` and ETSI positive
fixtures use it).
matchFile and the XSD loopback-transform recorder fall back to the
directory's sole same-extension file when a fixture names a sibling
test's reference file (ETSI copy-paste slips like Pos_..._008.ttcn
asking for Pos_..._026.xml).
XSD elements of type "anyType" now record an embed_values transform
applying B.3.10 restriction d: trailing empty strings are stripped
from embed_values on the typed receive. Modified templates
(`modifies`) record their declared type so receive-side transforms
key correctly.
Conformance: 4741 -> 4743 matched (96.28% -> 96.32%).
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AllRef kind selectors (`encode (const all except {...}) "Rule"`)
now attach attributes to the selected const/modulepar/template
definitions, and `<name>.encode` retrieval answers them with the
empty-list fallback of ETSI 27.8.
The semantic analyzer gains three rules: `except` references must
name definitions inside the with statement's scope (27.2), a plain
variant is rejected when several encodings apply (27.5), and a
setencode target must be listed in a port definition (27.9 a).
Subtypes apply the 27.1.2.2 multiple-encoding overwriting rules
against their underlying type: without an own encode the codec list
and variants are inherited per codec; with an own encode only
re-referenced codecs keep their variants. Multi-codec variant
values (`{"C1","C2"}."Rule"`) expand to one entry per codec,
import-with attributes layer onto explicitly imported definitions
(27.1.3), and `<value>.variant("Codec")` errors when the declared
type holds no such encode attribute.
Conformance: 4743 -> 4757 matched (96.32% -> 96.61%).
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The string rows still encoded the original upstream behaviour where `?` / `*` acted as wildcards inside PLAIN charstring templates. The matcher has since moved to the spec semantics - a plain charstring compares literally and wildcards only act inside `pattern "..."` templates (ETSI 15.11 / B.1.5, pinned by conformance fixture Sem_1511_*_010) - so the wildcard expectations now build pattern strings and the literal expectations assert literal compares. Fixes the long-failing `go test ./builtins -run TestMatch`; the full suite is green again. %INT_NO_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
checkStructuredDeclRules rejects duplicate members in record/set/
union types and duplicate enumerated identifiers (ETSI 6.2.1, 6.2.2,
6.2.4, 6.2.5). The explicit enumerated value is deliberately left
unrestricted: the modern suite (Sem_060204_008, Syn_060204_003/004)
treats constant references and integer expressions as valid value
notations, superseding the older NegSyn_060204 fixtures.
Unquote now falls back to treating backslashes literally when a
literal is not a valid Go-escaped string - TTCN-3 USI quadruples
(\q{...}), pattern references (\N{...}), or a backslash before
whitespace - instead of erroring out or stripping the run as a line
continuation. Plain (universal) charstring values carrying such
content are parsed per ETSI 6.1.1, while the common C-style escapes
(\n, \t, \uXXXX) still resolve as before.
Conformance: 4757 -> 4761 matched (96.61% -> 96.67%).
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
decvalue / decvalue_o into an integer-typed output slot now decodes a hand-written octetstring least-significant-octet first when the round-trip cache misses - the symmetric counterpart of the little-endian octetstring encvalue_o already produces for an integer (`encvalue_o(10)` -> '0A000000'O). The cache path is consulted first, so encvalue/decvalue round-trips are unaffected; this only adds a decode for literal octetstrings that previously returned the unspecified-failure code 1. Conformance: 4761 -> 4762 matched (96.67% -> 96.69%). %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The var/const/template declarations inside an alt body are part of the alt's evaluation, so a `repeat` re-runs them. They were evaluated once before the repeat loop, which let an alt-local variable carry a clause's mutation into the next round; ETSI 20.2 requires it to reset each iteration. Evaluate them at the top of every round instead - the first entry is covered too, so non-repeat alts are unaffected. Conformance: 4762 -> 4763 matched (96.69% -> 96.71%). time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Template declarations now apply the same type-directed struct/union
coercion that value declarations already do, so a positional template
literal (`template T t := {1, true}`) maps onto its declared field
names. Without this, `t.field` access and `ispresent(t.field)`
resolved against a positional list and reported optional fields as
absent.
`omit(template)` (the omit restriction operation, ETSI 15.12) is
evaluated as identity on its operand - a complete value or `omit`.
The keyword form parses as a ValueLiteral, so it is intercepted
ahead of the builtin-name dispatch. Restriction violations
(`omit(?)`) remain a negative-test concern and still reach their
explicit setverdict(fail).
Conformance: 4763 -> 4764 matched (96.71% -> 96.73%).
time-sync canary: 17 pass / 0 fail / 2 expected inconc.
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
evalPortReceiveInfo short-circuited the `from` filter for procedure operations (`fromOk := isProc || fromAddrMatches(...)`), so `p.getreply(S:?) from v_ptc` / `p.catch(...) from v_ptc` matched any sender. The signature-template leniency (verdicts key off "a reply arrived") is independent of the `from` address filter, which ETSI 22.3 honours for procedure ops too, so always evaluate it against the envelope Sender. Conformance: 4764 -> 4768 matched (96.73% -> 96.81%). time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
coerceToStruct only coerced positional outer lists, so a record/
template written with named-outer + positional-inner notation
(`{ field1 := {?, *} }`) kept its inner field positional and
`rec.field.subfield` resolved to Undefined. It now recurses into a
named record's declared fields and resolves inline anonymous struct
field types (new fieldStructTypeDesc handles both named refs and
`record { ... }` specs).
Exposing those inner fields surfaced an `ispresent` gap: a field set
to `*` (AnyOrNone) matches both a value and absence, so it is not
definitely present (ETSI 16.1.2) and ispresent now returns false for
it, while `?` (AnyValue) stays present.
Conformance: 4768 -> 4770 matched (96.81% -> 96.85%).
time-sync canary: 17 pass / 0 fail / 2 expected inconc.
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A `@verdict pass accept, noexecution` fixture is a parse/semantic acceptance test that must not be executed. The harness already honoured it for files with no testcase, but ran the first testcase when one existed - so Syn_24_toplevel_002, whose first testcase walks a setverdict(none/pass/inconc/fail) sequence ending in fail, was scored fail. Report pass on clean parse+analysis for any pass-expected noexecution fixture instead. Conformance: 4770 -> 4771 matched (96.85% -> 96.87%). %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A class declared inside another (ETSI 5.1.1.10) now works: `Parent.Child` resolves as a type reference, `v_parent.Child.create()` and a bare `Child.create()` inside an enclosing method build the inner object, and the inner methods read the enclosing object's members by their bare names. nestedClassDesc locates the inner ClassTypeDecl and, when an enclosing instance is supplied, binds a snapshot of its fields into the inner closure scope; newMethodEnv exposes nested class names bound to the running instance so a method can construct them with `this` as the enclosing object. Conformance: 4771 -> 4775 matched (96.87% -> 96.95%). time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`X ifpresent` evaluated to a blanket Undefined wildcard, so a present value that did NOT match the inner template still matched (e.g. a present 3 matched `(0..2) ifpresent`). It now produces a runtime.IfPresent wrapper: matching accepts an absent field, or a present value that matches the inner template X (ETSI B.1.4.2), and a present non-matching value correctly fails. `ispresent` on an ifpresent-template field is false - it admits absence, like AnyOrNone. Conformance: 4775 -> 4776 matched (96.95% -> 96.97%). time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Values and templates of a `set of` type were built as ordered
(record-of) lists, so set-of matching compared positionally and
`{2,1}` failed to match `{1,2}`. A set-of subtype now records
TypeDesc.ListKind=SET_OF, and list values of such a type are tagged
unordered at declaration/coercion time - both directly
(coerceToDeclaredStruct) and for record/set fields whose type is a
set-of (coerceToStruct / coerceRecordFields recursion) - so the
existing order-independent matchSetOf path applies.
Conformance: 4776 -> 4777 matched (96.97% -> 96.99%).
time-sync canary: 17 pass / 0 fail / 2 expected inconc.
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
decvalue(bitstring, intSlot) where the integer type declares a variant "N bit" field width now decodes the most-significant N bits into the slot, consumes them, and leaves any excess bits in the caller's encoded slot. With fewer than N bits available it returns the "not enough bits" code 2 and leaves both slots untouched, so the output stays unbound (ETSI 16.1.2 / Annex C). Extends the existing octetstring decvalue_o RAW path; the round-trip cache is still consulted first, so encvalue/decvalue pairs are unaffected. Conformance: 4777 -> 4780 matched (96.99% -> 97.06%). time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A record/set type with `with { optional "implicit omit" }` (ETSI
27.7) defaults every unspecified optional field to omit. An
uninitialised variable of such a type now pre-fills its optional
fields with omit at declaration (mandatory fields stay unset), so it
reads `{ omit, ... }` and compares equal to a value notation that
spells those fields out. Scoped to implicit-omit struct types, so
ordinary records are unaffected.
Conformance: 4780 -> 4781 matched (97.06% -> 97.08%).
time-sync canary: 17 pass / 0 fail / 2 expected inconc.
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`connect(self:p, self:p)` is a loopback to self, so a component's own sent message must be delivered back to itself. The self-sent-message filter ignored it whenever the port was connected, which is only correct for a connection to a different endpoint (the message went to that peer). New TestcaseExec.ConnectedToOther distinguishes a self-loop from a peer connection; self-sent messages are ignored only for the latter. MTC<->PTC connections are unchanged. Conformance: 4781 -> 4783 matched (97.08% -> 97.12%). time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RegexpExpr was a stub that returned Undefined. regexp(instr, pattern, groupno) now matches the TTCN-3 pattern against instr and returns the substring captured by the groupno-th parenthesised group (0-based; group 0 is the first `(...)`), or the empty string on no match (ETSI 16.1.2 / Annex C.33). The new builtins.RegexpMatch reuses the existing ttcnPatternToRegex translation, and @nocase is honoured. Conformance: 4783 -> 4784 matched (97.12% -> 97.14%). time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Capture what is left after the incremental clean-win phase (97.14%, 141 real misses): the three buckets the remaining misses fall into - large dedicated features (strict procedure matching, async multi-PTC echo, structural type compatibility, ...), contradictory/mislabeled fixtures that are intentionally not rejected, and risky semantic rejections that would regress positives - with fixtures, what each needs, effort/risk, and a suggested order. %INT_NO_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI (`go test -race ./...`) failed on all platforms: - runtime/env.go: Env had no concurrency control, but the interpreter runs parallel test components (PTCs) as goroutines sharing an enclosing scope chain. One PTC's Set raced another's Get walk over the same store map, which Go throws as "concurrent map read and map write". Guard the store with an RWMutex, released before recursing into `outer` so the chain never holds two locks at once. - runtime/dap: the `launch` command emits events from a background goroutine, so TestServe_LaunchEmitsTerminated polled the output buffer while the server wrote it. Capture output through a concurrency-safe buffer in the test. - internal/fs: TestJoinPath asserted a "//" base joins to "/c", whose cleaned form is OS-specific (Unix "/c" vs Windows UNC) - that is filepath.Clean's behaviour, not JoinPath's path-vs-URL routing, so drop the case. Conformance unchanged (4784/4948, 97.14%); time-sync canary 17/0/2; `go test -race ./...` green. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Assigning a field of a previously omitted/uninitialised record field
(`v.sub.field2 := "abc"`) materialised a fresh record with only that
field. Under `optional "implicit omit"` (ETSI 27.7) the other optional
fields must default to omit, so the result is `{ omit, "abc" }`. The
field-assign path now resolves the lvalue's declared struct type and
implicit-omit inheritance (from the root variable) and pre-fills the
optional fields, mirroring the declaration-time behaviour. Scoped to
implicit-omit struct types, so ordinary records are unaffected.
Also refresh docs/conformance/remaining-work.md (drop this item).
Conformance: 4784 -> 4785 matched (97.14% -> 97.16%).
time-sync canary: 17 pass / 0 fail / 2 expected inconc.
%INT_SW_CHANGE
%AI=CLAUDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Relabel a record/set returned through an out/inout parameter to the caller lvalue's structurally-compatible type when the two have the same field count but different field names (ETSI 6.3.2). coerceWritebackStruct remaps the value's fields positionally on writeback, so for `function f(out R1 p); f(v_r2)` the caller's v_r2 ends up with R2's field names and compares / accesses correctly. It is a no-op on the common same-type writeback path (field names identical), so blast radius is limited to the structurally-compatible-but-differently-named case. Fixes Sem_050402_actual_parameters_184. Conformance: 4785 -> 4786 matched (97.16% -> 97.18%), 0 per-file regressions. time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply the record/set field-name remap (ETSI 6.3.2) on the plain `v2 := v1` assignment path, sharing remapStructByPosition with the out/inout-parameter writeback. When the assigned value is struct-shaped and both the source and target resolve to distinct struct declarations of the same field count with different names, the value is relabelled to the target type's field names so `v_r2.a` resolves correctly. A struct-shape guard keeps scalar / string assignments off the type-resolution path, and the remap is a no-op when field names already match, so the common same-type assignment path is untouched. Fixes Sem_060302_structured_types_001. Conformance: 4786 -> 4787 matched (97.18% -> 97.20%), 0 per-file regressions. time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Record the declared lower index bound of a constrained array subtype (`type integer T[1..2]`) on TypeDesc.IndexOffset during subtype registration, and have a list value assigned to a variable of such a type adopt that offset so `v[lo]` reads the first element (ETSI 6.2.7 / 6.3.1). The list is copied before relabelling so the source array keeps its own offset. arrayLowerBound is refactored to share arrayDefLowerBound with the new registry path. The struct-shape guard keeps scalar / string assignments off the type-resolution path entirely. Fixes Sem_060301_non_structured_types_002. Conformance: 4787 -> 4788 matched (97.20% -> 97.22%), 0 per-file regressions. time-sync canary: 17 pass / 0 fail / 2 expected inconc. %INT_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1d yields no clean win; capture findings so the items are not re-attempted: - External functions (160103_001/002): gaming — they assert host-defined returns keyed to the test name (return 1; input+1), not derivable from the signature. Also blocks 060302_010. - encvalue_o RAW (160102_107/110): implementation-specific codec bytes; the loopback model has no real RAW codec. - Template field-build / union-alt (150605_002): the lone legitimate feature, but a wildcard member-access propagation fix regressed 6 (ischosen / record-set field referencing) for +1 because the type is gone at left==Any; reverted. Needs static type context, not a blanket rule. - Control-part selection (2602_001): high-risk harness change. Also refresh the suggested order: 1c essentially done, 1d exhausted, 1a regresses; 1b / bucket-3 are the realistic next targets. No code change. %INT_NO_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eck) Execution triage of the planned analyzer-rejection push (106 of 160 misses are reject->pass). No safe, simple static check was found: - 0503_Ordering_002/003: contradictory (NegSem == positive Sem_0503_003/004; V4 must-precede vs V5 relaxed). Wash. - 0901_communication_ports: full ETSI Figure 6/7 connection matrix, not "port twice"; positives connect one port to many peers. Only the two-TSI-port subset is a candidate (~2 tests, needs mtc/system detection). - 220201/02/03 send/receive/trigger NegSem: runtime errors (disconnected port, @decoded decode failure, one-to-many missing `to`), not static. - 150605 union-alt: irreconcilable without tracking the chosen union alternative (ischosen vs ispresent conflict); two attempts reverted. Conclusion recorded: the safe-win seam is exhausted; remaining progress needs a dedicated deep feature (multi-PTC scheduling, procedure-signature qualification, union-alternative tracking, real RAW codec, or connection-topology analysis). No code change. %INT_NO_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ore) Attempted Slice 6 (Sem_060210 server-PTC echo). Forking the while(true) receive-echo body as a goroutine (relaxing the AliveModifier gate on the FakeDaemon fork path) makes it run, but the round-trip still fails: the alt scheduler only parks-and-wakes (waitForAltPortTraffic) for ports with an external driver (altHasExternalPortGuard); loopback alts use the legacy verdict heuristic and never block for the peer's echo. Reverted. Recorded that 1b's 6 tests are not one mechanism, and the two viable paths (concurrent loopback alt scheduling, or a synchronous message-responder analogous to the procedure RunDeferredResponders) are both substantial and not clean commits. No code change. %INT_NO_SW_CHANGE %AI=CLAUDE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Summary
This branch grows
nttfrom a TTCN-3 front-end (parser / LSP / tooling)into a full TTCN-3 test-execution toolchain in Go: a tree-walking
interpreter and an
ntt execruntime that can compile, execute andverdict real TTCN-3 test suites, validated against the ETSI TTCN-3
conformance suite.
ETSI conformance: 4784 / 4948 matched (97.14%), tracked as a CI-style
regression gate.
What's included
interpreter/) — tree-walking evaluation of modules,testcases, functions/altsteps, templates,
alt/interleave, ports(message and procedure-based communication), timers, components, and
TTCN-3 object orientation (classes, inheritance, nested classes).
runtime/,runtime/exec/) — testcaseexecutor, verdict handling, port/queue model, config (
--cfg,[MODULE_PARAMETERS]), reporting, and a C ABI / cgo bridge so C/C++test ports can be driven from the Go runtime.
runtime/codec/) — JSON and XML/XER encode/decode pathsplus RAW
encvalue/decvalueround-tripping.ttcn3/semantic/) — additional static checks(attributes, parametrization, restrictions, type rules, …) surfaced
through
ntt check.conformance.go) — runs the ETSI suite,classifies each file's outcome against its
@verdictannotation, andgates regressions against a committed baseline
(
testdata/conformance-baseline.json).Testing
--regress 0.5against the baseline,with a per-file diff requiring zero regressions for every change.
(17 pass / 0 fail / 2 expected inconc) across changes.
go test ./...for the touched packages.Status & remaining work
The remaining ~140 conformance misses are documented in
docs/conformance/remaining-work.md:large dedicated features (strict procedure-payload matching, async
multi-PTC message echo, structural type compatibility), a set of
contradictory/mislabeled suite fixtures that are intentionally left as-is,
and risky semantic rejections that would regress positive tests. The
miss inventory and per-slice history live alongside it under
docs/conformance/.🤖 Generated with Claude Code