Artifact 468e481204b6ba524ad86a859707708dca88d32513f42ec3abe997ae9c3d6cdf:
- File
conformance/delta_fossil_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: 3479) [annotate] [blame] [check-ins using]
package conformance import ( "encoding/hex" "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" ) // fossil's delta functions live in its SQL layer, which wants a repository // even though these never read one. test-delta-apply cannot serve: it exits 0 // and writes an empty file whether it applied the delta or rejected it. // deltaRepo returns the scratch repository the SQL functions run against, // creating it on first use. func (f *fossilReference) deltaRepo() (string, error) { path := filepath.Join(f.dir, "delta-scratch.fossil") if _, err := os.Stat(path); err == nil { return path, nil } if output, err := exec.Command(f.bin, "new", path).CombinedOutput(); err != nil { return "", fmt.Errorf("fossil new: %v: %s", err, output) } return path, nil } // blobLiteral is b as an SQL hex literal. Bytes travel as hex so that NUL, // quotes and the bytes 0x80-0xFF reach fossil unchanged. func blobLiteral(b []byte) string { return "x'" + hex.EncodeToString(b) + "'" } // deltaSQL evaluates one SELECT expression. fossil reports a rejected delta // as "Error: corrupt fossil delta", with exit status 0; any other failure, // such as the crash on a zero-length blob, is an error, not a verdict. func (f *fossilReference) deltaSQL(expr string) (out string, rejected bool, err error) { if f == nil { panic("conformance: deltaSQL without fossil") } repo, err := f.deltaRepo() if err != nil { return "", false, err } output, runErr := exec.Command(f.bin, "sql", "-R", repo, "SELECT "+expr+";").CombinedOutput() text := strings.TrimSpace(string(output)) if strings.Contains(text, "corrupt fossil delta") { return "", true, nil } if runErr == nil { return text, false, nil } return "", false, fmt.Errorf("fossil sql %.60s: %v: %s", expr, runErr, text) } // refuseEmptyDelta stops a zero-length delta on its way to fossil, whose // delta_apply and delta_output_size crash on one (SIGSEGV). func refuseEmptyDelta(d []byte) error { if len(d) == 0 { return fmt.Errorf("conformance: a zero-length delta must not be sent to fossil") } return nil } // applyDelta is delta_apply(src, d): the output, or rejected. func (f *fossilReference) applyDelta(src, d []byte) (out []byte, accepted bool, err error) { if err := refuseEmptyDelta(d); err != nil { return nil, false, err } text, rejected, err := f.deltaSQL(fmt.Sprintf("hex(delta_apply(%s,%s))", blobLiteral(src), blobLiteral(d))) if err != nil || rejected { return nil, false, err } out, err = hex.DecodeString(text) return out, err == nil, err } // deltaSize is delta_output_size(d). fossil keeps the size in a signed int, // so a header of 2^31 or more is reported as rejected. func (f *fossilReference) deltaSize(d []byte) (size uint64, accepted bool, err error) { if err := refuseEmptyDelta(d); err != nil { return 0, false, err } text, rejected, err := f.deltaSQL(fmt.Sprintf("delta_output_size(%s)", blobLiteral(d))) if err != nil || rejected { return 0, false, err } size, err = strconv.ParseUint(text, 10, 64) return size, err == nil, err } // createDelta is delta_create(src, tgt), fossil's own encoder. func (f *fossilReference) createDelta(src, tgt []byte) ([]byte, error) { text, rejected, err := f.deltaSQL(fmt.Sprintf("hex(delta_create(%s,%s))", blobLiteral(src), blobLiteral(tgt))) if err != nil { return nil, err } if rejected { return nil, fmt.Errorf("fossil delta_create rejected its input") } return hex.DecodeString(text) }