xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/cover.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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