github.com/ajguerrer/rules_go@v0.20.3/go/tools/builders/cover.go (about) 1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "bytes" 19 "flag" 20 "fmt" 21 "go/ast" 22 "go/format" 23 "go/parser" 24 "go/token" 25 "io/ioutil" 26 "strconv" 27 ) 28 29 // cover transforms a source file with "go tool cover". It is invoked by the 30 // Go rules as an action. 31 func cover(args []string) error { 32 args, err := readParamsFiles(args) 33 if err != nil { 34 return err 35 } 36 flags := flag.NewFlagSet("cover", flag.ExitOnError) 37 var coverSrc, coverVar, origSrc, srcName, mode string 38 flags.StringVar(&coverSrc, "o", "", "coverage output file") 39 flags.StringVar(&coverVar, "var", "", "name of cover variable") 40 flags.StringVar(&origSrc, "src", "", "original source file") 41 flags.StringVar(&srcName, "srcname", "", "source name printed in coverage data") 42 flags.StringVar(&mode, "mode", "set", "coverage mode to use") 43 goenv := envFlags(flags) 44 if err := flags.Parse(args); err != nil { 45 return err 46 } 47 if err := goenv.checkFlags(); err != nil { 48 return err 49 } 50 if coverSrc == "" { 51 return fmt.Errorf("-o was not set") 52 } 53 if coverVar == "" { 54 return fmt.Errorf("-var was not set") 55 } 56 if origSrc == "" { 57 return fmt.Errorf("-src was not set") 58 } 59 if srcName == "" { 60 srcName = origSrc 61 } 62 63 return instrumentForCoverage(goenv, origSrc, srcName, coverVar, mode, coverSrc) 64 } 65 66 // instrumentForCoverage runs "go tool cover" on a source file to produce 67 // a coverage-instrumented version of the file. It also registers the file 68 // with the coverdata package. 69 func instrumentForCoverage(goenv *env, srcPath, srcName, coverVar, mode, outPath string) error { 70 goargs := goenv.goTool("cover", "-var", coverVar, "-mode", mode, "-o", outPath, srcPath) 71 if err := goenv.runCommand(goargs); err != nil { 72 return err 73 } 74 75 return registerCoverage(outPath, coverVar, srcName) 76 } 77 78 // registerCoverage modifies coverSrc, the output file from go tool cover. It 79 // adds a call to coverdata.RegisterCoverage, which ensures the coverage 80 // data from each file is reported. The name by which the file is registered 81 // need not match its original name (it may use the importpath). 82 func registerCoverage(coverSrc, varName, srcName string) error { 83 // Parse the file. 84 fset := token.NewFileSet() 85 f, err := parser.ParseFile(fset, coverSrc, nil, parser.ParseComments) 86 if err != nil { 87 return nil // parse error: proceed and let the compiler fail 88 } 89 90 // Ensure coverdata is imported in the AST. Use an existing import if present 91 // or add a new one. 92 const coverdataPath = "github.com/bazelbuild/rules_go/go/tools/coverdata" 93 var coverdataName string 94 for _, imp := range f.Imports { 95 path, err := strconv.Unquote(imp.Path.Value) 96 if err != nil { 97 return nil // parse error: proceed and let the compiler fail 98 } 99 if path == coverdataPath { 100 if imp.Name != nil { 101 // renaming import 102 if imp.Name.Name == "_" { 103 // Change blank import to named import 104 imp.Name.Name = "coverdata" 105 } 106 coverdataName = imp.Name.Name 107 } else { 108 // default import 109 coverdataName = "coverdata" 110 } 111 break 112 } 113 } 114 if coverdataName == "" { 115 // No existing import. Add a new one. 116 coverdataName = "coverdata" 117 addNamedImport(fset, f, coverdataName, coverdataPath) 118 } 119 var buf bytes.Buffer 120 if err := format.Node(&buf, fset, f); err != nil { 121 return fmt.Errorf("registerCoverage: could not reformat coverage source %s: %v", coverSrc, err) 122 } 123 124 // Append an init function. 125 fmt.Fprintf(&buf, ` 126 func init() { 127 %s.RegisterFile(%q, 128 %[3]s.Count[:], 129 %[3]s.Pos[:], 130 %[3]s.NumStmt[:]) 131 } 132 `, coverdataName, srcName, varName) 133 if err := ioutil.WriteFile(coverSrc, buf.Bytes(), 0666); err != nil { 134 return fmt.Errorf("registerCoverage: %v", err) 135 } 136 137 return nil 138 } 139 140 func addNamedImport(fset *token.FileSet, f *ast.File, name, path string) { 141 imp := &ast.ImportSpec{ 142 Name: &ast.Ident{Name: name}, 143 Path: &ast.BasicLit{ 144 Kind: token.STRING, 145 Value: strconv.Quote(path), 146 }, 147 } 148 impDecl := &ast.GenDecl{Tok: token.IMPORT, Specs: []ast.Spec{imp}} 149 f.Decls = append([]ast.Decl{impDecl}, f.Decls...) 150 151 // Our new import, preceded by a blank line, goes after the package declaration 152 // and after the comment, if any, that starts on the same line as the 153 // package declaration. 154 impDecl.TokPos = f.Package 155 file := fset.File(f.Package) 156 pkgLine := file.Line(f.Package) 157 for _, c := range f.Comments { 158 if file.Line(c.Pos()) > pkgLine { 159 break 160 } 161 // +2 for a blank line 162 impDecl.TokPos = c.End() + 2 163 } 164 }