c9089dcc7b
- Creation of a bazel command is independent of test/real implementation - Use a custom struct instead of cmd.Exec - Move mock bazel runner to the test Bug: 270989498 Test: m nothing Test: USE_PERSISTENT_BAZEL=0 m nothing Test: Treehugger Change-Id: Ieec35dad5e21aac644d5b8dc79a92397d42db861
377 lines
13 KiB
Go
377 lines
13 KiB
Go
package android
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"android/soong/bazel"
|
|
"android/soong/bazel/cquery"
|
|
analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
|
|
|
|
"github.com/google/blueprint/metrics"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
var testConfig = TestConfig("out", nil, "", nil)
|
|
|
|
type testInvokeBazelContext struct{}
|
|
|
|
type mockBazelRunner struct {
|
|
testHelper *testing.T
|
|
// Stores mock behavior. If an issueBazelCommand request is made for command
|
|
// k, and {k:v} is present in this map, then the mock will return v.
|
|
bazelCommandResults map[bazelCommand]string
|
|
// Requests actually made of the mockBazelRunner with issueBazelCommand,
|
|
// keyed by the command they represent.
|
|
bazelCommandRequests map[bazelCommand]bazel.CmdRequest
|
|
}
|
|
|
|
func (r *mockBazelRunner) bazelCommandForRequest(cmdRequest bazel.CmdRequest) bazelCommand {
|
|
for _, arg := range cmdRequest.Argv {
|
|
for _, cmdType := range allBazelCommands {
|
|
if arg == cmdType.command {
|
|
return cmdType
|
|
}
|
|
}
|
|
}
|
|
r.testHelper.Fatalf("Unrecognized bazel request: %s", cmdRequest)
|
|
return cqueryCmd
|
|
}
|
|
|
|
func (r *mockBazelRunner) issueBazelCommand(cmdRequest bazel.CmdRequest, paths *bazelPaths, eventHandler *metrics.EventHandler) (string, string, error) {
|
|
command := r.bazelCommandForRequest(cmdRequest)
|
|
r.bazelCommandRequests[command] = cmdRequest
|
|
return r.bazelCommandResults[command], "", nil
|
|
}
|
|
|
|
func (t *testInvokeBazelContext) GetEventHandler() *metrics.EventHandler {
|
|
return &metrics.EventHandler{}
|
|
}
|
|
|
|
func TestRequestResultsAfterInvokeBazel(t *testing.T) {
|
|
label_foo := "@//foo:foo"
|
|
label_bar := "@//foo:bar"
|
|
apexKey := ApexConfigKey{
|
|
WithinApex: true,
|
|
ApexSdkVersion: "29",
|
|
}
|
|
cfg_foo := configKey{"arm64_armv8-a", Android, apexKey}
|
|
cfg_bar := configKey{arch: "arm64_armv8-a", osType: Android}
|
|
cmd_results := []string{
|
|
`@//foo:foo|arm64_armv8-a|android|within_apex|29>>out/foo/foo.txt`,
|
|
`@//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
|
|
}
|
|
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{cqueryCmd: strings.Join(cmd_results, "\n")})
|
|
|
|
bazelContext.QueueBazelRequest(label_foo, cquery.GetOutputFiles, cfg_foo)
|
|
bazelContext.QueueBazelRequest(label_bar, cquery.GetOutputFiles, cfg_bar)
|
|
err := bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
|
|
if err != nil {
|
|
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
|
|
}
|
|
verifyCqueryResult(t, bazelContext, label_foo, cfg_foo, "out/foo/foo.txt")
|
|
verifyCqueryResult(t, bazelContext, label_bar, cfg_bar, "out/foo/bar.txt")
|
|
}
|
|
|
|
func verifyCqueryResult(t *testing.T, ctx *mixedBuildBazelContext, label string, cfg configKey, result string) {
|
|
g, err := ctx.GetOutputFiles(label, cfg)
|
|
if err != nil {
|
|
t.Errorf("Expected cquery results after running InvokeBazel(), but got err %v", err)
|
|
} else if w := []string{result}; !reflect.DeepEqual(w, g) {
|
|
t.Errorf("Expected output %s, got %s", w, g)
|
|
}
|
|
}
|
|
|
|
func TestInvokeBazelWritesBazelFiles(t *testing.T) {
|
|
bazelContext, baseDir := testBazelContext(t, map[bazelCommand]string{})
|
|
err := bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
|
|
if err != nil {
|
|
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "main.bzl")); os.IsNotExist(err) {
|
|
t.Errorf("Expected main.bzl to exist, but it does not")
|
|
} else if err != nil {
|
|
t.Errorf("Unexpected error stating main.bzl %s", err)
|
|
}
|
|
|
|
if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "BUILD.bazel")); os.IsNotExist(err) {
|
|
t.Errorf("Expected BUILD.bazel to exist, but it does not")
|
|
} else if err != nil {
|
|
t.Errorf("Unexpected error stating BUILD.bazel %s", err)
|
|
}
|
|
|
|
if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "WORKSPACE.bazel")); os.IsNotExist(err) {
|
|
t.Errorf("Expected WORKSPACE.bazel to exist, but it does not")
|
|
} else if err != nil {
|
|
t.Errorf("Unexpected error stating WORKSPACE.bazel %s", err)
|
|
}
|
|
}
|
|
|
|
func TestInvokeBazelPopulatesBuildStatements(t *testing.T) {
|
|
type testCase struct {
|
|
input string
|
|
command string
|
|
}
|
|
|
|
var testCases = []testCase{
|
|
{`
|
|
{
|
|
"artifacts": [
|
|
{ "id": 1, "path_fragment_id": 1 },
|
|
{ "id": 2, "path_fragment_id": 2 }],
|
|
"actions": [{
|
|
"target_Id": 1,
|
|
"action_Key": "x",
|
|
"mnemonic": "x",
|
|
"arguments": ["touch", "foo"],
|
|
"input_dep_set_ids": [1],
|
|
"output_Ids": [1],
|
|
"primary_output_id": 1
|
|
}],
|
|
"dep_set_of_files": [
|
|
{ "id": 1, "direct_artifact_ids": [1, 2] }],
|
|
"path_fragments": [
|
|
{ "id": 1, "label": "one" },
|
|
{ "id": 2, "label": "two" }]
|
|
}`,
|
|
"cd 'test/exec_root' && rm -rf 'one' && touch foo",
|
|
}, {`
|
|
{
|
|
"artifacts": [
|
|
{ "id": 1, "path_fragment_id": 10 },
|
|
{ "id": 2, "path_fragment_id": 20 }],
|
|
"actions": [{
|
|
"target_Id": 100,
|
|
"action_Key": "x",
|
|
"mnemonic": "x",
|
|
"arguments": ["bogus", "command"],
|
|
"output_Ids": [1, 2],
|
|
"primary_output_id": 1
|
|
}],
|
|
"path_fragments": [
|
|
{ "id": 10, "label": "one", "parent_id": 30 },
|
|
{ "id": 20, "label": "one.d", "parent_id": 30 },
|
|
{ "id": 30, "label": "parent" }]
|
|
}`,
|
|
`cd 'test/exec_root' && rm -rf 'parent/one' && bogus command && sed -i'' -E 's@(^|\s|")bazel-out/@\1test/bazel_out/@g' 'parent/one.d'`,
|
|
},
|
|
}
|
|
|
|
for i, testCase := range testCases {
|
|
data, err := JsonToActionGraphContainer(testCase.input)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{aqueryCmd: string(data)})
|
|
|
|
err = bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
|
|
if err != nil {
|
|
t.Fatalf("testCase #%d: did not expect error invoking Bazel, but got %s", i+1, err)
|
|
}
|
|
|
|
got := bazelContext.BuildStatementsToRegister()
|
|
if want := 1; len(got) != want {
|
|
t.Fatalf("expected %d registered build statements, but got %#v", want, got)
|
|
}
|
|
|
|
cmd := RuleBuilderCommand{}
|
|
ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))}
|
|
createCommand(&cmd, got[0], "test/exec_root", "test/bazel_out", ctx)
|
|
if actual, expected := cmd.buf.String(), testCase.command; expected != actual {
|
|
t.Errorf("expected: [%s], actual: [%s]", expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCoverageFlagsAfterInvokeBazel(t *testing.T) {
|
|
testConfig.productVariables.ClangCoverage = boolPtr(true)
|
|
|
|
testConfig.productVariables.NativeCoveragePaths = []string{"foo1", "foo2"}
|
|
testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1", "bar2"}
|
|
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1,+foo2,-bar1,-bar2")
|
|
|
|
testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
|
|
testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
|
|
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1,-bar1")
|
|
|
|
testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
|
|
testConfig.productVariables.NativeCoverageExcludePaths = nil
|
|
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1")
|
|
|
|
testConfig.productVariables.NativeCoveragePaths = nil
|
|
testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
|
|
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=-bar1")
|
|
|
|
testConfig.productVariables.NativeCoveragePaths = []string{"*"}
|
|
testConfig.productVariables.NativeCoverageExcludePaths = nil
|
|
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+.*")
|
|
|
|
testConfig.productVariables.ClangCoverage = boolPtr(false)
|
|
verifyAqueryDoesNotContainSubstrings(t, testConfig, "collect_code_coverage", "instrumentation_filter")
|
|
}
|
|
|
|
func TestBazelRequestsSorted(t *testing.T) {
|
|
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
|
|
|
|
cfgKeyArm64Android := configKey{arch: "arm64_armv8-a", osType: Android}
|
|
cfgKeyArm64Linux := configKey{arch: "arm64_armv8-a", osType: Linux}
|
|
cfgKeyOtherAndroid := configKey{arch: "otherarch", osType: Android}
|
|
|
|
bazelContext.QueueBazelRequest("zzz", cquery.GetOutputFiles, cfgKeyArm64Android)
|
|
bazelContext.QueueBazelRequest("ccc", cquery.GetApexInfo, cfgKeyArm64Android)
|
|
bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, cfgKeyArm64Android)
|
|
bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, cfgKeyArm64Android)
|
|
bazelContext.QueueBazelRequest("xxx", cquery.GetOutputFiles, cfgKeyArm64Linux)
|
|
bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, cfgKeyArm64Android)
|
|
bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, cfgKeyOtherAndroid)
|
|
bazelContext.QueueBazelRequest("bbb", cquery.GetOutputFiles, cfgKeyOtherAndroid)
|
|
|
|
if len(bazelContext.requests) != 7 {
|
|
t.Error("Expected 7 request elements, but got", len(bazelContext.requests))
|
|
}
|
|
|
|
lastString := ""
|
|
for _, val := range bazelContext.requests {
|
|
thisString := val.String()
|
|
if thisString <= lastString {
|
|
t.Errorf("Requests are not ordered correctly. '%s' came before '%s'", lastString, thisString)
|
|
}
|
|
lastString = thisString
|
|
}
|
|
}
|
|
|
|
func TestIsModuleNameAllowed(t *testing.T) {
|
|
libDisabled := "lib_disabled"
|
|
libEnabled := "lib_enabled"
|
|
libDclaWithinApex := "lib_dcla_within_apex"
|
|
libDclaNonApex := "lib_dcla_non_apex"
|
|
libNotConverted := "lib_not_converted"
|
|
|
|
disabledModules := map[string]bool{
|
|
libDisabled: true,
|
|
}
|
|
enabledModules := map[string]bool{
|
|
libEnabled: true,
|
|
}
|
|
dclaEnabledModules := map[string]bool{
|
|
libDclaWithinApex: true,
|
|
libDclaNonApex: true,
|
|
}
|
|
|
|
bazelContext := &mixedBuildBazelContext{
|
|
modulesDefaultToBazel: false,
|
|
bazelEnabledModules: enabledModules,
|
|
bazelDisabledModules: disabledModules,
|
|
bazelDclaEnabledModules: dclaEnabledModules,
|
|
}
|
|
|
|
if bazelContext.IsModuleNameAllowed(libDisabled, true) {
|
|
t.Fatalf("%s shouldn't be allowed for mixed build", libDisabled)
|
|
}
|
|
|
|
if !bazelContext.IsModuleNameAllowed(libEnabled, true) {
|
|
t.Fatalf("%s should be allowed for mixed build", libEnabled)
|
|
}
|
|
|
|
if !bazelContext.IsModuleNameAllowed(libDclaWithinApex, true) {
|
|
t.Fatalf("%s should be allowed for mixed build", libDclaWithinApex)
|
|
}
|
|
|
|
if bazelContext.IsModuleNameAllowed(libDclaNonApex, false) {
|
|
t.Fatalf("%s shouldn't be allowed for mixed build", libDclaNonApex)
|
|
}
|
|
|
|
if bazelContext.IsModuleNameAllowed(libNotConverted, true) {
|
|
t.Fatalf("%s shouldn't be allowed for mixed build", libNotConverted)
|
|
}
|
|
}
|
|
|
|
func verifyAqueryContainsFlags(t *testing.T, config Config, expected ...string) {
|
|
t.Helper()
|
|
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
|
|
|
|
err := bazelContext.InvokeBazel(config, &testInvokeBazelContext{})
|
|
if err != nil {
|
|
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
|
|
}
|
|
|
|
sliceContains := func(slice []string, x string) bool {
|
|
for _, s := range slice {
|
|
if s == x {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
aqueryArgv := bazelContext.bazelRunner.(*mockBazelRunner).bazelCommandRequests[aqueryCmd].Argv
|
|
|
|
for _, expectedFlag := range expected {
|
|
if !sliceContains(aqueryArgv, expectedFlag) {
|
|
t.Errorf("aquery does not contain expected flag %#v. Argv was: %#v", expectedFlag, aqueryArgv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyAqueryDoesNotContainSubstrings(t *testing.T, config Config, substrings ...string) {
|
|
t.Helper()
|
|
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
|
|
|
|
err := bazelContext.InvokeBazel(config, &testInvokeBazelContext{})
|
|
if err != nil {
|
|
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
|
|
}
|
|
|
|
sliceContainsSubstring := func(slice []string, substring string) bool {
|
|
for _, s := range slice {
|
|
if strings.Contains(s, substring) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
aqueryArgv := bazelContext.bazelRunner.(*mockBazelRunner).bazelCommandRequests[aqueryCmd].Argv
|
|
|
|
for _, substring := range substrings {
|
|
if sliceContainsSubstring(aqueryArgv, substring) {
|
|
t.Errorf("aquery contains unexpected substring %#v. Argv was: %#v", substring, aqueryArgv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testBazelContext(t *testing.T, bazelCommandResults map[bazelCommand]string) (*mixedBuildBazelContext, string) {
|
|
t.Helper()
|
|
p := bazelPaths{
|
|
soongOutDir: t.TempDir(),
|
|
outputBase: "outputbase",
|
|
workspaceDir: "workspace_dir",
|
|
}
|
|
if _, exists := bazelCommandResults[aqueryCmd]; !exists {
|
|
bazelCommandResults[aqueryCmd] = ""
|
|
}
|
|
runner := &mockBazelRunner{
|
|
testHelper: t,
|
|
bazelCommandResults: bazelCommandResults,
|
|
bazelCommandRequests: map[bazelCommand]bazel.CmdRequest{},
|
|
}
|
|
return &mixedBuildBazelContext{
|
|
bazelRunner: runner,
|
|
paths: &p,
|
|
}, p.soongOutDir
|
|
}
|
|
|
|
// Transform the json format to ActionGraphContainer
|
|
func JsonToActionGraphContainer(inputString string) ([]byte, error) {
|
|
var aqueryProtoResult analysis_v2_proto.ActionGraphContainer
|
|
err := json.Unmarshal([]byte(inputString), &aqueryProtoResult)
|
|
if err != nil {
|
|
return []byte(""), err
|
|
}
|
|
data, _ := proto.Marshal(&aqueryProtoResult)
|
|
return data, err
|
|
}
|