Skip to content

Commit

Permalink
cmd/bisect: add -compile and -godebug shorthands
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: David Chase <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
gopls-CI: kokoro <[email protected]>
  • Loading branch information
rsc committed May 9, 2023
1 parent ddfa220 commit 4609d79
Showing 1 changed file with 90 additions and 10 deletions.
100 changes: 90 additions & 10 deletions cmd/bisect/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -29,45 +29,84 @@
// 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.
//
// -v
//
// 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
Expand All @@ -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 (
Expand Down Expand Up @@ -127,6 +168,25 @@ func main() {
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()
Expand All @@ -140,6 +200,26 @@ func main() {
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)
}
Expand Down

0 comments on commit 4609d79

Please sign in to comment.