Artifact d55c635a3c9b352bd4bda8f55af9ed7d0498be4c3f8cde52c50bae3ee9f5f0cc:
- File
conformance/delta_fuzz_test.go
— part of check-in
[0629de2beb]
at
2026-10-11 01:07:38
on branch trunk
— test(conformance): delta-format conformance suite (#262) (#280)
* test(conformance): delta-format conformance suite (#262)
Check internal/delta against draft-fossil-delta-format-00, requirement by requirement: 39 requirements, 88 cases. Decoder cases (Apply and OutputSize) are byte vectors, each also given to fossil(1) through its SQL delta functions; fossil's verdict must match unless the case records why it follows the draft instead (fossil masks the high bit, does not verify the checksum, and returns the size as a signed int). Each rejection breaks one rule and keeps the trailer valid, so check order cannot hide the rule under test. Producer cases check Create against a strict oracle and against fossil's delta_apply, and fossil's delta_create against Apply.
FuzzDeltaApply compares Apply with an independent model decoder; FuzzDeltaRoundTrip checks Apply(Create(s, t)) == t. DF-3.1-6 (Create has no 2^32 bound) has no case: it needs a 4 GiB input.
* test(conformance): delta suite names the reason each rejection must give
A rejection case could pass because some other rule rejected the input. Every rejection case now names a substring of go-libfossil's error (or ErrChecksum), and the harness fails the case when the message differs.
Vectors that were rejected for the wrong reason are fixed or reattributed: copy-truncated (DF-4-2), reject-10-nul (non-empty count, so the NUL is the opcode), copy-overflow-64 (DF-4-11), and high-bit-count, which now tells a masking decoder from a conforming one (fossil accepts it). The phantom-base case uses a literal-only delta; the content-discard case checks ErrChecksum and the store-side header refusals move to DF-4.1-3. The allocation bound gets its own row (DF-6-5) and takes the least of five runs. DF-3.1-6 notes issue #279. Drops a duplicate size-query case, the sizeCase helper and the decorative fuzz guard bytes. (user: [email protected], size: 2102) [annotate] [blame] [check-ins using]
package conformance import ( "bytes" "testing" "github.com/danmestas/go-libfossil/internal/delta" ) // nonNil copies b, as an empty non-nil slice when b is empty: Apply treats a // nil source as a caller bug. func nonNil(b []byte) []byte { return append([]byte{}, b...) } // FuzzDeltaApply checks Apply against the model decoder for any source and // delta: no panic; success gives exactly the declared size, never more; the // output is what the model gives, which reads only inside the source; // failure returns nothing; and only a delta in a form Section 3.1 leaves to // the decoder may be rejected when the model accepts it. func FuzzDeltaApply(f *testing.F) { for _, c := range deltaTableCases() { f.Add([]byte(c.source), []byte(c.delta)) } f.Add([]byte(b1Source), []byte(b1Delta)) f.Fuzz(func(t *testing.T, src, d []byte) { src, d = nonNil(src), nonNil(d) out, err := delta.Apply(src, d) want, ok, optional := modelApply(src, d) if err != nil { if out != nil { t.Fatalf("failed Apply returned %q", out) } if ok && !optional { t.Fatalf("Apply rejects %q on %q, which Section 4 accepts: %v", d, src, err) } return } if !ok || !bytes.Equal(out, want) { t.Fatalf("Apply(%q, %q) = %q; the model says %q (accepted=%v)", src, d, out, want, ok) } if size, sizeErr := delta.OutputSize(d); sizeErr != nil || size != uint64(len(out)) { t.Fatalf("Apply gave %d bytes; OutputSize says %d (%v)", len(out), size, sizeErr) } }) } // FuzzDeltaRoundTrip checks Section 5 for any source and target: Create's // delta passes the producer oracle, and Apply gives the target back. func FuzzDeltaRoundTrip(f *testing.F) { for _, p := range boundaryPairs() { f.Add(p.src, p.tgt) } f.Fuzz(func(t *testing.T, src, tgt []byte) { d := delta.Create(src, tgt) if finding := producerFinding(src, tgt, d); finding != nil { t.Fatalf("Create(%q, %q) = %q: %s", src, tgt, d, finding.msg) } got, err := delta.Apply(src, d) if err != nil || !bytes.Equal(got, tgt) { t.Fatalf("Apply(%q, Create(%q, %q)) = %q, %v", src, src, tgt, got, err) } }) }