Source file src/cmd/cover/func.go
1
2
3
4
5
6
7 package main
8
9 import (
10 "bufio"
11 "bytes"
12 "encoding/json"
13 "errors"
14 "fmt"
15 "go/ast"
16 "go/parser"
17 "go/token"
18 "io"
19 "os"
20 "os/exec"
21 "path"
22 "path/filepath"
23 "runtime"
24 "strings"
25 "text/tabwriter"
26 )
27
28
29
30
31
32
33
34
35
36
37
38
39
40 func funcOutput(profile, outputFile string) error {
41 profiles, err := ParseProfiles(profile)
42 if err != nil {
43 return err
44 }
45
46 dirs, err := findPkgs(profiles)
47 if err != nil {
48 return err
49 }
50
51 var out *bufio.Writer
52 if outputFile == "" {
53 out = bufio.NewWriter(os.Stdout)
54 } else {
55 fd, err := os.Create(outputFile)
56 if err != nil {
57 return err
58 }
59 defer fd.Close()
60 out = bufio.NewWriter(fd)
61 }
62 defer out.Flush()
63
64 tabber := tabwriter.NewWriter(out, 1, 8, 1, '\t', 0)
65 defer tabber.Flush()
66
67 var total, covered int64
68 for _, profile := range profiles {
69 fn := profile.FileName
70 file, err := findFile(dirs, fn)
71 if err != nil {
72 return err
73 }
74 funcs, err := findFuncs(file)
75 if err != nil {
76 return err
77 }
78
79 for _, f := range funcs {
80 c, t := f.coverage(profile)
81 fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n", fn, f.startLine, f.name, percent(c, t))
82 total += t
83 covered += c
84 }
85 }
86 fmt.Fprintf(tabber, "total:\t(statements)\t%.1f%%\n", percent(covered, total))
87
88 return nil
89 }
90
91
92 func findFuncs(name string) ([]*FuncExtent, error) {
93 fset := token.NewFileSet()
94 parsedFile, err := parser.ParseFile(fset, name, nil, 0)
95 if err != nil {
96 return nil, err
97 }
98 visitor := &FuncVisitor{
99 fset: fset,
100 name: name,
101 astFile: parsedFile,
102 }
103 ast.Walk(visitor, visitor.astFile)
104 return visitor.funcs, nil
105 }
106
107
108 type FuncExtent struct {
109 name string
110 startLine int
111 startCol int
112 endLine int
113 endCol int
114 }
115
116
117 type FuncVisitor struct {
118 fset *token.FileSet
119 name string
120 astFile *ast.File
121 funcs []*FuncExtent
122 }
123
124
125 func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
126 switch n := node.(type) {
127 case *ast.FuncDecl:
128 if n.Body == nil {
129
130 break
131 }
132 start := v.fset.Position(n.Pos())
133 end := v.fset.Position(n.End())
134 fe := &FuncExtent{
135 name: n.Name.Name,
136 startLine: start.Line,
137 startCol: start.Column,
138 endLine: end.Line,
139 endCol: end.Column,
140 }
141 v.funcs = append(v.funcs, fe)
142 }
143 return v
144 }
145
146
147 func (f *FuncExtent) coverage(profile *Profile) (num, den int64) {
148
149
150 var covered, total int64
151
152 for _, b := range profile.Blocks {
153 if b.StartLine > f.endLine || (b.StartLine == f.endLine && b.StartCol >= f.endCol) {
154
155 break
156 }
157 if b.EndLine < f.startLine || (b.EndLine == f.startLine && b.EndCol <= f.startCol) {
158
159 continue
160 }
161 total += int64(b.NumStmt)
162 if b.Count > 0 {
163 covered += int64(b.NumStmt)
164 }
165 }
166 return covered, total
167 }
168
169
170 type Pkg struct {
171 ImportPath string
172 Dir string
173 Error *struct {
174 Err string
175 }
176 }
177
178 func findPkgs(profiles []*Profile) (map[string]*Pkg, error) {
179
180 pkgs := make(map[string]*Pkg)
181 var list []string
182 for _, profile := range profiles {
183 if strings.HasPrefix(profile.FileName, ".") || filepath.IsAbs(profile.FileName) {
184
185 continue
186 }
187 pkg := path.Dir(profile.FileName)
188 if _, ok := pkgs[pkg]; !ok {
189 pkgs[pkg] = nil
190 list = append(list, pkg)
191 }
192 }
193
194 if len(list) == 0 {
195 return pkgs, nil
196 }
197
198
199
200 goTool := filepath.Join(runtime.GOROOT(), "bin/go")
201 cmd := exec.Command(goTool, append([]string{"list", "-e", "-json"}, list...)...)
202 var stderr bytes.Buffer
203 cmd.Stderr = &stderr
204 stdout, err := cmd.Output()
205 if err != nil {
206 return nil, fmt.Errorf("cannot run go list: %v\n%s", err, stderr.Bytes())
207 }
208 dec := json.NewDecoder(bytes.NewReader(stdout))
209 for {
210 var pkg Pkg
211 err := dec.Decode(&pkg)
212 if err == io.EOF {
213 break
214 }
215 if err != nil {
216 return nil, fmt.Errorf("decoding go list json: %v", err)
217 }
218 pkgs[pkg.ImportPath] = &pkg
219 }
220 return pkgs, nil
221 }
222
223
224 func findFile(pkgs map[string]*Pkg, file string) (string, error) {
225 if strings.HasPrefix(file, ".") || filepath.IsAbs(file) {
226
227 return file, nil
228 }
229 pkg := pkgs[path.Dir(file)]
230 if pkg != nil {
231 if pkg.Dir != "" {
232 return filepath.Join(pkg.Dir, path.Base(file)), nil
233 }
234 if pkg.Error != nil {
235 return "", errors.New(pkg.Error.Err)
236 }
237 }
238 return "", fmt.Errorf("did not find package for %s in go list output", file)
239 }
240
241 func percent(covered, total int64) float64 {
242 if total == 0 {
243 total = 1
244 }
245 return 100.0 * float64(covered) / float64(total)
246 }
247
View as plain text