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  }