@@ -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.
64100type 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
83122type 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.
111149func (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
10651105func (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