github.com/maps90/godog@v0.7.5-0.20170923143419-0093943021d4/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  func FlagSet(opt *Options) *flag.FlagSet {
    38  	descFormatOption := "How to format tests output. Built-in formats:\n"
    39  	// @TODO: sort by name
    40  	for name, desc := range AvailableFormatters() {
    41  		descFormatOption += s(4) + "- " + colors.Yellow(name) + ": " + desc + "\n"
    42  	}
    43  	descFormatOption = strings.TrimSpace(descFormatOption)
    44  
    45  	set := flag.NewFlagSet("godog", flag.ExitOnError)
    46  	set.StringVar(&opt.Format, "format", "pretty", descFormatOption)
    47  	set.StringVar(&opt.Format, "f", "pretty", descFormatOption)
    48  	set.StringVar(&opt.Tags, "tags", "", descTagsOption)
    49  	set.StringVar(&opt.Tags, "t", "", descTagsOption)
    50  	set.IntVar(&opt.Concurrency, "concurrency", 1, descConcurrencyOption)
    51  	set.IntVar(&opt.Concurrency, "c", 1, descConcurrencyOption)
    52  	set.BoolVar(&opt.ShowStepDefinitions, "definitions", false, "Print all available step definitions.")
    53  	set.BoolVar(&opt.ShowStepDefinitions, "d", false, "Print all available step definitions.")
    54  	set.BoolVar(&opt.StopOnFailure, "stop-on-failure", false, "Stop processing on first failed scenario.")
    55  	set.BoolVar(&opt.Strict, "strict", false, "Fail suite when there are pending or undefined steps.")
    56  	set.BoolVar(&opt.NoColors, "no-colors", false, "Disable ansi colors.")
    57  	set.Var(&randomSeed{&opt.Randomize}, "random", descRandomOption)
    58  	set.Usage = usage(set, opt.Output)
    59  	return set
    60  }
    61  
    62  type flagged struct {
    63  	short, long, descr, dflt string
    64  }
    65  
    66  func (f *flagged) name() string {
    67  	var name string
    68  	switch {
    69  	case len(f.short) > 0 && len(f.long) > 0:
    70  		name = fmt.Sprintf("-%s, --%s", f.short, f.long)
    71  	case len(f.long) > 0:
    72  		name = fmt.Sprintf("--%s", f.long)
    73  	case len(f.short) > 0:
    74  		name = fmt.Sprintf("-%s", f.short)
    75  	}
    76  
    77  	if f.long == "random" {
    78  		// `random` is special in that we will later assign it randomly
    79  		// if the user specifies `--random` without specifying one,
    80  		// so mask the "default" value here to avoid UI confusion about
    81  		// what the value will end up being.
    82  		name += "[=SEED]"
    83  	} else if f.dflt != "true" && f.dflt != "false" {
    84  		name += "=" + f.dflt
    85  	}
    86  	return name
    87  }
    88  
    89  func usage(set *flag.FlagSet, w io.Writer) func() {
    90  	return func() {
    91  		var list []*flagged
    92  		var longest int
    93  		set.VisitAll(func(f *flag.Flag) {
    94  			var fl *flagged
    95  			for _, flg := range list {
    96  				if flg.descr == f.Usage {
    97  					fl = flg
    98  					break
    99  				}
   100  			}
   101  			if nil == fl {
   102  				fl = &flagged{
   103  					dflt:  f.DefValue,
   104  					descr: f.Usage,
   105  				}
   106  				list = append(list, fl)
   107  			}
   108  			if len(f.Name) > 2 {
   109  				fl.long = f.Name
   110  			} else {
   111  				fl.short = f.Name
   112  			}
   113  		})
   114  
   115  		for _, f := range list {
   116  			if len(f.name()) > longest {
   117  				longest = len(f.name())
   118  			}
   119  		}
   120  
   121  		// prints an option or argument with a description, or only description
   122  		opt := func(name, desc string) string {
   123  			var ret []string
   124  			lines := strings.Split(desc, "\n")
   125  			ret = append(ret, s(2)+colors.Green(name)+s(longest+2-len(name))+lines[0])
   126  			if len(lines) > 1 {
   127  				for _, ln := range lines[1:] {
   128  					ret = append(ret, s(2)+s(longest+2)+ln)
   129  				}
   130  			}
   131  			return strings.Join(ret, "\n")
   132  		}
   133  
   134  		// --- GENERAL ---
   135  		fmt.Fprintln(w, colors.Yellow("Usage:"))
   136  		fmt.Fprintf(w, s(2)+"godog [options] [<features>]\n\n")
   137  		// description
   138  		fmt.Fprintln(w, "Builds a test package and runs given feature files.")
   139  		fmt.Fprintf(w, "Command should be run from the directory of tested package and contain buildable go source.\n\n")
   140  
   141  		// --- ARGUMENTS ---
   142  		fmt.Fprintln(w, colors.Yellow("Arguments:"))
   143  		// --> features
   144  		fmt.Fprintln(w, opt("features", descFeaturesArgument))
   145  
   146  		// --- OPTIONS ---
   147  		fmt.Fprintln(w, colors.Yellow("Options:"))
   148  		for _, f := range list {
   149  			fmt.Fprintln(w, opt(f.name(), f.descr))
   150  		}
   151  		fmt.Fprintln(w, "")
   152  	}
   153  }
   154  
   155  // randomSeed implements `flag.Value`, see https://golang.org/pkg/flag/#Value
   156  type randomSeed struct {
   157  	ref *int64
   158  }
   159  
   160  // Choose randomly assigns a convenient pseudo-random seed value.
   161  // The resulting seed will be between `1-99999` for later ease of specification.
   162  func (rs *randomSeed) choose() {
   163  	r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
   164  	*rs.ref = r.Int63n(99998) + 1
   165  }
   166  
   167  func (rs *randomSeed) Set(s string) error {
   168  	if s == "true" {
   169  		rs.choose()
   170  		return nil
   171  	}
   172  
   173  	if s == "false" {
   174  		*rs.ref = 0
   175  		return nil
   176  	}
   177  
   178  	i, err := strconv.ParseInt(s, 10, 64)
   179  	*rs.ref = i
   180  	return err
   181  }
   182  
   183  func (rs randomSeed) String() string {
   184  	return strconv.FormatInt(*rs.ref, 10)
   185  }
   186  
   187  // If a Value has an IsBoolFlag() bool method returning true, the command-line
   188  // parser makes -name equivalent to -name=true rather than using the next
   189  // command-line argument.
   190  func (rs *randomSeed) IsBoolFlag() bool {
   191  	return *rs.ref == 0
   192  }