Skip to content

Commit

Permalink
Generate coverage reports in LCOV format (#3117)
Browse files Browse the repository at this point in the history
The generated TestMain now converts the go cover coverage reports to
LCOV. When combined with Bazel's --combined_report=lcov, this makes Go
coverage show up in cross-language, cross-target coverage reports by
default.

The combined report can be converted to HTML via e.g.

genhtml bazel-out/_coverage/_coverage_report.dat

If the old behavior of generating cover reports is still desired, it
can be enabled via:

--@io_bazel_rules_go//go/config:cover_format="go_cover"

Co-authored-by: Abhinav Gupta <[email protected]>
  • Loading branch information
fmeum and abhinav authored Apr 22, 2022
1 parent 29ca6bd commit c142604
Show file tree
Hide file tree
Showing 19 changed files with 677 additions and 25 deletions.
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ cgo_context_data_proxy(
# to depend on all build settings directly.
go_config(
name = "go_config",
cover_format = "//go/config:cover_format",
# Always include debug symbols with -c dbg.
debug = select({
"//go/private:is_compilation_mode_dbg": "//go/private:always_true",
Expand Down
10 changes: 10 additions & 0 deletions go/config/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ string_list_flag(
visibility = ["//visibility:public"],
)

string_flag(
name = "cover_format",
build_setting_default = "lcov",
values = [
"go_cover",
"lcov",
],
visibility = ["//visibility:public"],
)

filegroup(
name = "all_files",
testonly = True,
Expand Down
1 change: 1 addition & 0 deletions go/private/actions/compilepkg.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def emit_compilepkg(
args.add("-cover_mode", "atomic")
else:
args.add("-cover_mode", "set")
args.add("-cover_format", go.cover_format)
args.add_all(cover, before_each = "-cover")
args.add_all(archives, before_each = "-arc", map_each = _archive)
if importpath:
Expand Down
6 changes: 6 additions & 0 deletions go/private/context.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ def go_context(ctx, attr = None):
tags = tags,
stamp = mode.stamp,
label = ctx.label,
cover_format = mode.cover_format,

# Action generators
archive = toolchain.actions.archive,
Expand Down Expand Up @@ -780,6 +781,7 @@ def _go_config_impl(ctx):
linkmode = ctx.attr.linkmode[BuildSettingInfo].value,
tags = ctx.attr.gotags[BuildSettingInfo].value,
stamp = ctx.attr.stamp,
cover_format = ctx.attr.cover_format[BuildSettingInfo].value,
)]

go_config = rule(
Expand Down Expand Up @@ -818,6 +820,10 @@ go_config = rule(
providers = [BuildSettingInfo],
),
"stamp": attr.bool(mandatory = True),
"cover_format": attr.label(
mandatory = True,
providers = [BuildSettingInfo],
),
},
provides = [GoConfigInfo],
doc = """Collects information about build settings in the current
Expand Down
2 changes: 2 additions & 0 deletions go/private/mode.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def get_mode(ctx, go_toolchain, cgo_context_info, go_config_info):
stamp = go_config_info.stamp if go_config_info else False
debug = go_config_info.debug if go_config_info else False
linkmode = go_config_info.linkmode if go_config_info else LINKMODE_NORMAL
cover_format = go_config_info and go_config_info.cover_format
goos = go_toolchain.default_goos
goarch = go_toolchain.default_goarch

Expand Down Expand Up @@ -107,6 +108,7 @@ def get_mode(ctx, go_toolchain, cgo_context_info, go_config_info):
goos = goos,
goarch = goarch,
tags = tags,
cover_format = cover_format,
)

def installsuffix(mode):
Expand Down
1 change: 1 addition & 0 deletions go/private/rules/test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def _go_test_impl(ctx):
arguments.add("-cover_mode", "atomic")
else:
arguments.add("-cover_mode", "set")
arguments.add("-cover_format", go.cover_format)
arguments.add(
# the l is the alias for the package under test, the l_test must be the
# same with the test suffix
Expand Down
33 changes: 22 additions & 11 deletions go/tools/builders/compilepkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func compilePkg(args []string) error {
var outPath, outFactsPath, cgoExportHPath string
var testFilter string
var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag
var coverFormat string
fs.Var(&unfilteredSrcs, "src", ".go, .c, .cc, .m, .mm, .s, or .S file to be filtered and compiled")
fs.Var(&coverSrcs, "cover", ".go file that should be instrumented for coverage (must also be a -src)")
fs.Var(&embedSrcs, "embedsrc", "file that may be compiled into the package with a //go:embed directive")
Expand All @@ -67,6 +68,7 @@ func compilePkg(args []string) error {
fs.StringVar(&outFactsPath, "x", "", "The output archive file to write export data and nogo facts")
fs.StringVar(&cgoExportHPath, "cgoexport", "", "The _cgo_exports.h file to write")
fs.StringVar(&testFilter, "testfilter", "off", "Controls test package filtering")
fs.StringVar(&coverFormat, "cover_format", "", "Emit source file paths in coverage instrumentation suitable for the specified coverage format")
if err := fs.Parse(args); err != nil {
return err
}
Expand All @@ -85,9 +87,6 @@ func compilePkg(args []string) error {
for i := range embedSrcs {
embedSrcs[i] = abs(embedSrcs[i])
}
for i := range coverSrcs {
coverSrcs[i] = abs(coverSrcs[i])
}

// Filter sources.
srcs, err := filterAndSplitFiles(unfilteredSrcs)
Expand Down Expand Up @@ -143,7 +142,8 @@ func compilePkg(args []string) error {
packageListPath,
outPath,
outFactsPath,
cgoExportHPath)
cgoExportHPath,
coverFormat)
}

func compileArchive(
Expand All @@ -169,7 +169,8 @@ func compileArchive(
packageListPath string,
outPath string,
outXPath string,
cgoExportHPath string) error {
cgoExportHPath string,
coverFormat string) error {

workDir, cleanup, err := goenv.workDir()
if err != nil {
Expand Down Expand Up @@ -232,23 +233,33 @@ func compileArchive(

// Instrument source files for coverage.
if coverMode != "" {
shouldCover := make(map[string]bool)
relCoverPath := make(map[string]string)
for _, s := range coverSrcs {
shouldCover[s] = true
relCoverPath[abs(s)] = s
}

combined := append([]string{}, goSrcs...)
if cgoEnabled {
combined = append(combined, cgoSrcs...)
}
for i, origSrc := range combined {
if !shouldCover[origSrc] {
if _, ok := relCoverPath[origSrc]; !ok {
continue
}

srcName := origSrc
if importPath != "" {
srcName = path.Join(importPath, filepath.Base(origSrc))
var srcName string
switch coverFormat {
case "go_cover":
srcName = origSrc
if importPath != "" {
srcName = path.Join(importPath, filepath.Base(origSrc))
}
case "lcov":
// Bazel merges lcov reports across languages and thus assumes
// that the source file paths are relative to the exec root.
srcName = relCoverPath[origSrc]
default:
return fmt.Errorf("invalid value for -cover_format: %q", coverFormat)
}

stem := filepath.Base(origSrc)
Expand Down
32 changes: 26 additions & 6 deletions go/tools/builders/generate_test_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type Cases struct {
Examples []Example
TestMain string
CoverMode string
CoverFormat string
Pkgname string
}

Expand Down Expand Up @@ -171,10 +172,16 @@ func main() {
}
}
testDeps :=
{{if eq .CoverFormat "lcov"}}
bzltestutil.LcovTestDeps{TestDeps: testdeps.TestDeps{}}
{{else}}
testdeps.TestDeps{}
{{end}}
{{if .Version "go1.18"}}
m := testing.MainStart(testdeps.TestDeps{}, testsInShard(), benchmarks, fuzzTargets, examples)
m := testing.MainStart(testDeps, testsInShard(), benchmarks, fuzzTargets, examples)
{{else}}
m := testing.MainStart(testdeps.TestDeps{}, testsInShard(), benchmarks, examples)
m := testing.MainStart(testDeps, testsInShard(), benchmarks, examples)
{{end}}
if filter := os.Getenv("TESTBRIDGE_TEST_ONLY"); filter != "" {
Expand All @@ -185,6 +192,12 @@ func main() {
flag.Lookup("test.failfast").Value.Set("true")
}
// Setting this flag serves two purposes:
// 1. It attains parity with "go test", which enables this feature by default.
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.1:src/cmd/go/internal/test/test.go;l=1331-1337
// 2. It provides a way to run hooks right before testing.M.Run() returns.
flag.Lookup("test.paniconexit0").Value.Set("true")
{{if ne .CoverMode ""}}
if len(coverdata.Counters) > 0 {
testing.RegisterCover(testing.Cover{
Expand All @@ -194,18 +207,23 @@ func main() {
})
if coverageDat, ok := os.LookupEnv("COVERAGE_OUTPUT_FILE"); ok {
{{if eq .CoverFormat "lcov"}}
flag.Lookup("test.coverprofile").Value.Set(coverageDat+".cover")
{{else}}
flag.Lookup("test.coverprofile").Value.Set(coverageDat)
{{end}}
}
}
{{end}}
{{if not .TestMain}}
os.Exit(m.Run())
res := m.Run()
{{else}}
{{.TestMain}}(m)
{{/* See golang.org/issue/34129 and golang.org/cl/219639 */}}
os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
res := int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int())
{{end}}
os.Exit(res)
}
`

Expand All @@ -221,6 +239,7 @@ func genTestMain(args []string) error {
goenv := envFlags(flags)
out := flags.String("output", "", "output file to write. Defaults to stdout.")
coverMode := flags.String("cover_mode", "", "the coverage mode to use")
coverFormat := flags.String("cover_format", "", "the coverage report type to generate (go_cover or lcov)")
pkgname := flags.String("pkgname", "", "package name of test")
flags.Var(&imports, "import", "Packages to import")
flags.Var(&sources, "src", "Sources to process for tests")
Expand Down Expand Up @@ -270,8 +289,9 @@ func genTestMain(args []string) error {
}

cases := Cases{
CoverMode: *coverMode,
Pkgname: *pkgname,
CoverFormat: *coverFormat,
CoverMode: *coverMode,
Pkgname: *pkgname,
}

testFileSet := token.NewFileSet()
Expand Down
2 changes: 2 additions & 0 deletions go/tools/bzltestutil/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go_tool_library(
name = "bzltestutil",
srcs = [
"init.go",
"lcov.go",
"test2json.go",
"wrap.go",
"xml.go",
Expand All @@ -15,6 +16,7 @@ go_tool_library(
go_test(
name = "bzltestutil_test",
srcs = [
"lcov_test.go",
"wrap_test.go",
"xml_test.go",
],
Expand Down
Loading

0 comments on commit c142604

Please sign in to comment.