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 }