From 6d4e9f562ad347cb0769f7fc7fd4d4de0daf39f3 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sun, 18 Oct 2026 22:28:24 -0400 Subject: [PATCH 1/4] Test the merge's read-back guard, the splice layout and four kept-parts rules S2's merge of an edited binary entry had six parts the suite never exercised: removing any one of them left every test passing. Each now has a case in binaryEditKeepsUntouched.test.ts, and each case fails with its part removed and with nothing else: - the read-back guard in BinarySlddSerializer.mergedEntry: a struct entry whose Name line a hand edit moved after its Value gains a field named Name. Merged, that field would come before the entry's own Name line, and the reader would pair the merge with no entry. The entry is written from the model whole, and the saved chunk reopens with every entry still paired with its bytes; - the layout spliceEntryObject passes on: a bus element added in a CRLF, two-space chunk is spliced as the same text as in the LF, four-space one, in the chunk's layout; - keptPartsViolations' rules for a child written twice or with no name, a child the model did not change left out, a property the reader does not read dropped or changed (D13), and a bus written with more or fewer elements than the model holds. Tests only; no source change. --- test/binaryEditKeepsUntouched.test.ts | 141 +++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 3 deletions(-) diff --git a/test/binaryEditKeepsUntouched.test.ts b/test/binaryEditKeepsUntouched.test.ts index 2f85ef69..041bb88a 100644 --- a/test/binaryEditKeepsUntouched.test.ts +++ b/test/binaryEditKeepsUntouched.test.ts @@ -19,11 +19,13 @@ // (D1); // * an edit and its undo give back the bytes with only the LastMod line new (D2); // * what cannot be merged with certainty is written from the model whole, as every edited -// entry was: markup the scan cannot read, a duplicate name, an origin left in the text; +// entry was: markup the scan cannot read, a duplicate name, an origin left in the text, and a +// merge that, read again on its own, is not the model's text; // * no origin the writers spell for the merge reaches a saved file; // * the merge of a 5 MB entry costs a small multiple of writing it from the model; -// * and the three functions a host that splices chunk text needs: spliceEntryObject, -// keptPartsViolations and readEntryFragment. +// * and the three functions a host that splices chunk text needs: spliceEntryObject, which +// lays what it writes from the model out as the chunk is, keptPartsViolations, each rule it +// holds a write to, and readEntryFragment. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { readFileSync, readdirSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; @@ -448,6 +450,48 @@ describe('what cannot be merged with certainty is written from the model whole', expect(out).toBe(regenerated(entry)); expect(out).not.toContain('dex-core-origin'); }); + + it('a merge that, read again on its own, is not the model\'s text: here, one that reads as no entry', () => { + // A struct entry with its Name line moved after its Value, as a hand edit may leave it, and a + // sibling MATLAB spelled `1.0E+6`. The reader reads both, and pairs each with its bytes: the + // first `

{ + const lines = object.split('\n'); + const name = lines.splice(1, 1)[0]; + lines.splice(lines.length - 2, 0, name); + return lines.join('\n'); + }; + const value = '

\n \n

1.0E+6

\n \n

'; + const xml = chunk(moved(entryXml('s', 'u-s', value)) + entryXml('t', 'u-t', '

1.0E+6

')); + expect(objectOf(xml, 's').indexOf('

c.name)).toEqual(['a', 'Name']); + const saved = savedChunk(session, root); + expect(objectOf(saved, 's')).toBe(regenerated(s)); + expect(objectOf(saved, 's')).not.toContain('1.0E+6'); + expect(objectOf(saved, 't')).toBe(objectOf(xml, 't')); + // Which keeps the saved chunk one the reader pairs: reopened, t, renamed, still keeps its + // bytes. Had the merge been written, no entry of the reopened chunk would have had any. + const again = fromChunk(saved); + expect(entrySource.entrySourceOf(entryNamed(again.root, 's'))?.fragment).toBe(objectOf(saved, 's')); + expect(entryNamed(again.root, 't').setProperty('Name', 'tRenamed')).toBe(true); + expect(objectOf(savedChunk(again.session, again.root), 'tRenamed')).toBe(renamedAndStamped(objectOf(xml, 't'), 'tRenamed')); + }); }); function binaryDictionaries(): string[] { @@ -586,6 +630,28 @@ describe('for a host that splices chunk text (D12)', () => { }); } + it('spliceEntryObject lays out what it writes from the model as the chunk is: a bus element added, CRLF and two spaces', () => { + // The same bus, in the fixture's LF and four spaces and in CRLF and two spaces, given the same + // new element: the element is written from the model, which writes LF and four spaces, so only + // the chunk's own layout, read off it, makes the two splices one text in two layouts. + const { xml: lf } = open(fixture('bus_types_binary.sldd')); + const spliced = (xml: string): string => { + const { session, root } = fromChunk(xml); + const bus = entryNamed(root, 'btBus'); + expect(session.addChildTo(bus.id)).not.toBeNull(); + expect(bus.children.length).toBe(5); + const out = core.spliceEntryObject(xml, bus); + expect(out).not.toBeNull(); + return out!; + }; + const inLf = spliced(lf); + const inCrlf = spliced(crlf(lf)); + expect(objectOf(inLf, 'btBus').split('').length - 1).toBe(5); + expect(inLf).not.toContain('\r'); + expect(inCrlf).toBe(crlf(inLf)); + expect(inCrlf.replace(/\r\n/g, '')).not.toContain('\n'); + }); + it('spliceEntryObject answers null for an entry the chunk does not hold', () => { const { root, xml } = open(fixture('cellarr_binary.sldd')); expect(core.spliceEntryObject(xml, entries(root)[0], 'noSuchEntry')).toBeNull(); @@ -614,6 +680,75 @@ describe('for a host that splices chunk text (D12)', () => { expect(core.keptPartsViolations(entry, regenerated(entry))).toEqual(['P[Value] (open tag)']); }); + // Each rule keptPartsViolations holds a write to, on a write that breaks only that one. A + // Simulink.Parameter with its Min edited, whose siblings the model did not change. + const parameter = (): { entry: any; written: string } => { + const value = [ + '

', + ' ', + '

1.0E-5

', + '

-1.0E-5

', + '

1.0E+10

', + '

kept

', + ' ', + '

', + ].join('\n'); + const { session, root } = fromChunk(chunk(entryXml('p', 'u-p', value))); + const entry = entryNamed(root, 'p'); + expect(entry.setProperty('Min', '-2')).toBe(true); + const written = objectOf(savedChunk(session, root), 'p'); + expect(written).toContain('

-2.0

'); + expect(core.keptPartsViolations(entry, written)).toEqual([]); + return { entry, written }; + }; + const MAX = '\n

1.0E+10

'; + const DESCRIPTION = '\n

kept

'; + + it('keptPartsViolations: a child written twice, or with no name, where the model writes each once', () => { + const { entry, written } = parameter(); + const twice = written.replace(DESCRIPTION, DESCRIPTION + DESCRIPTION); + expect(core.keptPartsViolations(entry, twice)).toEqual(['P[Value]/Element[0]: a child of it is written twice or cannot be named']); + const unnamed = written.replace(DESCRIPTION, DESCRIPTION + '\n

kept

