github.com/MarianMacik/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  }