Skip to content

Commit 72d98df

Browse files
author
Bryan C. Mills
committed
cmd/go: bail out from script tests earlier when a timeout occurs
(I needed this to debug an accidental infinite-recursion I introduced while revising CL 293689.) Fixes #38768 Change-Id: I306122f15b5bbd2fc5e836b32fd4dd5992ea891e Reviewed-on: https://go-review.googlesource.com/c/go/+/302052 Trust: Bryan C. Mills <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Go Bot <[email protected]>
1 parent 1824667 commit 72d98df

File tree

1 file changed

+71
-61
lines changed

1 file changed

+71
-61
lines changed

src/cmd/go/script_test.go

+71-61
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,33 @@ func TestScript(t *testing.T) {
4141
testenv.MustHaveGoBuild(t)
4242
testenv.SkipIfShortAndSlow(t)
4343

44+
var (
45+
ctx = context.Background()
46+
gracePeriod = 100 * time.Millisecond
47+
)
48+
if deadline, ok := t.Deadline(); ok {
49+
timeout := time.Until(deadline)
50+
51+
// If time allows, increase the termination grace period to 5% of the
52+
// remaining time.
53+
if gp := timeout / 20; gp > gracePeriod {
54+
gracePeriod = gp
55+
}
56+
57+
// When we run commands that execute subprocesses, we want to reserve two
58+
// grace periods to clean up. We will send the first termination signal when
59+
// the context expires, then wait one grace period for the process to
60+
// produce whatever useful output it can (such as a stack trace). After the
61+
// first grace period expires, we'll escalate to os.Kill, leaving the second
62+
// grace period for the test function to record its output before the test
63+
// process itself terminates.
64+
timeout -= 2 * gracePeriod
65+
66+
var cancel context.CancelFunc
67+
ctx, cancel = context.WithTimeout(ctx, timeout)
68+
t.Cleanup(cancel)
69+
}
70+
4471
files, err := filepath.Glob("testdata/script/*.txt")
4572
if err != nil {
4673
t.Fatal(err)
@@ -50,40 +77,51 @@ func TestScript(t *testing.T) {
5077
name := strings.TrimSuffix(filepath.Base(file), ".txt")
5178
t.Run(name, func(t *testing.T) {
5279
t.Parallel()
53-
ts := &testScript{t: t, name: name, file: file}
80+
ctx, cancel := context.WithCancel(ctx)
81+
ts := &testScript{
82+
t: t,
83+
ctx: ctx,
84+
cancel: cancel,
85+
gracePeriod: gracePeriod,
86+
name: name,
87+
file: file,
88+
}
5489
ts.setup()
5590
if !*testWork {
5691
defer removeAll(ts.workdir)
5792
}
5893
ts.run()
94+
cancel()
5995
})
6096
}
6197
}
6298

6399
// A testScript holds execution state for a single test script.
64100
type testScript struct {
65-
t *testing.T
66-
workdir string // temporary work dir ($WORK)
67-
log bytes.Buffer // test execution log (printed at end of test)
68-
mark int // offset of next log truncation
69-
cd string // current directory during test execution; initially $WORK/gopath/src
70-
name string // short name of test ("foo")
71-
file string // full file name ("testdata/script/foo.txt")
72-
lineno int // line number currently executing
73-
line string // line currently executing
74-
env []string // environment list (for os/exec)
75-
envMap map[string]string // environment mapping (matches env)
76-
stdout string // standard output from last 'go' command; for 'stdout' command
77-
stderr string // standard error from last 'go' command; for 'stderr' command
78-
stopped bool // test wants to stop early
79-
start time.Time // time phase started
80-
background []*backgroundCmd // backgrounded 'exec' and 'go' commands
101+
t *testing.T
102+
ctx context.Context
103+
cancel context.CancelFunc
104+
gracePeriod time.Duration
105+
workdir string // temporary work dir ($WORK)
106+
log bytes.Buffer // test execution log (printed at end of test)
107+
mark int // offset of next log truncation
108+
cd string // current directory during test execution; initially $WORK/gopath/src
109+
name string // short name of test ("foo")
110+
file string // full file name ("testdata/script/foo.txt")
111+
lineno int // line number currently executing
112+
line string // line currently executing
113+
env []string // environment list (for os/exec)
114+
envMap map[string]string // environment mapping (matches env)
115+
stdout string // standard output from last 'go' command; for 'stdout' command
116+
stderr string // standard error from last 'go' command; for 'stderr' command
117+
stopped bool // test wants to stop early
118+
start time.Time // time phase started
119+
background []*backgroundCmd // backgrounded 'exec' and 'go' commands
81120
}
82121

83122
type backgroundCmd struct {
84123
want simpleStatus
85124
args []string
86-
cancel context.CancelFunc
87125
done <-chan struct{}
88126
err error
89127
stdout, stderr strings.Builder
@@ -109,6 +147,10 @@ var extraEnvKeys = []string{
109147

110148
// setup sets up the test execution temporary directory and environment.
111149
func (ts *testScript) setup() {
150+
if err := ts.ctx.Err(); err != nil {
151+
ts.t.Fatalf("test interrupted during setup: %v", err)
152+
}
153+
112154
StartProxy()
113155
ts.workdir = filepath.Join(testTmpDir, "script-"+ts.name)
114156
ts.check(os.MkdirAll(filepath.Join(ts.workdir, "tmp"), 0777))
@@ -200,9 +242,7 @@ func (ts *testScript) run() {
200242
// On a normal exit from the test loop, background processes are cleaned up
201243
// before we print PASS. If we return early (e.g., due to a test failure),
202244
// don't print anything about the processes that were still running.
203-
for _, bg := range ts.background {
204-
bg.cancel()
205-
}
245+
ts.cancel()
206246
for _, bg := range ts.background {
207247
<-bg.done
208248
}
@@ -275,6 +315,10 @@ Script:
275315
fmt.Fprintf(&ts.log, "> %s\n", line)
276316

277317
for _, cond := range parsed.conds {
318+
if err := ts.ctx.Err(); err != nil {
319+
ts.fatalf("test interrupted: %v", err)
320+
}
321+
278322
// Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short).
279323
//
280324
// NOTE: If you make changes here, update testdata/script/README too!
@@ -356,9 +400,7 @@ Script:
356400
}
357401
}
358402

359-
for _, bg := range ts.background {
360-
bg.cancel()
361-
}
403+
ts.cancel()
362404
ts.cmdWait(success, nil)
363405

364406
// Final phase ended.
@@ -798,9 +840,7 @@ func (ts *testScript) cmdSkip(want simpleStatus, args []string) {
798840

799841
// Before we mark the test as skipped, shut down any background processes and
800842
// make sure they have returned the correct status.
801-
for _, bg := range ts.background {
802-
bg.cancel()
803-
}
843+
ts.cancel()
804844
ts.cmdWait(success, nil)
805845

806846
if len(args) == 1 {
@@ -1065,38 +1105,9 @@ func (ts *testScript) exec(command string, args ...string) (stdout, stderr strin
10651105
func (ts *testScript) startBackground(want simpleStatus, command string, args ...string) (*backgroundCmd, error) {
10661106
done := make(chan struct{})
10671107
bg := &backgroundCmd{
1068-
want: want,
1069-
args: append([]string{command}, args...),
1070-
done: done,
1071-
cancel: func() {},
1072-
}
1073-
1074-
ctx := context.Background()
1075-
gracePeriod := 100 * time.Millisecond
1076-
if deadline, ok := ts.t.Deadline(); ok {
1077-
timeout := time.Until(deadline)
1078-
// If time allows, increase the termination grace period to 5% of the
1079-
// remaining time.
1080-
if gp := timeout / 20; gp > gracePeriod {
1081-
gracePeriod = gp
1082-
}
1083-
1084-
// Send the first termination signal with two grace periods remaining.
1085-
// If it still hasn't finished after the first period has elapsed,
1086-
// we'll escalate to os.Kill with a second period remaining until the
1087-
// test deadline..
1088-
timeout -= 2 * gracePeriod
1089-
1090-
if timeout <= 0 {
1091-
// The test has less than the grace period remaining. There is no point in
1092-
// even starting the command, because it will be terminated immediately.
1093-
// Save the expense of starting it in the first place.
1094-
bg.err = context.DeadlineExceeded
1095-
close(done)
1096-
return bg, nil
1097-
}
1098-
1099-
ctx, bg.cancel = context.WithTimeout(ctx, timeout)
1108+
want: want,
1109+
args: append([]string{command}, args...),
1110+
done: done,
11001111
}
11011112

11021113
cmd := exec.Command(command, args...)
@@ -1105,12 +1116,11 @@ func (ts *testScript) startBackground(want simpleStatus, command string, args ..
11051116
cmd.Stdout = &bg.stdout
11061117
cmd.Stderr = &bg.stderr
11071118
if err := cmd.Start(); err != nil {
1108-
bg.cancel()
11091119
return nil, err
11101120
}
11111121

11121122
go func() {
1113-
bg.err = waitOrStop(ctx, cmd, stopSignal(), gracePeriod)
1123+
bg.err = waitOrStop(ts.ctx, cmd, stopSignal(), ts.gracePeriod)
11141124
close(done)
11151125
}()
11161126
return bg, nil

0 commit comments

Comments
 (0)