Artifact 95155de5d4275c5ae87ced23829b369c2ecaf2aa71badaf18c023917b19f5ad7:
- File
conformance/harness_test.go
— part of check-in
[43df2fd2c2]
at
2026-10-26 00:59:23
on branch fix/conformance-subtest-t
— test(conformance): hand each case its subtest's t; cancel stale Race runs
check runs each case as a subtest, but the suites passed the case the parent's t, captured in a closure. A fixture that skips or fails, as the fossil-built R-card fixture skips when fossil(1) is absent, then called SkipNow or FailNow on the parent from inside the subtest, which Go fails as "subtest may have called FailNow on a parent test". The Race workflow installs no fossil and failed this way in TestSparseCone. check now hands conforms the subtest's t, and all 150 call sites take it, so a case's skip or failure ends that case alone. TestCheckSkipEndsOnlyTheCase guards the harness.
A Race run takes most of an hour and merges land faster, so a new run on a ref now cancels the older one. (user: [email protected], size: 2832) [annotate] [blame] [check-ins using] [more...]
package conformance import ( "fmt" "strings" "testing" ) // Case is what every conformance case carries, whatever it checks. type Case struct { // ID names the case: "<suite>.<section>.<what>", e.g. // "artifact.4.7.6.unsorted". Where libfossil's suite has the same case, // the ID matches its, so the two implementations can be compared. ID string // Req is the requirement ID from the suite's requirements table. Req string // Gap is the issue tracking a known failure, e.g. "#270". The case still // runs: while it fails it is skipped, and once it passes it fails until // Gap is removed. Gap string // Deviation says why go-libfossil deliberately does not do what the // draft requires. It behaves like Gap, but there is nothing to fix. Deviation string } // outcome is how one case run is reported. type outcome int const ( outcomePass outcome = iota outcomeFail outcomeSkip ) // judge decides how a case is reported, given the problem its check found // (nil when go-libfossil conforms). It is kept apart from check so the gap // rules are testable without a *testing.T. func judge(c Case, problem error) (outcome, string) { if c.ID == "" { panic("conformance: case without an ID") } if c.Gap != "" && c.Deviation != "" { panic("conformance: case " + c.ID + " is marked both Gap and Deviation") } marked, mark := c.Gap, "gap "+c.Gap if c.Deviation != "" { marked, mark = c.Deviation, "deviation: "+c.Deviation } if problem == nil { if marked == "" { return outcomePass, "" } return outcomeFail, fmt.Sprintf("%s now conforms; remove the mark (%s)", c.ID, mark) } if marked == "" { return outcomeFail, fmt.Sprintf("%s [%s]: %v", c.ID, c.Req, problem) } return outcomeSkip, fmt.Sprintf("%s (%s): %v", c.ID, mark, problem) } // check runs one case as a subtest. conforms returns nil when go-libfossil // does what the case expects, or a one-line description of what it did // instead. It is handed the subtest's t, so a fixture that skips or fails // (fossil(1) absent, a setup error) ends that case alone; calling Skip or // FailNow on the parent from inside the subtest fails the whole run. func check(t *testing.T, c Case, conforms func(t *testing.T) error) { t.Helper() t.Run(c.ID, func(t *testing.T) { t.Helper() result, message := judge(c, conforms(t)) switch result { case outcomePass: case outcomeFail: t.Fatal(message) case outcomeSkip: t.Skip(message) default: panic(fmt.Sprintf("conformance: unknown outcome %d", result)) } }) } // expectf builds the problem a check reports: what was expected, what // happened. func expectf(format string, args ...any) error { message := fmt.Sprintf(format, args...) if strings.ContainsRune(message, '\n') { message = strings.ReplaceAll(message, "\n", `\n`) } return fmt.Errorf("%s", message) }