Support cc code coverage for mixed build
Bug: 231322627
Test: Manual tests and unit tests
Change-Id: I786042af0d612192c54c3572f63a86a47174a242
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 4d9423a..4dae6f6 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -111,7 +111,7 @@
// Issues commands to Bazel to receive results for all cquery requests
// queued in the BazelContext.
- InvokeBazel() error
+ InvokeBazel(config Config) error
// Returns true if bazel is enabled for the given configuration.
BazelEnabled() bool
@@ -191,7 +191,7 @@
return result, nil
}
-func (m MockBazelContext) InvokeBazel() error {
+func (m MockBazelContext) InvokeBazel(config Config) error {
panic("unimplemented")
}
@@ -261,7 +261,7 @@
panic("unimplemented")
}
-func (n noopBazelContext) InvokeBazel() error {
+func (n noopBazelContext) InvokeBazel(config Config) error {
panic("unimplemented")
}
@@ -361,6 +361,7 @@
type mockBazelRunner struct {
bazelCommandResults map[bazelCommand]string
commands []bazelCommand
+ extraFlags []string
}
func (r *mockBazelRunner) issueBazelCommand(paths *bazelPaths,
@@ -368,6 +369,7 @@
command bazelCommand,
extraFlags ...string) (string, string, error) {
r.commands = append(r.commands, command)
+ r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " "))
if ret, ok := r.bazelCommandResults[command]; ok {
return ret, "", nil
}
@@ -676,7 +678,7 @@
// Issues commands to Bazel to receive results for all cquery requests
// queued in the BazelContext.
-func (context *bazelContext) InvokeBazel() error {
+func (context *bazelContext) InvokeBazel(config Config) error {
context.results = make(map[cqueryKey]string)
var cqueryOutput string
@@ -759,15 +761,31 @@
// Issue an aquery command to retrieve action information about the bazel build tree.
//
- // TODO(cparsons): Use --target_pattern_file to avoid command line limits.
var aqueryOutput string
+ var coverageFlags []string
+ if Bool(config.productVariables.ClangCoverage) {
+ coverageFlags = append(coverageFlags, "--collect_code_coverage")
+ if len(config.productVariables.NativeCoveragePaths) > 0 ||
+ len(config.productVariables.NativeCoverageExcludePaths) > 0 {
+ includePaths := JoinWithPrefixAndSeparator(config.productVariables.NativeCoveragePaths, "+", ",")
+ excludePaths := JoinWithPrefixAndSeparator(config.productVariables.NativeCoverageExcludePaths, "-", ",")
+ if len(includePaths) > 0 && len(excludePaths) > 0 {
+ includePaths += ","
+ }
+ coverageFlags = append(coverageFlags, fmt.Sprintf(`--instrumentation_filter=%s`,
+ includePaths+excludePaths))
+ }
+ }
+
+ extraFlags := append([]string{"--output=jsonproto"}, coverageFlags...)
+
aqueryOutput, _, err = context.issueBazelCommand(
context.paths,
bazel.AqueryBuildRootRunName,
bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)},
// Use jsonproto instead of proto; actual proto parsing would require a dependency on Bazel's
// proto sources, which would add a number of unnecessary dependencies.
- "--output=jsonproto")
+ extraFlags...)
if err != nil {
return err
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index cfdccd7..dd9a7ed 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -4,11 +4,14 @@
"os"
"path/filepath"
"reflect"
+ "strings"
"testing"
"android/soong/bazel/cquery"
)
+var testConfig = TestConfig("out", nil, "", nil)
+
func TestRequestResultsAfterInvokeBazel(t *testing.T) {
label := "//foo:bar"
cfg := configKey{"arm64_armv8-a", Android}
@@ -16,7 +19,7 @@
bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: `//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
})
bazelContext.QueueBazelRequest(label, cquery.GetOutputFiles, cfg)
- err := bazelContext.InvokeBazel()
+ err := bazelContext.InvokeBazel(testConfig)
if err != nil {
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
}
@@ -30,7 +33,7 @@
func TestInvokeBazelWritesBazelFiles(t *testing.T) {
bazelContext, baseDir := testBazelContext(t, map[bazelCommand]string{})
- err := bazelContext.InvokeBazel()
+ err := bazelContext.InvokeBazel(testConfig)
if err != nil {
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
}
@@ -86,7 +89,7 @@
}]
}`,
})
- err := bazelContext.InvokeBazel()
+ err := bazelContext.InvokeBazel(testConfig)
if err != nil {
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
}
@@ -97,6 +100,54 @@
}
}
+func TestCoverageFlagsAfterInvokeBazel(t *testing.T) {
+ testConfig.productVariables.ClangCoverage = boolPtr(true)
+
+ testConfig.productVariables.NativeCoveragePaths = []string{"foo1", "foo2"}
+ testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1", "bar2"}
+ verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1,+foo2,-bar1,-bar2`)
+
+ testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
+ testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
+ verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1,-bar1`)
+
+ testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
+ testConfig.productVariables.NativeCoverageExcludePaths = nil
+ verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1`)
+
+ testConfig.productVariables.NativeCoveragePaths = nil
+ testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
+ verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=-bar1`)
+
+ testConfig.productVariables.ClangCoverage = boolPtr(false)
+ actual := verifyExtraFlags(t, testConfig, ``)
+ if strings.Contains(actual, "--collect_code_coverage") ||
+ strings.Contains(actual, "--instrumentation_filter=") {
+ t.Errorf("Expected code coverage disabled, but got %#v", actual)
+ }
+}
+
+func verifyExtraFlags(t *testing.T, config Config, expected string) string {
+ bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
+
+ err := bazelContext.InvokeBazel(config)
+ if err != nil {
+ t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
+ }
+
+ flags := bazelContext.bazelRunner.(*mockBazelRunner).extraFlags
+ if expected := 3; len(flags) != expected {
+ t.Errorf("Expected %d extra flags got %#v", expected, flags)
+ }
+
+ actual := flags[1]
+ if !strings.Contains(actual, expected) {
+ t.Errorf("Expected %#v got %#v", expected, actual)
+ }
+
+ return actual
+}
+
func testBazelContext(t *testing.T, bazelCommandResults map[bazelCommand]string) (*bazelContext, string) {
t.Helper()
p := bazelPaths{
diff --git a/android/util.go b/android/util.go
index 47c4583..a0f7160 100644
--- a/android/util.go
+++ b/android/util.go
@@ -32,6 +32,12 @@
// JoinWithPrefix prepends the prefix to each string in the list and
// returns them joined together with " " as separator.
func JoinWithPrefix(strs []string, prefix string) string {
+ return JoinWithPrefixAndSeparator(strs, prefix, " ")
+}
+
+// JoinWithPrefixAndSeparator prepends the prefix to each string in the list and
+// returns them joined together with the given separator.
+func JoinWithPrefixAndSeparator(strs []string, prefix string, sep string) string {
if len(strs) == 0 {
return ""
}
@@ -40,7 +46,7 @@
buf.WriteString(prefix)
buf.WriteString(strs[0])
for i := 1; i < len(strs); i++ {
- buf.WriteString(" ")
+ buf.WriteString(sep)
buf.WriteString(prefix)
buf.WriteString(strs[i])
}