summaryrefslogtreecommitdiffstats
path: root/src/cmd/cover
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/cmd/cover
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/cmd/cover/cfg_test.go188
-rw-r--r--src/cmd/cover/cover.go1159
-rw-r--r--src/cmd/cover/cover_test.go649
-rw-r--r--src/cmd/cover/doc.go32
-rw-r--r--src/cmd/cover/export_test.go7
-rw-r--r--src/cmd/cover/func.go248
-rw-r--r--src/cmd/cover/html.go306
-rw-r--r--src/cmd/cover/pkgname_test.go31
-rw-r--r--src/cmd/cover/testdata/directives.go40
-rw-r--r--src/cmd/cover/testdata/html/html.go30
-rw-r--r--src/cmd/cover/testdata/html/html.golden18
-rw-r--r--src/cmd/cover/testdata/html/html_test.go8
-rw-r--r--src/cmd/cover/testdata/main.go116
-rw-r--r--src/cmd/cover/testdata/p.go27
-rw-r--r--src/cmd/cover/testdata/pkgcfg/a/a.go28
-rw-r--r--src/cmd/cover/testdata/pkgcfg/a/a2.go8
-rw-r--r--src/cmd/cover/testdata/pkgcfg/a/a_test.go14
-rw-r--r--src/cmd/cover/testdata/pkgcfg/b/b.go10
-rw-r--r--src/cmd/cover/testdata/pkgcfg/b/b_test.go9
-rw-r--r--src/cmd/cover/testdata/pkgcfg/main/main.go15
-rw-r--r--src/cmd/cover/testdata/profile.cov5
-rw-r--r--src/cmd/cover/testdata/test.go300
22 files changed, 3248 insertions, 0 deletions
diff --git a/src/cmd/cover/cfg_test.go b/src/cmd/cover/cfg_test.go
new file mode 100644
index 0000000..6782ec8
--- /dev/null
+++ b/src/cmd/cover/cfg_test.go
@@ -0,0 +1,188 @@
+// 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.
+
+package main_test
+
+import (
+ "encoding/json"
+ "fmt"
+ "internal/coverage"
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func writeFile(t *testing.T, path string, contents []byte) {
+ if err := os.WriteFile(path, contents, 0666); err != nil {
+ t.Fatalf("os.WriteFile(%s) failed: %v", path, err)
+ }
+}
+
+func writePkgConfig(t *testing.T, outdir, tag, ppath, pname string, gran string) string {
+ incfg := filepath.Join(outdir, tag+"incfg.txt")
+ outcfg := filepath.Join(outdir, "outcfg.txt")
+ p := coverage.CoverPkgConfig{
+ PkgPath: ppath,
+ PkgName: pname,
+ Granularity: gran,
+ OutConfig: outcfg,
+ }
+ data, err := json.Marshal(p)
+ if err != nil {
+ t.Fatalf("json.Marshal failed: %v", err)
+ }
+ writeFile(t, incfg, data)
+ return incfg
+}
+
+func writeOutFileList(t *testing.T, infiles []string, outdir, tag string) ([]string, string) {
+ outfilelist := filepath.Join(outdir, tag+"outfilelist.txt")
+ var sb strings.Builder
+ cv := filepath.Join(outdir, "covervars.go")
+ outfs := []string{cv}
+ fmt.Fprintf(&sb, "%s\n", cv)
+ for _, inf := range infiles {
+ base := filepath.Base(inf)
+ of := filepath.Join(outdir, tag+".cov."+base)
+ outfs = append(outfs, of)
+ fmt.Fprintf(&sb, "%s\n", of)
+ }
+ if err := os.WriteFile(outfilelist, []byte(sb.String()), 0666); err != nil {
+ t.Fatalf("writing %s: %v", outfilelist, err)
+ }
+ return outfs, outfilelist
+}
+
+func runPkgCover(t *testing.T, outdir string, tag string, incfg string, mode string, infiles []string, errExpected bool) ([]string, string, string) {
+ // Write the pkgcfg file.
+ outcfg := filepath.Join(outdir, "outcfg.txt")
+
+ // Form up the arguments and run the tool.
+ outfiles, outfilelist := writeOutFileList(t, infiles, outdir, tag)
+ args := []string{"-pkgcfg", incfg, "-mode=" + mode, "-var=var" + tag, "-outfilelist", outfilelist}
+ args = append(args, infiles...)
+ cmd := testenv.Command(t, testcover(t), args...)
+ if errExpected {
+ errmsg := runExpectingError(cmd, t)
+ return nil, "", errmsg
+ } else {
+ run(cmd, t)
+ return outfiles, outcfg, ""
+ }
+}
+
+// Set to true when debugging unit test (to inspect debris, etc).
+// Note that this functionality does not work on windows.
+const debugWorkDir = false
+
+func TestCoverWithCfg(t *testing.T) {
+ testenv.MustHaveGoRun(t)
+
+ t.Parallel()
+
+ // Subdir in testdata that has our input files of interest.
+ tpath := filepath.Join("testdata", "pkgcfg")
+
+ // Helper to collect input paths (go files) for a subdir in 'pkgcfg'
+ pfiles := func(subdir string) []string {
+ de, err := os.ReadDir(filepath.Join(tpath, subdir))
+ if err != nil {
+ t.Fatalf("reading subdir %s: %v", subdir, err)
+ }
+ paths := []string{}
+ for _, e := range de {
+ if !strings.HasSuffix(e.Name(), ".go") || strings.HasSuffix(e.Name(), "_test.go") {
+ continue
+ }
+ paths = append(paths, filepath.Join(tpath, subdir, e.Name()))
+ }
+ return paths
+ }
+
+ dir := tempDir(t)
+ if debugWorkDir {
+ dir = "/tmp/qqq"
+ os.RemoveAll(dir)
+ os.Mkdir(dir, 0777)
+ }
+ instdira := filepath.Join(dir, "insta")
+ if err := os.Mkdir(instdira, 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ scenarios := []struct {
+ mode, gran string
+ }{
+ {
+ mode: "count",
+ gran: "perblock",
+ },
+ {
+ mode: "set",
+ gran: "perfunc",
+ },
+ {
+ mode: "regonly",
+ gran: "perblock",
+ },
+ }
+
+ var incfg string
+ for _, scenario := range scenarios {
+ // Instrument package "a", producing a set of instrumented output
+ // files and an 'output config' file to pass on to the compiler.
+ ppath := "cfg/a"
+ pname := "a"
+ mode := scenario.mode
+ gran := scenario.gran
+ tag := mode + "_" + gran
+ incfg = writePkgConfig(t, instdira, tag, ppath, pname, gran)
+ ofs, outcfg, _ := runPkgCover(t, instdira, tag, incfg, mode,
+ pfiles("a"), false)
+ t.Logf("outfiles: %+v\n", ofs)
+
+ // Run the compiler on the files to make sure the result is
+ // buildable.
+ bargs := []string{"tool", "compile", "-p", "a", "-coveragecfg", outcfg}
+ bargs = append(bargs, ofs...)
+ cmd := testenv.Command(t, testenv.GoToolPath(t), bargs...)
+ cmd.Dir = instdira
+ run(cmd, t)
+ }
+
+ // Do some error testing to ensure that various bad options and
+ // combinations are properly rejected.
+
+ // Expect error if config file inaccessible/unreadable.
+ mode := "atomic"
+ errExpected := true
+ tag := "errors"
+ _, _, errmsg := runPkgCover(t, instdira, tag, "/not/a/file", mode,
+ pfiles("a"), errExpected)
+ want := "error reading pkgconfig file"
+ if !strings.Contains(errmsg, want) {
+ t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg)
+ }
+
+ // Expect err if config file contains unknown stuff.
+ t.Logf("mangling in config")
+ writeFile(t, incfg, []byte("blah=foo\n"))
+ _, _, errmsg = runPkgCover(t, instdira, tag, incfg, mode,
+ pfiles("a"), errExpected)
+ want = "error reading pkgconfig file"
+ if !strings.Contains(errmsg, want) {
+ t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg)
+ }
+
+ // Expect error on empty config file.
+ t.Logf("writing empty config")
+ writeFile(t, incfg, []byte("\n"))
+ _, _, errmsg = runPkgCover(t, instdira, tag, incfg, mode,
+ pfiles("a"), errExpected)
+ if !strings.Contains(errmsg, want) {
+ t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg)
+ }
+}
diff --git a/src/cmd/cover/cover.go b/src/cmd/cover/cover.go
new file mode 100644
index 0000000..a4b837c
--- /dev/null
+++ b/src/cmd/cover/cover.go
@@ -0,0 +1,1159 @@
+// Copyright 2013 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 main
+
+import (
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "internal/coverage"
+ "internal/coverage/encodemeta"
+ "internal/coverage/slicewriter"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+
+ "cmd/internal/edit"
+ "cmd/internal/objabi"
+)
+
+const usageMessage = "" +
+ `Usage of 'go tool cover':
+Given a coverage profile produced by 'go test':
+ go test -coverprofile=c.out
+
+Open a web browser displaying annotated source code:
+ go tool cover -html=c.out
+
+Write out an HTML file instead of launching a web browser:
+ go tool cover -html=c.out -o coverage.html
+
+Display coverage percentages to stdout for each function:
+ go tool cover -func=c.out
+
+Finally, to generate modified source code with coverage annotations
+for a package (what go test -cover does):
+ go tool cover -mode=set -var=CoverageVariableName \
+ -pkgcfg=<config> -outfilelist=<file> file1.go ... fileN.go
+
+where -pkgcfg points to a file containing the package path,
+package name, module path, and related info from "go build",
+and -outfilelist points to a file containing the filenames
+of the instrumented output files (one per input file).
+See https://pkg.go.dev/internal/coverage#CoverPkgConfig for
+more on the package config.
+`
+
+func usage() {
+ fmt.Fprint(os.Stderr, usageMessage)
+ fmt.Fprintln(os.Stderr, "\nFlags:")
+ flag.PrintDefaults()
+ fmt.Fprintln(os.Stderr, "\n Only one of -html, -func, or -mode may be set.")
+ os.Exit(2)
+}
+
+var (
+ mode = flag.String("mode", "", "coverage mode: set, count, atomic")
+ varVar = flag.String("var", "GoCover", "name of coverage variable to generate")
+ output = flag.String("o", "", "file for output")
+ outfilelist = flag.String("outfilelist", "", "file containing list of output files (one per line) if -pkgcfg is in use")
+ htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
+ funcOut = flag.String("func", "", "output coverage profile information for each function")
+ pkgcfg = flag.String("pkgcfg", "", "enable full-package instrumentation mode using params from specified config file")
+)
+
+var pkgconfig coverage.CoverPkgConfig
+
+// outputfiles is the list of *.cover.go instrumented outputs to write,
+// one per input (set when -pkgcfg is in use)
+var outputfiles []string
+
+// covervarsoutfile is an additional Go source file into which we'll
+// write definitions of coverage counter variables + meta data variables
+// (set when -pkgcfg is in use).
+var covervarsoutfile string
+
+var profile string // The profile to read; the value of -html or -func
+
+var counterStmt func(*File, string) string
+
+const (
+ atomicPackagePath = "sync/atomic"
+ atomicPackageName = "_cover_atomic_"
+)
+
+func main() {
+ objabi.AddVersionFlag()
+ flag.Usage = usage
+ objabi.Flagparse(usage)
+
+ // Usage information when no arguments.
+ if flag.NFlag() == 0 && flag.NArg() == 0 {
+ flag.Usage()
+ }
+
+ err := parseFlags()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`)
+ os.Exit(2)
+ }
+
+ // Generate coverage-annotated source.
+ if *mode != "" {
+ annotate(flag.Args())
+ return
+ }
+
+ // Output HTML or function coverage information.
+ if *htmlOut != "" {
+ err = htmlOutput(profile, *output)
+ } else {
+ err = funcOutput(profile, *output)
+ }
+
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cover: %v\n", err)
+ os.Exit(2)
+ }
+}
+
+// parseFlags sets the profile and counterStmt globals and performs validations.
+func parseFlags() error {
+ profile = *htmlOut
+ if *funcOut != "" {
+ if profile != "" {
+ return fmt.Errorf("too many options")
+ }
+ profile = *funcOut
+ }
+
+ // Must either display a profile or rewrite Go source.
+ if (profile == "") == (*mode == "") {
+ return fmt.Errorf("too many options")
+ }
+
+ if *varVar != "" && !token.IsIdentifier(*varVar) {
+ return fmt.Errorf("-var: %q is not a valid identifier", *varVar)
+ }
+
+ if *mode != "" {
+ switch *mode {
+ case "set":
+ counterStmt = setCounterStmt
+ case "count":
+ counterStmt = incCounterStmt
+ case "atomic":
+ counterStmt = atomicCounterStmt
+ case "regonly", "testmain":
+ counterStmt = nil
+ default:
+ return fmt.Errorf("unknown -mode %v", *mode)
+ }
+
+ if flag.NArg() == 0 {
+ return fmt.Errorf("missing source file(s)")
+ } else {
+ if *pkgcfg != "" {
+ if *output != "" {
+ return fmt.Errorf("please use '-outfilelist' flag instead of '-o'")
+ }
+ var err error
+ if outputfiles, err = readOutFileList(*outfilelist); err != nil {
+ return err
+ }
+ covervarsoutfile = outputfiles[0]
+ outputfiles = outputfiles[1:]
+ numInputs := len(flag.Args())
+ numOutputs := len(outputfiles)
+ if numOutputs != numInputs {
+ return fmt.Errorf("number of output files (%d) not equal to number of input files (%d)", numOutputs, numInputs)
+ }
+ if err := readPackageConfig(*pkgcfg); err != nil {
+ return err
+ }
+ return nil
+ } else {
+ if *outfilelist != "" {
+ return fmt.Errorf("'-outfilelist' flag applicable only when -pkgcfg used")
+ }
+ }
+ if flag.NArg() == 1 {
+ return nil
+ }
+ }
+ } else if flag.NArg() == 0 {
+ return nil
+ }
+ return fmt.Errorf("too many arguments")
+}
+
+func readOutFileList(path string) ([]string, error) {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("error reading -outfilelist file %q: %v", path, err)
+ }
+ return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
+}
+
+func readPackageConfig(path string) error {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return fmt.Errorf("error reading pkgconfig file %q: %v", path, err)
+ }
+ if err := json.Unmarshal(data, &pkgconfig); err != nil {
+ return fmt.Errorf("error reading pkgconfig file %q: %v", path, err)
+ }
+ if pkgconfig.Granularity != "perblock" && pkgconfig.Granularity != "perfunc" {
+ return fmt.Errorf(`%s: pkgconfig requires perblock/perfunc value`, path)
+ }
+ return nil
+}
+
+// Block represents the information about a basic block to be recorded in the analysis.
+// Note: Our definition of basic block is based on control structures; we don't break
+// apart && and ||. We could but it doesn't seem important enough to bother.
+type Block struct {
+ startByte token.Pos
+ endByte token.Pos
+ numStmt int
+}
+
+// Package holds package-specific state.
+type Package struct {
+ mdb *encodemeta.CoverageMetaDataBuilder
+ counterLengths []int
+}
+
+// Function holds func-specific state.
+type Func struct {
+ units []coverage.CoverableUnit
+ counterVar string
+}
+
+// File is a wrapper for the state of a file used in the parser.
+// The basic parse tree walker is a method of this type.
+type File struct {
+ fset *token.FileSet
+ name string // Name of file.
+ astFile *ast.File
+ blocks []Block
+ content []byte
+ edit *edit.Buffer
+ mdb *encodemeta.CoverageMetaDataBuilder
+ fn Func
+ pkg *Package
+}
+
+// findText finds text in the original source, starting at pos.
+// It correctly skips over comments and assumes it need not
+// handle quoted strings.
+// It returns a byte offset within f.src.
+func (f *File) findText(pos token.Pos, text string) int {
+ b := []byte(text)
+ start := f.offset(pos)
+ i := start
+ s := f.content
+ for i < len(s) {
+ if bytes.HasPrefix(s[i:], b) {
+ return i
+ }
+ if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {
+ for i < len(s) && s[i] != '\n' {
+ i++
+ }
+ continue
+ }
+ if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {
+ for i += 2; ; i++ {
+ if i+2 > len(s) {
+ return 0
+ }
+ if s[i] == '*' && s[i+1] == '/' {
+ i += 2
+ break
+ }
+ }
+ continue
+ }
+ i++
+ }
+ return -1
+}
+
+// Visit implements the ast.Visitor interface.
+func (f *File) Visit(node ast.Node) ast.Visitor {
+ switch n := node.(type) {
+ case *ast.BlockStmt:
+ // If it's a switch or select, the body is a list of case clauses; don't tag the block itself.
+ if len(n.List) > 0 {
+ switch n.List[0].(type) {
+ case *ast.CaseClause: // switch
+ for _, n := range n.List {
+ clause := n.(*ast.CaseClause)
+ f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
+ }
+ return f
+ case *ast.CommClause: // select
+ for _, n := range n.List {
+ clause := n.(*ast.CommClause)
+ f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
+ }
+ return f
+ }
+ }
+ f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true) // +1 to step past closing brace.
+ case *ast.IfStmt:
+ if n.Init != nil {
+ ast.Walk(f, n.Init)
+ }
+ ast.Walk(f, n.Cond)
+ ast.Walk(f, n.Body)
+ if n.Else == nil {
+ return nil
+ }
+ // The elses are special, because if we have
+ // if x {
+ // } else if y {
+ // }
+ // we want to cover the "if y". To do this, we need a place to drop the counter,
+ // so we add a hidden block:
+ // if x {
+ // } else {
+ // if y {
+ // }
+ // }
+ elseOffset := f.findText(n.Body.End(), "else")
+ if elseOffset < 0 {
+ panic("lost else")
+ }
+ f.edit.Insert(elseOffset+4, "{")
+ f.edit.Insert(f.offset(n.Else.End()), "}")
+
+ // We just created a block, now walk it.
+ // Adjust the position of the new block to start after
+ // the "else". That will cause it to follow the "{"
+ // we inserted above.
+ pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)
+ switch stmt := n.Else.(type) {
+ case *ast.IfStmt:
+ block := &ast.BlockStmt{
+ Lbrace: pos,
+ List: []ast.Stmt{stmt},
+ Rbrace: stmt.End(),
+ }
+ n.Else = block
+ case *ast.BlockStmt:
+ stmt.Lbrace = pos
+ default:
+ panic("unexpected node type in if")
+ }
+ ast.Walk(f, n.Else)
+ return nil
+ case *ast.SelectStmt:
+ // Don't annotate an empty select - creates a syntax error.
+ if n.Body == nil || len(n.Body.List) == 0 {
+ return nil
+ }
+ case *ast.SwitchStmt:
+ // Don't annotate an empty switch - creates a syntax error.
+ if n.Body == nil || len(n.Body.List) == 0 {
+ if n.Init != nil {
+ ast.Walk(f, n.Init)
+ }
+ if n.Tag != nil {
+ ast.Walk(f, n.Tag)
+ }
+ return nil
+ }
+ case *ast.TypeSwitchStmt:
+ // Don't annotate an empty type switch - creates a syntax error.
+ if n.Body == nil || len(n.Body.List) == 0 {
+ if n.Init != nil {
+ ast.Walk(f, n.Init)
+ }
+ ast.Walk(f, n.Assign)
+ return nil
+ }
+ case *ast.FuncDecl:
+ // Don't annotate functions with blank names - they cannot be executed.
+ // Similarly for bodyless funcs.
+ if n.Name.Name == "_" || n.Body == nil {
+ return nil
+ }
+ fname := n.Name.Name
+ // Skip AddUint32 and StoreUint32 if we're instrumenting
+ // sync/atomic itself in atomic mode (out of an abundance of
+ // caution), since as part of the instrumentation process we
+ // add calls to AddUint32/StoreUint32, and we don't want to
+ // somehow create an infinite loop.
+ //
+ // Note that in the current implementation (Go 1.20) both
+ // routines are assembly stubs that forward calls to the
+ // runtime/internal/atomic equivalents, hence the infinite
+ // loop scenario is purely theoretical (maybe if in some
+ // future implementation one of these functions might be
+ // written in Go). See #57445 for more details.
+ if atomicOnAtomic() && (fname == "AddUint32" || fname == "StoreUint32") {
+ return nil
+ }
+ // Determine proper function or method name.
+ if r := n.Recv; r != nil && len(r.List) == 1 {
+ t := r.List[0].Type
+ star := ""
+ if p, _ := t.(*ast.StarExpr); p != nil {
+ t = p.X
+ star = "*"
+ }
+ if p, _ := t.(*ast.Ident); p != nil {
+ fname = star + p.Name + "." + fname
+ }
+ }
+ walkBody := true
+ if *pkgcfg != "" {
+ f.preFunc(n, fname)
+ if pkgconfig.Granularity == "perfunc" {
+ walkBody = false
+ }
+ }
+ if walkBody {
+ ast.Walk(f, n.Body)
+ }
+ if *pkgcfg != "" {
+ flit := false
+ f.postFunc(n, fname, flit, n.Body)
+ }
+ return nil
+ case *ast.FuncLit:
+ // For function literals enclosed in functions, just glom the
+ // code for the literal in with the enclosing function (for now).
+ if f.fn.counterVar != "" {
+ return f
+ }
+
+ // Hack: function literals aren't named in the go/ast representation,
+ // and we don't know what name the compiler will choose. For now,
+ // just make up a descriptive name.
+ pos := n.Pos()
+ p := f.fset.File(pos).Position(pos)
+ fname := fmt.Sprintf("func.L%d.C%d", p.Line, p.Column)
+ if *pkgcfg != "" {
+ f.preFunc(n, fname)
+ }
+ if pkgconfig.Granularity != "perfunc" {
+ ast.Walk(f, n.Body)
+ }
+ if *pkgcfg != "" {
+ flit := true
+ f.postFunc(n, fname, flit, n.Body)
+ }
+ return nil
+ }
+ return f
+}
+
+func mkCounterVarName(idx int) string {
+ return fmt.Sprintf("%s_%d", *varVar, idx)
+}
+
+func mkPackageIdVar() string {
+ return *varVar + "P"
+}
+
+func mkMetaVar() string {
+ return *varVar + "M"
+}
+
+func mkPackageIdExpression() string {
+ ppath := pkgconfig.PkgPath
+ if hcid := coverage.HardCodedPkgID(ppath); hcid != -1 {
+ return fmt.Sprintf("uint32(%d)", uint32(hcid))
+ }
+ return mkPackageIdVar()
+}
+
+func (f *File) preFunc(fn ast.Node, fname string) {
+ f.fn.units = f.fn.units[:0]
+
+ // create a new counter variable for this function.
+ cv := mkCounterVarName(len(f.pkg.counterLengths))
+ f.fn.counterVar = cv
+}
+
+func (f *File) postFunc(fn ast.Node, funcname string, flit bool, body *ast.BlockStmt) {
+
+ // Tack on single counter write if we are in "perfunc" mode.
+ singleCtr := ""
+ if pkgconfig.Granularity == "perfunc" {
+ singleCtr = "; " + f.newCounter(fn.Pos(), fn.Pos(), 1)
+ }
+
+ // record the length of the counter var required.
+ nc := len(f.fn.units) + coverage.FirstCtrOffset
+ f.pkg.counterLengths = append(f.pkg.counterLengths, nc)
+
+ // FIXME: for windows, do we want "\" and not "/"? Need to test here.
+ // Currently filename is formed as packagepath + "/" + basename.
+ fnpos := f.fset.Position(fn.Pos())
+ ppath := pkgconfig.PkgPath
+ filename := ppath + "/" + filepath.Base(fnpos.Filename)
+
+ // The convention for cmd/cover is that if the go command that
+ // kicks off coverage specifies a local import path (e.g. "go test
+ // -cover ./thispackage"), the tool will capture full pathnames
+ // for source files instead of relative paths, which tend to work
+ // more smoothly for "go tool cover -html". See also issue #56433
+ // for more details.
+ if pkgconfig.Local {
+ filename = f.name
+ }
+
+ // Hand off function to meta-data builder.
+ fd := coverage.FuncDesc{
+ Funcname: funcname,
+ Srcfile: filename,
+ Units: f.fn.units,
+ Lit: flit,
+ }
+ funcId := f.mdb.AddFunc(fd)
+
+ hookWrite := func(cv string, which int, val string) string {
+ return fmt.Sprintf("%s[%d] = %s", cv, which, val)
+ }
+ if *mode == "atomic" {
+ hookWrite = func(cv string, which int, val string) string {
+ return fmt.Sprintf("%sStoreUint32(&%s[%d], %s)",
+ atomicPackagePrefix(), cv, which, val)
+ }
+ }
+
+ // Generate the registration hook sequence for the function. This
+ // sequence looks like
+ //
+ // counterVar[0] = <num_units>
+ // counterVar[1] = pkgId
+ // counterVar[2] = fnId
+ //
+ cv := f.fn.counterVar
+ regHook := hookWrite(cv, 0, strconv.Itoa(len(f.fn.units))) + " ; " +
+ hookWrite(cv, 1, mkPackageIdExpression()) + " ; " +
+ hookWrite(cv, 2, strconv.Itoa(int(funcId))) + singleCtr
+
+ // Insert the registration sequence into the function. We want this sequence to
+ // appear before any counter updates, so use a hack to ensure that this edit
+ // applies before the edit corresponding to the prolog counter update.
+
+ boff := f.offset(body.Pos())
+ ipos := f.fset.File(body.Pos()).Pos(boff)
+ ip := f.offset(ipos)
+ f.edit.Replace(ip, ip+1, string(f.content[ipos-1])+regHook+" ; ")
+
+ f.fn.counterVar = ""
+}
+
+func annotate(names []string) {
+ var p *Package
+ if *pkgcfg != "" {
+ pp := pkgconfig.PkgPath
+ pn := pkgconfig.PkgName
+ mp := pkgconfig.ModulePath
+ mdb, err := encodemeta.NewCoverageMetaDataBuilder(pp, pn, mp)
+ if err != nil {
+ log.Fatalf("creating coverage meta-data builder: %v\n", err)
+ }
+ p = &Package{
+ mdb: mdb,
+ }
+ }
+ // TODO: process files in parallel here if it matters.
+ for k, name := range names {
+ if strings.ContainsAny(name, "\r\n") {
+ // annotateFile uses '//line' directives, which don't permit newlines.
+ log.Fatalf("cover: input path contains newline character: %q", name)
+ }
+
+ fd := os.Stdout
+ isStdout := true
+ if *pkgcfg != "" {
+ var err error
+ fd, err = os.Create(outputfiles[k])
+ if err != nil {
+ log.Fatalf("cover: %s", err)
+ }
+ isStdout = false
+ } else if *output != "" {
+ var err error
+ fd, err = os.Create(*output)
+ if err != nil {
+ log.Fatalf("cover: %s", err)
+ }
+ isStdout = false
+ }
+ p.annotateFile(name, fd)
+ if !isStdout {
+ if err := fd.Close(); err != nil {
+ log.Fatalf("cover: %s", err)
+ }
+ }
+ }
+
+ if *pkgcfg != "" {
+ fd, err := os.Create(covervarsoutfile)
+ if err != nil {
+ log.Fatalf("cover: %s", err)
+ }
+ p.emitMetaData(fd)
+ if err := fd.Close(); err != nil {
+ log.Fatalf("cover: %s", err)
+ }
+ }
+}
+
+func (p *Package) annotateFile(name string, fd io.Writer) {
+ fset := token.NewFileSet()
+ content, err := os.ReadFile(name)
+ if err != nil {
+ log.Fatalf("cover: %s: %s", name, err)
+ }
+ parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments)
+ if err != nil {
+ log.Fatalf("cover: %s: %s", name, err)
+ }
+
+ file := &File{
+ fset: fset,
+ name: name,
+ content: content,
+ edit: edit.NewBuffer(content),
+ astFile: parsedFile,
+ }
+ if p != nil {
+ file.mdb = p.mdb
+ file.pkg = p
+ }
+
+ if *mode == "atomic" {
+ // Add import of sync/atomic immediately after package clause.
+ // We do this even if there is an existing import, because the
+ // existing import may be shadowed at any given place we want
+ // to refer to it, and our name (_cover_atomic_) is less likely to
+ // be shadowed. The one exception is if we're visiting the
+ // sync/atomic package itself, in which case we can refer to
+ // functions directly without an import prefix. See also #57445.
+ if pkgconfig.PkgPath != "sync/atomic" {
+ file.edit.Insert(file.offset(file.astFile.Name.End()),
+ fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
+ }
+ }
+ if pkgconfig.PkgName == "main" {
+ file.edit.Insert(file.offset(file.astFile.Name.End()),
+ "; import _ \"runtime/coverage\"")
+ }
+
+ if counterStmt != nil {
+ ast.Walk(file, file.astFile)
+ }
+ newContent := file.edit.Bytes()
+
+ if strings.ContainsAny(name, "\r\n") {
+ // This should have been checked by the caller already, but we double check
+ // here just to be sure we haven't missed a caller somewhere.
+ panic(fmt.Sprintf("annotateFile: name contains unexpected newline character: %q", name))
+ }
+ fmt.Fprintf(fd, "//line %s:1:1\n", name)
+ fd.Write(newContent)
+
+ // After printing the source tree, add some declarations for the
+ // counters etc. We could do this by adding to the tree, but it's
+ // easier just to print the text.
+ file.addVariables(fd)
+
+ // Emit a reference to the atomic package to avoid
+ // import and not used error when there's no code in a file.
+ if *mode == "atomic" {
+ fmt.Fprintf(fd, "\nvar _ = %sLoadUint32\n", atomicPackagePrefix())
+ }
+}
+
+// setCounterStmt returns the expression: __count[23] = 1.
+func setCounterStmt(f *File, counter string) string {
+ return fmt.Sprintf("%s = 1", counter)
+}
+
+// incCounterStmt returns the expression: __count[23]++.
+func incCounterStmt(f *File, counter string) string {
+ return fmt.Sprintf("%s++", counter)
+}
+
+// atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1)
+func atomicCounterStmt(f *File, counter string) string {
+ return fmt.Sprintf("%sAddUint32(&%s, 1)", atomicPackagePrefix(), counter)
+}
+
+// newCounter creates a new counter expression of the appropriate form.
+func (f *File) newCounter(start, end token.Pos, numStmt int) string {
+ var stmt string
+ if *pkgcfg != "" {
+ slot := len(f.fn.units) + coverage.FirstCtrOffset
+ if f.fn.counterVar == "" {
+ panic("internal error: counter var unset")
+ }
+ stmt = counterStmt(f, fmt.Sprintf("%s[%d]", f.fn.counterVar, slot))
+ stpos := f.fset.Position(start)
+ enpos := f.fset.Position(end)
+ stpos, enpos = dedup(stpos, enpos)
+ unit := coverage.CoverableUnit{
+ StLine: uint32(stpos.Line),
+ StCol: uint32(stpos.Column),
+ EnLine: uint32(enpos.Line),
+ EnCol: uint32(enpos.Column),
+ NxStmts: uint32(numStmt),
+ }
+ f.fn.units = append(f.fn.units, unit)
+ } else {
+ stmt = counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar,
+ len(f.blocks)))
+ f.blocks = append(f.blocks, Block{start, end, numStmt})
+ }
+ return stmt
+}
+
+// addCounters takes a list of statements and adds counters to the beginning of
+// each basic block at the top level of that list. For instance, given
+//
+// S1
+// if cond {
+// S2
+// }
+// S3
+//
+// counters will be added before S1 and before S3. The block containing S2
+// will be visited in a separate call.
+// TODO: Nested simple blocks get unnecessary (but correct) counters
+func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
+ // Special case: make sure we add a counter to an empty block. Can't do this below
+ // or we will add a counter to an empty statement list after, say, a return statement.
+ if len(list) == 0 {
+ f.edit.Insert(f.offset(insertPos), f.newCounter(insertPos, blockEnd, 0)+";")
+ return
+ }
+ // Make a copy of the list, as we may mutate it and should leave the
+ // existing list intact.
+ list = append([]ast.Stmt(nil), list...)
+ // We have a block (statement list), but it may have several basic blocks due to the
+ // appearance of statements that affect the flow of control.
+ for {
+ // Find first statement that affects flow of control (break, continue, if, etc.).
+ // It will be the last statement of this basic block.
+ var last int
+ end := blockEnd
+ for last = 0; last < len(list); last++ {
+ stmt := list[last]
+ end = f.statementBoundary(stmt)
+ if f.endsBasicSourceBlock(stmt) {
+ // If it is a labeled statement, we need to place a counter between
+ // the label and its statement because it may be the target of a goto
+ // and thus start a basic block. That is, given
+ // foo: stmt
+ // we need to create
+ // foo: ; stmt
+ // and mark the label as a block-terminating statement.
+ // The result will then be
+ // foo: COUNTER[n]++; stmt
+ // However, we can't do this if the labeled statement is already
+ // a control statement, such as a labeled for.
+ if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {
+ newLabel := *label
+ newLabel.Stmt = &ast.EmptyStmt{
+ Semicolon: label.Stmt.Pos(),
+ Implicit: true,
+ }
+ end = label.Pos() // Previous block ends before the label.
+ list[last] = &newLabel
+ // Open a gap and drop in the old statement, now without a label.
+ list = append(list, nil)
+ copy(list[last+1:], list[last:])
+ list[last+1] = label.Stmt
+ }
+ last++
+ extendToClosingBrace = false // Block is broken up now.
+ break
+ }
+ }
+ if extendToClosingBrace {
+ end = blockEnd
+ }
+ if pos != end { // Can have no source to cover if e.g. blocks abut.
+ f.edit.Insert(f.offset(insertPos), f.newCounter(pos, end, last)+";")
+ }
+ list = list[last:]
+ if len(list) == 0 {
+ break
+ }
+ pos = list[0].Pos()
+ insertPos = pos
+ }
+}
+
+// hasFuncLiteral reports the existence and position of the first func literal
+// in the node, if any. If a func literal appears, it usually marks the termination
+// of a basic block because the function body is itself a block.
+// Therefore we draw a line at the start of the body of the first function literal we find.
+// TODO: what if there's more than one? Probably doesn't matter much.
+func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
+ if n == nil {
+ return false, 0
+ }
+ var literal funcLitFinder
+ ast.Walk(&literal, n)
+ return literal.found(), token.Pos(literal)
+}
+
+// statementBoundary finds the location in s that terminates the current basic
+// block in the source.
+func (f *File) statementBoundary(s ast.Stmt) token.Pos {
+ // Control flow statements are easy.
+ switch s := s.(type) {
+ case *ast.BlockStmt:
+ // Treat blocks like basic blocks to avoid overlapping counters.
+ return s.Lbrace
+ case *ast.IfStmt:
+ found, pos := hasFuncLiteral(s.Init)
+ if found {
+ return pos
+ }
+ found, pos = hasFuncLiteral(s.Cond)
+ if found {
+ return pos
+ }
+ return s.Body.Lbrace
+ case *ast.ForStmt:
+ found, pos := hasFuncLiteral(s.Init)
+ if found {
+ return pos
+ }
+ found, pos = hasFuncLiteral(s.Cond)
+ if found {
+ return pos
+ }
+ found, pos = hasFuncLiteral(s.Post)
+ if found {
+ return pos
+ }
+ return s.Body.Lbrace
+ case *ast.LabeledStmt:
+ return f.statementBoundary(s.Stmt)
+ case *ast.RangeStmt:
+ found, pos := hasFuncLiteral(s.X)
+ if found {
+ return pos
+ }
+ return s.Body.Lbrace
+ case *ast.SwitchStmt:
+ found, pos := hasFuncLiteral(s.Init)
+ if found {
+ return pos
+ }
+ found, pos = hasFuncLiteral(s.Tag)
+ if found {
+ return pos
+ }
+ return s.Body.Lbrace
+ case *ast.SelectStmt:
+ return s.Body.Lbrace
+ case *ast.TypeSwitchStmt:
+ found, pos := hasFuncLiteral(s.Init)
+ if found {
+ return pos
+ }
+ return s.Body.Lbrace
+ }
+ // If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal.
+ // If it does, that's tricky because we want to exclude the body of the function from this block.
+ // Draw a line at the start of the body of the first function literal we find.
+ // TODO: what if there's more than one? Probably doesn't matter much.
+ found, pos := hasFuncLiteral(s)
+ if found {
+ return pos
+ }
+ return s.End()
+}
+
+// endsBasicSourceBlock reports whether s changes the flow of control: break, if, etc.,
+// or if it's just problematic, for instance contains a function literal, which will complicate
+// accounting due to the block-within-an expression.
+func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
+ switch s := s.(type) {
+ case *ast.BlockStmt:
+ // Treat blocks like basic blocks to avoid overlapping counters.
+ return true
+ case *ast.BranchStmt:
+ return true
+ case *ast.ForStmt:
+ return true
+ case *ast.IfStmt:
+ return true
+ case *ast.LabeledStmt:
+ return true // A goto may branch here, starting a new basic block.
+ case *ast.RangeStmt:
+ return true
+ case *ast.SwitchStmt:
+ return true
+ case *ast.SelectStmt:
+ return true
+ case *ast.TypeSwitchStmt:
+ return true
+ case *ast.ExprStmt:
+ // Calls to panic change the flow.
+ // We really should verify that "panic" is the predefined function,
+ // but without type checking we can't and the likelihood of it being
+ // an actual problem is vanishingly small.
+ if call, ok := s.X.(*ast.CallExpr); ok {
+ if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
+ return true
+ }
+ }
+ }
+ found, _ := hasFuncLiteral(s)
+ return found
+}
+
+// isControl reports whether s is a control statement that, if labeled, cannot be
+// separated from its label.
+func (f *File) isControl(s ast.Stmt) bool {
+ switch s.(type) {
+ case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
+ return true
+ }
+ return false
+}
+
+// funcLitFinder implements the ast.Visitor pattern to find the location of any
+// function literal in a subtree.
+type funcLitFinder token.Pos
+
+func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
+ if f.found() {
+ return nil // Prune search.
+ }
+ switch n := node.(type) {
+ case *ast.FuncLit:
+ *f = funcLitFinder(n.Body.Lbrace)
+ return nil // Prune search.
+ }
+ return f
+}
+
+func (f *funcLitFinder) found() bool {
+ return token.Pos(*f) != token.NoPos
+}
+
+// Sort interface for []block1; used for self-check in addVariables.
+
+type block1 struct {
+ Block
+ index int
+}
+
+type blockSlice []block1
+
+func (b blockSlice) Len() int { return len(b) }
+func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte }
+func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
+
+// offset translates a token position into a 0-indexed byte offset.
+func (f *File) offset(pos token.Pos) int {
+ return f.fset.Position(pos).Offset
+}
+
+// addVariables adds to the end of the file the declarations to set up the counter and position variables.
+func (f *File) addVariables(w io.Writer) {
+ if *pkgcfg != "" {
+ return
+ }
+ // Self-check: Verify that the instrumented basic blocks are disjoint.
+ t := make([]block1, len(f.blocks))
+ for i := range f.blocks {
+ t[i].Block = f.blocks[i]
+ t[i].index = i
+ }
+ sort.Sort(blockSlice(t))
+ for i := 1; i < len(t); i++ {
+ if t[i-1].endByte > t[i].startByte {
+ fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
+ // Note: error message is in byte positions, not token positions.
+ fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
+ f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
+ f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
+ }
+ }
+
+ // Declare the coverage struct as a package-level variable.
+ fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
+ fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks))
+ fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks))
+ fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks))
+ fmt.Fprintf(w, "} {\n")
+
+ // Initialize the position array field.
+ fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
+
+ // A nice long list of positions. Each position is encoded as follows to reduce size:
+ // - 32-bit starting line number
+ // - 32-bit ending line number
+ // - (16 bit ending column number << 16) | (16-bit starting column number).
+ for i, block := range f.blocks {
+ start := f.fset.Position(block.startByte)
+ end := f.fset.Position(block.endByte)
+
+ start, end = dedup(start, end)
+
+ fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
+ }
+
+ // Close the position array.
+ fmt.Fprintf(w, "\t},\n")
+
+ // Initialize the position array field.
+ fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
+
+ // A nice long list of statements-per-block, so we can give a conventional
+ // valuation of "percent covered". To save space, it's a 16-bit number, so we
+ // clamp it if it overflows - won't matter in practice.
+ for i, block := range f.blocks {
+ n := block.numStmt
+ if n > 1<<16-1 {
+ n = 1<<16 - 1
+ }
+ fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)
+ }
+
+ // Close the statements-per-block array.
+ fmt.Fprintf(w, "\t},\n")
+
+ // Close the struct initialization.
+ fmt.Fprintf(w, "}\n")
+}
+
+// It is possible for positions to repeat when there is a line
+// directive that does not specify column information and the input
+// has not been passed through gofmt.
+// See issues #27530 and #30746.
+// Tests are TestHtmlUnformatted and TestLineDup.
+// We use a map to avoid duplicates.
+
+// pos2 is a pair of token.Position values, used as a map key type.
+type pos2 struct {
+ p1, p2 token.Position
+}
+
+// seenPos2 tracks whether we have seen a token.Position pair.
+var seenPos2 = make(map[pos2]bool)
+
+// dedup takes a token.Position pair and returns a pair that does not
+// duplicate any existing pair. The returned pair will have the Offset
+// fields cleared.
+func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
+ key := pos2{
+ p1: p1,
+ p2: p2,
+ }
+
+ // We want to ignore the Offset fields in the map,
+ // since cover uses only file/line/column.
+ key.p1.Offset = 0
+ key.p2.Offset = 0
+
+ for seenPos2[key] {
+ key.p2.Column++
+ }
+ seenPos2[key] = true
+
+ return key.p1, key.p2
+}
+
+func (p *Package) emitMetaData(w io.Writer) {
+ if *pkgcfg == "" {
+ return
+ }
+
+ // Something went wrong if regonly/testmain mode is in effect and
+ // we have instrumented functions.
+ if counterStmt == nil && len(p.counterLengths) != 0 {
+ panic("internal error: seen functions with regonly/testmain")
+ }
+
+ // Emit package name.
+ fmt.Fprintf(w, "\npackage %s\n\n", pkgconfig.PkgName)
+
+ // Emit package ID var.
+ fmt.Fprintf(w, "\nvar %sP uint32\n", *varVar)
+
+ // Emit all of the counter variables.
+ for k := range p.counterLengths {
+ cvn := mkCounterVarName(k)
+ fmt.Fprintf(w, "var %s [%d]uint32\n", cvn, p.counterLengths[k])
+ }
+
+ // Emit encoded meta-data.
+ var sws slicewriter.WriteSeeker
+ digest, err := p.mdb.Emit(&sws)
+ if err != nil {
+ log.Fatalf("encoding meta-data: %v", err)
+ }
+ p.mdb = nil
+ fmt.Fprintf(w, "var %s = [...]byte{\n", mkMetaVar())
+ payload := sws.BytesWritten()
+ for k, b := range payload {
+ fmt.Fprintf(w, " 0x%x,", b)
+ if k != 0 && k%8 == 0 {
+ fmt.Fprintf(w, "\n")
+ }
+ }
+ fmt.Fprintf(w, "}\n")
+
+ fixcfg := coverage.CoverFixupConfig{
+ Strategy: "normal",
+ MetaVar: mkMetaVar(),
+ MetaLen: len(payload),
+ MetaHash: fmt.Sprintf("%x", digest),
+ PkgIdVar: mkPackageIdVar(),
+ CounterPrefix: *varVar,
+ CounterGranularity: pkgconfig.Granularity,
+ CounterMode: *mode,
+ }
+ fixdata, err := json.Marshal(fixcfg)
+ if err != nil {
+ log.Fatalf("marshal fixupcfg: %v", err)
+ }
+ if err := os.WriteFile(pkgconfig.OutConfig, fixdata, 0666); err != nil {
+ log.Fatalf("error writing %s: %v", pkgconfig.OutConfig, err)
+ }
+}
+
+// atomicOnAtomic returns true if we're instrumenting
+// the sync/atomic package AND using atomic mode.
+func atomicOnAtomic() bool {
+ return *mode == "atomic" && pkgconfig.PkgPath == "sync/atomic"
+}
+
+// atomicPackagePrefix returns the import path prefix used to refer to
+// our special import of sync/atomic; this is either set to the
+// constant atomicPackageName plus a dot or the empty string if we're
+// instrumenting the sync/atomic package itself.
+func atomicPackagePrefix() string {
+ if atomicOnAtomic() {
+ return ""
+ }
+ return atomicPackageName + "."
+}
diff --git a/src/cmd/cover/cover_test.go b/src/cmd/cover/cover_test.go
new file mode 100644
index 0000000..7bfe2d0
--- /dev/null
+++ b/src/cmd/cover/cover_test.go
@@ -0,0 +1,649 @@
+// Copyright 2013 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 main_test
+
+import (
+ "bufio"
+ "bytes"
+ cmdcover "cmd/cover"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "internal/testenv"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "sync"
+ "testing"
+)
+
+const (
+ // Data directory, also the package directory for the test.
+ testdata = "testdata"
+)
+
+// testcover returns the path to the cmd/cover binary that we are going to
+// test. At one point this was created via "go build"; we now reuse the unit
+// test executable itself.
+func testcover(t testing.TB) string {
+ exe, err := os.Executable()
+ if err != nil {
+ t.Helper()
+ t.Fatal(err)
+ }
+ return exe
+}
+
+// testTempDir is a temporary directory created in TestMain.
+var testTempDir string
+
+// If set, this will preserve all the tmpdir files from the test run.
+var debug = flag.Bool("debug", false, "keep tmpdir files for debugging")
+
+// TestMain used here so that we can leverage the test executable
+// itself as a cmd/cover executable; compare to similar usage in
+// the cmd/go tests.
+func TestMain(m *testing.M) {
+ if os.Getenv("CMDCOVER_TOOLEXEC") != "" {
+ // When CMDCOVER_TOOLEXEC is set, the test binary is also
+ // running as a -toolexec wrapper.
+ tool := strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe")
+ if tool == "cover" {
+ // Inject this test binary as cmd/cover in place of the
+ // installed tool, so that the go command's invocations of
+ // cover produce coverage for the configuration in which
+ // the test was built.
+ os.Args = os.Args[1:]
+ cmdcover.Main()
+ } else {
+ cmd := exec.Command(os.Args[1], os.Args[2:]...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ os.Exit(1)
+ }
+ }
+ os.Exit(0)
+ }
+ if os.Getenv("CMDCOVER_TEST_RUN_MAIN") != "" {
+ // When CMDCOVER_TEST_RUN_MAIN is set, we're reusing the test
+ // binary as cmd/cover. In this case we run the main func exported
+ // via export_test.go, and exit; CMDCOVER_TEST_RUN_MAIN is set below
+ // for actual test invocations.
+ cmdcover.Main()
+ os.Exit(0)
+ }
+ flag.Parse()
+ topTmpdir, err := os.MkdirTemp("", "cmd-cover-test-")
+ if err != nil {
+ log.Fatal(err)
+ }
+ testTempDir = topTmpdir
+ if !*debug {
+ defer os.RemoveAll(topTmpdir)
+ } else {
+ fmt.Fprintf(os.Stderr, "debug: preserving tmpdir %s\n", topTmpdir)
+ }
+ os.Setenv("CMDCOVER_TEST_RUN_MAIN", "normal")
+ os.Exit(m.Run())
+}
+
+var tdmu sync.Mutex
+var tdcount int
+
+func tempDir(t *testing.T) string {
+ tdmu.Lock()
+ dir := filepath.Join(testTempDir, fmt.Sprintf("%03d", tdcount))
+ tdcount++
+ if err := os.Mkdir(dir, 0777); err != nil {
+ t.Fatal(err)
+ }
+ defer tdmu.Unlock()
+ return dir
+}
+
+// TestCoverWithToolExec runs a set of subtests that all make use of a
+// "-toolexec" wrapper program to invoke the cover test executable
+// itself via "go test -cover".
+func TestCoverWithToolExec(t *testing.T) {
+ testenv.MustHaveExec(t)
+
+ toolexecArg := "-toolexec=" + testcover(t)
+
+ t.Run("CoverHTML", func(t *testing.T) {
+ testCoverHTML(t, toolexecArg)
+ })
+ t.Run("HtmlUnformatted", func(t *testing.T) {
+ testHtmlUnformatted(t, toolexecArg)
+ })
+ t.Run("FuncWithDuplicateLines", func(t *testing.T) {
+ testFuncWithDuplicateLines(t, toolexecArg)
+ })
+ t.Run("MissingTrailingNewlineIssue58370", func(t *testing.T) {
+ testMissingTrailingNewlineIssue58370(t, toolexecArg)
+ })
+}
+
+// Execute this command sequence:
+//
+// replace the word LINE with the line number < testdata/test.go > testdata/test_line.go
+// testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go
+// go run ./testdata/main.go ./testdata/test.go
+func TestCover(t *testing.T) {
+ testenv.MustHaveGoRun(t)
+ t.Parallel()
+ dir := tempDir(t)
+
+ // Read in the test file (testTest) and write it, with LINEs specified, to coverInput.
+ testTest := filepath.Join(testdata, "test.go")
+ file, err := os.ReadFile(testTest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ lines := bytes.Split(file, []byte("\n"))
+ for i, line := range lines {
+ lines[i] = bytes.ReplaceAll(line, []byte("LINE"), []byte(fmt.Sprint(i+1)))
+ }
+
+ // Add a function that is not gofmt'ed. This used to cause a crash.
+ // We don't put it in test.go because then we would have to gofmt it.
+ // Issue 23927.
+ lines = append(lines, []byte("func unFormatted() {"),
+ []byte("\tif true {"),
+ []byte("\t}else{"),
+ []byte("\t}"),
+ []byte("}"))
+ lines = append(lines, []byte("func unFormatted2(b bool) {if b{}else{}}"))
+
+ coverInput := filepath.Join(dir, "test_line.go")
+ if err := os.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ // testcover -mode=count -var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest -o ./testdata/test_cover.go testdata/test_line.go
+ coverOutput := filepath.Join(dir, "test_cover.go")
+ cmd := testenv.Command(t, testcover(t), "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
+ run(cmd, t)
+
+ cmd = testenv.Command(t, testcover(t), "-mode=set", "-var=Not_an-identifier", "-o", coverOutput, coverInput)
+ err = cmd.Run()
+ if err == nil {
+ t.Error("Expected cover to fail with an error")
+ }
+
+ // Copy testmain to tmpdir, so that it is in the same directory
+ // as coverOutput.
+ testMain := filepath.Join(testdata, "main.go")
+ b, err := os.ReadFile(testMain)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tmpTestMain := filepath.Join(dir, "main.go")
+ if err := os.WriteFile(tmpTestMain, b, 0444); err != nil {
+ t.Fatal(err)
+ }
+
+ // go run ./testdata/main.go ./testdata/test.go
+ cmd = testenv.Command(t, testenv.GoToolPath(t), "run", tmpTestMain, coverOutput)
+ run(cmd, t)
+
+ file, err = os.ReadFile(coverOutput)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // compiler directive must appear right next to function declaration.
+ if got, err := regexp.MatchString(".*\n//go:nosplit\nfunc someFunction().*", string(file)); err != nil || !got {
+ t.Error("misplaced compiler directive")
+ }
+ // "go:linkname" compiler directive should be present.
+ if got, err := regexp.MatchString(`.*go\:linkname some\_name some\_name.*`, string(file)); err != nil || !got {
+ t.Error("'go:linkname' compiler directive not found")
+ }
+
+ // Other comments should be preserved too.
+ c := ".*// This comment didn't appear in generated go code.*"
+ if got, err := regexp.MatchString(c, string(file)); err != nil || !got {
+ t.Errorf("non compiler directive comment %q not found", c)
+ }
+}
+
+// TestDirectives checks that compiler directives are preserved and positioned
+// correctly. Directives that occur before top-level declarations should remain
+// above those declarations, even if they are not part of the block of
+// documentation comments.
+func TestDirectives(t *testing.T) {
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ // Read the source file and find all the directives. We'll keep
+ // track of whether each one has been seen in the output.
+ testDirectives := filepath.Join(testdata, "directives.go")
+ source, err := os.ReadFile(testDirectives)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sourceDirectives := findDirectives(source)
+
+ // testcover -mode=atomic ./testdata/directives.go
+ cmd := testenv.Command(t, testcover(t), "-mode=atomic", testDirectives)
+ cmd.Stderr = os.Stderr
+ output, err := cmd.Output()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check that all directives are present in the output.
+ outputDirectives := findDirectives(output)
+ foundDirective := make(map[string]bool)
+ for _, p := range sourceDirectives {
+ foundDirective[p.name] = false
+ }
+ for _, p := range outputDirectives {
+ if found, ok := foundDirective[p.name]; !ok {
+ t.Errorf("unexpected directive in output: %s", p.text)
+ } else if found {
+ t.Errorf("directive found multiple times in output: %s", p.text)
+ }
+ foundDirective[p.name] = true
+ }
+ for name, found := range foundDirective {
+ if !found {
+ t.Errorf("missing directive: %s", name)
+ }
+ }
+
+ // Check that directives that start with the name of top-level declarations
+ // come before the beginning of the named declaration and after the end
+ // of the previous declaration.
+ fset := token.NewFileSet()
+ astFile, err := parser.ParseFile(fset, testDirectives, output, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ prevEnd := 0
+ for _, decl := range astFile.Decls {
+ var name string
+ switch d := decl.(type) {
+ case *ast.FuncDecl:
+ name = d.Name.Name
+ case *ast.GenDecl:
+ if len(d.Specs) == 0 {
+ // An empty group declaration. We still want to check that
+ // directives can be associated with it, so we make up a name
+ // to match directives in the test data.
+ name = "_empty"
+ } else if spec, ok := d.Specs[0].(*ast.TypeSpec); ok {
+ name = spec.Name.Name
+ }
+ }
+ pos := fset.Position(decl.Pos()).Offset
+ end := fset.Position(decl.End()).Offset
+ if name == "" {
+ prevEnd = end
+ continue
+ }
+ for _, p := range outputDirectives {
+ if !strings.HasPrefix(p.name, name) {
+ continue
+ }
+ if p.offset < prevEnd || pos < p.offset {
+ t.Errorf("directive %s does not appear before definition %s", p.text, name)
+ }
+ }
+ prevEnd = end
+ }
+}
+
+type directiveInfo struct {
+ text string // full text of the comment, not including newline
+ name string // text after //go:
+ offset int // byte offset of first slash in comment
+}
+
+func findDirectives(source []byte) []directiveInfo {
+ var directives []directiveInfo
+ directivePrefix := []byte("\n//go:")
+ offset := 0
+ for {
+ i := bytes.Index(source[offset:], directivePrefix)
+ if i < 0 {
+ break
+ }
+ i++ // skip newline
+ p := source[offset+i:]
+ j := bytes.IndexByte(p, '\n')
+ if j < 0 {
+ // reached EOF
+ j = len(p)
+ }
+ directive := directiveInfo{
+ text: string(p[:j]),
+ name: string(p[len(directivePrefix)-1 : j]),
+ offset: offset + i,
+ }
+ directives = append(directives, directive)
+ offset += i + j
+ }
+ return directives
+}
+
+// Makes sure that `cover -func=profile.cov` reports accurate coverage.
+// Issue #20515.
+func TestCoverFunc(t *testing.T) {
+ testenv.MustHaveExec(t)
+
+ // testcover -func ./testdata/profile.cov
+ coverProfile := filepath.Join(testdata, "profile.cov")
+ cmd := testenv.Command(t, testcover(t), "-func", coverProfile)
+ out, err := cmd.Output()
+ if err != nil {
+ if ee, ok := err.(*exec.ExitError); ok {
+ t.Logf("%s", ee.Stderr)
+ }
+ t.Fatal(err)
+ }
+
+ if got, err := regexp.Match(".*total:.*100.0.*", out); err != nil || !got {
+ t.Logf("%s", out)
+ t.Errorf("invalid coverage counts. got=(%v, %v); want=(true; nil)", got, err)
+ }
+}
+
+// Check that cover produces correct HTML.
+// Issue #25767.
+func testCoverHTML(t *testing.T, toolexecArg string) {
+ testenv.MustHaveGoRun(t)
+ dir := tempDir(t)
+
+ t.Parallel()
+
+ // go test -coverprofile testdata/html/html.cov cmd/cover/testdata/html
+ htmlProfile := filepath.Join(dir, "html.cov")
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "test", toolexecArg, "-coverprofile", htmlProfile, "cmd/cover/testdata/html")
+ cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
+ run(cmd, t)
+ // testcover -html testdata/html/html.cov -o testdata/html/html.html
+ htmlHTML := filepath.Join(dir, "html.html")
+ cmd = testenv.Command(t, testcover(t), "-html", htmlProfile, "-o", htmlHTML)
+ run(cmd, t)
+
+ // Extract the parts of the HTML with comment markers,
+ // and compare against a golden file.
+ entireHTML, err := os.ReadFile(htmlHTML)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var out strings.Builder
+ scan := bufio.NewScanner(bytes.NewReader(entireHTML))
+ in := false
+ for scan.Scan() {
+ line := scan.Text()
+ if strings.Contains(line, "// START") {
+ in = true
+ }
+ if in {
+ fmt.Fprintln(&out, line)
+ }
+ if strings.Contains(line, "// END") {
+ in = false
+ }
+ }
+ if scan.Err() != nil {
+ t.Error(scan.Err())
+ }
+ htmlGolden := filepath.Join(testdata, "html", "html.golden")
+ golden, err := os.ReadFile(htmlGolden)
+ if err != nil {
+ t.Fatalf("reading golden file: %v", err)
+ }
+ // Ignore white space differences.
+ // Break into lines, then compare by breaking into words.
+ goldenLines := strings.Split(string(golden), "\n")
+ outLines := strings.Split(out.String(), "\n")
+ // Compare at the line level, stopping at first different line so
+ // we don't generate tons of output if there's an inserted or deleted line.
+ for i, goldenLine := range goldenLines {
+ if i >= len(outLines) {
+ t.Fatalf("output shorter than golden; stops before line %d: %s\n", i+1, goldenLine)
+ }
+ // Convert all white space to simple spaces, for easy comparison.
+ goldenLine = strings.Join(strings.Fields(goldenLine), " ")
+ outLine := strings.Join(strings.Fields(outLines[i]), " ")
+ if outLine != goldenLine {
+ t.Fatalf("line %d differs: got:\n\t%s\nwant:\n\t%s", i+1, outLine, goldenLine)
+ }
+ }
+ if len(goldenLines) != len(outLines) {
+ t.Fatalf("output longer than golden; first extra output line %d: %q\n", len(goldenLines)+1, outLines[len(goldenLines)])
+ }
+}
+
+// Test HTML processing with a source file not run through gofmt.
+// Issue #27350.
+func testHtmlUnformatted(t *testing.T, toolexecArg string) {
+ testenv.MustHaveGoRun(t)
+ dir := tempDir(t)
+
+ t.Parallel()
+
+ htmlUDir := filepath.Join(dir, "htmlunformatted")
+ htmlU := filepath.Join(htmlUDir, "htmlunformatted.go")
+ htmlUTest := filepath.Join(htmlUDir, "htmlunformatted_test.go")
+ htmlUProfile := filepath.Join(htmlUDir, "htmlunformatted.cov")
+ htmlUHTML := filepath.Join(htmlUDir, "htmlunformatted.html")
+
+ if err := os.Mkdir(htmlUDir, 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := os.WriteFile(filepath.Join(htmlUDir, "go.mod"), []byte("module htmlunformatted\n"), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ const htmlUContents = `
+package htmlunformatted
+
+var g int
+
+func F() {
+//line x.go:1
+ { { F(); goto lab } }
+lab:
+}`
+
+ const htmlUTestContents = `package htmlunformatted`
+
+ if err := os.WriteFile(htmlU, []byte(htmlUContents), 0444); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(htmlUTest, []byte(htmlUTestContents), 0444); err != nil {
+ t.Fatal(err)
+ }
+
+ // go test -covermode=count -coverprofile TMPDIR/htmlunformatted.cov
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "test", "-test.v", toolexecArg, "-covermode=count", "-coverprofile", htmlUProfile)
+ cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
+ cmd.Dir = htmlUDir
+ run(cmd, t)
+
+ // testcover -html TMPDIR/htmlunformatted.cov -o unformatted.html
+ cmd = testenv.Command(t, testcover(t), "-html", htmlUProfile, "-o", htmlUHTML)
+ cmd.Dir = htmlUDir
+ run(cmd, t)
+}
+
+// lineDupContents becomes linedup.go in testFuncWithDuplicateLines.
+const lineDupContents = `
+package linedup
+
+var G int
+
+func LineDup(c int) {
+ for i := 0; i < c; i++ {
+//line ld.go:100
+ if i % 2 == 0 {
+ G++
+ }
+ if i % 3 == 0 {
+ G++; G++
+ }
+//line ld.go:100
+ if i % 4 == 0 {
+ G++; G++; G++
+ }
+ if i % 5 == 0 {
+ G++; G++; G++; G++
+ }
+ }
+}
+`
+
+// lineDupTestContents becomes linedup_test.go in testFuncWithDuplicateLines.
+const lineDupTestContents = `
+package linedup
+
+import "testing"
+
+func TestLineDup(t *testing.T) {
+ LineDup(100)
+}
+`
+
+// Test -func with duplicate //line directives with different numbers
+// of statements.
+func testFuncWithDuplicateLines(t *testing.T, toolexecArg string) {
+ testenv.MustHaveGoRun(t)
+ dir := tempDir(t)
+
+ t.Parallel()
+
+ lineDupDir := filepath.Join(dir, "linedup")
+ lineDupGo := filepath.Join(lineDupDir, "linedup.go")
+ lineDupTestGo := filepath.Join(lineDupDir, "linedup_test.go")
+ lineDupProfile := filepath.Join(lineDupDir, "linedup.out")
+
+ if err := os.Mkdir(lineDupDir, 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := os.WriteFile(filepath.Join(lineDupDir, "go.mod"), []byte("module linedup\n"), 0666); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(lineDupGo, []byte(lineDupContents), 0444); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(lineDupTestGo, []byte(lineDupTestContents), 0444); err != nil {
+ t.Fatal(err)
+ }
+
+ // go test -cover -covermode count -coverprofile TMPDIR/linedup.out
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "test", toolexecArg, "-cover", "-covermode", "count", "-coverprofile", lineDupProfile)
+ cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
+ cmd.Dir = lineDupDir
+ run(cmd, t)
+
+ // testcover -func=TMPDIR/linedup.out
+ cmd = testenv.Command(t, testcover(t), "-func", lineDupProfile)
+ cmd.Dir = lineDupDir
+ run(cmd, t)
+}
+
+func run(c *exec.Cmd, t *testing.T) {
+ t.Helper()
+ t.Log("running", c.Args)
+ out, err := c.CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func runExpectingError(c *exec.Cmd, t *testing.T) string {
+ t.Helper()
+ t.Log("running", c.Args)
+ out, err := c.CombinedOutput()
+ if err == nil {
+ return fmt.Sprintf("unexpected pass for %+v", c.Args)
+ }
+ return string(out)
+}
+
+// Test instrumentation of package that ends before an expected
+// trailing newline following package clause. Issue #58370.
+func testMissingTrailingNewlineIssue58370(t *testing.T, toolexecArg string) {
+ testenv.MustHaveGoBuild(t)
+ dir := tempDir(t)
+
+ t.Parallel()
+
+ noeolDir := filepath.Join(dir, "issue58370")
+ noeolGo := filepath.Join(noeolDir, "noeol.go")
+ noeolTestGo := filepath.Join(noeolDir, "noeol_test.go")
+
+ if err := os.Mkdir(noeolDir, 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := os.WriteFile(filepath.Join(noeolDir, "go.mod"), []byte("module noeol\n"), 0666); err != nil {
+ t.Fatal(err)
+ }
+ const noeolContents = `package noeol`
+ if err := os.WriteFile(noeolGo, []byte(noeolContents), 0444); err != nil {
+ t.Fatal(err)
+ }
+ const noeolTestContents = `
+package noeol
+import "testing"
+func TestCoverage(t *testing.T) { }
+`
+ if err := os.WriteFile(noeolTestGo, []byte(noeolTestContents), 0444); err != nil {
+ t.Fatal(err)
+ }
+
+ // go test -covermode atomic
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "test", toolexecArg, "-covermode", "atomic")
+ cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
+ cmd.Dir = noeolDir
+ run(cmd, t)
+}
+
+func TestSrcPathWithNewline(t *testing.T) {
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ // srcPath is intentionally not clean so that the path passed to testcover
+ // will not normalize the trailing / to a \ on Windows.
+ srcPath := t.TempDir() + string(filepath.Separator) + "\npackage main\nfunc main() { panic(string([]rune{'u', 'h', '-', 'o', 'h'}))\n/*/main.go"
+ mainSrc := ` package main
+
+func main() {
+ /* nothing here */
+ println("ok")
+}
+`
+ if err := os.MkdirAll(filepath.Dir(srcPath), 0777); err != nil {
+ t.Skipf("creating directory with bogus path: %v", err)
+ }
+ if err := os.WriteFile(srcPath, []byte(mainSrc), 0666); err != nil {
+ t.Skipf("writing file with bogus directory: %v", err)
+ }
+
+ cmd := testenv.Command(t, testcover(t), "-mode=atomic", srcPath)
+ cmd.Stderr = new(bytes.Buffer)
+ out, err := cmd.Output()
+ t.Logf("%v:\n%s", cmd, out)
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ if err == nil {
+ t.Errorf("unexpected success; want failure due to newline in file path")
+ }
+}
diff --git a/src/cmd/cover/doc.go b/src/cmd/cover/doc.go
new file mode 100644
index 0000000..82580cd
--- /dev/null
+++ b/src/cmd/cover/doc.go
@@ -0,0 +1,32 @@
+// Copyright 2013 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.
+
+/*
+Cover is a program for analyzing the coverage profiles generated by
+'go test -coverprofile=cover.out'.
+
+Cover is also used by 'go test -cover' to rewrite the source code with
+annotations to track which parts of each function are executed (this
+is referred to "instrumentation"). Cover can operate in "legacy mode"
+on a single Go source file at a time, or when invoked by the Go tool
+it will process all the source files in a single package at a time
+(package-scope instrumentation is enabled via "-pkgcfg" option,
+
+When generated instrumented code, the cover tool computes approximate
+basic block information by studying the source. It is thus more
+portable than binary-rewriting coverage tools, but also a little less
+capable. For instance, it does not probe inside && and || expressions,
+and can be mildly confused by single statements with multiple function
+literals.
+
+When computing coverage of a package that uses cgo, the cover tool
+must be applied to the output of cgo preprocessing, not the input,
+because cover deletes comments that are significant to cgo.
+
+For usage information, please see:
+
+ go help testflag
+ go tool cover -help
+*/
+package main
diff --git a/src/cmd/cover/export_test.go b/src/cmd/cover/export_test.go
new file mode 100644
index 0000000..e4592ee
--- /dev/null
+++ b/src/cmd/cover/export_test.go
@@ -0,0 +1,7 @@
+// 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.
+
+package main
+
+func Main() { main() }
diff --git a/src/cmd/cover/func.go b/src/cmd/cover/func.go
new file mode 100644
index 0000000..dffd3c1
--- /dev/null
+++ b/src/cmd/cover/func.go
@@ -0,0 +1,248 @@
+// Copyright 2013 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.
+
+// This file implements the visitor that computes the (line, column)-(line-column) range for each function.
+
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "text/tabwriter"
+
+ "golang.org/x/tools/cover"
+)
+
+// funcOutput takes two file names as arguments, a coverage profile to read as input and an output
+// file to write ("" means to write to standard output). The function reads the profile and produces
+// as output the coverage data broken down by function, like this:
+//
+// fmt/format.go:30: init 100.0%
+// fmt/format.go:57: clearflags 100.0%
+// ...
+// fmt/scan.go:1046: doScan 100.0%
+// fmt/scan.go:1075: advance 96.2%
+// fmt/scan.go:1119: doScanf 96.8%
+// total: (statements) 91.9%
+
+func funcOutput(profile, outputFile string) error {
+ profiles, err := cover.ParseProfiles(profile)
+ if err != nil {
+ return err
+ }
+
+ dirs, err := findPkgs(profiles)
+ if err != nil {
+ return err
+ }
+
+ var out *bufio.Writer
+ if outputFile == "" {
+ out = bufio.NewWriter(os.Stdout)
+ } else {
+ fd, err := os.Create(outputFile)
+ if err != nil {
+ return err
+ }
+ defer fd.Close()
+ out = bufio.NewWriter(fd)
+ }
+ defer out.Flush()
+
+ tabber := tabwriter.NewWriter(out, 1, 8, 1, '\t', 0)
+ defer tabber.Flush()
+
+ var total, covered int64
+ for _, profile := range profiles {
+ fn := profile.FileName
+ file, err := findFile(dirs, fn)
+ if err != nil {
+ return err
+ }
+ funcs, err := findFuncs(file)
+ if err != nil {
+ return err
+ }
+ // Now match up functions and profile blocks.
+ for _, f := range funcs {
+ c, t := f.coverage(profile)
+ fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n", fn, f.startLine, f.name, percent(c, t))
+ total += t
+ covered += c
+ }
+ }
+ fmt.Fprintf(tabber, "total:\t(statements)\t%.1f%%\n", percent(covered, total))
+
+ return nil
+}
+
+// findFuncs parses the file and returns a slice of FuncExtent descriptors.
+func findFuncs(name string) ([]*FuncExtent, error) {
+ fset := token.NewFileSet()
+ parsedFile, err := parser.ParseFile(fset, name, nil, 0)
+ if err != nil {
+ return nil, err
+ }
+ visitor := &FuncVisitor{
+ fset: fset,
+ name: name,
+ astFile: parsedFile,
+ }
+ ast.Walk(visitor, visitor.astFile)
+ return visitor.funcs, nil
+}
+
+// FuncExtent describes a function's extent in the source by file and position.
+type FuncExtent struct {
+ name string
+ startLine int
+ startCol int
+ endLine int
+ endCol int
+}
+
+// FuncVisitor implements the visitor that builds the function position list for a file.
+type FuncVisitor struct {
+ fset *token.FileSet
+ name string // Name of file.
+ astFile *ast.File
+ funcs []*FuncExtent
+}
+
+// Visit implements the ast.Visitor interface.
+func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
+ switch n := node.(type) {
+ case *ast.FuncDecl:
+ if n.Body == nil {
+ // Do not count declarations of assembly functions.
+ break
+ }
+ start := v.fset.Position(n.Pos())
+ end := v.fset.Position(n.End())
+ fe := &FuncExtent{
+ name: n.Name.Name,
+ startLine: start.Line,
+ startCol: start.Column,
+ endLine: end.Line,
+ endCol: end.Column,
+ }
+ v.funcs = append(v.funcs, fe)
+ }
+ return v
+}
+
+// coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator.
+func (f *FuncExtent) coverage(profile *cover.Profile) (num, den int64) {
+ // We could avoid making this n^2 overall by doing a single scan and annotating the functions,
+ // but the sizes of the data structures is never very large and the scan is almost instantaneous.
+ var covered, total int64
+ // The blocks are sorted, so we can stop counting as soon as we reach the end of the relevant block.
+ for _, b := range profile.Blocks {
+ if b.StartLine > f.endLine || (b.StartLine == f.endLine && b.StartCol >= f.endCol) {
+ // Past the end of the function.
+ break
+ }
+ if b.EndLine < f.startLine || (b.EndLine == f.startLine && b.EndCol <= f.startCol) {
+ // Before the beginning of the function
+ continue
+ }
+ total += int64(b.NumStmt)
+ if b.Count > 0 {
+ covered += int64(b.NumStmt)
+ }
+ }
+ return covered, total
+}
+
+// Pkg describes a single package, compatible with the JSON output from 'go list'; see 'go help list'.
+type Pkg struct {
+ ImportPath string
+ Dir string
+ Error *struct {
+ Err string
+ }
+}
+
+func findPkgs(profiles []*cover.Profile) (map[string]*Pkg, error) {
+ // Run go list to find the location of every package we care about.
+ pkgs := make(map[string]*Pkg)
+ var list []string
+ for _, profile := range profiles {
+ if strings.HasPrefix(profile.FileName, ".") || filepath.IsAbs(profile.FileName) {
+ // Relative or absolute path.
+ continue
+ }
+ pkg := path.Dir(profile.FileName)
+ if _, ok := pkgs[pkg]; !ok {
+ pkgs[pkg] = nil
+ list = append(list, pkg)
+ }
+ }
+
+ if len(list) == 0 {
+ return pkgs, nil
+ }
+
+ // Note: usually run as "go tool cover" in which case $GOROOT is set,
+ // in which case runtime.GOROOT() does exactly what we want.
+ goTool := filepath.Join(runtime.GOROOT(), "bin/go")
+ cmd := exec.Command(goTool, append([]string{"list", "-e", "-json"}, list...)...)
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ stdout, err := cmd.Output()
+ if err != nil {
+ return nil, fmt.Errorf("cannot run go list: %v\n%s", err, stderr.Bytes())
+ }
+ dec := json.NewDecoder(bytes.NewReader(stdout))
+ for {
+ var pkg Pkg
+ err := dec.Decode(&pkg)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, fmt.Errorf("decoding go list json: %v", err)
+ }
+ pkgs[pkg.ImportPath] = &pkg
+ }
+ return pkgs, nil
+}
+
+// findFile finds the location of the named file in GOROOT, GOPATH etc.
+func findFile(pkgs map[string]*Pkg, file string) (string, error) {
+ if strings.HasPrefix(file, ".") || filepath.IsAbs(file) {
+ // Relative or absolute path.
+ return file, nil
+ }
+ pkg := pkgs[path.Dir(file)]
+ if pkg != nil {
+ if pkg.Dir != "" {
+ return filepath.Join(pkg.Dir, path.Base(file)), nil
+ }
+ if pkg.Error != nil {
+ return "", errors.New(pkg.Error.Err)
+ }
+ }
+ return "", fmt.Errorf("did not find package for %s in go list output", file)
+}
+
+func percent(covered, total int64) float64 {
+ if total == 0 {
+ total = 1 // Avoid zero denominator.
+ }
+ return 100.0 * float64(covered) / float64(total)
+}
diff --git a/src/cmd/cover/html.go b/src/cmd/cover/html.go
new file mode 100644
index 0000000..400a7d8
--- /dev/null
+++ b/src/cmd/cover/html.go
@@ -0,0 +1,306 @@
+// Copyright 2013 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 main
+
+import (
+ "bufio"
+ "cmd/internal/browser"
+ "fmt"
+ "html/template"
+ "io"
+ "math"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/tools/cover"
+)
+
+// htmlOutput reads the profile data from profile and generates an HTML
+// coverage report, writing it to outfile. If outfile is empty,
+// it writes the report to a temporary file and opens it in a web browser.
+func htmlOutput(profile, outfile string) error {
+ profiles, err := cover.ParseProfiles(profile)
+ if err != nil {
+ return err
+ }
+
+ var d templateData
+
+ dirs, err := findPkgs(profiles)
+ if err != nil {
+ return err
+ }
+
+ for _, profile := range profiles {
+ fn := profile.FileName
+ if profile.Mode == "set" {
+ d.Set = true
+ }
+ file, err := findFile(dirs, fn)
+ if err != nil {
+ return err
+ }
+ src, err := os.ReadFile(file)
+ if err != nil {
+ return fmt.Errorf("can't read %q: %v", fn, err)
+ }
+ var buf strings.Builder
+ err = htmlGen(&buf, src, profile.Boundaries(src))
+ if err != nil {
+ return err
+ }
+ d.Files = append(d.Files, &templateFile{
+ Name: fn,
+ Body: template.HTML(buf.String()),
+ Coverage: percentCovered(profile),
+ })
+ }
+
+ var out *os.File
+ if outfile == "" {
+ var dir string
+ dir, err = os.MkdirTemp("", "cover")
+ if err != nil {
+ return err
+ }
+ out, err = os.Create(filepath.Join(dir, "coverage.html"))
+ } else {
+ out, err = os.Create(outfile)
+ }
+ if err != nil {
+ return err
+ }
+ err = htmlTemplate.Execute(out, d)
+ if err2 := out.Close(); err == nil {
+ err = err2
+ }
+ if err != nil {
+ return err
+ }
+
+ if outfile == "" {
+ if !browser.Open("file://" + out.Name()) {
+ fmt.Fprintf(os.Stderr, "HTML output written to %s\n", out.Name())
+ }
+ }
+
+ return nil
+}
+
+// percentCovered returns, as a percentage, the fraction of the statements in
+// the profile covered by the test run.
+// In effect, it reports the coverage of a given source file.
+func percentCovered(p *cover.Profile) float64 {
+ var total, covered int64
+ for _, b := range p.Blocks {
+ total += int64(b.NumStmt)
+ if b.Count > 0 {
+ covered += int64(b.NumStmt)
+ }
+ }
+ if total == 0 {
+ return 0
+ }
+ return float64(covered) / float64(total) * 100
+}
+
+// htmlGen generates an HTML coverage report with the provided filename,
+// source code, and tokens, and writes it to the given Writer.
+func htmlGen(w io.Writer, src []byte, boundaries []cover.Boundary) error {
+ dst := bufio.NewWriter(w)
+ for i := range src {
+ for len(boundaries) > 0 && boundaries[0].Offset == i {
+ b := boundaries[0]
+ if b.Start {
+ n := 0
+ if b.Count > 0 {
+ n = int(math.Floor(b.Norm*9)) + 1
+ }
+ fmt.Fprintf(dst, `<span class="cov%v" title="%v">`, n, b.Count)
+ } else {
+ dst.WriteString("</span>")
+ }
+ boundaries = boundaries[1:]
+ }
+ switch b := src[i]; b {
+ case '>':
+ dst.WriteString("&gt;")
+ case '<':
+ dst.WriteString("&lt;")
+ case '&':
+ dst.WriteString("&amp;")
+ case '\t':
+ dst.WriteString(" ")
+ default:
+ dst.WriteByte(b)
+ }
+ }
+ return dst.Flush()
+}
+
+// rgb returns an rgb value for the specified coverage value
+// between 0 (no coverage) and 10 (max coverage).
+func rgb(n int) string {
+ if n == 0 {
+ return "rgb(192, 0, 0)" // Red
+ }
+ // Gradient from gray to green.
+ r := 128 - 12*(n-1)
+ g := 128 + 12*(n-1)
+ b := 128 + 3*(n-1)
+ return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b)
+}
+
+// colors generates the CSS rules for coverage colors.
+func colors() template.CSS {
+ var buf strings.Builder
+ for i := 0; i < 11; i++ {
+ fmt.Fprintf(&buf, ".cov%v { color: %v }\n", i, rgb(i))
+ }
+ return template.CSS(buf.String())
+}
+
+var htmlTemplate = template.Must(template.New("html").Funcs(template.FuncMap{
+ "colors": colors,
+}).Parse(tmplHTML))
+
+type templateData struct {
+ Files []*templateFile
+ Set bool
+}
+
+// PackageName returns a name for the package being shown.
+// It does this by choosing the penultimate element of the path
+// name, so foo.bar/baz/foo.go chooses 'baz'. This is cheap
+// and easy, avoids parsing the Go file, and gets a better answer
+// for package main. It returns the empty string if there is
+// a problem.
+func (td templateData) PackageName() string {
+ if len(td.Files) == 0 {
+ return ""
+ }
+ fileName := td.Files[0].Name
+ elems := strings.Split(fileName, "/") // Package path is always slash-separated.
+ // Return the penultimate non-empty element.
+ for i := len(elems) - 2; i >= 0; i-- {
+ if elems[i] != "" {
+ return elems[i]
+ }
+ }
+ return ""
+}
+
+type templateFile struct {
+ Name string
+ Body template.HTML
+ Coverage float64
+}
+
+const tmplHTML = `
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>{{$pkg := .PackageName}}{{if $pkg}}{{$pkg}}: {{end}}Go Coverage Report</title>
+ <style>
+ body {
+ background: black;
+ color: rgb(80, 80, 80);
+ }
+ body, pre, #legend span {
+ font-family: Menlo, monospace;
+ font-weight: bold;
+ }
+ #topbar {
+ background: black;
+ position: fixed;
+ top: 0; left: 0; right: 0;
+ height: 42px;
+ border-bottom: 1px solid rgb(80, 80, 80);
+ }
+ #content {
+ margin-top: 50px;
+ }
+ #nav, #legend {
+ float: left;
+ margin-left: 10px;
+ }
+ #legend {
+ margin-top: 12px;
+ }
+ #nav {
+ margin-top: 10px;
+ }
+ #legend span {
+ margin: 0 5px;
+ }
+ {{colors}}
+ </style>
+ </head>
+ <body>
+ <div id="topbar">
+ <div id="nav">
+ <select id="files">
+ {{range $i, $f := .Files}}
+ <option value="file{{$i}}">{{$f.Name}} ({{printf "%.1f" $f.Coverage}}%)</option>
+ {{end}}
+ </select>
+ </div>
+ <div id="legend">
+ <span>not tracked</span>
+ {{if .Set}}
+ <span class="cov0">not covered</span>
+ <span class="cov8">covered</span>
+ {{else}}
+ <span class="cov0">no coverage</span>
+ <span class="cov1">low coverage</span>
+ <span class="cov2">*</span>
+ <span class="cov3">*</span>
+ <span class="cov4">*</span>
+ <span class="cov5">*</span>
+ <span class="cov6">*</span>
+ <span class="cov7">*</span>
+ <span class="cov8">*</span>
+ <span class="cov9">*</span>
+ <span class="cov10">high coverage</span>
+ {{end}}
+ </div>
+ </div>
+ <div id="content">
+ {{range $i, $f := .Files}}
+ <pre class="file" id="file{{$i}}" style="display: none">{{$f.Body}}</pre>
+ {{end}}
+ </div>
+ </body>
+ <script>
+ (function() {
+ var files = document.getElementById('files');
+ var visible;
+ files.addEventListener('change', onChange, false);
+ function select(part) {
+ if (visible)
+ visible.style.display = 'none';
+ visible = document.getElementById(part);
+ if (!visible)
+ return;
+ files.value = part;
+ visible.style.display = 'block';
+ location.hash = part;
+ }
+ function onChange() {
+ select(files.value);
+ window.scrollTo(0, 0);
+ }
+ if (location.hash != "") {
+ select(location.hash.substr(1));
+ }
+ if (!visible) {
+ select("file0");
+ }
+ })();
+ </script>
+</html>
+`
diff --git a/src/cmd/cover/pkgname_test.go b/src/cmd/cover/pkgname_test.go
new file mode 100644
index 0000000..1c731ad
--- /dev/null
+++ b/src/cmd/cover/pkgname_test.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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 main
+
+import "testing"
+
+func TestPackageName(t *testing.T) {
+ var tests = []struct {
+ fileName, pkgName string
+ }{
+ {"", ""},
+ {"///", ""},
+ {"fmt", ""}, // No Go file, improper form.
+ {"fmt/foo.go", "fmt"},
+ {"encoding/binary/foo.go", "binary"},
+ {"encoding/binary/////foo.go", "binary"},
+ }
+ var tf templateFile
+ for _, test := range tests {
+ tf.Name = test.fileName
+ td := templateData{
+ Files: []*templateFile{&tf},
+ }
+ got := td.PackageName()
+ if got != test.pkgName {
+ t.Errorf("%s: got %s want %s", test.fileName, got, test.pkgName)
+ }
+ }
+}
diff --git a/src/cmd/cover/testdata/directives.go b/src/cmd/cover/testdata/directives.go
new file mode 100644
index 0000000..dfb7b8e
--- /dev/null
+++ b/src/cmd/cover/testdata/directives.go
@@ -0,0 +1,40 @@
+// Copyright 2017 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.
+
+// This file is processed by the cover command, then a test verifies that
+// all compiler directives are preserved and positioned appropriately.
+
+//go:a
+
+//go:b
+package main
+
+//go:c1
+
+//go:c2
+//doc
+func c() {
+}
+
+//go:d1
+
+//doc
+//go:d2
+type d int
+
+//go:e1
+
+//doc
+//go:e2
+type (
+ e int
+ f int
+)
+
+//go:_empty1
+//doc
+//go:_empty2
+type ()
+
+//go:f
diff --git a/src/cmd/cover/testdata/html/html.go b/src/cmd/cover/testdata/html/html.go
new file mode 100644
index 0000000..2057825
--- /dev/null
+++ b/src/cmd/cover/testdata/html/html.go
@@ -0,0 +1,30 @@
+package html
+
+import "fmt"
+
+// This file is tested by html_test.go.
+// The comments below are markers for extracting the annotated source
+// from the HTML output.
+
+// This is a regression test for incorrect sorting of boundaries
+// that coincide, specifically for empty select clauses.
+// START f
+func f() {
+ ch := make(chan int)
+ select {
+ case <-ch:
+ default:
+ }
+}
+
+// END f
+
+// https://golang.org/issue/25767
+// START g
+func g() {
+ if false {
+ fmt.Printf("Hello")
+ }
+}
+
+// END g
diff --git a/src/cmd/cover/testdata/html/html.golden b/src/cmd/cover/testdata/html/html.golden
new file mode 100644
index 0000000..84377d1
--- /dev/null
+++ b/src/cmd/cover/testdata/html/html.golden
@@ -0,0 +1,18 @@
+// START f
+func f() <span class="cov8" title="1">{
+ ch := make(chan int)
+ select </span>{
+ case &lt;-ch:<span class="cov0" title="0"></span>
+ default:<span class="cov8" title="1"></span>
+ }
+}
+
+// END f
+// START g
+func g() <span class="cov8" title="1">{
+ if false </span><span class="cov0" title="0">{
+ fmt.Printf("Hello")
+ }</span>
+}
+
+// END g
diff --git a/src/cmd/cover/testdata/html/html_test.go b/src/cmd/cover/testdata/html/html_test.go
new file mode 100644
index 0000000..c15561f
--- /dev/null
+++ b/src/cmd/cover/testdata/html/html_test.go
@@ -0,0 +1,8 @@
+package html
+
+import "testing"
+
+func TestAll(t *testing.T) {
+ f()
+ g()
+}
diff --git a/src/cmd/cover/testdata/main.go b/src/cmd/cover/testdata/main.go
new file mode 100644
index 0000000..be74b4a
--- /dev/null
+++ b/src/cmd/cover/testdata/main.go
@@ -0,0 +1,116 @@
+// Copyright 2013 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.
+
+// Test runner for coverage test. This file is not coverage-annotated; test.go is.
+// It knows the coverage counter is called
+// "thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest".
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ testAll()
+ verify()
+}
+
+type block struct {
+ count uint32
+ line uint32
+}
+
+var counters = make(map[block]bool)
+
+// shorthand for the long counter variable.
+var coverTest = &thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest
+
+// check records the location and expected value for a counter.
+func check(line, count uint32) {
+ b := block{
+ count,
+ line,
+ }
+ counters[b] = true
+}
+
+// checkVal is a version of check that returns its extra argument,
+// so it can be used in conditionals.
+func checkVal(line, count uint32, val int) int {
+ b := block{
+ count,
+ line,
+ }
+ counters[b] = true
+ return val
+}
+
+var PASS = true
+
+// verify checks the expected counts against the actual. It runs after the test has completed.
+func verify() {
+ for b := range counters {
+ got, index := count(b.line)
+ if b.count == anything && got != 0 {
+ got = anything
+ }
+ if got != b.count {
+ fmt.Fprintf(os.Stderr, "test_go:%d expected count %d got %d [counter %d]\n", b.line, b.count, got, index)
+ PASS = false
+ }
+ }
+ verifyPanic()
+ if !PASS {
+ fmt.Fprintf(os.Stderr, "FAIL\n")
+ os.Exit(2)
+ }
+}
+
+// verifyPanic is a special check for the known counter that should be
+// after the panic call in testPanic.
+func verifyPanic() {
+ if coverTest.Count[panicIndex-1] != 1 {
+ // Sanity check for test before panic.
+ fmt.Fprintf(os.Stderr, "bad before panic")
+ PASS = false
+ }
+ if coverTest.Count[panicIndex] != 0 {
+ fmt.Fprintf(os.Stderr, "bad at panic: %d should be 0\n", coverTest.Count[panicIndex])
+ PASS = false
+ }
+ if coverTest.Count[panicIndex+1] != 1 {
+ fmt.Fprintf(os.Stderr, "bad after panic")
+ PASS = false
+ }
+}
+
+// count returns the count and index for the counter at the specified line.
+func count(line uint32) (uint32, int) {
+ // Linear search is fine. Choose perfect fit over approximate.
+ // We can have a closing brace for a range on the same line as a condition for an "else if"
+ // and we don't want that brace to steal the count for the condition on the "if".
+ // Therefore we test for a perfect (lo==line && hi==line) match, but if we can't
+ // find that we take the first imperfect match.
+ index := -1
+ indexLo := uint32(1e9)
+ for i := range coverTest.Count {
+ lo, hi := coverTest.Pos[3*i], coverTest.Pos[3*i+1]
+ if lo == line && line == hi {
+ return coverTest.Count[i], i
+ }
+ // Choose the earliest match (the counters are in unpredictable order).
+ if lo <= line && line <= hi && indexLo > lo {
+ index = i
+ indexLo = lo
+ }
+ }
+ if index == -1 {
+ fmt.Fprintln(os.Stderr, "cover_test: no counter for line", line)
+ PASS = false
+ return 0, 0
+ }
+ return coverTest.Count[index], index
+}
diff --git a/src/cmd/cover/testdata/p.go b/src/cmd/cover/testdata/p.go
new file mode 100644
index 0000000..ce3a8c0
--- /dev/null
+++ b/src/cmd/cover/testdata/p.go
@@ -0,0 +1,27 @@
+// Copyright 2017 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.
+
+// A package such that there are 3 functions with zero total and covered lines.
+// And one with 1 total and covered lines. Reproduces issue #20515.
+package p
+
+//go:noinline
+func A() {
+
+}
+
+//go:noinline
+func B() {
+
+}
+
+//go:noinline
+func C() {
+
+}
+
+//go:noinline
+func D() int64 {
+ return 42
+}
diff --git a/src/cmd/cover/testdata/pkgcfg/a/a.go b/src/cmd/cover/testdata/pkgcfg/a/a.go
new file mode 100644
index 0000000..44c380b
--- /dev/null
+++ b/src/cmd/cover/testdata/pkgcfg/a/a.go
@@ -0,0 +1,28 @@
+package a
+
+type Atyp int
+
+func (ap *Atyp) Set(q int) {
+ *ap = Atyp(q)
+}
+
+func (ap Atyp) Get() int {
+ inter := func(q Atyp) int {
+ return int(q)
+ }
+ return inter(ap)
+}
+
+var afunc = func(x int) int {
+ return x + 1
+}
+var Avar = afunc(42)
+
+func A(x int) int {
+ if x == 0 {
+ return 22
+ } else if x == 1 {
+ return 33
+ }
+ return 44
+}
diff --git a/src/cmd/cover/testdata/pkgcfg/a/a2.go b/src/cmd/cover/testdata/pkgcfg/a/a2.go
new file mode 100644
index 0000000..e6b2fc1
--- /dev/null
+++ b/src/cmd/cover/testdata/pkgcfg/a/a2.go
@@ -0,0 +1,8 @@
+package a
+
+func A2() {
+ {
+ }
+ {
+ }
+}
diff --git a/src/cmd/cover/testdata/pkgcfg/a/a_test.go b/src/cmd/cover/testdata/pkgcfg/a/a_test.go
new file mode 100644
index 0000000..a1608e0
--- /dev/null
+++ b/src/cmd/cover/testdata/pkgcfg/a/a_test.go
@@ -0,0 +1,14 @@
+package a_test
+
+import (
+ "cfg/a"
+ "testing"
+)
+
+func TestA(t *testing.T) {
+ a.A(0)
+ var aat a.Atyp
+ at := &aat
+ at.Set(42)
+ println(at.Get())
+}
diff --git a/src/cmd/cover/testdata/pkgcfg/b/b.go b/src/cmd/cover/testdata/pkgcfg/b/b.go
new file mode 100644
index 0000000..9e330ee
--- /dev/null
+++ b/src/cmd/cover/testdata/pkgcfg/b/b.go
@@ -0,0 +1,10 @@
+package b
+
+func B(x int) int {
+ if x == 0 {
+ return 22
+ } else if x == 1 {
+ return 33
+ }
+ return 44
+}
diff --git a/src/cmd/cover/testdata/pkgcfg/b/b_test.go b/src/cmd/cover/testdata/pkgcfg/b/b_test.go
new file mode 100644
index 0000000..7bdb73b
--- /dev/null
+++ b/src/cmd/cover/testdata/pkgcfg/b/b_test.go
@@ -0,0 +1,9 @@
+package b
+
+import "testing"
+
+func TestB(t *testing.T) {
+ B(0)
+ B(1)
+ B(2)
+}
diff --git a/src/cmd/cover/testdata/pkgcfg/main/main.go b/src/cmd/cover/testdata/pkgcfg/main/main.go
new file mode 100644
index 0000000..a908931
--- /dev/null
+++ b/src/cmd/cover/testdata/pkgcfg/main/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "cfg/a"
+ "cfg/b"
+)
+
+func main() {
+ a.A(2)
+ a.A(1)
+ a.A(0)
+ b.B(1)
+ b.B(0)
+ println("done")
+}
diff --git a/src/cmd/cover/testdata/profile.cov b/src/cmd/cover/testdata/profile.cov
new file mode 100644
index 0000000..db08602
--- /dev/null
+++ b/src/cmd/cover/testdata/profile.cov
@@ -0,0 +1,5 @@
+mode: set
+./testdata/p.go:10.10,12.2 0 0
+./testdata/p.go:15.10,17.2 0 0
+./testdata/p.go:20.10,22.2 0 0
+./testdata/p.go:25.16,27.2 1 1
diff --git a/src/cmd/cover/testdata/test.go b/src/cmd/cover/testdata/test.go
new file mode 100644
index 0000000..0e1dbc6
--- /dev/null
+++ b/src/cmd/cover/testdata/test.go
@@ -0,0 +1,300 @@
+// Copyright 2013 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.
+
+// This program is processed by the cover command, and then testAll is called.
+// The test driver in main.go can then compare the coverage statistics with expectation.
+
+// The word LINE is replaced by the line number in this file. When the file is executed,
+// the coverage processing has changed the line numbers, so we can't use runtime.Caller.
+
+package main
+
+import _ "unsafe" // for go:linkname
+
+//go:linkname some_name some_name
+var some_name int
+
+const anything = 1e9 // Just some unlikely value that means "we got here, don't care how often"
+
+func testAll() {
+ testSimple()
+ testBlockRun()
+ testIf()
+ testFor()
+ testRange()
+ testSwitch()
+ testTypeSwitch()
+ testSelect1()
+ testSelect2()
+ testPanic()
+ testEmptySwitches()
+ testFunctionLiteral()
+ testGoto()
+}
+
+// The indexes of the counters in testPanic are known to main.go
+const panicIndex = 3
+
+// This test appears first because the index of its counters is known to main.go
+func testPanic() {
+ defer func() {
+ recover()
+ }()
+ check(LINE, 1)
+ panic("should not get next line")
+ check(LINE, 0) // this is GoCover.Count[panicIndex]
+ // The next counter is in testSimple and it will be non-zero.
+ // If the panic above does not trigger a counter, the test will fail
+ // because GoCover.Count[panicIndex] will be the one in testSimple.
+}
+
+func testSimple() {
+ check(LINE, 1)
+}
+
+func testIf() {
+ if true {
+ check(LINE, 1)
+ } else {
+ check(LINE, 0)
+ }
+ if false {
+ check(LINE, 0)
+ } else {
+ check(LINE, 1)
+ }
+ for i := 0; i < 3; i++ {
+ if checkVal(LINE, 3, i) <= 2 {
+ check(LINE, 3)
+ }
+ if checkVal(LINE, 3, i) <= 1 {
+ check(LINE, 2)
+ }
+ if checkVal(LINE, 3, i) <= 0 {
+ check(LINE, 1)
+ }
+ }
+ for i := 0; i < 3; i++ {
+ if checkVal(LINE, 3, i) <= 1 {
+ check(LINE, 2)
+ } else {
+ check(LINE, 1)
+ }
+ }
+ for i := 0; i < 3; i++ {
+ if checkVal(LINE, 3, i) <= 0 {
+ check(LINE, 1)
+ } else if checkVal(LINE, 2, i) <= 1 {
+ check(LINE, 1)
+ } else if checkVal(LINE, 1, i) <= 2 {
+ check(LINE, 1)
+ } else if checkVal(LINE, 0, i) <= 3 {
+ check(LINE, 0)
+ }
+ }
+ if func(a, b int) bool { return a < b }(3, 4) {
+ check(LINE, 1)
+ }
+}
+
+func testFor() {
+ for i := 0; i < 10; func() { i++; check(LINE, 10) }() {
+ check(LINE, 10)
+ }
+}
+
+func testRange() {
+ for _, f := range []func(){
+ func() { check(LINE, 1) },
+ } {
+ f()
+ check(LINE, 1)
+ }
+}
+
+func testBlockRun() {
+ check(LINE, 1)
+ {
+ check(LINE, 1)
+ }
+ {
+ check(LINE, 1)
+ }
+ check(LINE, 1)
+ {
+ check(LINE, 1)
+ }
+ {
+ check(LINE, 1)
+ }
+ check(LINE, 1)
+}
+
+func testSwitch() {
+ for i := 0; i < 5; func() { i++; check(LINE, 5) }() {
+ goto label2
+ label1:
+ goto label1
+ label2:
+ switch i {
+ case 0:
+ check(LINE, 1)
+ case 1:
+ check(LINE, 1)
+ case 2:
+ check(LINE, 1)
+ default:
+ check(LINE, 2)
+ }
+ }
+}
+
+func testTypeSwitch() {
+ var x = []any{1, 2.0, "hi"}
+ for _, v := range x {
+ switch func() { check(LINE, 3) }(); v.(type) {
+ case int:
+ check(LINE, 1)
+ case float64:
+ check(LINE, 1)
+ case string:
+ check(LINE, 1)
+ case complex128:
+ check(LINE, 0)
+ default:
+ check(LINE, 0)
+ }
+ }
+}
+
+func testSelect1() {
+ c := make(chan int)
+ go func() {
+ for i := 0; i < 1000; i++ {
+ c <- i
+ }
+ }()
+ for {
+ select {
+ case <-c:
+ check(LINE, anything)
+ case <-c:
+ check(LINE, anything)
+ default:
+ check(LINE, 1)
+ return
+ }
+ }
+}
+
+func testSelect2() {
+ c1 := make(chan int, 1000)
+ c2 := make(chan int, 1000)
+ for i := 0; i < 1000; i++ {
+ c1 <- i
+ c2 <- i
+ }
+ for {
+ select {
+ case <-c1:
+ check(LINE, 1000)
+ case <-c2:
+ check(LINE, 1000)
+ default:
+ check(LINE, 1)
+ return
+ }
+ }
+}
+
+// Empty control statements created syntax errors. This function
+// is here just to be sure that those are handled correctly now.
+func testEmptySwitches() {
+ check(LINE, 1)
+ switch 3 {
+ }
+ check(LINE, 1)
+ switch i := (any)(3).(int); i {
+ }
+ check(LINE, 1)
+ c := make(chan int)
+ go func() {
+ check(LINE, 1)
+ c <- 1
+ select {}
+ }()
+ <-c
+ check(LINE, 1)
+}
+
+func testFunctionLiteral() {
+ a := func(f func()) error {
+ f()
+ f()
+ return nil
+ }
+
+ b := func(f func()) bool {
+ f()
+ f()
+ return true
+ }
+
+ check(LINE, 1)
+ a(func() {
+ check(LINE, 2)
+ })
+
+ if err := a(func() {
+ check(LINE, 2)
+ }); err != nil {
+ }
+
+ switch b(func() {
+ check(LINE, 2)
+ }) {
+ }
+
+ x := 2
+ switch x {
+ case func() int { check(LINE, 1); return 1 }():
+ check(LINE, 0)
+ panic("2=1")
+ case func() int { check(LINE, 1); return 2 }():
+ check(LINE, 1)
+ case func() int { check(LINE, 0); return 3 }():
+ check(LINE, 0)
+ panic("2=3")
+ }
+}
+
+func testGoto() {
+ for i := 0; i < 2; i++ {
+ if i == 0 {
+ goto Label
+ }
+ check(LINE, 1)
+ Label:
+ check(LINE, 2)
+ }
+ // Now test that we don't inject empty statements
+ // between a label and a loop.
+loop:
+ for {
+ check(LINE, 1)
+ break loop
+ }
+}
+
+// This comment didn't appear in generated go code.
+func haha() {
+ // Needed for cover to add counter increment here.
+ _ = 42
+}
+
+// Some someFunction.
+//
+//go:nosplit
+func someFunction() {
+}