package godog import ( "bytes" "fmt" "io" "io/ioutil" "os" "regexp" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/cucumber/godog/colors" "github.com/cucumber/godog/gherkin" ) func okStep() error { return nil } func TestPrintsStepDefinitions(t *testing.T) { var buf bytes.Buffer w := colors.Uncolored(&buf) s := &Suite{} steps := []string{ "^passing step$", `^with name "([^"])"`, } for _, step := range steps { s.Step(step, okStep) } s.printStepDefinitions(w) out := buf.String() ref := `okStep` for i, def := range strings.Split(strings.TrimSpace(out), "\n") { if idx := strings.Index(def, steps[i]); idx == -1 { t.Fatalf(`step "%s" was not found in output`, steps[i]) } if idx := strings.Index(def, ref); idx == -1 { t.Fatalf(`step definition reference "%s" was not found in output: "%s"`, ref, def) } } } func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) { var buf bytes.Buffer w := colors.Uncolored(&buf) s := &Suite{} s.printStepDefinitions(w) out := strings.TrimSpace(buf.String()) assert.Equal(t, "there were no contexts registered, could not find any step definition..", out) } func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) { feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature)) require.NoError(t, err) r := runner{ fmt: progressFunc("progress", ioutil.Discard), features: []*feature{&feature{Feature: feat}}, initializer: func(s *Suite) { s.Step(`^one$`, func() error { return nil }) s.Step(`^two$`, func() error { return ErrPending }) }, } assert.False(t, r.run()) r.strict = true assert.True(t, r.run()) } func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) { feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature)) require.NoError(t, err) r := runner{ fmt: progressFunc("progress", ioutil.Discard), features: []*feature{&feature{Feature: feat}}, initializer: func(s *Suite) { s.Step(`^one$`, func() error { return nil }) // two - is undefined }, } assert.False(t, r.run()) r.strict = true assert.True(t, r.run()) } func TestShouldFailOnError(t *testing.T) { feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature)) require.NoError(t, err) r := runner{ fmt: progressFunc("progress", ioutil.Discard), features: []*feature{&feature{Feature: feat}}, initializer: func(s *Suite) { s.Step(`^one$`, func() error { return nil }) s.Step(`^two$`, func() error { return fmt.Errorf("error") }) }, } assert.True(t, r.run()) } func TestFailsWithConcurrencyOptionError(t *testing.T) { stderr, closer := bufErrorPipe(t) defer closer() defer stderr.Close() opt := Options{ Format: "pretty", Paths: []string{"features/load:6"}, Concurrency: 2, Output: ioutil.Discard, } status := RunWithOptions("fails", func(_ *Suite) {}, opt) require.Equal(t, exitOptionError, status) closer() b, err := ioutil.ReadAll(stderr) require.NoError(t, err) out := strings.TrimSpace(string(b)) assert.Equal(t, `format "pretty" does not support concurrent execution`, out) } func TestFailsWithUnknownFormatterOptionError(t *testing.T) { stderr, closer := bufErrorPipe(t) defer closer() defer stderr.Close() opt := Options{ Format: "unknown", Paths: []string{"features/load:6"}, Output: ioutil.Discard, } status := RunWithOptions("fails", func(_ *Suite) {}, opt) require.Equal(t, exitOptionError, status) closer() b, err := ioutil.ReadAll(stderr) require.NoError(t, err) out := strings.TrimSpace(string(b)) assert.Contains(t, out, `unregistered formatter name: "unknown", use one of`) } func TestFailsWithOptionErrorWhenLookingForFeaturesInUnavailablePath(t *testing.T) { stderr, closer := bufErrorPipe(t) defer closer() defer stderr.Close() opt := Options{ Format: "progress", Paths: []string{"unavailable"}, Output: ioutil.Discard, } status := RunWithOptions("fails", func(_ *Suite) {}, opt) require.Equal(t, exitOptionError, status) closer() b, err := ioutil.ReadAll(stderr) require.NoError(t, err) out := strings.TrimSpace(string(b)) assert.Equal(t, `feature path "unavailable" is not available`, out) } func TestByDefaultRunsFeaturesPath(t *testing.T) { opt := Options{ Format: "progress", Output: ioutil.Discard, Strict: true, } status := RunWithOptions("fails", func(_ *Suite) {}, opt) // should fail in strict mode due to undefined steps assert.Equal(t, exitFailure, status) opt.Strict = false status = RunWithOptions("succeeds", func(_ *Suite) {}, opt) // should succeed in non strict mode due to undefined steps assert.Equal(t, exitSuccess, status) } func bufErrorPipe(t *testing.T) (io.ReadCloser, func()) { stderr := os.Stderr r, w, err := os.Pipe() require.NoError(t, err) os.Stderr = w return r, func() { w.Close() os.Stderr = stderr } } func TestFeatureFilePathParser(t *testing.T) { type Case struct { input string path string line int } cases := []Case{ {"/home/test.feature", "/home/test.feature", -1}, {"/home/test.feature:21", "/home/test.feature", 21}, {"test.feature", "test.feature", -1}, {"test.feature:2", "test.feature", 2}, {"", "", -1}, {"/c:/home/test.feature", "/c:/home/test.feature", -1}, {"/c:/home/test.feature:3", "/c:/home/test.feature", 3}, {"D:\\home\\test.feature:3", "D:\\home\\test.feature", 3}, } for _, c := range cases { p, ln := extractFeaturePathLine(c.input) assert.Equal(t, p, c.path) assert.Equal(t, ln, c.line) } } type succeedRunTestCase struct { format string // formatter to use concurrency int // concurrency option range to test filename string // expected output file } func TestSucceedRun(t *testing.T) { testCases := []succeedRunTestCase{ {format: "progress", concurrency: 4, filename: "fixtures/progress_output.txt"}, {format: "junit", concurrency: 4, filename: "fixtures/junit_output.xml"}, {format: "cucumber", concurrency: 2, filename: "fixtures/cucumber_output.json"}, } for _, tc := range testCases { expectedOutputNoConcurrency, err := ioutil.ReadFile(tc.filename) require.NoError(t, err) for concurrency := range make([]int, tc.concurrency) { t.Run( fmt.Sprintf("%s/concurrency/%d", tc.format, concurrency), func(t *testing.T) { testSucceedRun(t, tc.format, concurrency, string(expectedOutputNoConcurrency)) }, ) } } } func testSucceedRun(t *testing.T, format string, concurrency int, expectedOutput string) { output := new(bytes.Buffer) opt := Options{ Format: format, NoColors: true, Paths: []string{"features"}, Concurrency: concurrency, Output: output, } status := RunWithOptions("succeed", func(s *Suite) { SuiteContext(s) }, opt) assert.Equal(t, exitSuccess, status) b, err := ioutil.ReadAll(output) require.NoError(t, err) actual := strings.TrimSpace(string(b)) suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`) expectedOutput = suiteCtxReg.ReplaceAllString(expectedOutput, `suite_context.go:0`) actual = suiteCtxReg.ReplaceAllString(actual, `suite_context.go:0`) assert.Equalf(t, expectedOutput, actual, "[%s]", actual) }