Artifact fa1ada47f5a1b6d6abe8a559098f52b9de832a9eba6e3814e7c16cef02521d0a:
- File
conformance/fossil_test.go
— part of check-in
[d52a6fe050]
at
2026-10-11 00:42:57
on branch trunk
— test(conformance): artifact-format conformance suite (#263) (#276)
* test(conformance): artifact-format conformance suite (#263)
Adds the suite for draft-fossil-artifact-format-00 with errata 4.1.2, 4.2 and 4.6.4, so the parser, writer, naming, hash policy and delta manifest code are checked requirement by requirement instead of by scattered unit tests that each encode one reading of fossil.
- requirements/artifact.tsv: 101 requirements from Sections 3 to 8, pinned to the draft and the three errata. - Parse cases are a known-good base artifact per type plus one edit, with the Z card recomputed, so the edit alone explains a rejection. Every case also runs through fossil test-parse-manifest; fossil 2.28 agrees with all of them. Card legality, duplicates, arity and hash positions are generated from the bases and the draft's own tables. - Accepted inputs must re-encode byte for byte unless marked non-canonical. - API cases cover naming, verification, the comparator, the writer, the R card, the hash policy and delta-manifest enumeration. - FuzzArtifactRoundTrip checks parse/marshal/parse stability, and TestArtifactCorpus (CONFORMANCE_REPOS) parses every artifact fossil crosslinked in real repositories.
267 cases go-libfossil fails are marked with a "#0" placeholder in artifact_gaps_test.go, grouped by the code at fault, for issues to be filed against.
* test(conformance): tighten the artifact suite after review (#263)
A rejection only counts when the rule the case names makes it, so every rejection case that is not about the Z card must now pass deck.VerifyZ first. Inputs go-libfossil rejected only through the Z check (framing, residue, sterilizer, unstripped wrappers, an extra Z token) become gaps, along with VerifyZ itself, which rejects an absent Z card and reads the last byte where Section 6.2 says it must not.
- Cases can name the substring fossil's rejection reason must contain; the unknown-letter cases sit in sorted position, the sterilizer cases are filed under card order, which is what fossil rejects them for, and generated legality cases take their requirement from the draft's table. - The ratchet that never persists the hash policy is a gap: fossil opening the repository still reads auto. - New rows for 4.7.9, 6.2 (absent Z, last byte), 6.3.4 and 7.1. - The audit refuses the #0 placeholder, a test keeps the gap map and the fuzz test's forgiven losses tied to live gap cases, and one Z-card helper replaces two.
* test(conformance): mark artifact gaps with their issues (#263)
Replace the placeholder gap marks with the issues that track each fix (#265-#275), and keep coverage_test.go within 100 columns. (user: [email protected], size: 2804) [annotate] [blame] [check-ins using]
package conformance import ( "crypto/md5" "encoding/hex" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/danmestas/go-libfossil/testutil" ) // fossilReference runs fossil(1) on the same input a case gives // go-libfossil, so every case's expectation is checked against fossil on // every run rather than once, when the case was written. type fossilReference struct { bin string dir string count int } // newFossilReference finds fossil(1). Without it the cases still run against // go-libfossil, but nothing checks their expectations; CI sets // REQUIRE_FOSSIL_BIN=1 so a missing binary fails there instead. func newFossilReference(t *testing.T) *fossilReference { t.Helper() bin := testutil.FossilBinary() if bin == "" { if os.Getenv("REQUIRE_FOSSIL_BIN") == "1" { t.Fatal("REQUIRE_FOSSIL_BIN=1 but no fossil binary found") } t.Log("fossil not found: case expectations are not checked against fossil(1)") return nil } return &fossilReference{bin: bin, dir: t.TempDir()} } // parseArtifact reports whether fossil's manifest parser accepts data, and // fossil's reason when it does not. test-parse-manifest exits 0 either way, // so the verdict is read from its output. func (f *fossilReference) parseArtifact(data []byte) (accepted bool, reason string, err error) { if f == nil { panic("conformance: parseArtifact without fossil") } f.count++ path := filepath.Join(f.dir, fmt.Sprintf("artifact-%d", f.count)) if err := os.WriteFile(path, data, 0o644); err != nil { return false, "", err } output, err := exec.Command(f.bin, "test-parse-manifest", path).CombinedOutput() if err != nil { return false, "", fmt.Errorf("fossil test-parse-manifest: %v: %s", err, output) } text := string(output) if strings.Contains(text, "manifest_parse() worked") { return true, "", nil } for _, line := range strings.Split(text, "\n") { if strings.HasPrefix(line, "ERROR: ") { return false, strings.TrimPrefix(line, "ERROR: "), nil } } return false, "", fmt.Errorf("fossil test-parse-manifest: unrecognized output %q", text) } // withZ appends the Z card for body: the MD5 of every byte before it. Cases // build artifacts without one so that a single edit never also breaks the // checksum, which would make any rejection look deserved. func withZ(body string) []byte { if body != "" && !strings.HasSuffix(body, "\n") { panic("conformance: artifact body must end in a newline") } return []byte(body + zCard(body)) } // zCard is the Z card line for data: "Z", a space, the lowercase MD5 of // data, LF. Unlike withZ it accepts any data, for cases that put the Z // card somewhere a well-formed artifact would not. func zCard(data string) string { sum := md5.Sum([]byte(data)) return "Z " + hex.EncodeToString(sum[:]) + "\n" }