github.com/lonnblad/godog@v0.7.14-0.20200306004719-1b0cb3259847/suite.go (about)

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