github.com/agilebits/godog@v0.7.9/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 // user may have specified -1 option to create random seed 149 randomize := opt.Randomize 150 if randomize == -1 { 151 randomize = makeRandomSeed() 152 } 153 154 r := runner{ 155 fmt: formatter(suite, output), 156 initializer: contextInitializer, 157 features: features, 158 randomSeed: randomize, 159 stopOnFailure: opt.StopOnFailure, 160 strict: opt.Strict, 161 } 162 163 // store chosen seed in environment, so it could be seen in formatter summary report 164 os.Setenv("GODOG_SEED", strconv.FormatInt(r.randomSeed, 10)) 165 // determine tested package 166 _, filename, _, _ := runtime.Caller(1) 167 os.Setenv("GODOG_TESTED_PACKAGE", runsFromPackage(filename)) 168 169 var failed bool 170 if opt.Concurrency > 1 { 171 failed = r.concurrent(opt.Concurrency) 172 } else { 173 failed = r.run() 174 } 175 176 // @TODO: should prevent from having these 177 os.Setenv("GODOG_SEED", "") 178 os.Setenv("GODOG_TESTED_PACKAGE", "") 179 if failed && opt.Format != "events" { 180 return exitFailure 181 } 182 return exitSuccess 183 } 184 185 func runsFromPackage(fp string) string { 186 dir := filepath.Dir(fp) 187 for _, gp := range gopaths { 188 gp = filepath.Join(gp, "src") 189 if strings.Index(dir, gp) == 0 { 190 return strings.TrimLeft(strings.Replace(dir, gp, "", 1), string(filepath.Separator)) 191 } 192 } 193 return dir 194 } 195 196 // Run creates and runs the feature suite. 197 // Reads all configuration options from flags. 198 // uses contextInitializer to register contexts 199 // 200 // the concurrency option allows runner to 201 // initialize a number of suites to be run 202 // separately. Only progress formatter 203 // is supported when concurrency level is 204 // higher than 1 205 // 206 // contextInitializer must be able to register 207 // the step definitions and event handlers. 208 // 209 // The exit codes may vary from: 210 // 0 - success 211 // 1 - failed 212 // 2 - command line usage error 213 // 128 - or higher, os signal related error exit codes 214 // 215 // If there are flag related errors they will 216 // be directed to os.Stderr 217 func Run(suite string, contextInitializer func(suite *Suite)) int { 218 var opt Options 219 opt.Output = colors.Colored(os.Stdout) 220 flagSet := FlagSet(&opt) 221 if err := flagSet.Parse(os.Args[1:]); err != nil { 222 fmt.Fprintln(os.Stderr, err) 223 return exitOptionError 224 } 225 226 opt.Paths = flagSet.Args() 227 228 return RunWithOptions(suite, contextInitializer, opt) 229 } 230 231 func supportsConcurrency(format string) bool { 232 switch format { 233 case "events": 234 case "junit": 235 case "pretty": 236 case "cucumber": 237 default: 238 return true // supports concurrency 239 } 240 241 return false // does not support concurrency 242 }