cmd/bisect: add -compile and -godebug shorthands
This makes it easier for people to invoke bisect for its usual use cases:
bisect -compile=loopvar go test
Change-Id: I60ae74f145a2aef7b8d028accc89264e80019d1e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/493856
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/cmd/bisect/main.go b/cmd/bisect/main.go
index 1fc2b02..baae1fa 100644
--- a/cmd/bisect/main.go
+++ b/cmd/bisect/main.go
@@ -16,7 +16,7 @@
// code zero). With all the changes enabled, the target is known to fail
// (exit any other way). Bisect repeats the target with different sets of
// changes enabled, using binary search to find (non-overlapping) minimal
-// change sets that preserve the failure.
+// change sets that provoke the failure.
//
// The target must cooperate with bisect by accepting a change pattern
// and then enabling and reporting the changes that match that pattern.
@@ -29,25 +29,36 @@
// targets implement this protocol. We plan to publish that package
// in a non-internal location after finalizing its API.
//
+// Bisect starts by running the target with no changes enabled and then
+// with all changes enabled. It expects the former to succeed and the latter to fail,
+// and then it will search for the minimal set of changes that must be enabled
+// to provoke the failure. If the situation is reversed – the target fails with no
+// changes enabled and succeeds with all changes enabled – then bisect
+// automatically runs in reverse as well, searching for the minimal set of changes
+// that must be disabled to provoke the failure.
+//
+// Bisect prints tracing logs to standard error and the minimal change sets
+// to standard output.
+//
// # Command Line Flags
//
// Bisect supports the following command-line flags:
//
-// -max M
+// -max=M
//
// Stop after finding M minimal change sets. The default is no maximum, meaning to run until
// all changes that provoke a failure have been identified.
//
-// -maxset S
+// -maxset=S
//
// Disallow change sets larger than S elements. The default is no maximum.
//
-// -timeout D
+// -timeout=D
//
// If the target runs for longer than duration D, stop the target and interpret that as a failure.
// The default is no timeout.
//
-// -count N
+// -count=N
//
// Run each trial N times (default 2), checking for consistency.
//
@@ -55,19 +66,47 @@
//
// Print verbose output, showing each run and its match lines.
//
+// In addition to these general flags,
+// bisect supports a few “shortcut” flags that make it more convenient
+// to use with specific targets.
+//
+// -compile=<rewrite>
+//
+// This flag is equivalent to adding an environment variable
+// “GOCOMPILEDEBUG=<rewrite>hash=PATTERN”,
+// which, as discussed in more detail in the example below,
+// allows bisect to identify the specific source locations where the
+// compiler rewrite causes the target to fail.
+//
+// -godebug=<name>=<value>
+//
+// This flag is equivalent to adding an environment variable
+// “GODEBUG=<name>=<value>#PATTERN”,
+// which allows bisect to identify the specific call stacks where
+// the changed [GODEBUG setting] value causes the target to fail.
+//
// # Example
//
-// For example, the Go compiler can be used as a bisect target to
-// determine the source locations that cause a test failure when compiled with
-// a new optimization:
+// The Go compiler provides support for enabling or disabling certain rewrites
+// and optimizations to allow bisect to identify specific source locations where
+// the rewrite causes the program to fail. For example, to bisect a failure caused
+// by the new loop variable semantics:
//
// bisect go test -gcflags=all=-d=loopvarhash=PATTERN
//
// The -gcflags=all= instructs the go command to pass the -d=... to the Go compiler
-// when compiling all packages. Bisect replaces the literal text “PATTERN” with a specific pattern
-// on each invocation, varying the patterns to determine the minimal set of changes
+// when compiling all packages. Bisect varies PATTERN to determine the minimal set of changes
// needed to reproduce the failure.
//
+// The go command also checks the GOCOMPILEDEBUG environment variable for flags
+// to pass to the compiler, so the above command is equivalent to:
+//
+// bisect GOCOMPILEDEBUG=loopvarhash=PATTERN go test
+//
+// Finally, as mentioned earlier, the -compile flag allows shortening this command further:
+//
+// bisect -compile=loopvar go test
+//
// # Defeating Build Caches
//
// Build systems cache build results, to avoid repeating the same compilations
@@ -87,6 +126,8 @@
// previous example using Bazel, the invocation is:
//
// bazel test --define=gc_goopts=-d=loopvarhash=PATTERN,unused=RANDOM //path/to:test
+//
+// [GODEBUG setting]: https://tip.golang.org/doc/godebug
package main
import (
@@ -127,6 +168,25 @@
flag.IntVar(&b.Count, "count", 2, "run target `n` times for each trial")
flag.BoolVar(&b.Verbose, "v", false, "enable verbose output")
+ env := ""
+ envFlag := ""
+ flag.Func("compile", "bisect source locations affected by Go compiler `rewrite` (fma, loopvar, ...)", func(value string) error {
+ if envFlag != "" {
+ return fmt.Errorf("cannot use -%s and -compile", envFlag)
+ }
+ envFlag = "compile"
+ env = "GOCOMPILEDEBUG=" + value + "hash=PATTERN"
+ return nil
+ })
+ flag.Func("godebug", "bisect call stacks affected by GODEBUG setting `name=value`", func(value string) error {
+ if envFlag != "" {
+ return fmt.Errorf("cannot use -%s and -godebug", envFlag)
+ }
+ envFlag = "godebug"
+ env = "GODEBUG=" + value + "#PATTERN"
+ return nil
+ })
+
flag.Usage = usage
flag.Parse()
args := flag.Args()
@@ -140,6 +200,26 @@
usage()
}
b.Env, b.Cmd, b.Args = args[:i], args[i], args[i+1:]
+ if env != "" {
+ b.Env = append([]string{env}, b.Env...)
+ }
+
+ // Check that PATTERN is available for us to vary.
+ found := false
+ for _, e := range b.Env {
+ if _, v, _ := strings.Cut(e, "="); strings.Contains(v, "PATTERN") {
+ found = true
+ }
+ }
+ for _, a := range b.Args {
+ if strings.Contains(a, "PATTERN") {
+ found = true
+ }
+ }
+ if !found {
+ log.Fatalf("no PATTERN in target environment or args")
+ }
+
if !b.Search() {
os.Exit(1)
}