diff options
author | Bryan C. Mills <bcmills@google.com> | 2021-03-15 16:48:51 -0400 |
---|---|---|
committer | Bryan C. Mills <bcmills@google.com> | 2021-03-16 20:46:00 +0000 |
commit | 72d98df88e3cbbbe482f7d421c6fd5cbd84423b4 (patch) | |
tree | e9aaacf190a626e65abefa4605f8429bc72da4d9 /src/cmd/go/script_test.go | |
parent | 18246672593fd59c2671bc6407c06fd020716708 (diff) | |
download | go-git-72d98df88e3cbbbe482f7d421c6fd5cbd84423b4.tar.gz |
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 <bcmills@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Diffstat (limited to 'src/cmd/go/script_test.go')
-rw-r--r-- | src/cmd/go/script_test.go | 132 |
1 files changed, 71 insertions, 61 deletions
diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go index dfaa40548e..1f38be8ee4 100644 --- a/src/cmd/go/script_test.go +++ b/src/cmd/go/script_test.go @@ -41,6 +41,33 @@ func TestScript(t *testing.T) { testenv.MustHaveGoBuild(t) testenv.SkipIfShortAndSlow(t) + var ( + ctx = context.Background() + gracePeriod = 100 * time.Millisecond + ) + if deadline, ok := t.Deadline(); ok { + timeout := time.Until(deadline) + + // If time allows, increase the termination grace period to 5% of the + // remaining time. + if gp := timeout / 20; gp > gracePeriod { + gracePeriod = gp + } + + // When we run commands that execute subprocesses, we want to reserve two + // grace periods to clean up. We will send the first termination signal when + // the context expires, then wait one grace period for the process to + // produce whatever useful output it can (such as a stack trace). After the + // first grace period expires, we'll escalate to os.Kill, leaving the second + // grace period for the test function to record its output before the test + // process itself terminates. + timeout -= 2 * gracePeriod + + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, timeout) + t.Cleanup(cancel) + } + files, err := filepath.Glob("testdata/script/*.txt") if err != nil { t.Fatal(err) @@ -50,40 +77,51 @@ func TestScript(t *testing.T) { name := strings.TrimSuffix(filepath.Base(file), ".txt") t.Run(name, func(t *testing.T) { t.Parallel() - ts := &testScript{t: t, name: name, file: file} + ctx, cancel := context.WithCancel(ctx) + ts := &testScript{ + t: t, + ctx: ctx, + cancel: cancel, + gracePeriod: gracePeriod, + name: name, + file: file, + } ts.setup() if !*testWork { defer removeAll(ts.workdir) } ts.run() + cancel() }) } } // A testScript holds execution state for a single test script. type testScript struct { - t *testing.T - workdir string // temporary work dir ($WORK) - log bytes.Buffer // test execution log (printed at end of test) - mark int // offset of next log truncation - cd string // current directory during test execution; initially $WORK/gopath/src - name string // short name of test ("foo") - file string // full file name ("testdata/script/foo.txt") - lineno int // line number currently executing - line string // line currently executing - env []string // environment list (for os/exec) - envMap map[string]string // environment mapping (matches env) - stdout string // standard output from last 'go' command; for 'stdout' command - stderr string // standard error from last 'go' command; for 'stderr' command - stopped bool // test wants to stop early - start time.Time // time phase started - background []*backgroundCmd // backgrounded 'exec' and 'go' commands + t *testing.T + ctx context.Context + cancel context.CancelFunc + gracePeriod time.Duration + workdir string // temporary work dir ($WORK) + log bytes.Buffer // test execution log (printed at end of test) + mark int // offset of next log truncation + cd string // current directory during test execution; initially $WORK/gopath/src + name string // short name of test ("foo") + file string // full file name ("testdata/script/foo.txt") + lineno int // line number currently executing + line string // line currently executing + env []string // environment list (for os/exec) + envMap map[string]string // environment mapping (matches env) + stdout string // standard output from last 'go' command; for 'stdout' command + stderr string // standard error from last 'go' command; for 'stderr' command + stopped bool // test wants to stop early + start time.Time // time phase started + background []*backgroundCmd // backgrounded 'exec' and 'go' commands } type backgroundCmd struct { want simpleStatus args []string - cancel context.CancelFunc done <-chan struct{} err error stdout, stderr strings.Builder @@ -109,6 +147,10 @@ var extraEnvKeys = []string{ // setup sets up the test execution temporary directory and environment. func (ts *testScript) setup() { + if err := ts.ctx.Err(); err != nil { + ts.t.Fatalf("test interrupted during setup: %v", err) + } + StartProxy() ts.workdir = filepath.Join(testTmpDir, "script-"+ts.name) ts.check(os.MkdirAll(filepath.Join(ts.workdir, "tmp"), 0777)) @@ -200,9 +242,7 @@ func (ts *testScript) run() { // On a normal exit from the test loop, background processes are cleaned up // before we print PASS. If we return early (e.g., due to a test failure), // don't print anything about the processes that were still running. - for _, bg := range ts.background { - bg.cancel() - } + ts.cancel() for _, bg := range ts.background { <-bg.done } @@ -275,6 +315,10 @@ Script: fmt.Fprintf(&ts.log, "> %s\n", line) for _, cond := range parsed.conds { + if err := ts.ctx.Err(); err != nil { + ts.fatalf("test interrupted: %v", err) + } + // Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short). // // NOTE: If you make changes here, update testdata/script/README too! @@ -356,9 +400,7 @@ Script: } } - for _, bg := range ts.background { - bg.cancel() - } + ts.cancel() ts.cmdWait(success, nil) // Final phase ended. @@ -798,9 +840,7 @@ func (ts *testScript) cmdSkip(want simpleStatus, args []string) { // Before we mark the test as skipped, shut down any background processes and // make sure they have returned the correct status. - for _, bg := range ts.background { - bg.cancel() - } + ts.cancel() ts.cmdWait(success, nil) if len(args) == 1 { @@ -1065,38 +1105,9 @@ func (ts *testScript) exec(command string, args ...string) (stdout, stderr strin func (ts *testScript) startBackground(want simpleStatus, command string, args ...string) (*backgroundCmd, error) { done := make(chan struct{}) bg := &backgroundCmd{ - want: want, - args: append([]string{command}, args...), - done: done, - cancel: func() {}, - } - - ctx := context.Background() - gracePeriod := 100 * time.Millisecond - if deadline, ok := ts.t.Deadline(); ok { - timeout := time.Until(deadline) - // If time allows, increase the termination grace period to 5% of the - // remaining time. - if gp := timeout / 20; gp > gracePeriod { - gracePeriod = gp - } - - // Send the first termination signal with two grace periods remaining. - // If it still hasn't finished after the first period has elapsed, - // we'll escalate to os.Kill with a second period remaining until the - // test deadline.. - timeout -= 2 * gracePeriod - - if timeout <= 0 { - // The test has less than the grace period remaining. There is no point in - // even starting the command, because it will be terminated immediately. - // Save the expense of starting it in the first place. - bg.err = context.DeadlineExceeded - close(done) - return bg, nil - } - - ctx, bg.cancel = context.WithTimeout(ctx, timeout) + want: want, + args: append([]string{command}, args...), + done: done, } cmd := exec.Command(command, args...) @@ -1105,12 +1116,11 @@ func (ts *testScript) startBackground(want simpleStatus, command string, args .. cmd.Stdout = &bg.stdout cmd.Stderr = &bg.stderr if err := cmd.Start(); err != nil { - bg.cancel() return nil, err } go func() { - bg.err = waitOrStop(ctx, cmd, stopSignal(), gracePeriod) + bg.err = waitOrStop(ts.ctx, cmd, stopSignal(), ts.gracePeriod) close(done) }() return bg, nil |