github.com/lonnblad/godog@v0.7.14-0.20200306004719-1b0cb3259847/run_test.go (about) 1 package godog 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "regexp" 10 "strings" 11 "testing" 12 13 "github.com/cucumber/gherkin-go/v9" 14 "github.com/cucumber/messages-go/v9" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 18 "github.com/lonnblad/godog/colors" 19 ) 20 21 func okStep() error { 22 return nil 23 } 24 25 func TestPrintsStepDefinitions(t *testing.T) { 26 var buf bytes.Buffer 27 w := colors.Uncolored(&buf) 28 s := &Suite{} 29 30 steps := []string{ 31 "^passing step$", 32 `^with name "([^"])"`, 33 } 34 35 for _, step := range steps { 36 s.Step(step, okStep) 37 } 38 s.printStepDefinitions(w) 39 40 out := buf.String() 41 ref := `okStep` 42 for i, def := range strings.Split(strings.TrimSpace(out), "\n") { 43 if idx := strings.Index(def, steps[i]); idx == -1 { 44 t.Fatalf(`step "%s" was not found in output`, steps[i]) 45 } 46 if idx := strings.Index(def, ref); idx == -1 { 47 t.Fatalf(`step definition reference "%s" was not found in output: "%s"`, ref, def) 48 } 49 } 50 } 51 52 func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) { 53 var buf bytes.Buffer 54 w := colors.Uncolored(&buf) 55 s := &Suite{} 56 s.printStepDefinitions(w) 57 58 out := strings.TrimSpace(buf.String()) 59 assert.Equal(t, "there were no contexts registered, could not find any step definition..", out) 60 } 61 62 func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) { 63 const path = "any.feature" 64 65 gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId) 66 require.NoError(t, err) 67 68 pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) 69 70 r := runner{ 71 fmt: progressFunc("progress", ioutil.Discard), 72 features: []*feature{{GherkinDocument: gd, pickles: pickles}}, 73 initializer: func(s *Suite) { 74 s.Step(`^one$`, func() error { return nil }) 75 s.Step(`^two$`, func() error { return ErrPending }) 76 }, 77 } 78 79 assert.False(t, r.run()) 80 81 r.strict = true 82 assert.True(t, r.run()) 83 } 84 85 func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) { 86 const path = "any.feature" 87 88 gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId) 89 require.NoError(t, err) 90 91 pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) 92 93 r := runner{ 94 fmt: progressFunc("progress", ioutil.Discard), 95 features: []*feature{{GherkinDocument: gd, pickles: pickles}}, 96 initializer: func(s *Suite) { 97 s.Step(`^one$`, func() error { return nil }) 98 // two - is undefined 99 }, 100 } 101 102 assert.False(t, r.run()) 103 104 r.strict = true 105 assert.True(t, r.run()) 106 } 107 108 func TestShouldFailOnError(t *testing.T) { 109 const path = "any.feature" 110 111 gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId) 112 require.NoError(t, err) 113 114 pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) 115 116 r := runner{ 117 fmt: progressFunc("progress", ioutil.Discard), 118 features: []*feature{{GherkinDocument: gd, pickles: pickles}}, 119 initializer: func(s *Suite) { 120 s.Step(`^one$`, func() error { return nil }) 121 s.Step(`^two$`, func() error { return fmt.Errorf("error") }) 122 }, 123 } 124 125 assert.True(t, r.run()) 126 } 127 128 func TestFailsWithConcurrencyOptionError(t *testing.T) { 129 stderr, closer := bufErrorPipe(t) 130 defer closer() 131 defer stderr.Close() 132 133 opt := Options{ 134 Format: "pretty", 135 Paths: []string{"features/load:6"}, 136 Concurrency: 2, 137 Output: ioutil.Discard, 138 } 139 140 status := RunWithOptions("fails", func(_ *Suite) {}, opt) 141 require.Equal(t, exitOptionError, status) 142 143 closer() 144 145 b, err := ioutil.ReadAll(stderr) 146 require.NoError(t, err) 147 148 out := strings.TrimSpace(string(b)) 149 assert.Equal(t, `format "pretty" does not support concurrent execution`, out) 150 } 151 152 func TestFailsWithUnknownFormatterOptionError(t *testing.T) { 153 stderr, closer := bufErrorPipe(t) 154 defer closer() 155 defer stderr.Close() 156 157 opt := Options{ 158 Format: "unknown", 159 Paths: []string{"features/load:6"}, 160 Output: ioutil.Discard, 161 } 162 163 status := RunWithOptions("fails", func(_ *Suite) {}, opt) 164 require.Equal(t, exitOptionError, status) 165 166 closer() 167 168 b, err := ioutil.ReadAll(stderr) 169 require.NoError(t, err) 170 171 out := strings.TrimSpace(string(b)) 172 assert.Contains(t, out, `unregistered formatter name: "unknown", use one of`) 173 } 174 175 func TestFailsWithOptionErrorWhenLookingForFeaturesInUnavailablePath(t *testing.T) { 176 stderr, closer := bufErrorPipe(t) 177 defer closer() 178 defer stderr.Close() 179 180 opt := Options{ 181 Format: "progress", 182 Paths: []string{"unavailable"}, 183 Output: ioutil.Discard, 184 } 185 186 status := RunWithOptions("fails", func(_ *Suite) {}, opt) 187 require.Equal(t, exitOptionError, status) 188 189 closer() 190 191 b, err := ioutil.ReadAll(stderr) 192 require.NoError(t, err) 193 194 out := strings.TrimSpace(string(b)) 195 assert.Equal(t, `feature path "unavailable" is not available`, out) 196 } 197 198 func TestByDefaultRunsFeaturesPath(t *testing.T) { 199 opt := Options{ 200 Format: "progress", 201 Output: ioutil.Discard, 202 Strict: true, 203 } 204 205 status := RunWithOptions("fails", func(_ *Suite) {}, opt) 206 // should fail in strict mode due to undefined steps 207 assert.Equal(t, exitFailure, status) 208 209 opt.Strict = false 210 status = RunWithOptions("succeeds", func(_ *Suite) {}, opt) 211 // should succeed in non strict mode due to undefined steps 212 assert.Equal(t, exitSuccess, status) 213 } 214 215 func bufErrorPipe(t *testing.T) (io.ReadCloser, func()) { 216 stderr := os.Stderr 217 r, w, err := os.Pipe() 218 require.NoError(t, err) 219 220 os.Stderr = w 221 return r, func() { 222 w.Close() 223 os.Stderr = stderr 224 } 225 } 226 227 func TestFeatureFilePathParser(t *testing.T) { 228 229 type Case struct { 230 input string 231 path string 232 line int 233 } 234 235 cases := []Case{ 236 {"/home/test.feature", "/home/test.feature", -1}, 237 {"/home/test.feature:21", "/home/test.feature", 21}, 238 {"test.feature", "test.feature", -1}, 239 {"test.feature:2", "test.feature", 2}, 240 {"", "", -1}, 241 {"/c:/home/test.feature", "/c:/home/test.feature", -1}, 242 {"/c:/home/test.feature:3", "/c:/home/test.feature", 3}, 243 {"D:\\home\\test.feature:3", "D:\\home\\test.feature", 3}, 244 } 245 246 for _, c := range cases { 247 p, ln := extractFeaturePathLine(c.input) 248 assert.Equal(t, p, c.path) 249 assert.Equal(t, ln, c.line) 250 } 251 } 252 253 type succeedRunTestCase struct { 254 format string // formatter to use 255 concurrency int // concurrency option range to test 256 filename string // expected output file 257 } 258 259 func TestConcurrencyRun(t *testing.T) { 260 testCases := []succeedRunTestCase{ 261 {format: "progress", concurrency: 4, filename: "fixtures/progress_output.txt"}, 262 {format: "junit", concurrency: 4, filename: "fixtures/junit_output.xml"}, 263 } 264 265 for _, tc := range testCases { 266 expectedOutput, err := ioutil.ReadFile(tc.filename) 267 require.NoError(t, err) 268 269 for concurrency := range make([]int, tc.concurrency) { 270 t.Run( 271 fmt.Sprintf("%s/concurrency/%d", tc.format, concurrency), 272 func(t *testing.T) { 273 testSucceedRun(t, tc.format, concurrency, string(expectedOutput)) 274 }, 275 ) 276 } 277 } 278 } 279 280 func testSucceedRun(t *testing.T, format string, concurrency int, expected string) { 281 output := new(bytes.Buffer) 282 283 opt := Options{ 284 Format: format, 285 NoColors: true, 286 Paths: []string{"features"}, 287 Concurrency: concurrency, 288 Output: output, 289 } 290 291 status := RunWithOptions("succeed", func(s *Suite) { SuiteContext(s) }, opt) 292 assert.Equal(t, exitSuccess, status) 293 294 b, err := ioutil.ReadAll(output) 295 require.NoError(t, err) 296 297 suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`) 298 299 expected = suiteCtxReg.ReplaceAllString(expected, `suite_context.go:0`) 300 301 actual := strings.TrimSpace(string(b)) 302 actual = suiteCtxReg.ReplaceAllString(actual, `suite_context.go:0`) 303 304 assert.Equalf(t, expected, actual, "[%s]", actual) 305 }