summaryrefslogtreecommitdiffstats
path: root/src/cmd/cover
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:16:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:16:40 +0000
commit47ab3d4a42e9ab51c465c4322d2ec233f6324e6b (patch)
treea61a0ffd83f4a3def4b36e5c8e99630c559aa723 /src/cmd/cover
parentInitial commit. (diff)
downloadgolang-1.18-47ab3d4a42e9ab51c465c4322d2ec233f6324e6b.tar.xz
golang-1.18-47ab3d4a42e9ab51c465c4322d2ec233f6324e6b.zip
Adding upstream version 1.18.10.upstream/1.18.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/cover')
-rw-r--r--src/cmd/cover/cover.go729
-rw-r--r--src/cmd/cover/cover_test.go573
-rw-r--r--src/cmd/cover/doc.go25
-rw-r--r--src/cmd/cover/func.go248
-rw-r--r--src/cmd/cover/html.go307
-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/profile.cov5
-rw-r--r--src/cmd/cover/testdata/test.go300
-rw-r--r--src/cmd/cover/testdata/toolexec.go33
15 files changed, 2490 insertions, 0 deletions
diff --git a/src/cmd/cover/cover.go b/src/cmd/cover/cover.go
new file mode 100644
index 0000000..9c8529f
--- /dev/null
+++ b/src/cmd/cover/cover.go
@@ -0,0 +1,729 @@
+// 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"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io"
+ "log"
+ "os"
+ "sort"
+
+ "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
+(what go test -cover does):
+ go tool cover -mode=set -var=CoverageVariableName program.go
+`
+
+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; default: stdout")
+ htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
+ funcOut = flag.String("func", "", "output coverage profile information for each function")
+)
+
+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
+ flag.Parse()
+
+ // 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.Arg(0))
+ 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
+ default:
+ return fmt.Errorf("unknown -mode %v", *mode)
+ }
+
+ if flag.NArg() == 0 {
+ return fmt.Errorf("missing source file")
+ } else if flag.NArg() == 1 {
+ return nil
+ }
+ } else if flag.NArg() == 0 {
+ return nil
+ }
+ return fmt.Errorf("too many arguments")
+}
+
+// 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
+}
+
+// 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
+}
+
+// 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.
+ if n.Name.Name == "_" {
+ return nil
+ }
+ }
+ return f
+}
+
+func annotate(name string) {
+ 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 *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.
+ file.edit.Insert(file.offset(file.astFile.Name.End()),
+ fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
+ }
+
+ ast.Walk(file, file.astFile)
+ newContent := file.edit.Bytes()
+
+ fd := os.Stdout
+ if *output != "" {
+ var err error
+ fd, err = os.Create(*output)
+ if err != nil {
+ log.Fatalf("cover: %s", err)
+ }
+ }
+
+ fmt.Fprintf(fd, "//line %s: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)
+}
+
+// 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("%s.AddUint32(&%s, 1)", atomicPackageName, counter)
+}
+
+// newCounter creates a new counter expression of the appropriate form.
+func (f *File) newCounter(start, end token.Pos, numStmt int) string {
+ 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) {
+ // 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")
+
+ // 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(w, "var _ = %s.LoadUint32\n", atomicPackageName)
+ }
+}
+
+// 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
+}
diff --git a/src/cmd/cover/cover_test.go b/src/cmd/cover/cover_test.go
new file mode 100644
index 0000000..86c95d1
--- /dev/null
+++ b/src/cmd/cover/cover_test.go
@@ -0,0 +1,573 @@
+// 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"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "sync"
+ "testing"
+)
+
+const (
+ // Data directory, also the package directory for the test.
+ testdata = "testdata"
+)
+
+var (
+ // Input files.
+ testMain = filepath.Join(testdata, "main.go")
+ testTest = filepath.Join(testdata, "test.go")
+ coverProfile = filepath.Join(testdata, "profile.cov")
+ toolexecSource = filepath.Join(testdata, "toolexec.go")
+
+ // The HTML test files are in a separate directory
+ // so they are a complete package.
+ htmlGolden = filepath.Join(testdata, "html", "html.golden")
+
+ // Temporary files.
+ tmpTestMain string
+ coverInput string
+ coverOutput string
+ htmlProfile string
+ htmlHTML string
+ htmlUDir string
+ htmlU string
+ htmlUTest string
+ htmlUProfile string
+ htmlUHTML string
+ lineDupDir string
+ lineDupGo string
+ lineDupTestGo string
+ lineDupProfile string
+)
+
+var (
+ // testTempDir is a temporary directory created in TestMain.
+ testTempDir string
+
+ // testcover is a newly built version of the cover program.
+ testcover string
+
+ // toolexec is a program to use as the go tool's -toolexec argument.
+ toolexec string
+
+ // testcoverErr records an error building testcover or toolexec.
+ testcoverErr error
+
+ // testcoverOnce is used to build testcover once.
+ testcoverOnce sync.Once
+
+ // toolexecArg is the argument to pass to the go tool.
+ toolexecArg string
+)
+
+var debug = flag.Bool("debug", false, "keep rewritten files for debugging")
+
+// We use TestMain to set up a temporary directory and remove it when
+// the tests are done.
+func TestMain(m *testing.M) {
+ dir, err := os.MkdirTemp("", "go-testcover")
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ os.Setenv("GOPATH", filepath.Join(dir, "_gopath"))
+
+ testTempDir = dir
+
+ tmpTestMain = filepath.Join(dir, "main.go")
+ coverInput = filepath.Join(dir, "test_line.go")
+ coverOutput = filepath.Join(dir, "test_cover.go")
+ htmlProfile = filepath.Join(dir, "html.cov")
+ htmlHTML = filepath.Join(dir, "html.html")
+ 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")
+ lineDupDir = filepath.Join(dir, "linedup")
+ lineDupGo = filepath.Join(lineDupDir, "linedup.go")
+ lineDupTestGo = filepath.Join(lineDupDir, "linedup_test.go")
+ lineDupProfile = filepath.Join(lineDupDir, "linedup.out")
+
+ status := m.Run()
+
+ if !*debug {
+ os.RemoveAll(dir)
+ }
+
+ os.Exit(status)
+}
+
+// buildCover builds a version of the cover program for testing.
+// This ensures that "go test cmd/cover" tests the current cmd/cover.
+func buildCover(t *testing.T) {
+ t.Helper()
+ testenv.MustHaveGoBuild(t)
+ testcoverOnce.Do(func() {
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ var err1, err2 error
+ go func() {
+ defer wg.Done()
+ testcover = filepath.Join(testTempDir, "cover.exe")
+ t.Logf("running [go build -o %s]", testcover)
+ out, err := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover).CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ err1 = err
+ }()
+
+ go func() {
+ defer wg.Done()
+ toolexec = filepath.Join(testTempDir, "toolexec.exe")
+ t.Logf("running [go -build -o %s %s]", toolexec, toolexecSource)
+ out, err := exec.Command(testenv.GoToolPath(t), "build", "-o", toolexec, toolexecSource).CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ err2 = err
+ }()
+
+ wg.Wait()
+
+ testcoverErr = err1
+ if err2 != nil && err1 == nil {
+ testcoverErr = err2
+ }
+
+ toolexecArg = "-toolexec=" + toolexec + " " + testcover
+ })
+ if testcoverErr != nil {
+ t.Fatal("failed to build testcover or toolexec program:", testcoverErr)
+ }
+}
+
+// Run this shell script, but do it in Go so it can be run by "go test".
+//
+// replace the word LINE with the line number < testdata/test.go > testdata/test_line.go
+// go build -o testcover
+// 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) {
+ t.Parallel()
+ testenv.MustHaveGoRun(t)
+ buildCover(t)
+
+ // Read in the test file (testTest) and write it, with LINEs specified, to coverInput.
+ 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{}}"))
+
+ 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
+ cmd := exec.Command(testcover, "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
+ run(cmd, t)
+
+ cmd = exec.Command(testcover, "-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 testTempDir, so that it is in the same directory
+ // as coverOutput.
+ b, err := os.ReadFile(testMain)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(tmpTestMain, b, 0444); err != nil {
+ t.Fatal(err)
+ }
+
+ // go run ./testdata/main.go ./testdata/test.go
+ cmd = exec.Command(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) {
+ t.Parallel()
+ buildCover(t)
+
+ // 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 := exec.Command(testcover, "-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) {
+ t.Parallel()
+ buildCover(t)
+ // testcover -func ./testdata/profile.cov
+ cmd := exec.Command(testcover, "-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) {
+ t.Parallel()
+ testenv.MustHaveGoRun(t)
+ buildCover(t)
+
+ // go test -coverprofile testdata/html/html.cov cmd/cover/testdata/html
+ cmd := exec.Command(testenv.GoToolPath(t), "test", toolexecArg, "-coverprofile", htmlProfile, "cmd/cover/testdata/html")
+ run(cmd, t)
+ // testcover -html testdata/html/html.cov -o testdata/html/html.html
+ cmd = exec.Command(testcover, "-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 bytes.Buffer
+ 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())
+ }
+ 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) {
+ t.Parallel()
+ testenv.MustHaveGoRun(t)
+ buildCover(t)
+
+ 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 := exec.Command(testenv.GoToolPath(t), "test", toolexecArg, "-covermode=count", "-coverprofile", htmlUProfile)
+ cmd.Dir = htmlUDir
+ run(cmd, t)
+
+ // testcover -html TMPDIR/htmlunformatted.cov -o unformatted.html
+ cmd = exec.Command(testcover, "-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) {
+ t.Parallel()
+ testenv.MustHaveGoRun(t)
+ buildCover(t)
+
+ 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 := exec.Command(testenv.GoToolPath(t), "test", toolexecArg, "-cover", "-covermode", "count", "-coverprofile", lineDupProfile)
+ cmd.Dir = lineDupDir
+ run(cmd, t)
+
+ // testcover -func=TMPDIR/linedup.out
+ cmd = exec.Command(testcover, "-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)
+ }
+}
diff --git a/src/cmd/cover/doc.go b/src/cmd/cover/doc.go
new file mode 100644
index 0000000..e2c8494
--- /dev/null
+++ b/src/cmd/cover/doc.go
@@ -0,0 +1,25 @@
+// 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.
+It operates on one Go source file at a time, computing 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/func.go b/src/cmd/cover/func.go
new file mode 100644
index 0000000..76a16b3
--- /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"
+ exec "internal/execabs"
+ "io"
+ "os"
+ "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..3c1d17e
--- /dev/null
+++ b/src/cmd/cover/html.go
@@ -0,0 +1,307 @@
+// 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"
+ "bytes"
+ "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 bytes.Buffer
+ 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/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() {
+}
diff --git a/src/cmd/cover/testdata/toolexec.go b/src/cmd/cover/testdata/toolexec.go
new file mode 100644
index 0000000..458adae
--- /dev/null
+++ b/src/cmd/cover/testdata/toolexec.go
@@ -0,0 +1,33 @@
+// Copyright 2018 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.
+
+// The toolexec program is a helper program for cmd/cover tests.
+// It is used so that the go tool will call the newly built version
+// of the cover program, rather than the installed one.
+//
+// The tests arrange to run the go tool with the argument
+// -toolexec="/path/to/toolexec /path/to/testcover"
+// The go tool will invoke this program (compiled into /path/to/toolexec)
+// with the arguments shown above followed by the command to run.
+// This program will check whether it is expected to run the cover
+// program, and if so replace it with /path/to/testcover.
+package main
+
+import (
+ exec "internal/execabs"
+ "os"
+ "strings"
+)
+
+func main() {
+ if strings.HasSuffix(strings.TrimSuffix(os.Args[2], ".exe"), "cover") {
+ os.Args[2] = os.Args[1]
+ }
+ cmd := exec.Command(os.Args[2], os.Args[3:]...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ os.Exit(1)
+ }
+}