diff --git a/determinism_test.go b/determinism_test.go new file mode 100644 index 0000000..178b376 --- /dev/null +++ b/determinism_test.go @@ -0,0 +1,67 @@ +package render + +import ( + "crypto/sha256" + "testing" + + "github.com/go-pdfkit/reader" +) + +// TestTheSameFileDrawsTheSamePixelsEveryTime guards the dependency, not this +// package's own code. +// +// An inline image dictionary may carry both spellings of a key — /W beside +// /Width, /CS beside /ColorSpace — and disagree with itself. reader before +// v0.4.2 expanded such a dictionary in one pass over its map, so the winner +// was whichever Go's randomised iteration order yielded last, and the same +// page drew a different picture on different runs of the same binary. Eight +// renders of safedocs' Inline_Image_Abbreviations fixture gave five different +// answers. +// +// Nothing in this package's own tests could see that: every one of them draws +// a page once. Non-determinism is invisible to a suite that never repeats +// itself, which is why this test repeats itself. +// +// The image is built here rather than committed, so nobody else's PDF enters +// the repository. It says /W 2 and /Width 1, and /H 1 and /Height 2: the two +// readings give a 2x1 image and a 1x2 one, which do not cover the same pixels. +func TestTheSameFileDrawsTheSamePixelsEveryTime(t *testing.T) { + w := reader.NewWriter("1.7") + pagesRef := w.Reserve() + content := "q 20 0 0 20 0 0 cm " + + "BI /W 2 /Width 1 /H 1 /Height 2 /CS /G /ColorSpace /DeviceGray /BPC 8 " + + "ID \x00\xff EI Q" + pageRef := w.Add(reader.Dict{"Type": reader.Name("Page"), "Parent": pagesRef, + "MediaBox": nums(0, 0, 20, 20), + "Contents": w.Add(&reader.Stream{Dict: reader.Dict{}, Raw: []byte(content)})}) + w.Put(pagesRef, reader.Dict{"Type": reader.Name("Pages"), + "Kids": reader.Array{pageRef}, "Count": reader.Integer(1)}) + out, err := w.Finish(reader.Dict{"Root": w.Add(reader.Dict{ + "Type": reader.Name("Catalog"), "Pages": pagesRef})}) + if err != nil { + t.Fatal(err) + } + + var first string + for i := 0; i < 40; i++ { + d, err := reader.Open(out) + if err != nil { + t.Fatal(err) + } + img, err := Page(d, 1, Options{Scale: 1}) + if err != nil { + t.Fatal(err) + } + h := sha256.Sum256(img.Pix) + got := string(h[:]) + if i == 0 { + first = got + continue + } + if got != first { + t.Fatalf("draw %d differs from the first: the same file drew "+ + "different pixels, so something in the read path depends on "+ + "map iteration order", i) + } + } +} diff --git a/go.mod b/go.mod index bfc683a..4f5c08b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/go-gfx/gfx v0.10.0 github.com/go-opentype/fonts v0.9.0 github.com/go-opentype/opentype v0.10.0 - github.com/go-pdfkit/reader v0.4.1 + github.com/go-pdfkit/reader v0.4.2 ) require github.com/go-pdfkit/pdffont v0.3.0 diff --git a/go.sum b/go.sum index c500cb1..96588d0 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,5 @@ github.com/go-opentype/opentype v0.10.0 h1:cZVMZ3RVkcijXmxlmpqyVkjlNc33aSB2ugKVW github.com/go-opentype/opentype v0.10.0/go.mod h1:AOixevJf7XQaH7+WG+OMIOZEbYPXfMqklVk26Y6YTUU= github.com/go-pdfkit/pdffont v0.3.0 h1:G5DKcAmsZJ0e17QhSrcUaL7PKEXKjUx4P/iGMl7bAbI= github.com/go-pdfkit/pdffont v0.3.0/go.mod h1:bfmNLna1l1CljNX/Utg55YzFylovfmI7sJnvgA3bzKI= -github.com/go-pdfkit/reader v0.4.1 h1:pRxFqRjsn7H/VsGfWb9nYWyFuDgTU2Pjmoq/f5mgVq4= -github.com/go-pdfkit/reader v0.4.1/go.mod h1:fQFOVfCMUui1AdvD4qhimdyvvNr9KvvJ1S7IuKZjyV8= +github.com/go-pdfkit/reader v0.4.2 h1:0/qPShLzdG/roH4i13dwDTHnS7mEBVTaCBGwCZQhOH0= +github.com/go-pdfkit/reader v0.4.2/go.mod h1:fQFOVfCMUui1AdvD4qhimdyvvNr9KvvJ1S7IuKZjyV8=