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