@@ -31,32 +31,9 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
3131	defer  func () {
3232		ctx  =  gdtcontext .PopTrace (ctx )
3333	}()
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 )
6037	}
6138
6239	if  len (s .Fixtures ) >  0  {
@@ -73,6 +50,7 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
7350			defer  fix .Stop (ctx )
7451		}
7552	}
53+ 
7654	// If the test author has specified any pre-flight checks in the `skip-if` 
7755	// collection, evaluate those first and if any failed, skip the scenario's 
7856	// tests. 
@@ -90,79 +68,25 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
9068		}
9169	}
9270
71+ 	var  res  * api.Result 
9372	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 ()
9973
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 )
14577			if  err  !=  nil  {
146- 				popTracer ()
147- 				specCancel ()
14878				break 
14979			}
15080
151- 			if  wait  !=  nil  &&  wait .After  !=  ""  {
152- 				debug .Println (specCtx , "wait: %s after" , wait .After )
153- 				time .Sleep (wait .AfterDuration ())
154- 			}
155- 
15681			// Results can have arbitrary run data stored in them and we 
15782			// save this prior run data in the top-level context (and pass 
15883			// that context to the next Run invocation). 
15984			if  res .HasData () {
16085				ctx  =  gdtcontext .StorePriorRun (ctx , res .Data ())
16186			}
162- 			popTracer ()
163- 			specCancel ()
87+ 
16488			for  _ , fail  :=  range  res .Failures () {
165- 				t .Fatal (fail )
89+ 				tt .Fatal (fail )
16690			}
16791		}
16892	})
@@ -174,8 +98,71 @@ type runSpecRes struct {
17498	err  error 
17599}
176100
177- // runSpec executes an individual  test spec 
101+ // runSpec wraps the execution of a single  test spec 
178102func  (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 (
179166	ctx  context.Context ,
180167	ch  chan  runSpecRes ,
181168	retry  * api.Retry ,
@@ -190,7 +177,7 @@ func (s *Scenario) runSpec(
190177			return 
191178		}
192179		debug .Println (
193- 			ctx , "run: single-shot (no retries) ok: %v" ,
180+ 			ctx , "spec/ run: single-shot (no retries) ok: %v" ,
194181			! res .Failed (),
195182		)
196183		ch  <-  runSpecRes {res , nil }
@@ -229,7 +216,7 @@ func (s *Scenario) runSpec(
229216	for  tick  :=  range  ticker .C  {
230217		if  (maxAttempts  >  0 ) &&  (attempts  >  maxAttempts ) {
231218			debug .Println (
232- 				ctx , "run: exceeded max attempts %d. stopping." ,
219+ 				ctx , "spec/ run: exceeded max attempts %d. stopping." ,
233220				maxAttempts ,
234221			)
235222			ticker .Stop ()
@@ -244,7 +231,7 @@ func (s *Scenario) runSpec(
244231		}
245232		success  =  ! res .Failed ()
246233		debug .Println (
247- 			ctx , "run: attempt %d after %s ok: %v" ,
234+ 			ctx , "spec/ run: attempt %d after %s ok: %v" ,
248235			attempts , after , success ,
249236		)
250237		if  success  {
@@ -253,7 +240,7 @@ func (s *Scenario) runSpec(
253240		}
254241		for  _ , f  :=  range  res .Failures () {
255242			debug .Println (
256- 				ctx , "run: attempt %d failure: %s" ,
243+ 				ctx , "spec/ run: attempt %d failure: %s" ,
257244				attempts , f ,
258245			)
259246		}
@@ -262,6 +249,34 @@ func (s *Scenario) runSpec(
262249	ch  <-  runSpecRes {res , nil }
263250}
264251
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+ 
265280// getTimeout returns the timeout configuration for the test spec. We check for 
266281// overrides in timeout configuration using the following precedence: 
267282// 
@@ -271,7 +286,7 @@ func (s *Scenario) runSpec(
271286// * Plugin's default 
272287func  getTimeout (
273288	ctx  context.Context ,
274- 	scenDefaults  * Defaults ,
289+ 	defaults  * Defaults ,
275290	plugin  api.Plugin ,
276291	eval  api.Evaluable ,
277292) * api.Timeout  {
@@ -294,12 +309,12 @@ func getTimeout(
294309		return  baseTimeout 
295310	}
296311
297- 	if  scenDefaults  !=  nil  &&  scenDefaults .Timeout  !=  nil  {
312+ 	if  defaults  !=  nil  &&  defaults .Timeout  !=  nil  {
298313		debug .Println (
299314			ctx , "using timeout of %s [scenario default]" ,
300- 			scenDefaults .Timeout .After ,
315+ 			defaults .Timeout .After ,
301316		)
302- 		return  scenDefaults .Timeout 
317+ 		return  defaults .Timeout 
303318	}
304319
305320	pluginInfo  :=  plugin .Info ()
@@ -324,7 +339,7 @@ func getTimeout(
324339// * Plugin's default 
325340func  getRetry (
326341	ctx  context.Context ,
327- 	scenDefaults  * Defaults ,
342+ 	defaults  * Defaults ,
328343	plugin  api.Plugin ,
329344	eval  api.Evaluable ,
330345) * api.Retry  {
@@ -363,21 +378,21 @@ func getRetry(
363378		return  baseRetry 
364379	}
365380
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 
370385		}
371386		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 )
374389		}
375- 		if  scenRetry .Interval  !=  ""  {
376- 			msg  +=  fmt .Sprintf (" (interval: %s)" , scenRetry .Interval )
390+ 		if  defaultRetry .Interval  !=  ""  {
391+ 			msg  +=  fmt .Sprintf (" (interval: %s)" , defaultRetry .Interval )
377392		}
378- 		msg  +=  fmt .Sprintf (" (exponential: %t) [scenario default]" , scenRetry .Exponential )
393+ 		msg  +=  fmt .Sprintf (" (exponential: %t) [scenario default]" , defaultRetry .Exponential )
379394		debug .Println (ctx , msg )
380- 		return  scenRetry 
395+ 		return  defaultRetry 
381396	}
382397
383398	pluginInfo  :=  plugin .Info ()
@@ -401,9 +416,9 @@ func getRetry(
401416	return  nil 
402417}
403418
404- // getScenarioDefaults  returns the Defaults parsed from the scenario's YAML 
419+ // getDefaults  returns the Defaults parsed from the scenario's YAML 
405420// file's `defaults` field, or nil if none were specified. 
406- func  (s  * Scenario ) getScenarioDefaults () * Defaults  {
421+ func  (s  * Scenario ) getDefaults () * Defaults  {
407422	scDefaultsAny , found  :=  s .Defaults [DefaultsKey ]
408423	if  found  {
409424		return  scDefaultsAny .(* Defaults )
0 commit comments