Skip to content

Commit d94e692

Browse files
authored
Collect coverage for other languages (#3287)
When using `--combined_report=lcov`, the coverage report for a `go_test` will also include coverage for data dependencies executed by the test, e.g. `java_binary` or `cc_binary` tools run in an integration test. Note: This commit does *not* make it so that coverage is collected for `go_binary`s executed by `go_test`s - Go doesn't provide exit hooks, so this would be rather involved to implement.
1 parent cf20167 commit d94e692

File tree

6 files changed

+138
-42
lines changed

6 files changed

+138
-42
lines changed

go/private/rules/test.bzl

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,21 @@ _go_test_kwargs = {
402402
default = ["//go/tools/bzltestutil"],
403403
cfg = go_transition,
404404
),
405-
# Workaround for bazelbuild/bazel#6293. See comment in lcov_merger.sh.
405+
# Required for Bazel to collect coverage of instrumented C/C++ binaries
406+
# executed by go_test.
407+
# This is just a shell script and thus cheap enough to depend on
408+
# unconditionally.
409+
"_collect_cc_coverage": attr.label(
410+
default = "@bazel_tools//tools/test:collect_cc_coverage",
411+
cfg = "exec",
412+
),
413+
# Required for Bazel to merge coverage reports for Go and other
414+
# languages into a single report per test.
415+
# Using configuration_field ensures that the tool is only built when
416+
# run with bazel coverage, not with bazel test.
406417
"_lcov_merger": attr.label(
407-
executable = True,
408-
default = "//go/tools/builders:lcov_merger",
409-
cfg = "target",
418+
default = configuration_field(fragment = "coverage", name = "output_generator"),
419+
cfg = "exec",
410420
),
411421
"_allowlist_function_transition": attr.label(
412422
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",

go/tools/builders/BUILD.bazel

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,6 @@ go_reset_target(
149149
visibility = ["//visibility:public"],
150150
)
151151

152-
sh_binary(
153-
name = "lcov_merger",
154-
srcs = ["lcov_merger.sh"],
155-
visibility = ["//visibility:public"],
156-
)
157-
158152
filegroup(
159153
name = "all_builder_srcs",
160154
testonly = True,

go/tools/builders/lcov_merger.sh

Lines changed: 0 additions & 21 deletions
This file was deleted.

go/tools/bzltestutil/lcov.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,7 @@ import (
3333
// Bazel.
3434
// The conversion emits line and branch coverage, but not function coverage.
3535
func ConvertCoverToLcov() error {
36-
// The value of test.coverprofile has been set to
37-
// ${COVERAGE_OUTPUT_PATH}.cover by the generated TestMain. We have to strip
38-
// the ".cover" suffix here so that the generated expectedLcov report is at the
39-
// location where Bazel's expectedLcov merge picks it up.
4036
inPath := flag.Lookup("test.coverprofile").Value.String()
41-
outPath := strings.TrimSuffix(inPath, ".cover")
42-
4337
in, err := os.Open(inPath)
4438
if err != nil {
4539
// This can happen if there are no tests and should not be an error.
@@ -48,7 +42,8 @@ func ConvertCoverToLcov() error {
4842
}
4943
defer in.Close()
5044

51-
out, err := os.Create(outPath)
45+
// All *.dat files in $COVERAGE_DIR will be merged by Bazel's lcov_merger tool.
46+
out, err := os.CreateTemp(os.Getenv("COVERAGE_DIR"), "go_coverage.*.dat")
5247
if err != nil {
5348
return err
5449
}
@@ -62,8 +57,8 @@ var _coverLinePattern = regexp.MustCompile(`^(?P<path>.+):(?P<startLine>\d+)\.(?
6257
const (
6358
_pathIdx = 1
6459
_startLineIdx = 2
65-
_endLineIdx = 4
66-
_countIdx = 7
60+
_endLineIdx = 4
61+
_countIdx = 7
6762
)
6863

6964
func convertCoverToLcov(coverReader io.Reader, lcovWriter io.Writer) error {

tests/core/coverage/lcov_coverage_test.go

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ go_test(
4747
srcs = ["lib_test.go"],
4848
deps = [":lib"],
4949
)
50+
51+
java_binary(
52+
name = "Tool",
53+
srcs = ["Tool.java"],
54+
)
55+
56+
go_test(
57+
name = "lib_with_tool_test",
58+
srcs = ["lib_with_tool_test.go"],
59+
data = [":Tool"],
60+
deps = [":lib"],
61+
)
5062
-- src/lib.go --
5163
package lib
5264
@@ -90,6 +102,40 @@ func TestLib(t *testing.T) {
90102
t.Error("Expected a newline in the output")
91103
}
92104
}
105+
-- src/Tool.java --
106+
public class Tool {
107+
public static void main(String[] args) {
108+
if (args.length != 0) {
109+
System.err.println("Expected no arguments");
110+
System.exit(1);
111+
}
112+
System.err.println("Hello, world!");
113+
}
114+
}
115+
-- src/lib_with_tool_test.go --
116+
package lib_test
117+
118+
import (
119+
"os/exec"
120+
"strings"
121+
"testing"
122+
123+
"example.com/lib"
124+
)
125+
126+
func TestLib(t *testing.T) {
127+
if !strings.Contains(lib.HelloFromLib(false), "\n") {
128+
t.Error("Expected a newline in the output")
129+
}
130+
}
131+
132+
func TestTool(t *testing.T) {
133+
err := exec.Command("Tool").Run()
134+
if err != nil {
135+
t.Error(err)
136+
}
137+
}
138+
93139
`,
94140
})
95141
}
@@ -120,7 +166,7 @@ func testLcovCoverage(t *testing.T, extraArgs ...string) {
120166
if err != nil {
121167
t.Fatal(err)
122168
}
123-
for _, expectedIndividualCoverage := range expectedIndividualCoverages {
169+
for _, expectedIndividualCoverage := range expectedGoCoverage {
124170
if !strings.Contains(string(individualCoverageData), expectedIndividualCoverage) {
125171
t.Errorf(
126172
"%s: does not contain:\n\n%s\nactual content:\n\n%s",
@@ -146,8 +192,54 @@ func testLcovCoverage(t *testing.T, extraArgs ...string) {
146192
}
147193
}
148194

149-
var expectedIndividualCoverages = []string{
195+
func TestLcovCoverageWithTool(t *testing.T) {
196+
args := append([]string{
197+
"coverage",
198+
"--combined_report=lcov",
199+
"//src:lib_with_tool_test",
200+
})
201+
202+
if err := bazel_testing.RunBazel(args...); err != nil {
203+
t.Fatal(err)
204+
}
205+
206+
individualCoveragePath := filepath.FromSlash("bazel-testlogs/src/lib_with_tool_test/coverage.dat")
207+
individualCoverageData, err := ioutil.ReadFile(individualCoveragePath)
208+
if err != nil {
209+
t.Fatal(err)
210+
}
211+
expectedCoverage := append(expectedGoCoverage, expectedToolCoverage)
212+
for _, expected := range expectedCoverage {
213+
if !strings.Contains(string(individualCoverageData), expected) {
214+
t.Errorf(
215+
"%s: does not contain:\n\n%s\nactual content:\n\n%s",
216+
individualCoveragePath,
217+
expected,
218+
string(individualCoverageData),
219+
)
220+
}
221+
}
222+
223+
combinedCoveragePath := filepath.FromSlash("bazel-out/_coverage/_coverage_report.dat")
224+
combinedCoverageData, err := ioutil.ReadFile(combinedCoveragePath)
225+
if err != nil {
226+
t.Fatal(err)
227+
}
228+
for _, include := range []string{
229+
"SF:src/lib.go\n",
230+
"SF:src/other_lib.go\n",
231+
"SF:src/Tool.java\n",
232+
} {
233+
if !strings.Contains(string(combinedCoverageData), include) {
234+
t.Errorf("%s: does not contain %q\n", combinedCoverageData, include)
235+
}
236+
}
237+
}
238+
239+
var expectedGoCoverage = []string{
150240
`SF:src/other_lib.go
241+
FNF:0
242+
FNH:0
151243
DA:3,1
152244
DA:4,1
153245
DA:5,0
@@ -158,6 +250,8 @@ LF:5
158250
end_of_record
159251
`,
160252
`SF:src/lib.go
253+
FNF:0
254+
FNH:0
161255
DA:9,1
162256
DA:10,1
163257
DA:11,1
@@ -171,3 +265,25 @@ LH:8
171265
LF:9
172266
end_of_record
173267
`}
268+
269+
const expectedToolCoverage = `SF:src/Tool.java
270+
FN:1,Tool::<init> ()V
271+
FN:3,Tool::main ([Ljava/lang/String;)V
272+
FNDA:0,Tool::<init> ()V
273+
FNDA:1,Tool::main ([Ljava/lang/String;)V
274+
FNF:2
275+
FNH:1
276+
BRDA:3,0,0,1
277+
BRDA:3,0,1,0
278+
BRF:2
279+
BRH:1
280+
DA:1,0
281+
DA:3,1
282+
DA:4,0
283+
DA:5,0
284+
DA:7,1
285+
DA:8,1
286+
LH:3
287+
LF:6
288+
end_of_record
289+
`

tests/core/coverage/lcov_test_main_coverage_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ func TestLcovCoverageWithTestMain(t *testing.T) {
108108
}
109109

110110
const expectedIndividualCoverage = `SF:src/lib.go
111+
FNF:0
112+
FNH:0
111113
DA:3,1
112114
DA:4,1
113115
DA:5,0

0 commit comments

Comments
 (0)