Skip to content

Commit b2b65f2

Browse files
authored
Merge pull request #47 from jaypipes/cleanup
break run spec and exec spec into separate methods
2 parents 0732fa0 + a916cff commit b2b65f2

File tree

3 files changed

+130
-114
lines changed

3 files changed

+130
-114
lines changed

api/error.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,11 @@ func RequiredFixtureMissing(name string) error {
313313
// tool's timeout conflicts with either a total wait time or a timeout value
314314
// from a scenario or spec.
315315
func TimeoutConflict(
316-
gotestDeadline time.Duration,
317-
totalWait time.Duration,
318-
maxTimeout time.Duration,
316+
ti *Timings,
319317
) error {
318+
gotestDeadline := ti.GoTestTimeout
319+
totalWait := ti.TotalWait
320+
maxTimeout := ti.MaxTimeout
320321
msg := fmt.Sprintf(
321322
"go test -timeout value of %s ",
322323
(gotestDeadline + time.Second).Round(time.Second),

scenario/run.go

Lines changed: 123 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -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
178102
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(
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
272287
func 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
325340
func 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)

scenario/run_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func TestNoRetry(t *testing.T) {
193193
w.Flush()
194194
require.NotEqual(b.Len(), 0)
195195
debugout := b.String()
196-
require.Contains(debugout, "[gdt] [no-retry/0:bar] run: single-shot (no retries) ok: true")
196+
require.Contains(debugout, "[gdt] [no-retry/0:bar] spec/run: single-shot (no retries) ok: true")
197197
}
198198

199199
func TestNoRetryEvaluableOverride(t *testing.T) {
@@ -217,7 +217,7 @@ func TestNoRetryEvaluableOverride(t *testing.T) {
217217
w.Flush()
218218
require.NotEqual(b.Len(), 0)
219219
debugout := b.String()
220-
require.Contains(debugout, "[gdt] [no-retry-evaluable-override/0:bar] run: single-shot (no retries) ok: true")
220+
require.Contains(debugout, "[gdt] [no-retry-evaluable-override/0:bar] spec/run: single-shot (no retries) ok: true")
221221
}
222222

223223
func TestFailRetryTestOverride(t *testing.T) {
@@ -253,7 +253,7 @@ func TestRetryTestOverride(t *testing.T) {
253253
require.NotNil(err)
254254

255255
debugout := string(outerr)
256-
require.Contains(debugout, "[gdt] [retry-test-override/0:baz] run: exceeded max attempts 2. stopping.")
256+
require.Contains(debugout, "[gdt] [retry-test-override/0:baz] spec/run: exceeded max attempts 2. stopping.")
257257
}
258258

259259
func TestSkipIf(t *testing.T) {

0 commit comments

Comments
 (0)