Skip to content

Test legacy line-ending rewriting across chunk boundaries - #34

Merged
amrali-eg merged 3 commits into
masterfrom
test/legacy-line-ending-boundaries
Sep 19, 2026
Merged

amrali-eg merged 3 commits into
masterfrom
test/legacy-line-ending-boundaries

Conversation

@amrali-eg

Copy link
Copy Markdown
Owner

Summary

  • A coverage run showed four parts of LosslessFileWriter.NormalizeBytes, the raw-byte path for legacy encodings, never ran under the suite: the LF and CR target arms, a CR followed by another CR across a chunk boundary, and text after the last line ending.
  • Legacy files are read in 64 KiB chunks and a trailing CR is carried to the next chunk to see whether an LF follows. A mistake there changes the number of lines in a file without any error, so the result must not depend on where the cut falls.
  • Adds three kinds of test: every string of a, CR and LF up to length 6 run under no split, one split and two splits at every position for all three targets and compared with a plain reference (about 105,000 cases); a hand-written CR-then-CR case across two chunks; and a Windows-1252 file with the line ending straddling the real 65536-byte boundary, followed by unterminated text, for all three targets.
  • The code was correct, so this pins behavior rather than fixing a defect. Tests only; no production code changed.

Test plan

  • Mutation-tested in two rounds by breaking the LF swallow, the unterminated-tail copy, the CR-CR carry and the LF arm: each made the matching tests fail
  • LosslessFileWriter.cs: 61 -> 53 missed lines (92.5% -> 93.4%); the only line left in that function is the unreachable default arm
  • Full suite: 324/324 (316 existing + 8 new), 0 warnings

🤖 Generated with Claude Code

amrali-eg and others added 3 commits September 19, 2026 08:32
A coverage run showed four parts of LosslessFileWriter.NormalizeBytes,
the raw-byte path for legacy encodings, never ran under the suite: the
LF and CR target arms, a CR followed by another CR across a chunk
boundary, and text after the last line ending.

Legacy files are read in 64 KiB chunks and a trailing CR is carried to
the next chunk to see whether an LF follows. A mistake there changes
the number of lines in a file without any error, so the result must not
depend on where the cut falls.

Three kinds of test: every string of 'a', CR and LF up to length 6 is
run under no split, one split and two splits at every position for all
three targets and compared with a plain reference (about 105,000
cases); a hand-written CR-then-CR case across two chunks; and a
Windows-1252 file with the line ending straddling the real 65536-byte
boundary, followed by unterminated text, for all three targets.

The code was correct, so this pins behavior rather than fixing a
defect. Mutation-tested in two rounds by breaking the LF swallow, the
unterminated-tail copy, the CR-CR carry and the LF arm: each made the
matching tests fail. LosslessFileWriter.cs 61 -> 53 missed lines.
Full suite 324/324, up from 316.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up from review of this PR.

- Resolve the private members lazily and throw a message that names the
  member, so a rename no longer fails every test with an opaque
  type-initializer error; read the real BufferSize instead of assuming
  65536; register the code page provider in a static constructor.
- Report the target, input and cuts when the exhaustive test fails, and
  replace the >100,000 guard with the exact expected case count.
- Add 0x85 to the exhaustive alphabet and to the end-to-end filler: NEL
  is a line separator in Unicode but an ordinary byte in a legacy file.
- End-to-end cases: the CR at, one byte before and one byte after the
  read boundary for both CR+LF and CR+CR; a file of three reads with a
  line ending at each boundary and a trailing CR; files ending in a CR
  (short, exactly one read, only a CR); all-CR files that fill the output
  buffer to its worst case.
- Test the hash path: the hashes WriteConvertedFileBytes records match
  independently computed XxHash3 and SHA-256 values, and
  VerifyConvertedFileBytes accepts matching output and rejects a changed
  byte and a truncation.
- Correct the class comment: the CR+CR rows are not a CRLF, the
  exhaustive test goes to two cuts not every split, and drop the
  claim about the rest of the suite.

Mutation-tested with eight breaks of NormalizeBytes, the hash, the
verify step and the output buffer; each fails the matching tests. The
output-buffer break only fails because the pool rounds a request up to a
power of two, so the test needs a buffer of exactly BufferSize to catch
it. 38 tests, up from 8; full suite 354. LosslessFileWriter.cs 92.5% ->
93.7%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up from the second review of this PR.

- Check the reference model against a regular expression replace
  (CRLF | CR | LF, CRLF first) over every exhaustive input. The
  reference follows the same rule as the code, so a shared
  misunderstanding would have passed both. Latin-1 maps each byte to one
  character, so the bytes survive the round trip.
- Add an empty file through ConvertFile for all three targets: the read
  loop never runs, so only the final flush and the hash of nothing are
  involved.
- Rename the all-CR test to AllCrFilesDoubleInSizeExactly and say what
  it cannot see. It never filled the buffer to its worst case: a read
  of CRs emits at most twice its size because the last CR stays pending,
  and the pool rounds the request up to a larger array, so only a
  capacity of exactly one read fails. Rename its parameter to fullReads,
  since the file spans one read more than that.
- Say that 0x85 is an ellipsis in Windows-1252, not NEL.
- Unwrap TargetInvocationException in both reflective helpers through
  one InvokeWriter, so a writer failure is not reported as a reflection
  error.

Mutation-tested: a reference that treats CRLF as two endings fails the
oracle test; a final flush that emits a line ending with nothing
pending fails the three empty-file cases among others; an output
buffer of exactly one read fails the renamed test. 42 tests, up from
38; full suite 358.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@amrali-eg
amrali-eg merged commit 90f5c35 into master Sep 19, 2026
1 check passed
@amrali-eg
amrali-eg deleted the test/legacy-line-ending-boundaries branch September 19, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant