diff --git a/src/datamodel/node/data/Simulink.ValueType.md b/src/datamodel/node/data/Simulink.ValueType.md index 1901c0b..5898846 100644 --- a/src/datamodel/node/data/Simulink.ValueType.md +++ b/src/datamodel/node/data/Simulink.ValueType.md @@ -6,8 +6,9 @@ **MATLAB class:** `Simulink.ValueType` **Editable in our UI:** yes (Name, Description, Min, Max, Complexity, DimensionsMode) **Verified against:** MATLAB R2027a (probe_class('Simulink.ValueType')) -**Partly unverified:** the Min / Max / Complexity / DimensionsMode unlock is verified -in-process only. There is no MATLAB re-open gate for those four — see "Open questions". +**Re-open gate:** RUN and PASSING as of 2026-09-16 — MATLAB opens a `.sldd` we wrote and +reads Min / Max / Complexity / DimensionsMode back equal, in both formats. See +"Open questions" for what that does and does not settle. ## Overview @@ -142,11 +143,26 @@ produce a file MATLAB will not load. ## Open questions / deferred -- **MATLAB re-open gate for the newly editable properties — NOT RUN.** Min, Max, - Complexity and DimensionsMode are verified in-process (serialize → re-parse) only. No - MATLAB was available where the unlock was made, so there is no PASS to record that - MATLAB loads the written file and reads the values back. Same status as the - `Simulink.BusElement` enum unlock, and worth running in the same pass. +- **MATLAB re-open gate — RUN, PASS (2026-09-16, R2027a Prerelease 27.1.0.3393633).** + Min, Max, Complexity and DimensionsMode were verified end to end: this package wrote the + `.sldd`, MATLAB opened it, and MATLAB read every value back equal to what was set — + **in both formats**, text JSON and compressed-binary. Cases live in + `test/parity/matlab/writeback.live.test.ts` as `VALUE_TYPE_CASES`; run them with + `env DEX_MATLAB_CMD=" matlab" DEX_MATLAB_CWD="" npx vitest run + test/parity/matlab/writeback.live.test.ts`. + + Two things the gate settles that no in-process test could: + + - **`Min` is written as 0, not dropped.** The case deliberately uses a zero bound, the + value a truthiness gate loses. MATLAB reads back `0`, so the bound reaches the file as a + real number rather than an omitted key. + - **The enum casing is MATLAB's own.** `'complex'` lower-case and `'Variable'` + capitalised both load. This was the standing risk: MATLAB refuses the other spelling of + each, so a writer that normalised the case would have produced a file MATLAB cannot + open — a failure invisible to every test that reads back through our own parser. + + The `Unit` property is not in the gate because it is not editable here (its schema + override is not `projected`); it is read-only and passes through untouched. - **Dimensions stays read-only**: a positive double vector with a symbolic-char alternative. Left a label for the reason `BusElementNode` records — the constraint has diff --git a/test/busElementEnumEdit.test.ts b/test/busElementEnumEdit.test.ts index 06097c0..9a49a4f 100644 --- a/test/busElementEnumEdit.test.ts +++ b/test/busElementEnumEdit.test.ts @@ -20,12 +20,14 @@ // taught to copy it across, an edit was stored, displayed, and then dropped on // save — an unlock that changes nothing in the file. // -// NOT covered here, and not covered anywhere yet: that MATLAB reopens the file and -// reads these values back. That is the live tier -// (test/parity/matlab/writeback.live.test.ts, which now carries a case per -// property), it is gated on DEX_MATLAB_CMD, and it has never been run against this -// change — there is no MATLAB on the machine it was written on. Everything below is -// in-process only. +// NOT covered here: that MATLAB reopens the file and reads these values back. That is +// the live tier (test/parity/matlab/writeback.live.test.ts, which carries a case per +// property), gated on DEX_MATLAB_CMD. It has now been RUN — 2026-09-16, R2027a +// Prerelease, PASS for both properties in both .sldd formats. So the casing below is +// no longer an assumption: MATLAB itself accepts 'complex' lower-case and 'Variable' +// capitalised, and the `illegal` near-misses really are the values it refuses. +// Everything in this file is still in-process, by design — it is the tier that runs +// without MATLAB. import { describe, it, expect } from 'vitest'; import { readFileSync } from 'node:fs'; diff --git a/test/parity/matlab/writeback.live.test.ts b/test/parity/matlab/writeback.live.test.ts index 7bb359f..e431f24 100644 --- a/test/parity/matlab/writeback.live.test.ts +++ b/test/parity/matlab/writeback.live.test.ts @@ -16,11 +16,14 @@ // 29/30/42), shape (defect 25 and Phase 6), element order (Phase 11), and the non-finites. // // The second array, ELEMENT_ENUM_CASES, covers the two bus-element enum properties item 13 -// made editable. Those cases have NEVER BEEN RUN: they were written on a machine with no -// MATLAB, so their expectations state what the write is supposed to produce, not what MATLAB -// was observed to produce. Whoever first runs this file with DEX_MATLAB_CMD set is the one -// who finds out — and if MATLAB disagrees, the case is right and the writer is wrong until -// proven otherwise. +// made editable; the third, VALUE_TYPE_CASES, the Simulink.ValueType value properties. +// +// RUN AND PASSING as of 2026-09-16 — MATLAB R2027a Prerelease 27.1.0.3393633, all 32 cases +// in this file, both .sldd formats. Until then the element-enum cases had never executed +// (written on a machine with no MATLAB) and the ValueType cases did not exist, so their +// expectations stated what the write was SUPPOSED to produce. They now state what MATLAB was +// observed to produce. If a future run disagrees, the case is right and the writer is wrong +// until proven otherwise. // // Skipped wholesale when DEX_MATLAB_CMD is unset, so CI and external contributors stay // green. Set it to the launcher plus its fixed args, e.g. @@ -146,6 +149,44 @@ const ELEMENT_ENUM_CASES: Array<{ prop: string; field: string; set: string; why: }, ]; +// The Simulink.ValueType value properties this project unlocked alongside the element enums. +// Same reasoning as ELEMENT_ENUM_CASES, one fixture over: MyValueType in params.sldd, whose +// bounds MATLAB left empty and whose enums it wrote as 'real' / 'Fixed'. +// +// Grouped two-per-case rather than one-per-property on purpose. Each `it` costs a MATLAB +// launch, and verify_roundtrip.m already prints PASS/FAIL per KEY, so grouping halves the +// wall clock without losing attribution: a Min that came back wrong names itself in the +// output whether or not Max shared the launch. +// +// Min is 0, not a tidy 5. A zero bound is the value a `|| ''` fallback or a truthiness gate +// silently drops, and it is indistinguishable from "no bound" everywhere except here — so if +// the write path ever regresses to a truthy test, this is the case that fails. +const VALUE_TYPE_CASES: Array<{ + tag: string; + edits: Array<[string, string]>; + expect: Record; + why: string; +}> = [ + { + tag: 'bounds', + edits: [ + ['Min', '0'], + ['Max', '100'], + ], + expect: { Min: 0, Max: 100, __class__: 'Simulink.ValueType' }, + why: 'the bounds that used to render blank in both panes — Min 0 is the falsy one', + }, + { + tag: 'enums', + edits: [ + ['complexity', 'complex'], + ['dimensionsMode', 'Variable'], + ], + expect: { Complexity: 'complex', DimensionsMode: 'Variable', __class__: 'Simulink.ValueType' }, + why: "MATLAB's own non-uniform casing: 'complex' lower, 'Variable' capitalised", + }, +]; + // Each case launches MATLAB, so every `it` below needs an explicit timeout: vitest's // default is 5s and reports the whole tier as timeouts, which hides whatever MATLAB was // about to say. Measured over a full run of this file, 24 launches took 516s — ~21s each @@ -153,7 +194,14 @@ const ELEMENT_ENUM_CASES: Array<{ prop: string; field: string; set: string; why: // start pays for the licence checkout and MATLAB's own startup on top of the work. Hence // 120s and not the 60s the other live suites use: they make one or two launches, this one // makes two dozen, and only its first is slow. -const MATLAB_TIMEOUT = 120_000; +// +// Raised to 300s after measuring a second machine (2026-09-16), where MATLAB lives on a +// NETWORK sandbox rather than a local install: the genuinely cold launch took 276s, and +// warm launches settled at 33s. 120s would have failed the first test of the run and +// reported it as a timeout, hiding whatever MATLAB was about to say — which is the exact +// failure this constant already exists to avoid. The steady-state cost is unchanged; a +// ceiling only matters when it is hit. +const MATLAB_TIMEOUT = 300_000; // Both flavours: the two writers spell a typed value independently, which is how defect 30 // survived the fix for defect 29. @@ -223,5 +271,26 @@ for (const format of ['json', 'binary'] as const) { expect(out).toMatch(/RESULT PASS/); }); }); + + VALUE_TYPE_CASES.forEach((c) => { + it(`MATLAB reads back our edits to MyValueType ${c.tag} — ${c.why}`, { timeout: MATLAB_TIMEOUT }, () => { + const uri = 'test://wb-vt-' + format + '-' + c.tag + '.sldd'; + const model = loadModel(format, 'params.sldd', uri); + const node = entryByName(model, uri, 'MyValueType'); + for (const [prop, value] of c.edits) { + expect(node.setProperty(prop, value), prop).toBe(true); + } + const bytes = serializeModel(model, format); + // In-process first, so a failure here is legibly ours and not MATLAB's. Read the node + // FIELDS rather than a pane, because the claim is about what reached the file. + const reparsed = reparseEntry(bytes, format, 'params.sldd', 'MyValueType'); + for (const [key, want] of Object.entries(c.expect)) { + if (key.startsWith('__')) continue; + expect(reparsed[key], `${c.tag}/${key} in-process`).toBe(want); + } + const out = matlabAssertRoundTrip(bytes, 'MyValueType', c.expect); + expect(out).toMatch(/RESULT PASS/); + }); + }); }); }