Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions src/datamodel/node/data/Simulink.ValueType.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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="<launcher> matlab" DEX_MATLAB_CWD="<sandbox>" 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
Expand Down
14 changes: 8 additions & 6 deletions test/busElementEnumEdit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
81 changes: 75 additions & 6 deletions test/parity/matlab/writeback.live.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -146,14 +149,59 @@ 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<string, unknown>;
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
// in the steady state, and the FIRST launch of a session took over 65s, because a cold
// 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.
Expand Down Expand Up @@ -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/);
});
});
});
}