Skip to content

Commit

Permalink
Aggregate elapsed time when there are multiple runs
Browse files Browse the repository at this point in the history
A test case may have multiple runs if -count=x is used, or when the input contains the
test2json output from multiple test runs.

Using the median time should exclude some outliers.
  • Loading branch information
dnephin committed May 15, 2020
1 parent afef6d5 commit 7a39237
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 2 deletions.
45 changes: 43 additions & 2 deletions cmd/tool/slowest/slowest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"io/ioutil"
"math"
"os"
"sort"
"time"
Expand Down Expand Up @@ -122,15 +123,18 @@ func run(opts *options) error {
// slowTestCases returns a slice of all tests with an elapsed time greater than
// threshold. The slice is sorted by Elapsed time in descending order (slowest
// test first).
// FIXME: use medium elapsed time when there are multiple instances of the same test
//
// If there are multiple runs of a TestCase, all of them will be represented
// by a single TestCase with the median elapsed time in the returned slice.
func slowTestCases(exec *testjson.Execution, threshold time.Duration) []testjson.TestCase {
if threshold == 0 {
return nil
}
pkgs := exec.Packages()
tests := make([]testjson.TestCase, 0, len(pkgs))
for _, pkg := range pkgs {
tests = append(tests, exec.Package(pkg).TestCases()...)
pkgTests := aggregateTestCases(exec.Package(pkg).TestCases())
tests = append(tests, pkgTests...)
}
sort.Slice(tests, func(i, j int) bool {
return tests[i].Elapsed > tests[j].Elapsed
Expand All @@ -141,6 +145,43 @@ func slowTestCases(exec *testjson.Execution, threshold time.Duration) []testjson
return tests[:end]
}

// collectTestCases maps all test cases by name, and if there is more than one
// instance of a TestCase, finds the median elapsed time for all the runs.
//
// All cases are assumed to be part of the same package.
func aggregateTestCases(cases []testjson.TestCase) []testjson.TestCase {
if len(cases) < 2 {
return cases
}
pkg := cases[0].Package
m := make(map[string][]time.Duration)
for _, tc := range cases {
m[tc.Test] = append(m[tc.Test], tc.Elapsed)
}
var result []testjson.TestCase
for name, timing := range m {
result = append(result, testjson.TestCase{
Package: pkg,
Test: name,
Elapsed: median(timing),
})
}
return result
}

func median(times []time.Duration) time.Duration {
switch len(times) {
case 0:
return 0
case 1:
return times[0]
}
sort.Slice(times, func(i, j int) bool {
return times[i] < times[j]
})
return times[int(math.Ceil(float64(len(times)/2)))]
}

func jsonfileReader(v string) (io.ReadCloser, error) {
switch v {
case "", "-":
Expand Down
63 changes: 63 additions & 0 deletions cmd/tool/slowest/slowest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package slowest

import (
"strings"
"testing"
"time"

"github.com/google/go-cmp/cmp/cmpopts"
"gotest.tools/gotestsum/testjson"
"gotest.tools/v3/assert"
)

func TestAggregateTestCases(t *testing.T) {
cases := []testjson.TestCase{
{Test: "TestOne", Package: "pkg", Elapsed: time.Second},
{Test: "TestTwo", Package: "pkg", Elapsed: 2 * time.Second},
{Test: "TestOne", Package: "pkg", Elapsed: 3 * time.Second},
{Test: "TestTwo", Package: "pkg", Elapsed: 4 * time.Second},
{Test: "TestOne", Package: "pkg", Elapsed: 5 * time.Second},
{Test: "TestTwo", Package: "pkg", Elapsed: 6 * time.Second},
}
actual := aggregateTestCases(cases)
expected := []testjson.TestCase{
{Test: "TestOne", Package: "pkg", Elapsed: 3 * time.Second},
{Test: "TestTwo", Package: "pkg", Elapsed: 4 * time.Second},
}
assert.DeepEqual(t, actual, expected,
cmpopts.SortSlices(func(x, y testjson.TestCase) bool {
return strings.Compare(x.Test, y.Test) == -1
}),
cmpopts.IgnoreUnexported(testjson.TestCase{}))
}

func TestMedian(t *testing.T) {
var testcases = []struct {
name string
times []time.Duration
expected time.Duration
}{
{
name: "one item slice",
times: []time.Duration{time.Minute},
expected: time.Minute,
},
{
name: "odd number of items",
times: []time.Duration{time.Millisecond, time.Hour, time.Second},
expected: time.Second,
},
{
name: "even number of items",
times: []time.Duration{time.Second, time.Millisecond, time.Microsecond, time.Hour},
expected: time.Second,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
actual := median(tc.times)
assert.Equal(t, actual, tc.expected)
})
}
}

0 comments on commit 7a39237

Please sign in to comment.