github.com/agilebits/godog@v0.7.9/flags.go (about) 1 package godog 2 3 import ( 4 "flag" 5 "fmt" 6 "io" 7 "math/rand" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/DATA-DOG/godog/colors" 13 ) 14 15 var descFeaturesArgument = "Optional feature(s) to run. Can be:\n" + 16 s(4) + "- dir " + colors.Yellow("(features/)") + "\n" + 17 s(4) + "- feature " + colors.Yellow("(*.feature)") + "\n" + 18 s(4) + "- scenario at specific line " + colors.Yellow("(*.feature:10)") + "\n" + 19 "If no feature paths are listed, suite tries " + colors.Yellow("features") + " path by default.\n" 20 21 var descConcurrencyOption = "Run the test suite with concurrency level:\n" + 22 s(4) + "- " + colors.Yellow(`= 1`) + ": supports all types of formats.\n" + 23 s(4) + "- " + colors.Yellow(`>= 2`) + ": only supports " + colors.Yellow("progress") + ". Note, that\n" + 24 s(4) + "your context needs to support parallel execution." 25 26 var descTagsOption = "Filter scenarios by tags. Expression can be:\n" + 27 s(4) + "- " + colors.Yellow(`"@wip"`) + ": run all scenarios with wip tag\n" + 28 s(4) + "- " + colors.Yellow(`"~@wip"`) + ": exclude all scenarios with wip tag\n" + 29 s(4) + "- " + colors.Yellow(`"@wip && ~@new"`) + ": run wip scenarios, but exclude new\n" + 30 s(4) + "- " + colors.Yellow(`"@wip,@undone"`) + ": run wip or undone scenarios" 31 32 var descRandomOption = "Randomly shuffle the scenario execution order.\n" + 33 "Specify SEED to reproduce the shuffling from a previous run.\n" + 34 s(4) + `e.g. ` + colors.Yellow(`--random`) + " or " + colors.Yellow(`--random=5738`) 35 36 // FlagSet allows to manage flags by external suite runner 37 // builds flag.FlagSet with godog flags binded 38 func FlagSet(opt *Options) *flag.FlagSet { 39 set := flag.NewFlagSet("godog", flag.ExitOnError) 40 BindFlags("", set, opt) 41 set.Usage = usage(set, opt.Output) 42 return set 43 } 44 45 // BindFlags binds godog flags to given flag set prefixed 46 // by given prefix, without overriding usage 47 func BindFlags(prefix string, set *flag.FlagSet, opt *Options) { 48 descFormatOption := "How to format tests output. Built-in formats:\n" 49 // @TODO: sort by name 50 for name, desc := range AvailableFormatters() { 51 descFormatOption += s(4) + "- " + colors.Yellow(name) + ": " + desc + "\n" 52 } 53 descFormatOption = strings.TrimSpace(descFormatOption) 54 55 // override flag defaults if any corresponding properties were supplied on the incoming `opt` 56 defFormatOption := "pretty" 57 if opt.Format != "" { 58 defFormatOption = opt.Format 59 } 60 defTagsOption := "" 61 if opt.Tags != "" { 62 defTagsOption = opt.Tags 63 } 64 defConcurrencyOption := 1 65 if opt.Concurrency != 0 { 66 defConcurrencyOption = opt.Concurrency 67 } 68 defShowStepDefinitions := false 69 if opt.ShowStepDefinitions { 70 defShowStepDefinitions = opt.ShowStepDefinitions 71 } 72 defStopOnFailure := false 73 if opt.StopOnFailure { 74 defStopOnFailure = opt.StopOnFailure 75 } 76 defStrict := false 77 if opt.Strict { 78 defStrict = opt.Strict 79 } 80 defNoColors := false 81 if opt.NoColors { 82 defNoColors = opt.NoColors 83 } 84 85 set.StringVar(&opt.Format, prefix+"format", defFormatOption, descFormatOption) 86 set.StringVar(&opt.Format, prefix+"f", defFormatOption, descFormatOption) 87 set.StringVar(&opt.Tags, prefix+"tags", defTagsOption, descTagsOption) 88 set.StringVar(&opt.Tags, prefix+"t", defTagsOption, descTagsOption) 89 set.IntVar(&opt.Concurrency, prefix+"concurrency", defConcurrencyOption, descConcurrencyOption) 90 set.IntVar(&opt.Concurrency, prefix+"c", defConcurrencyOption, descConcurrencyOption) 91 set.BoolVar(&opt.ShowStepDefinitions, prefix+"definitions", defShowStepDefinitions, "Print all available step definitions.") 92 set.BoolVar(&opt.ShowStepDefinitions, prefix+"d", defShowStepDefinitions, "Print all available step definitions.") 93 set.BoolVar(&opt.StopOnFailure, prefix+"stop-on-failure", defStopOnFailure, "Stop processing on first failed scenario.") 94 set.BoolVar(&opt.Strict, prefix+"strict", defStrict, "Fail suite when there are pending or undefined steps.") 95 set.BoolVar(&opt.NoColors, prefix+"no-colors", defNoColors, "Disable ansi colors.") 96 set.Var(&randomSeed{&opt.Randomize}, prefix+"random", descRandomOption) 97 } 98 99 type flagged struct { 100 short, long, descr, dflt string 101 } 102 103 func (f *flagged) name() string { 104 var name string 105 switch { 106 case len(f.short) > 0 && len(f.long) > 0: 107 name = fmt.Sprintf("-%s, --%s", f.short, f.long) 108 case len(f.long) > 0: 109 name = fmt.Sprintf("--%s", f.long) 110 case len(f.short) > 0: 111 name = fmt.Sprintf("-%s", f.short) 112 } 113 114 if f.long == "random" { 115 // `random` is special in that we will later assign it randomly 116 // if the user specifies `--random` without specifying one, 117 // so mask the "default" value here to avoid UI confusion about 118 // what the value will end up being. 119 name += "[=SEED]" 120 } else if f.dflt != "true" && f.dflt != "false" { 121 name += "=" + f.dflt 122 } 123 return name 124 } 125 126 func usage(set *flag.FlagSet, w io.Writer) func() { 127 return func() { 128 var list []*flagged 129 var longest int 130 set.VisitAll(func(f *flag.Flag) { 131 var fl *flagged 132 for _, flg := range list { 133 if flg.descr == f.Usage { 134 fl = flg 135 break 136 } 137 } 138 if nil == fl { 139 fl = &flagged{ 140 dflt: f.DefValue, 141 descr: f.Usage, 142 } 143 list = append(list, fl) 144 } 145 if len(f.Name) > 2 { 146 fl.long = f.Name 147 } else { 148 fl.short = f.Name 149 } 150 }) 151 152 for _, f := range list { 153 if len(f.name()) > longest { 154 longest = len(f.name()) 155 } 156 } 157 158 // prints an option or argument with a description, or only description 159 opt := func(name, desc string) string { 160 var ret []string 161 lines := strings.Split(desc, "\n") 162 ret = append(ret, s(2)+colors.Green(name)+s(longest+2-len(name))+lines[0]) 163 if len(lines) > 1 { 164 for _, ln := range lines[1:] { 165 ret = append(ret, s(2)+s(longest+2)+ln) 166 } 167 } 168 return strings.Join(ret, "\n") 169 } 170 171 // --- GENERAL --- 172 fmt.Fprintln(w, colors.Yellow("Usage:")) 173 fmt.Fprintf(w, s(2)+"godog [options] [<features>]\n\n") 174 // description 175 fmt.Fprintln(w, "Builds a test package and runs given feature files.") 176 fmt.Fprintf(w, "Command should be run from the directory of tested package and contain buildable go source.\n\n") 177 178 // --- ARGUMENTS --- 179 fmt.Fprintln(w, colors.Yellow("Arguments:")) 180 // --> features 181 fmt.Fprintln(w, opt("features", descFeaturesArgument)) 182 183 // --- OPTIONS --- 184 fmt.Fprintln(w, colors.Yellow("Options:")) 185 for _, f := range list { 186 fmt.Fprintln(w, opt(f.name(), f.descr)) 187 } 188 fmt.Fprintln(w, "") 189 } 190 } 191 192 // randomSeed implements `flag.Value`, see https://golang.org/pkg/flag/#Value 193 type randomSeed struct { 194 ref *int64 195 } 196 197 // Choose randomly assigns a convenient pseudo-random seed value. 198 // The resulting seed will be between `1-99999` for later ease of specification. 199 func makeRandomSeed() int64 { 200 return rand.New(rand.NewSource(time.Now().UTC().UnixNano())).Int63n(99998) + 1 201 } 202 203 func (rs *randomSeed) Set(s string) error { 204 if s == "true" { 205 *rs.ref = makeRandomSeed() 206 return nil 207 } 208 209 if s == "false" { 210 *rs.ref = 0 211 return nil 212 } 213 214 i, err := strconv.ParseInt(s, 10, 64) 215 *rs.ref = i 216 return err 217 } 218 219 func (rs *randomSeed) String() string { 220 if rs.ref == nil { 221 return "0" 222 } 223 return strconv.FormatInt(*rs.ref, 10) 224 } 225 226 // If a Value has an IsBoolFlag() bool method returning true, the command-line 227 // parser makes -name equivalent to -name=true rather than using the next 228 // command-line argument. 229 func (rs *randomSeed) IsBoolFlag() bool { 230 return *rs.ref == 0 231 }