forked from cucumber/godog
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun.go
232 lines (208 loc) · 5.19 KB
/
run.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package godog
import (
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/DATA-DOG/godog/colors"
)
const (
exitSuccess int = iota
exitFailure
exitOptionError
)
type initializer func(*Suite)
type runner struct {
randomSeed int64
stopOnFailure, strict bool
features []*feature
fmt Formatter
initializer initializer
}
func (r *runner) concurrent(rate int) (failed bool) {
queue := make(chan int, rate)
for i, ft := range r.features {
queue <- i // reserve space in queue
go func(fail *bool, feat *feature) {
defer func() {
<-queue // free a space in queue
}()
if r.stopOnFailure && *fail {
return
}
suite := &Suite{
fmt: r.fmt,
randomSeed: r.randomSeed,
stopOnFailure: r.stopOnFailure,
strict: r.strict,
features: []*feature{feat},
}
r.initializer(suite)
suite.run()
if suite.failed {
*fail = true
}
}(&failed, ft)
}
// wait until last are processed
for i := 0; i < rate; i++ {
queue <- i
}
close(queue)
// print summary
r.fmt.Summary()
return
}
func (r *runner) run() bool {
suite := &Suite{
fmt: r.fmt,
randomSeed: r.randomSeed,
stopOnFailure: r.stopOnFailure,
strict: r.strict,
features: r.features,
}
r.initializer(suite)
suite.run()
r.fmt.Summary()
return suite.failed
}
// RunWithOptions is same as Run function, except
// it uses Options provided in order to run the
// test suite without parsing flags
//
// This method is useful in case if you run
// godog in for example TestMain function together
// with go tests
//
// The exit codes may vary from:
// 0 - success
// 1 - failed
// 2 - command line usage error
// 128 - or higher, os signal related error exit codes
//
// If there are flag related errors they will
// be directed to os.Stderr
func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Options) int {
var output io.Writer = os.Stdout
if nil != opt.Output {
output = opt.Output
}
if opt.NoColors {
output = colors.Uncolored(output)
} else {
output = colors.Colored(output)
}
if opt.ShowStepDefinitions {
s := &Suite{}
contextInitializer(s)
s.printStepDefinitions(output)
return exitOptionError
}
if len(opt.Paths) == 0 {
inf, err := os.Stat("features")
if err == nil && inf.IsDir() {
opt.Paths = []string{"features"}
}
}
if opt.Concurrency > 1 && !supportsConcurrency(opt.Format) {
fmt.Fprintln(os.Stderr, fmt.Errorf("format \"%s\" does not support concurrent execution", opt.Format))
return exitOptionError
}
formatter := FindFmt(opt.Format)
if nil == formatter {
var names []string
for name := range AvailableFormatters() {
names = append(names, name)
}
fmt.Fprintln(os.Stderr, fmt.Errorf(
`unregistered formatter name: "%s", use one of: %s`,
opt.Format,
strings.Join(names, ", "),
))
return exitOptionError
}
features, err := parseFeatures(opt.Tags, opt.Paths)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return exitOptionError
}
r := runner{
fmt: formatter(suite, output),
initializer: contextInitializer,
features: features,
randomSeed: opt.Randomize,
stopOnFailure: opt.StopOnFailure,
strict: opt.Strict,
}
// store chosen seed in environment, so it could be seen in formatter summary report
os.Setenv("GODOG_SEED", strconv.FormatInt(r.randomSeed, 10))
// determine tested package
_, filename, _, _ := runtime.Caller(1)
os.Setenv("GODOG_TESTED_PACKAGE", runsFromPackage(filename))
var failed bool
if opt.Concurrency > 1 {
failed = r.concurrent(opt.Concurrency)
} else {
failed = r.run()
}
if failed && opt.Format != "events" {
return exitFailure
}
return exitSuccess
}
func runsFromPackage(fp string) string {
dir := filepath.Dir(fp)
for _, gp := range gopaths {
gp = filepath.Join(gp, "src")
if strings.Index(dir, gp) == 0 {
return strings.TrimLeft(strings.Replace(dir, gp, "", 1), string(filepath.Separator))
}
}
return dir
}
// Run creates and runs the feature suite.
// Reads all configuration options from flags.
// uses contextInitializer to register contexts
//
// the concurrency option allows runner to
// initialize a number of suites to be run
// separately. Only progress formatter
// is supported when concurrency level is
// higher than 1
//
// contextInitializer must be able to register
// the step definitions and event handlers.
//
// The exit codes may vary from:
// 0 - success
// 1 - failed
// 2 - command line usage error
// 128 - or higher, os signal related error exit codes
//
// If there are flag related errors they will
// be directed to os.Stderr
func Run(suite string, contextInitializer func(suite *Suite)) int {
var opt Options
opt.Output = colors.Colored(os.Stdout)
flagSet := FlagSet(&opt)
if err := flagSet.Parse(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
return exitOptionError
}
opt.Paths = flagSet.Args()
return RunWithOptions(suite, contextInitializer, opt)
}
func supportsConcurrency(format string) bool {
switch format {
case "events":
case "junit":
case "pretty":
case "cucumber":
default:
return true // supports concurrency
}
return false // does not support concurrency
}