Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Commonly used formats (see `--help` for a full list):
* `pkgname` (default) - print a line for each package.
* `testname` - print a line for each test and package.
* `testdox` - print a sentence for each test using [gotestdox](https://github.com/bitfield/gotestdox).
* `github-actions` - print test output with GitHub Actions log groups.
* `github-actions-fails` - same as `github-actions` but only print failures.
* `standard-quiet` - the standard `go test` format.
* `standard-verbose` - the standard `go test -v` format.

Expand Down
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ Formats:
testname print a line for each test and package
testdox print a sentence for each test using gotestdox
github-actions testname format with github actions log grouping
github-actions-fails github-actions format without passing or skipped test output
standard-quiet standard go test format
standard-verbose standard go test -v format

Expand Down
1 change: 1 addition & 0 deletions cmd/testdata/gotestsum-help-text
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Formats:
testname print a line for each test and package
testdox print a sentence for each test using gotestdox
github-actions testname format with github actions log grouping
github-actions-fails github-actions format without passing or skipped test output
standard-quiet standard go test format
standard-verbose standard go test -v format

Expand Down
70 changes: 63 additions & 7 deletions testjson/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,12 +449,34 @@ func NewEventFormatter(out io.Writer, format string, formatOpts FormatOptions) E
return pkgNameWithFailuresFormat(out, formatOpts)
case "github-actions", "github-action":
return githubActionsFormat(out)
case "github-actions-fails":
return githubActionsFailsFormat(out)
default:
return nil
}
}

func githubActionsFormat(out io.Writer) EventFormatter {
return (&githubActionsConfig{}).NewFormatter(out)
}

func githubActionsFailsFormat(out io.Writer) EventFormatter {
return (&githubActionsConfig{
HideSuccessfulTests: true,
}).NewFormatter(out)
}

// githubActionsConfig controls how test events are presented using
// GitHub Actions log grouping.
type githubActionsConfig struct {
// HideSuccessfulTests suppresses passed and skipped test case lines.
//
// Package result lines are still printed so CI logs retain package-level
// progress and failure context.
HideSuccessfulTests bool
}

