| 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", | |
| ApiDomain: "myapex", | |
| } | |
| 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|myapex>>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, map[string]bazel.AqueryDepset{}, "") | |
| if actual, expected := cmd.buf.String(), testCase.command; expected != actual { | |
| t.Errorf("expected: [%s], actual: [%s]", expected, actual) | |
| } | |
| } | |
| } | |
| func TestMixedBuildSandboxedAction(t *testing.T) { | |
| input := `{ | |
| "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" }] | |
| }` | |
| data, err := JsonToActionGraphContainer(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("TestMixedBuildSandboxedAction did not expect error invoking Bazel, but got %s", err) | |
| } | |
| statement := bazelContext.BuildStatementsToRegister()[0] | |
| statement.ShouldRunInSbox = true | |
| cmd := RuleBuilderCommand{} | |
| ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))} | |
| createCommand(&cmd, statement, "test/exec_root", "test/bazel_out", ctx, map[string]bazel.AqueryDepset{}, "") | |
| // Assert that the output is generated in an intermediate directory | |
| // fe05bcdcdc4928012781a5f1a2a77cbb5398e106 is the sha1 checksum of "one" | |
| if actual, expected := cmd.outputs[0].String(), "out/soong/mixed_build_sbox_intermediates/fe05bcdcdc4928012781a5f1a2a77cbb5398e106/test/exec_root/one"; expected != actual { | |
| t.Errorf("expected: [%s], actual: [%s]", expected, actual) | |
| } | |
| // Assert the actual command remains unchanged inside the sandbox | |
| if actual, expected := cmd.buf.String(), "mkdir -p 'test/exec_root' && cd 'test/exec_root' && rm -rf 'one' && touch foo"; 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{ | |
| 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 | |
| } |