Commit 30adbc9
authored
* test(processing): refuse a vacuous pass in the module-size ratchet (#3200)
Finding 5 of the vacuity audit, reproduced by running it rather than by
reading: pointed at an empty directory, and again at one that does not
exist, `no_module_grows_past_its_ratchet_budget` reported success over
zero files both times. Every offender it can report is pushed inside
`for (rel, lines) in files`, so an empty walk gives an empty message
gives a green test.
Three guards now stand in the way:
- `collect_rs_files` no longer turns a failed `read_dir` into an empty
result. A missing scan root and an unreadable one are both hard
errors, and they are distinguished: the first means the walk roots
are wrong, the second means the environment is. A per-entry read
error is loud too, where `entries.flatten()` used to drop it.
- `line_count` no longer reports an unreadable file as 0 lines. 0 is
under LIMIT and under every allowlist budget, so a file this gate
could not open was indistinguishable from one that passes it.
- `FILE_FLOOR`, a measured lower bound on the walk. The healthy tree
reaches 362 non-exempt `.rs` files under `rust/` + `apps/`; the
floor is 240, roughly two thirds. The number only has to separate
"the walk works" from "the walk went blind", and every way it goes
blind takes it to zero or to a handful, never to a plausible
fraction.
Watched fail before being trusted: empty directory -> the floor message;
missing directory -> the missing-root message; a `.rs` file that cannot
be read -> the line-count message. Real tree still green at 362 files.
Refs #3200
* test(processing): refuse a vacuous pass in the styling parity guards (#3200)
Finding 6 of the vacuity audit, reproduced by running it rather than by
reading: pointed at an empty directory, and again at one that does not
exist, both `no_duplicate_default_color_tables` and
`no_duplicate_surface_style_color_extraction` reported success over zero
files, with not even a note on stderr. Both conclude from an ABSENCE, so
zero files scanned gives zero offenders gives green.
Four guards now stand in the way:
- `collect_rs_files` no longer turns a failed `read_dir` into an empty
result. Missing and unreadable scan roots are both hard errors, told
apart because they call for different fixes; a per-entry read error
is loud too, where `entries.flatten()` used to drop it.
- a walked file that cannot be read is a hard error, not a `continue`.
A forbidden declaration sitting in an unreadable file used to read
as "not found", which is the one answer these guards must never
give by accident.
- `SCANNED_FLOOR`, a measured lower bound. The healthy tree reaches
659 `.rs` files under `rust/` + `apps/`; the floor is 440, roughly
two thirds, for the same reason as the module-size ratchet's.
- positive controls, because a file count shows the walk is alive and
nothing about whether the detector still detects. Both detectors are
extracted into named functions and exercised against known-positive
and known-negative inputs, and the surface-style guard additionally
asserts end-to-end that it still matches the one occurrence it is
allowed to find, in `rust/geometry/examples/`.
Watched fail before being trusted: empty directory and missing directory
-> the floor and missing-root messages; a detector stubbed to return
false -> the positive control, "matched nothing at all across 659 walked
file(s)". Real tree still green.
Refs #3200
* fix(scripts): refuse an empty schema registry in the attr-index generator (#3200)
Finding 4 of the vacuity audit. `generate-server-attr-indices.mjs` had no
floor on `rows.length`, unlike its sibling `generate-bim-globals.mjs`
which refuses the identical condition in so many words. Reproduced with a
`packages/parser/dist` that imports cleanly and exports
`SCHEMA_REGISTRY = { name: 'EMPTY', entities: {} }`:
$ node scripts/generate-server-attr-indices.mjs --check
✗ ... is out of sync ... stale row (not in registry): IFCACTIONREQUEST
EXIT=1
$ node scripts/generate-server-attr-indices.mjs # its own remedy
wrote .../attr_indices.rs (0 types, registry EMPTY)
$ node scripts/generate-server-attr-indices.mjs --check
✓ attr_indices.rs in sync (0 types, registry EMPTY)
EXIT=0
Correct verdict, ruinous advice: the emitted `match` has no arms at all,
so `root_attr_indices` returns `None` for every type and — per this
file's own header — every KNOWN type falls back to the unknown-type
indices [3,4,7]. That is the parity break the generator exists to
prevent, and `--check` prints ✓ from then on.
Following the in-repo precedent rather than inventing one: the registry
is checked before EITHER mode proceeds, the way the sandbox generator
already refuses its empty schema. Behind it, a measured floor —
`ROW_FLOOR = 500` against the 776 types a healthy IFC4_ADD2_TC1 build
yields, headroom deep enough to survive a smaller schema (IFC2X3 has
roughly 650) while still catching what it is for, since every way this
extraction goes blind takes the count to zero, not to 499.
Same synthetic tree after the fix: both modes exit 1 naming the count,
the floor and what they expected, and the committed 776-arm table is
left untouched. Real repo still `✓ attr_indices.rs in sync (776 types,
registry IFC4_ADD2_TC1)`.
Refs #3200
* fix(scripts): count RESOLVED attributes, not rows, in the attr-index floor (#3200)
ROW_FLOOR counted rows, so the bug it was written for survived one level
down. `allAttributes` is optional on the schema registry's entity metadata
and `getAllAttributesForEntity` returns `metadata?.allAttributes || []`, so
a registry whose `entities` map is fully populated while `allAttributes`
stops being emitted yields 776 rows every one of which is [-1,-1,-1,-1].
Measured on exactly that shape: the generator wrote all 776 arms and exited
0, and `--check` printed "in sync" on the next run. Per the generated file's
own contract -1 means "known type, does not declare that attribute, never
fall back" — so the server-parse path would report Description / ObjectType
/ Tag / PredefinedType absent for every entity while the browser resolves
them normally.
RESOLVED_FLOOR = 400 closes that: 488 of today's 776 rows resolve at least
one of the four (the other 288 legitimately declare none), so the floor
costs nothing today and cannot be cleared by a table that resolves nothing.
Also raises ROW_FLOOR from 500 to 700 and rewrites its comment, which
justified the headroom with something that is not true: there is no runtime
schema selection here. SCHEMA_REGISTRY is a single committed codegen
artifact pinned to IFC4_ADD2_TC1; the multi-schema union lives in
ifc-schema.ts's ENTITY_INFO_BY_UPPER behind getAttributeNamesAcrossSchemas,
which this generator never calls. Repinning would be a regenerate-and-commit
event, which the failure message already instructs. Measured on the old
value: 499 rows refused and 500 rows WROTE, replacing all 776 committed arms
and exiting 0 — that band bought nothing.
Adds scripts/generate-server-attr-indices.test.mjs, a black-box harness in
the established scripts/*.test.mjs shape (picked up by the glob catch-all
step in test.yml). It pins both floors' boundaries, both refusal modes, and
that a refusal leaves the committed table byte-identical. It fails 4 of 6
against the pre-fix generator.
* fix(scripts): correct three overclaims and pin ROW_FLOOR byte-identity (#3214 followups)
Adversarial review of #3214 (sound, ship it) found accuracy gaps in the
comments and one coverage gap in the test:
- RESOLVED_FLOOR's comment claimed every blind-extraction failure takes
the resolved count to zero, never partway. True for the generator-level
failure (typescript-generator.ts emits allAttributes unconditionally,
so that failure is all-or-nothing) but false for the inheritance-walk
failure in express-parser.ts's getAllAttributes, which silently stops
at a missing supertype instead of erroring. Measured: 201 of 488
resolved entities resolve ONLY via inherited attributes; the costliest
single ancestor is IfcRoot at 70 rows (IfcRelationship 47, IfcObject 31,
IfcTypeProduct 21, IfcElement 20) — all comfortably inside the floor's
slack, so a partial loss there is reachable and RESOLVED_FLOOR alone
would wave it through. --check's drift comparison is the real backstop
for this failure mode.
- "comfortably under the 444" understated the real worst case: a 700-row
subset that keeps all 288 non-resolvers costs 412, not 444, so the
true margin over RESOLVED_FLOOR=400 is 12 rows (2.9%), not 44. The
floors still don't contradict.
- "reachable only through getAttributeNamesAcrossSchemas" was imprecise:
ENTITY_INFO_BY_UPPER is also read by getEntityInfoAcrossSchemas and
getInheritanceChainFromSchemaUnion.
Also seeds a committed table in the ROW_FLOOR test and asserts its hash
survives the refusal, closing a gap where a half-writing ROW_FLOOR mutant
could clobber a seeded table with the whole suite green. And adds a case
for the empty-registry guard's distinct value (a raw missing/non-object
entities key, not just the {} shape ROW_FLOOR already catches).
* fix(tests): refuse the no-repo-root skip under CI, the way around all the #3200 guards
The three source-walking gates hardened by #3200 each open with
let Some(root) = repo_root() else { eprintln!("packaged context"); return; };
which returns BEFORE the walk. That single early return bypasses the
missing-root panic, the unreadable-file panic and the scan floor at once --
every guard the hardening added, skipped by the one path it did not cover.
Reproduced with `repo_root` stubbed to `None`, `CI=true`, at 907f115:
test no_module_grows_past_its_ratchet_budget ... ok
test no_duplicate_default_color_tables ... ok
test no_duplicate_surface_style_color_extraction ... ok
test result: ok. 5 passed ... finished in 0.00s
test result: ok. 6 passed ... finished in 0.00s
Three green gates over a tree never opened, in zero seconds.
It cannot simply become a panic. `tests/` is packaged into the published
`.crate` (no `exclude` in `rust/processing/Cargo.toml`), so a downstream
`cargo test` on `ifc-lite-processing` runs these files with no `rust/` or
`apps/` above them, and skipping is correct there. The two cases cannot be
told apart by looking for the repo -- its absence is the thing being
explained. `CI` is the discriminator, using the truthiness form already in
`rust/geometry/tests/triangulation_invariance.rs` rather than a second
convention for the same variable.
One implementation in `tests/common/mod.rs`, not a copy per binary: two copies
of a gate's escape hatch are two chances to loosen one and not the other.
RED under the stub, verbatim:
thread 'no_module_grows_past_its_ratchet_budget' panicked at
rust/processing/tests/common/mod.rs:36:5:
module-size ratchet: no repo root above CARGO_MANIFEST_DIR (...), but CI
is set. Under CI this gate must scan the tree, not skip it ...
GREEN on the real tree with CI=true: 5 passed / 6 passed, 0.19s and 0.86s --
non-zero, because the walk now happens. The packaged path still skips: with
the stub and CI unset, 5 passed / 6 passed. clippy --tests clean.
Refs #3200
1 parent 006c7b3 commit 30adbc9
5 files changed
Lines changed: 693 additions & 31 deletions
File tree
- rust/processing/tests
- common
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
23 | 36 | | |
24 | 37 | | |
25 | 38 | | |
| |||
43 | 56 | | |
44 | 57 | | |
45 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
46 | 77 | | |
47 | 78 | | |
48 | 79 | | |
| |||
57 | 88 | | |
58 | 89 | | |
59 | 90 | | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
60 | 100 | | |
61 | | - | |
62 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
63 | 115 | | |
64 | | - | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
65 | 124 | | |
66 | 125 | | |
67 | 126 | | |
| |||
114 | 173 | | |
115 | 174 | | |
116 | 175 | | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
117 | 179 | | |
118 | | - | |
119 | | - | |
120 | | - | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
121 | 189 | | |
122 | 190 | | |
123 | 191 | | |
| |||
148 | 216 | | |
149 | 217 | | |
150 | 218 | | |
| 219 | + | |
151 | 220 | | |
152 | 221 | | |
153 | 222 | | |
| |||
169 | 238 | | |
170 | 239 | | |
171 | 240 | | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
172 | 255 | | |
173 | 256 | | |
174 | 257 | | |
| |||
0 commit comments