Repository navigation
Tests for S2 and the node ceiling, the group B deviations recorded, and MATLAB read-backs of old core saves (M4, M7) - #150
Merged
Conversation
…ts 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.
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.
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.
… 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 <Element> 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 <Element> is there, it fails. No existing test changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four items of group H's lane L4: tests, docs and a MATLAB record. No
src/change, sodist/is unchanged. No existing test assertion changed.B-05: tests for S2's untested parts (6d4e9f5)
Ruling. Save decisions D1 (S2): "Key an edited entry's Element children by MODEL-NODE IDENTITY ... never by position. Compare Name-keyed P/Field children three-way." D12 addendum: the save gate refuses a save where "every child whose bytes changed must be one the model changed, besides Name and LastMod" does not hold.
What was wrong. Six mutants survived the whole suite: the merge's read-back guard (
BinarySlddSerializer.ts:322,again === line ? merged : null), the layout argument ofspliceEntryObject(:474), and the four arms ofkeptPartsOf(EntryMerge.ts:605, :623, :626, :639). Root cause: nothing exercised them. With the guard instrumented, no test and no fuzz over every binary fixture reached it.Fix (tests only, in
test/binaryEditKeepsUntouched.test.ts). The guard needed no seam. Take a struct entry whose Name line was moved after its Value by hand (theme 1 input), then add a field namedName. Read again, the merge pairs with no entry, so the guard writes the entry from the model. Without the guard, the next edit of an untouched sibling respells it (1.0E+6becomes1000000.0). New cases:spliceEntryObjectlays out a new bus element in the chunk's own style, CRLF with two spaces (:474);keptPartsViolationsflags a child written twice or with no name (:605), an unchanged child not written (:623; a control shows that dropping the edited Min is not flagged), a property the reader does not read that is not kept, D13 (:626), and a bus with more or fewer elements than the model (:639).Fail before. We applied each mutant alone to the full suite. Each one fails exactly its own new case, so before this commit all six survived. One weaker guard still survives: checking only that the merge reads back as some entry (
again !== null). The new case covers a merge that reads back as no entry; no input was found that reads back as a different entry.B-10: the .mat node ceiling is set back after its build (49e0b2f)
Ruling. Group F, group 3: "a node-count ceiling with a source warning now; lazy expansion later."
What was wrong. If
building = outeris deleted frombuildUnderCeiling'sfinally(matNodeCeiling.ts:58-71), or moved out of it, the suite still passes. Root cause: no test builds anything after a ceiling is used up or after a build throws.Fix (test only, in
test/matNodeCeiling.test.ts). A second .mat file cannot show the leak, because it arms its own ceiling. What a ceiling left armed would cut is anything built outside one: MAT streams inside a dictionary, and a model workspace. The case openssparse_text.sldd,sparse_binary.slddandsparse_ws.slxthree times and checks each opens whole (same node count, MAT-stream rows, no_cutByCeiling):buildUnderCeilingaround the realMatNode._addVariablesuses up its ceiling and then throws. The case also checks that the throw reaches the caller.Fail before. Deleting the line fails at the check after the used-up build:
sparse_text.slddopens with 197 nodes, not 254. Moving it into thetryfails at the check after the throwing build. Moving it beforebuilding = ownfails at the check after the used-up build, like deleting the line. On the full suite, the delete and into-the-trymutants each fail only the new case; the before-building = ownmutant was run on this test file only.Left out. No file we found throws out of the ceiling: every variable is caught one at a time. The one input that does throw is a host parse with no
dimensions, and it breaks MatVariable's type, so it was not used.B-23: group B's kept display conventions recorded (221fcc5)
Ruling. Group B, 2026-10-15: "if our display makes more sense than MATLAB, keep ours. For example, to be consistent, we always use
<m x n className>for summary, then keep it. Only fix our display that could cause confusion to user." Also: "Kept: P9."What was wrong.
test/parity/matlab/DESIGN.md:107-113still said these conventions were "pending the maintainer's rulings". The empty-numeric row (now line 44) said every int-backed enumeration's empty shows[ ]. A probe found that true only for text dictionaries. A binary dictionary or a.matgives<1x0 DispLevel>and<0x0 DispColor>.Fix (docs only). The empty-numeric row now covers text dictionaries only. Lines 107-113 are replaced by the quoted ruling, the list of what was fixed instead (P1-P8 and P10 of the 2026-10-16 list), and a table of what is kept. Each row gives our text, MATLAB's browser text and the reason. The rows are Q1, the study's P1 (precision), H1, H2, H4, H6,
true/false, doubled quotes, comma-separated cells, P9, and the binary/.mat int-enum empty. H5 is P7, which was fixed.Tests. None new: the row says "none (docs)". Existing tests already pin every kept item except P9, and H2 (no separate edit text), which is the absence of a feature and has no specific test. No fixture in this repo holds a .mat
VariantExpression.Left out, for the maintainer.
DESIGN.md:52says an empty OnOffSwitchState is<0x0 OnOffSwitchState>. Binary and .mat show the full name,matlab.lang.OnOffSwitchState, and the P3 test pins that. Recommendation: narrow the row to text and record the long name as kept. The other option is a display change that would flip that pin.B-27: MATLAB read-backs of old core saves (M4, M7) and of D15's binary-to-text copies (3a85679)
Ruling. Group F: "build them under their existing rulings: ... the M1-M9 MATLAB checks, the read-backs". Save decisions: "Still open: M4, M7 (record only)."
What was missing. No truth file existed for M4 (dynamic properties in a save from before #93), for M7 (empty struct entries in a save from before 90caf06), or for D15's binary-to-text paste of a mixed Signal array.
What was built (all under
test/parity/).oldsaves/old_saves_r2027a_{binary,text}.slddfrommake_old_saves_r2027a.m. They hold shapes the repo fixtures lack: an AUTOSAR config set with 5 dynamic properties, a config set with SSC.SimscapeCC, a mixed[DerivedSignal, Signal]array, a base-first Signal array, a struct holding empty-struct fields, and empty structs (0x2, 2x0, 0x0x2, 1x0{a,b}; 0x0 empty structs are also in dispcat_bin.sldd and cases.sldd).matlab/probe_old_saves_readback.m, its truth filematlab/old_saves_readback.truth.json,matlab/old_saves.mjs(it makes the saves and the pastes), andoldSaves.truth.test.ts(16 cases).MATLAB truth.
<Element>, reads in its Dimension's shape with no warning. That writer also wrote no<Field>, so the declared fields are lost in the file.Fail before. No truth file existed (
ENOENT). The 9 old saves reproduce from their commits' committed dist in content, not byte for byte (zip timestamps differ), so the test pins the committed files' hashes. We applied three mutants, each tosrcand then reverted: D15's base rule off, the reader dropping_property_source, and a struct with no elements read as 1x1. Each fails 1, 1 and 2 cases.Not changed. Core's model still holds the stray
<Element>as one element. No output path uses it and MATLAB agrees with every output, so under "record only" nothing changed.Gates at the head (3a85679)
rm -rf dist && npm run build: tree clean.npm run verify: exit 0; 296 files passed, 1 skipped; 10211 tests passed, 27 skipped.DEX_READBACKS_CORPUSset:readbacks.truth,m9MergedSaves.truth,oldSaves.truthanddictionaryXmlFastDifferentialpass, 109 of 109. No live MATLAB test reaches these changes. The B-27 probe was run directly in R2027a.Not in this PR: B-28 (smol-toml), also in this lane, is blocked on registry access.
Implements the maintainer's rulings, as audited in group H (2026-10-19).