| // Copyright 2011 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 test |
| |
| import ( |
| "bytes" |
| "context" |
| "crypto/sha256" |
| "errors" |
| "fmt" |
| "go/build" |
| exec "internal/execabs" |
| "io" |
| "io/fs" |
| "os" |
| "path" |
| "path/filepath" |
| "regexp" |
| "sort" |
| "strconv" |
| "strings" |
| "sync" |
| "time" |
| |
| "cmd/go/internal/base" |
| "cmd/go/internal/cache" |
| "cmd/go/internal/cfg" |
| "cmd/go/internal/load" |
| "cmd/go/internal/lockedfile" |
| "cmd/go/internal/str" |
| "cmd/go/internal/trace" |
| "cmd/go/internal/work" |
| "cmd/internal/test2json" |
| ) |
| |
| // Break init loop. |
| func init() { |
| CmdTest.Run = runTest |
| } |
| |
| const testUsage = "go test [build/test flags] [packages] [build/test flags & test binary flags]" |
| |
| var CmdTest = &base.Command{ |
| CustomFlags: true, |
| UsageLine: testUsage, |
| Short: "test packages", |
| Long: ` |
| 'Go test' automates testing the packages named by the import paths. |
| It prints a summary of the test results in the format: |
| |
| ok archive/tar 0.011s |
| FAIL archive/zip 0.022s |
| ok compress/gzip 0.033s |
| ... |
| |
| followed by detailed output for each failed package. |
| |
| 'Go test' recompiles each package along with any files with names matching |
| the file pattern "*_test.go". |
| These additional files can contain test functions, benchmark functions, and |
| example functions. See 'go help testfunc' for more. |
| Each listed package causes the execution of a separate test binary. |
| Files whose names begin with "_" (including "_test.go") or "." are ignored. |
| |
| Test files that declare a package with the suffix "_test" will be compiled as a |
| separate package, and then linked and run with the main test binary. |
| |
| The go tool will ignore a directory named "testdata", making it available |
| to hold ancillary data needed by the tests. |
| |
| As part of building a test binary, go test runs go vet on the package |
| and its test source files to identify significant problems. If go vet |
| finds any problems, go test reports those and does not run the test |
| binary. Only a high-confidence subset of the default go vet checks are |
| used. That subset is: 'atomic', 'bool', 'buildtags', 'errorsas', |
| 'ifaceassert', 'nilfunc', 'printf', and 'stringintconv'. You can see |
| the documentation for these and other vet tests via "go doc cmd/vet". |
| To disable the running of go vet, use the -vet=off flag. |
| |
| All test output and summary lines are printed to the go command's |
| standard output, even if the test printed them to its own standard |
| error. (The go command's standard error is reserved for printing |
| errors building the tests.) |
| |
| Go test runs in two different modes: |
| |
| The first, called local directory mode, occurs when go test is |
| invoked with no package arguments (for example, 'go test' or 'go |
| test -v'). In this mode, go test compiles the package sources and |
| tests found in the current directory and then runs the resulting |
| test binary. In this mode, caching (discussed below) is disabled. |
| After the package test finishes, go test prints a summary line |
| showing the test status ('ok' or 'FAIL'), package name, and elapsed |
| time. |
| |
| The second, called package list mode, occurs when go test is invoked |
| with explicit package arguments (for example 'go test math', 'go |
| test ./...', and even 'go test .'). In this mode, go test compiles |
| and tests each of the packages listed on the command line. If a |
| package test passes, go test prints only the final 'ok' summary |
| line. If a package test fails, go test prints the full test output. |
| If invoked with the -bench or -v flag, go test prints the full |
| output even for passing package tests, in order to display the |
| requested benchmark results or verbose logging. After the package |
| tests for all of the listed packages finish, and their output is |
| printed, go test prints a final 'FAIL' status if any package test |
| has failed. |
| |
| In package list mode only, go test caches successful package test |
| results to avoid unnecessary repeated running of tests. When the |
| result of a test can be recovered from the cache, go test will |
| redisplay the previous output instead of running the test binary |
| again. When this happens, go test prints '(cached)' in place of the |
| elapsed time in the summary line. |
| |
| The rule for a match in the cache is that the run involves the same |
| test binary and the flags on the command line come entirely from a |
| restricted set of 'cacheable' test flags, defined as -cpu, -list, |
| -parallel, -run, -short, and -v. If a run of go test has any test |
| or non-test flags outside this set, the result is not cached. To |
| disable test caching, use any test flag or argument other than the |
| cacheable flags. The idiomatic way to disable test caching explicitly |
| is to use -count=1. Tests that open files within the package's source |
| root (usually $GOPATH) or that consult environment variables only |
| match future runs in which the files and environment variables are unchanged. |
| A cached test result is treated as executing in no time at all, |
| so a successful package test result will be cached and reused |
| regardless of -timeout setting. |
| |
| In addition to the build flags, the flags handled by 'go test' itself are: |
| |
| -args |
| Pass the remainder of the command line (everything after -args) |
| to the test binary, uninterpreted and unchanged. |
| Because this flag consumes the remainder of the command line, |
| the package list (if present) must appear before this flag. |
| |
| -c |
| Compile the test binary to pkg.test but do not run it |
| (where pkg is the last element of the package's import path). |
| The file name can be changed with the -o flag. |
| |
| -exec xprog |
| Run the test binary using xprog. The behavior is the same as |
| in 'go run'. See 'go help run' for details. |
| |
| -i |
| Install packages that are dependencies of the test. |
| Do not run the test. |
| The -i flag is deprecated. Compiled packages are cached automatically. |
| |
| -json |
| Convert test output to JSON suitable for automated processing. |
| See 'go doc test2json' for the encoding details. |
| |
| -o file |
| Compile the test binary to the named file. |
| The test still runs (unless -c or -i is specified). |
| |
| The test binary also accepts flags that control execution of the test; these |
| flags are also accessible by 'go test'. See 'go help testflag' for details. |
| |
| For more about build flags, see 'go help build'. |
| For more about specifying packages, see 'go help packages'. |
| |
| See also: go build, go vet. |
| `, |
| } |
| |
| var HelpTestflag = &base.Command{ |
| UsageLine: "testflag", |
| Short: "testing flags", |
| Long: ` |
| The 'go test' command takes both flags that apply to 'go test' itself |
| and flags that apply to the resulting test binary. |
| |
| Several of the flags control profiling and write an execution profile |
| suitable for "go tool pprof"; run "go tool pprof -h" for more |
| information. The --alloc_space, --alloc_objects, and --show_bytes |
| options of pprof control how the information is presented. |
| |
| The following flags are recognized by the 'go test' command and |
| control the execution of any test: |
| |
| -bench regexp |
| Run only those benchmarks matching a regular expression. |
| By default, no benchmarks are run. |
| To run all benchmarks, use '-bench .' or '-bench=.'. |
| The regular expression is split by unbracketed slash (/) |
| characters into a sequence of regular expressions, and each |
| part of a benchmark's identifier must match the corresponding |
| element in the sequence, if any. Possible parents of matches |
| are run with b.N=1 to identify sub-benchmarks. For example, |
| given -bench=X/Y, top-level benchmarks matching X are run |
| with b.N=1 to find any sub-benchmarks matching Y, which are |
| then run in full. |
| |
| -benchtime t |
| Run enough iterations of each benchmark to take t, specified |
| as a time.Duration (for example, -benchtime 1h30s). |
| The default is 1 second (1s). |
| The special syntax Nx means to run the benchmark N times |
| (for example, -benchtime 100x). |
| |
| -count n |
| Run each test and benchmark n times (default 1). |
| If -cpu is set, run n times for each GOMAXPROCS value. |
| Examples are always run once. |
| |
| -cover |
| Enable coverage analysis. |
| Note that because coverage works by annotating the source |
| code before compilation, compilation and test failures with |
| coverage enabled may report line numbers that don't correspond |
| to the original sources. |
| |
| -covermode set,count,atomic |
| Set the mode for coverage analysis for the package[s] |
| being tested. The default is "set" unless -race is enabled, |
| in which case it is "atomic". |
| The values: |
| set: bool: does this statement run? |
| count: int: how many times does this statement run? |
| atomic: int: count, but correct in multithreaded tests; |
| significantly more expensive. |
| Sets -cover. |
| |
| -coverpkg pattern1,pattern2,pattern3 |
| Apply coverage analysis in each test to packages matching the patterns. |
| The default is for each test to analyze only the package being tested. |
| See 'go help packages' for a description of package patterns. |
| Sets -cover. |
| |
| -cpu 1,2,4 |
| Specify a list of GOMAXPROCS values for which the tests or |
| benchmarks should be executed. The default is the current value |
| of GOMAXPROCS. |
| |
| -failfast |
| Do not start new tests after the first test failure. |
| |
| -list regexp |
| List tests, benchmarks, or examples matching the regular expression. |
| No tests, benchmarks or examples will be run. This will only |
| list top-level tests. No subtest or subbenchmarks will be shown. |
| |
| -parallel n |
| Allow parallel execution of test functions that call t.Parallel. |
| The value of this flag is the maximum number of tests to run |
| simultaneously; by default, it is set to the value of GOMAXPROCS. |
| Note that -parallel only applies within a single test binary. |
| The 'go test' command may run tests for different packages |
| in parallel as well, according to the setting of the -p flag |
| (see 'go help build'). |
| |
| -run regexp |
| Run only those tests and examples matching the regular expression. |
| For tests, the regular expression is split by unbracketed slash (/) |
| characters into a sequence of regular expressions, and each part |
| of a test's identifier must match the corresponding element in |
| the sequence, if any. Note that possible parents of matches are |
| run too, so that -run=X/Y matches and runs and reports the result |
| of all tests matching X, even those without sub-tests matching Y, |
| because it must run them to look for those sub-tests. |
| |
| -short |
| Tell long-running tests to shorten their run time. |
| It is off by default but set during all.bash so that installing |
| the Go tree can run a sanity check but not spend time running |
| exhaustive tests. |
| |
| -timeout d |
| If a test binary runs longer than duration d, panic. |
| If d is 0, the timeout is disabled. |
| The default is 10 minutes (10m). |
| |
| -v |
| Verbose output: log all tests as they are run. Also print all |
| text from Log and Logf calls even if the test succeeds. |
| |
| -vet list |
| Configure the invocation of "go vet" during "go test" |
| to use the comma-separated list of vet checks. |
| If list is empty, "go test" runs "go vet" with a curated list of |
| checks believed to be always worth addressing. |
| If list is "off", "go test" does not run "go vet" at all. |
| |
| The following flags are also recognized by 'go test' and can be used to |
| profile the tests during execution: |
| |
| -benchmem |
| Print memory allocation statistics for benchmarks. |
| |
| -blockprofile block.out |
| Write a goroutine blocking profile to the specified file |
| when all tests are complete. |
| Writes test binary as -c would. |
| |
| -blockprofilerate n |
| Control the detail provided in goroutine blocking profiles by |
| calling runtime.SetBlockProfileRate with n. |
| See 'go doc runtime.SetBlockProfileRate'. |
| The profiler aims to sample, on average, one blocking event every |
| n nanoseconds the program spends blocked. By default, |
| if -test.blockprofile is set without this flag, all blocking events |
| are recorded, equivalent to -test.blockprofilerate=1. |
| |
| -coverprofile cover.out |
| Write a coverage profile to the file after all tests have passed. |
| Sets -cover. |
| |
| -cpuprofile cpu.out |
| Write a CPU profile to the specified file before exiting. |
| Writes test binary as -c would. |
| |
| -memprofile mem.out |
| Write an allocation profile to the file after all tests have passed. |
| Writes test binary as -c would. |
| |
| -memprofilerate n |
| Enable more precise (and expensive) memory allocation profiles by |
| setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'. |
| To profile all memory allocations, use -test.memprofilerate=1. |
| |
| -mutexprofile mutex.out |
| Write a mutex contention profile to the specified file |
| when all tests are complete. |
| Writes test binary as -c would. |
| |
| -mutexprofilefraction n |
| Sample 1 in n stack traces of goroutines holding a |
| contended mutex. |
| |
| -outputdir directory |
| Place output files from profiling in the specified directory, |
| by default the directory in which "go test" is running. |
| |
| -trace trace.out |
| Write an execution trace to the specified file before exiting. |
| |
| Each of these flags is also recognized with an optional 'test.' prefix, |
| as in -test.v. When invoking the generated test binary (the result of |
| 'go test -c') directly, however, the prefix is mandatory. |
| |
| The 'go test' command rewrites or removes recognized flags, |
| as appropriate, both before and after the optional package list, |
| before invoking the test binary. |
| |
| For instance, the command |
| |
| go test -v -myflag testdata -cpuprofile=prof.out -x |
| |
| will compile the test binary and then run it as |
| |
| pkg.test -test.v -myflag testdata -test.cpuprofile=prof.out |
| |
| (The -x flag is removed because it applies only to the go command's |
| execution, not to the test itself.) |
| |
| The test flags that generate profiles (other than for coverage) also |
| leave the test binary in pkg.test for use when analyzing the profiles. |
| |
| When 'go test' runs a test binary, it does so from within the |
| corresponding package's source code directory. Depending on the test, |
| it may be necessary to do the same when invoking a generated test |
| binary directly. |
| |
| The command-line package list, if present, must appear before any |
| flag not known to the go test command. Continuing the example above, |
| the package list would have to appear before -myflag, but could appear |
| on either side of -v. |
| |
| When 'go test' runs in package list mode, 'go test' caches successful |
| package test results to avoid unnecessary repeated running of tests. To |
| disable test caching, use any test flag or argument other than the |
| cacheable flags. The idiomatic way to disable test caching explicitly |
| is to use -count=1. |
| |
| To keep an argument for a test binary from being interpreted as a |
| known flag or a package name, use -args (see 'go help test') which |
| passes the remainder of the command line through to the test binary |
| uninterpreted and unaltered. |
| |
| For instance, the command |
| |
| go test -v -args -x -v |
| |
| will compile the test binary and then run it as |
| |
| pkg.test -test.v -x -v |
| |
| Similarly, |
| |
| go test -args math |
| |
| will compile the test binary and then run it as |
| |
| pkg.test math |
| |
| In the first example, the -x and the second -v are passed through to the |
| test binary unchanged and with no effect on the go command itself. |
| In the second example, the argument math is passed through to the test |
| binary, instead of being interpreted as the package list. |
| `, |
| } |
| |
| var HelpTestfunc = &base.Command{ |
| UsageLine: "testfunc", |
| Short: "testing functions", |
| Long: ` |
| The 'go test' command expects to find test, benchmark, and example functions |
| in the "*_test.go" files corresponding to the package under test. |
| |
| A test function is one named TestXxx (where Xxx does not start with a |
| lower case letter) and should have the signature, |
| |
| func TestXxx(t *testing.T) { ... } |
| |
| A benchmark function is one named BenchmarkXxx and should have the signature, |
| |
| func BenchmarkXxx(b *testing.B) { ... } |
| |
| An example function is similar to a test function but, instead of using |
| *testing.T to report success or failure, prints output to os.Stdout. |
| If the last comment in the function starts with "Output:" then the output |
| is compared exactly against the comment (see examples below). If the last |
| comment begins with "Unordered output:" then the output is compared to the |
| comment, however the order of the lines is ignored. An example with no such |
| comment is compiled but not executed. An example with no text after |
| "Output:" is compiled, executed, and expected to produce no output. |
| |
| Godoc displays the body of ExampleXxx to demonstrate the use |
| of the function, constant, or variable Xxx. An example of a method M with |
| receiver type T or *T is named ExampleT_M. There may be multiple examples |
| for a given function, constant, or variable, distinguished by a trailing _xxx, |
| where xxx is a suffix not beginning with an upper case letter. |
| |
| Here is an example of an example: |
| |
| func ExamplePrintln() { |
| Println("The output of\nthis example.") |
| // Output: The output of |
| // this example. |
| } |
| |
| Here is another example where the ordering of the output is ignored: |
| |
| func ExamplePerm() { |
| for _, value := range Perm(4) { |
| fmt.Println(value) |
| } |
| |
| // Unordered output: 4 |
| // 2 |
| // 1 |
| // 3 |
| // 0 |
| } |
| |
| The entire test file is presented as the example when it contains a single |
| example function, at least one other function, type, variable, or constant |
| declaration, and no test or benchmark functions. |
| |
| See the documentation of the testing package for more information. |
| `, |
| } |
| |
| var ( |
| testBench string // -bench flag |
| testC bool // -c flag |
| testCover bool // -cover flag |
| testCoverMode string // -covermode flag |
| testCoverPaths []string // -coverpkg flag |
| testCoverPkgs []*load.Package // -coverpkg flag |
| testCoverProfile string // -coverprofile flag |
| testJSON bool // -json flag |
| testList string // -list flag |
| testO string // -o flag |
| testOutputDir = base.Cwd // -outputdir flag |
| testTimeout time.Duration // -timeout flag |
| testV bool // -v flag |
| testVet = vetFlag{flags: defaultVetFlags} // -vet flag |
| ) |
| |
| var ( |
| testArgs []string |
| pkgArgs []string |
| pkgs []*load.Package |
| |
| testHelp bool // -help option passed to test via -args |
| |
| testKillTimeout = 100 * 365 * 24 * time.Hour // backup alarm; defaults to about a century if no timeout is set |
| testCacheExpire time.Time // ignore cached test results before this time |
| |
| testBlockProfile, testCPUProfile, testMemProfile, testMutexProfile, testTrace string // profiling flag that limits test to one package |
| ) |
| |
| // testProfile returns the name of an arbitrary single-package profiling flag |
| // that is set, if any. |
| func testProfile() string { |
| switch { |
| case testBlockProfile != "": |
| return "-blockprofile" |
| case testCPUProfile != "": |
| return "-cpuprofile" |
| case testMemProfile != "": |
| return "-memprofile" |
| case testMutexProfile != "": |
| return "-mutexprofile" |
| case testTrace != "": |
| return "-trace" |
| default: |
| return "" |
| } |
| } |
| |
| // testNeedBinary reports whether the test needs to keep the binary around. |
| func testNeedBinary() bool { |
| switch { |
| case testBlockProfile != "": |
| return true |
| case testCPUProfile != "": |
| return true |
| case testMemProfile != "": |
| return true |
| case testMutexProfile != "": |
| return true |
| case testO != "": |
| return true |
| default: |
| return false |
| } |
| } |
| |
| // testShowPass reports whether the output for a passing test should be shown. |
| func testShowPass() bool { |
| return testV || (testList != "") || testHelp |
| } |
| |
| var defaultVetFlags = []string{ |
| // TODO(rsc): Decide which tests are enabled by default. |
| // See golang.org/issue/18085. |
| // "-asmdecl", |
| // "-assign", |
| "-atomic", |
| "-bool", |
| "-buildtags", |
| // "-cgocall", |
| // "-composites", |
| // "-copylocks", |
| "-errorsas", |
| // "-httpresponse", |
| "-ifaceassert", |
| // "-lostcancel", |
| // "-methods", |
| "-nilfunc", |
| "-printf", |
| // "-rangeloops", |
| // "-shift", |
| "-stringintconv", |
| // "-structtags", |
| // "-tests", |
| // "-unreachable", |
| // "-unsafeptr", |
| // "-unusedresult", |
| } |
| |
| func runTest(ctx context.Context, cmd *base.Command, args []string) { |
| load.ModResolveTests = true |
| |
| pkgArgs, testArgs = testFlags(args) |
| |
| if cfg.DebugTrace != "" { |
| var close func() error |
| var err error |
| ctx, close, err = trace.Start(ctx, cfg.DebugTrace) |
| if err != nil { |
| base.Fatalf("failed to start trace: %v", err) |
| } |
| defer func() { |
| if err := close(); err != nil { |
| base.Fatalf("failed to stop trace: %v", err) |
| } |
| }() |
| } |
| |
| ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command")) |
| defer span.Done() |
| |
| work.FindExecCmd() // initialize cached result |
| |
| work.BuildInit() |
| work.VetFlags = testVet.flags |
| work.VetExplicit = testVet.explicit |
| |
| pkgs = load.PackagesAndErrors(ctx, pkgArgs) |
| load.CheckPackageErrors(pkgs) |
| if len(pkgs) == 0 { |
| base.Fatalf("no packages to test") |
| } |
| |
| if testC && len(pkgs) != 1 { |
| base.Fatalf("cannot use -c flag with multiple packages") |
| } |
| if testO != "" && len(pkgs) != 1 { |
| base.Fatalf("cannot use -o flag with multiple packages") |
| } |
| if testProfile() != "" && len(pkgs) != 1 { |
| base.Fatalf("cannot use %s flag with multiple packages", testProfile()) |
| } |
| initCoverProfile() |
| defer closeCoverProfile() |
| |
| // If a test timeout is finite, set our kill timeout |
| // to that timeout plus one minute. This is a backup alarm in case |
| // the test wedges with a goroutine spinning and its background |
| // timer does not get a chance to fire. |
| if testTimeout > 0 { |
| testKillTimeout = testTimeout + 1*time.Minute |
| } |
| |
| // For 'go test -i -o x.test', we want to build x.test. Imply -c to make the logic easier. |
| if cfg.BuildI && testO != "" { |
| testC = true |
| } |
| |
| // Read testcache expiration time, if present. |
| // (We implement go clean -testcache by writing an expiration date |
| // instead of searching out and deleting test result cache entries.) |
| if dir := cache.DefaultDir(); dir != "off" { |
| if data, _ := lockedfile.Read(filepath.Join(dir, "testexpire.txt")); len(data) > 0 && data[len(data)-1] == '\n' { |
| if t, err := strconv.ParseInt(string(data[:len(data)-1]), 10, 64); err == nil { |
| testCacheExpire = time.Unix(0, t) |
| } |
| } |
| } |
| |
| var b work.Builder |
| b.Init() |
| |
| if cfg.BuildI { |
| fmt.Fprint(os.Stderr, "go test: -i flag is deprecated\n") |
| cfg.BuildV = testV |
| |
| deps := make(map[string]bool) |
| for _, dep := range load.TestMainDeps { |
| deps[dep] = true |
| } |
| |
| for _, p := range pkgs { |
| // Dependencies for each test. |
| for _, path := range p.Imports { |
| deps[path] = true |
| } |
| for _, path := range p.Resolve(p.TestImports) { |
| deps[path] = true |
| } |
| for _, path := range p.Resolve(p.XTestImports) { |
| deps[path] = true |
| } |
| } |
| |
| // translate C to runtime/cgo |
| if deps["C"] { |
| delete(deps, "C") |
| deps["runtime/cgo"] = true |
| } |
| // Ignore pseudo-packages. |
| delete(deps, "unsafe") |
| |
| all := []string{} |
| for path := range deps { |
| if !build.IsLocalImport(path) { |
| all = append(all, path) |
| } |
| } |
| sort.Strings(all) |
| |
| a := &work.Action{Mode: "go test -i"} |
| pkgs := load.PackagesAndErrors(ctx, all) |
| load.CheckPackageErrors(pkgs) |
| for _, p := range pkgs { |
| if cfg.BuildToolchainName == "gccgo" && p.Standard { |
| // gccgo's standard library packages |
| // can not be reinstalled. |
| continue |
| } |
| a.Deps = append(a.Deps, b.CompileAction(work.ModeInstall, work.ModeInstall, p)) |
| } |
| b.Do(ctx, a) |
| if !testC || a.Failed { |
| return |
| } |
| b.Init() |
| } |
| |
| var builds, runs, prints []*work.Action |
| |
| if testCoverPaths != nil { |
| match := make([]func(*load.Package) bool, len(testCoverPaths)) |
| matched := make([]bool, len(testCoverPaths)) |
| for i := range testCoverPaths { |
| match[i] = load.MatchPackage(testCoverPaths[i], base.Cwd) |
| } |
| |
| // Select for coverage all dependencies matching the testCoverPaths patterns. |
| for _, p := range load.TestPackageList(ctx, pkgs) { |
| haveMatch := false |
| for i := range testCoverPaths { |
| if match[i](p) { |
| matched[i] = true |
| haveMatch = true |
| } |
| } |
| |
| // Silently ignore attempts to run coverage on |
| // sync/atomic when using atomic coverage mode. |
| // Atomic coverage mode uses sync/atomic, so |
| // we can't also do coverage on it. |
| if testCoverMode == "atomic" && p.Standard && p.ImportPath == "sync/atomic" { |
| continue |
| } |
| |
| // If using the race detector, silently ignore |
| // attempts to run coverage on the runtime |
| // packages. It will cause the race detector |
| // to be invoked before it has been initialized. |
| if cfg.BuildRace && p.Standard && (p.ImportPath == "runtime" || strings.HasPrefix(p.ImportPath, "runtime/internal")) { |
| continue |
| } |
| |
| if haveMatch { |
| testCoverPkgs = append(testCoverPkgs, p) |
| } |
| } |
| |
| // Warn about -coverpkg arguments that are not actually used. |
| for i := range testCoverPaths { |
| if !matched[i] { |
| fmt.Fprintf(os.Stderr, "warning: no packages being tested depend on matches for pattern %s\n", testCoverPaths[i]) |
| } |
| } |
| |
| // Mark all the coverage packages for rebuilding with coverage. |
| for _, p := range testCoverPkgs { |
| // There is nothing to cover in package unsafe; it comes from the compiler. |
| if p.ImportPath == "unsafe" { |
| continue |
| } |
| p.Internal.CoverMode = testCoverMode |
| var coverFiles []string |
| coverFiles = append(coverFiles, p.GoFiles...) |
| coverFiles = append(coverFiles, p.CgoFiles...) |
| coverFiles = append(coverFiles, p.TestGoFiles...) |
| p.Internal.CoverVars = declareCoverVars(p, coverFiles...) |
| if testCover && testCoverMode == "atomic" { |
| ensureImport(p, "sync/atomic") |
| } |
| } |
| } |
| |
| // Prepare build + run + print actions for all packages being tested. |
| for _, p := range pkgs { |
| // sync/atomic import is inserted by the cover tool. See #18486 |
| if testCover && testCoverMode == "atomic" { |
| ensureImport(p, "sync/atomic") |
| } |
| |
| buildTest, runTest, printTest, err := builderTest(&b, ctx, p) |
| if err != nil { |
| str := err.Error() |
| str = strings.TrimPrefix(str, "\n") |
| if p.ImportPath != "" { |
| base.Errorf("# %s\n%s", p.ImportPath, str) |
| } else { |
| base.Errorf("%s", str) |
| } |
| fmt.Printf("FAIL\t%s [setup failed]\n", p.ImportPath) |
| continue |
| } |
| builds = append(builds, buildTest) |
| runs = append(runs, runTest) |
| prints = append(prints, printTest) |
| } |
| |
| // Ultimately the goal is to print the output. |
| root := &work.Action{Mode: "go test", Func: printExitStatus, Deps: prints} |
| |
| // Force the printing of results to happen in order, |
| // one at a time. |
| for i, a := range prints { |
| if i > 0 { |
| a.Deps = append(a.Deps, prints[i-1]) |
| } |
| } |
| |
| // Force benchmarks to run in serial. |
| if !testC && (testBench != "") { |
| // The first run must wait for all builds. |
| // Later runs must wait for the previous run's print. |
| for i, run := range runs { |
| if i == 0 { |
| run.Deps = append(run.Deps, builds...) |
| } else { |
| run.Deps = append(run.Deps, prints[i-1]) |
| } |
| } |
| } |
| |
| b.Do(ctx, root) |
| } |
| |
| // ensures that package p imports the named package |
| func ensureImport(p *load.Package, pkg string) { |
| for _, d := range p.Internal.Imports { |
| if d.Name == pkg { |
| return |
| } |
| } |
| |
| p1 := load.LoadImportWithFlags(pkg, p.Dir, p, &load.ImportStack{}, nil, 0) |
| if p1.Error != nil { |
| base.Fatalf("load %s: %v", pkg, p1.Error) |
| } |
| |
| p.Internal.Imports = append(p.Internal.Imports, p1) |
| } |
| |
| var windowsBadWords = []string{ |
| "install", |
| "patch", |
| "setup", |
| "update", |
| } |
| |
| func builderTest(b *work.Builder, ctx context.Context, p *load.Package) (buildAction, runAction, printAction *work.Action, err error) { |
| if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { |
| build := b.CompileAction(work.ModeBuild, work.ModeBuild, p) |
| run := &work.Action{Mode: "test run", Package: p, Deps: []*work.Action{build}} |
| addTestVet(b, p, run, nil) |
| print := &work.Action{Mode: "test print", Func: builderNoTest, Package: p, Deps: []*work.Action{run}} |
| return build, run, print, nil |
| } |
| |
| // Build Package structs describing: |
| // pmain - pkg.test binary |
| // ptest - package + test files |
| // pxtest - package of external test files |
| var cover *load.TestCover |
| if testCover { |
| cover = &load.TestCover{ |
| Mode: testCoverMode, |
| Local: testCover && testCoverPaths == nil, |
| Pkgs: testCoverPkgs, |
| Paths: testCoverPaths, |
| DeclVars: declareCoverVars, |
| } |
| } |
| pmain, ptest, pxtest, err := load.TestPackagesFor(ctx, p, cover) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| |
| // Use last element of import path, not package name. |
| // They differ when package name is "main". |
| // But if the import path is "command-line-arguments", |
| // like it is during 'go run', use the package name. |
| var elem string |
| if p.ImportPath == "command-line-arguments" { |
| elem = p.Name |
| } else { |
| elem = p.DefaultExecName() |
| } |
| testBinary := elem + ".test" |
| |
| testDir := b.NewObjdir() |
| if err := b.Mkdir(testDir); err != nil { |
| return nil, nil, nil, err |
| } |
| |
| pmain.Dir = testDir |
| pmain.Internal.OmitDebug = !testC && !testNeedBinary() |
| |
| if !cfg.BuildN { |
| // writeTestmain writes _testmain.go, |
| // using the test description gathered in t. |
| if err := os.WriteFile(testDir+"_testmain.go", *pmain.Internal.TestmainGo, 0666); err != nil { |
| return nil, nil, nil, err |
| } |
| } |
| |
| // Set compile objdir to testDir we've already created, |
| // so that the default file path stripping applies to _testmain.go. |
| b.CompileAction(work.ModeBuild, work.ModeBuild, pmain).Objdir = testDir |
| |
| a := b.LinkAction(work.ModeBuild, work.ModeBuild, pmain) |
| a.Target = testDir + testBinary + cfg.ExeSuffix |
| if cfg.Goos == "windows" { |
| // There are many reserved words on Windows that, |
| // if used in the name of an executable, cause Windows |
| // to try to ask for extra permissions. |
| // The word list includes setup, install, update, and patch, |
| // but it does not appear to be defined anywhere. |
| // We have run into this trying to run the |
| // go.codereview/patch tests. |
| // For package names containing those words, use test.test.exe |
| // instead of pkgname.test.exe. |
| // Note that this file name is only used in the Go command's |
| // temporary directory. If the -c or other flags are |
| // given, the code below will still use pkgname.test.exe. |
| // There are two user-visible effects of this change. |
| // First, you can actually run 'go test' in directories that |
| // have names that Windows thinks are installer-like, |
| // without getting a dialog box asking for more permissions. |
| // Second, in the Windows process listing during go test, |
| // the test shows up as test.test.exe, not pkgname.test.exe. |
| // That second one is a drawback, but it seems a small |
| // price to pay for the test running at all. |
| // If maintaining the list of bad words is too onerous, |
| // we could just do this always on Windows. |
| for _, bad := range windowsBadWords { |
| if strings.Contains(testBinary, bad) { |
| a.Target = testDir + "test.test" + cfg.ExeSuffix |
| break |
| } |
| } |
| } |
| buildAction = a |
| var installAction, cleanAction *work.Action |
| if testC || testNeedBinary() { |
| // -c or profiling flag: create action to copy binary to ./test.out. |
| target := filepath.Join(base.Cwd, testBinary+cfg.ExeSuffix) |
| if testO != "" { |
| target = testO |
| if !filepath.IsAbs(target) { |
| target = filepath.Join(base.Cwd, target) |
| } |
| } |
| if target == os.DevNull { |
| runAction = buildAction |
| } else { |
| pmain.Target = target |
| installAction = &work.Action{ |
| Mode: "test build", |
| Func: work.BuildInstallFunc, |
| Deps: []*work.Action{buildAction}, |
| Package: pmain, |
| Target: target, |
| } |
| runAction = installAction // make sure runAction != nil even if not running test |
| } |
| } |
| var vetRunAction *work.Action |
| if testC { |
| printAction = &work.Action{Mode: "test print (nop)", Package: p, Deps: []*work.Action{runAction}} // nop |
| vetRunAction = printAction |
| } else { |
| // run test |
| c := new(runCache) |
| runAction = &work.Action{ |
| Mode: "test run", |
| Func: c.builderRunTest, |
| Deps: []*work.Action{buildAction}, |
| Package: p, |
| IgnoreFail: true, // run (prepare output) even if build failed |
| TryCache: c.tryCache, |
| Objdir: testDir, |
| } |
| vetRunAction = runAction |
| cleanAction = &work.Action{ |
| Mode: "test clean", |
| Func: builderCleanTest, |
| Deps: []*work.Action{runAction}, |
| Package: p, |
| IgnoreFail: true, // clean even if test failed |
| Objdir: testDir, |
| } |
| printAction = &work.Action{ |
| Mode: "test print", |
| Func: builderPrintTest, |
| Deps: []*work.Action{cleanAction}, |
| Package: p, |
| IgnoreFail: true, // print even if test failed |
| } |
| } |
| |
| if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 { |
| addTestVet(b, ptest, vetRunAction, installAction) |
| } |
| if pxtest != nil { |
| addTestVet(b, pxtest, vetRunAction, installAction) |
| } |
| |
| if installAction != nil { |
| if runAction != installAction { |
| installAction.Deps = append(installAction.Deps, runAction) |
| } |
| if cleanAction != nil { |
| cleanAction.Deps = append(cleanAction.Deps, installAction) |
| } |
| } |
| |
| return buildAction, runAction, printAction, nil |
| } |
| |
| func addTestVet(b *work.Builder, p *load.Package, runAction, installAction *work.Action) { |
| if testVet.off { |
| return |
| } |
| |
| vet := b.VetAction(work.ModeBuild, work.ModeBuild, p) |
| runAction.Deps = append(runAction.Deps, vet) |
| // Install will clean the build directory. |
| // Make sure vet runs first. |
| // The install ordering in b.VetAction does not apply here |
| // because we are using a custom installAction (created above). |
| if installAction != nil { |
| installAction.Deps = append(installAction.Deps, vet) |
| } |
| } |
| |
| // isTestFile reports whether the source file is a set of tests and should therefore |
| // be excluded from coverage analysis. |
| func isTestFile(file string) bool { |
| // We don't cover tests, only the code they test. |
| return strings.HasSuffix(file, "_test.go") |
| } |
| |
| // declareCoverVars attaches the required cover variables names |
| // to the files, to be used when annotating the files. |
| func declareCoverVars(p *load.Package, files ...string) map[string]*load.CoverVar { |
| coverVars := make(map[string]*load.CoverVar) |
| coverIndex := 0 |
| // We create the cover counters as new top-level variables in the package. |
| // We need to avoid collisions with user variables (GoCover_0 is unlikely but still) |
| // and more importantly with dot imports of other covered packages, |
| // so we append 12 hex digits from the SHA-256 of the import path. |
| // The point is only to avoid accidents, not to defeat users determined to |
| // break things. |
| sum := sha256.Sum256([]byte(p.ImportPath)) |
| h := fmt.Sprintf("%x", sum[:6]) |
| for _, file := range files { |
| if isTestFile(file) { |
| continue |
| } |
| // For a package that is "local" (imported via ./ import or command line, outside GOPATH), |
| // we record the full path to the file name. |
| // Otherwise we record the import path, then a forward slash, then the file name. |
| // This makes profiles within GOPATH file system-independent. |
| // These names appear in the cmd/cover HTML interface. |
| var longFile string |
| if p.Internal.Local { |
| longFile = filepath.Join(p.Dir, file) |
| } else { |
| longFile = path.Join(p.ImportPath, file) |
| } |
| coverVars[file] = &load.CoverVar{ |
| File: longFile, |
| Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h), |
| } |
| coverIndex++ |
| } |
| return coverVars |
| } |
| |
| var noTestsToRun = []byte("\ntesting: warning: no tests to run\n") |
| |
| type runCache struct { |
| disableCache bool // cache should be disabled for this run |
| |
| buf *bytes.Buffer |
| id1 cache.ActionID |
| id2 cache.ActionID |
| } |
| |
| // stdoutMu and lockedStdout provide a locked standard output |
| // that guarantees never to interlace writes from multiple |
| // goroutines, so that we can have multiple JSON streams writing |
| // to a lockedStdout simultaneously and know that events will |
| // still be intelligible. |
| var stdoutMu sync.Mutex |
| |
| type lockedStdout struct{} |
| |
| func (lockedStdout) Write(b []byte) (int, error) { |
| stdoutMu.Lock() |
| defer stdoutMu.Unlock() |
| return os.Stdout.Write(b) |
| } |
| |
| // builderRunTest is the action for running a test binary. |
| func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.Action) error { |
| if a.Failed { |
| // We were unable to build the binary. |
| a.Failed = false |
| a.TestOutput = new(bytes.Buffer) |
| fmt.Fprintf(a.TestOutput, "FAIL\t%s [build failed]\n", a.Package.ImportPath) |
| base.SetExitStatus(1) |
| return nil |
| } |
| |
| var stdout io.Writer = os.Stdout |
| var err error |
| if testJSON { |
| json := test2json.NewConverter(lockedStdout{}, a.Package.ImportPath, test2json.Timestamp) |
| defer func() { |
| json.Exited(err) |
| json.Close() |
| }() |
| stdout = json |
| } |
| |
| var buf bytes.Buffer |
| if len(pkgArgs) == 0 || (testBench != "") { |
| // Stream test output (no buffering) when no package has |
| // been given on the command line (implicit current directory) |
| // or when benchmarking. |
| // No change to stdout. |
| } else { |
| // If we're only running a single package under test or if parallelism is |
| // set to 1, and if we're displaying all output (testShowPass), we can |
| // hurry the output along, echoing it as soon as it comes in. |
| // We still have to copy to &buf for caching the result. This special |
| // case was introduced in Go 1.5 and is intentionally undocumented: |
| // the exact details of output buffering are up to the go command and |
| // subject to change. It would be nice to remove this special case |
| // entirely, but it is surely very helpful to see progress being made |
| // when tests are run on slow single-CPU ARM systems. |
| // |
| // If we're showing JSON output, then display output as soon as |
| // possible even when multiple tests are being run: the JSON output |
| // events are attributed to specific package tests, so interlacing them |
| // is OK. |
| if testShowPass() && (len(pkgs) == 1 || cfg.BuildP == 1) || testJSON { |
| // Write both to stdout and buf, for possible saving |
| // to cache, and for looking for the "no tests to run" message. |
| stdout = io.MultiWriter(stdout, &buf) |
| } else { |
| stdout = &buf |
| } |
| } |
| |
| if c.buf == nil { |
| // We did not find a cached result using the link step action ID, |
| // so we ran the link step. Try again now with the link output |
| // content ID. The attempt using the action ID makes sure that |
| // if the link inputs don't change, we reuse the cached test |
| // result without even rerunning the linker. The attempt using |
| // the link output (test binary) content ID makes sure that if |
| // we have different link inputs but the same final binary, |
| // we still reuse the cached test result. |
| // c.saveOutput will store the result under both IDs. |
| c.tryCacheWithID(b, a, a.Deps[0].BuildContentID()) |
| } |
| if c.buf != nil { |
| if stdout != &buf { |
| stdout.Write(c.buf.Bytes()) |
| c.buf.Reset() |
| } |
| a.TestOutput = c.buf |
| return nil |
| } |
| |
| execCmd := work.FindExecCmd() |
| testlogArg := []string{} |
| if !c.disableCache && len(execCmd) == 0 { |
| testlogArg = []string{"-test.testlogfile=" + a.Objdir + "testlog.txt"} |
| } |
| panicArg := "-test.paniconexit0" |
| args := str.StringList(execCmd, a.Deps[0].BuiltTarget(), testlogArg, panicArg, testArgs) |
| |
| if testCoverProfile != "" { |
| // Write coverage to temporary profile, for merging later. |
| for i, arg := range args { |
| if strings.HasPrefix(arg, "-test.coverprofile=") { |
| args[i] = "-test.coverprofile=" + a.Objdir + "_cover_.out" |
| } |
| } |
| } |
| |
| if cfg.BuildN || cfg.BuildX { |
| b.Showcmd("", "%s", strings.Join(args, " ")) |
| if cfg.BuildN { |
| return nil |
| } |
| } |
| |
| cmd := exec.Command(args[0], args[1:]...) |
| cmd.Dir = a.Package.Dir |
| cmd.Env = base.AppendPWD(cfg.OrigEnv[:len(cfg.OrigEnv):len(cfg.OrigEnv)], cmd.Dir) |
| cmd.Stdout = stdout |
| cmd.Stderr = stdout |
| |
| // If there are any local SWIG dependencies, we want to load |
| // the shared library from the build directory. |
| if a.Package.UsesSwig() { |
| env := cmd.Env |
| found := false |
| prefix := "LD_LIBRARY_PATH=" |
| for i, v := range env { |
| if strings.HasPrefix(v, prefix) { |
| env[i] = v + ":." |
| found = true |
| break |
| } |
| } |
| if !found { |
| env = append(env, "LD_LIBRARY_PATH=.") |
| } |
| cmd.Env = env |
| } |
| |
| t0 := time.Now() |
| err = cmd.Start() |
| |
| // This is a last-ditch deadline to detect and |
| // stop wedged test binaries, to keep the builders |
| // running. |
| if err == nil { |
| tick := time.NewTimer(testKillTimeout) |
| base.StartSigHandlers() |
| done := make(chan error) |
| go func() { |
| done <- cmd.Wait() |
| }() |
| Outer: |
| select { |
| case err = <-done: |
| // ok |
| case <-tick.C: |
| if base.SignalTrace != nil { |
| // Send a quit signal in the hope that the program will print |
| // a stack trace and exit. Give it five seconds before resorting |
| // to Kill. |
| cmd.Process.Signal(base.SignalTrace) |
| select { |
| case err = <-done: |
| fmt.Fprintf(cmd.Stdout, "*** Test killed with %v: ran too long (%v).\n", base.SignalTrace, testKillTimeout) |
| break Outer |
| case <-time.After(5 * time.Second): |
| } |
| } |
| cmd.Process.Kill() |
| err = <-done |
| fmt.Fprintf(cmd.Stdout, "*** Test killed: ran too long (%v).\n", testKillTimeout) |
| } |
| tick.Stop() |
| } |
| out := buf.Bytes() |
| a.TestOutput = &buf |
| t := fmt.Sprintf("%.3fs", time.Since(t0).Seconds()) |
| |
| mergeCoverProfile(cmd.Stdout, a.Objdir+"_cover_.out") |
| |
| if err == nil { |
| norun := "" |
| if !testShowPass() && !testJSON { |
| buf.Reset() |
| } |
| if bytes.HasPrefix(out, noTestsToRun[1:]) || bytes.Contains(out, noTestsToRun) { |
| norun = " [no tests to run]" |
| } |
| fmt.Fprintf(cmd.Stdout, "ok \t%s\t%s%s%s\n", a.Package.ImportPath, t, coveragePercentage(out), norun) |
| c.saveOutput(a) |
| } else { |
| base.SetExitStatus(1) |
| // If there was test output, assume we don't need to print the exit status. |
| // Buf there's no test output, do print the exit status. |
| if len(out) == 0 { |
| fmt.Fprintf(cmd.Stdout, "%s\n", err) |
| } |
| // NOTE(golang.org/issue/37555): test2json reports that a test passes |
| // unless "FAIL" is printed at the beginning of a line. The test may not |
| // actually print that if it panics, exits, or terminates abnormally, |
| // so we print it here. We can't always check whether it was printed |
| // because some tests need stdout to be a terminal (golang.org/issue/34791), |
| // not a pipe. |
| // TODO(golang.org/issue/29062): tests that exit with status 0 without |
| // printing a final result should fail. |
| fmt.Fprintf(cmd.Stdout, "FAIL\t%s\t%s\n", a.Package.ImportPath, t) |
| } |
| |
| if cmd.Stdout != &buf { |
| buf.Reset() // cmd.Stdout was going to os.Stdout already |
| } |
| return nil |
| } |
| |
| // tryCache is called just before the link attempt, |
| // to see if the test result is cached and therefore the link is unneeded. |
| // It reports whether the result can be satisfied from cache. |
| func (c *runCache) tryCache(b *work.Builder, a *work.Action) bool { |
| return c.tryCacheWithID(b, a, a.Deps[0].BuildActionID()) |
| } |
| |
| func (c *runCache) tryCacheWithID(b *work.Builder, a *work.Action, id string) bool { |
| if len(pkgArgs) == 0 { |
| // Caching does not apply to "go test", |
| // only to "go test foo" (including "go test ."). |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: caching disabled in local directory mode\n") |
| } |
| c.disableCache = true |
| return false |
| } |
| |
| if a.Package.Root == "" { |
| // Caching does not apply to tests outside of any module, GOPATH, or GOROOT. |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: caching disabled for package outside of module root, GOPATH, or GOROOT: %s\n", a.Package.ImportPath) |
| } |
| c.disableCache = true |
| return false |
| } |
| |
| var cacheArgs []string |
| for _, arg := range testArgs { |
| i := strings.Index(arg, "=") |
| if i < 0 || !strings.HasPrefix(arg, "-test.") { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: caching disabled for test argument: %s\n", arg) |
| } |
| c.disableCache = true |
| return false |
| } |
| switch arg[:i] { |
| case "-test.cpu", |
| "-test.list", |
| "-test.parallel", |
| "-test.run", |
| "-test.short", |
| "-test.timeout", |
| "-test.v": |
| // These are cacheable. |
| // Note that this list is documented above, |
| // so if you add to this list, update the docs too. |
| cacheArgs = append(cacheArgs, arg) |
| |
| default: |
| // nothing else is cacheable |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: caching disabled for test argument: %s\n", arg) |
| } |
| c.disableCache = true |
| return false |
| } |
| } |
| |
| if cache.Default() == nil { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: GOCACHE=off\n") |
| } |
| c.disableCache = true |
| return false |
| } |
| |
| // The test cache result fetch is a two-level lookup. |
| // |
| // First, we use the content hash of the test binary |
| // and its command-line arguments to find the |
| // list of environment variables and files consulted |
| // the last time the test was run with those arguments. |
| // (To avoid unnecessary links, we store this entry |
| // under two hashes: id1 uses the linker inputs as a |
| // proxy for the test binary, and id2 uses the actual |
| // test binary. If the linker inputs are unchanged, |
| // this way we avoid the link step, even though we |
| // do not cache link outputs.) |
| // |
| // Second, we compute a hash of the values of the |
| // environment variables and the content of the files |
| // listed in the log from the previous run. |
| // Then we look up test output using a combination of |
| // the hash from the first part (testID) and the hash of the |
| // test inputs (testInputsID). |
| // |
| // In order to store a new test result, we must redo the |
| // testInputsID computation using the log from the run |
| // we want to cache, and then we store that new log and |
| // the new outputs. |
| |
| h := cache.NewHash("testResult") |
| fmt.Fprintf(h, "test binary %s args %q execcmd %q", id, cacheArgs, work.ExecCmd) |
| testID := h.Sum() |
| if c.id1 == (cache.ActionID{}) { |
| c.id1 = testID |
| } else { |
| c.id2 = testID |
| } |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: test ID %x => %x\n", a.Package.ImportPath, id, testID) |
| } |
| |
| // Load list of referenced environment variables and files |
| // from last run of testID, and compute hash of that content. |
| data, entry, err := cache.Default().GetBytes(testID) |
| if !bytes.HasPrefix(data, testlogMagic) || data[len(data)-1] != '\n' { |
| if cache.DebugTest { |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "testcache: %s: input list not found: %v\n", a.Package.ImportPath, err) |
| } else { |
| fmt.Fprintf(os.Stderr, "testcache: %s: input list malformed\n", a.Package.ImportPath) |
| } |
| } |
| return false |
| } |
| testInputsID, err := computeTestInputsID(a, data) |
| if err != nil { |
| return false |
| } |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: test ID %x => input ID %x => %x\n", a.Package.ImportPath, testID, testInputsID, testAndInputKey(testID, testInputsID)) |
| } |
| |
| // Parse cached result in preparation for changing run time to "(cached)". |
| // If we can't parse the cached result, don't use it. |
| data, entry, err = cache.Default().GetBytes(testAndInputKey(testID, testInputsID)) |
| if len(data) == 0 || data[len(data)-1] != '\n' { |
| if cache.DebugTest { |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "testcache: %s: test output not found: %v\n", a.Package.ImportPath, err) |
| } else { |
| fmt.Fprintf(os.Stderr, "testcache: %s: test output malformed\n", a.Package.ImportPath) |
| } |
| } |
| return false |
| } |
| if entry.Time.Before(testCacheExpire) { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: test output expired due to go clean -testcache\n", a.Package.ImportPath) |
| } |
| return false |
| } |
| i := bytes.LastIndexByte(data[:len(data)-1], '\n') + 1 |
| if !bytes.HasPrefix(data[i:], []byte("ok \t")) { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: test output malformed\n", a.Package.ImportPath) |
| } |
| return false |
| } |
| j := bytes.IndexByte(data[i+len("ok \t"):], '\t') |
| if j < 0 { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: test output malformed\n", a.Package.ImportPath) |
| } |
| return false |
| } |
| j += i + len("ok \t") + 1 |
| |
| // Committed to printing. |
| c.buf = new(bytes.Buffer) |
| c.buf.Write(data[:j]) |
| c.buf.WriteString("(cached)") |
| for j < len(data) && ('0' <= data[j] && data[j] <= '9' || data[j] == '.' || data[j] == 's') { |
| j++ |
| } |
| c.buf.Write(data[j:]) |
| return true |
| } |
| |
| var errBadTestInputs = errors.New("error parsing test inputs") |
| var testlogMagic = []byte("# test log\n") // known to testing/internal/testdeps/deps.go |
| |
| // computeTestInputsID computes the "test inputs ID" |
| // (see comment in tryCacheWithID above) for the |
| // test log. |
| func computeTestInputsID(a *work.Action, testlog []byte) (cache.ActionID, error) { |
| testlog = bytes.TrimPrefix(testlog, testlogMagic) |
| h := cache.NewHash("testInputs") |
| pwd := a.Package.Dir |
| for _, line := range bytes.Split(testlog, []byte("\n")) { |
| if len(line) == 0 { |
| continue |
| } |
| s := string(line) |
| i := strings.Index(s, " ") |
| if i < 0 { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: input list malformed (%q)\n", a.Package.ImportPath, line) |
| } |
| return cache.ActionID{}, errBadTestInputs |
| } |
| op := s[:i] |
| name := s[i+1:] |
| switch op { |
| default: |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: input list malformed (%q)\n", a.Package.ImportPath, line) |
| } |
| return cache.ActionID{}, errBadTestInputs |
| case "getenv": |
| fmt.Fprintf(h, "env %s %x\n", name, hashGetenv(name)) |
| case "chdir": |
| pwd = name // always absolute |
| fmt.Fprintf(h, "chdir %s %x\n", name, hashStat(name)) |
| case "stat": |
| if !filepath.IsAbs(name) { |
| name = filepath.Join(pwd, name) |
| } |
| if a.Package.Root == "" || !inDir(name, a.Package.Root) { |
| // Do not recheck files outside the module, GOPATH, or GOROOT root. |
| break |
| } |
| fmt.Fprintf(h, "stat %s %x\n", name, hashStat(name)) |
| case "open": |
| if !filepath.IsAbs(name) { |
| name = filepath.Join(pwd, name) |
| } |
| if a.Package.Root == "" || !inDir(name, a.Package.Root) { |
| // Do not recheck files outside the module, GOPATH, or GOROOT root. |
| break |
| } |
| fh, err := hashOpen(name) |
| if err != nil { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: input file %s: %s\n", a.Package.ImportPath, name, err) |
| } |
| return cache.ActionID{}, err |
| } |
| fmt.Fprintf(h, "open %s %x\n", name, fh) |
| } |
| } |
| sum := h.Sum() |
| return sum, nil |
| } |
| |
| func inDir(path, dir string) bool { |
| if str.HasFilePathPrefix(path, dir) { |
| return true |
| } |
| xpath, err1 := filepath.EvalSymlinks(path) |
| xdir, err2 := filepath.EvalSymlinks(dir) |
| if err1 == nil && err2 == nil && str.HasFilePathPrefix(xpath, xdir) { |
| return true |
| } |
| return false |
| } |
| |
| func hashGetenv(name string) cache.ActionID { |
| h := cache.NewHash("getenv") |
| v, ok := os.LookupEnv(name) |
| if !ok { |
| h.Write([]byte{0}) |
| } else { |
| h.Write([]byte{1}) |
| h.Write([]byte(v)) |
| } |
| return h.Sum() |
| } |
| |
| const modTimeCutoff = 2 * time.Second |
| |
| var errFileTooNew = errors.New("file used as input is too new") |
| |
| func hashOpen(name string) (cache.ActionID, error) { |
| h := cache.NewHash("open") |
| info, err := os.Stat(name) |
| if err != nil { |
| fmt.Fprintf(h, "err %v\n", err) |
| return h.Sum(), nil |
| } |
| hashWriteStat(h, info) |
| if info.IsDir() { |
| files, err := os.ReadDir(name) |
| if err != nil { |
| fmt.Fprintf(h, "err %v\n", err) |
| } |
| for _, f := range files { |
| fmt.Fprintf(h, "file %s ", f.Name()) |
| finfo, err := f.Info() |
| if err != nil { |
| fmt.Fprintf(h, "err %v\n", err) |
| } else { |
| hashWriteStat(h, finfo) |
| } |
| } |
| } else if info.Mode().IsRegular() { |
| // Because files might be very large, do not attempt |
| // to hash the entirety of their content. Instead assume |
| // the mtime and size recorded in hashWriteStat above |
| // are good enough. |
| // |
| // To avoid problems for very recent files where a new |
| // write might not change the mtime due to file system |
| // mtime precision, reject caching if a file was read that |
| // is less than modTimeCutoff old. |
| if time.Since(info.ModTime()) < modTimeCutoff { |
| return cache.ActionID{}, errFileTooNew |
| } |
| } |
| return h.Sum(), nil |
| } |
| |
| func hashStat(name string) cache.ActionID { |
| h := cache.NewHash("stat") |
| if info, err := os.Stat(name); err != nil { |
| fmt.Fprintf(h, "err %v\n", err) |
| } else { |
| hashWriteStat(h, info) |
| } |
| if info, err := os.Lstat(name); err != nil { |
| fmt.Fprintf(h, "err %v\n", err) |
| } else { |
| hashWriteStat(h, info) |
| } |
| return h.Sum() |
| } |
| |
| func hashWriteStat(h io.Writer, info fs.FileInfo) { |
| fmt.Fprintf(h, "stat %d %x %v %v\n", info.Size(), uint64(info.Mode()), info.ModTime(), info.IsDir()) |
| } |
| |
| // testAndInputKey returns the actual cache key for the pair (testID, testInputsID). |
| func testAndInputKey(testID, testInputsID cache.ActionID) cache.ActionID { |
| return cache.Subkey(testID, fmt.Sprintf("inputs:%x", testInputsID)) |
| } |
| |
| func (c *runCache) saveOutput(a *work.Action) { |
| if c.id1 == (cache.ActionID{}) && c.id2 == (cache.ActionID{}) { |
| return |
| } |
| |
| // See comment about two-level lookup in tryCacheWithID above. |
| testlog, err := os.ReadFile(a.Objdir + "testlog.txt") |
| if err != nil || !bytes.HasPrefix(testlog, testlogMagic) || testlog[len(testlog)-1] != '\n' { |
| if cache.DebugTest { |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "testcache: %s: reading testlog: %v\n", a.Package.ImportPath, err) |
| } else { |
| fmt.Fprintf(os.Stderr, "testcache: %s: reading testlog: malformed\n", a.Package.ImportPath) |
| } |
| } |
| return |
| } |
| testInputsID, err := computeTestInputsID(a, testlog) |
| if err != nil { |
| return |
| } |
| if c.id1 != (cache.ActionID{}) { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: save test ID %x => input ID %x => %x\n", a.Package.ImportPath, c.id1, testInputsID, testAndInputKey(c.id1, testInputsID)) |
| } |
| cache.Default().PutNoVerify(c.id1, bytes.NewReader(testlog)) |
| cache.Default().PutNoVerify(testAndInputKey(c.id1, testInputsID), bytes.NewReader(a.TestOutput.Bytes())) |
| } |
| if c.id2 != (cache.ActionID{}) { |
| if cache.DebugTest { |
| fmt.Fprintf(os.Stderr, "testcache: %s: save test ID %x => input ID %x => %x\n", a.Package.ImportPath, c.id2, testInputsID, testAndInputKey(c.id2, testInputsID)) |
| } |
| cache.Default().PutNoVerify(c.id2, bytes.NewReader(testlog)) |
| cache.Default().PutNoVerify(testAndInputKey(c.id2, testInputsID), bytes.NewReader(a.TestOutput.Bytes())) |
| } |
| } |
| |
| // coveragePercentage returns the coverage results (if enabled) for the |
| // test. It uncovers the data by scanning the output from the test run. |
| func coveragePercentage(out []byte) string { |
| if !testCover { |
| return "" |
| } |
| // The string looks like |
| // test coverage for encoding/binary: 79.9% of statements |
| // Extract the piece from the percentage to the end of the line. |
| re := regexp.MustCompile(`coverage: (.*)\n`) |
| matches := re.FindSubmatch(out) |
| if matches == nil { |
| // Probably running "go test -cover" not "go test -cover fmt". |
| // The coverage output will appear in the output directly. |
| return "" |
| } |
| return fmt.Sprintf("\tcoverage: %s", matches[1]) |
| } |
| |
| // builderCleanTest is the action for cleaning up after a test. |
| func builderCleanTest(b *work.Builder, ctx context.Context, a *work.Action) error { |
| if cfg.BuildWork { |
| return nil |
| } |
| if cfg.BuildX { |
| b.Showcmd("", "rm -r %s", a.Objdir) |
| } |
| os.RemoveAll(a.Objdir) |
| return nil |
| } |
| |
| // builderPrintTest is the action for printing a test result. |
| func builderPrintTest(b *work.Builder, ctx context.Context, a *work.Action) error { |
| clean := a.Deps[0] |
| run := clean.Deps[0] |
| if run.TestOutput != nil { |
| os.Stdout.Write(run.TestOutput.Bytes()) |
| run.TestOutput = nil |
| } |
| return nil |
| } |
| |
| // builderNoTest is the action for testing a package with no test files. |
| func builderNoTest(b *work.Builder, ctx context.Context, a *work.Action) error { |
| var stdout io.Writer = os.Stdout |
| if testJSON { |
| json := test2json.NewConverter(lockedStdout{}, a.Package.ImportPath, test2json.Timestamp) |
| defer json.Close() |
| stdout = json |
| } |
| fmt.Fprintf(stdout, "? \t%s\t[no test files]\n", a.Package.ImportPath) |
| return nil |
| } |
| |
| // printExitStatus is the action for printing the exit status |
| func printExitStatus(b *work.Builder, ctx context.Context, a *work.Action) error { |
| if !testJSON && len(pkgArgs) != 0 { |
| if base.GetExitStatus() != 0 { |
| fmt.Println("FAIL") |
| return nil |
| } |
| } |
| return nil |
| } |