From 8fd2db15b8c43bdb4b6a525ac844208bdd416a8a Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Sat, 12 Sep 2026 12:26:14 +0000 Subject: [PATCH] [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. --- tools/gotestsummary/main.go | 17 ++++++++++++----- tools/gotestsummary/main_test.go | 23 ++++++++++++++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/tools/gotestsummary/main.go b/tools/gotestsummary/main.go index 5870ecd78..33c83f29b 100644 --- a/tools/gotestsummary/main.go +++ b/tools/gotestsummary/main.go @@ -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) } diff --git a/tools/gotestsummary/main_test.go b/tools/gotestsummary/main_test.go index 202fff2f6..e011f2016 100644 --- a/tools/gotestsummary/main_test.go +++ b/tools/gotestsummary/main_test.go @@ -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"}