@@ -31,32 +31,9 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
31
31
defer func () {
32
32
ctx = gdtcontext .PopTrace (ctx )
33
33
}()
34
- now := time .Now ()
35
- d , ok := t .Deadline ()
36
- if ok && ! d .IsZero () {
37
- s .Timings .GoTestTimeout = d .Sub (now )
38
- debug .Println (
39
- ctx , "scenario/run: go test tool deadline: %s" ,
40
- (s .Timings .GoTestTimeout + time .Second ).Round (time .Second ),
41
- )
42
- if s .Timings .TotalWait > 0 {
43
- if s .Timings .TotalWait .Abs () > s .Timings .GoTestTimeout .Abs () {
44
- return api .TimeoutConflict (
45
- s .Timings .GoTestTimeout ,
46
- s .Timings .TotalWait ,
47
- s .Timings .MaxTimeout ,
48
- )
49
- }
50
- }
51
- if s .Timings .MaxTimeout > 0 {
52
- if s .Timings .MaxTimeout .Abs () > s .Timings .GoTestTimeout .Abs () {
53
- return api .TimeoutConflict (
54
- s .Timings .GoTestTimeout ,
55
- s .Timings .TotalWait ,
56
- s .Timings .MaxTimeout ,
57
- )
58
- }
59
- }
34
+
35
+ if s .hasTimeoutConflict (ctx , t ) {
36
+ return api .TimeoutConflict (s .Timings )
60
37
}
61
38
62
39
if len (s .Fixtures ) > 0 {
@@ -73,6 +50,7 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
73
50
defer fix .Stop (ctx )
74
51
}
75
52
}
53
+
76
54
// If the test author has specified any pre-flight checks in the `skip-if`
77
55
// collection, evaluate those first and if any failed, skip the scenario's
78
56
// tests.
@@ -90,79 +68,25 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
90
68
}
91
69
}
92
70
71
+ var res * api.Result
93
72
var err error
94
- scDefaults := s .getScenarioDefaults ()
95
-
96
- t .Run (s .Title (), func (t * testing.T ) {
97
- for idx , spec := range s .Tests {
98
- sb := spec .Base ()
99
73
100
- // Create a brand new context that inherits the top-level context's
101
- // cancel func. We want to set deadlines for each test spec and if
102
- // we mutate the single supplied top-level context, then only the
103
- // first deadline/timeout will be used.
104
- specCtx , specCancel := context .WithCancel (ctx )
105
-
106
- specTraceMsg := strconv .Itoa (idx )
107
- if sb .Name != "" {
108
- specTraceMsg += ":" + sb .Name
109
- }
110
- specCtx = gdtcontext .PushTrace (specCtx , specTraceMsg )
111
- popTracer := func () {
112
- specCtx = gdtcontext .PopTrace (specCtx )
113
- }
114
-
115
- plugin := sb .Plugin
116
-
117
- rt := getRetry (specCtx , scDefaults , plugin , spec )
118
-
119
- to := getTimeout (specCtx , scDefaults , plugin , spec )
120
-
121
- var res * api.Result
122
- ch := make (chan runSpecRes , 1 )
123
-
124
- wait := sb .Wait
125
- if wait != nil && wait .Before != "" {
126
- debug .Println (specCtx , "wait: %s before" , wait .Before )
127
- time .Sleep (wait .BeforeDuration ())
128
- }
129
-
130
- if to != nil {
131
- specCtx , specCancel = context .WithTimeout (specCtx , to .Duration ())
132
- }
133
-
134
- go s .runSpec (specCtx , ch , rt , idx , spec )
135
-
136
- select {
137
- case <- specCtx .Done ():
138
- popTracer ()
139
- t .Fatalf ("assertion failed: timeout exceeded (%s)" , to .After )
140
- break
141
- case runres := <- ch :
142
- res = runres .r
143
- err = runres .err
144
- }
74
+ t .Run (s .Title (), func (tt * testing.T ) {
75
+ for idx := range s .Tests {
76
+ res , err = s .runSpec (ctx , tt , idx )
145
77
if err != nil {
146
- popTracer ()
147
- specCancel ()
148
78
break
149
79
}
150
80
151
- if wait != nil && wait .After != "" {
152
- debug .Println (specCtx , "wait: %s after" , wait .After )
153
- time .Sleep (wait .AfterDuration ())
154
- }
155
-
156
81
// Results can have arbitrary run data stored in them and we
157
82
// save this prior run data in the top-level context (and pass
158
83
// that context to the next Run invocation).
159
84
if res .HasData () {
160
85
ctx = gdtcontext .StorePriorRun (ctx , res .Data ())
161
86
}
162
- popTracer ()
163
- specCancel ()
87
+
164
88
for _ , fail := range res .Failures () {
165
- t .Fatal (fail )
89
+ tt .Fatal (fail )
166
90
}
167
91
}
168
92
})
@@ -174,8 +98,71 @@ type runSpecRes struct {
174
98
err error
175
99
}
176
100
177
- // runSpec executes an individual test spec
101
+ // runSpec wraps the execution of a single test spec
178
102
func (s * Scenario ) runSpec (
103
+ ctx context.Context , // this is the overall scenario's context
104
+ t * testing.T , // T specific to the goroutine running this test spec
105
+ idx int , // index of the test spec within Scenario.Tests
106
+ ) (res * api.Result , err error ) {
107
+ // Create a brand new context that inherits the top-level context's
108
+ // cancel func. We want to set deadlines for each test spec and if
109
+ // we mutate the single supplied top-level context, then only the
110
+ // first deadline/timeout will be used.
111
+ specCtx , specCancel := context .WithCancel (ctx )
112
+ defer specCancel ()
113
+
114
+ defaults := s .getDefaults ()
115
+ spec := s .Tests [idx ]
116
+ sb := spec .Base ()
117
+
118
+ specTraceMsg := strconv .Itoa (idx )
119
+ if sb .Name != "" {
120
+ specTraceMsg += ":" + sb .Name
121
+ }
122
+ specCtx = gdtcontext .PushTrace (specCtx , specTraceMsg )
123
+ defer func () {
124
+ specCtx = gdtcontext .PopTrace (specCtx )
125
+ }()
126
+
127
+ plugin := sb .Plugin
128
+ rt := getRetry (specCtx , defaults , plugin , spec )
129
+ to := getTimeout (specCtx , defaults , plugin , spec )
130
+ ch := make (chan runSpecRes , 1 )
131
+
132
+ wait := sb .Wait
133
+ if wait != nil && wait .Before != "" {
134
+ debug .Println (specCtx , "wait: %s before" , wait .Before )
135
+ time .Sleep (wait .BeforeDuration ())
136
+ }
137
+
138
+ if to != nil {
139
+ specCtx , specCancel = context .WithTimeout (specCtx , to .Duration ())
140
+ defer specCancel ()
141
+ }
142
+
143
+ go s .execSpec (specCtx , ch , rt , idx , spec )
144
+
145
+ select {
146
+ case <- specCtx .Done ():
147
+ t .Fatalf ("assertion failed: timeout exceeded (%s)" , to .After )
148
+ case runres := <- ch :
149
+ res = runres .r
150
+ err = runres .err
151
+ }
152
+ if err != nil {
153
+ return nil , err
154
+ }
155
+
156
+ if wait != nil && wait .After != "" {
157
+ debug .Println (specCtx , "wait: %s after" , wait .After )
158
+ time .Sleep (wait .AfterDuration ())
159
+ }
160
+ return res , nil
161
+ }
162
+
163
+ // execSpec executes an individual test spec, performing any retries as
164
+ // necessary until a timeout is exceeded or the test spec succeeds
165
+ func (s * Scenario ) execSpec (
179
166
ctx context.Context ,
180
167
ch chan runSpecRes ,
181
168
retry * api.Retry ,
@@ -190,7 +177,7 @@ func (s *Scenario) runSpec(
190
177
return
191
178
}
192
179
debug .Println (
193
- ctx , "run: single-shot (no retries) ok: %v" ,
180
+ ctx , "spec/ run: single-shot (no retries) ok: %v" ,
194
181
! res .Failed (),
195
182
)
196
183
ch <- runSpecRes {res , nil }
@@ -229,7 +216,7 @@ func (s *Scenario) runSpec(
229
216
for tick := range ticker .C {
230
217
if (maxAttempts > 0 ) && (attempts > maxAttempts ) {
231
218
debug .Println (
232
- ctx , "run: exceeded max attempts %d. stopping." ,
219
+ ctx , "spec/ run: exceeded max attempts %d. stopping." ,
233
220
maxAttempts ,
234
221
)
235
222
ticker .Stop ()
@@ -244,7 +231,7 @@ func (s *Scenario) runSpec(
244
231
}
245
232
success = ! res .Failed ()
246
233
debug .Println (
247
- ctx , "run: attempt %d after %s ok: %v" ,
234
+ ctx , "spec/ run: attempt %d after %s ok: %v" ,
248
235
attempts , after , success ,
249
236
)
250
237
if success {
@@ -253,7 +240,7 @@ func (s *Scenario) runSpec(
253
240
}
254
241
for _ , f := range res .Failures () {
255
242
debug .Println (
256
- ctx , "run: attempt %d failure: %s" ,
243
+ ctx , "spec/ run: attempt %d failure: %s" ,
257
244
attempts , f ,
258
245
)
259
246
}
@@ -262,6 +249,34 @@ func (s *Scenario) runSpec(
262
249
ch <- runSpecRes {res , nil }
263
250
}
264
251
252
+ // hasTimeoutConflict returns true if the scenario or any of its test specs has
253
+ // a wait or timeout that exceeds the go test tool's specified timeout value
254
+ func (s * Scenario ) hasTimeoutConflict (
255
+ ctx context.Context ,
256
+ t * testing.T ,
257
+ ) bool {
258
+ d , ok := t .Deadline ()
259
+ if ok && ! d .IsZero () {
260
+ now := time .Now ()
261
+ s .Timings .GoTestTimeout = d .Sub (now )
262
+ debug .Println (
263
+ ctx , "scenario/run: go test tool timeout: %s" ,
264
+ (s .Timings .GoTestTimeout + time .Second ).Round (time .Second ),
265
+ )
266
+ if s .Timings .TotalWait > 0 {
267
+ if s .Timings .TotalWait .Abs () > s .Timings .GoTestTimeout .Abs () {
268
+ return true
269
+ }
270
+ }
271
+ if s .Timings .MaxTimeout > 0 {
272
+ if s .Timings .MaxTimeout .Abs () > s .Timings .GoTestTimeout .Abs () {
273
+ return true
274
+ }
275
+ }
276
+ }
277
+ return false
278
+ }
279
+
265
280
// getTimeout returns the timeout configuration for the test spec. We check for
266
281
// overrides in timeout configuration using the following precedence:
267
282
//
@@ -271,7 +286,7 @@ func (s *Scenario) runSpec(
271
286
// * Plugin's default
272
287
func getTimeout (
273
288
ctx context.Context ,
274
- scenDefaults * Defaults ,
289
+ defaults * Defaults ,
275
290
plugin api.Plugin ,
276
291
eval api.Evaluable ,
277
292
) * api.Timeout {
@@ -294,12 +309,12 @@ func getTimeout(
294
309
return baseTimeout
295
310
}
296
311
297
- if scenDefaults != nil && scenDefaults .Timeout != nil {
312
+ if defaults != nil && defaults .Timeout != nil {
298
313
debug .Println (
299
314
ctx , "using timeout of %s [scenario default]" ,
300
- scenDefaults .Timeout .After ,
315
+ defaults .Timeout .After ,
301
316
)
302
- return scenDefaults .Timeout
317
+ return defaults .Timeout
303
318
}
304
319
305
320
pluginInfo := plugin .Info ()
@@ -324,7 +339,7 @@ func getTimeout(
324
339
// * Plugin's default
325
340
func getRetry (
326
341
ctx context.Context ,
327
- scenDefaults * Defaults ,
342
+ defaults * Defaults ,
328
343
plugin api.Plugin ,
329
344
eval api.Evaluable ,
330
345
) * api.Retry {
@@ -363,21 +378,21 @@ func getRetry(
363
378
return baseRetry
364
379
}
365
380
366
- if scenDefaults != nil && scenDefaults .Retry != nil {
367
- scenRetry := scenDefaults .Retry
368
- if scenRetry == api .NoRetry {
369
- return scenRetry
381
+ if defaults != nil && defaults .Retry != nil {
382
+ defaultRetry := defaults .Retry
383
+ if defaultRetry == api .NoRetry {
384
+ return defaultRetry
370
385
}
371
386
msg := "using retry"
372
- if scenRetry .Attempts != nil {
373
- msg += fmt .Sprintf (" (attempts: %d)" , * scenRetry .Attempts )
387
+ if defaultRetry .Attempts != nil {
388
+ msg += fmt .Sprintf (" (attempts: %d)" , * defaultRetry .Attempts )
374
389
}
375
- if scenRetry .Interval != "" {
376
- msg += fmt .Sprintf (" (interval: %s)" , scenRetry .Interval )
390
+ if defaultRetry .Interval != "" {
391
+ msg += fmt .Sprintf (" (interval: %s)" , defaultRetry .Interval )
377
392
}
378
- msg += fmt .Sprintf (" (exponential: %t) [scenario default]" , scenRetry .Exponential )
393
+ msg += fmt .Sprintf (" (exponential: %t) [scenario default]" , defaultRetry .Exponential )
379
394
debug .Println (ctx , msg )
380
- return scenRetry
395
+ return defaultRetry
381
396
}
382
397
383
398
pluginInfo := plugin .Info ()
@@ -401,9 +416,9 @@ func getRetry(
401
416
return nil
402
417
}
403
418
404
- // getScenarioDefaults returns the Defaults parsed from the scenario's YAML
419
+ // getDefaults returns the Defaults parsed from the scenario's YAML
405
420
// file's `defaults` field, or nil if none were specified.
406
- func (s * Scenario ) getScenarioDefaults () * Defaults {
421
+ func (s * Scenario ) getDefaults () * Defaults {
407
422
scDefaultsAny , found := s .Defaults [DefaultsKey ]
408
423
if found {
409
424
return scDefaultsAny .(* Defaults )
0 commit comments