github.com/maps90/godog@v0.7.5-0.20170923143419-0093943021d4/run.go (about)

     1  package godog
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/DATA-DOG/godog/colors"
    13  )
    14  
    15  const (
    16  	exitSuccess int = iota
    17  	exitFailure
    18  	exitOptionError
    19  )
    20  
    21  type initializer func(*Suite)
    22  
    23  type runner struct {
    24  	randomSeed            int64
    25  	stopOnFailure, strict bool
    26  	features              []*feature
    27  	fmt                   Formatter
    28  	initializer           initializer
    29  }
    30  
    31  func (r *runner) concurrent(rate int) (failed bool) {
    32  	queue := make(chan int, rate)
    33  	for i, ft := range r.features {
    34  		queue <- i // reserve space in queue
    35  		go func(fail *bool, feat *feature) {
    36  			defer func() {
    37  				<-queue // free a space in queue
    38  			}()
    39  			if r.stopOnFailure && *fail {
    40  				return
    41  			}
    42  			suite := &Suite{
    43  				fmt:           r.fmt,
    44  				randomSeed:    r.randomSeed,
    45  				stopOnFailure: r.stopOnFailure,
    46  				strict:        r.strict,
    47  				features:      []*feature{feat},
    48  			}
    49  			r.initializer(suite)
    50  			suite.run()
    51  			if suite.failed {
    52  				*fail = true
    53  			}
    54  		}(&failed, ft)
    55  	}
    56  	// wait until last are processed
    57  	for i := 0; i < rate; i++ {
    58  		queue <- i
    59  	}
    60  	close(queue)
    61  
    62  	// print summary
    63  	r.fmt.Summary()
    64  	return
    65  }
    66  
    67  func (r *runner) run() bool {
    68  	suite := &Suite{
    69  		fmt:           r.fmt,
    70  		randomSeed:    r.randomSeed,
    71  		stopOnFailure: r.stopOnFailure,
    72  		strict:        r.strict,
    73  		features:      r.features,
    74  	}
    75  	r.initializer(suite)
    76  	suite.run()
    77  
    78  	r.fmt.Summary()
    79  	return suite.failed
    80  }
    81  
    82  // RunWithOptions is same as Run function, except
    83  // it uses Options provided in order to run the
    84  // test suite without parsing flags
    85  //
    86  // This method is useful in case if you run
    87  // godog in for example TestMain function together
    88  // with go tests
    89  //
    90  // The exit codes may vary from:
    91  //  0 - success
    92  //  1 - failed
    93  //  2 - command line usage error
    94  //  128 - or higher, os signal related error exit codes
    95  //
    96  // If there are flag related errors they will
    97  // be directed to os.Stderr
    98  func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Options) int {
    99  	var output io.Writer = os.Stdout
   100  	if nil != opt.Output {
   101  		output = opt.Output
   102  	}
   103  
   104  	if opt.NoColors {
   105  		output = colors.Uncolored(output)
   106  	} else {
   107  		output = colors.Colored(output)
   108  	}
   109  
   110  	if opt.ShowStepDefinitions {
   111  		s := &Suite{}
   112  		contextInitializer(s)
   113  		s.printStepDefinitions(output)
   114  		return exitOptionError
   115  	}
   116  
   117  	if len(opt.Paths) == 0 {
   118  		inf, err := os.Stat("features")
   119  		if err == nil && inf.IsDir() {
   120  			opt.Paths = []string{"features"}
   121  		}
   122  	}
   123  
   124  	if opt.Concurrency > 1 && !supportsConcurrency(opt.Format) {
   125  		fmt.Fprintln(os.Stderr, fmt.Errorf("format \"%s\" does not support concurrent execution", opt.Format))
   126  		return exitOptionError
   127  	}
   128  	formatter := findFmt(opt.Format)
   129  	if nil == formatter {
   130  		var names []string
   131  		for name := range AvailableFormatters() {
   132  			names = append(names, name)
   133  		}
   134  		fmt.Fprintln(os.Stderr, fmt.Errorf(
   135  			`unregistered formatter name: "%s", use one of: %s`,
   136  			opt.Format,
   137  			strings.Join(names, ", "),
   138  		))
   139  		return exitOptionError
   140  	}
   141  
   142  	features, err := parseFeatures(opt.Tags, opt.Paths)
   143  	if err != nil {
   144  		fmt.Fprintln(os.Stderr, err)
   145  		return exitOptionError
   146  	}
   147  
   148  	r := runner{
   149  		fmt:           formatter(suite, output),
   150  		initializer:   contextInitializer,
   151  		features:      features,
   152  		randomSeed:    opt.Randomize,
   153  		stopOnFailure: opt.StopOnFailure,
   154  		strict:        opt.Strict,
   155  	}
   156  
   157  	// store chosen seed in environment, so it could be seen in formatter summary report
   158  	os.Setenv("GODOG_SEED", strconv.FormatInt(r.randomSeed, 10))
   159  	// determine tested package
   160  	_, filename, _, _ := runtime.Caller(1)
   161  	os.Setenv("GODOG_TESTED_PACKAGE", runsFromPackage(filename))
   162  
   163  	var failed bool
   164  	if opt.Concurrency > 1 {
   165  		failed = r.concurrent(opt.Concurrency)
   166  	} else {
   167  		failed = r.run()
   168  	}
   169  	if failed && opt.Format != "events" {
   170  		return exitFailure
   171  	}
   172  	return exitSuccess
   173  }
   174  
   175  func runsFromPackage(fp string) string {
   176  	dir := filepath.Dir(fp)
   177  	for _, gp := range gopaths {
   178  		gp = filepath.Join(gp, "src")
   179  		if strings.Index(dir, gp) == 0 {
   180  			return strings.TrimLeft(strings.Replace(dir, gp, "", 1), string(filepath.Separator))
   181  		}
   182  	}
   183  	return dir
   184  }
   185  
   186  // Run creates and runs the feature suite.
   187  // Reads all configuration options from flags.
   188  // uses contextInitializer to register contexts
   189  //
   190  // the concurrency option allows runner to
   191  // initialize a number of suites to be run
   192  // separately. Only progress formatter
   193  // is supported when concurrency level is
   194  // higher than 1
   195  //
   196  // contextInitializer must be able to register
   197  // the step definitions and event handlers.
   198  //
   199  // The exit codes may vary from:
   200  //  0 - success
   201  //  1 - failed
   202  //  2 - command line usage error
   203  //  128 - or higher, os signal related error exit codes
   204  //
   205  // If there are flag related errors they will
   206  // be directed to os.Stderr
   207  func Run(suite string, contextInitializer func(suite *Suite)) int {
   208  	var opt Options
   209  	opt.Output = colors.Colored(os.Stdout)
   210  	flagSet := FlagSet(&opt)
   211  	if err := flagSet.Parse(os.Args[1:]); err != nil {
   212  		fmt.Fprintln(os.Stderr, err)
   213  		return exitOptionError
   214  	}
   215  
   216  	opt.Paths = flagSet.Args()
   217  
   218  	return RunWithOptions(suite, contextInitializer, opt)
   219  }
   220  
   221  func supportsConcurrency(format string) bool {
   222  	switch format {
   223  	case "events":
   224  	case "junit":
   225  	case "pretty":
   226  	case "cucumber":
   227  	default:
   228  		return true // supports concurrency
   229  	}
   230  
   231  	return false // does not support concurrency
   232  }