// Supported only on linux/arm64, linux/amd64.
// Supported only on linux/amd64 or linux/arm64 and only with GCC 7 and higher
// or Clang/LLVM 9 and higher.
+// -cover
+// enable code coverage instrumentation (requires
+// that GOEXPERIMENT=coverageredesign be set).
+// -coverpkg pattern1,pattern2,pattern3
+// For a build that targets package 'main' (e.g. building a Go
+// executable), apply coverage analysis to each package matching
+// the patterns. The default is to apply coverage analysis to
+// packages in the main Go module. See 'go help packages' for a
+// description of package patterns. Sets -cover.
// -v
// print the names of packages as they are compiled.
// -work
// For GOARCH=wasm, comma-separated list of experimental WebAssembly features to use.
// Valid values are satconv, signext.
//
+// Environment variables for use with code coverage:
+//
+// GOCOVERDIR
+// Directory into which to write code coverage data files
+// generated by running a "go build -cover" binary.
+// Requires that GOEXPERIMENT=coverageredesign is enabled.
+//
// Special-purpose environment variables:
//
// GCCGOTOOLDIR
"src/runtime",
"src/internal/abi",
"src/internal/bytealg",
+ "src/internal/coverage/rtcov",
"src/internal/cpu",
"src/internal/goarch",
"src/internal/goexperiment",
BuildLinkshared bool // -linkshared flag
BuildMSan bool // -msan flag
BuildASan bool // -asan flag
+ BuildCover bool // -cover flag
+ BuildCoverMode string // -covermode flag
+ BuildCoverPkg []string // -coverpkg flag
BuildN bool // -n flag
BuildO string // -o flag
BuildP = runtime.GOMAXPROCS(0) // -p flag
For GOARCH=wasm, comma-separated list of experimental WebAssembly features to use.
Valid values are satconv, signext.
+Environment variables for use with code coverage:
+
+ GOCOVERDIR
+ Directory into which to write code coverage data files
+ generated by running a "go build -cover" binary.
+ Requires that GOEXPERIMENT=coverageredesign is enabled.
+
Special-purpose environment variables:
GCCGOTOOLDIR
import (
"bytes"
"context"
+ "crypto/sha256"
"encoding/json"
"errors"
"fmt"
FuzzInstrument bool // package should be instrumented for fuzzing
CoverMode string // preprocess Go source files with the coverage tool in this mode
CoverVars map[string]*CoverVar // variables created by coverage analysis
+ CoverageCfg string // coverage info config file path (passed to compiler)
OmitDebug bool // tell linker not to write debug information
GobinSubdir bool // install target would be subdir of GOBIN
BuildInfo string // add this info to package main
if cfg.BuildASan {
deps = append(deps, "runtime/asan")
}
+ // Building for coverage forces an import of runtime/coverage.
+ if cfg.BuildCover && cfg.Experiment.CoverageRedesign {
+ deps = append(deps, "runtime/coverage")
+ }
return deps
}
}
return pkgs, nil
}
+
+// EnsureImport ensures that package p imports the named package.
+func EnsureImport(p *Package, pkg string) {
+ for _, d := range p.Internal.Imports {
+ if d.Name == pkg {
+ return
+ }
+ }
+
+ p1 := LoadImportWithFlags(pkg, p.Dir, p, &ImportStack{}, nil, 0)
+ if p1.Error != nil {
+ base.Fatalf("load %s: %v", pkg, p1.Error)
+ }
+
+ p.Internal.Imports = append(p.Internal.Imports, p1)
+}
+
+// PrepareForCoverageBuild is a helper invoked for "go install -cover"
+// and "go build -cover"; it walks through the packages being built
+// (and dependencies) and marks them for coverage instrumentation
+// when appropriate, and adding dependencies where needed.
+func PrepareForCoverageBuild(pkgs []*Package) {
+ var match []func(*Package) bool
+
+ matchMainMod := func(p *Package) bool {
+ return !p.Standard && p.Module != nil && p.Module.Main
+ }
+
+ // The set of packages instrumented by default varies depending on
+ // options and the nature of the build. If "-coverpkg" has been
+ // set, then match packages below using that value; if we're
+ // building with a module in effect, then default to packages in
+ // the main module. If no module is in effect and we're building
+ // in GOPATH mode, instrument the named packages and their
+ // dependencies in GOPATH. Otherwise, for "go run ..." and for the
+ // "go build ..." case, instrument just the packages named on the
+ // command line.
+ if len(cfg.BuildCoverPkg) == 0 {
+ if modload.Enabled() {
+ // Default is main module.
+ match = []func(*Package) bool{matchMainMod}
+ } else {
+ // These matchers below are intended to handle the cases of:
+ //
+ // 1. "go run ..." and "go build ..."
+ // 2. building in gopath mode with GO111MODULE=off
+ //
+ // In case 2 above, the assumption here is that (in the
+ // absence of a -coverpkg flag) we will be instrumenting
+ // the named packages only.
+ matchMain := func(p *Package) bool { return p.Internal.CmdlineFiles || p.Internal.CmdlinePkg }
+ match = []func(*Package) bool{matchMain}
+ }
+ } else {
+ match = make([]func(*Package) bool, len(cfg.BuildCoverPkg))
+ for i := range cfg.BuildCoverPkg {
+ match[i] = MatchPackage(cfg.BuildCoverPkg[i], base.Cwd())
+ }
+ }
+
+ // Visit the packages being built or installed, along with all
+ // of their dependencies, and mark them to be instrumented,
+ // taking into account the value of -coverpkg.
+ SelectCoverPackages(PackageList(pkgs), match, "build")
+}
+
+func SelectCoverPackages(roots []*Package, match []func(*Package) bool, op string) []*Package {
+ var warntag string
+ var includeMain bool
+ switch op {
+ case "build":
+ warntag = "built"
+ includeMain = true
+ case "test":
+ warntag = "tested"
+ default:
+ panic("internal error, bad mode passed to SelectCoverPackages")
+ }
+
+ covered := []*Package{}
+ matched := make([]bool, len(match))
+ for _, p := range roots {
+ haveMatch := false
+ for i := range match {
+ if match[i](p) {
+ matched[i] = true
+ haveMatch = true
+ }
+ }
+ if !haveMatch {
+ continue
+ }
+
+ // There is nothing to cover in package unsafe; it comes from
+ // the compiler.
+ if p.ImportPath == "unsafe" {
+ continue
+ }
+
+ // A package which only has test files can't be imported as a
+ // dependency, and at the moment we don't try to instrument it
+ // for coverage. There isn't any technical reason why
+ // *_test.go files couldn't be instrumented, but it probably
+ // doesn't make much sense to lump together coverage metrics
+ // (ex: percent stmts covered) of *_test.go files with
+ // non-test Go code.
+ if len(p.GoFiles)+len(p.CgoFiles) == 0 {
+ continue
+ }
+
+ // Silently ignore attempts to run coverage on sync/atomic
+ // and/or runtime/internal/atomic when using atomic coverage
+ // mode. Atomic coverage mode uses sync/atomic, so we can't
+ // also do coverage on it.
+ if cfg.BuildCoverMode == "atomic" && p.Standard &&
+ (p.ImportPath == "sync/atomic" || p.ImportPath == "runtime/internal/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. Note
+ // the use of "regonly" instead of just ignoring the package
+ // completely-- we do this due to the requirements of the
+ // package ID numbering scheme. See the comment in
+ // $GOROOT/src/internal/coverage/pkid.go dealing with
+ // hard-coding of runtime package IDs.
+ cmode := cfg.BuildCoverMode
+ if cfg.BuildRace && p.Standard && (p.ImportPath == "runtime" || strings.HasPrefix(p.ImportPath, "runtime/internal")) {
+ cmode = "regonly"
+ }
+
+ // If -coverpkg is in effect and for some reason we don't want
+ // coverage data for the main package, make sure that we at
+ // least process it for registration hooks.
+ if includeMain && p.Name == "main" && !haveMatch {
+ haveMatch = true
+ cmode = "regonly"
+ }
+
+ // Mark package for instrumentation.
+ p.Internal.CoverMode = cmode
+ covered = append(covered, p)
+
+ // Force import of sync/atomic into package if atomic mode.
+ if cfg.BuildCoverMode == "atomic" {
+ EnsureImport(p, "sync/atomic")
+ }
+
+ // Generate covervars if using legacy coverage design.
+ if !cfg.Experiment.CoverageRedesign {
+ var coverFiles []string
+ coverFiles = append(coverFiles, p.GoFiles...)
+ coverFiles = append(coverFiles, p.CgoFiles...)
+ p.Internal.CoverVars = DeclareCoverVars(p, coverFiles...)
+ }
+ }
+
+ // Warn about -coverpkg arguments that are not actually used.
+ for i := range cfg.BuildCoverPkg {
+ if !matched[i] {
+ fmt.Fprintf(os.Stderr, "warning: no packages being %s depend on matches for pattern %s\n", warntag, cfg.BuildCoverPkg[i])
+ }
+ }
+
+ return covered
+}
+
+// declareCoverVars attaches the required cover variables names
+// to the files, to be used when annotating the files. This
+// function only called when using legacy coverage test/build
+// (e.g. GOEXPERIMENT=coverageredesign is off).
+func DeclareCoverVars(p *Package, files ...string) map[string]*CoverVar {
+ coverVars := make(map[string]*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 base.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 = pathpkg.Join(p.ImportPath, file)
+ }
+ coverVars[file] = &CoverVar{
+ File: longFile,
+ Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
+ }
+ coverIndex++
+ }
+ return coverVars
+}
"unicode"
"unicode/utf8"
+ "cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/str"
"cmd/go/internal/trace"
}
type TestCover struct {
- Mode string
- Local bool
- Pkgs []*Package
- Paths []string
- Vars []coverInfo
- DeclVars func(*Package, ...string) map[string]*CoverVar
+ Mode string
+ Local bool
+ Pkgs []*Package
+ Paths []string
+ Vars []coverInfo
}
// TestPackagesFor is like TestPackagesAndErrors but it returns
}
stk.Pop()
- if cover != nil && cover.Pkgs != nil {
+ if cover != nil && cover.Pkgs != nil && !cfg.Experiment.CoverageRedesign {
// Add imports, but avoid duplicates.
seen := map[*Package]bool{p: true, ptest: true}
for _, p1 := range pmain.Internal.Imports {
// Replace pmain's transitive dependencies with test copies, as necessary.
recompileForTest(pmain, p, ptest, pxtest)
- // Should we apply coverage analysis locally,
- // only for this package and only for this test?
- // Yes, if -cover is on but -coverpkg has not specified
- // a list of packages for global coverage.
- if cover != nil && cover.Local {
- ptest.Internal.CoverMode = cover.Mode
- var coverFiles []string
- coverFiles = append(coverFiles, ptest.GoFiles...)
- coverFiles = append(coverFiles, ptest.CgoFiles...)
- ptest.Internal.CoverVars = cover.DeclVars(ptest, coverFiles...)
- }
+ if cover != nil {
+ if cfg.Experiment.CoverageRedesign {
+ // Here ptest needs to inherit the proper coverage mode (since
+ // it contains p's Go files), whereas pmain contains only
+ // test harness code (don't want to instrument it, and
+ // we don't want coverage hooks in the pkg init).
+ ptest.Internal.CoverMode = p.Internal.CoverMode
+ pmain.Internal.CoverMode = "testmain"
+ }
+ // Should we apply coverage analysis locally, only for this
+ // package and only for this test? Yes, if -cover is on but
+ // -coverpkg has not specified a list of packages for global
+ // coverage.
+ if cover.Local {
+ ptest.Internal.CoverMode = cover.Mode
+
+ if !cfg.Experiment.CoverageRedesign {
+ var coverFiles []string
+ coverFiles = append(coverFiles, ptest.GoFiles...)
+ coverFiles = append(coverFiles, ptest.CgoFiles...)
+ ptest.Internal.CoverVars = DeclareCoverVars(ptest, coverFiles...)
+ }
+ }
- for _, cp := range pmain.Internal.Imports {
- if len(cp.Internal.CoverVars) > 0 {
- t.Cover.Vars = append(t.Cover.Vars, coverInfo{cp, cp.Internal.CoverVars})
+ if !cfg.Experiment.CoverageRedesign {
+ for _, cp := range pmain.Internal.Imports {
+ if len(cp.Internal.CoverVars) > 0 {
+ t.Cover.Vars = append(t.Cover.Vars, coverInfo{cp, cp.Internal.CoverVars})
+ }
+ }
}
}
// formatTestmain returns the content of the _testmain.go file for t.
func formatTestmain(t *testFuncs) ([]byte, error) {
var buf bytes.Buffer
- if err := testmainTmpl.Execute(&buf, t); err != nil {
+ tmpl := testmainTmpl
+ if cfg.Experiment.CoverageRedesign {
+ tmpl = testmainTmplNewCoverage
+ }
+ if err := tmpl.Execute(&buf, t); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
`)
+
+var testmainTmplNewCoverage = lazytemplate.New("main", `
+// Code generated by 'go test'. DO NOT EDIT.
+
+package main
+
+import (
+ "os"
+{{if .Cover}}
+ _ "unsafe"
+{{end}}
+{{if .TestMain}}
+ "reflect"
+{{end}}
+ "testing"
+ "testing/internal/testdeps"
+
+{{if .ImportTest}}
+ {{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
+{{end}}
+{{if .ImportXtest}}
+ {{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
+{{end}}
+)
+
+var tests = []testing.InternalTest{
+{{range .Tests}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}},
+{{end}}
+}
+
+var benchmarks = []testing.InternalBenchmark{
+{{range .Benchmarks}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}},
+{{end}}
+}
+
+var fuzzTargets = []testing.InternalFuzzTarget{
+{{range .FuzzTargets}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}},
+{{end}}
+}
+
+var examples = []testing.InternalExample{
+{{range .Examples}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
+{{end}}
+}
+
+func init() {
+ testdeps.ImportPath = {{.ImportPath | printf "%q"}}
+}
+
+{{if .Cover}}
+
+//go:linkname runtime_coverage_processCoverTestDir runtime/coverage.processCoverTestDir
+func runtime_coverage_processCoverTestDir(dir string, cfile string, cmode string, cpkgs string) error
+
+//go:linkname testing_registerCover2 testing.registerCover2
+func testing_registerCover2(mode string, tearDown func(coverprofile string, gocoverdir string) (string, error))
+
+//go:linkname runtime_coverage_markProfileEmitted runtime/coverage.markProfileEmitted
+func runtime_coverage_markProfileEmitted(val bool)
+
+func coverTearDown(coverprofile string, gocoverdir string) (string, error) {
+ var err error
+ if gocoverdir == "" {
+ gocoverdir, err = os.MkdirTemp("", "gocoverdir")
+ if err != nil {
+ return "error setting GOCOVERDIR: bad os.MkdirTemp return", err
+ }
+ defer os.RemoveAll(gocoverdir)
+ }
+ runtime_coverage_markProfileEmitted(true)
+ cmode := {{printf "%q" .Cover.Mode}}
+ if err := runtime_coverage_processCoverTestDir(gocoverdir, coverprofile, cmode, {{printf "%q" .Covered}}); err != nil {
+ return "error generating coverage report", err
+ }
+ return "", nil
+}
+{{end}}
+
+func main() {
+{{if .Cover}}
+ testing_registerCover2({{printf "%q" .Cover.Mode}}, coverTearDown)
+{{end}}
+ m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
+{{with .TestMain}}
+ {{.Package}}.{{.Name}}(m)
+ os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
+{{else}}
+ os.Exit(m.Run())
+{{end}}
+}
+
+`)
CmdRun.Run = runRun // break init loop
work.AddBuildFlags(CmdRun, work.DefaultBuildFlags)
+ if cfg.Experiment != nil && cfg.Experiment.CoverageRedesign {
+ work.AddCoverFlags(CmdRun, nil)
+ }
CmdRun.Flag.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "")
}
cmdArgs := args[i:]
load.CheckPackageErrors([]*load.Package{p})
+ if cfg.Experiment.CoverageRedesign && cfg.BuildCover {
+ load.PrepareForCoverageBuild([]*load.Package{p})
+ }
+
p.Internal.OmitDebug = true
p.Target = "" // must build - not up to date
if p.Internal.CmdlineFiles {
import (
"cmd/go/internal/base"
+ "cmd/go/internal/cfg"
"fmt"
"io"
"os"
if err != nil {
base.Fatalf("%v", err)
}
- _, err = fmt.Fprintf(f, "mode: %s\n", testCoverMode)
+ _, err = fmt.Fprintf(f, "mode: %s\n", cfg.BuildCoverMode)
if err != nil {
base.Fatalf("%v", err)
}
coverMerge.Lock()
defer coverMerge.Unlock()
- expect := fmt.Sprintf("mode: %s\n", testCoverMode)
+ expect := fmt.Sprintf("mode: %s\n", cfg.BuildCoverMode)
buf := make([]byte, len(expect))
r, err := os.Open(file)
if err != nil {
return
}
if err != nil || string(buf) != expect {
- fmt.Fprintf(ew, "error: test wrote malformed coverage profile.\n")
+ fmt.Fprintf(ew, "error: test wrote malformed coverage profile %s.\n", file)
return
}
_, err = io.Copy(coverMerge.f, r)
}
name := strings.TrimPrefix(f.Name, "test.")
switch name {
- case "testlogfile", "paniconexit0", "fuzzcachedir", "fuzzworker":
+ case "testlogfile", "paniconexit0", "fuzzcachedir", "fuzzworker",
+ "gocoverdir":
// These are internal flags.
default:
if !passFlagToTest[name] {
import (
"bytes"
"context"
- "crypto/sha256"
"errors"
"fmt"
"go/build"
"io/fs"
"os"
"os/exec"
- "path"
"path/filepath"
"regexp"
"sort"
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
testFuzz string // -fuzz flag
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())
+ if cfg.BuildCoverPkg != nil {
+ match := make([]func(*load.Package) bool, len(cfg.BuildCoverPkg))
+ for i := range cfg.BuildCoverPkg {
+ match[i] = load.MatchPackage(cfg.BuildCoverPkg[i], base.Cwd())
}
- // Select for coverage all dependencies matching the testCoverPaths patterns.
- for _, p := range load.TestPackageList(ctx, pkgOpts, pkgs) {
- haveMatch := false
- for i := range testCoverPaths {
- if match[i](p) {
- matched[i] = true
- haveMatch = true
- }
- }
-
- // A package which only has test files can't be imported
- // as a dependency, nor can it be instrumented for coverage.
- if len(p.GoFiles)+len(p.CgoFiles) == 0 {
- continue
- }
-
- // 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")
- }
- }
+ // Select for coverage all dependencies matching the -coverpkg
+ // patterns.
+ plist := load.TestPackageList(ctx, pkgOpts, pkgs)
+ testCoverPkgs = load.SelectCoverPackages(plist, match, "test")
}
// Inform the compiler that it should instrument the binary at
// 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")
+ if cfg.BuildCover && cfg.BuildCoverMode == "atomic" {
+ load.EnsureImport(p, "sync/atomic")
}
buildTest, runTest, printTest, err := builderTest(b, ctx, pkgOpts, p, allImports[p])
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",
// ptest - package + test files
// pxtest - package of external test files
var cover *load.TestCover
- if testCover {
+ if cfg.BuildCover {
cover = &load.TestCover{
- Mode: testCoverMode,
- Local: testCover && testCoverPaths == nil,
- Pkgs: testCoverPkgs,
- Paths: testCoverPaths,
- DeclVars: declareCoverVars,
+ Mode: cfg.BuildCoverMode,
+ Local: cfg.BuildCoverPkg == nil,
+ Pkgs: testCoverPkgs,
+ Paths: cfg.BuildCoverPkg,
}
}
pmain, ptest, pxtest, err := load.TestPackagesFor(ctx, pkgOpts, p, cover)
}
}
-// 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")
var noFuzzTestsToFuzz = []byte("\ntesting: warning: no fuzz tests to fuzz\n")
var tooManyFuzzTestsToFuzz = []byte("\ntesting: warning: -fuzz matches more than one fuzz test, won't fuzz\n")
fuzzCacheDir := filepath.Join(cache.Default().FuzzDir(), a.Package.ImportPath)
fuzzArg = []string{"-test.fuzzcachedir=" + fuzzCacheDir}
}
- args := str.StringList(execCmd, a.Deps[0].BuiltTarget(), testlogArg, panicArg, fuzzArg, testArgs)
+ coverdirArg := []string{}
+ if cfg.BuildCover {
+ gcd := filepath.Join(a.Objdir, "gocoverdir")
+ if err := b.Mkdir(gcd); err != nil {
+ // If we can't create a temp dir, terminate immediately
+ // with an error as opposed to returning an error to the
+ // caller; failed MkDir most likely indicates that we're
+ // out of disk space or there is some other systemic error
+ // that will make forward progress unlikely.
+ base.Fatalf("failed to create temporary dir: %v", err)
+ }
+ coverdirArg = append(coverdirArg, "-test.gocoverdir="+gcd)
+ }
+ args := str.StringList(execCmd, a.Deps[0].BuiltTarget(), testlogArg, panicArg, fuzzArg, coverdirArg, testArgs)
if testCoverProfile != "" {
// Write coverage to temporary profile, for merging later.
// 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 {
+ if !cfg.BuildCover {
return ""
}
// The string looks like
cf.BoolVar(&testC, "c", false, "")
cf.BoolVar(&cfg.BuildI, "i", false, "")
cf.StringVar(&testO, "o", "", "")
-
- cf.BoolVar(&testCover, "cover", false, "")
- cf.Var(coverFlag{(*coverModeFlag)(&testCoverMode)}, "covermode", "")
- cf.Var(coverFlag{commaListFlag{&testCoverPaths}}, "coverpkg", "")
-
+ work.AddCoverFlags(CmdTest, &testCoverProfile)
cf.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "")
cf.BoolVar(&testJSON, "json", false, "")
cf.Var(&testVet, "vet", "")
cf.StringVar(&testBlockProfile, "blockprofile", "", "")
cf.String("blockprofilerate", "", "")
cf.Int("count", 0, "")
- cf.Var(coverFlag{stringFlag{&testCoverProfile}}, "coverprofile", "")
cf.String("cpu", "", "")
cf.StringVar(&testCPUProfile, "cpuprofile", "", "")
cf.Bool("failfast", false, "")
}
}
-// A coverFlag is a flag.Value that also implies -cover.
-type coverFlag struct{ v flag.Value }
-
-func (f coverFlag) String() string { return f.v.String() }
-
-func (f coverFlag) Set(value string) error {
- if err := f.v.Set(value); err != nil {
- return err
- }
- testCover = true
- return nil
-}
-
-type coverModeFlag string
-
-func (f *coverModeFlag) String() string { return string(*f) }
-func (f *coverModeFlag) Set(value string) error {
- switch value {
- case "", "set", "count", "atomic":
- *f = coverModeFlag(value)
- return nil
- default:
- return errors.New(`valid modes are "set", "count", or "atomic"`)
- }
-}
-
-// A commaListFlag is a flag.Value representing a comma-separated list.
-type commaListFlag struct{ vals *[]string }
-
-func (f commaListFlag) String() string { return strings.Join(*f.vals, ",") }
-
-func (f commaListFlag) Set(value string) error {
- if value == "" {
- *f.vals = nil
- } else {
- *f.vals = strings.Split(value, ",")
- }
- return nil
-}
-
-// A stringFlag is a flag.Value representing a single string.
-type stringFlag struct{ val *string }
-
-func (f stringFlag) String() string { return *f.val }
-func (f stringFlag) Set(value string) error {
- *f.val = value
- return nil
-}
-
// outputdirFlag implements the -outputdir flag.
// It interprets an empty value as the working directory of the 'go' command.
type outputdirFlag struct {
}
}
- // Ensure that -race and -covermode are compatible.
- if testCoverMode == "" {
- testCoverMode = "set"
- if cfg.BuildRace {
- // Default coverage mode is atomic when -race is set.
- testCoverMode = "atomic"
- }
- }
- if cfg.BuildRace && testCoverMode != "atomic" {
- base.Fatalf(`-covermode must be "atomic", not %q, when -race is enabled`, testCoverMode)
- }
-
// Forward any unparsed arguments (following --args) to the test binary.
return packageNames, append(injectedFlags, explicitArgs...)
}
import (
"context"
"errors"
+ "flag"
"fmt"
"go/build"
"os"
Supported only on linux/arm64, linux/amd64.
Supported only on linux/amd64 or linux/arm64 and only with GCC 7 and higher
or Clang/LLVM 9 and higher.
+ -cover
+ enable code coverage instrumentation (requires
+ that GOEXPERIMENT=coverageredesign be set).
+ -coverpkg pattern1,pattern2,pattern3
+ For a build that targets package 'main' (e.g. building a Go
+ executable), apply coverage analysis to each package matching
+ the patterns. The default is to apply coverage analysis to
+ packages in the main Go module. See 'go help packages' for a
+ description of package patterns. Sets -cover.
-v
print the names of packages as they are compiled.
-work
AddBuildFlags(CmdBuild, DefaultBuildFlags)
AddBuildFlags(CmdInstall, DefaultBuildFlags)
+ if cfg.Experiment != nil && cfg.Experiment.CoverageRedesign {
+ AddCoverFlags(CmdBuild, nil)
+ AddCoverFlags(CmdInstall, nil)
+ }
}
// Note that flags consulted by other parts of the code
cmd.Flag.StringVar(&cfg.DebugTrace, "debug-trace", "", "")
}
+// AddCoverFlags adds coverage-related flags to "cmd". If the
+// CoverageRedesign experiment is enabled, we add -cover{mode,pkg} to
+// the build command and only -coverprofile to the test command. If
+// the CoverageRedesign experiment is disabled, -cover* flags are
+// added only to the test command.
+func AddCoverFlags(cmd *base.Command, coverProfileFlag *string) {
+ addCover := false
+ if cfg.Experiment != nil && cfg.Experiment.CoverageRedesign {
+ // New coverage enabled: both build and test commands get
+ // coverage flags.
+ addCover = true
+ } else {
+ // New coverage disabled: only test command gets cover flags.
+ addCover = coverProfileFlag != nil
+ }
+ if addCover {
+ cmd.Flag.BoolVar(&cfg.BuildCover, "cover", false, "")
+ cmd.Flag.Var(coverFlag{(*coverModeFlag)(&cfg.BuildCoverMode)}, "covermode", "")
+ cmd.Flag.Var(coverFlag{commaListFlag{&cfg.BuildCoverPkg}}, "coverpkg", "")
+ }
+ if coverProfileFlag != nil {
+ cmd.Flag.Var(coverFlag{V: stringFlag{coverProfileFlag}}, "coverprofile", "")
+ }
+}
+
// tagsFlag is the implementation of the -tags flag.
type tagsFlag []string
cfg.BuildO = ""
}
+ if cfg.Experiment.CoverageRedesign && cfg.BuildCover {
+ load.PrepareForCoverageBuild(pkgs)
+ }
+
if cfg.BuildO != "" {
// If the -o name exists and is a directory or
// ends with a slash or backslash, then
}
}
+ if cfg.Experiment.CoverageRedesign && cfg.BuildCover {
+ load.PrepareForCoverageBuild(pkgs)
+ }
+
InstallPackages(ctx, args, pkgs)
}
}
return ExecCmd
}
+
+// A coverFlag is a flag.Value that also implies -cover.
+type coverFlag struct{ V flag.Value }
+
+func (f coverFlag) String() string { return f.V.String() }
+
+func (f coverFlag) Set(value string) error {
+ if err := f.V.Set(value); err != nil {
+ return err
+ }
+ cfg.BuildCover = true
+ return nil
+}
+
+type coverModeFlag string
+
+func (f *coverModeFlag) String() string { return string(*f) }
+func (f *coverModeFlag) Set(value string) error {
+ switch value {
+ case "", "set", "count", "atomic":
+ *f = coverModeFlag(value)
+ cfg.BuildCoverMode = value
+ return nil
+ default:
+ return errors.New(`valid modes are "set", "count", or "atomic"`)
+ }
+}
+
+// A commaListFlag is a flag.Value representing a comma-separated list.
+type commaListFlag struct{ Vals *[]string }
+
+func (f commaListFlag) String() string { return strings.Join(*f.Vals, ",") }
+
+func (f commaListFlag) Set(value string) error {
+ if value == "" {
+ *f.Vals = nil
+ } else {
+ *f.Vals = strings.Split(value, ",")
+ }
+ return nil
+}
+
+// A stringFlag is a flag.Value representing a single string.
+type stringFlag struct{ val *string }
+
+func (f stringFlag) String() string { return *f.val }
+func (f stringFlag) Set(value string) error {
+ *f.val = value
+ return nil
+}
import (
"bytes"
"context"
+ "crypto/sha256"
"encoding/json"
"errors"
"fmt"
+ "internal/coverage"
"internal/lazyregexp"
"io"
"io/fs"
// If we're doing coverage, preprocess the .go files and put them in the work directory
if p.Internal.CoverMode != "" {
+ outfiles := []string{}
+ infiles := []string{}
for i, file := range str.StringList(gofiles, cgofiles) {
+ if base.IsTestFile(file) {
+ continue // Not covering this file.
+ }
+
var sourceFile string
var coverFile string
var key string
key = file
}
coverFile = strings.TrimSuffix(coverFile, ".go") + ".cover.go"
- cover := p.Internal.CoverVars[key]
- if cover == nil || base.IsTestFile(file) {
- // Not covering this file.
- continue
- }
- if err := b.cover(a, coverFile, sourceFile, cover.Var); err != nil {
- return err
+ if cfg.Experiment.CoverageRedesign {
+ infiles = append(infiles, sourceFile)
+ outfiles = append(outfiles, coverFile)
+ } else {
+ cover := p.Internal.CoverVars[key]
+ if cover == nil {
+ continue // Not covering this file.
+ }
+ if err := b.cover(a, coverFile, sourceFile, cover.Var); err != nil {
+ return err
+ }
}
if i < len(gofiles) {
gofiles[i] = coverFile
cgofiles[i-len(gofiles)] = coverFile
}
}
+
+ if cfg.Experiment.CoverageRedesign {
+ if len(infiles) != 0 {
+ // Coverage instrumentation creates new top level
+ // variables in the target package for things like
+ // meta-data containers, counter vars, etc. To avoid
+ // collisions with user variables, suffix the var name
+ // with 12 hex digits from the SHA-256 hash of the
+ // import path. Choice of 12 digits is historical/arbitrary,
+ // we just need enough of the hash to avoid accidents,
+ // as opposed to precluding determined attempts by
+ // users to break things.
+ sum := sha256.Sum256([]byte(a.Package.ImportPath))
+ coverVar := fmt.Sprintf("goCover_%x_", sum[:6])
+ mode := a.Package.Internal.CoverMode
+ if mode == "" {
+ panic("covermode should be set at this point")
+ }
+ pkgcfg := a.Objdir + "pkgcfg.txt"
+ if err := b.cover2(a, pkgcfg, infiles, outfiles, coverVar, mode); err != nil {
+ return err
+ }
+ } else {
+ // If there are no input files passed to cmd/cover,
+ // then we don't want to pass -covercfg when building
+ // the package with the compiler, so set covermode to
+ // the empty string so as to signal that we need to do
+ // that.
+ p.Internal.CoverMode = ""
+ }
+ }
}
// Run cgo.
src)
}
+// cover2 runs, in effect,
+//
+// go tool cover -pkgcfg=<config file> -mode=b.coverMode -var="varName" -o <outfiles> <infiles>
+func (b *Builder) cover2(a *Action, pkgcfg string, infiles, outfiles []string, varName string, mode string) error {
+ if err := b.writeCoverPkgCfg(a, pkgcfg); err != nil {
+ return err
+ }
+ args := []string{base.Tool("cover"),
+ "-pkgcfg", pkgcfg,
+ "-mode", mode,
+ "-var", varName,
+ "-o", strings.Join(outfiles, string(os.PathListSeparator)),
+ }
+ args = append(args, infiles...)
+ return b.run(a, a.Objdir, "cover "+a.Package.ImportPath, nil,
+ cfg.BuildToolexec, args)
+}
+
+func (b *Builder) writeCoverPkgCfg(a *Action, file string) error {
+ p := a.Package
+ p.Internal.CoverageCfg = a.Objdir + "coveragecfg"
+ pcfg := coverage.CoverPkgConfig{
+ PkgPath: p.ImportPath,
+ PkgName: p.Name,
+ // Note: coverage granularity is currently hard-wired to
+ // 'perblock'; there isn't a way using "go build -cover" or "go
+ // test -cover" to select it. This may change in the future
+ // depending on user demand.
+ Granularity: "perblock",
+ OutConfig: p.Internal.CoverageCfg,
+ }
+ if a.Package.Module != nil {
+ pcfg.ModulePath = a.Package.Module.Path
+ }
+ data, err := json.Marshal(pcfg)
+ if err != nil {
+ return err
+ }
+ return b.writeFile(file, data)
+}
+
var objectMagic = [][]byte{
{'!', '<', 'a', 'r', 'c', 'h', '>', '\n'}, // Package archive
{'<', 'b', 'i', 'g', 'a', 'f', '>', '\n'}, // Package AIX big archive
if strings.HasPrefix(RuntimeVersion, "go1") && !strings.Contains(os.Args[0], "go_bootstrap") {
defaultGcFlags = append(defaultGcFlags, "-goversion", RuntimeVersion)
}
+ if p.Internal.CoverageCfg != "" {
+ defaultGcFlags = append(defaultGcFlags, "-coveragecfg="+p.Internal.CoverageCfg)
+ }
if symabis != "" {
defaultGcFlags = append(defaultGcFlags, "-symabis", symabis)
}
base.Fatalf("go: %s environment variable is relative; must be absolute path: %s\n", key, path)
}
}
+
+ // Set covermode if not already set.
+ // Ensure that -race and -covermode are compatible.
+ if cfg.BuildCoverMode == "" {
+ cfg.BuildCoverMode = "set"
+ if cfg.BuildRace {
+ // Default coverage mode is atomic when -race is set.
+ cfg.BuildCoverMode = "atomic"
+ }
+ }
+ if cfg.BuildRace && cfg.BuildCoverMode != "atomic" {
+ base.Fatalf(`-covermode must be "atomic", not %q, when -race is enabled`, cfg.BuildCoverMode)
+ }
}
// fuzzInstrumentFlags returns compiler flags that enable fuzzing instrumation
"GOARCH=" + runtime.GOARCH,
"TESTGO_GOHOSTARCH=" + goHostArch,
"GOCACHE=" + testGOCACHE,
+ "GOCOVERDIR=" + os.Getenv("GOCOVERDIR"),
"GODEBUG=" + os.Getenv("GODEBUG"),
"GOEXE=" + cfg.ExeSuffix,
"GOEXPERIMENT=" + os.Getenv("GOEXPERIMENT"),
PATH=<actual PATH>
TMPDIR=$WORK/tmp
GODEBUG=<actual GODEBUG>
+ GOCOVERDIR=<current setting of GOCOVERDIR>
devnull=<value of os.DevNull>
goversion=<current Go version; for example, 1.12>
--- /dev/null
+# This test checks more of the "go build -cover" functionality,
+# specifically which packages get selected when building.
+
+[short] skip
+
+# Skip if new coverage is not enabled.
+[!GOEXPERIMENT:coverageredesign] skip
+
+#-------------------------------------------
+
+# Build for coverage.
+go build -mod=mod -o $WORK/modex.exe -cover mod.example/main
+
+# Save off old GOCOVERDIR setting
+env SAVEGOCOVERDIR=$GOCOVERDIR
+
+# Execute.
+mkdir $WORK/covdata
+env GOCOVERDIR=$WORK/covdata
+exec $WORK/modex.exe
+
+# Restore previous GOCOVERDIR setting
+env GOCOVERDIR=$SAVEGOCOVERDIR
+
+# Examine the result.
+go tool covdata percent -i=$WORK/covdata
+stdout 'coverage: 100.0% of statements'
+
+# By default we want to see packages resident in the module covered,
+# but not dependencies.
+go tool covdata textfmt -i=$WORK/covdata -o=$WORK/covdata/out.txt
+grep 'mode: set' $WORK/covdata/out.txt
+grep 'mod.example/main/main.go:' $WORK/covdata/out.txt
+grep 'mod.example/sub/sub.go:' $WORK/covdata/out.txt
+! grep 'rsc.io' $WORK/covdata/out.txt
+
+rm $WORK/covdata
+rm $WORK/modex.exe
+
+#-------------------------------------------
+
+# Repeat the build but with -coverpkg=all
+
+go build -mod=mod -coverpkg=all -o $WORK/modex.exe -cover mod.example/main
+
+# Execute.
+mkdir $WORK/covdata
+env GOCOVERDIR=$WORK/covdata
+exec $WORK/modex.exe
+
+# Restore previous GOCOVERDIR setting
+env GOCOVERDIR=$SAVEGOCOVERDIR
+
+# Examine the result.
+go tool covdata percent -i=$WORK/covdata
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# The whole enchilada.
+go tool covdata textfmt -i=$WORK/covdata -o=$WORK/covdata/out.txt
+grep 'mode: set' $WORK/covdata/out.txt
+grep 'mod.example/main/main.go:' $WORK/covdata/out.txt
+grep 'mod.example/sub/sub.go:' $WORK/covdata/out.txt
+grep 'rsc.io' $WORK/covdata/out.txt
+grep 'bufio/bufio.go:' $WORK/covdata/out.txt
+
+# Use the covdata tool to select a specific set of module paths
+mkdir $WORK/covdata2
+go tool covdata merge -pkg=rsc.io/quote -i=$WORK/covdata -o=$WORK/covdata2
+
+# Examine the result.
+go tool covdata percent -i=$WORK/covdata2
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# Check for expected packages + check that we don't see things from stdlib.
+go tool covdata textfmt -i=$WORK/covdata2 -o=$WORK/covdata2/out.txt
+grep 'mode: set' $WORK/covdata2/out.txt
+! grep 'mod.example/main/main.go:' $WORK/covdata2/out.txt
+! grep 'mod.example/sub/sub.go:' $WORK/covdata2/out.txt
+grep 'rsc.io' $WORK/covdata2/out.txt
+! grep 'bufio/bufio.go:' $WORK/covdata2/out.txt
+
+#-------------------------------------------
+# end of test cmds, start of harness and related files.
+
+-- go.mod --
+module mod.example
+
+go 1.20
+
+require rsc.io/quote/v3 v3.0.0
+
+-- main/main.go --
+package main
+
+import (
+ "fmt"
+ "mod.example/sub"
+
+ "rsc.io/quote"
+)
+
+func main() {
+ fmt.Println(quote.Go(), sub.F())
+}
+
+-- sub/sub.go --
+
+package sub
+
+func F() int {
+ return 42
+}
+
+
--- /dev/null
+# This test checks basic "go build -cover" functionality.
+
+[short] skip
+
+# Hard-wire new coverage for this test.
+env GOEXPERIMENT=coverageredesign
+
+# Build for coverage.
+go build -gcflags=-m -o example.exe -cover example/main &
+[race] go build -o examplewithrace.exe -race -cover example/main &
+wait
+
+# First execute without GOCOVERDIR set...
+env GOCOVERDIR=
+exec ./example.exe normal
+stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+
+# ... then with GOCOVERDIR set.
+env GOCOVERDIR=data/normal
+exec ./example.exe normal
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data/normal
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# Program makes a direct call to os.Exit(0).
+env GOCOVERDIR=data/goodexit
+exec ./example.exe goodexit
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data/goodexit
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# Program makes a direct call to os.Exit(1).
+env GOCOVERDIR=data/badexit
+! exec ./example.exe badexit
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data/badexit
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# Program invokes panic.
+env GOCOVERDIR=data/panic
+! exec ./example.exe panic
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data/panic
+stdout 'coverage:.*[0-9.]+%'
+
+# Skip remainder if no race detector support.
+[!race] skip
+
+env GOCOVERDIR=data2/normal
+exec ./examplewithrace.exe normal
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data2/normal
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# Program makes a direct call to os.Exit(0).
+env GOCOVERDIR=data2/goodexit
+exec ./examplewithrace.exe goodexit
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data2/goodexit
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# Program makes a direct call to os.Exit(1).
+env GOCOVERDIR=data2/badexit
+! exec ./examplewithrace.exe badexit
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data2/badexit
+stdout 'coverage:.*[1-9][0-9.]+%'
+
+# Program invokes panic.
+env GOCOVERDIR=data2/panic
+! exec ./examplewithrace.exe panic
+! stderr '^warning: GOCOVERDIR not set, no coverage data emitted'
+go tool covdata percent -i=data2/panic
+stdout 'coverage:.*[0-9.]+%'
+
+# end of test cmds, start of harness and related files.
+
+-- go.mod --
+module example
+
+go 1.18
+
+-- main/example.go --
+package main
+
+import "example/sub"
+
+func main() {
+ sub.S()
+}
+
+-- sub/sub.go --
+
+package sub
+
+import "os"
+
+func S() {
+ switch os.Args[1] {
+ case "normal":
+ println("hi")
+ case "goodexit":
+ os.Exit(0)
+ case "badexit":
+ os.Exit(1)
+ case "panic":
+ panic("something bad happened")
+ }
+}
+
+-- data/README.txt --
+
+Just a location where we can write coverage profiles.
+
+-- data/normal/f.txt --
+
+X
+
+-- data/goodexit/f.txt --
+
+X
+
+-- data/badexit/f.txt --
+
+X
+
+-- data/panic/f.txt --
+
+X
+
+-- data2/README.txt --
+
+Just a location where we can write coverage profiles.
+
+-- data2/normal/f.txt --
+
+X
+
+-- data2/goodexit/f.txt --
+
+X
+
+-- data2/badexit/f.txt --
+
+X
+
+-- data2/panic/f.txt --
+
+X
--- /dev/null
+
+[short] skip
+
+# Hard-wire new coverage for this test.
+env GOEXPERIMENT=coverageredesign
+
+# Baseline run.
+go test -cover example/foo
+stdout 'coverage: 50.0% of statements$'
+
+# Coverage percentage output should mention -coverpkg selection.
+go test -coverpkg=example/foo example/foo
+stdout 'coverage: 50.0% of statements in example/foo'
+
+# Try to ask for coverage of a package that doesn't exist.
+go test -coverpkg nonexistent example/bar
+stderr 'no packages being tested depend on matches for pattern nonexistent'
+stdout 'coverage: \[no statements\]'
+
+# Ask for foo coverage, but test bar.
+go test -coverpkg=example/foo example/bar
+stdout 'coverage: 50.0% of statements in example/foo'
+
+# end of test cmds, start of harness and related files.
+
+-- go.mod --
+module example
+
+go 1.18
+
+-- foo/foo.go --
+package foo
+
+func FooFunc() int {
+ return 42
+}
+func FooFunc2() int {
+ return 42
+}
+
+-- foo/foo_test.go --
+package foo
+
+import "testing"
+
+func TestFoo(t *testing.T) {
+ if FooFunc() != 42 {
+ t.Fatalf("bad")
+ }
+}
+
+-- bar/bar.go --
+package bar
+
+import "example/foo"
+
+func BarFunc() int {
+ return foo.FooFunc2()
+}
+
+-- bar/bar_test.go --
+package bar_test
+
+import (
+ "example/bar"
+ "testing"
+)
+
+func TestBar(t *testing.T) {
+ if bar.BarFunc() != 42 {
+ t.Fatalf("bad")
+ }
+}
+
+-- baz/baz.go --
+package baz
+
+func BazFunc() int {
+ return -42
+}
import (
"fmt"
+ "internal/goexperiment"
"os"
"sync/atomic"
)
// coverReport reports the coverage percentage and writes a coverage profile if requested.
func coverReport() {
+ if goexperiment.CoverageRedesign {
+ coverReport2()
+ return
+ }
var f *os.File
var err error
if *coverProfile != "" {
--- /dev/null
+// Copyright 2022 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.
+
+// Support for test coverage with redesigned coverage implementation.
+
+package testing
+
+import (
+ "fmt"
+ "internal/goexperiment"
+ "os"
+)
+
+// cover2 variable stores the current coverage mode and a
+// tear-down function to be called at the end of the testing run.
+var cover2 struct {
+ mode string
+ tearDown func(coverprofile string, gocoverdir string) (string, error)
+}
+
+// registerCover2 is invoked during "go test -cover" runs by the test harness
+// code in _testmain.go; it is used to record a 'tear down' function
+// (to be called when the test is complete) and the coverage mode.
+func registerCover2(mode string, tearDown func(coverprofile string, gocoverdir string) (string, error)) {
+ cover2.mode = mode
+ cover2.tearDown = tearDown
+}
+
+// coverReport2 invokes a callback in _testmain.go that will
+// emit coverage data at the point where test execution is complete,
+// for "go test -cover" runs.
+func coverReport2() {
+ if !goexperiment.CoverageRedesign {
+ panic("unexpected")
+ }
+ if errmsg, err := cover2.tearDown(*coverProfile, *gocoverdir); err != nil {
+ fmt.Fprintf(os.Stderr, "%s: %v\n", errmsg, err)
+ os.Exit(2)
+ }
+}
"errors"
"flag"
"fmt"
+ "internal/goexperiment"
"internal/race"
"io"
"math/rand"
chatty = flag.Bool("test.v", false, "verbose: print additional output")
count = flag.Uint("test.count", 1, "run tests and benchmarks `n` times")
coverProfile = flag.String("test.coverprofile", "", "write a coverage profile to `file`")
+ gocoverdir = flag.String("test.gocoverdir", "", "write coverage intermediate files to this directory")
matchList = flag.String("test.list", "", "list tests, examples, and benchmarks matching `regexp` then exit")
match = flag.String("test.run", "", "run only tests and examples matching `regexp`")
skip = flag.String("test.skip", "", "do not list or run tests matching `regexp`")
chatty *bool
count *uint
coverProfile *string
+ gocoverdir *string
matchList *string
match *string
skip *string
// values are "set", "count", or "atomic". The return value will be
// empty if test coverage is not enabled.
func CoverMode() string {
+ if goexperiment.CoverageRedesign {
+ return cover2.mode
+ }
return cover.Mode
}
if *mutexProfile != "" && *mutexProfileFraction >= 0 {
runtime.SetMutexProfileFraction(*mutexProfileFraction)
}
- if *coverProfile != "" && cover.Mode == "" {
+ if *coverProfile != "" && CoverMode() == "" {
fmt.Fprintf(os.Stderr, "testing: cannot use -test.coverprofile because test binary was not built with coverage enabled\n")
os.Exit(2)
}
+ if *gocoverdir != "" && CoverMode() == "" {
+ fmt.Fprintf(os.Stderr, "testing: cannot use -test.gocoverdir because test binary was not built with coverage enabled\n")
+ os.Exit(2)
+ }
if *testlog != "" {
// Note: Not using toOutputDir.
// This file is for use by cmd/go, not users.
}
f.Close()
}
- if cover.Mode != "" {
+ if CoverMode() != "" {
coverReport()
}
}