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 15package main 16 17import ( 18 "bytes" 19 "fmt" 20 "go/parser" 21 "go/token" 22 "io/ioutil" 23 "os" 24 "strconv" 25) 26 27// instrumentForCoverage runs "go tool cover" on a source file to produce 28// a coverage-instrumented version of the file. It also registers the file 29// with the coverdata package. 30func instrumentForCoverage(goenv *env, srcPath, srcName, coverVar, mode, outPath string) error { 31 goargs := goenv.goTool("cover", "-var", coverVar, "-mode", mode, "-o", outPath, srcPath) 32 if err := goenv.runCommand(goargs); err != nil { 33 return err 34 } 35 36 return registerCoverage(outPath, coverVar, srcName) 37} 38 39// registerCoverage modifies coverSrcFilename, the output file from go tool cover. 40// It adds a call to coverdata.RegisterCoverage, which ensures the coverage 41// data from each file is reported. The name by which the file is registered 42// need not match its original name (it may use the importpath). 43func registerCoverage(coverSrcFilename, varName, srcName string) error { 44 coverSrc, err := os.ReadFile(coverSrcFilename) 45 if err != nil { 46 return fmt.Errorf("instrumentForCoverage: reading instrumented source: %w", err) 47 } 48 49 // Parse the file. 50 fset := token.NewFileSet() 51 f, err := parser.ParseFile(fset, coverSrcFilename, coverSrc, parser.ParseComments) 52 if err != nil { 53 return nil // parse error: proceed and let the compiler fail 54 } 55 56 // Perform edits using a byte buffer instead of the AST, because 57 // we can not use go/format to write the AST back out without 58 // changing line numbers. 59 editor := NewBuffer(coverSrc) 60 61 // Ensure coverdata is imported. Use an existing import if present 62 // or add a new one. 63 const coverdataPath = "github.com/bazelbuild/rules_go/go/tools/coverdata" 64 var coverdataName string 65 for _, imp := range f.Imports { 66 path, err := strconv.Unquote(imp.Path.Value) 67 if err != nil { 68 return nil // parse error: proceed and let the compiler fail 69 } 70 if path == coverdataPath { 71 if imp.Name != nil { 72 // renaming import 73 if imp.Name.Name == "_" { 74 // Change blank import to named import 75 editor.Replace( 76 fset.Position(imp.Name.Pos()).Offset, 77 fset.Position(imp.Name.End()).Offset, 78 "coverdata") 79 coverdataName = "coverdata" 80 } else { 81 coverdataName = imp.Name.Name 82 } 83 } else { 84 // default import 85 coverdataName = "coverdata" 86 } 87 break 88 } 89 } 90 if coverdataName == "" { 91 // No existing import. Add a new one. 92 coverdataName = "coverdata" 93 editor.Insert(fset.Position(f.Name.End()).Offset, fmt.Sprintf("; import %q", coverdataPath)) 94 } 95 96 // Append an init function. 97 var buf = bytes.NewBuffer(editor.Bytes()) 98 fmt.Fprintf(buf, ` 99func init() { 100 %s.RegisterFile(%q, 101 %[3]s.Count[:], 102 %[3]s.Pos[:], 103 %[3]s.NumStmt[:]) 104} 105`, coverdataName, srcName, varName) 106 if err := ioutil.WriteFile(coverSrcFilename, buf.Bytes(), 0666); err != nil { 107 return fmt.Errorf("registerCoverage: %v", err) 108 } 109 return nil 110} 111