From c97419ea16bdd0276bee527d2e50b4bb1428fe29 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sat, 14 Nov 2026 13:32:41 +0100 Subject: [PATCH] linearize: the negative control had only one judge in it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #40 asks for this before a linearizing writer exists, and the suite had done half of it. TestAnOrdinaryFileIsRefusedByBothChecks feeds this package's own writer output to Linearized and CheckLinearized and asserts both refuse it — but "both" there means the two checks in this file, which were written together, by me, from the same reading of Annex F. Two judges that have only ever agreed prove nothing about either. ⛔ qpdf WAS THE ONE THAT HAD NOT BEEN ASKED. It validates the hint tables, which this package explicitly does not, so it is the judge that can disagree. It now sees the same ordinary file and says `File is not linearized`. ⛔⛔ AND ITS EXIT STATUS IS 0 FOR A FILE IT HAS JUST CALLED NOT LINEARIZED. Measured on qpdf 12.4.2: $ qpdf --check plain.pdf ; echo $? checking plain.pdf PDF Version: 1.7 File is not encrypted File is not linearized No syntax or stream encoding errors found; ... 0 0 for "not linearized", 3 for a file it merely warns about, 2 only when it cannot read one at all. So the status cannot carry this verdict in either direction and the error is discarded on purpose, with the reason written where somebody will edit it. ⛔ THE PAIR IS WHAT MAKES IT A TEST. On its own, "File is not linearized" could be a line qpdf prints about every file, and the assertion would hold against a judge that judges nothing. So qpdf's OWN linearized output is checked for the absence of that phrase in the same test. ⛔ AND THE CONTROL HAS A CONTROL. If qpdf failed to open the file, it would print no verdict about linearization, and "the phrase is absent" would read exactly like "the file is linearized". Measured on a 16-byte non-PDF: qpdf prints four warnings, never `PDF Version:`, and exits 2. So both halves first require `PDF Version:` in the output — proof it read the file — and t.Fatal otherwise. This is a control, not a gate on new behaviour: nothing in linearized.go changes, and the row it serves stays MISSING until something writes one. gofmt clean, go vet clean, 0 uncovered blocks of 1806. Refs #40. Co-Authored-By: Claude Opus 5 --- linearized_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/linearized_test.go b/linearized_test.go index 60ec460..960856e 100644 --- a/linearized_test.go +++ b/linearized_test.go @@ -845,6 +845,58 @@ func TestAnOrdinaryFileIsRefusedByBothChecks(t *testing.T) { } } +// ⛔⛔ AND THE FOREIGN JUDGE REFUSES IT TOO, which is the half the negative +// control above was missing. go-pdfkit/reader#40 asks for exactly this before +// a linearizing writer exists: two judges that have only ever agreed prove +// nothing about either, so the control is that an ordinary file from this +// package's own writer is refused by BOTH — and qpdf is the one that had not +// been asked. +// +// ⛔⛔ ITS EXIT STATUS IS 0 FOR A FILE IT HAS JUST CALLED NOT LINEARIZED. +// Measured on qpdf 12.4.2: `qpdf --check` over a plain file prints +// +// File is not linearized +// +// and exits 0. A test that read the status would pass against a qpdf that had +// said nothing at all, or that had failed to open the file — the same vacuity +// these checks exist to refuse. The TEXT is the verdict. +func TestQpdfAlsoRefusesAnOrdinaryFileFromThisWriter(t *testing.T) { + plain, linear := linearizedFixture(t, 2) + qpdf := qpdfOrSkip(t) + + check := func(name string, b []byte) string { + t.Helper() + p := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(p, b, 0o644); err != nil { + t.Fatal(err) + } + cmd := exec.Command(qpdf, "--check", p) + cmd.Stdin = nil + // ⛔ The error is DISCARDED on purpose: qpdf exits 0 for a file it + // calls unlinearized and 3 for one it merely warns about, so the + // status cannot carry this verdict in either direction. + out, _ := cmd.CombinedOutput() + got := string(out) + // The control's control: qpdf must have actually read the file, or + // its silence about linearization would be indistinguishable from + // never having opened it. + if !strings.Contains(got, "PDF Version:") { + t.Fatalf("qpdf did not report on %s at all, so what it does not say proves nothing:\n%s", name, got) + } + return got + } + + if got := check("plain.pdf", plain); !strings.Contains(got, "File is not linearized") { + t.Errorf("qpdf did not refuse this writer's ordinary output:\n%s", got) + } + // ⛔ AND THE PAIR IS WHAT MAKES IT A TEST. Without this, "File is not + // linearized" might be a line qpdf prints about every file, and the + // negative above would hold against a judge that judges nothing. + if got := check("linear.pdf", linear); strings.Contains(got, "File is not linearized") { + t.Errorf("qpdf said its OWN linearized output is not linearized, so that phrase decides nothing:\n%s", got) + } +} + // qpdfExtra lets a test ask qpdf for a different kind of linearized file; it // is reset by each subtest that sets it. var qpdfExtra []string