github.com/Secbyte/godog@v0.7.14-0.20200116175429-d8f0aeeb70cf/suite.go (about)

     1  package godog
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"math/rand"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"regexp"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"unicode/utf8"
    16  
    17  	"github.com/DATA-DOG/godog/gherkin"
    18  )
    19  
    20  var errorInterface = reflect.TypeOf((*error)(nil)).Elem()
    21  var typeOfBytes = reflect.TypeOf([]byte(nil))
    22  
    23  type feature struct {
    24  	*gherkin.Feature
    25  	Content []byte `json:"-"`
    26  	Path    string `json:"path"`
    27  	order   int
    28  }
    29  
    30  // ErrUndefined is returned in case if step definition was not found
    31  var ErrUndefined = fmt.Errorf("step is undefined")
    32  
    33  // ErrPending should be returned by step definition if
    34  // step implementation is pending
    35  var ErrPending = fmt.Errorf("step implementation is pending")
    36  
    37  // Suite allows various contexts
    38  // to register steps and event handlers.
    39  //
    40  // When running a test suite, the instance of Suite
    41  // is passed to all functions (contexts), which
    42  // have it as a first and only argument.
    43  //
    44  // Note that all event hooks does not catch panic errors
    45  // in order to have a trace information. Only step
    46  // executions are catching panic error since it may
    47  // be a context specific error.
    48  type Suite struct {
    49  	steps    []*StepDef
    50  	features []*feature
    51  	fmt      Formatter
    52  
    53  	failed        bool
    54  	randomSeed    int64
    55  	stopOnFailure bool
    56  	strict        bool
    57  
    58  	// suite event handlers
    59  	beforeSuiteHandlers    []func()
    60  	beforeFeatureHandlers  []func(*gherkin.Feature)
    61  	beforeScenarioHandlers []func(interface{})
    62  	beforeStepHandlers     []func(*gherkin.Step)
    63  	afterStepHandlers      []func(*gherkin.Step, error)
    64  	afterScenarioHandlers  []func(interface{}, error)
    65  	afterFeatureHandlers   []func(*gherkin.Feature)
    66  	afterSuiteHandlers     []func()
    67  }
    68  
    69  // Step allows to register a *StepDef in Godog
    70  // feature suite, the definition will be applied
    71  // to all steps matching the given Regexp expr.
    72  //
    73  // It will panic if expr is not a valid regular
    74  // expression or stepFunc is not a valid step
    75  // handler.
    76  //
    77  // Note that if there are two definitions which may match
    78  // the same step, then only the first matched handler
    79  // will be applied.
    80  //
    81  // If none of the *StepDef is matched, then
    82  // ErrUndefined error will be returned when
    83  // running steps.
    84  func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
    85  	var regex *regexp.Regexp
    86  
    87  	switch t := expr.(type) {
    88  	case *regexp.Regexp:
    89  		regex = t
    90  	case string:
    91  		regex = regexp.MustCompile(t)
    92  	case []byte:
    93  		regex = regexp.MustCompile(string(t))
    94  	default:
    95  		panic(fmt.Sprintf("expecting expr to be a *regexp.Regexp or a string, got type: %T", expr))
    96  	}
    97  
    98  	v := reflect.ValueOf(stepFunc)
    99  	typ := v.Type()
   100  	if typ.Kind() != reflect.Func {
   101  		panic(fmt.Sprintf("expected handler to be func, but got: %T", stepFunc))
   102  	}
   103  
   104  	if typ.NumOut() != 1 {
   105  		panic(fmt.Sprintf("expected handler to return only one value, but it has: %d", typ.NumOut()))
   106  	}
   107  
   108  	def := &StepDef{
   109  		Handler: stepFunc,
   110  		Expr:    regex,
   111  		hv:      v,
   112  	}
   113  
   114  	typ = typ.Out(0)
   115  	switch typ.Kind() {
   116  	case reflect.Interface:
   117  		if !typ.Implements(errorInterface) {
   118  			panic(fmt.Sprintf("expected handler to return an error, but got: %s", typ.Kind()))
   119  		}
   120  	case reflect.Slice:
   121  		if typ.Elem().Kind() != reflect.String {
   122  			panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind()))
   123  		}
   124  		def.nested = true
   125  	default:
   126  		panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind()))
   127  	}
   128  
   129  	s.steps = append(s.steps, def)
   130  }
   131  
   132  // BeforeSuite registers a function or method
   133  // to be run once before suite runner.
   134  //
   135  // Use it to prepare the test suite for a spin.
   136  // Connect and prepare database for instance...
   137  func (s *Suite) BeforeSuite(fn func()) {
   138  	s.beforeSuiteHandlers = append(s.beforeSuiteHandlers, fn)
   139  }
   140  
   141  // BeforeFeature registers a function or method
   142  // to be run once before every feature execution.
   143  //
   144  // If godog is run with concurrency option, it will
   145  // run every feature per goroutine. So user may choose
   146  // whether to isolate state within feature context or
   147  // scenario.
   148  //
   149  // Best practice is not to have any state dependency on
   150  // every scenario, but in some cases if VM for example
   151  // needs to be started it may take very long for each
   152  // scenario to restart it.
   153  //
   154  // Use it wisely and avoid sharing state between scenarios.
   155  func (s *Suite) BeforeFeature(fn func(*gherkin.Feature)) {
   156  	s.beforeFeatureHandlers = append(s.beforeFeatureHandlers, fn)
   157  }
   158  
   159  // BeforeScenario registers a function or method
   160  // to be run before every scenario or scenario outline.
   161  //
   162  // The interface argument may be *gherkin.Scenario
   163  // or *gherkin.ScenarioOutline
   164  //
   165  // It is a good practice to restore the default state
   166  // before every scenario so it would be isolated from
   167  // any kind of state.
   168  func (s *Suite) BeforeScenario(fn func(interface{})) {
   169  	s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, fn)
   170  }
   171  
   172  // BeforeStep registers a function or method
   173  // to be run before every scenario
   174  func (s *Suite) BeforeStep(fn func(*gherkin.Step)) {
   175  	s.beforeStepHandlers = append(s.beforeStepHandlers, fn)
   176  }
   177  
   178  // AfterStep registers an function or method
   179  // to be run after every scenario
   180  //
   181  // It may be convenient to return a different kind of error
   182  // in order to print more state details which may help
   183  // in case of step failure
   184  //
   185  // In some cases, for example when running a headless
   186  // browser, to take a screenshot after failure.
   187  func (s *Suite) AfterStep(fn func(*gherkin.Step, error)) {
   188  	s.afterStepHandlers = append(s.afterStepHandlers, fn)
   189  }
   190  
   191  // AfterScenario registers an function or method
   192  // to be run after every scenario or scenario outline
   193  //
   194  // The interface argument may be *gherkin.Scenario
   195  // or *gherkin.ScenarioOutline
   196  func (s *Suite) AfterScenario(fn func(interface{}, error)) {
   197  	s.afterScenarioHandlers = append(s.afterScenarioHandlers, fn)
   198  }
   199  
   200  // AfterFeature registers a function or method
   201  // to be run once after feature executed all scenarios.
   202  func (s *Suite) AfterFeature(fn func(*gherkin.Feature)) {
   203  	s.afterFeatureHandlers = append(s.afterFeatureHandlers, fn)
   204  }
   205  
   206  // AfterSuite registers a function or method
   207  // to be run once after suite runner
   208  func (s *Suite) AfterSuite(fn func()) {
   209  	s.afterSuiteHandlers = append(s.afterSuiteHandlers, fn)
   210  }
   211  
   212  func (s *Suite) run() {
   213  	// run before suite handlers
   214  	for _, f := range s.beforeSuiteHandlers {
   215  		f()
   216  	}
   217  	// run features
   218  	for _, f := range s.features {
   219  		s.runFeature(f)
   220  		if s.failed && s.stopOnFailure {
   221  			// stop on first failure
   222  			break
   223  		}
   224  	}
   225  	// run after suite handlers
   226  	for _, f := range s.afterSuiteHandlers {
   227  		f()
   228  	}
   229  }
   230  
   231  func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
   232  	def := s.matchStepText(step.Text)
   233  	if def != nil && step.Argument != nil {
   234  		def.args = append(def.args, step.Argument)
   235  	}
   236  	return def
   237  }
   238  
   239  func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
   240  	// run before step handlers
   241  	for _, f := range s.beforeStepHandlers {
   242  		f(step)
   243  	}
   244  
   245  	match := s.matchStep(step)
   246  	s.fmt.Defined(step, match)
   247  
   248  	// user multistep definitions may panic
   249  	defer func() {
   250  		if e := recover(); e != nil {
   251  			err = &traceError{
   252  				msg:   fmt.Sprintf("%v", e),
   253  				stack: callStack(),
   254  			}
   255  		}
   256  
   257  		if prevStepErr != nil {
   258  			return
   259  		}
   260  
   261  		if err == ErrUndefined {
   262  			return
   263  		}
   264  
   265  		switch err {
   266  		case nil:
   267  			s.fmt.Passed(step, match)
   268  		case ErrPending:
   269  			s.fmt.Pending(step, match)
   270  		default:
   271  			s.fmt.Failed(step, match, err)
   272  		}
   273  
   274  		// run after step handlers
   275  		for _, f := range s.afterStepHandlers {
   276  			f(step, err)
   277  		}
   278  	}()
   279  
   280  	if undef, err := s.maybeUndefined(step.Text, step.Argument); err != nil {
   281  		return err
   282  	} else if len(undef) > 0 {
   283  		if match != nil {
   284  			match = &StepDef{
   285  				args:      match.args,
   286  				hv:        match.hv,
   287  				Expr:      match.Expr,
   288  				Handler:   match.Handler,
   289  				nested:    match.nested,
   290  				undefined: undef,
   291  			}
   292  		}
   293  		s.fmt.Undefined(step, match)
   294  		return ErrUndefined
   295  	}
   296  
   297  	if prevStepErr != nil {
   298  		s.fmt.Skipped(step, match)
   299  		return nil
   300  	}
   301  
   302  	err = s.maybeSubSteps(match.run())
   303  	return
   304  }
   305  
   306  func (s *Suite) maybeUndefined(text string, arg interface{}) ([]string, error) {
   307  	step := s.matchStepText(text)
   308  	if nil == step {
   309  		return []string{text}, nil
   310  	}
   311  
   312  	var undefined []string
   313  	if !step.nested {
   314  		return undefined, nil
   315  	}
   316  
   317  	if arg != nil {
   318  		step.args = append(step.args, arg)
   319  	}
   320  
   321  	for _, next := range step.run().(Steps) {
   322  		lines := strings.Split(next, "\n")
   323  		// @TODO: we cannot currently parse table or content body from nested steps
   324  		if len(lines) > 1 {
   325  			return undefined, fmt.Errorf("nested steps cannot be multiline and have table or content body argument")
   326  		}
   327  		if len(lines[0]) > 0 && lines[0][len(lines[0])-1] == ':' {
   328  			return undefined, fmt.Errorf("nested steps cannot be multiline and have table or content body argument")
   329  		}
   330  		undef, err := s.maybeUndefined(next, nil)
   331  		if err != nil {
   332  			return undefined, err
   333  		}
   334  		undefined = append(undefined, undef...)
   335  	}
   336  	return undefined, nil
   337  }
   338  
   339  func (s *Suite) maybeSubSteps(result interface{}) error {
   340  	if nil == result {
   341  		return nil
   342  	}
   343  
   344  	if err, ok := result.(error); ok {
   345  		return err
   346  	}
   347  
   348  	steps, ok := result.(Steps)
   349  	if !ok {
   350  		return fmt.Errorf("unexpected error, should have been []string: %T - %+v", result, result)
   351  	}
   352  
   353  	for _, text := range steps {
   354  		if def := s.matchStepText(text); def == nil {
   355  			return ErrUndefined
   356  		} else if err := s.maybeSubSteps(def.run()); err != nil {
   357  			return fmt.Errorf("%s: %+v", text, err)
   358  		}
   359  	}
   360  	return nil
   361  }
   362  
   363  func (s *Suite) matchStepText(text string) *StepDef {
   364  	for _, h := range s.steps {
   365  		if m := h.Expr.FindStringSubmatch(text); len(m) > 0 {
   366  			var args []interface{}
   367  			for _, m := range m[1:] {
   368  				args = append(args, m)
   369  			}
   370  
   371  			// since we need to assign arguments
   372  			// better to copy the step definition
   373  			return &StepDef{
   374  				args:    args,
   375  				hv:      h.hv,
   376  				Expr:    h.Expr,
   377  				Handler: h.Handler,
   378  				nested:  h.nested,
   379  			}
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  func (s *Suite) runSteps(steps []*gherkin.Step) (err error) {
   386  	for _, step := range steps {
   387  		stepErr := s.runStep(step, err)
   388  		switch stepErr {
   389  		case ErrUndefined:
   390  			// do not overwrite failed error
   391  			if err == ErrUndefined || err == nil {
   392  				err = stepErr
   393  			}
   394  		case ErrPending:
   395  			err = stepErr
   396  		case nil:
   397  		default:
   398  			err = stepErr
   399  		}
   400  	}
   401  	return
   402  }
   403  
   404  func (s *Suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Background) (failErr error) {
   405  	s.fmt.Node(outline)
   406  
   407  	for _, ex := range outline.Examples {
   408  		example, hasExamples := examples(ex)
   409  		if !hasExamples {
   410  			// @TODO: may need to print empty example node, but
   411  			// for backward compatibility, cannot cast to *gherkin.ExamplesBase
   412  			// at the moment
   413  			continue
   414  		}
   415  
   416  		s.fmt.Node(example)
   417  		placeholders := example.TableHeader.Cells
   418  		groups := example.TableBody
   419  
   420  		for _, group := range groups {
   421  			if !isEmptyScenario(outline) {
   422  				for _, f := range s.beforeScenarioHandlers {
   423  					f(outline)
   424  				}
   425  			}
   426  			var steps []*gherkin.Step
   427  			for _, outlineStep := range outline.Steps {
   428  				text := outlineStep.Text
   429  				for i, placeholder := range placeholders {
   430  					text = strings.Replace(text, "<"+placeholder.Value+">", group.Cells[i].Value, -1)
   431  				}
   432  
   433  				// translate argument
   434  				arg := outlineStep.Argument
   435  				switch t := outlineStep.Argument.(type) {
   436  				case *gherkin.DataTable:
   437  					tbl := &gherkin.DataTable{
   438  						Node: t.Node,
   439  						Rows: make([]*gherkin.TableRow, len(t.Rows)),
   440  					}
   441  					for i, row := range t.Rows {
   442  						cells := make([]*gherkin.TableCell, len(row.Cells))
   443  						for j, cell := range row.Cells {
   444  							trans := cell.Value
   445  							for i, placeholder := range placeholders {
   446  								trans = strings.Replace(trans, "<"+placeholder.Value+">", group.Cells[i].Value, -1)
   447  							}
   448  							cells[j] = &gherkin.TableCell{
   449  								Node:  cell.Node,
   450  								Value: trans,
   451  							}
   452  						}
   453  						tbl.Rows[i] = &gherkin.TableRow{
   454  							Node:  row.Node,
   455  							Cells: cells,
   456  						}
   457  					}
   458  					arg = tbl
   459  				case *gherkin.DocString:
   460  					trans := t.Content
   461  					for i, placeholder := range placeholders {
   462  						trans = strings.Replace(trans, "<"+placeholder.Value+">", group.Cells[i].Value, -1)
   463  					}
   464  					arg = &gherkin.DocString{
   465  						Node:        t.Node,
   466  						Content:     trans,
   467  						ContentType: t.ContentType,
   468  						Delimitter:  t.Delimitter,
   469  					}
   470  				}
   471  
   472  				// clone a step
   473  				step := &gherkin.Step{
   474  					Node:     outlineStep.Node,
   475  					Text:     text,
   476  					Keyword:  outlineStep.Keyword,
   477  					Argument: arg,
   478  				}
   479  				steps = append(steps, step)
   480  			}
   481  
   482  			// run example table row
   483  			s.fmt.Node(group)
   484  
   485  			if b != nil {
   486  				steps = append(b.Steps, steps...)
   487  			}
   488  
   489  			err := s.runSteps(steps)
   490  
   491  			if !isEmptyScenario(outline) {
   492  				for _, f := range s.afterScenarioHandlers {
   493  					f(outline, err)
   494  				}
   495  			}
   496  
   497  			if s.shouldFail(err) {
   498  				failErr = err
   499  				if s.stopOnFailure {
   500  					return
   501  				}
   502  			}
   503  		}
   504  	}
   505  	return
   506  }
   507  
   508  func (s *Suite) shouldFail(err error) bool {
   509  	if err == nil {
   510  		return false
   511  	}
   512  
   513  	if err == ErrUndefined || err == ErrPending {
   514  		return s.strict
   515  	}
   516  
   517  	return true
   518  }
   519  
   520  func (s *Suite) runFeature(f *feature) {
   521  	if !isEmptyFeature(f.Feature) {
   522  		for _, fn := range s.beforeFeatureHandlers {
   523  			fn(f.Feature)
   524  		}
   525  	}
   526  
   527  	s.fmt.Feature(f.Feature, f.Path, f.Content)
   528  
   529  	// make a local copy of the feature scenario defenitions,
   530  	// then shuffle it if we are randomizing scenarios
   531  	scenarios := make([]interface{}, len(f.ScenarioDefinitions))
   532  	if s.randomSeed != 0 {
   533  		r := rand.New(rand.NewSource(s.randomSeed))
   534  		perm := r.Perm(len(f.ScenarioDefinitions))
   535  		for i, v := range perm {
   536  			scenarios[v] = f.ScenarioDefinitions[i]
   537  		}
   538  	} else {
   539  		copy(scenarios, f.ScenarioDefinitions)
   540  	}
   541  
   542  	defer func() {
   543  		if !isEmptyFeature(f.Feature) {
   544  			for _, fn := range s.afterFeatureHandlers {
   545  				fn(f.Feature)
   546  			}
   547  		}
   548  	}()
   549  
   550  	for _, scenario := range scenarios {
   551  		var err error
   552  		if f.Background != nil {
   553  			s.fmt.Node(f.Background)
   554  		}
   555  		switch t := scenario.(type) {
   556  		case *gherkin.ScenarioOutline:
   557  			err = s.runOutline(t, f.Background)
   558  		case *gherkin.Scenario:
   559  			err = s.runScenario(t, f.Background)
   560  		}
   561  		if s.shouldFail(err) {
   562  			s.failed = true
   563  			if s.stopOnFailure {
   564  				return
   565  			}
   566  		}
   567  	}
   568  }
   569  
   570  func (s *Suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) (err error) {
   571  	if isEmptyScenario(scenario) {
   572  		s.fmt.Node(scenario)
   573  		return ErrUndefined
   574  	}
   575  
   576  	// run before scenario handlers
   577  	for _, f := range s.beforeScenarioHandlers {
   578  		f(scenario)
   579  	}
   580  
   581  	s.fmt.Node(scenario)
   582  
   583  	// background
   584  	steps := scenario.Steps
   585  	if b != nil {
   586  		steps = append(b.Steps, steps...)
   587  	}
   588  
   589  	// scenario
   590  	err = s.runSteps(steps)
   591  
   592  	// run after scenario handlers
   593  	for _, f := range s.afterScenarioHandlers {
   594  		f(scenario, err)
   595  	}
   596  
   597  	return
   598  }
   599  
   600  func (s *Suite) printStepDefinitions(w io.Writer) {
   601  	var longest int
   602  	for _, def := range s.steps {
   603  		n := utf8.RuneCountInString(def.Expr.String())
   604  		if longest < n {
   605  			longest = n
   606  		}
   607  	}
   608  	for _, def := range s.steps {
   609  		n := utf8.RuneCountInString(def.Expr.String())
   610  		location := def.definitionID()
   611  		spaces := strings.Repeat(" ", longest-n)
   612  		fmt.Fprintln(w, yellow(def.Expr.String())+spaces, blackb("# "+location))
   613  	}
   614  	if len(s.steps) == 0 {
   615  		fmt.Fprintln(w, "there were no contexts registered, could not find any step definition..")
   616  	}
   617  }
   618  
   619  var pathLineRe = regexp.MustCompile(`:([\d]+)$`)
   620  
   621  func extractFeaturePathLine(p string) (string, int) {
   622  	line := -1
   623  	retPath := p
   624  	if m := pathLineRe.FindStringSubmatch(p); len(m) > 0 {
   625  		if i, err := strconv.Atoi(m[1]); err == nil {
   626  			line = i
   627  			retPath = p[:strings.LastIndexByte(p, ':')]
   628  		}
   629  	}
   630  	return retPath, line
   631  }
   632  
   633  func parseFeatureFile(path string) (*feature, error) {
   634  	reader, err := os.Open(path)
   635  	if err != nil {
   636  		return nil, err
   637  	}
   638  	defer reader.Close()
   639  
   640  	var buf bytes.Buffer
   641  	ft, err := gherkin.ParseFeature(io.TeeReader(reader, &buf))
   642  	if err != nil {
   643  		return nil, fmt.Errorf("%s - %v", path, err)
   644  	}
   645  
   646  	return &feature{
   647  		Path:    path,
   648  		Feature: ft,
   649  		Content: buf.Bytes(),
   650  	}, nil
   651  }
   652  
   653  func parseFeatureDir(dir string) ([]*feature, error) {
   654  	var features []*feature
   655  	return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
   656  		if err != nil {
   657  			return err
   658  		}
   659  
   660  		if f.IsDir() {
   661  			return nil
   662  		}
   663  
   664  		if !strings.HasSuffix(p, ".feature") {
   665  			return nil
   666  		}
   667  
   668  		feat, err := parseFeatureFile(p)
   669  		if err != nil {
   670  			return err
   671  		}
   672  		features = append(features, feat)
   673  		return nil
   674  	})
   675  }
   676  
   677  func parsePath(path string) ([]*feature, error) {
   678  	var features []*feature
   679  	// check if line number is specified
   680  	var line int
   681  	path, line = extractFeaturePathLine(path)
   682  
   683  	fi, err := os.Stat(path)
   684  	if err != nil {
   685  		return features, err
   686  	}
   687  
   688  	if fi.IsDir() {
   689  		return parseFeatureDir(path)
   690  	}
   691  
   692  	ft, err := parseFeatureFile(path)
   693  	if err != nil {
   694  		return features, err
   695  	}
   696  
   697  	// filter scenario by line number
   698  	var scenarios []interface{}
   699  	for _, def := range ft.ScenarioDefinitions {
   700  		var ln int
   701  		switch t := def.(type) {
   702  		case *gherkin.Scenario:
   703  			ln = t.Location.Line
   704  		case *gherkin.ScenarioOutline:
   705  			ln = t.Location.Line
   706  		}
   707  		if line == -1 || ln == line {
   708  			scenarios = append(scenarios, def)
   709  		}
   710  	}
   711  	ft.ScenarioDefinitions = scenarios
   712  	return append(features, ft), nil
   713  }
   714  
   715  func parseFeatures(filter string, paths []string) ([]*feature, error) {
   716  	byPath := make(map[string]*feature)
   717  	var order int
   718  	for _, path := range paths {
   719  		feats, err := parsePath(path)
   720  		switch {
   721  		case os.IsNotExist(err):
   722  			return nil, fmt.Errorf(`feature path "%s" is not available`, path)
   723  		case os.IsPermission(err):
   724  			return nil, fmt.Errorf(`feature path "%s" is not accessible`, path)
   725  		case err != nil:
   726  			return nil, err
   727  		}
   728  
   729  		for _, ft := range feats {
   730  			if _, duplicate := byPath[ft.Path]; duplicate {
   731  				continue
   732  			}
   733  
   734  			ft.order = order
   735  			order++
   736  			byPath[ft.Path] = ft
   737  		}
   738  	}
   739  	return filterFeatures(filter, byPath), nil
   740  }
   741  
   742  type sortByOrderGiven []*feature
   743  
   744  func (s sortByOrderGiven) Len() int           { return len(s) }
   745  func (s sortByOrderGiven) Less(i, j int) bool { return s[i].order < s[j].order }
   746  func (s sortByOrderGiven) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   747  
   748  func filterFeatures(tags string, collected map[string]*feature) (features []*feature) {
   749  	for _, ft := range collected {
   750  		applyTagFilter(tags, ft.Feature)
   751  		features = append(features, ft)
   752  	}
   753  
   754  	sort.Sort(sortByOrderGiven(features))
   755  
   756  	return features
   757  }
   758  
   759  func applyTagFilter(tags string, ft *gherkin.Feature) {
   760  	if len(tags) == 0 {
   761  		return
   762  	}
   763  
   764  	var scenarios []interface{}
   765  	for _, scenario := range ft.ScenarioDefinitions {
   766  		switch t := scenario.(type) {
   767  		case *gherkin.ScenarioOutline:
   768  			var allExamples []*gherkin.Examples
   769  			for _, examples := range t.Examples {
   770  				if matchesTags(tags, allTags(ft, t, examples)) {
   771  					allExamples = append(allExamples, examples)
   772  				}
   773  			}
   774  			t.Examples = allExamples
   775  			if len(t.Examples) > 0 {
   776  				scenarios = append(scenarios, scenario)
   777  			}
   778  		case *gherkin.Scenario:
   779  			if matchesTags(tags, allTags(ft, t)) {
   780  				scenarios = append(scenarios, scenario)
   781  			}
   782  		}
   783  	}
   784  	ft.ScenarioDefinitions = scenarios
   785  }
   786  
   787  func allTags(nodes ...interface{}) []string {
   788  	var tags, tmp []string
   789  	for _, node := range nodes {
   790  		var gr []*gherkin.Tag
   791  		switch t := node.(type) {
   792  		case *gherkin.Feature:
   793  			gr = t.Tags
   794  		case *gherkin.ScenarioOutline:
   795  			gr = t.Tags
   796  		case *gherkin.Scenario:
   797  			gr = t.Tags
   798  		case *gherkin.Examples:
   799  			gr = t.Tags
   800  		}
   801  
   802  		for _, gtag := range gr {
   803  			tag := strings.TrimSpace(gtag.Name)
   804  			if tag[0] == '@' {
   805  				tag = tag[1:]
   806  			}
   807  			copy(tmp, tags)
   808  			var found bool
   809  			for _, tg := range tmp {
   810  				if tg == tag {
   811  					found = true
   812  					break
   813  				}
   814  			}
   815  			if !found {
   816  				tags = append(tags, tag)
   817  			}
   818  		}
   819  	}
   820  	return tags
   821  }
   822  
   823  func hasTag(tags []string, tag string) bool {
   824  	for _, t := range tags {
   825  		if t == tag {
   826  			return true
   827  		}
   828  	}
   829  	return false
   830  }
   831  
   832  // based on http://behat.readthedocs.org/en/v2.5/guides/6.cli.html#gherkin-filters
   833  func matchesTags(filter string, tags []string) (ok bool) {
   834  	ok = true
   835  	for _, andTags := range strings.Split(filter, "&&") {
   836  		var okComma bool
   837  		for _, tag := range strings.Split(andTags, ",") {
   838  			tag = strings.Replace(strings.TrimSpace(tag), "@", "", -1)
   839  			if tag[0] == '~' {
   840  				tag = tag[1:]
   841  				okComma = !hasTag(tags, tag) || okComma
   842  			} else {
   843  				okComma = hasTag(tags, tag) || okComma
   844  			}
   845  		}
   846  		ok = ok && okComma
   847  	}
   848  	return
   849  }
   850  
   851  // GetFormatter this allows the formatter copy for that instance of the suite to be referenced
   852  func (s *Suite) GetFormatter() Formatter {
   853  	return s.fmt
   854  }