Skip to content

Commit a0149eb

Browse files
authored
Merge pull request #318 from dnephin/slowest-n-tests
slowest: Add num flag for limiting the number of slow tests to print
2 parents aac34a0 + a56ceda commit a0149eb

File tree

5 files changed

+128
-3
lines changed

5 files changed

+128
-3
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,20 @@ quoting the whole command.
172172
gotestsum --post-run-command "notify me --date"
173173
```
174174

175+
**Example: printing slowest tests**
176+
177+
The post-run command can be combined with other `gotestsum` commands and tools to provide
178+
a more detailed summary. This example uses `gotestsum tool slowest` to print the
179+
slowest 10 tests after the summary.
180+
181+
```
182+
gotestsum \
183+
--jsonfile tmp.json.log \
184+
--post-run-command "bash -c '
185+
echo; echo Slowest tests;
186+
gotestsum tool slowest --num 10 --jsonfile tmp.json.log'"
187+
```
188+
175189
### Re-running failed tests
176190

177191
When the `--rerun-fails` flag is set, `gotestsum` will re-run any failed tests.

cmd/tool/slowest/slowest.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ func setupFlags(name string) (*pflag.FlagSet, *options) {
3737
"path to test2json output, defaults to stdin")
3838
flags.DurationVar(&opts.threshold, "threshold", 100*time.Millisecond,
3939
"test cases with elapsed time greater than threshold are slow tests")
40+
flags.IntVar(&opts.topN, "num", 0,
41+
"print at most num slowest tests, instead of all tests above the threshold")
4042
flags.StringVar(&opts.skipStatement, "skip-stmt", "",
4143
"add this go statement to slow tests, instead of printing the list of slow tests")
4244
flags.BoolVar(&opts.debug, "debug", false,
@@ -94,6 +96,7 @@ Flags:
9496

9597
type options struct {
9698
threshold time.Duration
99+
topN int
97100
jsonfile string
98101
skipStatement string
99102
debug bool
@@ -118,7 +121,7 @@ func run(opts *options) error {
118121
return fmt.Errorf("failed to scan testjson: %v", err)
119122
}
120123

121-
tcs := aggregate.Slowest(exec, opts.threshold)
124+
tcs := aggregate.Slowest(exec, opts.threshold, opts.topN)
122125
if opts.skipStatement != "" {
123126
skipStmt, err := parseSkipStatement(opts.skipStatement)
124127
if err != nil {

cmd/tool/slowest/testdata/cmd-flags-help-text

+1
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ https://golang.org/cmd/go/#hdr-Environment_variables.
4242
Flags:
4343
--debug enable debug logging.
4444
--jsonfile string path to test2json output, defaults to stdin
45+
--num int print at most num slowest tests, instead of all tests above the threshold
4546
--skip-stmt string add this go statement to slow tests, instead of printing the list of slow tests
4647
--threshold duration test cases with elapsed time greater than threshold are slow tests (default 100ms)

internal/aggregate/slowest.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313
//
1414
// If there are multiple runs of a TestCase, all of them will be represented
1515
// by a single TestCase with the median elapsed time in the returned slice.
16-
func Slowest(exec *testjson.Execution, threshold time.Duration) []testjson.TestCase {
17-
if threshold == 0 {
16+
func Slowest(exec *testjson.Execution, threshold time.Duration, num int) []testjson.TestCase {
17+
if threshold == 0 && num == 0 {
1818
return nil
1919
}
2020
pkgs := exec.Packages()
@@ -26,6 +26,13 @@ func Slowest(exec *testjson.Execution, threshold time.Duration) []testjson.TestC
2626
sort.Slice(tests, func(i, j int) bool {
2727
return tests[i].Elapsed > tests[j].Elapsed
2828
})
29+
if num >= len(tests) {
30+
return tests
31+
}
32+
if num > 0 {
33+
return tests[:num]
34+
}
35+
2936
end := sort.Search(len(tests), func(i int) bool {
3037
return tests[i].Elapsed < threshold
3138
})

internal/aggregate/slowest_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,115 @@
11
package aggregate
22

33
import (
4+
"bytes"
5+
"encoding/json"
46
"strings"
57
"testing"
68
"time"
79

10+
"github.com/google/go-cmp/cmp"
811
"github.com/google/go-cmp/cmp/cmpopts"
912
"gotest.tools/gotestsum/testjson"
1013
"gotest.tools/v3/assert"
1114
)
1215

16+
func TestSlowest(t *testing.T) {
17+
newEvent := func(pkg, test string, elapsed float64) testjson.TestEvent {
18+
return testjson.TestEvent{
19+
Package: pkg,
20+
Test: test,
21+
Action: testjson.ActionPass,
22+
Elapsed: elapsed,
23+
}
24+
}
25+
26+
exec := newExecutionFromEvents(t,
27+
newEvent("one", "TestOmega", 22.2),
28+
newEvent("one", "TestOmega", 1.5),
29+
newEvent("one", "TestOmega", 0.6),
30+
newEvent("one", "TestOnion", 0.5),
31+
newEvent("two", "TestTents", 2.5),
32+
newEvent("two", "TestTin", 0.3),
33+
newEvent("two", "TestTunnel", 1.1))
34+
35+
cmpCasesShallow := cmp.Comparer(func(x, y testjson.TestCase) bool {
36+
return x.Package == y.Package && x.Test == y.Test
37+
})
38+
39+
type testCase struct {
40+
name string
41+
threshold time.Duration
42+
num int
43+
expected []testjson.TestCase
44+
}
45+
46+
run := func(t *testing.T, tc testCase) {
47+
actual := Slowest(exec, tc.threshold, tc.num)
48+
assert.DeepEqual(t, actual, tc.expected, cmpCasesShallow)
49+
}
50+
51+
testCases := []testCase{
52+
{
53+
name: "threshold only",
54+
threshold: time.Second,
55+
expected: []testjson.TestCase{
56+
{Package: "two", Test: "TestTents"},
57+
{Package: "one", Test: "TestOmega"},
58+
{Package: "two", Test: "TestTunnel"},
59+
},
60+
},
61+
{
62+
name: "threshold only 2s",
63+
threshold: 2 * time.Second,
64+
expected: []testjson.TestCase{
65+
{Package: "two", Test: "TestTents"},
66+
},
67+
},
68+
{
69+
name: "threshold and num",
70+
threshold: 400 * time.Millisecond,
71+
num: 2,
72+
expected: []testjson.TestCase{
73+
{Package: "two", Test: "TestTents"},
74+
{Package: "one", Test: "TestOmega"},
75+
},
76+
},
77+
{
78+
name: "num only",
79+
num: 4,
80+
expected: []testjson.TestCase{
81+
{Package: "two", Test: "TestTents"},
82+
{Package: "one", Test: "TestOmega"},
83+
{Package: "two", Test: "TestTunnel"},
84+
{Package: "one", Test: "TestOnion"},
85+
},
86+
},
87+
}
88+
89+
for _, tc := range testCases {
90+
t.Run(tc.name, func(t *testing.T) {
91+
run(t, tc)
92+
})
93+
}
94+
}
95+
96+
func newExecutionFromEvents(t *testing.T, events ...testjson.TestEvent) *testjson.Execution {
97+
t.Helper()
98+
99+
buf := new(bytes.Buffer)
100+
encoder := json.NewEncoder(buf)
101+
for i, event := range events {
102+
assert.NilError(t, encoder.Encode(event), "event %d", i)
103+
}
104+
105+
exec, err := testjson.ScanTestOutput(testjson.ScanConfig{
106+
Stdout: buf,
107+
Stderr: strings.NewReader(""),
108+
})
109+
assert.NilError(t, err)
110+
return exec
111+
}
112+
13113
func TestByElapsed_WithMedian(t *testing.T) {
14114
cases := []testjson.TestCase{
15115
{Test: "TestOne", Package: "pkg", Elapsed: time.Second},

0 commit comments

Comments
 (0)