-
Notifications
You must be signed in to change notification settings - Fork 9
/
runner.go
150 lines (128 loc) · 4.2 KB
/
runner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package gocuke
import (
"reflect"
"testing"
messages "github.com/cucumber/messages/go/v22"
tag "github.com/cucumber/tag-expressions/go/v6"
"pgregory.net/rapid"
)
// Runner is a test runner.
type Runner struct {
topLevelT *testing.T
suiteType reflect.Type
incr *messages.Incrementing
paths []string
parallel bool
stepDefs []*stepDef
haveSuggestion map[string]bool
suggestions []methodSig
supportedSpecialArgs map[reflect.Type]specialArgGetter
suiteInjectors []*suiteInjector
beforeHooks []*stepDef
afterHooks []*stepDef
beforeStepHooks []*stepDef
afterStepHooks []*stepDef
suiteUsesRapid bool
tagExpr tag.Evaluatable
shortTagExpr tag.Evaluatable
}
type suiteInjector struct {
getValue specialArgGetter
field reflect.StructField
}
// NewRunner constructs a new Runner with the provided suite type instance.
// Suite type is expected to be a pointer to a struct or a struct.
// A new instance of suiteType will be constructed for every scenario.
//
// The following special argument types will be injected into exported fields of
// the suite type struct: TestingT, Scenario, Step, *rapid.T.
//
// Methods defined on the suite type will be auto-registered as step definitions
// if they correspond to the expected method name for a step. Method
// parameters can start with the special argument types listed above and must
// be followed by step argument types for each captured step argument and
// DocString or DataTable at the end if the step uses one of these.
// Valid step argument types are int64, string, *big.Int and *apd.Decimal.
//
// The methods Before, After, BeforeStep and AfterStep will be recognized
// as hooks and can take the special argument types listed above.
func NewRunner(t *testing.T, suiteType interface{}) *Runner {
t.Helper()
initGlobalTagExpr()
r := &Runner{
topLevelT: t,
incr: &messages.Incrementing{},
parallel: true,
haveSuggestion: map[string]bool{},
supportedSpecialArgs: map[reflect.Type]specialArgGetter{
// TestingT
reflect.TypeOf((*TestingT)(nil)).Elem(): func(runner *scenarioRunner) interface{} {
return runner.t
},
// *rapid.T
reflect.TypeOf(&rapid.T{}): func(runner *scenarioRunner) interface{} {
if t, ok := runner.t.(*rapidT); ok {
return t.T
}
runner.t.Fatalf("expected %T, but got %T", &rapid.T{}, runner.t)
return nil
},
// Scenario
reflect.TypeOf((*Scenario)(nil)).Elem(): func(runner *scenarioRunner) interface{} {
return scenario{runner.pickle}
},
// Step
reflect.TypeOf((*Step)(nil)).Elem(): func(runner *scenarioRunner) interface{} {
return step{runner.step}
},
},
suiteUsesRapid: false,
}
r.registerSuite(suiteType)
return r
}
func (r *Runner) registerSuite(suiteType interface{}) *Runner {
r.topLevelT.Helper()
typ := reflect.TypeOf(suiteType)
r.suiteType = typ
kind := typ.Kind()
suiteElemType := r.suiteType
if kind == reflect.Ptr {
suiteElemType = suiteElemType.Elem()
}
if suiteElemType.Kind() != reflect.Struct {
r.topLevelT.Fatalf("expected a struct or a pointer to a struct, got %T", suiteType)
}
for i := 0; i < suiteElemType.NumField(); i++ {
field := suiteElemType.Field(i)
if !field.IsExported() {
continue
}
for specialArgType, getter := range r.supportedSpecialArgs {
if field.Type.AssignableTo(specialArgType) {
r.suiteInjectors = append(r.suiteInjectors, &suiteInjector{getValue: getter, field: field})
if field.Type == rapidTType {
r.suiteUsesRapid = true
}
break
}
}
}
r.supportedSpecialArgs[r.suiteType] = func(runner *scenarioRunner) interface{} {
return runner.s
}
if before, ok := r.suiteType.MethodByName("Before"); ok {
r.addHook(&r.beforeHooks, before.Func)
}
if after, ok := r.suiteType.MethodByName("After"); ok {
r.addHook(&r.afterHooks, after.Func)
}
if beforeStep, ok := r.suiteType.MethodByName("BeforeStep"); ok {
r.addHook(&r.beforeStepHooks, beforeStep.Func)
}
if afterStep, ok := r.suiteType.MethodByName("AfterStep"); ok {
r.addHook(&r.afterStepHooks, afterStep.Func)
}
return r
}
var rapidTType = reflect.TypeOf(&rapid.T{})