'); + expect(core.keptPartsViolations(entry, unnamed)).toEqual(['P[Value]/Element[0]: a child of it is written twice or cannot be named']); + }); + + it('keptPartsViolations: a child the model did not change, not written', () => { + const { entry, written } = parameter(); + expect(written).toContain(MAX); + expect(core.keptPartsViolations(entry, written.replace(MAX, ''))).toEqual(['P[Value]/Element[0]/P[Max] (not written)']); + // The one the model changed may go: what is written there is the model's to say. + expect(core.keptPartsViolations(entry, written.replace('\n

-2.0

', ''))).toEqual([]); + }); + + it('keptPartsViolations: a property the reader does not read, not kept (D13)', () => { + const unread = '\n

kept as read

'; + const xml = chunk(entryXml('a', 'u-a', '

1.0E+6

').replace('\n

{ + const { xml } = open(fixture('bus_types_binary.sldd')); + const { session, root } = fromChunk(xml); + const bus = entryNamed(root, 'btBus'); + expect(session.deleteNodeById(bus.children[1].id)).toBe(true); + expect(bus.children.length).toBe(3); + const written = objectOf(savedChunk(session, root), 'btBus'); + expect(core.keptPartsViolations(bus, written)).toEqual([]); + const at = written.lastIndexOf(''); + const last = written.slice(written.lastIndexOf('\n', at), written.indexOf('', at) + ''.length); + expect(last).toContain('

matrix

'); + const path = 'P[Value]/Element[0]/P[Elements_internal]'; + expect(core.keptPartsViolations(bus, written.replace(last, ''))).toEqual([`${path}: written with 2 elements where the model holds 3`]); + expect(core.keptPartsViolations(bus, written.replace(last, last + last))).toEqual([`${path}: written with 4 elements where the model holds 3`]); + }); + it('readEntryFragment rebuilds an entry with the bytes it came from, as an undo that rebuilds from text does', () => { const { session, root, xml } = openOlderSave(fixture('cellarr_binary.sldd')); const old = entryNamed(root, 'cObjArr'); From 49e0b2fc23caea9f863cca9243d69a3c11772b67 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sun, 18 Oct 2026 22:43:01 -0400 Subject: [PATCH 2/4] Test that a .mat file's node ceiling is set back after its build MatNode.fromParsed arms the node ceiling for one .mat file's tree, and buildUnderCeiling sets it back in a finally. Nothing tested that: deleting the set-back, or moving it out of the finally, passed the whole suite. The new case opens a text dictionary, a binary dictionary and a model workspace, each holding values read out of MAT streams, before and after a .mat file whose ceiling is used up, and again after a build that throws once its ceiling is used up. Each must open as it did, with all its rows. With the ceiling left armed they open with their sparse arrays and cells shown without contents. Test only, no change to src. --- test/matNodeCeiling.test.ts | 46 +++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/test/matNodeCeiling.test.ts b/test/matNodeCeiling.test.ts index 575123ff..6e73b3e2 100644 --- a/test/matNodeCeiling.test.ts +++ b/test/matNodeCeiling.test.ts @@ -11,15 +11,17 @@ // // Every case opens the file through MatNode.fromParsed, the path addMatSource takes, twice: // once under the ceiling the case is about and once with no ceiling it can reach, which is the -// tree every file opened as before. What a cut tree shows is graded against that one. +// tree every file opened as before. What a cut tree shows is graded against that one. The last +// case opens other files after a .mat one, to see that the ceiling was armed for that file alone. import { describe, it, expect } from 'vitest'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { deflateSync } from 'node:zlib'; -import { createSession } from '../src/index.js'; +import { createSession, ingest } from '../src/index.js'; import MatNode from '../src/datamodel/node/container/MatNode.js'; import MatlabVariableNode from '../src/datamodel/node/data/MatlabVariableNode.js'; +import { buildUnderCeiling } from '../src/datamodel/node/data/matNodeCeiling.js'; import { parseMat } from '../src/datamodel/parser/MatParser.js'; import { encodeMatStream } from '../src/datamodel/parser/MatWriter.js'; import { MAT_NODE_CEILING } from '../src/datamodel/display/DisplayConvention.js'; @@ -368,4 +370,44 @@ describe('a .mat file past its node ceiling', () => { const value = JSON.parse(copied(big)).value; expect([value._dimensions, value._elements.length, value._elements[299_999]]).toEqual([[1, 300_000], 300_000, { v: 299_999 }]); }); + + it('is armed only while a .mat file is built: what opens after one used up, or after a build that threw, is whole', () => { + // matNodeCeiling arms the ceiling for one .mat file's tree and sets it back in a finally. + // Nothing else is built under it: a text dictionary's cdata and a binary one's hex, each a + // value read out of a MAT stream, and a model's workspace. Each holds sparse arrays, built + // with their rows as a .mat file's are. A ceiling left armed, used up, would build them + // with none. + // Each opened on its own: how many nodes, how many of them a MAT stream's value built with + // rows, and which have none (`_cutByCeiling`). + const others = (): [string, number, number, string[]][] => + ['sparse_text.sldd', 'sparse_binary.sldd', 'sparse_ws.slx'].map((name) => { + const u8 = new Uint8Array(readFileSync(fileURLToPath(new URL('fixtures/sparse/' + name, import.meta.url)))); + const source: Any = ingest(createSession(), u8.buffer as ArrayBuffer, { filename: name }); + const nodes = nodesUnder(source); + const withRows = nodes.filter((n) => n._matVar && n.children.length > 0).length; + return [name, nodes.length, withRows, nodes.filter((n) => n._cutByCeiling).map((n) => n.matlabPath)]; + }); + const before = others(); + for (const [name, , withRows, cut] of before) { + expect([name, withRows > 0, cut]).toEqual([name, true, []]); + } + + // A .mat file whose ceiling is used up: everything after its first variable has no rows. + const { root, warnings } = open(everyKind(), 21); + expect([shownWithoutContents(root).length, warnings.map((w) => w.code)]).toEqual([12, ['part-not-expanded']]); + expect(others()).toEqual(before); + + // A build that throws after it used up its ceiling: the throw reaches the caller, and the + // ceiling is not left armed. + const parsed = parseMat(everyKind()); + const thrown = (): unknown => + buildUnderCeiling(21, () => { + const node = new MatNode('threw.mat'); + MatNode._addVariables(node, parsed); + expect(shownWithoutContents(node).length).toBe(12); + throw new Error('the build failed'); + }); + expect(thrown).toThrow('the build failed'); + expect(others()).toEqual(before); + }); }); From 221fcc59d46e3daa510031acf7f84bdad9ff3660 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sun, 18 Oct 2026 22:51:52 -0400 Subject: [PATCH 3/4] Record the display conventions kept on purpose as deviations from MATLAB DESIGN.md still said whether the rest of the display follows MATLAB was pending the maintainer's rulings. The group B ruling of 2026-10-15 settled it: keep this project's display where it makes more sense, and fix only a display that could mislead. Replace that paragraph with the ruling and the kept list (the summary spelling, full precision, a Parameter's row, no separate edit text, a 1x1 cell's braces, the length guard, true/false, doubled quotes, comma-separated cells, a .mat VariantExpression's recorded class, and the binary and .mat int-backed enumeration empty), each with MATLAB's browser text and the reason. Narrow the empty-numeric row: an int-backed enumeration's empty is [ ] as a text dictionary holds it; a binary dictionary's and a .mat's keep their summary. --- test/parity/matlab/DESIGN.md | 38 ++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/test/parity/matlab/DESIGN.md b/test/parity/matlab/DESIGN.md index 69587c23..8bc4a6fd 100644 --- a/test/parity/matlab/DESIGN.md +++ b/test/parity/matlab/DESIGN.md @@ -41,7 +41,7 @@ can. | case | display | note | |---|---|---| -| empty numeric, any class, shape and complexity | `[ ]` | MATLAB's Workspace browser's own text, space inside (`mat2str` says `[]`). An int-backed enumeration's empty too: MATLAB counts it numeric. Not a sparse double's: see the next row | +| empty numeric, any class, shape and complexity | `[ ]` | MATLAB's Workspace browser's own text, space inside (`mat2str` says `[]`). An int-backed enumeration's empty too, as a text dictionary holds it: MATLAB counts it numeric. A binary dictionary's and a `.mat`'s is not `[ ]`: see the kept list below. Not a sparse double's: see the next row | | empty sparse double | `<0x0 sparse double>`, `<2x0 sparse double>` | **a deliberate deviation from MATLAB**, whose browser shows `[ ]` (measured at 0x0 and 2x0). See "The sparse empty keeps its summary" below | | empty cell | `{}` at 0x0, `<0x3 cell>` at any other empty shape | the browser's `{}` and `{0×3 cell}` | | a text dictionary's `"value": null` | `[ ]`, a 1x0 double, with no parse warning | what MATLAB reads the entry back as; MATLAB writes `null` for a value it could not save in a text dictionary (a 2x2x2 string), and opens the file again without complaint. It read as `0` (P8 of the 2026-10-16 list) | @@ -104,13 +104,35 @@ logical-backed enumeration. A complex scalar is its own class (`int16`, not `double`), and so is each element of a complex array. getWorkspaceDisplay's own Class (`complex double`) is a different spelling; the browser's is the one shown. -**Only the rows above that cite the browser follow it so far.** Whether the rest -follow MATLAB is pending the maintainer's rulings on numeric precision (P1), -the summary's spelling (Q1: MATLAB writes `0×3 string`, `{0×3 cell}`, with no -angle brackets), and H1-H6 (the Parameter row, the edit seed, 1x1 cells, char -control characters, length bounds), in the 2026-10-15 display study -(data-explorer-vscode `docs/deep-work/2026-10-15-decided-items/display-study/`, -`proposed-open-questions.md` and `not-followable-*.md`). +**The rest differs from the browser on purpose, as recorded deviations.** The +maintainer's ruling of 2026-10-15 (evening, group B, display alignment): "if our +display makes more sense than MATLAB, keep ours. For example, to be consistent, we +always use `` for summary, then keep it. Only fix our display that +could cause confusion to user." A display that could mislead (a wrong class, size, +shape or value, a value that looks like a different one, or `null` / `unknown` where +the file holds the information) was fixed: P1-P8 and P10 of the 2026-10-16 list. +The rows below are kept. The browser's side of each was measured in R2027a by the +2026-10-15 display study (data-explorer-vscode +`docs/deep-work/2026-10-15-decided-items/display-study/`, `not-followable-*.md` and +`vs-matlab.md`). Q1, P1 and H1-H6 are that study's ids, so the study's P1 is +precision, not the 2026-10-16 list's P1. + +| kept | here | MATLAB's Workspace browser | why | +|---|---|---|---| +| Q1, a summary's spelling | `<1x11 int8>`, `<0x3 cell>` | `1×11 int8`, `{0×3 cell}` | one spelling for every summary. The angle brackets are the italic signal, and the host withholds the in-place editor on them | +| P1 (the study's), precision | full JS round-trip precision, `3.141592653589793` | the user's `format`, short by default: `3.1416`, so `pi` and `3.14159` look the same, and `[1e-10 1]` is `[0,1]` | the host filters and compares on the cell text | +| H1, a Parameter's entry row | its value: `500`, `DispColor.Green` | `1×1 Parameter`, whatever the value is | a Parameter with a scalar value has no Value row of its own, so its entry cell is the only place the value shows and is edited | +| H2, the edit text | none of its own: the cell text seeds the editor | a separate text, in `format long` (`FormatDataUtils.getDisplayEditValue`) | the cell is already a valid literal at full precision | +| H4, a 1x1 cell | `{1}` | `1` | the browser tells `{1}` from `1` only by the Class column, which the host hides by default | +| H6, the length guard | a summary past `SUMMARY_MAX_CHARS = 1000` (see "Threshold rule") | the whole text, up to 99,999 characters | a runaway guard against a 100 KB grid cell | +| logical | `true` / `false` | `1` / `0` | | +| quotes in char and string | doubled: `'it''s'`, `"say ""hi"""` | as they are: `'it's'`, `"say "hi""` | the browser's text is no literal; this one is | +| cell elements | comma-separated: `{1, 'two', [3 4]}` | space-separated: `{1 'two' [3 4]}` | | +| P9 of the 2026-10-16 list, a `.mat` `Simulink.VariantExpression` | `<1x1 Simulink.Variant>`, Class `Simulink.Variant` | Class `Simulink.VariantExpression` | the class name the `.mat` itself records, and the class's documented former name. A dictionary's reads as a VariantExpression, with its condition. No fixture of this repo holds one | +| an int-backed enumeration's empty, from a binary dictionary or a `.mat` | `<1x0 DispLevel>`, `<0x0 DispColor>` | `[ ]` | read there as an enumeration value (an ObjectNode), it keeps the summary every empty object array has. It names the class and the size, so nothing on it misleads. A text dictionary's is `[ ]` (the first row above) | + +H3 is the evidence for the study's P1. H5, control characters, is not kept: it is P7 +of the 2026-10-16 list, fixed (the char row above). ### A property sheet is not a table cell From 3a856790fcf560c5e470642ea176fcf38a47c945 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sun, 18 Oct 2026 23:27:54 -0400 Subject: [PATCH 4/4] Ask MATLAB to reopen saves made before two fixes (M4, M7), and core's mixed-array copies into text M4 and M7 were the last MATLAB read-backs of the 2026-10-13 save-fidelity plan still open, and D15's binary-to-text reopen had no truth file. Each core before #93 and before 90caf06 wrote every entry of a save from its model, so a file it saved is still on a user's disk: what do MATLAB R2027a, and core now, read of it? No binary fixture held a dynamic property, so make_old_saves_r2027a.m had MATLAB write one dictionary, in both formats, of the shapes none holds: an AUTOSAR config set (a Simulink.STFCustomTargetCC with five dynamic properties), a config set with an SSC.SimscapeCC attached, a dexdata.DerivedSignal beside a Simulink.Signal (either first, as an NdHolder's Kids in Other Data: a dictionary refuses a Signal array as an entry's value), and structs of no element in five shapes, one with fields, and as a struct's fields. old_saves.mjs made untouched saves of that file and of the two fixtures holding a struct entry of no element (dispcat_bin.sldd, cases.sldd) with core built at 50549bd^, at 69664b0^ (#93 less the commit that kept a dynamic property's Source and PropertyType, so that M4 is asked about alone) and at 90caf06^. The nine saves are committed in test/parity/oldsaves/, since the builds are not in the tree. It also makes seven pastes with core as it is now into the text twin: each mixed-array entry, all four together, and the empty structs from the original and from an old save. probe_old_saves_readback.m had MATLAB open every save and paste and a copy of its original, and wrote old_saves_readback.truth.json. What MATLAB read, with no error or warning anywhere: - M4: a dynamic property written as an ordinary one reads back as the dynamic property it was, with its value. Harmless. A save before #93 is not: that core wrote every element of a mixed array as the first element's class (the config set's components all Simulink.SolverCC, its target as a Simulink.CodeAppCC), so MATLAB reads both config sets otherwise, none of the AUTOSAR options exists, and the Signal array is two of its first element's class. - M7: a struct of no element written around an reads in the shape its Dimension states, with no element. But that writer wrote none of the fields it declares, so a 1x0 struct with fields a and b reads with none: lost in the file. - Core now reads each such struct as MATLAB does wherever it shows or writes it (its size, no row, no field, regenerated self-closed), and its copy of one into a text dictionary reads in MATLAB as the old save does. So no source changes. - D15: core's text copies of the mixed Signal arrays read as a Simulink.Signal array of each element's own class, the config sets' Components as the original, SSC.SimscapeCC included, and the AUTOSAR options, which the text form holds among the ordinary properties, as the dynamic properties they were. - The same old saves show other losses, each fixed since: dispcat_bin.sldd's shaped empty cells, chars and strings, and before #93 its enumeration values. oldSaves.truth.test.ts (16 cases) checks that the old saves and their originals are the files MATLAB read, makes the pastes again from source and asks that each is still the one MATLAB was asked about, holds MATLAB's answers, and asks that core reads each old save's dynamic properties and empty structs as MATLAB does. With no truth file it fails to open it. With D15's base-class rule taken out, the dynamic property's Source dropped by the reader, or a struct of no element read as 1x1 when an is there, it fails. No existing test changes. --- test/parity/matlab/README.md | 1 + test/parity/matlab/old_saves.mjs | 277 +++ .../matlab/old_saves_readback.truth.json | 2142 +++++++++++++++++ test/parity/matlab/probe_old_saves_readback.m | 454 ++++ test/parity/oldSaves.truth.test.ts | 337 +++ test/parity/oldsaves/make_old_saves_r2027a.m | 138 ++ .../oldsaves/old_saves_r2027a_binary.sldd | Bin 0 -> 21785 bytes .../oldsaves/old_saves_r2027a_text.sldd | 769 ++++++ test/parity/oldsaves/pre69664b0_cases.sldd | Bin 0 -> 7689 bytes .../oldsaves/pre69664b0_dispcat_bin.sldd | Bin 0 -> 9822 bytes .../pre69664b0_old_saves_r2027a_binary.sldd | Bin 0 -> 21843 bytes test/parity/oldsaves/pre90caf06_cases.sldd | Bin 0 -> 7689 bytes .../oldsaves/pre90caf06_dispcat_bin.sldd | Bin 0 -> 9906 bytes .../pre90caf06_old_saves_r2027a_binary.sldd | Bin 0 -> 21920 bytes test/parity/oldsaves/pre93_cases.sldd | Bin 0 -> 7689 bytes test/parity/oldsaves/pre93_dispcat_bin.sldd | Bin 0 -> 9822 bytes .../pre93_old_saves_r2027a_binary.sldd | Bin 0 -> 21706 bytes 17 files changed, 4118 insertions(+) create mode 100644 test/parity/matlab/old_saves.mjs create mode 100644 test/parity/matlab/old_saves_readback.truth.json create mode 100644 test/parity/matlab/probe_old_saves_readback.m create mode 100644 test/parity/oldSaves.truth.test.ts create mode 100644 test/parity/oldsaves/make_old_saves_r2027a.m create mode 100644 test/parity/oldsaves/old_saves_r2027a_binary.sldd create mode 100644 test/parity/oldsaves/old_saves_r2027a_text.sldd create mode 100644 test/parity/oldsaves/pre69664b0_cases.sldd create mode 100644 test/parity/oldsaves/pre69664b0_dispcat_bin.sldd create mode 100644 test/parity/oldsaves/pre69664b0_old_saves_r2027a_binary.sldd create mode 100644 test/parity/oldsaves/pre90caf06_cases.sldd create mode 100644 test/parity/oldsaves/pre90caf06_dispcat_bin.sldd create mode 100644 test/parity/oldsaves/pre90caf06_old_saves_r2027a_binary.sldd create mode 100644 test/parity/oldsaves/pre93_cases.sldd create mode 100644 test/parity/oldsaves/pre93_dispcat_bin.sldd create mode 100644 test/parity/oldsaves/pre93_old_saves_r2027a_binary.sldd diff --git a/test/parity/matlab/README.md b/test/parity/matlab/README.md index bb7f1205..fe163d3b 100644 --- a/test/parity/matlab/README.md +++ b/test/parity/matlab/README.md @@ -126,6 +126,7 @@ directory, because `Simulink.data.dictionary.open` rejects a relative one with | `probe_parameter_value.m` | which values does a `Simulink.Parameter` refuse as its `Value`, at the top level and as a field of a struct Value, and with which message? And which structural edits inside it (removing or adding a field or an array element) leave a Value it refuses? (C-160, C-042, A2: core mirrors the answers at edit time) | `$PARAM_VALUE_OUT` or `tempdir/paramvalue` | `parameter_value_truth.json` (written by `27.1.0.3400455 (R2027a) Prerelease`) | | `probe_save_readbacks.m` + `save_readbacks.mjs` | does MATLAB reopen what core SAVES as what it was: a config set regenerated and pasted into text (M1, D15), enumeration values and empty ones (M2), an emptied and a new bus and the two spellings older saves gave one (M3)? The `.mjs` makes the saves through core's API; graded by `../readbacks.truth.test.ts`. M1 needs two corpus files (`DEX_READBACKS_CORPUS`). Ends `SAVE_READBACKS OK` | `$SAVE_READBACKS_OUT` | `save_readbacks.truth.json` (here), written by `27.1.0.3409468 (R2027a) Prerelease` | | `probe_m9_merged_reopen.m` + `m9_merged_saves.mjs` | does MATLAB reopen S2's merged saves of a binary dictionary (an edited entry saved as its bytes, with only what the edit changed written from the model) and read every entry as it should (M9)? In every compressed-binary fixture under `test/fixtures` and `test/parity/artifacts`, and in a copy of each as an earlier core save left it (1x1 cells spelled `Dimension="1*1"`, so that the merge differs from writing the entry whole), one entry of each kind is renamed, has its Description set, and where it is cheap a bus element's Description or a struct field edited, each through `session.editProperty` and saved by `serializeSource` into a file of its own. MATLAB opens each save and a copy of its original: every unedited entry must read as in the original (class, `isequaln`, `fullsig`), the edited one as the original with the same edit made in MATLAB, with no error or warning. Opens only the copies and the saves in `$M9_OUT`; graded by `../m9MergedSaves.truth.test.ts`. Ends `M9_MERGED_REOPEN OK` | `$M9_OUT` | `m9_merged_saves.truth.json` (here), written by `27.1.0.3409468 (R2027a) Prerelease` | +| `probe_old_saves_readback.m` + `old_saves.mjs` | what does MATLAB read of a binary dictionary core saved before two of its fixes, and of what core copies now from a binary dictionary into a text one (M4, M7, D15)? `../oldsaves/` holds untouched saves, by core built at `50549bd^`, `69664b0^` and `90caf06^`, of every binary fixture holding a dynamic property or a struct entry of no element, and of `old_saves_r2027a_binary.sldd` (`make_old_saves_r2027a.m`: an AUTOSAR config set, a config set holding an `SSC.SimscapeCC`, a `dexdata.DerivedSignal` beside a `Simulink.Signal`, and empty structs of five shapes); the `.mjs` makes them once per build (`save`), and the pastes into the text twin with this checkout (`cases`). MATLAB opens each save and paste and a copy of its original: every entry as in the original, each dynamic property by `get_param`, each empty struct's shape. Opens only the files in `$OLD_SAVES_OUT`; graded by `../oldSaves.truth.test.ts`. Ends `OLD_SAVES_READBACK OK` | `$OLD_SAVES_OUT` | `old_saves_readback.truth.json` (here), written by `27.1.0.3409468 (R2027a) Prerelease` | | `probe_fieldless_struct_readback.m` + `fieldless_struct_readback.mjs` | does MATLAB read back core's one-stream spelling of a field-less `repmat(struct(),1e4,1e4)` (a 132-byte cdata in text, a 72-byte hex stream in binary), as an entry, a struct field and a cell's element, where its own spelling is 1e8 elements? (F1-9; graded by `../../fieldlessStructReadback.test.ts`). Ends `FIELDLESS_STRUCT_READBACK OK` | `$FIELDLESS_OUT` | `fieldless_struct_readback.truth.json` (here), from `../../fixtures/mcos/fieldless_1e4x1e4.mat` (MATLAB's `save`), written by `27.1.0.3409468 (R2027a) Prerelease` | | `probe_text_empty_readback.m` | how does MATLAB read back a classed or shaped EMPTY out of a text dictionary, at an entry, a cell, a struct field, a Parameter's Value and an object's property, in each candidate spelling; and how does it spell one there itself? (1A-TEXT, CELL-EMPTIES-87, D8; graded by `test/textEmptyReadback.test.ts`). Ends `TEXT_EMPTY_READBACK OK` | `$TEXT_EMPTY_OUT` or `tempdir/textempty` | `text_empty_readback.truth.json` (here) and `../../fixtures/text_empty_readback_binary.sldd`, written by `27.1.0.3409468 (R2027a) Prerelease` | | `probe_display.m` + `probe_display_catalog.m` | what does MATLAB's Workspace browser show in its Value, Size and Class columns for 283 values, and how do the same values read back from a `.mat` and both dictionary flavours? (the 2026-10-15 display ruling: Value and Class follow the browser). Ends `DONE t=…` | `$DISPLAY_OUT` or `tempdir/dexdisp` | `display/`: `browser_short.truth.json` and `meta.truth.json` (the browser's own column code, `ClassCol` / `ValueCol` / `ObjectValueSummary`, written by `27.1.0.3400455 (R2027a) Prerelease`), and `dispcat.mat`, `dispcat_text.sldd`, `dispcat_bin.sldd`, cut to the values the display tests read by `display/trim-display-fixtures.mjs` | diff --git a/test/parity/matlab/old_saves.mjs b/test/parity/matlab/old_saves.mjs new file mode 100644 index 00000000..a343ba36 --- /dev/null +++ b/test/parity/matlab/old_saves.mjs @@ -0,0 +1,277 @@ +// Copyright 2026 The MathWorks, Inc. +// +// The core half of probe_old_saves_readback.m: what MATLAB R2027a is asked to reopen about +// saves core made before two fixes of the 2026-10-13 save-fidelity plan (its M4 and M7), and +// about copies core makes now from a binary dictionary into a text one. +// +// OLD SAVES. Before #93 (50549bd) a binary dictionary's reader dropped the Source="dynamic" +// and PropertyType="..." MATLAB writes on the options a custom or AUTOSAR target adds to its +// Simulink.STFCustomTargetCC, so a save wrote each as an ordinary property (M4); it also named +// a mixed-class object array's elements all after the first. Before 90caf06 the writer of a +// struct entry wrote a struct of no element around an `` it never had, and without the +// `` of the fields it declares (M7). Every save then wrote every entry from the +// model, edited or not. A file saved by such a core is still on a user's disk; the question is +// what MATLAB, and core now, read of it. +// +// The saves were made once, each an untouched save (session.serializeSource with nothing +// edited) of every original oldSaveOriginals names, by core built at each commit BUILDS names, +// and are committed beside the originals in test/parity/oldsaves/ (the builds are not in the +// tree, so a test cannot make them again): +// node test/parity/matlab/old_saves.mjs save +// once per build, from a checkout of that commit with dist built. 'pre69664b0' is #93 less its +// last commit: it keeps the element classes and drops the dynamic attributes, so that M4 is +// asked about alone. +// +// PASTES. pastesOf copies entries as a host copies one (the entry's serialize(), handed to the +// target section's parseEntry under a new name and uuid) into old_saves_r2027a_text.sldd, +// MATLAB's own text twin of the binary original, and saves the text: +// - from the binary original, each entry holding a mixed-class object array (since D15, +// 83cc70a, read under the base class MATLAB's text format names): a user's subclass of +// Simulink.Signal beside a Simulink.Signal, and two config sets' Components, one of them +// holding the AUTOSAR target's dynamic properties; one save per entry and one with all four; +// - the struct entries of no element, and the struct entry holding two, from the binary +// original and from its pre90caf06 save, as core reads each now: one save of each. +// The probe's inputs are written by +// node test/parity/matlab/old_saves.mjs cases +// with this checkout's dist built: the pastes, the old saves and their originals' copies, and +// cases.json. test/parity/oldSaves.truth.test.ts makes the pastes again from source and asks +// that each is still the one MATLAB was asked about. +// +// `cases` (cases.json, for the probe): one row per old save ({kind: 'old save', build, fixture, +// file, original, sha, dynamic, emptyStructs}) and one per paste ({kind: 'paste', file, +// original, source, pasted: [{section, from, to}], dynamic, sha}). `dynamic` lists every dynamic +// property the original (or the pasted entry's source) holds, as it holds it ({entry, to, name, +// value}), and `emptyStructs` every struct of no element at an entry ({entry, dims, fields}), +// so that the probe can ask MATLAB about each. +import { createHash } from 'node:crypto'; +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { strFromU8, unzipSync } from 'fflate'; + +const CHUNK = 'data/chunk0.xml'; +const ab = (u8) => u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength); +const sha = (data) => createHash('sha256').update(data).digest('hex'); + +/** The builds, each named for the commit it is the parent of. */ +export const BUILDS = { pre93: '50549bd^', pre69664b0: '69664b0^', pre90caf06: '90caf06^' }; +/** The originals' home, and the saves', under test/. */ +export const OLD_SAVES_DIR = 'parity/oldsaves'; +export const GENERATED = `${OLD_SAVES_DIR}/old_saves_r2027a_binary.sldd`; +export const TEXT_TWIN = `${OLD_SAVES_DIR}/old_saves_r2027a_text.sldd`; + +/** Each `…` of a chunk, as [name, object], in order. */ +export function objectsOf(xml) { + const out = []; + let at = 0; + for (;;) { + const s = xml.indexOf('', at); + if (s < 0) return out; + const e = xml.indexOf('', s) + ''.length; + const object = xml.slice(s, e); + const m = /

([^<]*)<\/P>/.exec(object); + out.push([m ? m[1] : '', object]); + at = e; + } +} + +/** + * What a binary dictionary's chunk spells of the two shapes: every dynamic property (a `

` + * with Source="dynamic"), and every struct at an entry's Value whose Dimension holds no + * element, with the fields it declares and whether it is written around an ``. + */ +export function shapesOf(bytes) { + const xml = strFromU8(unzipSync(bytes)[CHUNK]); + const dynamic = []; + const emptyStructs = []; + for (const [entry, object] of objectsOf(xml)) { + for (const m of object.matchAll(/

([^<]*)<\/P>/g)) { + dynamic.push({ entry, name: m[1], value: m[2] }); + } + const value = /\n( *)

/.exec(object); + if (value && value[2].split('*').some((n) => n === '0')) { + const body = value[3] ? '' : object.slice(value.index + value[0].length, object.indexOf(`\n${value[1]}

`, value.index)); + emptyStructs.push({ + entry, + dims: value[2].split('*').map(Number), + fields: [...body.matchAll(/^ *$/gm)].map((f) => f[1]), + aroundElement: //.test(body), + }); + } + } + return { dynamic, emptyStructs }; +} + +/** The `

` line of each named property of an entry, as a chunk spells it, or null: how a save wrote it. */ +export function linesOf(bytes, props) { + const objects = new Map(objectsOf(strFromU8(unzipSync(bytes)[CHUNK]))); + return props.map(({ entry, name }) => { + const line = new RegExp(`

]*>[^<]*

`).exec(objects.get(entry) ?? ''); + return line ? line[0] : null; + }); +} + +/** + * The originals of the old saves: every compressed-binary .sldd under test/fixtures and + * test/parity/artifacts that holds a dynamic property or a struct of no element at an entry, + * and old_saves_r2027a_binary.sldd, which make_old_saves_r2027a.m wrote for the shapes none of + * them holds; as [{ rel, bytes }], in path order. + */ +export function oldSaveOriginals(testDir) { + const out = []; + const walk = (dir) => { + for (const name of readdirSync(dir).sort()) { + const path = join(dir, name); + if (statSync(path).isDirectory()) { + walk(path); + } else if (name.endsWith('.sldd')) { + const bytes = new Uint8Array(readFileSync(path)); + if (bytes[0] === 0x50 && bytes[1] === 0x4b) { + const { dynamic, emptyStructs } = shapesOf(bytes); + if (dynamic.length + emptyStructs.length > 0) { + out.push({ rel: relative(testDir, path).split('\\').join('/'), bytes }); + } + } + } + } + }; + for (const sub of ['fixtures', join('parity', 'artifacts')]) { + walk(join(testDir, sub)); + } + out.push({ rel: GENERATED, bytes: new Uint8Array(readFileSync(join(testDir, GENERATED))) }); + return out; +} + +const fileTag = (rel) => rel.split('/').pop().replace(/\.sldd$/, '').replace(/[^A-Za-z0-9]+/g, '_'); +/** The name of a build's save of an original, and of the original's copy beside it. */ +export const saveName = (build, rel) => `${build}_${fileTag(rel)}.sldd`; +export const originalName = (rel) => `orig_${fileTag(rel)}.sldd`; + +/** An untouched save of `bytes`, by `core` ({ createSession, ingest }): what a save by that core wrote of every entry. */ +export function untouchedSave(core, bytes, filename) { + const session = core.createSession(); + const root = core.ingest(session, ab(bytes), { filename }); + return session.serializeSource(root.name).bytes; +} + +/** The old-save rows of cases.json, for the saves in `dir` (as saveName names them) of `originals`. */ +export function oldSaveCases(originals, dir) { + const cases = []; + for (const build of Object.keys(BUILDS)) { + for (const { rel, bytes } of originals) { + const saved = new Uint8Array(readFileSync(join(dir, saveName(build, rel)))); + const { dynamic, emptyStructs } = shapesOf(bytes); + cases.push({ + kind: 'old save', + build, + fixture: rel, + file: saveName(build, rel), + original: originalName(rel), + sha: { save: sha(saved), original: sha(bytes) }, + dynamic: dynamic.map(({ entry, name, value }) => ({ entry, to: entry, name, value })), + emptyStructs: emptyStructs.map(({ entry, dims, fields }) => ({ entry, dims, fields })), + }); + } + } + return cases; +} + +/** The entries of the binary original that hold a mixed-class object array, by section. */ +export const MIXED = [ + { section: 'other', from: 'holdsMixed' }, + { section: 'other', from: 'holdsBase' }, + { section: 'config', from: 'csAutosar' }, + { section: 'config', from: 'csSimscape' }, +]; +/** The struct entries of no element in the binary original, and the one holding two such fields. */ +export const EMPTIES = ['sE00', 'sE02', 'sE20', 'sE002', 'sE10ab', 'sHolds'].map((from) => ({ section: 'design', from })); +/** The old save the empties are also pasted from: one that wrote them around an ``, and nothing else otherwise. */ +export const EMPTIES_OLD_SAVE = saveName('pre90caf06', GENERATED); + +/** + * The pastes, as { files, cases }. `sources` names each source's bytes: the binary original + * (`orig_old_saves_r2027a_binary.sldd`) and EMPTIES_OLD_SAVE; `text` is the text twin's. + */ +export function pastesOf(core, sources, text) { + const { createSession, ingest } = core; + const ORIGINAL = originalName(GENERATED); + const sets = [ + ...MIXED.map((p) => [ORIGINAL, [p]]), + [ORIGINAL, MIXED], + [ORIGINAL, EMPTIES], + [EMPTIES_OLD_SAVE, EMPTIES], + ]; + const files = {}; + const cases = []; + sets.forEach(([sourceName, set], k) => { + const session = createSession(); + const source = ingest(session, ab(sources[sourceName]), { filename: sourceName }); + const host = ingest(session, strFromU8(text), { filename: originalName(TEXT_TWIN) }); + const pasted = []; + for (const { section, from } of set) { + const entry = source.getSection(section).children.find((e) => e.name === from); + const payload = JSON.parse(JSON.stringify(entry.serialize())); + const to = `${from}Pasted`; + const uuid = `b27b27b2-0000-4000-8000-${String((k + 1) * 100 + pasted.length + 1).padStart(12, '0')}`; + host.getSection(section).parseEntry({ ...payload, name: to, metadata: { ...payload.metadata, uuid } }); + pasted.push({ section, from, to }); + } + const saved = session.serializeSource(host.name).text; + const file = `paste_${k + 1}.sldd`; + files[file] = saved; + const dynamic = shapesOf(sources[sourceName]).dynamic.flatMap((d) => { + const p = pasted.find((x) => x.section === 'config' && x.from === d.entry); + return p ? [{ entry: d.entry, to: p.to, name: d.name, value: d.value }] : []; + }); + cases.push({ kind: 'paste', file, original: originalName(TEXT_TWIN), source: sourceName, pasted, dynamic, sha: sha(saved) }); + }); + return { files, cases }; +} + +/** The two sources of the pastes, from the tree. */ +export function pasteSources(testDir) { + return { + [originalName(GENERATED)]: new Uint8Array(readFileSync(join(testDir, GENERATED))), + [EMPTIES_OLD_SAVE]: new Uint8Array(readFileSync(join(testDir, OLD_SAVES_DIR, EMPTIES_OLD_SAVE))), + }; +} + +// As a script. +if (process.argv[1] && import.meta.url === new URL('file://' + process.argv[1]).href) { + const { mkdirSync, writeFileSync, copyFileSync } = await import('node:fs'); + const { fileURLToPath } = await import('node:url'); + const here = fileURLToPath(new URL('.', import.meta.url)); + const testDir = join(here, '..', '..'); + const [mode, ...rest] = process.argv.slice(2); + if (mode === 'save') { + const [checkout, build, outDir] = rest; + if (!BUILDS[build] || !outDir) throw new Error(`usage: node old_saves.mjs save <${Object.keys(BUILDS).join('|')}> `); + const core = await import(join(checkout, 'dist', 'index.js')); + mkdirSync(outDir, { recursive: true }); + for (const { rel, bytes } of oldSaveOriginals(testDir)) { + writeFileSync(join(outDir, saveName(build, rel)), untouchedSave(core, bytes, rel.split('/').pop())); + } + console.log('saved', build); + } else if (mode === 'cases') { + const [outDir] = rest; + if (!outDir) throw new Error('usage: node old_saves.mjs cases '); + const dist = join(testDir, '..', 'dist'); + const core = await import(join(dist, 'index.js')); + await import(join(dist, 'datamodel', 'node', 'data', 'NodeClassMap.js')); + const { files, cases } = pastesOf(core, pasteSources(testDir), new Uint8Array(readFileSync(join(testDir, TEXT_TWIN)))); + mkdirSync(outDir, { recursive: true }); + for (const [name, data] of Object.entries(files)) writeFileSync(join(outDir, name), data); + copyFileSync(join(testDir, TEXT_TWIN), join(outDir, originalName(TEXT_TWIN))); + const originals = oldSaveOriginals(testDir); + for (const { rel, bytes } of originals) { + writeFileSync(join(outDir, originalName(rel)), bytes); + for (const build of Object.keys(BUILDS)) { + copyFileSync(join(testDir, OLD_SAVES_DIR, saveName(build, rel)), join(outDir, saveName(build, rel))); + } + } + const old = oldSaveCases(originals, join(testDir, OLD_SAVES_DIR)); + writeFileSync(join(outDir, 'cases.json'), JSON.stringify([...old, ...cases], null, 2) + '\n'); + console.log('wrote', Object.keys(files).length, 'pastes and', old.length + cases.length, 'cases to', outDir); + } else { + throw new Error('usage: node old_saves.mjs save|cases ...'); + } +} diff --git a/test/parity/matlab/old_saves_readback.truth.json b/test/parity/matlab/old_saves_readback.truth.json new file mode 100644 index 00000000..e7baa058 --- /dev/null +++ b/test/parity/matlab/old_saves_readback.truth.json @@ -0,0 +1,2142 @@ +{ + "version": "27.1.0.3409468 (R2027a) Prerelease", + "release": "2027a", + "date": "2026-10-18", + "originals": [ + { + "file": "orig_dispcat_bin.sldd", + "opened": true, + "warnings": [], + "entries": 76, + "notSelfEqual": [], + "shapes": [ + { + "key": "design|c_newline", + "shape": "char[1 3]" + }, + { + "key": "design|c_nul", + "shape": "char[1 3]" + }, + { + "key": "design|c_tab", + "shape": "char[1 3]" + }, + { + "key": "design|cell_1", + "shape": "cell[1 1]" + }, + { + "key": "design|cell_of_param", + "shape": "cell[1 1]" + }, + { + "key": "design|dx_cell_0x0", + "shape": "cell[0 0]" + }, + { + "key": "design|dx_cell_0x3", + "shape": "cell[0 3]" + }, + { + "key": "design|dx_cell_1x0", + "shape": "cell[1 0]" + }, + { + "key": "design|dx_char_0x0", + "shape": "char[0 0]" + }, + { + "key": "design|dx_char_0x2", + "shape": "char[0 2]" + }, + { + "key": "design|dx_char_1x0", + "shape": "char[1 0]" + }, + { + "key": "design|dx_complex_0x0", + "shape": "double[0 0]" + }, + { + "key": "design|dx_complex_1x0", + "shape": "double[1 0]" + }, + { + "key": "design|dx_complex_int16", + "shape": "int16[1 1]" + }, + { + "key": "design|dx_complex_int16_1x0", + "shape": "int16[1 0]" + }, + { + "key": "design|dx_complex_scalar", + "shape": "double[1 1]" + }, + { + "key": "design|dx_complex_vector", + "shape": "double[1 2]" + }, + { + "key": "design|dx_double_0x0", + "shape": "double[0 0]" + }, + { + "key": "design|dx_double_0x0x2", + "shape": "double[0 0 2]" + }, + { + "key": "design|dx_double_0x1", + "shape": "double[0 1]" + }, + { + "key": "design|dx_double_0x3", + "shape": "double[0 3]" + }, + { + "key": "design|dx_double_1x0", + "shape": "double[1 0]" + }, + { + "key": "design|dx_enum_0x0", + "shape": "matlab.lang.OnOffSwitchState[0 0]" + }, + { + "key": "design|dx_enum_1x0", + "shape": "matlab.lang.OnOffSwitchState[1 0]" + }, + { + "key": "design|dx_int8_0x3", + "shape": "int8[0 3]" + }, + { + "key": "design|dx_logical_1x0", + "shape": "logical[1 0]" + }, + { + "key": "design|dx_param_complex_1x0", + "shape": "Simulink.Parameter[1 1]" + }, + { + "key": "design|dx_param_empty", + "shape": "Simulink.Parameter[1 1]" + }, + { + "key": "design|dx_single_0x0", + "shape": "single[0 0]" + }, + { + "key": "design|dx_sparse_0x0", + "shape": "double[0 0]" + }, + { + "key": "design|dx_sparse_2x0", + "shape": "double[2 0]" + }, + { + "key": "design|dx_string_0x0", + "shape": "string[0 0]" + }, + { + "key": "design|dx_string_1x0", + "shape": "string[1 0]" + }, + { + "key": "design|dx_string_empty_1x1", + "shape": "string[1 1]" + }, + { + "key": "design|dx_struct_0x0", + "shape": "struct[0 0]{}" + }, + { + "key": "design|dx_struct_0x0_fields", + "shape": "struct[0 0]{a}" + }, + { + "key": "design|dx_struct_1x0", + "shape": "struct[1 0]{a}" + }, + { + "key": "design|e_logical_0x0", + "shape": "logical[0 0]" + }, + { + "key": "design|e_logical_0x3", + "shape": "logical[0 3]" + }, + { + "key": "design|e_single_complex_1x0", + "shape": "single[1 0]" + }, + { + "key": "design|e_sparse_logical_0x0", + "shape": "logical[0 0]" + }, + { + "key": "design|e_uint64_0x0", + "shape": "uint64[0 0]" + }, + { + "key": "design|en_i32_empty", + "shape": "DispLevel[1 0]" + }, + { + "key": "design|en_i32_high", + "shape": "DispLevel[1 1]" + }, + { + "key": "design|en_i32_vec", + "shape": "DispLevel[1 2]" + }, + { + "key": "design|en_off", + "shape": "matlab.lang.OnOffSwitchState[1 1]" + }, + { + "key": "design|en_on", + "shape": "matlab.lang.OnOffSwitchState[1 1]" + }, + { + "key": "design|en_onoff_vec", + "shape": "matlab.lang.OnOffSwitchState[1 3]" + }, + { + "key": "design|en_sl_empty", + "shape": "DispColor[0 0]" + }, + { + "key": "design|en_sl_red", + "shape": "DispColor[1 1]" + }, + { + "key": "design|en_sl_vec", + "shape": "DispColor[1 2]" + }, + { + "key": "design|enumdef", + "shape": "Simulink.data.dictionary.EnumTypeDefinition[1 1]" + }, + { + "key": "design|fi_vec", + "shape": "embedded.fi[1 3]" + }, + { + "key": "design|i8_1x10", + "shape": "int8[1 10]" + }, + { + "key": "design|i8_1x11", + "shape": "int8[1 11]" + }, + { + "key": "design|l_1x10", + "shape": "logical[1 10]" + }, + { + "key": "design|l_1x11", + "shape": "logical[1 11]" + }, + { + "key": "design|p_enum", + "shape": "Simulink.Parameter[1 1]" + }, + { + "key": "design|sp_complex", + "shape": "double[1 2]" + }, + { + "key": "design|sp_complex_scalar", + "shape": "double[1 1]" + }, + { + "key": "design|sp_logical", + "shape": "logical[1 3]" + }, + { + "key": "design|sp_logical_scalar", + "shape": "logical[1 1]" + }, + { + "key": "design|sp_scalar", + "shape": "double[1 1]" + }, + { + "key": "design|sp_vec", + "shape": "double[1 3]" + }, + { + "key": "design|str_0x3", + "shape": "string[0 3]" + }, + { + "key": "design|str_newline", + "shape": "string[1 1]" + }, + { + "key": "design|z_1x11", + "shape": "double[1 11]" + }, + { + "key": "design|z_int16", + "shape": "int16[1 1]" + }, + { + "key": "design|z_int16_vec", + "shape": "int16[1 2]" + }, + { + "key": "design|z_int8_vec", + "shape": "int8[1 2]" + }, + { + "key": "design|z_scalar", + "shape": "double[1 1]" + }, + { + "key": "design|z_single", + "shape": "single[1 1]" + }, + { + "key": "design|z_uint8", + "shape": "uint8[1 1]" + }, + { + "key": "design|z_vector", + "shape": "double[1 2]" + }, + { + "key": "other|cat_ab", + "shape": "categorical[1 2]" + }, + { + "key": "other|tbl_2x2", + "shape": "table[2 2]" + } + ] + }, + { + "file": "orig_cases.sldd", + "opened": true, + "warnings": [], + "entries": 73, + "notSelfEqual": [], + "shapes": [ + { + "key": "design|aBus", + "shape": "Simulink.Bus[1 1]" + }, + { + "key": "design|aLookup", + "shape": "Simulink.LookupTable[1 1]" + }, + { + "key": "design|aParam", + "shape": "Simulink.Parameter[1 1]" + }, + { + "key": "design|aSignal", + "shape": "Simulink.Signal[1 1]" + }, + { + "key": "design|aVariant", + "shape": "Simulink.VariantVariable[1 1]" + }, + { + "key": "design|bigD", + "shape": "double[1 1]" + }, + { + "key": "design|boolF", + "shape": "logical[1 1]" + }, + { + "key": "design|boolT", + "shape": "logical[1 1]" + }, + { + "key": "design|boolVec", + "shape": "logical[1 3]" + }, + { + "key": "design|cell2x3", + "shape": "cell[2 3]" + }, + { + "key": "design|cellEmpty", + "shape": "cell[0 0]" + }, + { + "key": "design|cellFlat", + "shape": "cell[1 3]" + }, + { + "key": "design|cellNd", + "shape": "cell[2 3 2]" + }, + { + "key": "design|cellNest", + "shape": "cell[1 2]" + }, + { + "key": "design|charStr", + "shape": "char[1 4]" + }, + { + "key": "design|colVec", + "shape": "double[3 1]" + }, + { + "key": "design|cplxScalar", + "shape": "double[1 1]" + }, + { + "key": "design|cplxVec", + "shape": "double[1 2]" + }, + { + "key": "design|eleven", + "shape": "double[1 11]" + }, + { + "key": "design|emptyD", + "shape": "double[0 0]" + }, + { + "key": "design|exactly10", + "shape": "double[1 10]" + }, + { + "key": "design|hugeChar", + "shape": "char[1 1500]" + }, + { + "key": "design|i16Vec", + "shape": "int16[1 3]" + }, + { + "key": "design|i64Unsafe", + "shape": "int64[1 1]" + }, + { + "key": "design|i64Vec", + "shape": "int64[1 3]" + }, + { + "key": "design|infN", + "shape": "double[1 1]" + }, + { + "key": "design|infP", + "shape": "double[1 1]" + }, + { + "key": "design|kp", + "shape": "double[1 1]" + }, + { + "key": "design|long30", + "shape": "double[1 30]" + }, + { + "key": "design|longChar", + "shape": "char[1 300]" + }, + { + "key": "design|mat2x3", + "shape": "double[2 3]" + }, + { + "key": "design|maxU64", + "shape": "uint64[1 1]" + }, + { + "key": "design|max_int16", + "shape": "int16[1 1]" + }, + { + "key": "design|max_int32", + "shape": "int32[1 1]" + }, + { + "key": "design|max_int64", + "shape": "int64[1 1]" + }, + { + "key": "design|max_int8", + "shape": "int8[1 1]" + }, + { + "key": "design|max_uint16", + "shape": "uint16[1 1]" + }, + { + "key": "design|max_uint32", + "shape": "uint32[1 1]" + }, + { + "key": "design|max_uint8", + "shape": "uint8[1 1]" + }, + { + "key": "design|min_int16", + "shape": "int16[1 1]" + }, + { + "key": "design|min_int32", + "shape": "int32[1 1]" + }, + { + "key": "design|min_int64", + "shape": "int64[1 1]" + }, + { + "key": "design|min_int8", + "shape": "int8[1 1]" + }, + { + "key": "design|min_uint16", + "shape": "uint16[1 1]" + }, + { + "key": "design|min_uint32", + "shape": "uint32[1 1]" + }, + { + "key": "design|min_uint64", + "shape": "uint64[1 1]" + }, + { + "key": "design|min_uint8", + "shape": "uint8[1 1]" + }, + { + "key": "design|nanV", + "shape": "double[1 1]" + }, + { + "key": "design|nd2x3x2", + "shape": "double[2 3 2]" + }, + { + "key": "design|negD", + "shape": "double[1 1]" + }, + { + "key": "design|nonFinVec", + "shape": "double[1 5]" + }, + { + "key": "design|rowVec", + "shape": "double[1 3]" + }, + { + "key": "design|s_int16", + "shape": "int16[1 1]" + }, + { + "key": "design|s_int32", + "shape": "int32[1 1]" + }, + { + "key": "design|s_int64", + "shape": "int64[1 1]" + }, + { + "key": "design|s_int8", + "shape": "int8[1 1]" + }, + { + "key": "design|s_uint16", + "shape": "uint16[1 1]" + }, + { + "key": "design|s_uint32", + "shape": "uint32[1 1]" + }, + { + "key": "design|s_uint64", + "shape": "uint64[1 1]" + }, + { + "key": "design|s_uint8", + "shape": "uint8[1 1]" + }, + { + "key": "design|scalarD", + "shape": "double[1 1]" + }, + { + "key": "design|scalarS", + "shape": "single[1 1]" + }, + { + "key": "design|strArray", + "shape": "string[1 3]" + }, + { + "key": "design|strMat", + "shape": "string[2 3]" + }, + { + "key": "design|strScalar", + "shape": "string[1 1]" + }, + { + "key": "design|struct1x3", + "shape": "struct[1 3]{a,b}" + }, + { + "key": "design|struct2x3", + "shape": "struct[2 3]{a}" + }, + { + "key": "design|structEmpty", + "shape": "struct[0 0]{}" + }, + { + "key": "design|structNd", + "shape": "struct[2 3 2]{a}" + }, + { + "key": "design|structNest", + "shape": "struct[1 1]{a}" + }, + { + "key": "design|structScalar", + "shape": "struct[1 1]{a,b}" + }, + { + "key": "design|tinyD", + "shape": "double[1 1]" + }, + { + "key": "design|u64Vec", + "shape": "uint64[1 3]" + } + ] + }, + { + "file": "orig_old_saves_r2027a_binary.sldd", + "opened": true, + "warnings": [], + "entries": 10, + "notSelfEqual": [ + "config|csAutosar" + ], + "shapes": [ + { + "key": "design|sE00", + "shape": "struct[0 0]{}" + }, + { + "key": "design|sE002", + "shape": "struct[0 0 2]{}" + }, + { + "key": "design|sE02", + "shape": "struct[0 2]{}" + }, + { + "key": "design|sE10ab", + "shape": "struct[1 0]{a,b}" + }, + { + "key": "design|sE20", + "shape": "struct[2 0]{}" + }, + { + "key": "design|sHolds", + "shape": "struct[1 1]{x,e,none}" + }, + { + "key": "other|holdsBase", + "shape": "NdHolder[1 1]" + }, + { + "key": "other|holdsMixed", + "shape": "NdHolder[1 1]" + }, + { + "key": "config|csAutosar", + "shape": "Simulink.ConfigSet[1 1]" + }, + { + "key": "config|csSimscape", + "shape": "Simulink.ConfigSet[1 1]" + } + ] + }, + { + "file": "orig_old_saves_r2027a_text.sldd", + "opened": true, + "warnings": [], + "entries": 10, + "notSelfEqual": [ + "config|csAutosar" + ], + "shapes": [ + { + "key": "design|sE00", + "shape": "struct[0 0]{}" + }, + { + "key": "design|sE002", + "shape": "struct[0 0]{}" + }, + { + "key": "design|sE02", + "shape": "struct[0 0]{}" + }, + { + "key": "design|sE10ab", + "shape": "struct[0 0]{}" + }, + { + "key": "design|sE20", + "shape": "struct[0 0]{}" + }, + { + "key": "design|sHolds", + "shape": "struct[1 1]{x,e,none}" + }, + { + "key": "other|holdsBase", + "shape": "NdHolder[1 1]" + }, + { + "key": "other|holdsMixed", + "shape": "NdHolder[1 1]" + }, + { + "key": "config|csAutosar", + "shape": "Simulink.ConfigSet[1 1]" + }, + { + "key": "config|csSimscape", + "shape": "Simulink.ConfigSet[1 1]" + } + ] + } + ], + "cases": [ + { + "kind": "old save", + "build": "pre93", + "fixture": "fixtures/display/dispcat_bin.sldd", + "file": "pre93_dispcat_bin.sldd", + "original": "orig_dispcat_bin.sldd", + "sha": { + "save": "c3c34f9ace2a1aec5bc41babdd58a8855ccec482803e09abd3f1ed50e7c4cce7", + "original": "043c5c7bc7c136e96284580dae7401e394b0e1bc3f338ae0043e7a94020ff1f1" + }, + "dynamic": [], + "emptyStructs": [ + { + "entry": "dx_struct_0x0", + "dims": [ + 0, + 0 + ], + "fields": [] + }, + { + "entry": "dx_struct_0x0_fields", + "dims": [ + 0, + 0 + ], + "fields": [ + "a" + ] + }, + { + "entry": "dx_struct_1x0", + "dims": [ + 1, + 0 + ], + "fields": [ + "a" + ] + } + ], + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": false, + "entries": { + "checked": 76, + "same": 62, + "differ": [ + "design|dx_cell_0x3: isequaln 0, sig 0: cell[0 0]{} | wanted cell[0 3]{}", + "design|dx_cell_1x0: isequaln 0, sig 0: cell[0 0]{} | wanted cell[1 0]{}", + "design|dx_char_0x2: isequaln 0, sig 0: char[0 0]='' | wanted char[0 2]=''", + "design|dx_char_1x0: isequaln 0, sig 0: char[0 0]='' | wanted char[1 0]=''", + "design|dx_string_0x0: isequaln 0, sig 0: string[1 1]{\"\"} | wanted string[0 0]{}", + "design|dx_string_1x0: isequaln 0, sig 0: string[1 1]{\"\"} | wanted string[1 0]{}", + "design|dx_struct_0x0_fields: isequaln 0, sig 0: struct[0 0]<>{} | wanted struct[0 0]{}", + "design|dx_struct_1x0: isequaln 0, sig 0: struct[1 0]<>{} | wanted struct[1 0]{}", + "design|en_i32_high: isequaln 0, sig 0: DispLevel[0 0]=[] | wanted DispLevel[1 1]=High", + "design|en_i32_vec: isequaln 0, sig 0: DispLevel[0 0]=[] | wanted DispLevel[1 2]=Low,High", + "design|en_sl_red: isequaln 0, sig 0: DispColor[0 0]=[] | wanted DispColor[1 1]=Red", + "design|en_sl_vec: isequaln 0, sig 0: DispColor[0 0]=[] | wanted DispColor[1 2]=Red,Blue", + "design|p_enum: isequaln 0, sig 0: Simulink.Parameter[1 1]{Value:DispColor[0 0]=[],Complexity:char[1 4]='real',Dimensions:double[1 2]=0,0,CoderInfo:Simulink.CoderInfo[1 1]{StorageClass:char[1 4]='Auto',TypeQualifier:char[0 0]='',Identifier:char[0 0]='',Alignment:int32[1 1]=-1,CustomStorageClass:char[1 7]='Default',CustomAttributes:SimulinkCSC.AttribClass_Simulink_Default[1 1]{disp:SimulinkCSC.AttribClass_Simulink_Default {} | wanted struct[1 0]{}", + "design|sHolds: isequaln 0, sig 0: struct[1 1]{x:double[1 1]=1,e:struct[0 0]<>{},none:struct[0 0]<>{}} | wanted struct[1 1]{x:double[1 1]=1,e:struct[0 0]{},none:struct[0 0]<>{}}", + "other|holdsBase: isequaln 0, sig 1: ',Min:double[0 0]=[],Max:double[0 0]=[],Unit:char[0 0]=''},Tag:char[1 4]='base'} | wanted ',Min:double[0 0]=[],Max:double[0 0]=[],Unit:char[0 0]=''},Tag:char[1 4]='base'}", + "other|holdsMixed: isequaln 0, sig 0: NdHolder[1 1]{Kids:dexdata.DerivedSignal[1 2]{Routing:char[1 3]='bus',Priority:int32[1 1]=5,Dimensions:double[1 1]=-1,DimensionsMode:char[1 4]='auto',Complexity:char[1 4]='auto',SampleTime:double[1 1]=-1,SamplingMode:char[1 4]='auto',InitialValue:char[0 0]='',LoggingInfo:Simulink.LoggingInfo[1 1]{DataLogging:logical[1 1]=false,NameMode:logical[1 1]=false,LoggingName:char[0 0]='',DecimateData:logic ...(1951 chars) | wanted NdHolder[1 1]{Kids:Simulink.Signal[1 2]{Dimensions:double[1 1]=-1,DimensionsMode:char[1 4]='auto',Complexity:char[1 4]='auto',SampleTime:double[1 1]=-1,SamplingMode:char[1 4]='auto',InitialValue:char[0 0]='',LoggingInfo:Simulink.LoggingInfo[1 1]{DataLogging:logical[1 1]=false,NameMode:logical[1 1]=false,LoggingName:char[0 0]='',DecimateData:logical[1 1]=false,Decimation:double[1 1]=2,LimitDataPoin ...(1853 chars)", + "config|csAutosar: isequaln 1, sig 0: mponents:Simulink.ConfigComponent[9 1]{Name:char[1 6]='Solver',Description:char[1 41]='Simulink Coverage Configuration Component',Components:double[0 0]=[],Name:char[1 18]='Data Import/Export',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 12]='Optimization',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 11]='Diagnostics',Description:char[0 0]='',Components:double[0 ...(1090 chars) | wanted mponents:Simulink.ConfigComponent[9 1]{Name:char[1 6]='Solver',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 18]='Data Import/Export',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 12]='Optimization',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 11]='Diagnostics',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 23]='Hardware Implement ...(6172 chars)", + "config|csSimscape: isequaln 0, sig 0: ,Path:char[0 0]='',Description:char[0 0]='',Components:Simulink.ConfigComponent[9 1]{Name:char[1 6]='Solver',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 18]='Data Import/Export',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 12]='Optimization',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 11]='Diagnostics',Description:char[0 0]='',Components:doub ...(1094 chars) | wanted ,Path:char[0 0]='',Description:char[0 0]='',Components:Simulink.ConfigComponent[10 1]{Name:char[1 6]='Solver',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 18]='Data Import/Export',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 12]='Optimization',Description:char[0 0]='',Components:double[0 0]=[],Name:char[1 11]='Diagnostics',Description:char[0 0]='',Components:dou ...(1466 chars)" + ] + }, + "dynamic": [ + { + "entry": "csAutosar", + "name": "AutosarMatrixIOAsArray", + "original": "off", + "save": "", + "dynamicInSave": false + }, + { + "entry": "csAutosar", + "name": "AutosarCompilerAbstraction", + "original": "off", + "save": "", + "dynamicInSave": false + }, + { + "entry": "csAutosar", + "name": "AutosarMaxShortNameLength", + "original": "128", + "save": "", + "dynamicInSave": false + }, + { + "entry": "csAutosar", + "name": "DisableAUTOSARRoutinesHostLibrary", + "original": "off", + "save": "", + "dynamicInSave": false + }, + { + "entry": "csAutosar", + "name": "AutosarSchemaVersion", + "original": "R23-11", + "save": "", + "dynamicInSave": false + } + ], + "emptyStructs": [ + { + "entry": "sE00", + "shape": "struct[0 0]{}" + }, + { + "entry": "sE002", + "shape": "struct[0 0 2]{}" + }, + { + "entry": "sE02", + "shape": "struct[0 2]{}" + }, + { + "entry": "sE10ab", + "shape": "struct[1 0]{}" + }, + { + "entry": "sE20", + "shape": "struct[2 0]{}" + } + ], + "pasted": [], + "error": "" + } + }, + { + "kind": "old save", + "build": "pre69664b0", + "fixture": "fixtures/display/dispcat_bin.sldd", + "file": "pre69664b0_dispcat_bin.sldd", + "original": "orig_dispcat_bin.sldd", + "sha": { + "save": "470a691f7d0eaaa0fe4bc98d60c22c355dfe7167607aa9efeb95bc4dd948347e", + "original": "043c5c7bc7c136e96284580dae7401e394b0e1bc3f338ae0043e7a94020ff1f1" + }, + "dynamic": [], + "emptyStructs": [ + { + "entry": "dx_struct_0x0", + "dims": [ + 0, + 0 + ], + "fields": [] + }, + { + "entry": "dx_struct_0x0_fields", + "dims": [ + 0, + 0 + ], + "fields": [ + "a" + ] + }, + { + "entry": "dx_struct_1x0", + "dims": [ + 1, + 0 + ], + "fields": [ + "a" + ] + } + ], + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": false, + "entries": { + "checked": 76, + "same": 62, + "differ": [ + "design|dx_cell_0x3: isequaln 0, sig 0: cell[0 0]{} | wanted cell[0 3]{}", + "design|dx_cell_1x0: isequaln 0, sig 0: cell[0 0]{} | wanted cell[1 0]{}", + "design|dx_char_0x2: isequaln 0, sig 0: char[0 0]='' | wanted char[0 2]=''", + "design|dx_char_1x0: isequaln 0, sig 0: char[0 0]='' | wanted char[1 0]=''", + "design|dx_string_0x0: isequaln 0, sig 0: string[1 1]{\"\"} | wanted string[0 0]{}", + "design|dx_string_1x0: isequaln 0, sig 0: string[1 1]{\"\"} | wanted string[1 0]{}", + "design|dx_struct_0x0_fields: isequaln 0, sig 0: struct[0 0]<>{} | wanted struct[0 0]{}", + "design|dx_struct_1x0: isequaln 0, sig 0: struct[1 0]<>{} | wanted struct[1 0]{}", + "design|en_i32_high: isequaln 0, sig 0: DispLevel[0 0]=[] | wanted DispLevel[1 1]=High", + "design|en_i32_vec: isequaln 0, sig 0: DispLevel[0 0]=[] | wanted DispLevel[1 2]=Low,High", + "design|en_sl_red: isequaln 0, sig 0: DispColor[0 0]=[] | wanted DispColor[1 1]=Red", + "design|en_sl_vec: isequaln 0, sig 0: DispColor[0 0]=[] | wanted DispColor[1 2]=Red,Blue", + "design|p_enum: isequaln 0, sig 0: Simulink.Parameter[1 1]{Value:DispColor[0 0]=[],Complexity:char[1 4]='real',Dimensions:double[1 2]=0,0,CoderInfo:Simulink.CoderInfo[1 1]{StorageClass:char[1 4]='Auto',TypeQualifier:char[0 0]='',Identifier:char[0 0]='',Alignment:int32[1 1]=-1,CustomStorageClass:char[1 7]='Default',CustomAttributes:SimulinkCSC.AttribClass_Simulink_Default[1 1]{disp:SimulinkCSC.AttribClass_Simulink_Default {} | wanted struct[1 0]{}", + "design|sHolds: isequaln 0, sig 0: struct[1 1]{x:double[1 1]=1,e:struct[0 0]<>{},none:struct[0 0]<>{}} | wanted struct[1 1]{x:double[1 1]=1,e:struct[0 0]{},none:struct[0 0]<>{}}" + ] + }, + "dynamic": [ + { + "entry": "csAutosar", + "name": "AutosarMatrixIOAsArray", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "AutosarCompilerAbstraction", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "AutosarMaxShortNameLength", + "original": "128", + "save": "128", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "DisableAUTOSARRoutinesHostLibrary", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "AutosarSchemaVersion", + "original": "R23-11", + "save": "R23-11", + "dynamicInSave": true + } + ], + "emptyStructs": [ + { + "entry": "sE00", + "shape": "struct[0 0]{}" + }, + { + "entry": "sE002", + "shape": "struct[0 0 2]{}" + }, + { + "entry": "sE02", + "shape": "struct[0 2]{}" + }, + { + "entry": "sE10ab", + "shape": "struct[1 0]{}" + }, + { + "entry": "sE20", + "shape": "struct[2 0]{}" + } + ], + "pasted": [], + "error": "" + } + }, + { + "kind": "old save", + "build": "pre90caf06", + "fixture": "fixtures/display/dispcat_bin.sldd", + "file": "pre90caf06_dispcat_bin.sldd", + "original": "orig_dispcat_bin.sldd", + "sha": { + "save": "aaa3809dab7124b097a092f2110db92a187aaae6158ac46c25823defa5f9379f", + "original": "043c5c7bc7c136e96284580dae7401e394b0e1bc3f338ae0043e7a94020ff1f1" + }, + "dynamic": [], + "emptyStructs": [ + { + "entry": "dx_struct_0x0", + "dims": [ + 0, + 0 + ], + "fields": [] + }, + { + "entry": "dx_struct_0x0_fields", + "dims": [ + 0, + 0 + ], + "fields": [ + "a" + ] + }, + { + "entry": "dx_struct_1x0", + "dims": [ + 1, + 0 + ], + "fields": [ + "a" + ] + } + ], + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": false, + "entries": { + "checked": 76, + "same": 68, + "differ": [ + "design|dx_cell_0x3: isequaln 0, sig 0: cell[0 0]{} | wanted cell[0 3]{}", + "design|dx_cell_1x0: isequaln 0, sig 0: cell[0 0]{} | wanted cell[1 0]{}", + "design|dx_char_1x0: isequaln 0, sig 0: char[0 0]='' | wanted char[1 0]=''", + "design|dx_string_0x0: isequaln 0, sig 0: string[1 1]{\"\"} | wanted string[0 0]{}", + "design|dx_string_1x0: isequaln 0, sig 0: string[1 1]{\"\"} | wanted string[1 0]{}", + "design|dx_struct_0x0_fields: isequaln 0, sig 0: struct[0 0]<>{} | wanted struct[0 0]{}", + "design|dx_struct_1x0: isequaln 0, sig 0: struct[1 0]<>{} | wanted struct[1 0]{}", + "design|str_0x3: isequaln 0, sig 0: string[1 1]{\"\"} | wanted string[0 3]{}" + ] + }, + "dynamic": [], + "emptyStructs": [ + { + "entry": "dx_struct_0x0", + "shape": "struct[0 0]{}" + }, + { + "entry": "dx_struct_0x0_fields", + "shape": "struct[0 0]{}" + }, + { + "entry": "dx_struct_1x0", + "shape": "struct[1 0]{}" + } + ], + "pasted": [], + "error": "" + } + }, + { + "kind": "old save", + "build": "pre90caf06", + "fixture": "parity/artifacts/binary/cases.sldd", + "file": "pre90caf06_cases.sldd", + "original": "orig_cases.sldd", + "sha": { + "save": "fd4e8e3c5cae301bb2d0666f9dfd962e783b012da2becf51f2028eded902894a", + "original": "ac37f72df80b998b05f40fe615fb05b84f19712d07fbf46a618b06c5b317b435" + }, + "dynamic": [], + "emptyStructs": { + "entry": "structEmpty", + "dims": [ + 0, + 0 + ], + "fields": [] + }, + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": true, + "entries": { + "checked": 73, + "same": 73, + "differ": [] + }, + "dynamic": [], + "emptyStructs": [ + { + "entry": "structEmpty", + "shape": "struct[0 0]{}" + } + ], + "pasted": [], + "error": "" + } + }, + { + "kind": "old save", + "build": "pre90caf06", + "fixture": "parity/oldsaves/old_saves_r2027a_binary.sldd", + "file": "pre90caf06_old_saves_r2027a_binary.sldd", + "original": "orig_old_saves_r2027a_binary.sldd", + "sha": { + "save": "227c527959d33d47b8b71454f74985f6a85debfa73f9db48263c1ee43e15fbb9", + "original": "f3f744bd51813831b3c679aa8a93c5c4e3e31be355ed5f484c6fd39edb977692" + }, + "dynamic": [ + { + "entry": "csAutosar", + "to": "csAutosar", + "name": "AutosarMatrixIOAsArray", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosar", + "name": "AutosarCompilerAbstraction", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosar", + "name": "AutosarMaxShortNameLength", + "value": "128.0" + }, + { + "entry": "csAutosar", + "to": "csAutosar", + "name": "DisableAUTOSARRoutinesHostLibrary", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosar", + "name": "AutosarSchemaVersion", + "value": "R23-11" + } + ], + "emptyStructs": [ + { + "entry": "sE00", + "dims": [ + 0, + 0 + ], + "fields": [] + }, + { + "entry": "sE002", + "dims": [ + 0, + 0, + 2 + ], + "fields": [] + }, + { + "entry": "sE02", + "dims": [ + 0, + 2 + ], + "fields": [] + }, + { + "entry": "sE10ab", + "dims": [ + 1, + 0 + ], + "fields": [ + "a", + "b" + ] + }, + { + "entry": "sE20", + "dims": [ + 2, + 0 + ], + "fields": [] + } + ], + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": false, + "entries": { + "checked": 10, + "same": 8, + "differ": [ + "design|sE10ab: isequaln 0, sig 0: struct[1 0]<>{} | wanted struct[1 0]{}", + "design|sHolds: isequaln 0, sig 0: struct[1 1]{x:double[1 1]=1,e:struct[0 0]<>{},none:struct[0 0]<>{}} | wanted struct[1 1]{x:double[1 1]=1,e:struct[0 0]{},none:struct[0 0]<>{}}" + ] + }, + "dynamic": [ + { + "entry": "csAutosar", + "name": "AutosarMatrixIOAsArray", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "AutosarCompilerAbstraction", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "AutosarMaxShortNameLength", + "original": "128", + "save": "128", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "DisableAUTOSARRoutinesHostLibrary", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosar", + "name": "AutosarSchemaVersion", + "original": "R23-11", + "save": "R23-11", + "dynamicInSave": true + } + ], + "emptyStructs": [ + { + "entry": "sE00", + "shape": "struct[0 0]{}" + }, + { + "entry": "sE002", + "shape": "struct[0 0 2]{}" + }, + { + "entry": "sE02", + "shape": "struct[0 2]{}" + }, + { + "entry": "sE10ab", + "shape": "struct[1 0]{}" + }, + { + "entry": "sE20", + "shape": "struct[2 0]{}" + } + ], + "pasted": [], + "error": "" + } + }, + { + "kind": "paste", + "file": "paste_1.sldd", + "original": "orig_old_saves_r2027a_text.sldd", + "source": "orig_old_saves_r2027a_binary.sldd", + "pasted": { + "section": "other", + "from": "holdsMixed", + "to": "holdsMixedPasted" + }, + "dynamic": [], + "sha": "be48b525f16c2cd582cc00993b3a372ab4214ff19b91850817a22e52c93f4f89", + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": true, + "entries": { + "checked": 10, + "same": 10, + "differ": [] + }, + "dynamic": [], + "emptyStructs": [], + "pasted": [ + { + "to": "holdsMixedPasted", + "same": true, + "why": "", + "shape": "NdHolder[1 1]", + "kids": "Simulink.Signal[1 2]{dexdata.DerivedSignal,Simulink.Signal}", + "sourceKids": "Simulink.Signal[1 2]{dexdata.DerivedSignal,Simulink.Signal}" + } + ], + "error": "" + } + }, + { + "kind": "paste", + "file": "paste_2.sldd", + "original": "orig_old_saves_r2027a_text.sldd", + "source": "orig_old_saves_r2027a_binary.sldd", + "pasted": { + "section": "other", + "from": "holdsBase", + "to": "holdsBasePasted" + }, + "dynamic": [], + "sha": "5261643b7b4b81dbe1ec6e049a78d9a584b53d78ff98ba60215a2b8bbee9ac04", + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": true, + "entries": { + "checked": 10, + "same": 10, + "differ": [] + }, + "dynamic": [], + "emptyStructs": [], + "pasted": [ + { + "to": "holdsBasePasted", + "same": true, + "why": "", + "shape": "NdHolder[1 1]", + "kids": "Simulink.Signal[1 2]{Simulink.Signal,dexdata.DerivedSignal}", + "sourceKids": "Simulink.Signal[1 2]{Simulink.Signal,dexdata.DerivedSignal}" + } + ], + "error": "" + } + }, + { + "kind": "paste", + "file": "paste_3.sldd", + "original": "orig_old_saves_r2027a_text.sldd", + "source": "orig_old_saves_r2027a_binary.sldd", + "pasted": { + "section": "config", + "from": "csAutosar", + "to": "csAutosarPasted" + }, + "dynamic": [ + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarMatrixIOAsArray", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarCompilerAbstraction", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarMaxShortNameLength", + "value": "128.0" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "DisableAUTOSARRoutinesHostLibrary", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarSchemaVersion", + "value": "R23-11" + } + ], + "sha": "b4a26ff8826b2a1f6450ae943ef4df7831e3694acf9c79b44ea9ea758e53741d", + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": true, + "entries": { + "checked": 10, + "same": 10, + "differ": [] + }, + "dynamic": [ + { + "entry": "csAutosarPasted", + "name": "AutosarMatrixIOAsArray", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "AutosarCompilerAbstraction", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "AutosarMaxShortNameLength", + "original": "128", + "save": "128", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "DisableAUTOSARRoutinesHostLibrary", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "AutosarSchemaVersion", + "original": "R23-11", + "save": "R23-11", + "dynamicInSave": true + } + ], + "emptyStructs": [], + "pasted": [ + { + "to": "csAutosarPasted", + "same": true, + "why": "", + "shape": "Simulink.ConfigSet[1 1]", + "kids": "Simulink.ConfigComponent[9 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp}", + "sourceKids": "Simulink.ConfigComponent[9 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp}" + } + ], + "error": "" + } + }, + { + "kind": "paste", + "file": "paste_4.sldd", + "original": "orig_old_saves_r2027a_text.sldd", + "source": "orig_old_saves_r2027a_binary.sldd", + "pasted": { + "section": "config", + "from": "csSimscape", + "to": "csSimscapePasted" + }, + "dynamic": [], + "sha": "faac406a16d238fb6f083290eda776a30a5dc1c90a63f0e42619fa0078fbdb67", + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": true, + "entries": { + "checked": 10, + "same": 10, + "differ": [] + }, + "dynamic": [], + "emptyStructs": [], + "pasted": [ + { + "to": "csSimscapePasted", + "same": true, + "why": "", + "shape": "Simulink.ConfigSet[1 1]", + "kids": "Simulink.ConfigComponent[10 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp,SSC.SimscapeCC}", + "sourceKids": "Simulink.ConfigComponent[10 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp,SSC.SimscapeCC}" + } + ], + "error": "" + } + }, + { + "kind": "paste", + "file": "paste_5.sldd", + "original": "orig_old_saves_r2027a_text.sldd", + "source": "orig_old_saves_r2027a_binary.sldd", + "pasted": [ + { + "section": "other", + "from": "holdsMixed", + "to": "holdsMixedPasted" + }, + { + "section": "other", + "from": "holdsBase", + "to": "holdsBasePasted" + }, + { + "section": "config", + "from": "csAutosar", + "to": "csAutosarPasted" + }, + { + "section": "config", + "from": "csSimscape", + "to": "csSimscapePasted" + } + ], + "dynamic": [ + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarMatrixIOAsArray", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarCompilerAbstraction", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarMaxShortNameLength", + "value": "128.0" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "DisableAUTOSARRoutinesHostLibrary", + "value": "off" + }, + { + "entry": "csAutosar", + "to": "csAutosarPasted", + "name": "AutosarSchemaVersion", + "value": "R23-11" + } + ], + "sha": "a900b4ac24796f3a8c4f725ccb4b41945a7d8d712fe3ba7c5cb67becdbf7fdb2", + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": true, + "entries": { + "checked": 10, + "same": 10, + "differ": [] + }, + "dynamic": [ + { + "entry": "csAutosarPasted", + "name": "AutosarMatrixIOAsArray", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "AutosarCompilerAbstraction", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "AutosarMaxShortNameLength", + "original": "128", + "save": "128", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "DisableAUTOSARRoutinesHostLibrary", + "original": "off", + "save": "off", + "dynamicInSave": true + }, + { + "entry": "csAutosarPasted", + "name": "AutosarSchemaVersion", + "original": "R23-11", + "save": "R23-11", + "dynamicInSave": true + } + ], + "emptyStructs": [], + "pasted": [ + { + "to": "holdsMixedPasted", + "same": true, + "why": "", + "shape": "NdHolder[1 1]", + "kids": "Simulink.Signal[1 2]{dexdata.DerivedSignal,Simulink.Signal}", + "sourceKids": "Simulink.Signal[1 2]{dexdata.DerivedSignal,Simulink.Signal}" + }, + { + "to": "holdsBasePasted", + "same": true, + "why": "", + "shape": "NdHolder[1 1]", + "kids": "Simulink.Signal[1 2]{Simulink.Signal,dexdata.DerivedSignal}", + "sourceKids": "Simulink.Signal[1 2]{Simulink.Signal,dexdata.DerivedSignal}" + }, + { + "to": "csAutosarPasted", + "same": true, + "why": "", + "shape": "Simulink.ConfigSet[1 1]", + "kids": "Simulink.ConfigComponent[9 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp}", + "sourceKids": "Simulink.ConfigComponent[9 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp}" + }, + { + "to": "csSimscapePasted", + "same": true, + "why": "", + "shape": "Simulink.ConfigSet[1 1]", + "kids": "Simulink.ConfigComponent[10 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp,SSC.SimscapeCC}", + "sourceKids": "Simulink.ConfigComponent[10 1]{Simulink.SolverCC,Simulink.DataIOCC,Simulink.OptimizationCC,Simulink.DebuggingCC,Simulink.HardwareCC,Simulink.ModelReferenceCC,Simulink.SFSimCC,Simulink.RTWCC,SlCovCC.ConfigComp,SSC.SimscapeCC}" + } + ], + "error": "" + } + }, + { + "kind": "paste", + "file": "paste_6.sldd", + "original": "orig_old_saves_r2027a_text.sldd", + "source": "orig_old_saves_r2027a_binary.sldd", + "pasted": [ + { + "section": "design", + "from": "sE00", + "to": "sE00Pasted" + }, + { + "section": "design", + "from": "sE02", + "to": "sE02Pasted" + }, + { + "section": "design", + "from": "sE20", + "to": "sE20Pasted" + }, + { + "section": "design", + "from": "sE002", + "to": "sE002Pasted" + }, + { + "section": "design", + "from": "sE10ab", + "to": "sE10abPasted" + }, + { + "section": "design", + "from": "sHolds", + "to": "sHoldsPasted" + } + ], + "dynamic": [], + "sha": "c5538b5710fb679deaa27182d20d7ab969cbc887b052a19c825b058e26566fc2", + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": false, + "entries": { + "checked": 10, + "same": 10, + "differ": [] + }, + "dynamic": [], + "emptyStructs": [], + "pasted": [ + { + "to": "sE00Pasted", + "same": true, + "why": "", + "shape": "struct[0 0]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE02Pasted", + "same": true, + "why": "", + "shape": "struct[0 2]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE20Pasted", + "same": true, + "why": "", + "shape": "struct[2 0]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE002Pasted", + "same": false, + "why": "isequaln 1, sig 0: struct[0 0]<>{} | wanted struct[0 0 2]<>{}", + "shape": "struct[0 0]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE10abPasted", + "same": true, + "why": "", + "shape": "struct[1 0]{a,b}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sHoldsPasted", + "same": true, + "why": "", + "shape": "struct[1 1]{x,e,none}", + "kids": "", + "sourceKids": "" + } + ], + "error": "" + } + }, + { + "kind": "paste", + "file": "paste_7.sldd", + "original": "orig_old_saves_r2027a_text.sldd", + "source": "pre90caf06_old_saves_r2027a_binary.sldd", + "pasted": [ + { + "section": "design", + "from": "sE00", + "to": "sE00Pasted" + }, + { + "section": "design", + "from": "sE02", + "to": "sE02Pasted" + }, + { + "section": "design", + "from": "sE20", + "to": "sE20Pasted" + }, + { + "section": "design", + "from": "sE002", + "to": "sE002Pasted" + }, + { + "section": "design", + "from": "sE10ab", + "to": "sE10abPasted" + }, + { + "section": "design", + "from": "sHolds", + "to": "sHoldsPasted" + } + ], + "dynamic": [], + "sha": "2ce0350c00d68c2e0b61906ee6755f941cf2cb58d2f539298010d0066cd2592f", + "matlab": { + "opened": true, + "warnings": [], + "asOriginal": false, + "entries": { + "checked": 10, + "same": 10, + "differ": [] + }, + "dynamic": [], + "emptyStructs": [], + "pasted": [ + { + "to": "sE00Pasted", + "same": true, + "why": "", + "shape": "struct[0 0]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE02Pasted", + "same": true, + "why": "", + "shape": "struct[0 2]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE20Pasted", + "same": true, + "why": "", + "shape": "struct[2 0]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE002Pasted", + "same": false, + "why": "isequaln 1, sig 0: struct[0 0]<>{} | wanted struct[0 0 2]<>{}", + "shape": "struct[0 0]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sE10abPasted", + "same": true, + "why": "", + "shape": "struct[1 0]{}", + "kids": "", + "sourceKids": "" + }, + { + "to": "sHoldsPasted", + "same": true, + "why": "", + "shape": "struct[1 1]{x,e,none}", + "kids": "", + "sourceKids": "" + } + ], + "error": "" + } + } + ] +} diff --git a/test/parity/matlab/probe_old_saves_readback.m b/test/parity/matlab/probe_old_saves_readback.m new file mode 100644 index 00000000..f9e90fb3 --- /dev/null +++ b/test/parity/matlab/probe_old_saves_readback.m @@ -0,0 +1,454 @@ +% What does MATLAB read of a binary dictionary core saved before two of its fixes, and of what +% core copies now from a binary dictionary into a text one? (M4 and M7 of the 2026-10-13 +% save-fidelity plan, the last of its MATLAB read-backs, and D15's binary-to-text reopen.) +% +% For each row of cases.json, which old_saves.mjs writes: +% +% an old save, an untouched save of a binary fixture by core built before #93 ('pre93'), +% before #93's last commit ('pre69664b0') or before 90caf06 ('pre90caf06'), each of which +% wrote every entry from its model: +% (1) every entry reads as it does in the original: the same class, isequaln, and the same +% `sig` (wbcompare.m's fullsig, which here also names the fields of a struct, so that a +% struct of no element keeps them in its signature); an entry the original is not +% isequaln to itself in, read twice, is held to its class and sig alone (`notSelfEqual`: +% a config set, a handle, is equal to no other); +% (2) each dynamic property the original holds (M4), as get_param reads it of the config +% set in the original and in the save, and whether the save's target component holds it +% as a dynamic property (findprop gives a matlab.metadata.DynamicProperty); +% (3) each struct of no element the original holds at an entry (M7), as MATLAB reads it in +% the save: its class, size and field names; +% (4) and whether the save opens and reads with no error or warning; +% a paste, core's copy of entries of a binary dictionary (old_saves_r2027a_binary.sldd, or its +% pre90caf06 save) into old_saves_r2027a_text.sldd, MATLAB's text twin of it, under new names: +% (1) and (4) for every entry of the text twin; (2) for each pasted config set; and +% (5) each pasted entry as the entry it was copied from reads (class, isequaln, sig; a +% config set's Name aside, which is its entry's name), its shape, and its object array's +% class and the class of each element in order, beside the source's. +% +% Opens only files in OLD_SAVES_OUT: the saves and the pastes, and the copies of the originals +% old_saves.mjs writes beside them. Nothing is saved: every dictionary is closed with +% discardChanges and Simulink.data.dictionary.closeAll('-discard'). +% +% node test/parity/matlab/old_saves.mjs cases /tmp/oldsaves +% env OLD_SAVES_OUT=/tmp/oldsaves mw -using Bmain matlab -nodesktop \ +% -batch "run('$PWD/test/parity/matlab/probe_old_saves_readback.m')" +% cp /tmp/oldsaves/old_saves_readback.truth.json test/parity/matlab/ +% +% Prints OLD_SAVES_READBACK OK on its last line. Output: old_saves_readback.truth.json, {version, +% release, date, originals, cases}: `originals` one row per original or source ({file, opened, +% warnings, entries, notSelfEqual, shapes}), `shapes` each entry's class, size and, for a +% struct, field names; `cases` each row of cases.json, with `matlab`: {opened, warnings, +% asOriginal, entries: {checked, same, differ}, dynamic: [{entry, name, original, save, +% dynamicInSave}], emptyStructs: [{entry, shape}], pasted: [{to, same, why, shape, kids, +% sourceKids}], error}. Messages name files without the directory. +% test/parity/oldSaves.truth.test.ts reads it. + +outdir = getenv('OLD_SAVES_OUT'); +if isempty(outdir), error('set OLD_SAVES_OUT to the directory old_saves.mjs wrote'); end +here = fileparts(mfilename('fullpath')); +fixtures = fullfile(here, '..', '..', 'fixtures'); +addpath(fullfile(fixtures, 'mcos')); % GearMode, Widget, WidgetPart, NdHolder, +dexdata +addpath(fullfile(fixtures, 'display')); % DispLevel, DispWidget +warning('off', 'backtrace'); +try + Simulink.defineIntEnumType('DispColor', {'Red', 'Green', 'Blue'}, [1 2 3]); % as probe_display.m +catch +end +Simulink.data.dictionary.closeAll('-discard'); +fprintf('VERSION %s\n', version); + +cases = jsondecode(fileread(fullfile(outdir, 'cases.json'))); +if isstruct(cases), cases = num2cell(cases); end +originals = {}; +read = containers.Map(); +for i = 1:numel(cases) + c = cases{i}; + t0 = tic; + if ~read.isKey(c.original) + r = readAll(fullfile(outdir, c.original)); + again = readAll(fullfile(outdir, c.original)); + r.notSelfEqual = {}; + for k = 1:numel(r.keys) + a = r.vals(r.keys{k}); + if again.vals.isKey(r.keys{k}) && isempty(a.error) + b = again.vals(r.keys{k}); + if ~isequaln(a.value, b.value) || ~strcmp(a.sig, b.sig) + r.notSelfEqual{end+1} = r.keys{k}; + end + end + end + read(c.original) = r; + originals{end+1} = struct('file', c.original, 'opened', r.opened, 'warnings', {r.warnings}, 'entries', numel(r.keys), ... + 'notSelfEqual', {r.notSelfEqual}, 'shapes', {shapesOf(r)}); %#ok + fprintf('ORIGINAL %-40s opened=%d entries=%d warnings=%d notSelfEqual=%s %s\n', c.original, r.opened, numel(r.keys), numel(r.warnings), strjoin(r.notSelfEqual, ','), r.error); + end + base = read(c.original); + m = struct('opened', false, 'warnings', {{}}, 'asOriginal', false, 'entries', struct(), 'dynamic', {{}}, 'emptyStructs', {{}}, 'pasted', {{}}, 'error', ''); + try + got = readAll(fullfile(outdir, c.file)); + m.opened = got.opened; + m.warnings = got.warnings; + if ~got.opened, error('oldsaves:open', '%s', got.error); end + % (1) every entry of the original + differ = {}; + nsame = 0; + for k = 1:numel(base.keys) + key = base.keys{k}; + if ~got.vals.isKey(key) + differ{end+1} = [key ': not in the save']; %#ok + continue + end + [same, why] = sameValue(got.vals(key), base.vals(key), ismember(key, base.notSelfEqual)); + nsame = nsame + same; + if ~same, differ{end+1} = [key ': ' why]; end %#ok + end + pastedKeys = {}; + from = base; + if strcmp(c.kind, 'paste') + p = c.pasted; + if isstruct(p), p = num2cell(p); end + pastedKeys = reshape(cellfun(@(x) [x.section '|' x.to], p, 'UniformOutput', false), 1, []); + % (5) the pasted entry, as the entry it was copied from reads + if ~read.isKey(c.source), read(c.source) = readAll(fullfile(outdir, c.source)); end + from = read(c.source); + for k = 1:numel(p) + to = [p{k}.section '|' p{k}.to]; + key = [p{k}.section '|' p{k}.from]; + row = struct('to', p{k}.to, 'same', false, 'why', '', 'shape', '', 'kids', '', 'sourceKids', ''); + if ~got.vals.isKey(to) + row.why = 'not in the save'; + elseif ~from.vals.isKey(key) + row.why = 'not in the source'; + else + g = got.vals(to); + % A config set's Name is its entry's: the paste's is the new one. + if isa(g.value, 'Simulink.ConfigSet') + g.value.Name = from.vals(key).value.Name; + g.sig = safesig(g.value); + end + [row.same, row.why] = sameValue(g, from.vals(key), true); + row.shape = g.shape; + row.kids = arrayOf(g.value); + row.sourceKids = arrayOf(from.vals(key).value); + end + m.pasted{end+1} = row; + end + end + extra = setdiff(got.keys, [base.keys, pastedKeys]); + for k = 1:numel(extra), differ{end+1} = [extra{k} ': only in the save']; end %#ok + m.entries = struct('checked', numel(base.keys), 'same', nsame, 'differ', {differ}); + % (2) each dynamic property, as get_param reads it in the original (or the pasted entry's + % source) and in the save + d = c.dynamic; + if isstruct(d), d = num2cell(d); end + for k = 1:numel(d) + key = ['config|' d{k}.entry]; + to = ['config|' d{k}.to]; + m.dynamic{end+1} = struct('entry', d{k}.to, 'name', d{k}.name, 'original', paramOf(from, key, d{k}.name), 'save', paramOf(got, to, d{k}.name), ... + 'dynamicInSave', isDynamic(got, to, d{k}.name)); + end + if strcmp(c.kind, 'old save') + % (3) each struct of no element, as MATLAB reads it in the save + e = c.emptyStructs; + if isstruct(e), e = num2cell(e); end + for k = 1:numel(e) + key = ['design|' e{k}.entry]; + shape = ''; + if got.vals.isKey(key), shape = got.vals(key).shape; end + m.emptyStructs{end+1} = struct('entry', e{k}.entry, 'shape', shape); + end + end + m.asOriginal = got.opened && numel(got.warnings) == numel(base.warnings) && isempty(differ) && ... + all(cellfun(@(x) x.same, m.pasted)); + catch err + m.error = strrep(err.message, newline, ' '); + end + Simulink.data.dictionary.closeAll('-discard'); + c.matlab = m; + cases{i} = c; + fprintf('%-4s %-40s %5.1fs %s\n', tern(m.asOriginal, 'OK', 'NOT'), c.file, toc(t0), summary(m)); +end +for i = 1:numel(cases) + cases{i}.matlab.error = unrooted(cases{i}.matlab.error, outdir); + cases{i}.matlab.warnings = unrooted(cases{i}.matlab.warnings, outdir); +end +for i = 1:numel(originals) + originals{i}.warnings = unrooted(originals{i}.warnings, outdir); +end +truth = struct('version', version, 'release', version('-release'), 'date', char(datetime('now', 'Format', 'yyyy-MM-dd')), ... + 'originals', {originals}, 'cases', {cases}); +fid = fopen(fullfile(outdir, 'old_saves_readback.truth.json'), 'w', 'n', 'UTF-8'); +fprintf(fid, '%s\n', jsonencode(truth, 'PrettyPrint', true)); +fclose(fid); +fprintf('OLD_SAVES_READBACK OK\n'); + +function x = unrooted(x, outdir) + for root = {['/private' outdir], outdir} + x = strrep(x, [root{1} filesep], ''); + end +end + +% ---- every entry of a dictionary, read once, with what the open and the reads warned ----------- +function r = readAll(path) + Simulink.data.dictionary.closeAll('-discard'); + r = struct('opened', false, 'warnings', {{}}, 'error', '', 'keys', {{}}, 'vals', containers.Map()); + lastwarn(''); + dd = []; + try + printed = evalc('dd = Simulink.data.dictionary.open(path);'); + r.warnings = noted(r.warnings, printed); + r.opened = true; + for s = {'design', 'other', 'config'} + sec = getSection(dd, sectionName(s{1})); + entries = find(sec); %#ok + for k = 1:numel(entries) + key = [s{1} '|' entries(k).Name]; + v = struct('class', '', 'value', {[]}, 'sig', '', 'shape', '', 'error', ''); + try + x = []; + printed = evalc('x = getValue(entries(k));'); + r.warnings = noted(r.warnings, printed); + v.class = class(x); + v.value = x; + v.sig = safesig(x); + v.shape = shapeOf(x); + catch err + v.error = err.identifier; + r.warnings{end+1} = sprintf('%s: %s', key, strrep(err.message, newline, ' ')); + end + r.keys{end+1} = key; + r.vals(key) = v; + end + end + catch err + r.error = strrep(err.message, newline, ' '); + end + [msg, id] = lastwarn; + if ~isempty(msg), r.warnings{end+1} = sprintf('lastwarn %s: %s', id, strrep(msg, newline, ' ')); end + if ~isempty(dd) + try, discardChanges(dd); close(dd); catch, end + end + Simulink.data.dictionary.closeAll('-discard'); +end + +% Each entry's shape, as {key, shape}, of a dictionary readAll read. +function out = shapesOf(r) + out = {}; + for k = 1:numel(r.keys) + out{end+1} = struct('key', r.keys{k}, 'shape', r.vals(r.keys{k}).shape); %#ok + end +end + +% A value's class and size, and for a struct its field names: `struct[0 2]{a,b}`. +function s = shapeOf(x) + s = [class(x) mat2str(size(x))]; + if isstruct(x), s = [s '{' joinparts(fieldnames(x)') '}']; end +end + +% The object array a pasted value holds, as its class, size and each element's class: an +% NdHolder's Kids, a config set's Components. +function s = arrayOf(x) + s = ''; + try + if isa(x, 'Simulink.ConfigSet') + a = x.Components; + elseif isprop(x, 'Kids') + a = x.Kids; + else + return + end + s = [class(a) mat2str(size(a)) '{' joinparts(arrayfun(@(k) {class(a(k))}, 1:numel(a))) '}']; + catch err + s = ['<' err.identifier '>']; + end +end + +% A config set's parameter, as get_param reads it, as text; or what get_param threw. +function s = paramOf(r, key, name) + s = ''; + if ~r.vals.isKey(key), return, end + v = r.vals(key); + try + x = get_param(v.value, name); + if ischar(x), s = x; else, s = mat2str(x); end + catch err + s = ['<' err.identifier '>']; + end +end + +% Whether a config set's target component holds the property as a dynamic one, as MATLAB adds +% the options of a target file. +function b = isDynamic(r, key, name) + b = false; + if ~r.vals.isKey(key), return, end + try + t = r.vals(key).value.getComponent('Code Generation').getComponent('Target'); + b = isa(findprop(t, name), 'matlab.metadata.DynamicProperty'); + catch + end +end + +% The same class, isequal (NaN equal to itself), and the same leaf-by-leaf signature; or the +% same error. `bySignature`, for a value the original is not isequaln to itself in: the class and +% the signature alone. +function [same, why] = sameValue(got, want, bySignature) + if nargin < 3, bySignature = false; end + why = ''; + if ~isempty(got.error) || ~isempty(want.error) + same = strcmp(got.error, want.error); + if ~same, why = sprintf('error %s, not %s', got.error, want.error); end + return + end + if ~strcmp(got.class, want.class) + same = false; + why = sprintf('class %s, not %s', got.class, want.class); + return + end + eq = bySignature || isequaln(got.value, want.value); + sg = strcmp(got.sig, want.sig); + same = eq && sg; + if ~same + why = sprintf('isequaln %d, sig %d: %s | wanted %s', eq, sg, trunc(firstDiff(got.sig, want.sig)), trunc(firstDiff(want.sig, got.sig))); + end +end + +% A signature from the first character where it differs from another, with a little before it. +function s = firstDiff(a, b) + n = min(numel(a), numel(b)); + k = find(a(1:n) ~= b(1:n), 1); + if isempty(k), k = n + 1; end + s = a(max(1, k - 80):end); +end + +function w = noted(w, printed) + printed = strtrim(printed); + if ~isempty(printed), w{end+1} = strrep(printed, newline, ' '); end +end + +function s = sectionName(key) + switch key + case 'design', s = 'Design Data'; + case 'other', s = 'Other Data'; + case 'config', s = 'Configurations'; + otherwise, error('oldsaves:section', 'no section %s', key); + end +end + +function s = summary(m) + if ~isempty(m.error), s = ['ERROR ' m.error]; return, end + s = sprintf('entries %d/%d warnings=%d', m.entries.same, m.entries.checked, numel(m.warnings)); + if ~isempty(m.entries.differ), s = [s ' | ' strjoin(m.entries.differ, '; ')]; end + for k = 1:numel(m.dynamic) + d = m.dynamic{k}; + if ~strcmp(d.original, d.save), s = sprintf('%s | %s.%s %s -> %s', s, d.entry, d.name, d.original, d.save); end + end + for k = 1:numel(m.emptyStructs), s = sprintf('%s | %s %s', s, m.emptyStructs{k}.entry, m.emptyStructs{k}.shape); end + for k = 1:numel(m.pasted), s = sprintf('%s | pasted %s same=%d %s %s', s, m.pasted{k}.to, m.pasted{k}.same, m.pasted{k}.kids, m.pasted{k}.why); end + if ~isempty(m.warnings), s = [s ' | ' strjoin(m.warnings, '; ')]; end +end + +function s = tern(b, y, n) + if b, s = y; else, s = n; end +end + +function s = trunc(s) + if numel(s) > 400, s = [s(1:400) ' ...(' num2str(numel(s)) ' chars)']; end +end + +function s = safesig(x) + try + s = fullsig(x); + catch err + s = ['']; + end +end + +function s = joinparts(c) + if isempty(c) || ~iscell(c), s = ''; else, s = strjoin(c, ','); end +end + +% wbcompare.m's fullsig and valstr, copied, with one change: a struct's signature names its +% fields first, so that a struct of no element that lost them differs from one that has them. +function s = fullsig(v, depth) + if nargin < 2, depth = 0; end + s = [class(v) mat2str(size(v))]; + if depth > 12 + s = [s '/DEPTHCAP']; + return + end + parts = {}; + if isstruct(v) + fn = fieldnames(v); + s = [s '<' joinparts(fn') '>']; + for i = 1:numel(v) + for f = 1:numel(fn) + parts{end+1} = [fn{f} ':' fullsig(v(i).(fn{f}), depth + 1)]; %#ok + end + end + elseif iscell(v) + flat = reshape(v, 1, []); + for i = 1:numel(flat) + parts{end+1} = fullsig(flat{i}, depth + 1); %#ok + end + elseif ischar(v) + s = [s '=''' reshape(v, 1, []) '''']; + return + elseif isstring(v) + parts = arrayfun(@(x) {['"' char(x) '"']}, reshape(v, 1, [])); + s = [s '{' joinparts(parts) '}']; + return + elseif isnumeric(v) || islogical(v) + if ~isreal(v), s = [s '/cplx']; end + if issparse(v), s = [s '/sparse']; end + s = [s '=' valstr(v)]; + return + elseif isenum(v) + parts = arrayfun(@(x) {char(x)}, reshape(v, 1, [])); + s = [s '{' joinparts(parts) '}']; + return + elseif isobject(v) || isa(v, 'handle') + p = properties(v); + gotone = false; + for i = 1:numel(v) + for j = 1:numel(p) + try + parts{end+1} = [p{j} ':' fullsig(v(i).(p{j}), depth + 1)]; %#ok + gotone = true; + catch ME + parts{end+1} = [p{j} ':<' ME.identifier '>']; %#ok + end + end + end + if ~gotone && numel(v) == 1 + try + txt = formattedDisplayText(v, 'SuppressMarkup', true); + parts{end+1} = ['disp:' strtrim(regexprep(char(txt), '\s+', ' '))]; + catch + parts{end+1} = 'disp:'; + end + end + else + return + end + s = [s '{' joinparts(parts) '}']; +end + +function s = valstr(v) + if isempty(v) + s = '[]'; + return + end + if issparse(v) + [ii, jj, xx] = find(v); + s = ['nz:' joinparts(arrayfun(@(a, b) {sprintf('%d,%d', a, b)}, ii', jj')) '=' valstr(full(reshape(xx, 1, [])))]; + return + end + flat = reshape(v, 1, []); + if isinteger(v) || islogical(v) + s = joinparts(arrayfun(@(x) {char(string(x))}, flat)); + elseif isreal(flat) + s = joinparts(arrayfun(@(x) {sprintf('%.17g', x)}, flat)); + else + s = joinparts(arrayfun(@(x) {sprintf('%.17g%+.17gi', real(x), imag(x))}, flat)); + end +end diff --git a/test/parity/oldSaves.truth.test.ts b/test/parity/oldSaves.truth.test.ts new file mode 100644 index 00000000..9fe0d356 --- /dev/null +++ b/test/parity/oldSaves.truth.test.ts @@ -0,0 +1,337 @@ +// Copyright 2026 The MathWorks, Inc. +// +// What MATLAB R2027a read back of binary dictionaries core saved before two of its fixes (M4 and +// M7 of the 2026-10-13 save-fidelity plan, the last of its MATLAB read-backs), and of what core +// copies now from a binary dictionary into a text one (D15's binary-to-text reopen); held to what +// core reads and writes now. +// +// The old saves, in test/parity/oldsaves/, are untouched saves of every binary fixture holding a +// dynamic property or a struct entry of no element, and of old_saves_r2027a_binary.sldd +// (make_old_saves_r2027a.m, for the shapes no other fixture holds), made by core built at +// 50549bd^ ('pre93', before #93), at 69664b0^ ('pre69664b0', #93 less the commit that kept a +// dynamic property's Source and PropertyType) and at 90caf06^ ('pre90caf06', before the struct +// of no element was written as MATLAB writes it). Each of those cores wrote every entry of a save +// from its model. matlab/old_saves.mjs made them, and makes the pastes; +// matlab/probe_old_saves_readback.m had MATLAB open each and wrote what it read to +// matlab/old_saves_readback.truth.json. +// +// What MATLAB read: +// - M4: a dynamic property written as an ordinary one (no Source="dynamic", no PropertyType) is +// read back as the dynamic property it was, with its value. Harmless. A save before #93 is not: +// that core wrote every element of a mixed-class object array as the first element's class, so +// a config set's components were all written as a Simulink.SolverCC (the target component as a +// Simulink.CodeAppCC), and MATLAB reads a config set without the AUTOSAR target's options, and +// a user's subclass of Simulink.Signal beside a Simulink.Signal as two of the first's class. +// - M7: a struct of no element written around an `` is read in the shape its Dimension +// states, with no element and no warning; but that writer wrote none of the fields it declares, +// so a 1x0 struct with fields a and b reads as a 1x0 struct with none. Lost in the file. +// - Other losses of those cores, fixed since, show in the same saves (the shaped empties and +// enumeration values of display/dispcat_bin.sldd); they are listed here, not asked about. +// - D15: core's copy into a text dictionary of a mixed Signal array and of a config set's +// Components (one holding an SSC.SimscapeCC, one the AUTOSAR target's dynamic properties, which +// the text form holds among the ordinary ones) reads in MATLAB as the binary original does. +// +// The test makes the pastes again from the package's source and asks that each is still the one +// MATLAB was asked about, checks that the old saves and their originals are the files MATLAB +// read, and that core reads each old save as MATLAB does, where MATLAB and the file agree. +import { describe, it, expect } from 'vitest'; +import { createHash } from 'node:crypto'; +import { existsSync, readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { createSession, ingest } from '../../src/index.js'; +import { regenerateEntryXml } from '../../src/datamodel/parser/BinarySlddSerializer.js'; +import { + BUILDS, + EMPTIES_OLD_SAVE, + OLD_SAVES_DIR, + TEXT_TWIN, + linesOf, + objectsOf, + pasteSources, + pastesOf, + shapesOf, +} from './matlab/old_saves.mjs'; +import { strFromU8, unzipSync } from 'fflate'; +import '../../src/datamodel/node/data/NodeClassMap.js'; + +type Dynamic = { entry: string; name: string; original: string; save: string; dynamicInSave: boolean }; +type Pasted = { to: string; same: boolean; why: string; shape: string; kids: string; sourceKids: string }; +type Matlab = { + opened: boolean; + warnings: string[] | string; + asOriginal: boolean; + entries: { checked: number; same: number; differ: string[] | string }; + dynamic: Dynamic[] | Dynamic; + emptyStructs: { entry: string; shape: string }[] | { entry: string; shape: string }; + pasted: Pasted[] | Pasted; + error: string; +}; +type Case = { + kind: 'old save' | 'paste'; + build?: string; + fixture?: string; + file: string; + original: string; + source?: string; + sha: { save: string; original: string } | string; + pasted?: { section: string; from: string; to: string }[] | { section: string; from: string; to: string }; + dynamic: { entry: string; to: string; name: string; value: string }[] | { entry: string; to: string; name: string; value: string }; + emptyStructs?: { entry: string; dims: number[]; fields: string[] | string }[] | { entry: string; dims: number[]; fields: string[] | string }; + matlab: Matlab; +}; +type Original = { file: string; opened: boolean; warnings: string[] | string; entries: number; notSelfEqual: string[] | string; shapes: { key: string; shape: string }[] }; + +const at = (rel: string) => fileURLToPath(new URL(rel, import.meta.url)); +const TEST_DIR = at('..'); +const TRUTH: { version: string; release: string; originals: Original[]; cases: Case[] } = JSON.parse( + readFileSync(at('./matlab/old_saves_readback.truth.json'), 'utf8'), +); +/** MATLAB writes a one-element array as the element, and an empty one as []: every list as a list. */ +const list = (x: T[] | T | undefined): T[] => (x === undefined ? [] : ([] as T[]).concat(x)); +const sha = (b: Uint8Array | string) => createHash('sha256').update(b).digest('hex'); +const ab = (u8: Uint8Array) => u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer; +const OLD = TRUTH.cases.filter((c) => c.kind === 'old save'); +const PASTES = TRUTH.cases.filter((c) => c.kind === 'paste'); +const oldSave = (build: string, fixture: string) => OLD.find((c) => c.build === build && c.fixture === fixture)!; +const GENERATED = `${OLD_SAVES_DIR}/old_saves_r2027a_binary.sldd`; +const savedBytes = (file: string) => new Uint8Array(readFileSync(at(`../${OLD_SAVES_DIR}/${file}`))); +const differKeys = (c: Case) => list(c.matlab.entries.differ).map((d) => d.slice(0, d.indexOf(': '))); + +/** An old save, opened by core as it is now. */ +function openSave(file: string) { + const session = createSession(); + return ingest(session, ab(savedBytes(file)), { filename: file }) as any; +} +const entryOf = (root: any, section: string, name: string) => root.getSection(section).children.find((e: any) => e.name === name); + +/** The value core holds for a named property anywhere in a value, and whether its element names a Source for it. */ +function propertyIn(value: unknown, name: string): { value: unknown; source: unknown } | null { + if (!value || typeof value !== 'object') return null; + const o = value as Record; + if (o._properties && typeof o._properties === 'object' && name in o._properties) { + return { value: o._properties[name], source: o._property_source?.[name] ?? null }; + } + for (const v of Object.values(o)) { + const found = propertyIn(v, name); + if (found) return found; + } + return null; +} + +describe("MATLAB's read-back of core's older binary saves (M4, M7) and of its binary-to-text copies (D15)", () => { + it("the truth is MATLAB R2027a's, and every save opened and read with no error or warning", () => { + expect(TRUTH.release).toBe('2027a'); + expect(OLD.length).toBe(Object.keys(BUILDS).length * 3); + expect(PASTES.length).toBe(7); + expect(TRUTH.cases.filter((c) => c.matlab.error !== '' || !c.matlab.opened || list(c.matlab.warnings).length > 0).map((c) => c.file)).toEqual([]); + expect(list(TRUTH.originals).filter((o) => !o.opened || list(o.warnings).length > 0).map((o) => o.file)).toEqual([]); + }); + + it('the old saves in the tree are the files MATLAB read, of the originals it read them against', () => { + for (const c of OLD) { + const s = c.sha as { save: string; original: string }; + expect([c.file, sha(savedBytes(c.file))]).toEqual([c.file, s.save]); + const original = at(`../${c.fixture}`); + if (existsSync(original)) { + expect([c.fixture, sha(new Uint8Array(readFileSync(original)))]).toEqual([c.fixture, s.original]); + } + } + }); + + it('core still makes each paste MATLAB was asked about', () => { + const { cases } = pastesOf({ createSession, ingest }, pasteSources(TEST_DIR), new Uint8Array(readFileSync(at(`../${TEXT_TWIN}`)))); + const asked = (c: Case) => [c.file, c.source, list(c.pasted), list(c.dynamic), c.sha]; + expect(cases.map(asked)).toEqual(PASTES.map(asked)); + }); + + describe('M4: a dynamic property a save wrote as an ordinary one', () => { + const dynamic = shapesOf(new Uint8Array(readFileSync(at(`../${GENERATED}`)))).dynamic; + + it("is how the save before #93's last commit spells the AUTOSAR target's options, where the original and a later save keep Source and PropertyType", () => { + expect(dynamic.map((d: { name: string }) => d.name)).toEqual([ + 'AutosarMatrixIOAsArray', + 'AutosarCompilerAbstraction', + 'AutosarMaxShortNameLength', + 'DisableAUTOSARRoutinesHostLibrary', + 'AutosarSchemaVersion', + ]); + for (const build of ['pre93', 'pre69664b0']) { + for (const line of linesOf(savedBytes(oldSave(build, GENERATED).file), dynamic)) { + expect(line).toMatch(/^

[^<]*<\/P>$/); + } + } + for (const line of linesOf(savedBytes(oldSave('pre90caf06', GENERATED).file), dynamic)) { + expect(line).toMatch(/^

[^<]*<\/P>$/); + } + }); + + it('MATLAB reads as the dynamic property it was, with its value, and the config set as the original', () => { + for (const build of ['pre69664b0', 'pre90caf06']) { + const c = oldSave(build, GENERATED); + expect(list(c.matlab.dynamic).map((d) => [d.name, d.save, d.dynamicInSave])).toEqual(list(c.matlab.dynamic).map((d) => [d.name, d.original, true])); + expect(differKeys(c).filter((k) => k.startsWith('config|'))).toEqual([]); + } + }); + + it('core reads each with the value MATLAB reads; as an ordinary property, there being nothing in the file to say it is not', () => { + const values = new Map(list(oldSave('pre69664b0', GENERATED).matlab.dynamic).map((d) => [d.name, d.save])); + for (const [build, source] of [ + ['pre69664b0', null], + ['pre90caf06', 'dynamic'], + ] as const) { + const value = entryOf(openSave(oldSave(build, GENERATED).file), 'config', 'csAutosar').serialize().value; + for (const { name } of dynamic) { + const read = propertyIn(JSON.parse(JSON.stringify(value)), name)!; + expect([name, String(read.value), (read.source as { source?: string } | null)?.source ?? null]).toEqual([name, values.get(name), source]); + } + } + }); + + it("but a save before #93 wrote a mixed array's elements as the first's class: MATLAB reads the config sets and the Signal arrays otherwise, and no AUTOSAR option", () => { + const c = oldSave('pre93', GENERATED); + expect(list(c.matlab.dynamic).map((d) => [d.name, d.save, d.dynamicInSave])).toEqual(dynamic.map((d: { name: string }) => [d.name, '', false])); + expect(differKeys(c).filter((k) => !k.startsWith('design|'))).toEqual(['other|holdsBase', 'other|holdsMixed', 'config|csAutosar', 'config|csSimscape']); + // What that save wrote: the objects' classes in each entry, in order, the char cells aside. + const classes = (bytes: Uint8Array, entry: string) => + [...new Map(objectsOf(strFromU8(unzipSync(bytes)['data/chunk0.xml']))).get(entry)!.matchAll(//g)] + .map((m) => m[1]) + .filter((k) => k !== 'char' && !/^Simulink(\.LoggingInfo|\.CoderInfo|CSC\.)/.test(k)); + const original = new Uint8Array(readFileSync(at(`../${GENERATED}`))); + expect(classes(original, 'holdsMixed')).toEqual(['NdHolder', 'dexdata.DerivedSignal', 'Simulink.Signal']); + expect(classes(savedBytes(c.file), 'holdsMixed')).toEqual(['NdHolder', 'dexdata.DerivedSignal', 'dexdata.DerivedSignal']); + expect(classes(original, 'csAutosar').slice(9, 11)).toEqual(['Simulink.CodeAppCC', 'Simulink.STFCustomTargetCC']); + expect(classes(savedBytes(c.file), 'csAutosar').slice(1, 11)).toEqual([...Array(8).fill('Simulink.SolverCC'), 'Simulink.CodeAppCC', 'Simulink.CodeAppCC']); + expect(classes(savedBytes(oldSave('pre69664b0', GENERATED).file), 'csAutosar')).toEqual(classes(original, 'csAutosar')); + }); + }); + + describe('M7: a struct entry of no element a save wrote around an , without its fields', () => { + const BUILT = ['pre93', 'pre69664b0', 'pre90caf06']; + const FIXTURES = ['fixtures/display/dispcat_bin.sldd', 'parity/artifacts/binary/cases.sldd', GENERATED]; + + it('is how each of those saves spells every one of them, where the original states its fields', () => { + for (const build of BUILT) { + for (const fixture of FIXTURES) { + const c = oldSave(build, fixture); + const original = list(c.emptyStructs); + const saved = shapesOf(savedBytes(c.file)).emptyStructs; + expect(saved.map((s: { entry: string; dims: number[]; fields: string[]; aroundElement: boolean }) => [s.entry, s.dims, s.fields, s.aroundElement])).toEqual( + original.map((s) => [s.entry, s.dims, [], true]), + ); + } + } + const declared = FIXTURES.flatMap((f) => list(oldSave('pre90caf06', f).emptyStructs).filter((s) => list(s.fields).length > 0).map((s) => [s.entry, list(s.fields)])); + expect(declared).toEqual([ + ['dx_struct_0x0_fields', ['a']], + ['dx_struct_1x0', ['a']], + ['sE10ab', ['a', 'b']], + ]); + }); + + it('MATLAB reads in the shape its Dimension states, with no element; the fields are lost, and nothing else of it', () => { + for (const build of BUILT) { + for (const fixture of FIXTURES) { + const c = oldSave(build, fixture); + expect(list(c.matlab.emptyStructs).map((s) => [s.entry, s.shape])).toEqual(list(c.emptyStructs).map((s) => [s.entry, `struct[${s.dims.join(' ')}]{}`])); + const lost = list(c.emptyStructs) + .filter((s) => list(s.fields).length > 0) + .map((s) => `design|${s.entry}`); + const fine = list(c.emptyStructs) + .filter((s) => list(s.fields).length === 0) + .map((s) => `design|${s.entry}`); + expect(differKeys(c).filter((k) => lost.includes(k))).toEqual(lost); + expect(differKeys(c).filter((k) => fine.includes(k))).toEqual([]); + } + // A struct entry's field of no element went out the same way: sHolds.e, a 0x0 struct with field a. + const holds = list(oldSave(build, GENERATED).matlab.entries.differ).find((d) => d.startsWith('design|sHolds: ')); + expect(holds).toContain('e:struct[0 0]<>{}'); + expect(holds).toContain('wanted struct[1 1]{x:double[1 1]=1,e:struct[0 0]{}'); + } + }); + + it('core reads each as MATLAB does: in the shape the Dimension states, with no row and no field, and writes it so', () => { + for (const build of BUILT) { + for (const fixture of FIXTURES) { + const c = oldSave(build, fixture); + const root = openSave(c.file); + for (const s of list(c.matlab.emptyStructs)) { + const entry = entryOf(root, 'design', s.entry); + const value = JSON.parse(JSON.stringify(entry.serialize().value)); + const dims = s.shape.slice('struct['.length, s.shape.indexOf(']')).split(' ').map(Number); + expect([s.entry, entry.displayValue, entry.children.length, value._dimensions, value._fields]).toEqual([s.entry, `<${dims.join('x')} struct>`, 0, dims, []]); + expect(regenerateEntryXml(entry)).toContain(`

`); + } + } + } + }); + + it("and core's copy of each into a text dictionary reads in MATLAB as the old save does; a 0x0x2 as 0x0, as a copy of the original and MATLAB's own text save of it read", () => { + const fromOld = PASTES.find((c) => c.source === EMPTIES_OLD_SAVE)!; + const fromOriginal = PASTES.find((c) => list(c.pasted)[0]?.from === 'sE00' && c.source !== EMPTIES_OLD_SAVE)!; + for (const c of [fromOld, fromOriginal]) { + expect(differKeys(c)).toEqual([]); + expect(list(c.matlab.pasted).filter((p) => !p.same).map((p) => [p.to, p.shape])).toEqual([['sE002Pasted', 'struct[0 0]{}']]); + } + expect(list(fromOld.matlab.pasted).map((p) => [p.to, p.shape])).toEqual([ + ['sE00Pasted', 'struct[0 0]{}'], + ['sE02Pasted', 'struct[0 2]{}'], + ['sE20Pasted', 'struct[2 0]{}'], + ['sE002Pasted', 'struct[0 0]{}'], + ['sE10abPasted', 'struct[1 0]{}'], + ['sHoldsPasted', 'struct[1 1]{x,e,none}'], + ]); + const twin = list(TRUTH.originals).find((o) => o.file === 'orig_old_saves_r2027a_text.sldd')!; + expect(list(twin.shapes).find((s) => s.key === 'design|sE002')!.shape).toBe('struct[0 0]{}'); + }); + }); + + it("the other losses of those saves, each fixed since, are a shaped empty's shape and an enumeration value", () => { + // dispcat_bin.sldd holds every shaped empty: a cell, char or string of no element lost its + // extents (each since kept, in one form at an entry, a property and a cell), and before #93 + // an enumeration value at an entry its enumeral (kept since 146cb64), and a 0x2 char its shape. + const shaped = ['dx_cell_0x3', 'dx_cell_1x0', 'dx_char_1x0', 'dx_string_0x0', 'dx_string_1x0', 'str_0x3']; + const enums = ['dx_char_0x2', 'en_i32_high', 'en_i32_vec', 'en_sl_red', 'en_sl_vec', 'p_enum']; + const m7 = ['dx_struct_0x0_fields', 'dx_struct_1x0']; + const dispcat = 'fixtures/display/dispcat_bin.sldd'; + for (const [build, keys] of [ + ['pre93', [...shaped, ...enums, ...m7]], + ['pre69664b0', [...shaped, ...enums, ...m7]], + ['pre90caf06', [...shaped, ...m7]], + ] as const) { + expect([build, [...differKeys(oldSave(build, dispcat))].sort()]).toEqual([build, keys.map((k) => `design|${k}`).sort()]); + expect([build, differKeys(oldSave(build, 'parity/artifacts/binary/cases.sldd'))]).toEqual([build, []]); + } + }); + + describe("D15: core's copy of a mixed-class object array from a binary dictionary into a text one", () => { + const pasted = (to: string) => PASTES.flatMap((c) => list(c.matlab.pasted).filter((p) => p.to === to)); + + it('reads in MATLAB as the binary original, every other entry as it was', () => { + for (const c of PASTES.filter((c) => c.source !== EMPTIES_OLD_SAVE && list(c.pasted)[0]?.from !== 'sE00')) { + expect([c.file, c.matlab.asOriginal, differKeys(c)]).toEqual([c.file, true, []]); + } + }); + + it("a user's subclass of Simulink.Signal beside a Simulink.Signal, either first, as a Simulink.Signal array of each element's own class", () => { + expect(pasted('holdsMixedPasted').map((p) => [p.same, p.kids, p.sourceKids])).toEqual( + Array(2).fill([true, 'Simulink.Signal[1 2]{dexdata.DerivedSignal,Simulink.Signal}', 'Simulink.Signal[1 2]{dexdata.DerivedSignal,Simulink.Signal}']), + ); + expect(pasted('holdsBasePasted').map((p) => [p.same, p.kids, p.sourceKids])).toEqual( + Array(2).fill([true, 'Simulink.Signal[1 2]{Simulink.Signal,dexdata.DerivedSignal}', 'Simulink.Signal[1 2]{Simulink.Signal,dexdata.DerivedSignal}']), + ); + }); + + it("a config set's Components, as a Simulink.ConfigComponent array, an SSC.SimscapeCC among them", () => { + for (const p of pasted('csSimscapePasted')) { + expect([p.same, p.kids === p.sourceKids, p.kids]).toEqual([true, true, expect.stringMatching(/^Simulink\.ConfigComponent\[10 1\]\{Simulink\.SolverCC,.*,SSC\.SimscapeCC\}$/)]); + } + }); + + it("the AUTOSAR target's dynamic properties, which the text form holds among the ordinary ones, as the dynamic properties they were", () => { + for (const c of PASTES.filter((c) => list(c.dynamic).length > 0)) { + expect(list(c.matlab.dynamic).map((d) => [d.name, d.save === d.original, d.dynamicInSave])).toEqual(list(c.dynamic).map((d) => [d.name, true, true])); + } + expect(PASTES.filter((c) => list(c.dynamic).length > 0).length).toBe(2); + }); + }); +}); diff --git a/test/parity/oldsaves/make_old_saves_r2027a.m b/test/parity/oldsaves/make_old_saves_r2027a.m new file mode 100644 index 00000000..07715d2d --- /dev/null +++ b/test/parity/oldsaves/make_old_saves_r2027a.m @@ -0,0 +1,138 @@ +function make_old_saves_r2027a(outDir) + % Copyright 2026 The MathWorks, Inc. + % Authors old_saves_r2027a_binary.sldd and old_saves_r2027a_text.sldd: one dictionary, in + % each format, holding the shapes no other binary fixture holds that the read-back of core's + % older saves asks about (probe_old_saves_readback.m). Run (one line: mw splits -batch on + % newlines): + % + % mw -using Bmain matlab -nodesktop -batch "cd(''); make_old_saves_r2027a" + % + % `outDir` defaults to this folder. Verify by the WROTE lines and the trailing + % MAKE_OLD_SAVES_R2027A_DONE; a FAIL line means a shape below is not in the file. The + % committed files were written by R2027a (27.1.0.3409468) on 2026-10-18; a re-run gives new + % UUIDs and timestamps, and the same values. The old saves beside them were made from the + % committed binary file (test/parity/matlab/old_saves.mjs), so a re-run here asks for them, + % and for MATLAB's read-back, again. + % + % THE ENTRIES + % Configurations + % csAutosar a default Simulink.ConfigSet switched to autosar.tlc: its target component + % is a Simulink.STFCustomTargetCC, and the options autosar.tlc adds to it are + % written as dynamic properties (Source="dynamic" PropertyType="...") in a + % binary dictionary, and under _dynamic_properties in a text one + % csSimscape a default Simulink.ConfigSet with an SSC.SimscapeCC attached, a component + % no text dictionary lists under Simulink.ConfigComponent, where Simscape is + % installed + % Other Data + % holdsMixed an NdHolder whose Kids is [dexdata.DerivedSignal, Simulink.Signal]: a + % Simulink.Signal array whose first element is a user's subclass of it (a + % dictionary refuses a Simulink.Signal array as an entry's value, and Design + % Data a value of a user's class; Other Data takes one, whose property may hold + % the array) + % holdsBase the same with the base class first, [Simulink.Signal, dexdata.DerivedSignal] + % Design Data + % sE00 struct([]), a 0x0 struct with no fields + % sE02 a 0x2 struct with no fields + % sE20 a 2x0 struct with no fields + % sE002 a 0x0x2 struct with no fields + % sE10ab a 1x0 struct with fields a and b + % sHolds a 1x1 struct whose field e is a 0x0 struct with field a, and whose field + % none is a 0x0 struct with no fields + here = fileparts(mfilename('fullpath')); + if nargin < 1 || isempty(outDir) + outDir = here; + end + addpath(fullfile(here, '..', '..', 'fixtures', 'mcos')); % NdHolder, +dexdata + Simulink.data.dictionary.closeAll('-discard'); + configs = {}; + try + cs = Simulink.ConfigSet; + cs.Name = 'csAutosar'; + switchTarget(cs, 'autosar.tlc', []); + target = cs.getComponent('Code Generation').getComponent('Target'); + fprintf('CONFIG csAutosar target %s components %s\n', class(target), classesOf(cs.Components)); + configs(end + 1, :) = {'csAutosar', cs}; + catch err + fprintf('FAIL csAutosar: %s\n', err.message); + end + try + cs = Simulink.ConfigSet; + cs.Name = 'csSimscape'; + attachComponent(cs, SSC.SimscapeCC); + fprintf('CONFIG csSimscape components %s\n', classesOf(cs.Components)); + configs(end + 1, :) = {'csSimscape', cs}; + catch err + fprintf('FAIL csSimscape: %s\n', err.message); + end + other = {}; + try + mixed = NdHolder; + mixed.Tag = 'mixed'; + mixed.Kids = [dexdata.DerivedSignal, Simulink.Signal]; + base = NdHolder; + base.Tag = 'base'; + base.Kids = [Simulink.Signal, dexdata.DerivedSignal]; + fprintf('MIXED %s %s; BASE %s %s\n', class(mixed.Kids), classesOf(mixed.Kids), class(base.Kids), classesOf(base.Kids)); + other = {'holdsMixed', mixed; 'holdsBase', base}; + catch err + fprintf('FAIL mixed signals: %s\n', err.message); + end + design = {}; + design(end + 1, :) = {'sE00', struct([])}; + design(end + 1, :) = {'sE02', repmat(struct(), 0, 2)}; + design(end + 1, :) = {'sE20', repmat(struct(), 2, 0)}; + design(end + 1, :) = {'sE002', repmat(struct(), [0 0 2])}; + design(end + 1, :) = {'sE10ab', struct('a', cell(1, 0), 'b', cell(1, 0))}; + design(end + 1, :) = {'sHolds', struct('x', 1, 'e', struct('a', {}), 'none', struct([]))}; + formats = {'uncompressed-text', 'text'; 'compressed-binary', 'binary'}; + for i = 1:size(formats, 1) + fn = fullfile(outDir, ['old_saves_r2027a_' formats{i, 2} '.sldd']); + if exist(fn, 'file'), delete(fn); end + dd = Simulink.data.dictionary.create(fn); + dd.FileFormat = formats{i, 1}; + ds = dd.getSection('Design Data'); + for k = 1:size(design, 1) + try + ds.addEntry(design{k, 1}, design{k, 2}); + catch err + fprintf('FAIL %s %s: %s\n', formats{i, 2}, design{k, 1}, err.message); + end + end + os = dd.getSection('Other Data'); + for k = 1:size(other, 1) + try + os.addEntry(other{k, 1}, other{k, 2}); + catch err + fprintf('FAIL %s %s: %s\n', formats{i, 2}, other{k, 1}, err.message); + end + end + cfg = dd.getSection('Configurations'); + for k = 1:size(configs, 1) + try + cfg.addEntry(configs{k, 1}, configs{k, 2}); + catch err + fprintf('FAIL %s %s: %s\n', formats{i, 2}, configs{k, 1}, err.message); + end + end + clear ds os cfg + dd.saveChanges(); + dd.close(); + fprintf('WROTE %s\n', fn); + end + % What MATLAB reads of each entry, back from the binary file. + dd = Simulink.data.dictionary.open(fullfile(outDir, 'old_saves_r2027a_binary.sldd')); + for s = {'Design Data', 'Other Data', 'Configurations'} + entries = find(getSection(dd, s{1})); %#ok + for k = 1:numel(entries) + v = getValue(entries(k)); + fprintf('READ %s %s %s %s\n', s{1}, entries(k).Name, class(v), mat2str(size(v))); + end + end + close(dd); + Simulink.data.dictionary.closeAll('-discard'); + fprintf('MAKE_OLD_SAVES_R2027A_DONE\n'); +end + +function s = classesOf(objects) + s = strjoin(arrayfun(@(k) {class(objects(k))}, 1:numel(objects)), ','); +end diff --git a/test/parity/oldsaves/old_saves_r2027a_binary.sldd b/test/parity/oldsaves/old_saves_r2027a_binary.sldd new file mode 100644 index 0000000000000000000000000000000000000000..dbf925e9582d337905b446f846b0dbae7847ca0b GIT binary patch literal 21785 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 DKYwQN literal 0 HcmV?d00001 diff --git a/test/parity/oldsaves/old_saves_r2027a_text.sldd b/test/parity/oldsaves/old_saves_r2027a_text.sldd new file mode 100644 index 00000000..edaff791 --- /dev/null +++ b/test/parity/oldsaves/old_saves_r2027a_text.sldd @@ -0,0 +1,769 @@ +{ + "__MW_TEXT_COREPROPERTIES__": { + "release": "R2027a" + }, + "__MW_TEXT_PARTS__": { + "__MW_TEXT_PART__/data/chunk0": { + "__MW_TEXT_content": { + "entries": [ + { + "name": "sE00", + "metadata": { + "uuid": "4e7876e5-399b-4a22-be29-31007668dc7d", + "namespace": "dacaf35e-55a5-454d-a7c1-93db038a210e", + "lastmod": "20261019T030832.502587", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_type": "struct", + "_value": "[]" + } + }, + { + "name": "sE002", + "metadata": { + "uuid": "7fd87c02-0923-4d6d-9214-1bdc8dfc2951", + "namespace": "dacaf35e-55a5-454d-a7c1-93db038a210e", + "lastmod": "20261019T030832.505428", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_type": "struct", + "_value": "[]" + } + }, + { + "name": "sE02", + "metadata": { + "uuid": "b52c5ea8-593b-4372-a7b9-83d3ab45b86f", + "namespace": "dacaf35e-55a5-454d-a7c1-93db038a210e", + "lastmod": "20261019T030832.505069", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_type": "struct", + "_value": "[]" + } + }, + { + "name": "sE10ab", + "metadata": { + "uuid": "9a6707de-258e-4094-9d11-19a2a7238113", + "namespace": "dacaf35e-55a5-454d-a7c1-93db038a210e", + "lastmod": "20261019T030832.505616", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_type": "struct", + "_value": "[]" + } + }, + { + "name": "sE20", + "metadata": { + "uuid": "90827797-fc7e-4bf5-826f-5a81d5efbf82", + "namespace": "dacaf35e-55a5-454d-a7c1-93db038a210e", + "lastmod": "20261019T030832.505228", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_type": "struct", + "_value": "[]" + } + }, + { + "name": "sHolds", + "metadata": { + "uuid": "8733824d-6e2e-4e4b-915f-f3155ac74b6c", + "namespace": "dacaf35e-55a5-454d-a7c1-93db038a210e", + "lastmod": "20261019T030832.505772", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_array_type": "Struct", + "_dimensions": [ + 1, + 1 + ], + "_elements": [ + { + "e": { + "_type": "struct", + "_value": "[]" + }, + "none": { + "_type": "struct", + "_value": "[]" + }, + "x": 1 + } + ], + "_fields": [ + "x", + "e", + "none" + ], + "_mw_element_type": "MATLABArray" + } + }, + { + "name": "csAutosar", + "metadata": { + "uuid": "185eb70b-104e-43a5-a0f3-3aa84b46ca2e", + "namespace": "a3b2532e-8e6e-47f5-94fb-b15daf666a84", + "lastmod": "20261019T030832.510190", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_array_class": "Simulink.ConfigSet", + "_dimensions": [ + 1, + 1 + ], + "_elements": [ + { + "_id": "1", + "_properties": { + "Components": { + "_array_class": "Simulink.ConfigComponent", + "_dimensions": [ + 9, + 1 + ], + "_elements": [ + { + "_id": "2", + "_object_class": "Simulink.SolverCC", + "_properties": { + "SolverName": "VariableStepAuto", + "Version": "27.0.0" + } + }, + { + "_id": "3", + "_object_class": "Simulink.DataIOCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "4", + "_object_class": "Simulink.OptimizationCC", + "_properties": { + "DefaultParameterBehavior": "Inlined", + "DifferentSizesBufferReuse": "on", + "EfficientTunableParamExpr": "on", + "EnhancedBackFolding": "on", + "OptimizationCustomize": "off", + "OptimizeBlockOrder": "speed", + "PassReuseOutputArgsAs": "Individual arguments", + "RemoveInternalMemoryZeroInit": "on", + "RemoveRootLevelIOMemoryZeroInit": "on", + "Version": "27.0.0" + } + }, + { + "_id": "5", + "_object_class": "Simulink.DebuggingCC", + "_properties": { + "ParameterTunabilityLossMsg": "error", + "Version": "27.0.0" + } + }, + { + "_id": "6", + "_object_class": "Simulink.HardwareCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "7", + "_object_class": "Simulink.ModelReferenceCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "8", + "_object_class": "Simulink.SFSimCC", + "_properties": { + "CustomCodeFunctionArrayLayout": [], + "MATLABDynamicMemAlloc": "off", + "Version": "27.0.0" + } + }, + { + "_id": "9", + "_object_class": "Simulink.RTWCC", + "_properties": { + "BuildConfiguration": "Faster Builds", + "Components": { + "_array_class": "Simulink.ConfigComponent", + "_dimensions": [ + 2, + 1 + ], + "_elements": [ + { + "_id": "10", + "_object_class": "Simulink.CodeAppCC", + "_properties": { + "ForceParamTrailComments": "on", + "IgnoreCustomStorageClasses": "off", + "InsertBlockDesc": "on", + "OperatorAnnotations": "on", + "SFDataObjDesc": "on", + "ShowEliminatedStatement": "on", + "SimulinkDataObjDesc": "on", + "Version": "27.0.0" + } + }, + { + "_dynamic_properties": { + "AutosarCompilerAbstraction": "off", + "AutosarMatrixIOAsArray": "off", + "AutosarMaxShortNameLength": 128, + "AutosarSchemaVersion": "R23-11", + "DisableAUTOSARRoutinesHostLibrary": "off" + }, + "_id": "11", + "_object_class": "Simulink.STFCustomTargetCC", + "_properties": { + "AutosarCompliant": "on", + "CPPClassGenCompliant": "off", + "CombineOutputUpdateFcns": "on", + "CombineSignalStateStructs": "on", + "CompOptLevelCompliant": "on", + "Components": { + "_id": "12", + "_object_class": "Simulink.ERTTargetCC", + "_properties": { + "AutosarCompliant": "on", + "BooleanFalseId": "FALSE", + "BooleanTrueId": "TRUE", + "CPPClassGenCompliant": "off", + "CombineOutputUpdateFcns": "on", + "CombineSignalStateStructs": "on", + "CompOptLevelCompliant": "on", + "ConcurrentExecutionCompliant": "off", + "ConvertIfToSwitch": "on", + "ERTCustomFileBanners": "on", + "GRTInterface": "off", + "GenerateAllocFcn": "off", + "GenerateSampleERTMain": "off", + "IncludeMdlTerminateFcn": "off", + "LUTObjectStructOrderEvenSpacing": "Size,Table,Breakpoints", + "LookupTableObjectStructAxisOrder": "2,1,3,4,...", + "MATLABClassNameForMDSCustomization": "Simulink.SoftwareTarget.ERTCustomization", + "MatFileLogging": "off", + "MaxIdInt16": "", + "MaxIdInt32": "", + "MaxIdInt64": "", + "MaxIdInt8": "", + "MaxIdUint16": "", + "MaxIdUint32": "", + "MaxIdUint64": "", + "MaxIdUint8": "", + "MinIdInt16": "", + "MinIdInt32": "", + "MinIdInt64": "", + "MinIdInt8": "", + "ModelReferenceCompliant": "on", + "ModelStepFunctionPrototypeControlCompliant": "off", + "MultiwordLength": 256, + "ParMdlRefBuildCompliant": "on", + "RemoveResetFunc": "on", + "SupportComplex": "off", + "SupportContinuousTime": "off", + "SupportNonFinite": "off", + "SupportNonInlinedSFcns": "off", + "SuppressErrorStatus": "on", + "SuppressUnreachableDefaultCases": "on", + "UseToolchainInfoCompliant": "on", + "Version": "27.0.0" + } + }, + "ConcurrentExecutionCompliant": "off", + "ERTCustomFileBanners": "on", + "GRTInterface": "off", + "GenerateAllocFcn": "off", + "GenerateSampleERTMain": "off", + "IncludeMdlTerminateFcn": "off", + "LUTObjectStructOrderEvenSpacing": "Size,Table,Breakpoints", + "LookupTableObjectStructAxisOrder": "2,1,3,4,...", + "MATLABClassNameForMDSCustomization": "Simulink.SoftwareTarget.ERTCustomization", + "MatFileLogging": "off", + "ModelReferenceCompliant": "on", + "ModelStepFunctionPrototypeControlCompliant": "off", + "MultiwordLength": 256, + "ParMdlRefBuildCompliant": "on", + "RemoveResetFunc": "on", + "SupportComplex": "off", + "SupportContinuousTime": "off", + "SupportNonFinite": "off", + "SupportNonInlinedSFcns": "off", + "SuppressErrorStatus": "on", + "SystemTargetFile": "autosar.tlc", + "UseToolchainInfoCompliant": "on", + "Version": "27.0.0" + } + } + ] + }, + "DataTypeReplacement": "CDataTypesFixedWidth", + "Description": "AUTOSAR", + "GenCodeOnly": "on", + "SystemTargetFile": "autosar.tlc", + "TemplateMakefile": "ert_default_tmf", + "Toolchain": "Automatically locate an installed toolchain", + "Version": "27.0.0" + } + }, + { + "_id": "13", + "_object_class": "SlCovCC.ConfigComp", + "_properties": { + "CovEnable": "off", + "CovIncludeTopModel": "on", + "CovModelRefEnable": "off", + "CovScope": "EntireSystem", + "Description": "Simulink Coverage Configuration Component", + "Version": "27.0.0" + } + } + ] + }, + "ConfigPrmDlgPosition": [ + 0, + 0, + 0, + 0 + ], + "CurrentDlgPage": "", + "ExtraOptions": "", + "Name": "csAutosar", + "Version": "27.0.0" + } + } + ], + "_mw_element_type": "MATLABArray" + } + }, + { + "name": "csSimscape", + "metadata": { + "uuid": "9ea55741-afc2-4183-b145-87155f8f40dd", + "namespace": "a3b2532e-8e6e-47f5-94fb-b15daf666a84", + "lastmod": "20261019T030832.520987", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_array_class": "Simulink.ConfigSet", + "_dimensions": [ + 1, + 1 + ], + "_elements": [ + { + "_id": "1", + "_properties": { + "Components": { + "_array_class": "Simulink.ConfigComponent", + "_dimensions": [ + 10, + 1 + ], + "_elements": [ + { + "_id": "2", + "_object_class": "Simulink.SolverCC", + "_properties": { + "SolverName": "VariableStepAuto", + "Version": "27.0.0" + } + }, + { + "_id": "3", + "_object_class": "Simulink.DataIOCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "4", + "_object_class": "Simulink.OptimizationCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "5", + "_object_class": "Simulink.DebuggingCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "6", + "_object_class": "Simulink.HardwareCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "7", + "_object_class": "Simulink.ModelReferenceCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "8", + "_object_class": "Simulink.SFSimCC", + "_properties": { + "CustomCodeFunctionArrayLayout": [], + "Version": "27.0.0" + } + }, + { + "_id": "9", + "_object_class": "Simulink.RTWCC", + "_properties": { + "BuildConfiguration": "Faster Builds", + "Components": { + "_array_class": "Simulink.ConfigComponent", + "_dimensions": [ + 2, + 1 + ], + "_elements": [ + { + "_id": "10", + "_object_class": "Simulink.CodeAppCC", + "_properties": { + "Version": "27.0.0" + } + }, + { + "_id": "11", + "_object_class": "Simulink.GRTTargetCC", + "_properties": { + "CPPClassGenCompliant": "on", + "CombineOutputUpdateFcns": "on", + "CompOptLevelCompliant": "on", + "ConcurrentExecutionCompliant": "on", + "ERTCustomFileBanners": "off", + "GRTInterface": "off", + "GenerateAllocFcn": "off", + "GenerateSampleERTMain": "off", + "MATLABClassNameForMDSCustomization": "Simulink.SoftwareTarget.GRTCustomization", + "MatFileLogging": "on", + "ModelReferenceCompliant": "on", + "ModelStepFunctionPrototypeControlCompliant": "off", + "MultiwordLength": 2048, + "ParMdlRefBuildCompliant": "on", + "SupportContinuousTime": "on", + "SupportNonFinite": "on", + "SupportNonInlinedSFcns": "on", + "UseToolchainInfoCompliant": "on", + "Version": "27.0.0" + } + } + ] + }, + "Description": "Generic Real-Time Target", + "SystemTargetFile": "grt.tlc", + "Toolchain": "Automatically locate an installed toolchain", + "Version": "27.0.0" + } + }, + { + "_id": "12", + "_object_class": "SlCovCC.ConfigComp", + "_properties": { + "CovEnable": "off", + "CovIncludeTopModel": "on", + "CovModelRefEnable": "off", + "CovScope": "EntireSystem", + "Description": "Simulink Coverage Configuration Component", + "Version": "27.0.0" + } + }, + { + "_id": "13", + "_object_class": "SSC.SimscapeCC", + "_properties": { + "Components": { + "_id": "14", + "_object_class": "simmechanics.ConfigurationSet", + "_properties": { + "Components": { + "_array_class": "Simulink.CustomCC", + "_dimensions": [ + 2, + 1 + ], + "_elements": [ + { + "_id": "15", + "_object_class": "simmechanics.DiagnosticsConfigSet", + "_properties": { + "SimMechanicsConflictingReferenceFrames": "warning", + "SimMechanicsCrossSectionNullEdge": "warning", + "SimMechanicsInvalidVisualProperty": "warning", + "SimMechanicsJointTargetOverSpecification": "error", + "SimMechanicsRedundantBlock": "warning", + "SimMechanicsRigidlyBoundBlock": "error", + "SimMechanicsUnconnectedFramePorts": "warning", + "SimMechanicsUnconnectedGeometryPorts": "warning", + "SimMechanicsUnsatisfiedHighPriorityTargets": "warning", + "Version": "27.0.0" + } + }, + { + "_id": "16", + "_object_class": "simmechanics.ExplorerConfigSet", + "_properties": { + "InternalSimMechanicsExplorerSettings": "", + "InternalSimMechanicsPlaybackDataRate": "30", + "InternalSimMechanicsSavePlaybackData": "none", + "InternalSimMechanicsSavePlaybackDataVariable": "smplaybacklog", + "SimMechanicsOpenEditorOnUpdate": "on", + "Version": "27.0.0" + } + } + ] + }, + "Version": "27.0.0" + } + }, + "EditingMode": "Full", + "ExplicitSolverDiagnosticOptions": "warning", + "GlobalZcOffDiagnosticOptions": "warning", + "SelectedTab": "", + "SimscapeAutoSelectReusableComponents": "on", + "SimscapeCacheCompileArtifacts": "on", + "SimscapeCompileArtifactsDiskCache": "off", + "SimscapeCompileComponentReuse": "off", + "SimscapeLogDataHistory": 10000, + "SimscapeLogDecimation": 1, + "SimscapeLogLimitData": "on", + "SimscapeLogName": "simlog", + "SimscapeLogOpenViewer": "off", + "SimscapeLogSimulationStatistics": "off", + "SimscapeLogType": "all", + "SimscapeMultithreadedCompilation": "on", + "SimscapeNominalValues": "[{\"value\":\"1\",\"unit\":\"A\"},{\"value\":\"1\",\"unit\":\"bar\"},{\"value\":\"1\",\"unit\":\"cm^2\"},{\"value\":\"1\",\"unit\":\"cm^3/s\"},{\"value\":\"1\",\"unit\":\"kJ/kg\"},{\"value\":\"1\",\"unit\":\"kW\"},{\"value\":\"1\",\"unit\":\"l\"},{\"value\":\"1\",\"unit\":\"N\"},{\"value\":\"1\",\"unit\":\"N*m\"},{\"value\":\"1\",\"unit\":\"V\"},{\"value\":\"0.001\",\"unit\":\"Wb\"}]", + "SimscapeNormalizeSystem": "off", + "SimscapeOperatingPoint": "", + "SimscapeUseEnergyAccounting": "off", + "SimscapeUseOperatingPoints": "off", + "Version": "27.0.0" + } + } + ] + }, + "ConfigPrmDlgPosition": [ + 0, + 0, + 0, + 0 + ], + "CurrentDlgPage": "", + "ExtraOptions": "", + "Name": "csSimscape", + "Version": "27.0.0" + } + } + ], + "_mw_element_type": "MATLABArray" + } + }, + { + "name": "holdsBase", + "metadata": { + "uuid": "bf293156-5cd5-4341-ad52-ec7964744b09", + "namespace": "42516768-0ace-4981-8ac7-0a9b32cba471", + "lastmod": "20261019T030832.509844", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_array_class": "NdHolder", + "_dimensions": [ + 1, + 1 + ], + "_elements": [ + { + "_id": "1", + "_properties": { + "Kids": { + "_array_class": "Simulink.Signal", + "_dimensions": [ + 1, + 2 + ], + "_elements": [ + { + "_id": "2", + "_properties": { + "CoderInfo": { + "_id": "4", + "_object_class": "Simulink.CoderInfo", + "_properties": { + "CSCPackageName": "Simulink", + "CustomAttributes": { + "_id": "5", + "_object_class": "SimulinkCSC.AttribClass_Simulink_Default", + "_properties": {} + }, + "CustomStorageClass": "Default", + "ParameterOrSignal": "Signal", + "StorageClass": "Auto" + } + }, + "LoggingInfo": { + "_id": "3", + "_object_class": "Simulink.LoggingInfo", + "_properties": {} + } + } + }, + { + "_id": "6", + "_object_class": "dexdata.DerivedSignal", + "_properties": { + "CoderInfo": { + "_id": "8", + "_object_class": "Simulink.CoderInfo", + "_properties": { + "CSCPackageName": "Simulink", + "CustomAttributes": { + "_id": "9", + "_object_class": "SimulinkCSC.AttribClass_Simulink_Default", + "_properties": {} + }, + "CustomStorageClass": "Default", + "ParameterOrSignal": "Signal", + "StorageClass": "Auto" + } + }, + "LoggingInfo": { + "_id": "7", + "_object_class": "Simulink.LoggingInfo", + "_properties": {} + } + } + } + ] + }, + "Tag": "base" + } + } + ], + "_mw_element_type": "MATLABArray" + } + }, + { + "name": "holdsMixed", + "metadata": { + "uuid": "969ed16c-ddf7-4f08-a565-78ba997a999c", + "namespace": "42516768-0ace-4981-8ac7-0a9b32cba471", + "lastmod": "20261019T030832.509126", + "modifiedby": "weiwang", + "isderived": "0" + }, + "value": { + "_array_class": "NdHolder", + "_dimensions": [ + 1, + 1 + ], + "_elements": [ + { + "_id": "1", + "_properties": { + "Kids": { + "_array_class": "Simulink.Signal", + "_dimensions": [ + 1, + 2 + ], + "_elements": [ + { + "_id": "2", + "_object_class": "dexdata.DerivedSignal", + "_properties": { + "CoderInfo": { + "_id": "4", + "_object_class": "Simulink.CoderInfo", + "_properties": { + "CSCPackageName": "Simulink", + "CustomAttributes": { + "_id": "5", + "_object_class": "SimulinkCSC.AttribClass_Simulink_Default", + "_properties": {} + }, + "CustomStorageClass": "Default", + "ParameterOrSignal": "Signal", + "StorageClass": "Auto" + } + }, + "LoggingInfo": { + "_id": "3", + "_object_class": "Simulink.LoggingInfo", + "_properties": {} + } + } + }, + { + "_id": "6", + "_properties": { + "CoderInfo": { + "_id": "8", + "_object_class": "Simulink.CoderInfo", + "_properties": { + "CSCPackageName": "Simulink", + "CustomAttributes": { + "_id": "9", + "_object_class": "SimulinkCSC.AttribClass_Simulink_Default", + "_properties": {} + }, + "CustomStorageClass": "Default", + "ParameterOrSignal": "Signal", + "StorageClass": "Auto" + } + }, + "LoggingInfo": { + "_id": "7", + "_object_class": "Simulink.LoggingInfo", + "_properties": {} + } + } + } + ] + }, + "Tag": "mixed" + } + } + ], + "_mw_element_type": "MATLABArray" + } + } + ], + "Dictionary References": [], + "AllowAccessBWS": false + } + } + } +} \ No newline at end of file diff --git a/test/parity/oldsaves/pre69664b0_cases.sldd b/test/parity/oldsaves/pre69664b0_cases.sldd new file mode 100644 index 0000000000000000000000000000000000000000..d92ecba591c6863144ff525a2b5497ef1cf3d75b GIT binary patch literal 7689 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 OTf2EvMV;oK-v0q&hIY&V literal 0 HcmV?d00001 diff --git a/test/parity/oldsaves/pre69664b0_old_saves_r2027a_binary.sldd b/test/parity/oldsaves/pre69664b0_old_saves_r2027a_binary.sldd new file mode 100644 index 0000000000000000000000000000000000000000..c1f1e73115bf798103af1291f6252da3555fcf60 GIT binary patch literal 21843 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 literal 0 HcmV?d00001 diff --git a/test/parity/oldsaves/pre90caf06_dispcat_bin.sldd b/test/parity/oldsaves/pre90caf06_dispcat_bin.sldd new file mode 100644 index 0000000000000000000000000000000000000000..edb0d7438129aa0e62f3ed43e38101b1736280e3 GIT binary patch literal 9906 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 literal 0 HcmV?d00001 diff --git a/test/parity/oldsaves/pre90caf06_old_saves_r2027a_binary.sldd b/test/parity/oldsaves/pre90caf06_old_saves_r2027a_binary.sldd new file mode 100644 index 0000000000000000000000000000000000000000..5e534f04e05a8f8289652fbd6cc3e183bb7700d9 GIT binary patch literal 21920 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 literal 0 HcmV?d00001 diff --git a/test/parity/oldsaves/pre93_cases.sldd b/test/parity/oldsaves/pre93_cases.sldd new file mode 100644 index 0000000000000000000000000000000000000000..356617fc76f4cbb5727cf58bf29c005974a9ee5f GIT binary patch literal 7689 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 literal 0 HcmV?d00001 diff --git a/test/parity/oldsaves/pre93_dispcat_bin.sldd b/test/parity/oldsaves/pre93_dispcat_bin.sldd new file mode 100644 index 0000000000000000000000000000000000000000..957d61c1e7c1840070512ad5fdbd72b440a6c7b8 GIT binary patch literal 9822 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 OTf2EvMV;oK-v0q2lX;c^ literal 0 HcmV?d00001 diff --git a/test/parity/oldsaves/pre93_old_saves_r2027a_binary.sldd b/test/parity/oldsaves/pre93_old_saves_r2027a_binary.sldd new file mode 100644 index 0000000000000000000000000000000000000000..deb7b4c9d88326d8aac4c84f3cbda9095d94b157 GIT binary patch literal 21706 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 literal 0 HcmV?d00001