Skip to content

Commit

Permalink
Collect coverage for other languages
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fmeum committed Sep 10, 2022
1 parent 4068712 commit ceca8e4
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 42 deletions.
18 changes: 14 additions & 4 deletions go/private/rules/test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -402,11 +402,21 @@ _go_test_kwargs = {
default = ["//go/tools/bzltestutil"],
cfg = go_transition,
),
# Workaround for bazelbuild/bazel#6293. See comment in lcov_merger.sh.
# Required for Bazel to collect coverage of instrumented C/C++ binaries
# executed by go_test.
# This is just a shell script and thus cheap enough to depend on
# unconditionally.
"_collect_cc_coverage": attr.label(
default = "@bazel_tools//tools/test:collect_cc_coverage",
cfg = "exec",
),
# Required for Bazel to merge coverage reports for Go and other
# languages into a single report per test.
# Using configuration_field ensures that the tool is only built when
# run with bazel coverage, not with bazel test.
"_lcov_merger": attr.label(
executable = True,
default = "//go/tools/builders:lcov_merger",
cfg = "target",
default = configuration_field(fragment = "coverage", name = "output_generator"),
cfg = "exec",
),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
Expand Down
6 changes: 0 additions & 6 deletions go/tools/builders/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,6 @@ go_reset_target(
visibility = ["//visibility:public"],
)

sh_binary(
name = "lcov_merger",
srcs = ["lcov_merger.sh"],
visibility = ["//visibility:public"],
)

filegroup(
name = "all_builder_srcs",
testonly = True,
Expand Down
21 changes: 0 additions & 21 deletions go/tools/builders/lcov_merger.sh

This file was deleted.

13 changes: 4 additions & 9 deletions go/tools/bzltestutil/lcov.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,7 @@ import (
// Bazel.
// The conversion emits line and branch coverage, but not function coverage.
func ConvertCoverToLcov() error {
// The value of test.coverprofile has been set to
// ${COVERAGE_OUTPUT_PATH}.cover by the generated TestMain. We have to strip
// the ".cover" suffix here so that the generated expectedLcov report is at the
// location where Bazel's expectedLcov merge picks it up.
inPath := flag.Lookup("test.coverprofile").Value.String()
outPath := strings.TrimSuffix(inPath, ".cover")

in, err := os.Open(inPath)
if err != nil {
// This can happen if there are no tests and should not be an error.
Expand All @@ -48,7 +42,8 @@ func ConvertCoverToLcov() error {
}
defer in.Close()

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

func convertCoverToLcov(coverReader io.Reader, lcovWriter io.Writer) error {
Expand Down
120 changes: 118 additions & 2 deletions tests/core/coverage/lcov_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ go_test(
srcs = ["lib_test.go"],
deps = [":lib"],
)
java_binary(
name = "Tool",
srcs = ["Tool.java"],
)
go_test(
name = "lib_with_tool_test",
srcs = ["lib_with_tool_test.go"],
data = [":Tool"],
deps = [":lib"],
)
-- src/lib.go --
package lib
Expand Down Expand Up @@ -90,6 +102,40 @@ func TestLib(t *testing.T) {
t.Error("Expected a newline in the output")
}
}
-- src/Tool.java --
public class Tool {
public static void main(String[] args) {
if (args.length != 0) {
System.err.println("Expected no arguments");
System.exit(1);
}
System.err.println("Hello, world!");
}
}
-- src/lib_with_tool_test.go --
package lib_test
import (
"os/exec"
"strings"
"testing"
"example.com/lib"
)
func TestLib(t *testing.T) {
if !strings.Contains(lib.HelloFromLib(false), "\n") {
t.Error("Expected a newline in the output")
}
}
func TestTool(t *testing.T) {
err := exec.Command("Tool").Run()
if err != nil {
t.Error(err)
}
}
`,
})
}
Expand Down Expand Up @@ -120,7 +166,7 @@ func testLcovCoverage(t *testing.T, extraArgs ...string) {
if err != nil {
t.Fatal(err)
}
for _, expectedIndividualCoverage := range expectedIndividualCoverages {
for _, expectedIndividualCoverage := range expectedGoCoverage {
if !strings.Contains(string(individualCoverageData), expectedIndividualCoverage) {
t.Errorf(
"%s: does not contain:\n\n%s\nactual content:\n\n%s",
Expand All @@ -146,8 +192,54 @@ func testLcovCoverage(t *testing.T, extraArgs ...string) {
}
}

var expectedIndividualCoverages = []string{
func TestLcovCoverageWithTool(t *testing.T) {
args := append([]string{
"coverage",
"--combined_report=lcov",
"//src:lib_with_tool_test",
})

if err := bazel_testing.RunBazel(args...); err != nil {
t.Fatal(err)
}

individualCoveragePath := filepath.FromSlash("bazel-testlogs/src/lib_with_tool_test/coverage.dat")
individualCoverageData, err := ioutil.ReadFile(individualCoveragePath)
if err != nil {
t.Fatal(err)
}
expectedCoverage := append(expectedGoCoverage, expectedToolCoverage)
for _, expected := range expectedCoverage {
if !strings.Contains(string(individualCoverageData), expected) {
t.Errorf(
"%s: does not contain:\n\n%s\nactual content:\n\n%s",
individualCoveragePath,
expected,
string(individualCoverageData),
)
}
}

combinedCoveragePath := filepath.FromSlash("bazel-out/_coverage/_coverage_report.dat")
combinedCoverageData, err := ioutil.ReadFile(combinedCoveragePath)
if err != nil {
t.Fatal(err)
}
for _, include := range []string{
"SF:src/lib.go\n",
"SF:src/other_lib.go\n",
"SF:src/Tool.java\n",
} {
if !strings.Contains(string(combinedCoverageData), include) {
t.Errorf("%s: does not contain %q\n", combinedCoverageData, include)
}
}
}

var expectedGoCoverage = []string{
`SF:src/other_lib.go
FNF:0
FNH:0
DA:3,1
DA:4,1
DA:5,0
Expand All @@ -158,6 +250,8 @@ LF:5
end_of_record
`,
`SF:src/lib.go
FNF:0
FNH:0
DA:9,1
DA:10,1
DA:11,1
Expand All @@ -171,3 +265,25 @@ LH:8
LF:9
end_of_record
`}

const expectedToolCoverage = `SF:src/Tool.java
FN:1,Tool::<init> ()V
FN:3,Tool::main ([Ljava/lang/String;)V
FNDA:0,Tool::<init> ()V
FNDA:1,Tool::main ([Ljava/lang/String;)V
FNF:2
FNH:1
BRDA:3,0,0,1
BRDA:3,0,1,0
BRF:2
BRH:1
DA:1,0
DA:3,1
DA:4,0
DA:5,0
DA:7,1
DA:8,1
LH:3
LF:6
end_of_record
`
2 changes: 2 additions & 0 deletions tests/core/coverage/lcov_test_main_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ func TestLcovCoverageWithTestMain(t *testing.T) {
}

const expectedIndividualCoverage = `SF:src/lib.go
FNF:0
FNH:0
DA:3,1
DA:4,1
DA:5,0
Expand Down

0 comments on commit ceca8e4

Please sign in to comment.