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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Pass | Fail

By default, `tparse` will always return test failures and panics, if any, followed by a package-level summary table.

To get additional info on passed tests run `tparse` with `-pass` flag. Tests are grouped by package and sorted by elapsed time in descending order (longest to shortest).
To get additional info on passed tests run `tparse` with `-pass` flag. To display only failed tests, use the `-fail-only` flag. Tests are grouped by package and sorted by elapsed time in descending order (longest to shortest).

### [But why?!](#but-why) for more info.

Expand Down Expand Up @@ -47,7 +47,7 @@ Tip: run `tparse -h` to get usage and options.

`tparse` attempts to do just that; return failed tests and panics, if any, followed by a single package-level summary. No more searching for the literal string: "--- FAIL".

But, let's take it a bit further. With `-all` (`-pass` and `-skip` combined) you can get additional info, such as skipped tests and elapsed time of each passed test.
But, let's take it a bit further. With `-all` (`-pass` and `-skip` combined) you can get additional info, such as skipped tests and elapsed time of each passed test. For the opposite use case, use `-fail-only` to display only failed tests.

`tparse` comes with a `-follow` flag to print raw output. Yep, go test pipes JSON, it's parsed and the output is printed back out as if you ran go test without `-json` flag. Eliminating the need for `tee /dev/tty` between pipes.

