| 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 |
| } |