Skip to content

Commit

Permalink
testing: testing: add (*T).Deadline method for test timeout
Browse files Browse the repository at this point in the history
Fixes #28135

Change-Id: I62818595eaf4a59d8b5c26cd6848c08fec795ad1
Reviewed-on: https://go-review.googlesource.com/c/go/+/202758
Run-TryBot: Bryan C. Mills <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
Bryan C. Mills committed Feb 21, 2020
1 parent 1b47fde commit 9ca5792
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 12 deletions.
1 change: 1 addition & 0 deletions api/next.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pkg testing, method (*T) Deadline() (time.Time, bool)
9 changes: 9 additions & 0 deletions doc/go1.15.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ <h2 id="library">Core library</h2>
TODO
</p>

<dl id="testing"><dt><a href="/pkg/testing/">testing</a></dt>
<dd>
<p><!-- golang.org/issue/28135 -->
The <code>testing.T</code> type now has a <code>Deadline</code> method
that reports the time at which the test binary will have exceeded its
timeout.
</p>
</dl><!-- testing -->

<h3 id="minor_library_changes">Minor changes to the library</h3>

<p>
Expand Down
50 changes: 50 additions & 0 deletions src/cmd/go/testdata/script/test_deadline.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[short] skip

go test -timeout=0 -run=TestNoDeadline
go test -timeout=1m -run=TestDeadlineWithinMinute
go test -timeout=1m -run=TestSubtestDeadlineWithinMinute

-- deadline_test.go --
package testing_test

import (
"testing"
"time"
)

func TestNoDeadline(t *testing.T) {
d, ok := t.Deadline()
if ok || !d.IsZero() {
t.Fatalf("t.Deadline() = %v, %v; want 0, false", d, ok)
}
}

func TestDeadlineWithinMinute(t *testing.T) {
now := time.Now()
d, ok := t.Deadline()
if !ok || d.IsZero() {
t.Fatalf("t.Deadline() = %v, %v; want nonzero deadline", d, ok)
}
if !d.After(now) {
t.Fatalf("t.Deadline() = %v; want after start of test (%v)", d, now)
}
if d.Sub(now) > time.Minute {
t.Fatalf("t.Deadline() = %v; want within one minute of start of test (%v)", d, now)
}
}

func TestSubtestDeadlineWithinMinute(t *testing.T) {
t.Run("sub", func(t *testing.T) {
now := time.Now()
d, ok := t.Deadline()
if !ok || d.IsZero() {
t.Fatalf("t.Deadline() = %v, %v; want nonzero deadline", d, ok)
}
if !d.After(now) {
t.Fatalf("t.Deadline() = %v; want after start of test (%v)", d, now)
}
if d.Sub(now) > time.Minute {
t.Fatalf("t.Deadline() = %v; want within one minute of start of test (%v)", d, now)
}
})
}
43 changes: 31 additions & 12 deletions src/testing/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -1049,10 +1049,20 @@ func (t *T) Run(name string, f func(t *T)) bool {
return !t.failed
}

// Deadline reports the time at which the test binary will have
// exceeded the timeout specified by the -timeout flag.
//
// The ok result is false if the -timeout flag indicates “no timeout” (0).
func (t *T) Deadline() (deadline time.Time, ok bool) {
deadline = t.context.deadline
return deadline, !deadline.IsZero()
}

// testContext holds all fields that are common to all tests. This includes
// synchronization primitives to run at most *parallel tests.
type testContext struct {
match *matcher
match *matcher
deadline time.Time

mu sync.Mutex

Expand Down Expand Up @@ -1195,9 +1205,9 @@ func (m *M) Run() int {

m.before()
defer m.after()
m.startAlarm()
deadline := m.startAlarm()
haveExamples = len(m.examples) > 0
testRan, testOk := runTests(m.deps.MatchString, m.tests)
testRan, testOk := runTests(m.deps.MatchString, m.tests, deadline)
exampleRan, exampleOk := runExamples(m.deps.MatchString, m.examples)
m.stopAlarm()
if !testRan && !exampleRan && *matchBenchmarks == "" {
Expand Down Expand Up @@ -1255,14 +1265,18 @@ func listTests(matchString func(pat, str string) (bool, error), tests []Internal
// RunTests is an internal function but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) {
ran, ok := runTests(matchString, tests)
var deadline time.Time
if *timeout > 0 {
deadline = time.Now().Add(*timeout)
}
ran, ok := runTests(matchString, tests, deadline)
if !ran && !haveExamples {
fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
}
return ok
}

func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) {
func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest, deadline time.Time) (ran, ok bool) {
ok = true
for _, procs := range cpuList {
runtime.GOMAXPROCS(procs)
Expand All @@ -1271,6 +1285,7 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
break
}
ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run"))
ctx.deadline = deadline
t := &T{
common: common{
signal: make(chan bool),
Expand Down Expand Up @@ -1452,14 +1467,18 @@ func toOutputDir(path string) string {
}

// startAlarm starts an alarm if requested.
func (m *M) startAlarm() {
if *timeout > 0 {
m.timer = time.AfterFunc(*timeout, func() {
m.after()
debug.SetTraceback("all")
panic(fmt.Sprintf("test timed out after %v", *timeout))
})
func (m *M) startAlarm() time.Time {
if *timeout <= 0 {
return time.Time{}
}

deadline := time.Now().Add(*timeout)
m.timer = time.AfterFunc(*timeout, func() {
m.after()
debug.SetTraceback("all")
panic(fmt.Sprintf("test timed out after %v", *timeout))
})
return deadline
}

// stopAlarm turns off the alarm.
Expand Down

0 comments on commit 9ca5792

Please sign in to comment.