[misc] Split output events into lines and key build events by full path

Build output events may carry several lines in one payload; buffering
them as one entry undercounted the caps and skipped the indentation of
continuation lines. Each payload is split into lines first.

A package compiled for several test binaries reports build events under
"pkg [a.test]" and "pkg [b.test]". Cutting the suffix off merged their
buffers, and the first build-fail printed and deleted both. The full
import path is now the key and the suffix is only dropped for display.
This commit is contained in:
mlsmaycon
2026-09-12 12:26:14 +00:00
parent 053c0fe599
commit 8fd2db15b8
2 changed files with 32 additions and 8 deletions
+12 -5
View File
@@ -48,8 +48,9 @@ type event struct {
Test string `json:"Test"`
Output string `json:"Output"`
Elapsed float64 `json:"Elapsed"`
// ImportPath is set instead of Package on build events; it carries a
// " [pkg.test]" suffix for a test binary.
// ImportPath is set instead of Package on build events. It carries a
// " [pkg.test]" suffix naming the test binary the package was compiled
// for, and the same package can be built for several binaries at once.
ImportPath string `json:"ImportPath"`
}
@@ -150,8 +151,10 @@ func (s *summarizer) consume(r io.Reader) error {
}
func (s *summarizer) handle(ev event) {
if ev.Package == "" && ev.ImportPath != "" {
ev.Package, _, _ = strings.Cut(ev.ImportPath, " [")
if ev.Package == "" {
// Keep the full path as the key so concurrent builds of one package for
// different test binaries do not share, and delete, each other's state.
ev.Package = ev.ImportPath
}
key := testKey{pkg: ev.Package, name: ev.Test}
switch ev.Action {
@@ -164,7 +167,10 @@ func (s *summarizer) handle(ev event) {
}
}
case "output", "build-output":
s.handleOutput(key, strings.TrimRight(ev.Output, "\n"))
// Test output arrives one line per event; build output may carry several.
for _, line := range strings.Split(strings.TrimRight(ev.Output, "\n"), "\n") {
s.handleOutput(key, line)
}
case "build-fail":
s.handlePackageResult(ev)
case "pass", "fail", "skip":
@@ -384,5 +390,6 @@ func (s *summarizer) printSlowest(title string, limit int, keep func(testResult)
}
func shortPkg(pkg string) string {
pkg, _, _ = strings.Cut(pkg, " [")
return strings.TrimPrefix(pkg, modulePrefix)
}
+20 -3
View File
@@ -84,15 +84,14 @@ func TestStoreSetupTimeIsAttributedToTheTest(t *testing.T) {
func TestBuildFailureShowsCompilerOutput(t *testing.T) {
events := `
{"Action":"build-output","ImportPath":"a [a.test]","Output":"# a [a.test]\n"}
{"Action":"build-output","ImportPath":"a [a.test]","Output":"a_test.go:7:2: undefined: nope\n"}
{"Action":"build-output","ImportPath":"a [a.test]","Output":"# a [a.test]\na_test.go:7:2: undefined: nope\na_test.go:9:2: undefined: nope2\n"}
{"Action":"build-fail","ImportPath":"a [a.test]"}
`
got := feed(t, events)
for _, want := range []string{
"FAIL a 0s",
"==== output of a outside tests ====",
" a_test.go:7:2: undefined: nope",
" a_test.go:7:2: undefined: nope\n a_test.go:9:2: undefined: nope2",
} {
if !strings.Contains(got, want) {
t.Errorf("output lacks %q:\n%s", want, got)
@@ -100,6 +99,24 @@ func TestBuildFailureShowsCompilerOutput(t *testing.T) {
}
}
func TestBuildVariantsOfOnePackageKeepSeparateOutput(t *testing.T) {
events := `
{"Action":"build-output","ImportPath":"a [a.test]","Output":"a.go:1:1: broken for a.test\n"}
{"Action":"build-output","ImportPath":"a [b.test]","Output":"a.go:1:1: broken for b.test\n"}
{"Action":"build-fail","ImportPath":"a [a.test]"}
{"Action":"build-fail","ImportPath":"a [b.test]"}
`
got := feed(t, events)
if strings.Count(got, "==== output of a outside tests ====") != 2 {
t.Errorf("expected one output block per build variant:\n%s", got)
}
for _, want := range []string{"broken for a.test", "broken for b.test"} {
if strings.Count(got, want) != 1 {
t.Errorf("expected %q exactly once:\n%s", want, got)
}
}
}
func TestPassingPackageOutputIsNotPrinted(t *testing.T) {
events := `
{"Action":"output","Package":"a","Output":"level=info msg=\"noise between tests\"\n"}