package f2 import ( "flag" "fmt" "os" "path/filepath" "strings" "github.com/bazelbuild/rules_go/go/runfiles" "github.com/peterbourgon/ff/v3" "edge-infra.dev/pkg/lib/build/bazel" ) // Flags is the flag set parsed and used by the [Framework]. Test authors should // bind custom flags to this instead of directly adding to the global command // line. var Flags = flag.NewFlagSet("", flag.ContinueOnError) var ( CfgFlagName = "test-config" CfgPath = "test/config.json" cfgFlag string Labels map[string]string SkipLabels map[string]string ) // handleFlags copies all flags registered with [Flags] to the command line, // and then parses them with ff for environment variable and config file // support func handleFlags() error { // register flag for config file path, so ff can find it Flags.StringVar(&cfgFlag, CfgFlagName, resolveCfgPath(CfgPath), "Path to test configuration file") // copy all the flags from the global flagset each test suite registers // config options with to the command line for parsing CopyFlags(Flags, flag.CommandLine) RegisterCommonFlags(flag.CommandLine) // parse test configuration err := ff.Parse(flag.CommandLine, os.Args[1:], ff.WithConfigFileFlag(CfgFlagName), ff.WithConfigFileParser(ff.JSONParser), ff.WithAllowMissingConfigFile(true), ff.WithIgnoreUndefined(true), ) if err != nil { return err } return Validate() } // resolveCfgPath resolves the default config path value. if the test is being // executed by Bazel, it attempts to resolve the path for the test config.json // provided via runfiles. otherwise, it returns the default provided and we // assume that test/config.json will be provided explicitly. func resolveCfgPath(d string) string { if ws := os.Getenv(bazel.TestWorkspace); ws != "" { path, err := runfiles.Rlocation(filepath.Join(ws, d)) if err == nil { return path } } return d } // CopyFlags ensures that all flags that are defined in the source flag // set appear in the target flag set as if they had been defined there // directly. From the flag package it inherits the behavior that there // is a panic if the target already contains a flag from the source. func CopyFlags(source *flag.FlagSet, target *flag.FlagSet) { source.VisitAll(func(flag *flag.Flag) { // We don't need to copy flag.DefValue. The original // default (from, say, flag.String) was stored in // the value and gets extracted by Var for the help // message. target.Var(flag.Value, flag.Name, flag.Usage) }) } func RegisterCommonFlags(flags *flag.FlagSet) { flags.Func("labels", "Only run tests with the provided comma separated labels (eg: foo=bar,bar=baz,bar=boo)", func(s string) error { Labels = commaSepValues(s) return nil }) flags.Func("skip-labels", "Run all tests except those with the provided comma separated labels (eg: foo=bar,bar=baz,bar=boo)", func(s string) error { SkipLabels = commaSepValues(s) return nil }) } // parses comma separated map values func commaSepValues(s string) map[string]string { a := strings.Split(s, ",") r := map[string]string{} for _, v := range a { if v != "" { kv := strings.Split(v, "=") // drop malformed input if len(kv) > 1 { // if theres already an entry append if len(r[kv[0]]) != 0 { r[kv[0]] = fmt.Sprintf("%s,%s", r[kv[0]], kv[1]) } else { r[kv[0]] = kv[1] } } } } return r } // Validate checks that the base test context was provided valid values via config func Validate() error { // check if theres any matching labels and error if so for key := range Labels { if _, ok := SkipLabels[key]; ok { return fmt.Errorf("-labels and -skip-labels cannot contain the same label %s", key) } } return nil }