Source file src/cmd/cover/cover.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "flag"
10 "fmt"
11 "go/ast"
12 "go/parser"
13 "go/token"
14 "io"
15 "io/ioutil"
16 "log"
17 "os"
18 "sort"
19
20 "cmd/internal/edit"
21 "cmd/internal/objabi"
22 )
23
24 const usageMessage = "" +
25 `Usage of 'go tool cover':
26 Given a coverage profile produced by 'go test':
27 go test -coverprofile=c.out
28
29 Open a web browser displaying annotated source code:
30 go tool cover -html=c.out
31
32 Write out an HTML file instead of launching a web browser:
33 go tool cover -html=c.out -o coverage.html
34
35 Display coverage percentages to stdout for each function:
36 go tool cover -func=c.out
37
38 Finally, to generate modified source code with coverage annotations
39 (what go test -cover does):
40 go tool cover -mode=set -var=CoverageVariableName program.go
41 `
42
43 func usage() {
44 fmt.Fprintln(os.Stderr, usageMessage)
45 fmt.Fprintln(os.Stderr, "Flags:")
46 flag.PrintDefaults()
47 fmt.Fprintln(os.Stderr, "\n Only one of -html, -func, or -mode may be set.")
48 os.Exit(2)
49 }
50
51 var (
52 mode = flag.String("mode", "", "coverage mode: set, count, atomic")
53 varVar = flag.String("var", "GoCover", "name of coverage variable to generate")
54 output = flag.String("o", "", "file for output; default: stdout")
55 htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
56 funcOut = flag.String("func", "", "output coverage profile information for each function")
57 )
58
59 var profile string
60
61 var counterStmt func(*File, string) string
62
63 const (
64 atomicPackagePath = "sync/atomic"
65 atomicPackageName = "_cover_atomic_"
66 )
67
68 func main() {
69 objabi.AddVersionFlag()
70 flag.Usage = usage
71 flag.Parse()
72
73
74 if flag.NFlag() == 0 && flag.NArg() == 0 {
75 flag.Usage()
76 }
77
78 err := parseFlags()
79 if err != nil {
80 fmt.Fprintln(os.Stderr, err)
81 fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`)
82 os.Exit(2)
83 }
84
85
86 if *mode != "" {
87 annotate(flag.Arg(0))
88 return
89 }
90
91
92 if *htmlOut != "" {
93 err = htmlOutput(profile, *output)
94 } else {
95 err = funcOutput(profile, *output)
96 }
97
98 if err != nil {
99 fmt.Fprintf(os.Stderr, "cover: %v\n", err)
100 os.Exit(2)
101 }
102 }
103
104
105 func parseFlags() error {
106 profile = *htmlOut
107 if *funcOut != "" {
108 if profile != "" {
109 return fmt.Errorf("too many options")
110 }
111 profile = *funcOut
112 }
113
114
115 if (profile == "") == (*mode == "") {
116 return fmt.Errorf("too many options")
117 }
118
119 if *varVar != "" && !token.IsIdentifier(*varVar) {
120 return fmt.Errorf("-var: %q is not a valid identifier", *varVar)
121 }
122
123 if *mode != "" {
124 switch *mode {
125 case "set":
126 counterStmt = setCounterStmt
127 case "count":
128 counterStmt = incCounterStmt
129 case "atomic":
130 counterStmt = atomicCounterStmt
131 default:
132 return fmt.Errorf("unknown -mode %v", *mode)
133 }
134
135 if flag.NArg() == 0 {
136 return fmt.Errorf("missing source file")
137 } else if flag.NArg() == 1 {
138 return nil
139 }
140 } else if flag.NArg() == 0 {
141 return nil
142 }
143 return fmt.Errorf("too many arguments")
144 }
145
146
147
148
149 type Block struct {
150 startByte token.Pos
151 endByte token.Pos
152 numStmt int
153 }
154
155
156
157 type File struct {
158 fset *token.FileSet
159 name string
160 astFile *ast.File
161 blocks []Block
162 content []byte
163 edit *edit.Buffer
164 }
165
166
167
168
169
170 func (f *File) findText(pos token.Pos, text string) int {
171 b := []byte(text)
172 start := f.offset(pos)
173 i := start
174 s := f.content
175 for i < len(s) {
176 if bytes.HasPrefix(s[i:], b) {
177 return i
178 }
179 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {
180 for i < len(s) && s[i] != '\n' {
181 i++
182 }
183 continue
184 }
185 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {
186 for i += 2; ; i++ {
187 if i+2 > len(s) {
188 return 0
189 }
190 if s[i] == '*' && s[i+1] == '/' {
191 i += 2
192 break
193 }
194 }
195 continue
196 }
197 i++
198 }
199 return -1
200 }
201
202
203 func (f *File) Visit(node ast.Node) ast.Visitor {
204 switch n := node.(type) {
205 case *ast.BlockStmt:
206
207 if len(n.List) > 0 {
208 switch n.List[0].(type) {
209 case *ast.CaseClause:
210 for _, n := range n.List {
211 clause := n.(*ast.CaseClause)
212 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
213 }
214 return f
215 case *ast.CommClause:
216 for _, n := range n.List {
217 clause := n.(*ast.CommClause)
218 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
219 }
220 return f
221 }
222 }
223 f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true)
224 case *ast.IfStmt:
225 if n.Init != nil {
226 ast.Walk(f, n.Init)
227 }
228 ast.Walk(f, n.Cond)
229 ast.Walk(f, n.Body)
230 if n.Else == nil {
231 return nil
232 }
233
234
235
236
237
238
239
240
241
242
243
244 elseOffset := f.findText(n.Body.End(), "else")
245 if elseOffset < 0 {
246 panic("lost else")
247 }
248 f.edit.Insert(elseOffset+4, "{")
249 f.edit.Insert(f.offset(n.Else.End()), "}")
250
251
252
253
254
255 pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)
256 switch stmt := n.Else.(type) {
257 case *ast.IfStmt:
258 block := &ast.BlockStmt{
259 Lbrace: pos,
260 List: []ast.Stmt{stmt},
261 Rbrace: stmt.End(),
262 }
263 n.Else = block
264 case *ast.BlockStmt:
265 stmt.Lbrace = pos
266 default:
267 panic("unexpected node type in if")
268 }
269 ast.Walk(f, n.Else)
270 return nil
271 case *ast.SelectStmt:
272
273 if n.Body == nil || len(n.Body.List) == 0 {
274 return nil
275 }
276 case *ast.SwitchStmt:
277
278 if n.Body == nil || len(n.Body.List) == 0 {
279 if n.Init != nil {
280 ast.Walk(f, n.Init)
281 }
282 if n.Tag != nil {
283 ast.Walk(f, n.Tag)
284 }
285 return nil
286 }
287 case *ast.TypeSwitchStmt:
288
289 if n.Body == nil || len(n.Body.List) == 0 {
290 if n.Init != nil {
291 ast.Walk(f, n.Init)
292 }
293 ast.Walk(f, n.Assign)
294 return nil
295 }
296 }
297 return f
298 }
299
300 func annotate(name string) {
301 fset := token.NewFileSet()
302 content, err := ioutil.ReadFile(name)
303 if err != nil {
304 log.Fatalf("cover: %s: %s", name, err)
305 }
306 parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments)
307 if err != nil {
308 log.Fatalf("cover: %s: %s", name, err)
309 }
310
311 file := &File{
312 fset: fset,
313 name: name,
314 content: content,
315 edit: edit.NewBuffer(content),
316 astFile: parsedFile,
317 }
318 if *mode == "atomic" {
319
320
321
322
323
324 file.edit.Insert(file.offset(file.astFile.Name.End()),
325 fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
326 }
327
328 ast.Walk(file, file.astFile)
329 newContent := file.edit.Bytes()
330
331 fd := os.Stdout
332 if *output != "" {
333 var err error
334 fd, err = os.Create(*output)
335 if err != nil {
336 log.Fatalf("cover: %s", err)
337 }
338 }
339
340 fmt.Fprintf(fd, "//line %s:1\n", name)
341 fd.Write(newContent)
342
343
344
345 file.addVariables(fd)
346 }
347
348
349 func setCounterStmt(f *File, counter string) string {
350 return fmt.Sprintf("%s = 1", counter)
351 }
352
353
354 func incCounterStmt(f *File, counter string) string {
355 return fmt.Sprintf("%s++", counter)
356 }
357
358
359 func atomicCounterStmt(f *File, counter string) string {
360 return fmt.Sprintf("%s.AddUint32(&%s, 1)", atomicPackageName, counter)
361 }
362
363
364 func (f *File) newCounter(start, end token.Pos, numStmt int) string {
365 stmt := counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar, len(f.blocks)))
366 f.blocks = append(f.blocks, Block{start, end, numStmt})
367 return stmt
368 }
369
370
371
372
373
374
375
376
377
378
379
380
381
382 func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
383
384
385 if len(list) == 0 {
386 f.edit.Insert(f.offset(insertPos), f.newCounter(insertPos, blockEnd, 0)+";")
387 return
388 }
389
390
391 list = append([]ast.Stmt(nil), list...)
392
393
394 for {
395
396
397 var last int
398 end := blockEnd
399 for last = 0; last < len(list); last++ {
400 stmt := list[last]
401 end = f.statementBoundary(stmt)
402 if f.endsBasicSourceBlock(stmt) {
403
404
405
406
407
408
409
410
411
412
413
414 if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {
415 newLabel := *label
416 newLabel.Stmt = &ast.EmptyStmt{
417 Semicolon: label.Stmt.Pos(),
418 Implicit: true,
419 }
420 end = label.Pos()
421 list[last] = &newLabel
422
423 list = append(list, nil)
424 copy(list[last+1:], list[last:])
425 list[last+1] = label.Stmt
426 }
427 last++
428 extendToClosingBrace = false
429 break
430 }
431 }
432 if extendToClosingBrace {
433 end = blockEnd
434 }
435 if pos != end {
436 f.edit.Insert(f.offset(insertPos), f.newCounter(pos, end, last)+";")
437 }
438 list = list[last:]
439 if len(list) == 0 {
440 break
441 }
442 pos = list[0].Pos()
443 insertPos = pos
444 }
445 }
446
447
448
449
450
451
452 func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
453 if n == nil {
454 return false, 0
455 }
456 var literal funcLitFinder
457 ast.Walk(&literal, n)
458 return literal.found(), token.Pos(literal)
459 }
460
461
462
463 func (f *File) statementBoundary(s ast.Stmt) token.Pos {
464
465 switch s := s.(type) {
466 case *ast.BlockStmt:
467
468 return s.Lbrace
469 case *ast.IfStmt:
470 found, pos := hasFuncLiteral(s.Init)
471 if found {
472 return pos
473 }
474 found, pos = hasFuncLiteral(s.Cond)
475 if found {
476 return pos
477 }
478 return s.Body.Lbrace
479 case *ast.ForStmt:
480 found, pos := hasFuncLiteral(s.Init)
481 if found {
482 return pos
483 }
484 found, pos = hasFuncLiteral(s.Cond)
485 if found {
486 return pos
487 }
488 found, pos = hasFuncLiteral(s.Post)
489 if found {
490 return pos
491 }
492 return s.Body.Lbrace
493 case *ast.LabeledStmt:
494 return f.statementBoundary(s.Stmt)
495 case *ast.RangeStmt:
496 found, pos := hasFuncLiteral(s.X)
497 if found {
498 return pos
499 }
500 return s.Body.Lbrace
501 case *ast.SwitchStmt:
502 found, pos := hasFuncLiteral(s.Init)
503 if found {
504 return pos
505 }
506 found, pos = hasFuncLiteral(s.Tag)
507 if found {
508 return pos
509 }
510 return s.Body.Lbrace
511 case *ast.SelectStmt:
512 return s.Body.Lbrace
513 case *ast.TypeSwitchStmt:
514 found, pos := hasFuncLiteral(s.Init)
515 if found {
516 return pos
517 }
518 return s.Body.Lbrace
519 }
520
521
522
523
524 found, pos := hasFuncLiteral(s)
525 if found {
526 return pos
527 }
528 return s.End()
529 }
530
531
532
533
534 func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
535 switch s := s.(type) {
536 case *ast.BlockStmt:
537
538 return true
539 case *ast.BranchStmt:
540 return true
541 case *ast.ForStmt:
542 return true
543 case *ast.IfStmt:
544 return true
545 case *ast.LabeledStmt:
546 return true
547 case *ast.RangeStmt:
548 return true
549 case *ast.SwitchStmt:
550 return true
551 case *ast.SelectStmt:
552 return true
553 case *ast.TypeSwitchStmt:
554 return true
555 case *ast.ExprStmt:
556
557
558
559
560 if call, ok := s.X.(*ast.CallExpr); ok {
561 if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
562 return true
563 }
564 }
565 }
566 found, _ := hasFuncLiteral(s)
567 return found
568 }
569
570
571
572 func (f *File) isControl(s ast.Stmt) bool {
573 switch s.(type) {
574 case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
575 return true
576 }
577 return false
578 }
579
580
581
582 type funcLitFinder token.Pos
583
584 func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
585 if f.found() {
586 return nil
587 }
588 switch n := node.(type) {
589 case *ast.FuncLit:
590 *f = funcLitFinder(n.Body.Lbrace)
591 return nil
592 }
593 return f
594 }
595
596 func (f *funcLitFinder) found() bool {
597 return token.Pos(*f) != token.NoPos
598 }
599
600
601
602 type block1 struct {
603 Block
604 index int
605 }
606
607 type blockSlice []block1
608
609 func (b blockSlice) Len() int { return len(b) }
610 func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte }
611 func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
612
613
614 func (f *File) offset(pos token.Pos) int {
615 return f.fset.Position(pos).Offset
616 }
617
618
619 func (f *File) addVariables(w io.Writer) {
620
621 t := make([]block1, len(f.blocks))
622 for i := range f.blocks {
623 t[i].Block = f.blocks[i]
624 t[i].index = i
625 }
626 sort.Sort(blockSlice(t))
627 for i := 1; i < len(t); i++ {
628 if t[i-1].endByte > t[i].startByte {
629 fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
630
631 fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
632 f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
633 f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
634 }
635 }
636
637
638 fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
639 fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks))
640 fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks))
641 fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks))
642 fmt.Fprintf(w, "} {\n")
643
644
645 fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
646
647
648
649
650
651 for i, block := range f.blocks {
652 start := f.fset.Position(block.startByte)
653 end := f.fset.Position(block.endByte)
654
655 start, end = dedup(start, end)
656
657 fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
658 }
659
660
661 fmt.Fprintf(w, "\t},\n")
662
663
664 fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
665
666
667
668
669 for i, block := range f.blocks {
670 n := block.numStmt
671 if n > 1<<16-1 {
672 n = 1<<16 - 1
673 }
674 fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)
675 }
676
677
678 fmt.Fprintf(w, "\t},\n")
679
680
681 fmt.Fprintf(w, "}\n")
682
683
684
685 if *mode == "atomic" {
686 fmt.Fprintf(w, "var _ = %s.LoadUint32\n", atomicPackageName)
687 }
688 }
689
690
691
692
693
694
695
696
697
698 type pos2 struct {
699 p1, p2 token.Position
700 }
701
702
703 var seenPos2 = make(map[pos2]bool)
704
705
706
707
708 func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
709 key := pos2{
710 p1: p1,
711 p2: p2,
712 }
713
714
715
716 key.p1.Offset = 0
717 key.p2.Offset = 0
718
719 for seenPos2[key] {
720 key.p2.Column++
721 }
722 seenPos2[key] = true
723
724 return key.p1, key.p2
725 }
726
View as plain text