Expand Down
2 changes: 1 addition & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func display(w io.Writer, summary *parse.GoTestSummary, option Options) {
// Sort packages by name ASC.
packages := summary.GetSortedPackages(option.Sorter)
// Only print the tests table if either pass or skip is true.
if option.TestTableOptions.Pass || option.TestTableOptions.Skip {
if option.TestTableOptions.Pass || option.TestTableOptions.Skip || option.TestTableOptions.FailOnly {
if option.Format == OutputFormatMarkdown {
cw.testsTableMarkdown(packages, option.TestTableOptions)
} else {
Expand Down
11 changes: 10 additions & 1 deletion internal/app/table_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type SummaryTableOptions struct {

// TrimPath is the path prefix to trim from the package name.
TrimPath string

// FailOnly will display only packages with failed tests.
FailOnly bool
}

func (c *consoleWriter) summaryTable(
Expand Down Expand Up @@ -191,6 +194,9 @@ func (c *consoleWriter) summaryTable(
return
}
for _, r := range passed {
if options.FailOnly && r.fail == "0" {
continue
}
data.Append(r.toRow())
}

Expand All @@ -203,7 +209,10 @@ func (c *consoleWriter) summaryTable(
data.Append(r.toRow())
}
}

if options.FailOnly && data.Rows() == 0 {
fmt.Fprintln(c, "No tests failed.")
return
}
fmt.Fprintln(c, tbl.Data(data).Render())
}

Expand Down
6 changes: 6 additions & 0 deletions internal/app/table_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ var (
type TestTableOptions struct {
// Display passed or skipped tests. If both are true this is equivalent to all.
Pass, Skip bool
// FailOnly will display only tests with failed status.
// If true, it takes precedence over Pass and Skip.
FailOnly bool
// For narrow screens, trim long test identifiers vertically. Example:
// TestNoVersioning/seed-up-down-to-zero
//
Expand Down Expand Up @@ -102,6 +105,9 @@ func (c *consoleWriter) testsTable(packages []*parse.Package, option TestTableOp
data.Append(row.toRow())
}
if i != (len(packages) - 1) {
if option.FailOnly && data.Rows() == 0 {
continue
}
// Add a blank row between packages.
data.Append(testRow{}.toRow())
}
Expand Down
12 changes: 11 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var (
allPtr = flag.Bool("all", false, "")
passPtr = flag.Bool("pass", false, "")
skipPtr = flag.Bool("skip", false, "")
failOnlyPtr = flag.Bool("fail-only", false, "")
showNoTestsPtr = flag.Bool("notests", false, "")
smallScreenPtr = flag.Bool("smallscreen", false, "")
noColorPtr = flag.Bool("nocolor", false, "")
Expand Down Expand Up @@ -54,6 +55,7 @@ Options:
-all Display table event for pass and skip. (Failed items always displayed)
-pass Display table for passed tests.
-skip Display table for skipped tests.
-fail-only Display only failed tests. (overrides -pass, -skip and -all)
-notests Display packages containing no test files or empty test files.
-smallscreen Split subtest names vertically to fit on smaller screens.
-slow Number of slowest tests to display. Default is 0, display all.
Expand Down Expand Up @@ -120,7 +122,13 @@ func main() {
return
}

if *allPtr {
if *failOnlyPtr {
if *passPtr || *skipPtr || *allPtr {
fmt.Fprintln(os.Stdout, "warning: -fail-only takes precedence over -pass, -skip, and -all flags")
*passPtr = false
*skipPtr = false
}
} else if *allPtr {
*passPtr = true
*skipPtr = true
}
Expand Down Expand Up @@ -157,13 +165,15 @@ func main() {
TestTableOptions: app.TestTableOptions{
Pass: *passPtr,
Skip: *skipPtr,
FailOnly: *failOnlyPtr,
Trim: *smallScreenPtr,
TrimPath: *trimPathPtr,
Slow: *slowPtr,
},
SummaryTableOptions: app.SummaryTableOptions{
Trim: *smallScreenPtr,
TrimPath: *trimPathPtr,
FailOnly: *failOnlyPtr,
},
Format: format,
Sorter: sorter,
Expand Down
42 changes: 42 additions & 0 deletions tests/failed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,45 @@ func TestFailedTestsTable(t *testing.T) {
})
}
}

func TestFailOnly(t *testing.T) {
t.Parallel()

base := filepath.Join("testdata", "fail-only")

tt := []struct {
fileName string
exitCode int
}{
{"test_01", 1},
{"test_02", 0},
}

for _, tc := range tt {
t.Run(tc.fileName, func(t *testing.T) {
buf := bytes.NewBuffer(nil)
inputFile := filepath.Join(base, tc.fileName+".jsonl")
options := app.Options{
FileName: inputFile,
Output: buf,
Sorter: parse.SortByPackageName,
TestTableOptions: app.TestTableOptions{
FailOnly: true,
},
SummaryTableOptions: app.SummaryTableOptions{
FailOnly: true,
},
}
gotExitCode, err := app.Run(options)
require.NoError(t, err)
assert.Equal(t, tc.exitCode, gotExitCode)

goldenFile := filepath.Join(base, tc.fileName+".golden")
want, err := os.ReadFile(goldenFile)
if err != nil {
t.Fatal(err)
}
checkGolden(t, inputFile, goldenFile, buf.Bytes(), want)
})
}
}
18 changes: 18 additions & 0 deletions tests/testdata/fail-only/test_01.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
╭────────┬─────────┬──────────┬─────────────────────────────╮
│ Status │ Elapsed │ Test │ Package │
├────────┼─────────┼──────────┼─────────────────────────────┤
│ FAIL │ 0.01 │ TestFail │ github.com/example/testpkg2 │
╰────────┴─────────┴──────────┴─────────────────────────────╯
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ FAIL package: github.com/example/testpkg2 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

--- FAIL: TestFail (0.01s)

example_test.go:10: test failed

╭────────┬─────────┬─────────────────────────────┬───────┬──────┬──────┬──────╮
│ Status │ Elapsed │ Package │ Cover │ Pass │ Fail │ Skip │
├────────┼─────────┼─────────────────────────────┼───────┼──────┼──────┼──────┤
│ FAIL │ 0.03s │ github.com/example/testpkg2 │ -- │ 2 │ 1 │ 0 │
╰────────┴─────────┴─────────────────────────────┴───────┴──────┴──────┴──────╯
27 changes: 27 additions & 0 deletions tests/testdata/fail-only/test_01.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{"Time":"2024-01-15T10:00:00Z","Action":"run","Package":"github.com/example/testpkg","Test":"TestPass1"}
{"Time":"2024-01-15T10:00:00.001Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass1","Output":"=== RUN TestPass1\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass1","Output":"--- PASS: TestPass1 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"pass","Package":"github.com/example/testpkg","Test":"TestPass1","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.011Z","Action":"run","Package":"github.com/example/testpkg","Test":"TestPass2"}
{"Time":"2024-01-15T10:00:00.012Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass2","Output":"=== RUN TestPass2\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass2","Output":"--- PASS: TestPass2 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"pass","Package":"github.com/example/testpkg","Test":"TestPass2","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.021Z","Action":"output","Package":"github.com/example/testpkg","Output":"PASS\n"}
{"Time":"2024-01-15T10:00:00.022Z","Action":"output","Package":"github.com/example/testpkg","Output":"ok \tgithub.com/example/testpkg\t0.022s\n"}
{"Time":"2024-01-15T10:00:00.022Z","Action":"pass","Package":"github.com/example/testpkg","Elapsed":0.022}
{"Time":"2024-01-15T10:00:00Z","Action":"run","Package":"github.com/example/testpkg2","Test":"TestPass1"}
{"Time":"2024-01-15T10:00:00.001Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass1","Output":"=== RUN TestPass1\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass1","Output":"--- PASS: TestPass1 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"pass","Package":"github.com/example/testpkg2","Test":"TestPass1","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.011Z","Action":"run","Package":"github.com/example/testpkg2","Test":"TestPass2"}
{"Time":"2024-01-15T10:00:00.012Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass2","Output":"=== RUN TestPass2\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass2","Output":"--- PASS: TestPass2 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"pass","Package":"github.com/example/testpkg2","Test":"TestPass2","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.021Z","Action":"run","Package":"github.com/example/testpkg2","Test":"TestFail"}
{"Time":"2024-01-15T10:00:00.022Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestFail","Output":"=== RUN TestFail\n"}
{"Time":"2024-01-15T10:00:00.023Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestFail","Output":" example_test.go:10: test failed\n"}
{"Time":"2024-01-15T10:00:00.030Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestFail","Output":"--- FAIL: TestFail (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.030Z","Action":"fail","Package":"github.com/example/testpkg2","Test":"TestFail","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.031Z","Action":"output","Package":"github.com/example/testpkg2","Output":"FAIL\n"}
{"Time":"2024-01-15T10:00:00.032Z","Action":"output","Package":"github.com/example/testpkg2","Output":"FAIL\tgithub.com/example/testpkg2\t0.032s\n"}
{"Time":"2024-01-15T10:00:00.032Z","Action":"fail","Package":"github.com/example/testpkg2","Elapsed":0.032}
1 change: 1 addition & 0 deletions tests/testdata/fail-only/test_02.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
No tests failed.
22 changes: 22 additions & 0 deletions tests/testdata/fail-only/test_02.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{"Time":"2024-01-15T10:00:00Z","Action":"run","Package":"github.com/example/testpkg","Test":"TestPass1"}
{"Time":"2024-01-15T10:00:00.001Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass1","Output":"=== RUN TestPass1\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass1","Output":"--- PASS: TestPass1 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"pass","Package":"github.com/example/testpkg","Test":"TestPass1","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.011Z","Action":"run","Package":"github.com/example/testpkg","Test":"TestPass2"}
{"Time":"2024-01-15T10:00:00.012Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass2","Output":"=== RUN TestPass2\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"output","Package":"github.com/example/testpkg","Test":"TestPass2","Output":"--- PASS: TestPass2 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"pass","Package":"github.com/example/testpkg","Test":"TestPass2","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.021Z","Action":"output","Package":"github.com/example/testpkg","Output":"PASS\n"}
{"Time":"2024-01-15T10:00:00.022Z","Action":"output","Package":"github.com/example/testpkg","Output":"ok \tgithub.com/example/testpkg\t0.022s\n"}
{"Time":"2024-01-15T10:00:00.022Z","Action":"pass","Package":"github.com/example/testpkg","Elapsed":0.022}
{"Time":"2024-01-15T10:00:00Z","Action":"run","Package":"github.com/example/testpkg2","Test":"TestPass1"}
{"Time":"2024-01-15T10:00:00.001Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass1","Output":"=== RUN TestPass1\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass1","Output":"--- PASS: TestPass1 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.010Z","Action":"pass","Package":"github.com/example/testpkg2","Test":"TestPass1","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.011Z","Action":"run","Package":"github.com/example/testpkg2","Test":"TestPass2"}
{"Time":"2024-01-15T10:00:00.012Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass2","Output":"=== RUN TestPass2\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"output","Package":"github.com/example/testpkg2","Test":"TestPass2","Output":"--- PASS: TestPass2 (0.01s)\n"}
{"Time":"2024-01-15T10:00:00.020Z","Action":"pass","Package":"github.com/example/testpkg2","Test":"TestPass2","Elapsed":0.01}
{"Time":"2024-01-15T10:00:00.021Z","Action":"output","Package":"github.com/example/testpkg2","Output":"PASS\n"}
{"Time":"2024-01-15T10:00:00.022Z","Action":"output","Package":"github.com/example/testpkg2","Output":"ok \tgithub.com/example/testpkg2\t0.022s\n"}
{"Time":"2024-01-15T10:00:00.022Z","Action":"pass","Package":"github.com/example/testpkg2","Elapsed":0.022}
Loading