@@ -41,6 +41,33 @@ func TestScript(t *testing.T) {
41
41
testenv .MustHaveGoBuild (t )
42
42
testenv .SkipIfShortAndSlow (t )
43
43
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
+
44
71
files , err := filepath .Glob ("testdata/script/*.txt" )
45
72
if err != nil {
46
73
t .Fatal (err )
@@ -50,40 +77,51 @@ func TestScript(t *testing.T) {
50
77
name := strings .TrimSuffix (filepath .Base (file ), ".txt" )
51
78
t .Run (name , func (t * testing.T ) {
52
79
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
+ }
54
89
ts .setup ()
55
90
if ! * testWork {
56
91
defer removeAll (ts .workdir )
57
92
}
58
93
ts .run ()
94
+ cancel ()
59
95
})
60
96
}
61
97
}
62
98
63
99
// A testScript holds execution state for a single test script.
64
100
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
81
120
}
82
121
83
122
type backgroundCmd struct {
84
123
want simpleStatus
85
124
args []string
86
- cancel context.CancelFunc
87
125
done <- chan struct {}
88
126
err error
89
127
stdout , stderr strings.Builder
@@ -109,6 +147,10 @@ var extraEnvKeys = []string{
109
147
110
148
// setup sets up the test execution temporary directory and environment.
111
149
func (ts * testScript ) setup () {
150
+ if err := ts .ctx .Err (); err != nil {
151
+ ts .t .Fatalf ("test interrupted during setup: %v" , err )
152
+ }
153
+
112
154
StartProxy ()
113
155
ts .workdir = filepath .Join (testTmpDir , "script-" + ts .name )
114
156
ts .check (os .MkdirAll (filepath .Join (ts .workdir , "tmp" ), 0777 ))
@@ -200,9 +242,7 @@ func (ts *testScript) run() {
200
242
// On a normal exit from the test loop, background processes are cleaned up
201
243
// before we print PASS. If we return early (e.g., due to a test failure),
202
244
// don't print anything about the processes that were still running.
203
- for _ , bg := range ts .background {
204
- bg .cancel ()
205
- }
245
+ ts .cancel ()
206
246
for _ , bg := range ts .background {
207
247
<- bg .done
208
248
}
@@ -275,6 +315,10 @@ Script:
275
315
fmt .Fprintf (& ts .log , "> %s\n " , line )
276
316
277
317
for _ , cond := range parsed .conds {
318
+ if err := ts .ctx .Err (); err != nil {
319
+ ts .fatalf ("test interrupted: %v" , err )
320
+ }
321
+
278
322
// Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short).
279
323
//
280
324
// NOTE: If you make changes here, update testdata/script/README too!
@@ -356,9 +400,7 @@ Script:
356
400
}
357
401
}
358
402
359
- for _ , bg := range ts .background {
360
- bg .cancel ()
361
- }
403
+ ts .cancel ()
362
404
ts .cmdWait (success , nil )
363
405
364
406
// Final phase ended.
@@ -798,9 +840,7 @@ func (ts *testScript) cmdSkip(want simpleStatus, args []string) {
798
840
799
841
// Before we mark the test as skipped, shut down any background processes and
800
842
// make sure they have returned the correct status.
801
- for _ , bg := range ts .background {
802
- bg .cancel ()
803
- }
843
+ ts .cancel ()
804
844
ts .cmdWait (success , nil )
805
845
806
846
if len (args ) == 1 {
@@ -1065,38 +1105,9 @@ func (ts *testScript) exec(command string, args ...string) (stdout, stderr strin
1065
1105
func (ts * testScript ) startBackground (want simpleStatus , command string , args ... string ) (* backgroundCmd , error ) {
1066
1106
done := make (chan struct {})
1067
1107
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 ,
1100
1111
}
1101
1112
1102
1113
cmd := exec .Command (command , args ... )
@@ -1105,12 +1116,11 @@ func (ts *testScript) startBackground(want simpleStatus, command string, args ..
1105
1116
cmd .Stdout = & bg .stdout
1106
1117
cmd .Stderr = & bg .stderr
1107
1118
if err := cmd .Start (); err != nil {
1108
- bg .cancel ()
1109
1119
return nil , err
1110
1120
}
1111
1121
1112
1122
go func () {
1113
- bg .err = waitOrStop (ctx , cmd , stopSignal (), gracePeriod )
1123
+ bg .err = waitOrStop (ts . ctx , cmd , stopSignal (), ts . gracePeriod )
1114
1124
close (done )
1115
1125
}()
1116
1126
return bg , nil
0 commit comments