mesh/delivery-group group fix/1901-package-cause held: a member is held: novox/mesh-catalog@155124b59af4 (merged without a passing check: only a person decides…
mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/check pass dd9138310c48 on 51444a834173 | unit 2015 run, 2883 kept, 5 skipped | integration 2037 run, 2978 kept, 9 skipped | over bound: unit, in…
The build module on main sends a timeout, exit or kill as only 'FAIL <pkg>': dropping summary lines left empty words. They now say the package failed with no cause sent. Causes match at column 0 only, summary lines only in go's exact forms, and the words count the cause, the marker and the newlines within MaxWords.
93 lines
6.0 KiB
Go
93 lines
6.0 KiB
Go
package check
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// What the build module before this fix sent for two packages that failed after their tests passed (issue 1901, its third
|
|
// meeting: a try of mesh-controller kept "PASS" as its failure's words). Each is that build module's Output for the real
|
|
// `go test -json` of a throwaway package (mesh-catalog modules/mesh-build-go testdata/captured/go-test-json-pkg-*.jsonl):
|
|
// TestMain panicking in a goroutine after m.Run, TestMain exiting 2 after m.Run, and TestMain racing after m.Run.
|
|
const (
|
|
oldPassThenPanic = "PASS\npanic: the bus closed after the tests: nats: connection closed\ngoroutine 20 [running]:\n" +
|
|
"example.com/passthenfail.TestMain.func1()\n\t/tmp/fix-1901b/passthenfail/p_test.go:13 +0x25\n" +
|
|
"created by example.com/passthenfail.TestMain in goroutine 1\n\t/tmp/fix-1901b/passthenfail/p_test.go:13 +0x25\n" +
|
|
"FAIL\texample.com/passthenfail\t0.005s"
|
|
oldPassThenExit = "PASS\nFAIL\texample.com/mainexit\t0.004s"
|
|
// TestMain racing after m.Run passed, under go test -race (go-test-json-pkg-racemain.jsonl).
|
|
oldPassThenRace = "PASS\n==================\nWARNING: DATA RACE\nWrite at 0x000000836428 by goroutine 9:\n example.com/racemain.TestMain.func1()\n /tmp/fix-1901b/racemain/p_test.go:15 +0x24\nPrevious write at 0x000000836428 by main goroutine:\n example.com/racemain.TestMain()\n /tmp/fix-1901b/racemain/p_test.go:16 +0x44\n main.main()\n _testmain.go:48 +0x171\nGoroutine 9 (running) created at:\n example.com/racemain.TestMain()\n /tmp/fix-1901b/racemain/p_test.go:15 +0x37\n main.main()\n _testmain.go:48 +0x171\n==================\nFound 1 data race(s)\nFAIL\texample.com/racemain\t1.031s"
|
|
)
|
|
|
|
// A failure with no cause told apart never has "PASS" or "ok" as its words: the checker takes the panic line from the
|
|
// output, or says that the tests passed and the program failed after them (1901/AC5).
|
|
func TestAPackageFailureIsNeverWordedPass(t *testing.T) { // 1901/AC5
|
|
cases := []struct {
|
|
name, output, want string
|
|
}{
|
|
{"panic after the tests", oldPassThenPanic, "panic: the bus closed after the tests: nats: connection closed"},
|
|
{"exit after the tests", oldPassThenExit, "the tests passed, then the test program exited with a failure"},
|
|
{"race after the tests", oldPassThenRace, "WARNING: DATA RACE: Write at 0x000000836428 by goroutine 9 in example.com/racemain.TestMain.func1()"},
|
|
{"a panic deep in a long output", "PASS\npanic: the seat row vanished\n" + strings.Repeat("goroutine 7 [chan receive]:\n\tsome/frame.go:1 +0x1\n", 300) + "FAIL\tp\t2.0s", "panic: the seat row vanished"},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
v := levelFailure(&target{}, "integration", Tested{Failed: []Failure{{Package: "github.com/novox/mesh-controller/cmd/mesh-controller", Output: c.output}}})
|
|
for _, l := range strings.Split(v.Failed.Words, "\n") {
|
|
if l = strings.TrimSpace(l); l == "PASS" || strings.HasPrefix(l, "ok ") || strings.HasPrefix(l, "ok\t") {
|
|
t.Errorf("1901/AC5: the words keep %q: %q", l, v.Failed.Words)
|
|
}
|
|
}
|
|
if !strings.HasPrefix(v.Failed.Words, c.want) {
|
|
t.Errorf("1901/AC5: words start %q, want %q", firstLine(v.Failed.Words), c.want)
|
|
}
|
|
if got := firstFailed(*v); !strings.Contains(got, c.want) || strings.HasSuffix(got, ": PASS") {
|
|
t.Errorf("1901/AC5: status = %q", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A cause the build module told apart is cut to a line's bound, so the words keep their bound (the 1901 review's note).
|
|
func TestALongCauseIsCutToALinesBound(t *testing.T) { // 1901/AC5
|
|
f := Failure{Package: "p", Test: "TestX", Cause: strings.Repeat("c", 5000), Output: "x_test.go:1: something"}
|
|
v := levelFailure(&target{}, "unit", Tested{Failed: []Failure{f}})
|
|
if len(v.Failed.Words) > MaxWords+200 {
|
|
t.Errorf("1901/AC5: words are %d bytes, bound %d", len(v.Failed.Words), MaxWords)
|
|
}
|
|
}
|
|
|
|
// The build module on main sends a package that stopped (a timeout, an exit, a kill) as only go test's last line, with no
|
|
// cause: the words then say so, never nothing (review round 1). Each output is what that build module sends for the
|
|
// capture of the same name (go-test-json-pkg-timeout, -exiting, -killed): the package's own output, its "FAIL" line.
|
|
func TestAPackageStoppedWithOnlyItsFailLineIsStillWorded(t *testing.T) { // 1901/AC5
|
|
for _, out := range []string{"FAIL\texample.com/timeout\t2.017s", "FAIL\texample.com/exiting\t0.006s", "FAIL\texample.com/killed\t0.006s"} {
|
|
v := levelFailure(&target{}, "integration", Tested{Failed: []Failure{{Package: "example.com/x", Output: out}}})
|
|
if !strings.HasPrefix(v.Failed.Words, "the package failed with no test to name and no panic, signal or build error in its output") {
|
|
t.Errorf("1901/AC5: %q is worded %q", out, v.Failed.Words)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A line of the test's own that starts as go's summary does, or that holds a runtime's words indented, is not taken for
|
|
// either (review round 1): go prints its summary in exact forms and the runtime's lines at column 0.
|
|
func TestOnlyGosExactFormsAreSummaryOrCause(t *testing.T) { // 1901/AC5
|
|
out := " x_test.go:3: ok so far\nok then: the seat is free\n panic: child retried and recovered\npanic: the real one\nFAIL\tp\t1.0s"
|
|
v := levelFailure(&target{}, "unit", Tested{Failed: []Failure{{Package: "p", Output: out}}})
|
|
if !strings.HasPrefix(v.Failed.Words, "panic: the real one") || !strings.Contains(v.Failed.Words, "ok then: the seat is free") {
|
|
t.Errorf("1901/AC5: words = %q", v.Failed.Words)
|
|
}
|
|
}
|
|
|
|
// The words keep MaxWords exactly, counting the cause, the marker and the newlines (review round 1).
|
|
func TestTheWordsKeepTheirBoundExactly(t *testing.T) { // 1901/AC5
|
|
for _, cause := range []string{"", "a cause", strings.Repeat("c", 3000)} {
|
|
for _, n := range []int{100, 170, 171, 172, 500} {
|
|
f := Failure{Package: "p", Test: "TestX", Cause: cause, Output: strings.Repeat("a log line of twenty\n", n) + "the end"}
|
|
if w := failureWords(f); len(w) > MaxWords {
|
|
t.Errorf("1901/AC5: cause %d bytes, %d lines: words are %d bytes, bound %d", len(cause), n, len(w), MaxWords)
|
|
}
|
|
}
|
|
}
|
|
}
|