func (c *githubActionsConfig) NewFormatter(out io.Writer) EventFormatter {
buf := bufio.NewWriter(out)

type name struct {
Expand All @@ -466,16 +488,31 @@ func githubActionsFormat(out io.Writer) EventFormatter {
return eventFormatterFunc(func(event TestEvent, exec *Execution) error {
key := name{Package: event.Package, Test: event.Test}

// test case output
if event.Test != "" && event.Action == ActionOutput {
if !isFramingLine(event.Output, event.Test) {
if event.Action == ActionOutput {
switch {
case event.Test != "":
// test case output
if !isFramingLine(event.Output, event.Test) {
output[key] = append(output[key], event.Output)
}
case event.Package != "":
// Package output is usually just the go test PASS/FAIL footer,
// but TestMain and init failures may put their only useful
// diagnostics here.
output[key] = append(output[key], event.Output)
}
return nil
}

// test case end event
if event.Test != "" && event.Action.IsTerminal() {
defer delete(output, key)
// Failure-only mode keeps package progress visible,
// but drops successful test cases before they can create log noise.
if c.HideSuccessfulTests && event.Action != ActionFail {
return nil
}

if len(output[key]) > 0 {
buf.WriteString("::group::")
} else {
Expand All @@ -489,14 +526,14 @@ func githubActionsFormat(out io.Writer) EventFormatter {
if len(output[key]) > 0 {
buf.WriteString("\n::endgroup::\n")
}
delete(output, key)
return buf.Flush()
}

// package event
if !event.Action.IsTerminal() {
if !event.PackageEvent() || !event.Action.IsTerminal() {
return nil
}
defer delete(output, key)

result := colorEvent(event)(strings.ToUpper(string(event.Action)))
pkg := exec.Package(event.Package)
Expand All @@ -505,11 +542,30 @@ func githubActionsFormat(out io.Writer) EventFormatter {
result = colorEvent(event)("EMPTY")
}

buf.WriteString(" ")
openGroup := func() { buf.WriteString(" ") }
closeGroup := func() { buf.WriteString("\n") }

// In failure-only mode,
// package-level failures need their output promoted.
// Otherwise failures in TestMain or init code
// can lose information.
var packageOutput []string
if c.HideSuccessfulTests && event.Action == ActionFail && pkg.TestMainFailed() {
packageOutput = output[key]
if len(packageOutput) > 0 {
openGroup = func() { buf.WriteString("::group::") }
closeGroup = func() { buf.WriteString("\n::endgroup::\n") }
}
}

openGroup()
buf.WriteString(result)
buf.WriteString(" Package ")
buf.WriteString(packageLine(event, exec.Package(event.Package)))
buf.WriteString("\n")
for _, item := range packageOutput {
buf.WriteString(item)
}
closeGroup()
return buf.Flush()
})
}
11 changes: 10 additions & 1 deletion testjson/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ func TestFormats_DefaultGoTestJson(t *testing.T) {

run := func(t *testing.T, tc testCase) {
out := new(bytes.Buffer)
shim := newFakeHandler(tc.format(out), "input/go-test-json")
formatter := tc.format(out)
assert.Assert(t, formatter != nil)
shim := newFakeHandler(formatter, "input/go-test-json")
exec, err := ScanTestOutput(shim.Config(t))
assert.NilError(t, err)

Expand Down Expand Up @@ -172,6 +174,13 @@ func TestFormats_DefaultGoTestJson(t *testing.T) {
format: githubActionsFormat,
expectedOut: "format/github-actions.out",
},
{
name: "github-actions-fails",
format: func(out io.Writer) EventFormatter {
return NewEventFormatter(out, "github-actions-fails", FormatOptions{})
},
expectedOut: "format/github-actions-fails.out",
},
}

for _, tc := range testCases {
Expand Down
61 changes: 61 additions & 0 deletions testjson/testdata/format/github-actions-fails.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
::group::FAIL Package testjson/internal/badmain (1ms)
sometimes main can exit 2
FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s

::endgroup::
EMPTY Package testjson/internal/empty (cached)

PASS Package testjson/internal/good (cached)

::group::FAIL testjson/internal/parallelfails.TestNestedParallelFailures/a (0.00s)
fails_test.go:50: failed sub a
--- FAIL: TestNestedParallelFailures/a (0.00s)

::endgroup::
::group::FAIL testjson/internal/parallelfails.TestNestedParallelFailures/d (0.00s)
fails_test.go:50: failed sub d
--- FAIL: TestNestedParallelFailures/d (0.00s)

::endgroup::
::group::FAIL testjson/internal/parallelfails.TestNestedParallelFailures/c (0.00s)
fails_test.go:50: failed sub c
--- FAIL: TestNestedParallelFailures/c (0.00s)

::endgroup::
::group::FAIL testjson/internal/parallelfails.TestNestedParallelFailures/b (0.00s)
fails_test.go:50: failed sub b
--- FAIL: TestNestedParallelFailures/b (0.00s)

::endgroup::
FAIL testjson/internal/parallelfails.TestNestedParallelFailures (0.00s)
::group::FAIL testjson/internal/parallelfails.TestParallelTheFirst (0.01s)
fails_test.go:29: failed the first

::endgroup::
::group::FAIL testjson/internal/parallelfails.TestParallelTheThird (0.00s)
fails_test.go:41: failed the third

::endgroup::
::group::FAIL testjson/internal/parallelfails.TestParallelTheSecond (0.01s)
fails_test.go:35: failed the second

::endgroup::
FAIL Package testjson/internal/parallelfails (20ms)

::group::FAIL testjson/internal/withfails.TestFailed (0.00s)
fails_test.go:34: this failed

::endgroup::
::group::FAIL testjson/internal/withfails.TestFailedWithStderr (0.00s)
this is stderr
fails_test.go:43: also failed

::endgroup::
::group::FAIL testjson/internal/withfails.TestNestedWithFailure/c (0.00s)
fails_test.go:65: failed
--- FAIL: TestNestedWithFailure/c (0.00s)

::endgroup::
FAIL testjson/internal/withfails.TestNestedWithFailure (0.00s)
FAIL Package testjson/internal/withfails (20ms)