github.com/joeky888/godog@v0.7.9/suite_context.go (about)

     1  package godog
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"path/filepath"
     9  	"reflect"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/DATA-DOG/godog/gherkin"
    15  )
    16  
    17  // SuiteContext provides steps for godog suite execution and
    18  // can be used for meta-testing of godog features/steps themselves.
    19  //
    20  // Beware, steps or their definitions might change without backward
    21  // compatibility guarantees. A typical user of the godog library should never
    22  // need this, rather it is provided for those developing add-on libraries for godog.
    23  //
    24  // For an example of how to use, see godog's own `features/` and `suite_test.go`.
    25  func SuiteContext(s *Suite, additionalContextInitializers ...func(suite *Suite)) {
    26  	c := &suiteContext{
    27  		extraCIs: additionalContextInitializers,
    28  	}
    29  
    30  	// apply any additional context intializers to modify the context that the
    31  	// meta-tests will be run in
    32  	for _, ci := range additionalContextInitializers {
    33  		ci(s)
    34  	}
    35  
    36  	s.BeforeScenario(c.ResetBeforeEachScenario)
    37  
    38  	s.Step(`^(?:a )?feature path "([^"]*)"$`, c.featurePath)
    39  	s.Step(`^I parse features$`, c.parseFeatures)
    40  	s.Step(`^I'm listening to suite events$`, c.iAmListeningToSuiteEvents)
    41  	s.Step(`^I run feature suite$`, c.iRunFeatureSuite)
    42  	s.Step(`^I run feature suite with tags "([^"]*)"$`, c.iRunFeatureSuiteWithTags)
    43  	s.Step(`^I run feature suite with formatter "([^"]*)"$`, c.iRunFeatureSuiteWithFormatter)
    44  	s.Step(`^(?:a )?feature "([^"]*)"(?: file)?:$`, c.aFeatureFile)
    45  	s.Step(`^the suite should have (passed|failed)$`, c.theSuiteShouldHave)
    46  
    47  	s.Step(`^I should have ([\d]+) features? files?:$`, c.iShouldHaveNumFeatureFiles)
    48  	s.Step(`^I should have ([\d]+) scenarios? registered$`, c.numScenariosRegistered)
    49  	s.Step(`^there (was|were) ([\d]+) "([^"]*)" events? fired$`, c.thereWereNumEventsFired)
    50  	s.Step(`^there was event triggered before scenario "([^"]*)"$`, c.thereWasEventTriggeredBeforeScenario)
    51  	s.Step(`^these events had to be fired for a number of times:$`, c.theseEventsHadToBeFiredForNumberOfTimes)
    52  
    53  	s.Step(`^(?:a )?failing step`, c.aFailingStep)
    54  	s.Step(`^this step should fail`, c.aFailingStep)
    55  	s.Step(`^the following steps? should be (passed|failed|skipped|undefined|pending):`, c.followingStepsShouldHave)
    56  	s.Step(`^all steps should (?:be|have|have been) (passed|failed|skipped|undefined|pending)$`, c.allStepsShouldHave)
    57  	s.Step(`^the undefined step snippets should be:$`, c.theUndefinedStepSnippetsShouldBe)
    58  
    59  	// event stream
    60  	s.Step(`^the following events should be fired:$`, c.thereShouldBeEventsFired)
    61  
    62  	// lt
    63  	s.Step(`^savybių aplankas "([^"]*)"$`, c.featurePath)
    64  	s.Step(`^aš išskaitau savybes$`, c.parseFeatures)
    65  	s.Step(`^aš turėčiau turėti ([\d]+) savybių failus:$`, c.iShouldHaveNumFeatureFiles)
    66  
    67  	s.Step(`^(?:a )?pending step$`, func() error {
    68  		return ErrPending
    69  	})
    70  	s.Step(`^(?:a )?passing step$`, func() error {
    71  		return nil
    72  	})
    73  
    74  	// Introduced to test formatter/cucumber.feature
    75  	s.Step(`^the rendered json will be as follows:$`, c.theRenderJSONWillBe)
    76  
    77  	s.Step(`^(?:a )?failing multistep$`, func() Steps {
    78  		return Steps{"passing step", "failing step"}
    79  	})
    80  
    81  	s.Step(`^(?:a |an )?undefined multistep$`, func() Steps {
    82  		return Steps{"passing step", "undefined step", "passing step"}
    83  	})
    84  
    85  	s.Step(`^(?:a )?passing multistep$`, func() Steps {
    86  		return Steps{"passing step", "passing step", "passing step"}
    87  	})
    88  
    89  	s.Step(`^(?:a )?failing nested multistep$`, func() Steps {
    90  		return Steps{"passing step", "passing multistep", "failing multistep"}
    91  	})
    92  }
    93  
    94  type firedEvent struct {
    95  	name string
    96  	args []interface{}
    97  }
    98  
    99  type suiteContext struct {
   100  	paths       []string
   101  	testedSuite *Suite
   102  	extraCIs    []func(suite *Suite)
   103  	events      []*firedEvent
   104  	out         bytes.Buffer
   105  }
   106  
   107  func (s *suiteContext) ResetBeforeEachScenario(interface{}) {
   108  	// reset whole suite with the state
   109  	s.out.Reset()
   110  	s.paths = []string{}
   111  	s.testedSuite = &Suite{}
   112  	// our tested suite will have the same context registered
   113  	SuiteContext(s.testedSuite, s.extraCIs...)
   114  	// reset all fired events
   115  	s.events = []*firedEvent{}
   116  }
   117  
   118  func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error {
   119  	if err := s.parseFeatures(); err != nil {
   120  		return err
   121  	}
   122  	for _, feat := range s.testedSuite.features {
   123  		applyTagFilter(tags, feat.Feature)
   124  	}
   125  	s.testedSuite.fmt = testFormatterFunc("godog", &s.out)
   126  	s.testedSuite.run()
   127  	s.testedSuite.fmt.Summary()
   128  	return nil
   129  }
   130  
   131  func (s *suiteContext) iRunFeatureSuiteWithFormatter(name string) error {
   132  	f := FindFmt(name)
   133  	if f == nil {
   134  		return fmt.Errorf(`formatter "%s" is not available`, name)
   135  	}
   136  	s.testedSuite.fmt = f("godog", &s.out)
   137  	if err := s.parseFeatures(); err != nil {
   138  		return err
   139  	}
   140  	s.testedSuite.run()
   141  	s.testedSuite.fmt.Summary()
   142  	return nil
   143  }
   144  
   145  func (s *suiteContext) thereShouldBeEventsFired(doc *gherkin.DocString) error {
   146  	actual := strings.Split(strings.TrimSpace(s.out.String()), "\n")
   147  	expect := strings.Split(strings.TrimSpace(doc.Content), "\n")
   148  	if len(expect) != len(actual) {
   149  		return fmt.Errorf("expected %d events, but got %d", len(expect), len(actual))
   150  	}
   151  
   152  	type ev struct {
   153  		Event string
   154  	}
   155  
   156  	for i, event := range actual {
   157  		exp := strings.TrimSpace(expect[i])
   158  		var act ev
   159  		if err := json.Unmarshal([]byte(event), &act); err != nil {
   160  			return fmt.Errorf("failed to read event data: %v", err)
   161  		}
   162  
   163  		if act.Event != exp {
   164  			return fmt.Errorf(`expected event: "%s" at position: %d, but actual was "%s"`, exp, i, act.Event)
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func (s *suiteContext) cleanupSnippet(snip string) string {
   171  	lines := strings.Split(strings.TrimSpace(snip), "\n")
   172  	for i := 0; i < len(lines); i++ {
   173  		lines[i] = strings.TrimSpace(lines[i])
   174  	}
   175  	return strings.Join(lines, "\n")
   176  }
   177  
   178  func (s *suiteContext) theUndefinedStepSnippetsShouldBe(body *gherkin.DocString) error {
   179  	f, ok := s.testedSuite.fmt.(*testFormatter)
   180  	if !ok {
   181  		return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
   182  	}
   183  	actual := s.cleanupSnippet(f.snippets())
   184  	expected := s.cleanupSnippet(body.Content)
   185  	if actual != expected {
   186  		return fmt.Errorf("snippets do not match actual: %s", f.snippets())
   187  	}
   188  	return nil
   189  }
   190  
   191  func (s *suiteContext) followingStepsShouldHave(status string, steps *gherkin.DocString) error {
   192  	var expected = strings.Split(steps.Content, "\n")
   193  	var actual, unmatched, matched []string
   194  
   195  	f, ok := s.testedSuite.fmt.(*testFormatter)
   196  	if !ok {
   197  		return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
   198  	}
   199  	switch status {
   200  	case "passed":
   201  		for _, st := range f.passed {
   202  			actual = append(actual, st.step.Text)
   203  		}
   204  	case "failed":
   205  		for _, st := range f.failed {
   206  			actual = append(actual, st.step.Text)
   207  		}
   208  	case "skipped":
   209  		for _, st := range f.skipped {
   210  			actual = append(actual, st.step.Text)
   211  		}
   212  	case "undefined":
   213  		for _, st := range f.undefined {
   214  			actual = append(actual, st.step.Text)
   215  		}
   216  	case "pending":
   217  		for _, st := range f.pending {
   218  			actual = append(actual, st.step.Text)
   219  		}
   220  	default:
   221  		return fmt.Errorf("unexpected step status wanted: %s", status)
   222  	}
   223  
   224  	if len(expected) > len(actual) {
   225  		return fmt.Errorf("number of expected %s steps: %d is less than actual %s steps: %d", status, len(expected), status, len(actual))
   226  	}
   227  
   228  	for _, a := range actual {
   229  		for _, e := range expected {
   230  			if a == e {
   231  				matched = append(matched, e)
   232  				break
   233  			}
   234  		}
   235  	}
   236  
   237  	if len(matched) >= len(expected) {
   238  		return nil
   239  	}
   240  	for _, s := range expected {
   241  		var found bool
   242  		for _, m := range matched {
   243  			if s == m {
   244  				found = true
   245  				break
   246  			}
   247  		}
   248  		if !found {
   249  			unmatched = append(unmatched, s)
   250  		}
   251  	}
   252  
   253  	return fmt.Errorf("the steps: %s - are not %s", strings.Join(unmatched, ", "), status)
   254  }
   255  
   256  func (s *suiteContext) allStepsShouldHave(status string) error {
   257  	f, ok := s.testedSuite.fmt.(*testFormatter)
   258  	if !ok {
   259  		return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
   260  	}
   261  
   262  	total := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending)
   263  	var actual int
   264  	switch status {
   265  	case "passed":
   266  		actual = len(f.passed)
   267  	case "failed":
   268  		actual = len(f.failed)
   269  	case "skipped":
   270  		actual = len(f.skipped)
   271  	case "undefined":
   272  		actual = len(f.undefined)
   273  	case "pending":
   274  		actual = len(f.pending)
   275  	default:
   276  		return fmt.Errorf("unexpected step status wanted: %s", status)
   277  	}
   278  
   279  	if total > actual {
   280  		return fmt.Errorf("number of expected %s steps: %d is less than actual %s steps: %d", status, total, status, actual)
   281  	}
   282  	return nil
   283  }
   284  
   285  func (s *suiteContext) iAmListeningToSuiteEvents() error {
   286  	s.testedSuite.BeforeSuite(func() {
   287  		s.events = append(s.events, &firedEvent{"BeforeSuite", []interface{}{}})
   288  	})
   289  	s.testedSuite.AfterSuite(func() {
   290  		s.events = append(s.events, &firedEvent{"AfterSuite", []interface{}{}})
   291  	})
   292  	s.testedSuite.BeforeFeature(func(ft *gherkin.Feature) {
   293  		s.events = append(s.events, &firedEvent{"BeforeFeature", []interface{}{ft}})
   294  	})
   295  	s.testedSuite.AfterFeature(func(ft *gherkin.Feature) {
   296  		s.events = append(s.events, &firedEvent{"AfterFeature", []interface{}{ft}})
   297  	})
   298  	s.testedSuite.BeforeScenario(func(scenario interface{}) {
   299  		s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
   300  	})
   301  	s.testedSuite.AfterScenario(func(scenario interface{}, err error) {
   302  		s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, err}})
   303  	})
   304  	s.testedSuite.BeforeStep(func(step *gherkin.Step) {
   305  		s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
   306  	})
   307  	s.testedSuite.AfterStep(func(step *gherkin.Step, err error) {
   308  		s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, err}})
   309  	})
   310  	return nil
   311  }
   312  
   313  func (s *suiteContext) aFailingStep() error {
   314  	return fmt.Errorf("intentional failure")
   315  }
   316  
   317  // parse a given feature file body as a feature
   318  func (s *suiteContext) aFeatureFile(name string, body *gherkin.DocString) error {
   319  	ft, err := gherkin.ParseFeature(strings.NewReader(body.Content))
   320  	s.testedSuite.features = append(s.testedSuite.features, &feature{Feature: ft, Path: name})
   321  	return err
   322  }
   323  
   324  func (s *suiteContext) featurePath(path string) error {
   325  	s.paths = append(s.paths, path)
   326  	return nil
   327  }
   328  
   329  func (s *suiteContext) parseFeatures() error {
   330  	fts, err := parseFeatures("", s.paths)
   331  	if err != nil {
   332  		return err
   333  	}
   334  	s.testedSuite.features = append(s.testedSuite.features, fts...)
   335  	return nil
   336  }
   337  
   338  func (s *suiteContext) theSuiteShouldHave(state string) error {
   339  	if s.testedSuite.failed && state == "passed" {
   340  		return fmt.Errorf("the feature suite has failed")
   341  	}
   342  	if !s.testedSuite.failed && state == "failed" {
   343  		return fmt.Errorf("the feature suite has passed")
   344  	}
   345  	return nil
   346  }
   347  
   348  func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *gherkin.DocString) error {
   349  	if len(s.testedSuite.features) != num {
   350  		return fmt.Errorf("expected %d features to be parsed, but have %d", num, len(s.testedSuite.features))
   351  	}
   352  	expected := strings.Split(files.Content, "\n")
   353  	var actual []string
   354  	for _, ft := range s.testedSuite.features {
   355  		actual = append(actual, ft.Path)
   356  	}
   357  	if len(expected) != len(actual) {
   358  		return fmt.Errorf("expected %d feature paths to be parsed, but have %d", len(expected), len(actual))
   359  	}
   360  	for i := 0; i < len(expected); i++ {
   361  		var matched bool
   362  		split := strings.Split(expected[i], "/")
   363  		exp := filepath.Join(split...)
   364  		for j := 0; j < len(actual); j++ {
   365  			split = strings.Split(actual[j], "/")
   366  			act := filepath.Join(split...)
   367  			if exp == act {
   368  				matched = true
   369  				break
   370  			}
   371  		}
   372  		if !matched {
   373  			return fmt.Errorf(`expected feature path "%s" at position: %d, was not parsed, actual are %+v`, exp, i, actual)
   374  		}
   375  	}
   376  	return nil
   377  }
   378  
   379  func (s *suiteContext) iRunFeatureSuite() error {
   380  	if err := s.parseFeatures(); err != nil {
   381  		return err
   382  	}
   383  	s.testedSuite.fmt = testFormatterFunc("godog", &s.out)
   384  	s.testedSuite.run()
   385  	s.testedSuite.fmt.Summary()
   386  
   387  	return nil
   388  }
   389  
   390  func (s *suiteContext) numScenariosRegistered(expected int) (err error) {
   391  	var num int
   392  	for _, ft := range s.testedSuite.features {
   393  		num += len(ft.ScenarioDefinitions)
   394  	}
   395  	if num != expected {
   396  		err = fmt.Errorf("expected %d scenarios to be registered, but got %d", expected, num)
   397  	}
   398  	return
   399  }
   400  
   401  func (s *suiteContext) thereWereNumEventsFired(_ string, expected int, typ string) error {
   402  	var num int
   403  	for _, event := range s.events {
   404  		if event.name == typ {
   405  			num++
   406  		}
   407  	}
   408  	if num != expected {
   409  		return fmt.Errorf("expected %d %s events to be fired, but got %d", expected, typ, num)
   410  	}
   411  	return nil
   412  }
   413  
   414  func (s *suiteContext) thereWasEventTriggeredBeforeScenario(expected string) error {
   415  	var found []string
   416  	for _, event := range s.events {
   417  		if event.name != "BeforeScenario" {
   418  			continue
   419  		}
   420  
   421  		var name string
   422  		switch t := event.args[0].(type) {
   423  		case *gherkin.Scenario:
   424  			name = t.Name
   425  		case *gherkin.ScenarioOutline:
   426  			name = t.Name
   427  		}
   428  		if name == expected {
   429  			return nil
   430  		}
   431  
   432  		found = append(found, name)
   433  	}
   434  
   435  	if len(found) == 0 {
   436  		return fmt.Errorf("before scenario event was never triggered or listened")
   437  	}
   438  
   439  	return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, expected, `"`+strings.Join(found, `", "`)+`"`)
   440  }
   441  
   442  func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.DataTable) error {
   443  	if len(tbl.Rows[0].Cells) != 2 {
   444  		return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0].Cells))
   445  	}
   446  
   447  	for _, row := range tbl.Rows {
   448  		num, err := strconv.ParseInt(row.Cells[1].Value, 10, 0)
   449  		if err != nil {
   450  			return err
   451  		}
   452  		if err := s.thereWereNumEventsFired("", int(num), row.Cells[0].Value); err != nil {
   453  			return err
   454  		}
   455  	}
   456  	return nil
   457  }
   458  
   459  func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
   460  	loc := regexp.MustCompile(`"suite_context.go:\d+"`)
   461  	var expected []cukeFeatureJSON
   462  	if err := json.Unmarshal([]byte(loc.ReplaceAllString(docstring.Content, `"suite_context.go:0"`)), &expected); err != nil {
   463  		return err
   464  	}
   465  
   466  	var actual []cukeFeatureJSON
   467  	replaced := loc.ReplaceAllString(s.out.String(), `"suite_context.go:0"`)
   468  	if err := json.Unmarshal([]byte(replaced), &actual); err != nil {
   469  		return err
   470  	}
   471  
   472  	if !reflect.DeepEqual(expected, actual) {
   473  		return fmt.Errorf("expected json does not match actual: %s", replaced)
   474  	}
   475  	return nil
   476  }
   477  
   478  type testFormatter struct {
   479  	basefmt
   480  	scenarios []interface{}
   481  }
   482  
   483  func testFormatterFunc(suite string, out io.Writer) Formatter {
   484  	return &testFormatter{
   485  		basefmt: basefmt{
   486  			started: timeNowFunc(),
   487  			indent:  2,
   488  			out:     out,
   489  		},
   490  	}
   491  }
   492  
   493  func (f *testFormatter) Node(node interface{}) {
   494  	f.basefmt.Node(node)
   495  	switch t := node.(type) {
   496  	case *gherkin.Scenario:
   497  		f.scenarios = append(f.scenarios, t)
   498  	case *gherkin.ScenarioOutline:
   499  		f.scenarios = append(f.scenarios, t)
   500  	}
   501  }
   502  
   503  func (f *testFormatter) Summary() {}