You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
4.4 KiB

  1. // Copyright 2013 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // This file implements the visitor that computes the (line, column)-(line-column) range for each function.
  5. package main
  6. import (
  7. "bufio"
  8. "fmt"
  9. "go/ast"
  10. "go/build"
  11. "go/parser"
  12. "go/token"
  13. "os"
  14. "path/filepath"
  15. "text/tabwriter"
  16. "golang.org/x/tools/cover"
  17. )
  18. // funcOutput takes two file names as arguments, a coverage profile to read as input and an output
  19. // file to write ("" means to write to standard output). The function reads the profile and produces
  20. // as output the coverage data broken down by function, like this:
  21. //
  22. // fmt/format.go:30: init 100.0%
  23. // fmt/format.go:57: clearflags 100.0%
  24. // ...
  25. // fmt/scan.go:1046: doScan 100.0%
  26. // fmt/scan.go:1075: advance 96.2%
  27. // fmt/scan.go:1119: doScanf 96.8%
  28. // total: (statements) 91.9%
  29. func funcOutput(profile, outputFile string) error {
  30. profiles, err := cover.ParseProfiles(profile)
  31. if err != nil {
  32. return err
  33. }
  34. var out *bufio.Writer
  35. if outputFile == "" {
  36. out = bufio.NewWriter(os.Stdout)
  37. } else {
  38. fd, err := os.Create(outputFile)
  39. if err != nil {
  40. return err
  41. }
  42. defer fd.Close()
  43. out = bufio.NewWriter(fd)
  44. }
  45. defer out.Flush()
  46. tabber := tabwriter.NewWriter(out, 1, 8, 1, '\t', 0)
  47. defer tabber.Flush()
  48. var total, covered int64
  49. for _, profile := range profiles {
  50. fn := profile.FileName
  51. file, err := findFile(fn)
  52. if err != nil {
  53. return err
  54. }
  55. funcs, err := findFuncs(file)
  56. if err != nil {
  57. return err
  58. }
  59. // Now match up functions and profile blocks.
  60. for _, f := range funcs {
  61. c, t := f.coverage(profile)
  62. fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n", fn, f.startLine, f.name, 100.0*float64(c)/float64(t))
  63. total += t
  64. covered += c
  65. }
  66. }
  67. fmt.Fprintf(tabber, "total:\t(statements)\t%.1f%%\n", 100.0*float64(covered)/float64(total))
  68. return nil
  69. }
  70. // findFuncs parses the file and returns a slice of FuncExtent descriptors.
  71. func findFuncs(name string) ([]*FuncExtent, error) {
  72. fset := token.NewFileSet()
  73. parsedFile, err := parser.ParseFile(fset, name, nil, 0)
  74. if err != nil {
  75. return nil, err
  76. }
  77. visitor := &FuncVisitor{
  78. fset: fset,
  79. name: name,
  80. astFile: parsedFile,
  81. }
  82. ast.Walk(visitor, visitor.astFile)
  83. return visitor.funcs, nil
  84. }
  85. // FuncExtent describes a function's extent in the source by file and position.
  86. type FuncExtent struct {
  87. name string
  88. startLine int
  89. startCol int
  90. endLine int
  91. endCol int
  92. }
  93. // FuncVisitor implements the visitor that builds the function position list for a file.
  94. type FuncVisitor struct {
  95. fset *token.FileSet
  96. name string // Name of file.
  97. astFile *ast.File
  98. funcs []*FuncExtent
  99. }
  100. // Visit implements the ast.Visitor interface.
  101. func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
  102. switch n := node.(type) {
  103. case *ast.FuncDecl:
  104. start := v.fset.Position(n.Pos())
  105. end := v.fset.Position(n.End())
  106. fe := &FuncExtent{
  107. name: n.Name.Name,
  108. startLine: start.Line,
  109. startCol: start.Column,
  110. endLine: end.Line,
  111. endCol: end.Column,
  112. }
  113. v.funcs = append(v.funcs, fe)
  114. }
  115. return v
  116. }
  117. // coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator.
  118. func (f *FuncExtent) coverage(profile *cover.Profile) (num, den int64) {
  119. // We could avoid making this n^2 overall by doing a single scan and annotating the functions,
  120. // but the sizes of the data structures is never very large and the scan is almost instantaneous.
  121. var covered, total int64
  122. // The blocks are sorted, so we can stop counting as soon as we reach the end of the relevant block.
  123. for _, b := range profile.Blocks {
  124. if b.StartLine > f.endLine || (b.StartLine == f.endLine && b.StartCol >= f.endCol) {
  125. // Past the end of the function.
  126. break
  127. }
  128. if b.EndLine < f.startLine || (b.EndLine == f.startLine && b.EndCol <= f.startCol) {
  129. // Before the beginning of the function
  130. continue
  131. }
  132. total += int64(b.NumStmt)
  133. if b.Count > 0 {
  134. covered += int64(b.NumStmt)
  135. }
  136. }
  137. if total == 0 {
  138. total = 1 // Avoid zero denominator.
  139. }
  140. return covered, total
  141. }
  142. // findFile finds the location of the named file in GOROOT, GOPATH etc.
  143. func findFile(file string) (string, error) {
  144. dir, file := filepath.Split(file)
  145. pkg, err := build.Import(dir, ".", build.FindOnly)
  146. if err != nil {
  147. return "", fmt.Errorf("can't find %q: %v", file, err)
  148. }
  149. return filepath.Join(pkg.Dir, file), nil
  150. }