Source file src/cmd/go/counters_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main_test
     6  
     7  import (
     8  	"cmd/go/internal/base"
     9  	"cmd/go/internal/cfg"
    10  	"flag"
    11  	"go/build"
    12  	"internal/diff"
    13  	"os"
    14  	"slices"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  var update = flag.Bool("update", false, "if true update testdata/counternames.txt")
    20  
    21  func TestCounterNamesUpToDate(t *testing.T) {
    22  	if !*update {
    23  		t.Parallel()
    24  	}
    25  
    26  	var counters []string
    27  	// -C is a special case because it's handled by handleChdirFlag rather than
    28  	// standard flag processing with FlagSets.
    29  	// go/subcommand:unknown is also a special case: it's used when the subcommand
    30  	// doesn't match any of the known commands.
    31  	counters = append(counters, "go/flag:C", "go/subcommand:unknown")
    32  	counters = append(counters, flagscounters("go/flag:", *flag.CommandLine)...)
    33  
    34  	// Add help (without any arguments) as a special case. cmdcounters adds go help <cmd>
    35  	// for all subcommands, but it's also valid to invoke go help without any arguments.
    36  	counters = append(counters, "go/subcommand:help")
    37  	for _, cmd := range base.Go.Commands {
    38  		cmdcounters, err := cmdcounters(nil, cmd)
    39  		if err != nil {
    40  			t.Fatal(err)
    41  		}
    42  		counters = append(counters, cmdcounters...)
    43  	}
    44  
    45  	counters = append(counters, base.RegisteredCounterNames()...)
    46  	for _, c := range counters {
    47  		const counterPrefix = "go/"
    48  		if !strings.HasPrefix(c, counterPrefix) {
    49  			t.Fatalf("registered counter %q does not start with %q", c, counterPrefix)
    50  		}
    51  	}
    52  
    53  	cstr := []byte(strings.Join(counters, "\n") + "\n")
    54  	const counterNamesFile = "testdata/counters.txt"
    55  	old, err := os.ReadFile(counterNamesFile)
    56  	if err != nil {
    57  		t.Fatalf("error reading %s: %v", counterNamesFile, err)
    58  	}
    59  	diff := diff.Diff(counterNamesFile, old, "generated counter names", cstr)
    60  	if diff == nil {
    61  		t.Logf("%s is up to date.", counterNamesFile)
    62  		return
    63  	}
    64  
    65  	if *update {
    66  		if err := os.WriteFile(counterNamesFile, cstr, 0666); err != nil {
    67  			t.Fatal(err)
    68  		}
    69  		t.Logf("wrote %d bytes to %s", len(cstr), counterNamesFile)
    70  		t.Logf("don't forget to file a proposal to update the list of collected counters")
    71  	} else {
    72  		t.Logf("\n%s", diff)
    73  		t.Errorf("%s is stale. To update, run 'go generate cmd/go'.", counterNamesFile)
    74  	}
    75  }
    76  
    77  func flagscounters(prefix string, flagSet flag.FlagSet) []string {
    78  	var counters []string
    79  	flagSet.VisitAll(func(f *flag.Flag) {
    80  		counters = append(counters, prefix+f.Name)
    81  	})
    82  	return counters
    83  }
    84  
    85  func cmdcounters(previous []string, cmd *base.Command) ([]string, error) {
    86  	const subcommandPrefix = "go/subcommand:"
    87  	const flagPrefix = "go/flag:"
    88  	var counters []string
    89  	previousComponent := strings.Join(previous, "-")
    90  	if len(previousComponent) > 0 {
    91  		previousComponent += "-"
    92  	}
    93  	if cmd.Runnable() {
    94  		if cmd.Name() == "tool" {
    95  			// TODO(matloob): Do we expect the same tools to be present on all
    96  			// platforms/configurations? Should we only run this on certain
    97  			// platforms?
    98  			tools, err := toolNames()
    99  			if err != nil {
   100  				return nil, err
   101  			}
   102  			for _, t := range tools {
   103  				counters = append(counters, subcommandPrefix+previousComponent+cmd.Name()+"-"+t)
   104  			}
   105  			counters = append(counters, subcommandPrefix+previousComponent+cmd.Name()+"-unknown")
   106  		}
   107  		counters = append(counters, subcommandPrefix+previousComponent+cmd.Name())
   108  	}
   109  	counters = append(counters, flagscounters(flagPrefix+previousComponent+cmd.Name()+"-", cmd.Flag)...)
   110  	if len(previous) != 0 {
   111  		counters = append(counters, subcommandPrefix+previousComponent+"help-"+cmd.Name())
   112  	}
   113  	counters = append(counters, subcommandPrefix+"help-"+previousComponent+cmd.Name())
   114  
   115  	for _, subcmd := range cmd.Commands {
   116  		subcmdcounters, err := cmdcounters(append(slices.Clone(previous), cmd.Name()), subcmd)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		counters = append(counters, subcmdcounters...)
   121  	}
   122  	return counters, nil
   123  }
   124  
   125  // toolNames returns the list of basenames of executables in the tool dir.
   126  func toolNames() ([]string, error) {
   127  	entries, err := os.ReadDir(build.ToolDir)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	var names []string
   132  	for _, e := range entries {
   133  		if e.IsDir() {
   134  			continue
   135  		}
   136  		name := strings.TrimSuffix(e.Name(), cfg.ToolExeSuffix())
   137  		names = append(names, name)
   138  	}
   139  	return names, nil
   140  }
   141  

View as plain text