diff options
Diffstat (limited to 'cmd/cover')
-rw-r--r-- | cmd/cover/README.md | 3 | ||||
-rw-r--r-- | cmd/cover/cover.go | 722 | ||||
-rw-r--r-- | cmd/cover/cover_test.go | 108 | ||||
-rw-r--r-- | cmd/cover/doc.go | 24 | ||||
-rw-r--r-- | cmd/cover/func.go | 166 | ||||
-rw-r--r-- | cmd/cover/html.go | 284 | ||||
-rw-r--r-- | cmd/cover/testdata/main.go | 112 | ||||
-rw-r--r-- | cmd/cover/testdata/test.go | 218 |
8 files changed, 0 insertions, 1637 deletions
diff --git a/cmd/cover/README.md b/cmd/cover/README.md deleted file mode 100644 index 62e60279a..000000000 --- a/cmd/cover/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Deprecated - -NOTE: For Go releases 1.5 and later, this tool lives in the standard repository. The code here is not maintained. diff --git a/cmd/cover/cover.go b/cmd/cover/cover.go deleted file mode 100644 index 42a7e37aa..000000000 --- a/cmd/cover/cover.go +++ /dev/null @@ -1,722 +0,0 @@ -// 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/printer" - "go/token" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "sort" - "strconv" - "strings" -) - -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, ast.Expr) ast.Stmt - -const ( - atomicPackagePath = "sync/atomic" - atomicPackageName = "_cover_atomic_" -) - -func main() { - 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 *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 - atomicPkg string // Package name for "sync/atomic" in this file. -} - -// 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) - clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false) - } - return f - case *ast.CommClause: // select - for _, n := range n.List { - clause := n.(*ast.CommClause) - clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false) - } - return f - } - } - n.List = f.addCounters(n.Lbrace, n.Rbrace+1, n.List, true) // +1 to step past closing brace. - case *ast.IfStmt: - 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 { - // } - // } - switch stmt := n.Else.(type) { - case *ast.IfStmt: - block := &ast.BlockStmt{ - Lbrace: n.Body.End(), // Start at end of the "if" block so the covered part looks like it starts at the "else". - List: []ast.Stmt{stmt}, - Rbrace: stmt.End(), - } - n.Else = block - case *ast.BlockStmt: - stmt.Lbrace = n.Body.End() // Start at end of the "if" block so the covered part looks like it starts at the "else". - 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 { - 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 { - return nil - } - } - return f -} - -// unquote returns the unquoted string. -func unquote(s string) string { - t, err := strconv.Unquote(s) - if err != nil { - log.Fatalf("cover: improperly quoted string %q\n", s) - } - return t -} - -// addImport adds an import for the specified path, if one does not already exist, and returns -// the local package name. -func (f *File) addImport(path string) string { - // Does the package already import it? - for _, s := range f.astFile.Imports { - if unquote(s.Path.Value) == path { - if s.Name != nil { - return s.Name.Name - } - return filepath.Base(path) - } - } - newImport := &ast.ImportSpec{ - Name: ast.NewIdent(atomicPackageName), - Path: &ast.BasicLit{ - Kind: token.STRING, - Value: fmt.Sprintf("%q", path), - }, - } - impDecl := &ast.GenDecl{ - Tok: token.IMPORT, - Specs: []ast.Spec{ - newImport, - }, - } - // Make the new import the first Decl in the file. - astFile := f.astFile - astFile.Decls = append(astFile.Decls, nil) - copy(astFile.Decls[1:], astFile.Decls[0:]) - astFile.Decls[0] = impDecl - astFile.Imports = append(astFile.Imports, newImport) - - // Now refer to the package, just in case it ends up unused. - // That is, append to the end of the file the declaration - // var _ = _cover_atomic_.AddUint32 - reference := &ast.GenDecl{ - Tok: token.VAR, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ - ast.NewIdent("_"), - }, - Values: []ast.Expr{ - &ast.SelectorExpr{ - X: ast.NewIdent(atomicPackageName), - Sel: ast.NewIdent("AddUint32"), - }, - }, - }, - }, - } - astFile.Decls = append(astFile.Decls, reference) - return atomicPackageName -} - -var slashslash = []byte("//") - -// initialComments returns the prefix of content containing only -// whitespace and line comments. Any +build directives must appear -// within this region. This approach is more reliable than using -// go/printer to print a modified AST containing comments. -// -func initialComments(content []byte) []byte { - // Derived from go/build.Context.shouldBuild. - end := 0 - p := content - for len(p) > 0 { - line := p - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, p = line[:i], p[i+1:] - } else { - p = p[len(p):] - } - line = bytes.TrimSpace(line) - if len(line) == 0 { // Blank line. - end = len(content) - len(p) - continue - } - if !bytes.HasPrefix(line, slashslash) { // Not comment line. - break - } - } - return content[:end] -} - -func annotate(name string) { - fset := token.NewFileSet() - content, err := ioutil.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) - } - parsedFile.Comments = trimComments(parsedFile, fset) - - file := &File{ - fset: fset, - name: name, - astFile: parsedFile, - } - if *mode == "atomic" { - file.atomicPkg = file.addImport(atomicPackagePath) - } - ast.Walk(file, file.astFile) - fd := os.Stdout - if *output != "" { - var err error - fd, err = os.Create(*output) - if err != nil { - log.Fatalf("cover: %s", err) - } - } - fd.Write(initialComments(content)) // Retain '// +build' directives. - file.print(fd) - // 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) -} - -// trimComments drops all but the //go: comments, some of which are semantically important. -// We drop all others because they can appear in places that cause our counters -// to appear in syntactically incorrect places. //go: appears at the beginning of -// the line and is syntactically safe. -func trimComments(file *ast.File, fset *token.FileSet) []*ast.CommentGroup { - var comments []*ast.CommentGroup - for _, group := range file.Comments { - var list []*ast.Comment - for _, comment := range group.List { - if strings.HasPrefix(comment.Text, "//go:") && fset.Position(comment.Slash).Column == 1 { - list = append(list, comment) - } - } - if list != nil { - comments = append(comments, &ast.CommentGroup{List: list}) - } - } - return comments -} - -func (f *File) print(w io.Writer) { - printer.Fprint(w, f.fset, f.astFile) -} - -// intLiteral returns an ast.BasicLit representing the integer value. -func (f *File) intLiteral(i int) *ast.BasicLit { - node := &ast.BasicLit{ - Kind: token.INT, - Value: fmt.Sprint(i), - } - return node -} - -// index returns an ast.BasicLit representing the number of counters present. -func (f *File) index() *ast.BasicLit { - return f.intLiteral(len(f.blocks)) -} - -// setCounterStmt returns the expression: __count[23] = 1. -func setCounterStmt(f *File, counter ast.Expr) ast.Stmt { - return &ast.AssignStmt{ - Lhs: []ast.Expr{counter}, - Tok: token.ASSIGN, - Rhs: []ast.Expr{f.intLiteral(1)}, - } -} - -// incCounterStmt returns the expression: __count[23]++. -func incCounterStmt(f *File, counter ast.Expr) ast.Stmt { - return &ast.IncDecStmt{ - X: counter, - Tok: token.INC, - } -} - -// atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1) -func atomicCounterStmt(f *File, counter ast.Expr) ast.Stmt { - return &ast.ExprStmt{ - X: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(f.atomicPkg), - Sel: ast.NewIdent("AddUint32"), - }, - Args: []ast.Expr{&ast.UnaryExpr{ - Op: token.AND, - X: counter, - }, - f.intLiteral(1), - }, - }, - } -} - -// newCounter creates a new counter expression of the appropriate form. -func (f *File) newCounter(start, end token.Pos, numStmt int) ast.Stmt { - counter := &ast.IndexExpr{ - X: &ast.SelectorExpr{ - X: ast.NewIdent(*varVar), - Sel: ast.NewIdent("Count"), - }, - Index: f.index(), - } - stmt := counterStmt(f, counter) - 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, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt { - // 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 { - return []ast.Stmt{f.newCounter(pos, blockEnd, 0)} - } - // 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. - var newList []ast.Stmt - 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++ { - end = f.statementBoundary(list[last]) - if f.endsBasicSourceBlock(list[last]) { - extendToClosingBrace = false // Block is broken up now. - last++ - break - } - } - if extendToClosingBrace { - end = blockEnd - } - if pos != end { // Can have no source to cover if e.g. blocks abut. - newList = append(newList, f.newCounter(pos, end, last)) - } - newList = append(newList, list[0:last]...) - list = list[last:] - if len(list) == 0 { - break - } - pos = list[0].Pos() - } - return newList -} - -// 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 f.endsBasicSourceBlock(s.Stmt) - 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 -} - -// 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) - 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") -} diff --git a/cmd/cover/cover_test.go b/cmd/cover/cover_test.go deleted file mode 100644 index 228c91144..000000000 --- a/cmd/cover/cover_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// 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. - -// No testdata on Android. - -//go:build !android -// +build !android - -package main_test - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "testing" - - "golang.org/x/tools/internal/testenv" -) - -const ( - // Data directory, also the package directory for the test. - testdata = "testdata" -) - -var debug = false // Keeps the rewritten files around if set. - -// 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) { - testenv.NeedsTool(t, "go") - - tmpdir, err := ioutil.TempDir("", "TestCover") - if err != nil { - t.Fatal(err) - } - defer func() { - if debug { - fmt.Printf("test files left in %s\n", tmpdir) - } else { - os.RemoveAll(tmpdir) - } - }() - - testcover := filepath.Join(tmpdir, "testcover.exe") - testMain := filepath.Join(tmpdir, "main.go") - testTest := filepath.Join(tmpdir, "test.go") - coverInput := filepath.Join(tmpdir, "test_line.go") - coverOutput := filepath.Join(tmpdir, "test_cover.go") - - for _, f := range []string{testMain, testTest} { - data, err := ioutil.ReadFile(filepath.Join(testdata, filepath.Base(f))) - if err != nil { - t.Fatal(err) - } - if err := ioutil.WriteFile(f, data, 0644); err != nil { - t.Fatal(err) - } - } - - // Read in the test file (testTest) and write it, with LINEs specified, to coverInput. - file, err := ioutil.ReadFile(testTest) - if err != nil { - t.Fatal(err) - } - lines := bytes.Split(file, []byte("\n")) - for i, line := range lines { - lines[i] = bytes.Replace(line, []byte("LINE"), []byte(fmt.Sprint(i+1)), -1) - } - err = ioutil.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666) - if err != nil { - t.Fatal(err) - } - - // go build -o testcover - cmd := exec.Command("go", "build", "-o", testcover) - run(cmd, t) - - // ./testcover -mode=count -var=coverTest -o ./testdata/test_cover.go testdata/test_line.go - cmd = exec.Command(testcover, "-mode=count", "-var=coverTest", "-o", coverOutput, coverInput) - run(cmd, t) - - // defer removal of ./testdata/test_cover.go - if !debug { - defer os.Remove(coverOutput) - } - - // go run ./testdata/main.go ./testdata/test.go - cmd = exec.Command("go", "run", testMain, coverOutput) - run(cmd, t) -} - -func run(c *exec.Cmd, t *testing.T) { - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err := c.Run() - if err != nil { - t.Fatal(err) - } -} diff --git a/cmd/cover/doc.go b/cmd/cover/doc.go deleted file mode 100644 index f903d8508..000000000 --- a/cmd/cover/doc.go +++ /dev/null @@ -1,24 +0,0 @@ -// 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'. - -Deprecated: For Go releases 1.5 and later, this tool lives in the -standard repository. The code here is not maintained. - -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. - -For usage information, please see: - go help testflag - go tool cover -help -*/ -package main // import "golang.org/x/tools/cmd/cover" diff --git a/cmd/cover/func.go b/cmd/cover/func.go deleted file mode 100644 index 41d9fceca..000000000 --- a/cmd/cover/func.go +++ /dev/null @@ -1,166 +0,0 @@ -// 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" - "fmt" - "go/ast" - "go/build" - "go/parser" - "go/token" - "os" - "path/filepath" - "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 - } - - 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(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, 100.0*float64(c)/float64(t)) - total += t - covered += c - } - } - fmt.Fprintf(tabber, "total:\t(statements)\t%.1f%%\n", 100.0*float64(covered)/float64(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: - 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) - } - } - if total == 0 { - total = 1 // Avoid zero denominator. - } - return covered, total -} - -// findFile finds the location of the named file in GOROOT, GOPATH etc. -func findFile(file string) (string, error) { - dir, file := filepath.Split(file) - pkg, err := build.Import(dir, ".", build.FindOnly) - if err != nil { - return "", fmt.Errorf("can't find %q: %v", file, err) - } - return filepath.Join(pkg.Dir, file), nil -} diff --git a/cmd/cover/html.go b/cmd/cover/html.go deleted file mode 100644 index 0f8c72542..000000000 --- a/cmd/cover/html.go +++ /dev/null @@ -1,284 +0,0 @@ -// 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" - "fmt" - exec "golang.org/x/sys/execabs" - "html/template" - "io" - "io/ioutil" - "math" - "os" - "path/filepath" - "runtime" - - "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 - - for _, profile := range profiles { - fn := profile.FileName - if profile.Mode == "set" { - d.Set = true - } - file, err := findFile(fn) - if err != nil { - return err - } - src, err := ioutil.ReadFile(file) - if err != nil { - return fmt.Errorf("can't read %q: %v", fn, err) - } - var buf bytes.Buffer - 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 = ioutil.TempDir("", "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 err == nil { - err = out.Close() - } - if err != nil { - return err - } - - if outfile == "" { - if !startBrowser("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(">") - case '<': - dst.WriteString("<") - case '&': - dst.WriteString("&") - case '\t': - dst.WriteString(" ") - default: - dst.WriteByte(b) - } - } - return dst.Flush() -} - -// startBrowser tries to open the URL in a browser -// and reports whether it succeeds. -func startBrowser(url string) bool { - // try to start the browser - var args []string - switch runtime.GOOS { - case "darwin": - args = []string{"open"} - case "windows": - args = []string{"cmd", "/c", "start"} - default: - args = []string{"xdg-open"} - } - cmd := exec.Command(args[0], append(args[1:], url)...) - return cmd.Start() == nil -} - -// 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 -} - -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"> - <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}}" {{if $i}}style="display: none"{{end}}>{{$f.Body}}</pre> - {{end}} - </div> - </body> - <script> - (function() { - var files = document.getElementById('files'); - var visible = document.getElementById('file0'); - files.addEventListener('change', onChange, false); - function onChange() { - visible.style.display = 'none'; - visible = document.getElementById(files.value); - visible.style.display = 'block'; - window.scrollTo(0, 0); - } - })(); - </script> -</html> -` diff --git a/cmd/cover/testdata/main.go b/cmd/cover/testdata/main.go deleted file mode 100644 index 6ed39c4f2..000000000 --- a/cmd/cover/testdata/main.go +++ /dev/null @@ -1,112 +0,0 @@ -// 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 "coverTest". - -package main - -import ( - "fmt" - "os" -) - -func main() { - testAll() - verify() -} - -type block struct { - count uint32 - line uint32 -} - -var counters = make(map[block]bool) - -// 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/cmd/cover/testdata/test.go b/cmd/cover/testdata/test.go deleted file mode 100644 index 9013950a2..000000000 --- a/cmd/cover/testdata/test.go +++ /dev/null @@ -1,218 +0,0 @@ -// 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 - -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() -} - -// 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) }() { - 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 = []interface{}{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 := (interface{})(3).(int); i { - } - check(LINE, 1) - c := make(chan int) - go func() { - check(LINE, 1) - c <- 1 - select {} - }() - <-c - check(LINE, 1) -} |