github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/cmd/cover/cover.go (about) 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 5 package main 6 7 import ( 8 "bytes" 9 "flag" 10 "fmt" 11 "go/ast" 12 "go/parser" 13 "go/printer" 14 "go/token" 15 "io" 16 "io/ioutil" 17 "log" 18 "os" 19 "path/filepath" 20 "sort" 21 "strconv" 22 "strings" 23 ) 24 25 const usageMessage = "" + 26 `Usage of 'go tool cover': 27 Given a coverage profile produced by 'go test': 28 go test -coverprofile=c.out 29 30 Open a web browser displaying annotated source code: 31 go tool cover -html=c.out 32 33 Write out an HTML file instead of launching a web browser: 34 go tool cover -html=c.out -o coverage.html 35 36 Display coverage percentages to stdout for each function: 37 go tool cover -func=c.out 38 39 Finally, to generate modified source code with coverage annotations 40 (what go test -cover does): 41 go tool cover -mode=set -var=CoverageVariableName program.go 42 ` 43 44 func usage() { 45 fmt.Fprintln(os.Stderr, usageMessage) 46 fmt.Fprintln(os.Stderr, "Flags:") 47 flag.PrintDefaults() 48 fmt.Fprintln(os.Stderr, "\n Only one of -html, -func, or -mode may be set.") 49 os.Exit(2) 50 } 51 52 var ( 53 mode = flag.String("mode", "", "coverage mode: set, count, atomic") 54 varVar = flag.String("var", "GoCover", "name of coverage variable to generate") 55 output = flag.String("o", "", "file for output; default: stdout") 56 htmlOut = flag.String("html", "", "generate HTML representation of coverage profile") 57 funcOut = flag.String("func", "", "output coverage profile information for each function") 58 ) 59 60 var profile string // The profile to read; the value of -html or -func 61 62 var counterStmt func(*File, ast.Expr) ast.Stmt 63 64 const ( 65 atomicPackagePath = "sync/atomic" 66 atomicPackageName = "_cover_atomic_" 67 ) 68 69 func main() { 70 flag.Usage = usage 71 flag.Parse() 72 73 // Usage information when no arguments. 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 // Generate coverage-annotated source. 86 if *mode != "" { 87 annotate(flag.Arg(0)) 88 return 89 } 90 91 // Output HTML or function coverage information. 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 // parseFlags sets the profile and counterStmt globals and performs validations. 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 // Must either display a profile or rewrite Go source. 115 if (profile == "") == (*mode == "") { 116 return fmt.Errorf("too many options") 117 } 118 119 if *mode != "" { 120 switch *mode { 121 case "set": 122 counterStmt = setCounterStmt 123 case "count": 124 counterStmt = incCounterStmt 125 case "atomic": 126 counterStmt = atomicCounterStmt 127 default: 128 return fmt.Errorf("unknown -mode %v", *mode) 129 } 130 131 if flag.NArg() == 0 { 132 return fmt.Errorf("missing source file") 133 } else if flag.NArg() == 1 { 134 return nil 135 } 136 } else if flag.NArg() == 0 { 137 return nil 138 } 139 return fmt.Errorf("too many arguments") 140 } 141 142 // Block represents the information about a basic block to be recorded in the analysis. 143 // Note: Our definition of basic block is based on control structures; we don't break 144 // apart && and ||. We could but it doesn't seem important enough to bother. 145 type Block struct { 146 startByte token.Pos 147 endByte token.Pos 148 numStmt int 149 } 150 151 // File is a wrapper for the state of a file used in the parser. 152 // The basic parse tree walker is a method of this type. 153 type File struct { 154 fset *token.FileSet 155 name string // Name of file. 156 astFile *ast.File 157 blocks []Block 158 atomicPkg string // Package name for "sync/atomic" in this file. 159 directives map[*ast.Comment]bool // Map of compiler directives to whether it's processed in ast.Visitor or not. 160 } 161 162 // Visit implements the ast.Visitor interface. 163 func (f *File) Visit(node ast.Node) ast.Visitor { 164 switch n := node.(type) { 165 case *ast.BlockStmt: 166 // If it's a switch or select, the body is a list of case clauses; don't tag the block itself. 167 if len(n.List) > 0 { 168 switch n.List[0].(type) { 169 case *ast.CaseClause: // switch 170 for _, n := range n.List { 171 clause := n.(*ast.CaseClause) 172 clause.Body = f.addCounters(clause.Colon+1, clause.End(), clause.Body, false) 173 } 174 return f 175 case *ast.CommClause: // select 176 for _, n := range n.List { 177 clause := n.(*ast.CommClause) 178 clause.Body = f.addCounters(clause.Colon+1, clause.End(), clause.Body, false) 179 } 180 return f 181 } 182 } 183 n.List = f.addCounters(n.Lbrace, n.Rbrace+1, n.List, true) // +1 to step past closing brace. 184 case *ast.IfStmt: 185 if n.Init != nil { 186 ast.Walk(f, n.Init) 187 } 188 ast.Walk(f, n.Cond) 189 ast.Walk(f, n.Body) 190 if n.Else == nil { 191 return nil 192 } 193 // The elses are special, because if we have 194 // if x { 195 // } else if y { 196 // } 197 // we want to cover the "if y". To do this, we need a place to drop the counter, 198 // so we add a hidden block: 199 // if x { 200 // } else { 201 // if y { 202 // } 203 // } 204 switch stmt := n.Else.(type) { 205 case *ast.IfStmt: 206 block := &ast.BlockStmt{ 207 Lbrace: n.Body.End(), // Start at end of the "if" block so the covered part looks like it starts at the "else". 208 List: []ast.Stmt{stmt}, 209 Rbrace: stmt.End(), 210 } 211 n.Else = block 212 case *ast.BlockStmt: 213 stmt.Lbrace = n.Body.End() // Start at end of the "if" block so the covered part looks like it starts at the "else". 214 default: 215 panic("unexpected node type in if") 216 } 217 ast.Walk(f, n.Else) 218 return nil 219 case *ast.SelectStmt: 220 // Don't annotate an empty select - creates a syntax error. 221 if n.Body == nil || len(n.Body.List) == 0 { 222 return nil 223 } 224 case *ast.SwitchStmt: 225 // Don't annotate an empty switch - creates a syntax error. 226 if n.Body == nil || len(n.Body.List) == 0 { 227 if n.Init != nil { 228 ast.Walk(f, n.Init) 229 } 230 if n.Tag != nil { 231 ast.Walk(f, n.Tag) 232 } 233 return nil 234 } 235 case *ast.TypeSwitchStmt: 236 // Don't annotate an empty type switch - creates a syntax error. 237 if n.Body == nil || len(n.Body.List) == 0 { 238 if n.Init != nil { 239 ast.Walk(f, n.Init) 240 } 241 ast.Walk(f, n.Assign) 242 return nil 243 } 244 case *ast.CommentGroup: 245 var list []*ast.Comment 246 // Drop all but the //go: comments, some of which are semantically important. 247 // We drop all others because they can appear in places that cause our counters 248 // to appear in syntactically incorrect places. //go: appears at the beginning of 249 // the line and is syntactically safe. 250 for _, c := range n.List { 251 if f.isDirective(c) { 252 list = append(list, c) 253 254 // Mark compiler directive as handled. 255 f.directives[c] = true 256 } 257 } 258 n.List = list 259 } 260 return f 261 } 262 263 // unquote returns the unquoted string. 264 func unquote(s string) string { 265 t, err := strconv.Unquote(s) 266 if err != nil { 267 log.Fatalf("cover: improperly quoted string %q\n", s) 268 } 269 return t 270 } 271 272 // addImport adds an import for the specified path, if one does not already exist, and returns 273 // the local package name. 274 func (f *File) addImport(path string) string { 275 // Does the package already import it? 276 for _, s := range f.astFile.Imports { 277 if unquote(s.Path.Value) == path { 278 if s.Name != nil { 279 return s.Name.Name 280 } 281 return filepath.Base(path) 282 } 283 } 284 newImport := &ast.ImportSpec{ 285 Name: ast.NewIdent(atomicPackageName), 286 Path: &ast.BasicLit{ 287 Kind: token.STRING, 288 Value: fmt.Sprintf("%q", path), 289 }, 290 } 291 impDecl := &ast.GenDecl{ 292 Tok: token.IMPORT, 293 Specs: []ast.Spec{ 294 newImport, 295 }, 296 } 297 // Make the new import the first Decl in the file. 298 astFile := f.astFile 299 astFile.Decls = append(astFile.Decls, nil) 300 copy(astFile.Decls[1:], astFile.Decls[0:]) 301 astFile.Decls[0] = impDecl 302 astFile.Imports = append(astFile.Imports, newImport) 303 304 // Now refer to the package, just in case it ends up unused. 305 // That is, append to the end of the file the declaration 306 // var _ = _cover_atomic_.AddUint32 307 reference := &ast.GenDecl{ 308 Tok: token.VAR, 309 Specs: []ast.Spec{ 310 &ast.ValueSpec{ 311 Names: []*ast.Ident{ 312 ast.NewIdent("_"), 313 }, 314 Values: []ast.Expr{ 315 &ast.SelectorExpr{ 316 X: ast.NewIdent(atomicPackageName), 317 Sel: ast.NewIdent("AddUint32"), 318 }, 319 }, 320 }, 321 }, 322 } 323 astFile.Decls = append(astFile.Decls, reference) 324 return atomicPackageName 325 } 326 327 var slashslash = []byte("//") 328 329 // initialComments returns the prefix of content containing only 330 // whitespace and line comments. Any +build directives must appear 331 // within this region. This approach is more reliable than using 332 // go/printer to print a modified AST containing comments. 333 // 334 func initialComments(content []byte) []byte { 335 // Derived from go/build.Context.shouldBuild. 336 end := 0 337 p := content 338 for len(p) > 0 { 339 line := p 340 if i := bytes.IndexByte(line, '\n'); i >= 0 { 341 line, p = line[:i], p[i+1:] 342 } else { 343 p = p[len(p):] 344 } 345 line = bytes.TrimSpace(line) 346 if len(line) == 0 { // Blank line. 347 end = len(content) - len(p) 348 continue 349 } 350 if !bytes.HasPrefix(line, slashslash) { // Not comment line. 351 break 352 } 353 } 354 return content[:end] 355 } 356 357 func annotate(name string) { 358 fset := token.NewFileSet() 359 content, err := ioutil.ReadFile(name) 360 if err != nil { 361 log.Fatalf("cover: %s: %s", name, err) 362 } 363 parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments) 364 if err != nil { 365 log.Fatalf("cover: %s: %s", name, err) 366 } 367 368 file := &File{ 369 fset: fset, 370 name: name, 371 astFile: parsedFile, 372 directives: map[*ast.Comment]bool{}, 373 } 374 if *mode == "atomic" { 375 file.atomicPkg = file.addImport(atomicPackagePath) 376 } 377 378 for _, cg := range parsedFile.Comments { 379 for _, c := range cg.List { 380 if file.isDirective(c) { 381 file.directives[c] = false 382 } 383 } 384 } 385 // Remove comments. Or else they interfere with new AST. 386 parsedFile.Comments = nil 387 388 ast.Walk(file, file.astFile) 389 fd := os.Stdout 390 if *output != "" { 391 var err error 392 fd, err = os.Create(*output) 393 if err != nil { 394 log.Fatalf("cover: %s", err) 395 } 396 } 397 fd.Write(initialComments(content)) // Retain '// +build' directives. 398 399 // Retain compiler directives that are not processed in ast.Visitor. 400 // Some compiler directives like "go:linkname" and "go:cgo_" 401 // can be not attached to anything in the tree and hence will not be printed by printer. 402 // So, we have to explicitly print them here. 403 for cd, handled := range file.directives { 404 if !handled { 405 fmt.Fprintln(fd, cd.Text) 406 } 407 } 408 409 file.print(fd) 410 // After printing the source tree, add some declarations for the counters etc. 411 // We could do this by adding to the tree, but it's easier just to print the text. 412 file.addVariables(fd) 413 } 414 415 func (f *File) print(w io.Writer) { 416 printer.Fprint(w, f.fset, f.astFile) 417 } 418 419 // isDirective reports whether a comment is a compiler directive. 420 func (f *File) isDirective(c *ast.Comment) bool { 421 return strings.HasPrefix(c.Text, "//go:") && f.fset.Position(c.Slash).Column == 1 422 } 423 424 // intLiteral returns an ast.BasicLit representing the integer value. 425 func (f *File) intLiteral(i int) *ast.BasicLit { 426 node := &ast.BasicLit{ 427 Kind: token.INT, 428 Value: fmt.Sprint(i), 429 } 430 return node 431 } 432 433 // index returns an ast.BasicLit representing the number of counters present. 434 func (f *File) index() *ast.BasicLit { 435 return f.intLiteral(len(f.blocks)) 436 } 437 438 // setCounterStmt returns the expression: __count[23] = 1. 439 func setCounterStmt(f *File, counter ast.Expr) ast.Stmt { 440 return &ast.AssignStmt{ 441 Lhs: []ast.Expr{counter}, 442 Tok: token.ASSIGN, 443 Rhs: []ast.Expr{f.intLiteral(1)}, 444 } 445 } 446 447 // incCounterStmt returns the expression: __count[23]++. 448 func incCounterStmt(f *File, counter ast.Expr) ast.Stmt { 449 return &ast.IncDecStmt{ 450 X: counter, 451 Tok: token.INC, 452 } 453 } 454 455 // atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1) 456 func atomicCounterStmt(f *File, counter ast.Expr) ast.Stmt { 457 return &ast.ExprStmt{ 458 X: &ast.CallExpr{ 459 Fun: &ast.SelectorExpr{ 460 X: ast.NewIdent(f.atomicPkg), 461 Sel: ast.NewIdent("AddUint32"), 462 }, 463 Args: []ast.Expr{&ast.UnaryExpr{ 464 Op: token.AND, 465 X: counter, 466 }, 467 f.intLiteral(1), 468 }, 469 }, 470 } 471 } 472 473 // newCounter creates a new counter expression of the appropriate form. 474 func (f *File) newCounter(start, end token.Pos, numStmt int) ast.Stmt { 475 counter := &ast.IndexExpr{ 476 X: &ast.SelectorExpr{ 477 X: ast.NewIdent(*varVar), 478 Sel: ast.NewIdent("Count"), 479 }, 480 Index: f.index(), 481 } 482 stmt := counterStmt(f, counter) 483 f.blocks = append(f.blocks, Block{start, end, numStmt}) 484 return stmt 485 } 486 487 // addCounters takes a list of statements and adds counters to the beginning of 488 // each basic block at the top level of that list. For instance, given 489 // 490 // S1 491 // if cond { 492 // S2 493 // } 494 // S3 495 // 496 // counters will be added before S1 and before S3. The block containing S2 497 // will be visited in a separate call. 498 // TODO: Nested simple blocks get unnecessary (but correct) counters 499 func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt { 500 // Special case: make sure we add a counter to an empty block. Can't do this below 501 // or we will add a counter to an empty statement list after, say, a return statement. 502 if len(list) == 0 { 503 return []ast.Stmt{f.newCounter(pos, blockEnd, 0)} 504 } 505 // We have a block (statement list), but it may have several basic blocks due to the 506 // appearance of statements that affect the flow of control. 507 var newList []ast.Stmt 508 for { 509 // Find first statement that affects flow of control (break, continue, if, etc.). 510 // It will be the last statement of this basic block. 511 var last int 512 end := blockEnd 513 for last = 0; last < len(list); last++ { 514 stmt := list[last] 515 end = f.statementBoundary(stmt) 516 if f.endsBasicSourceBlock(stmt) { 517 // If it is a labeled statement, we need to place a counter between 518 // the label and its statement because it may be the target of a goto 519 // and thus start a basic block. That is, given 520 // foo: stmt 521 // we need to create 522 // foo: ; stmt 523 // and mark the label as a block-terminating statement. 524 // The result will then be 525 // foo: COUNTER[n]++; stmt 526 // However, we can't do this if the labeled statement is already 527 // a control statement, such as a labeled for. 528 if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) { 529 newLabel := *label 530 newLabel.Stmt = &ast.EmptyStmt{ 531 Semicolon: label.Stmt.Pos(), 532 Implicit: true, 533 } 534 end = label.Pos() // Previous block ends before the label. 535 list[last] = &newLabel 536 // Open a gap and drop in the old statement, now without a label. 537 list = append(list, nil) 538 copy(list[last+1:], list[last:]) 539 list[last+1] = label.Stmt 540 } 541 last++ 542 extendToClosingBrace = false // Block is broken up now. 543 break 544 } 545 } 546 if extendToClosingBrace { 547 end = blockEnd 548 } 549 if pos != end { // Can have no source to cover if e.g. blocks abut. 550 newList = append(newList, f.newCounter(pos, end, last)) 551 } 552 newList = append(newList, list[0:last]...) 553 list = list[last:] 554 if len(list) == 0 { 555 break 556 } 557 pos = list[0].Pos() 558 } 559 return newList 560 } 561 562 // hasFuncLiteral reports the existence and position of the first func literal 563 // in the node, if any. If a func literal appears, it usually marks the termination 564 // of a basic block because the function body is itself a block. 565 // Therefore we draw a line at the start of the body of the first function literal we find. 566 // TODO: what if there's more than one? Probably doesn't matter much. 567 func hasFuncLiteral(n ast.Node) (bool, token.Pos) { 568 if n == nil { 569 return false, 0 570 } 571 var literal funcLitFinder 572 ast.Walk(&literal, n) 573 return literal.found(), token.Pos(literal) 574 } 575 576 // statementBoundary finds the location in s that terminates the current basic 577 // block in the source. 578 func (f *File) statementBoundary(s ast.Stmt) token.Pos { 579 // Control flow statements are easy. 580 switch s := s.(type) { 581 case *ast.BlockStmt: 582 // Treat blocks like basic blocks to avoid overlapping counters. 583 return s.Lbrace 584 case *ast.IfStmt: 585 found, pos := hasFuncLiteral(s.Init) 586 if found { 587 return pos 588 } 589 found, pos = hasFuncLiteral(s.Cond) 590 if found { 591 return pos 592 } 593 return s.Body.Lbrace 594 case *ast.ForStmt: 595 found, pos := hasFuncLiteral(s.Init) 596 if found { 597 return pos 598 } 599 found, pos = hasFuncLiteral(s.Cond) 600 if found { 601 return pos 602 } 603 found, pos = hasFuncLiteral(s.Post) 604 if found { 605 return pos 606 } 607 return s.Body.Lbrace 608 case *ast.LabeledStmt: 609 return f.statementBoundary(s.Stmt) 610 case *ast.RangeStmt: 611 found, pos := hasFuncLiteral(s.X) 612 if found { 613 return pos 614 } 615 return s.Body.Lbrace 616 case *ast.SwitchStmt: 617 found, pos := hasFuncLiteral(s.Init) 618 if found { 619 return pos 620 } 621 found, pos = hasFuncLiteral(s.Tag) 622 if found { 623 return pos 624 } 625 return s.Body.Lbrace 626 case *ast.SelectStmt: 627 return s.Body.Lbrace 628 case *ast.TypeSwitchStmt: 629 found, pos := hasFuncLiteral(s.Init) 630 if found { 631 return pos 632 } 633 return s.Body.Lbrace 634 } 635 // If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal. 636 // If it does, that's tricky because we want to exclude the body of the function from this block. 637 // Draw a line at the start of the body of the first function literal we find. 638 // TODO: what if there's more than one? Probably doesn't matter much. 639 found, pos := hasFuncLiteral(s) 640 if found { 641 return pos 642 } 643 return s.End() 644 } 645 646 // endsBasicSourceBlock reports whether s changes the flow of control: break, if, etc., 647 // or if it's just problematic, for instance contains a function literal, which will complicate 648 // accounting due to the block-within-an expression. 649 func (f *File) endsBasicSourceBlock(s ast.Stmt) bool { 650 switch s := s.(type) { 651 case *ast.BlockStmt: 652 // Treat blocks like basic blocks to avoid overlapping counters. 653 return true 654 case *ast.BranchStmt: 655 return true 656 case *ast.ForStmt: 657 return true 658 case *ast.IfStmt: 659 return true 660 case *ast.LabeledStmt: 661 return true // A goto may branch here, starting a new basic block. 662 case *ast.RangeStmt: 663 return true 664 case *ast.SwitchStmt: 665 return true 666 case *ast.SelectStmt: 667 return true 668 case *ast.TypeSwitchStmt: 669 return true 670 case *ast.ExprStmt: 671 // Calls to panic change the flow. 672 // We really should verify that "panic" is the predefined function, 673 // but without type checking we can't and the likelihood of it being 674 // an actual problem is vanishingly small. 675 if call, ok := s.X.(*ast.CallExpr); ok { 676 if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 { 677 return true 678 } 679 } 680 } 681 found, _ := hasFuncLiteral(s) 682 return found 683 } 684 685 // isControl reports whether s is a control statement that, if labeled, cannot be 686 // separated from its label. 687 func (f *File) isControl(s ast.Stmt) bool { 688 switch s.(type) { 689 case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt: 690 return true 691 } 692 return false 693 } 694 695 // funcLitFinder implements the ast.Visitor pattern to find the location of any 696 // function literal in a subtree. 697 type funcLitFinder token.Pos 698 699 func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) { 700 if f.found() { 701 return nil // Prune search. 702 } 703 switch n := node.(type) { 704 case *ast.FuncLit: 705 *f = funcLitFinder(n.Body.Lbrace) 706 return nil // Prune search. 707 } 708 return f 709 } 710 711 func (f *funcLitFinder) found() bool { 712 return token.Pos(*f) != token.NoPos 713 } 714 715 // Sort interface for []block1; used for self-check in addVariables. 716 717 type block1 struct { 718 Block 719 index int 720 } 721 722 type blockSlice []block1 723 724 func (b blockSlice) Len() int { return len(b) } 725 func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte } 726 func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 727 728 // offset translates a token position into a 0-indexed byte offset. 729 func (f *File) offset(pos token.Pos) int { 730 return f.fset.Position(pos).Offset 731 } 732 733 // addVariables adds to the end of the file the declarations to set up the counter and position variables. 734 func (f *File) addVariables(w io.Writer) { 735 // Self-check: Verify that the instrumented basic blocks are disjoint. 736 t := make([]block1, len(f.blocks)) 737 for i := range f.blocks { 738 t[i].Block = f.blocks[i] 739 t[i].index = i 740 } 741 sort.Sort(blockSlice(t)) 742 for i := 1; i < len(t); i++ { 743 if t[i-1].endByte > t[i].startByte { 744 fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index) 745 // Note: error message is in byte positions, not token positions. 746 fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n", 747 f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte), 748 f.name, f.offset(t[i].startByte), f.offset(t[i].endByte)) 749 } 750 } 751 752 // Declare the coverage struct as a package-level variable. 753 fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar) 754 fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks)) 755 fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks)) 756 fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks)) 757 fmt.Fprintf(w, "} {\n") 758 759 // Initialize the position array field. 760 fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks)) 761 762 // A nice long list of positions. Each position is encoded as follows to reduce size: 763 // - 32-bit starting line number 764 // - 32-bit ending line number 765 // - (16 bit ending column number << 16) | (16-bit starting column number). 766 for i, block := range f.blocks { 767 start := f.fset.Position(block.startByte) 768 end := f.fset.Position(block.endByte) 769 fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i) 770 } 771 772 // Close the position array. 773 fmt.Fprintf(w, "\t},\n") 774 775 // Initialize the position array field. 776 fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks)) 777 778 // A nice long list of statements-per-block, so we can give a conventional 779 // valuation of "percent covered". To save space, it's a 16-bit number, so we 780 // clamp it if it overflows - won't matter in practice. 781 for i, block := range f.blocks { 782 n := block.numStmt 783 if n > 1<<16-1 { 784 n = 1<<16 - 1 785 } 786 fmt.Fprintf(w, "\t\t%d, // %d\n", n, i) 787 } 788 789 // Close the statements-per-block array. 790 fmt.Fprintf(w, "\t},\n") 791 792 // Close the struct initialization. 793 fmt.Fprintf(w, "}\n") 794 }