103 lines
6.4 KiB
Go
103 lines
6.4 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(afterHid(v.Failed.Words), c.want) {
|
|
t.Errorf("1901/AC5: words after the loud line start %q, want %q", firstLine(afterHid(v.Failed.Words)), c.want)
|
|
}
|
|
if got := firstFailed(*v); !strings.Contains(got, "the build module hid the failure") || strings.HasSuffix(got, ": PASS") {
|
|
t.Errorf("1901/AC5, AC8: 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(afterHid(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(afterHid(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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// afterHid is a verdict's words after the checker's loud line, which a failure with no cause from an older build module
|
|
// now starts with (1901/AC8).
|
|
func afterHid(w string) string {
|
|
if strings.HasPrefix(w, "the build module hid the failure") {
|
|
_, rest, _ := strings.Cut(w, "\n")
|
|
return rest
|
|
}
|
|
return w
|
|
}
|