From ceee7ab26f16ad2a6a5d31ff37aa88c094edc8b6 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Thu, 23 Jul 2026 15:15:40 -0400 Subject: [PATCH] fix(test): make the snapshot-schema audit tolerate a CRLF checkout (Windows) The v2.2.3 merge went green on the PR but FAILED on `main`, blocking the release. The PR's CI matrix is Linux-only (the `select matrix` job trims it for pull requests); the `main` push runs the full matrix, and `test (windows-latest)` failed in `snapshot_schema_audit` -- a test added in this very release (`244e6c18`), so this is its first Windows run. Two of its cases panicked in `struct_fields` with "unterminated struct body". Root cause is line endings, not logic: the chip source is embedded with `include_str!`, which captures the file with the on-disk endings at compile time, and a Windows checkout without an `eol=lf` attribute for `.rs` yields CRLF. The struct-terminator search is LF-anchored -- `body.find("\n}\n")` -- and `\r\n}\r\n` does NOT contain `\n}\n` (it is `\n}\r`), so the search returned None and the `unwrap_or_else` panicked. The sibling helpers already tolerate CRLF and passed: `writer_body`'s `\n }` anchor survives inside `\r\n }`, and `touches_field`'s `self.` needle never spans a line break -- only `struct_fields` had the LF-only terminator. Fix: strip `\r` at the top of `struct_fields` (it returns owned `Vec`, so a normalized local copy has no borrow implications), making every anchor and the line scan line-ending-agnostic regardless of how the file was checked out. Proven deterministically rather than by waiting on Windows CI (which the PR matrix cannot exercise): feeding a CRLF-converted real `ppu.rs` to the old anchor returns index -1 (the exact panic), and to the fixed parser returns a valid offset. Added `struct_fields_tolerates_crlf_line_endings`, which parses the same synthetic struct in both LF and CRLF form and asserts identical output; being a pure string test it runs on Linux CI too, so the guarantee holds on every platform rather than only where the audit happens to be checked out LF. Verification: `cargo test -p rustynes-test-harness --test snapshot_schema_audit` 7 passed / 0 failed; clippy -p rustynes-test-harness --all-targets -D warnings clean; fmt clean. --- .../tests/snapshot_schema_audit.rs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/crates/rustynes-test-harness/tests/snapshot_schema_audit.rs b/crates/rustynes-test-harness/tests/snapshot_schema_audit.rs index bef916f7..d04ed8ea 100644 --- a/crates/rustynes-test-harness/tests/snapshot_schema_audit.rs +++ b/crates/rustynes-test-harness/tests/snapshot_schema_audit.rs @@ -254,6 +254,18 @@ const CHIPS: &[Chip] = &[ /// `pub(crate)`) is a lowercase identifier followed by `:`. Doc comments, /// attributes, and nested type syntax (`[u8; 8]`, `Option`) never match. fn struct_fields(src: &str, name: &str) -> Vec { + // The chip source is embedded with `include_str!`, which captures the file + // with whatever line endings are on disk at compile time — and a Windows + // checkout without an `eol=lf` attribute for `.rs` gives CRLF. The struct + // terminator search below is LF-anchored (`\n}\n`), and `\r\n}\r\n` does not + // contain `\n}\n` (it is `\n}\r`), so a CRLF checkout made this panic with + // "unterminated struct body" on Windows only. Strip `\r` so every anchor and + // the line scan are line-ending-agnostic. (`writer_body`'s `\n }` anchor + // survives inside `\r\n }`, and `touches_field`'s `self.` needle + // never spans a line break, so both already tolerate CRLF — but normalizing + // here removes the one path that did not.) + let src = src.replace('\r', ""); + let src = src.as_str(); let header = format!("pub struct {name} {{"); let start = src .find(&header) @@ -490,3 +502,35 @@ fn field_boundary_matching_rejects_prefixes() { "oam_bus_addr_h" )); } + +#[test] +fn struct_fields_tolerates_crlf_line_endings() { + // Regression pin for the Windows-only failure: `include_str!` captures the + // chip source with the on-disk line endings, and a checkout without an + // `eol=lf` attribute for `.rs` yields CRLF, whose struct terminator is + // `\r\n}\r\n`. This test feeds CRLF source explicitly so the parser stays + // line-ending-agnostic on every platform, not just where the audit happens + // to be checked out LF. The Linux CI runs it too, so the guarantee holds + // even though the original break only showed on Windows. + // A struct with more than the parser's `> 10` drift-guard threshold, so the + // sanity check inside `struct_fields` passes and only the line-ending + // behavior is under test. Doc comments and attributes are interleaved to + // exercise the same skip paths the real chip structs hit. + use std::fmt::Write as _; + let mut lf = String::from("pub struct Demo {\n"); + for i in 0..12 { + let _ = write!(lf, " /// field {i}\n pub f{i}: u8,\n"); + } + lf.push_str("}\n"); + let crlf = lf.replace('\n', "\r\n"); + + let from_lf = struct_fields(&lf, "Demo"); + let from_crlf = struct_fields(&crlf, "Demo"); + + let expected: Vec = (0..12).map(|i| format!("f{i}")).collect(); + assert_eq!(from_lf, expected, "LF parse baseline"); + assert_eq!( + from_crlf, from_lf, + "CRLF source must yield the same fields as LF source", + ); +}