| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main_test |
| |
| import ( |
| "cmd/go/internal/base" |
| "cmd/go/internal/cfg" |
| "flag" |
| "go/build" |
| "internal/diff" |
| "os" |
| "slices" |
| "strings" |
| "testing" |
| ) |
| |
| var update = flag.Bool("update", false, "if true update testdata/counternames.txt") |
| |
| func TestCounterNamesUpToDate(t *testing.T) { |
| if !*update { |
| t.Parallel() |
| } |
| |
| var counters []string |
| // -C is a special case because it's handled by handleChdirFlag rather than |
| // standard flag processing with FlagSets. |
| // go/subcommand:unknown is also a special case: it's used when the subcommand |
| // doesn't match any of the known commands. |
| counters = append(counters, "go/flag:C", "go/subcommand:unknown") |
| counters = append(counters, flagscounters("go/flag:", *flag.CommandLine)...) |
| |
| // Add help (without any arguments) as a special case. cmdcounters adds go help <cmd> |
| // for all subcommands, but it's also valid to invoke go help without any arguments. |
| counters = append(counters, "go/subcommand:help") |
| for _, cmd := range base.Go.Commands { |
| cmdcounters, err := cmdcounters(nil, cmd) |
| if err != nil { |
| t.Fatal(err) |
| } |
| counters = append(counters, cmdcounters...) |
| } |
| |
| counters = append(counters, base.RegisteredCounterNames()...) |
| for _, c := range counters { |
| const counterPrefix = "go/" |
| if !strings.HasPrefix(c, counterPrefix) { |
| t.Fatalf("registered counter %q does not start with %q", c, counterPrefix) |
| } |
| } |
| |
| cstr := []byte(strings.Join(counters, "\n") + "\n") |
| const counterNamesFile = "testdata/counters.txt" |
| old, err := os.ReadFile(counterNamesFile) |
| if err != nil { |
| t.Fatalf("error reading %s: %v", counterNamesFile, err) |
| } |
| diff := diff.Diff(counterNamesFile, old, "generated counter names", cstr) |
| if diff == nil { |
| t.Logf("%s is up to date.", counterNamesFile) |
| return |
| } |
| |
| if *update { |
| if err := os.WriteFile(counterNamesFile, cstr, 0666); err != nil { |
| t.Fatal(err) |
| } |
| t.Logf("wrote %d bytes to %s", len(cstr), counterNamesFile) |
| t.Logf("don't forget to file a proposal to update the list of collected counters") |
| } else { |
| t.Logf("\n%s", diff) |
| t.Errorf("%s is stale. To update, run 'go generate cmd/go'.", counterNamesFile) |
| } |
| } |
| |
| func flagscounters(prefix string, flagSet flag.FlagSet) []string { |
| var counters []string |
| flagSet.VisitAll(func(f *flag.Flag) { |
| counters = append(counters, prefix+f.Name) |
| }) |
| return counters |
| } |
| |
| func cmdcounters(previous []string, cmd *base.Command) ([]string, error) { |
| const subcommandPrefix = "go/subcommand:" |
| const flagPrefix = "go/flag:" |
| var counters []string |
| previousComponent := strings.Join(previous, "-") |
| if len(previousComponent) > 0 { |
| previousComponent += "-" |
| } |
| if cmd.Runnable() { |
| if cmd.Name() == "tool" { |
| // TODO(matloob): Do we expect the same tools to be present on all |
| // platforms/configurations? Should we only run this on certain |
| // platforms? |
| tools, err := toolNames() |
| if err != nil { |
| return nil, err |
| } |
| for _, t := range tools { |
| counters = append(counters, subcommandPrefix+previousComponent+cmd.Name()+"-"+t) |
| } |
| counters = append(counters, subcommandPrefix+previousComponent+cmd.Name()+"-unknown") |
| } |
| counters = append(counters, subcommandPrefix+previousComponent+cmd.Name()) |
| } |
| counters = append(counters, flagscounters(flagPrefix+previousComponent+cmd.Name()+"-", cmd.Flag)...) |
| if len(previous) != 0 { |
| counters = append(counters, subcommandPrefix+previousComponent+"help-"+cmd.Name()) |
| } |
| counters = append(counters, subcommandPrefix+"help-"+previousComponent+cmd.Name()) |
| |
| for _, subcmd := range cmd.Commands { |
| subcmdcounters, err := cmdcounters(append(slices.Clone(previous), cmd.Name()), subcmd) |
| if err != nil { |
| return nil, err |
| } |
| counters = append(counters, subcmdcounters...) |
| } |
| return counters, nil |
| } |
| |
| // toolNames returns the list of basenames of executables in the tool dir. |
| func toolNames() ([]string, error) { |
| entries, err := os.ReadDir(build.ToolDir) |
| if err != nil { |
| return nil, err |
| } |
| var names []string |
| for _, e := range entries { |
| if e.IsDir() { |
| continue |
| } |
| name := strings.TrimSuffix(e.Name(), cfg.ToolExeSuffix()) |
| names = append(names, name) |
| } |
| return names, nil |
| } |