1package android 2 3import ( 4 "encoding/json" 5 "os" 6 "path/filepath" 7 "reflect" 8 "strings" 9 "testing" 10 11 "android/soong/bazel/cquery" 12 analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2" 13 14 "github.com/google/blueprint/metrics" 15 "google.golang.org/protobuf/proto" 16) 17 18var testConfig = TestConfig("out", nil, "", nil) 19 20type testInvokeBazelContext struct{} 21 22func (t *testInvokeBazelContext) GetEventHandler() *metrics.EventHandler { 23 return &metrics.EventHandler{} 24} 25 26func TestRequestResultsAfterInvokeBazel(t *testing.T) { 27 label_foo := "@//foo:foo" 28 label_bar := "@//foo:bar" 29 apexKey := ApexConfigKey{ 30 WithinApex: true, 31 ApexSdkVersion: "29", 32 } 33 cfg_foo := configKey{"arm64_armv8-a", Android, apexKey} 34 cfg_bar := configKey{arch: "arm64_armv8-a", osType: Android} 35 cmd_results := []string{ 36 `@//foo:foo|arm64_armv8-a|android|within_apex|29>>out/foo/foo.txt`, 37 `@//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`, 38 } 39 bazelContext, _ := testBazelContext(t, map[bazelCommand]string{ 40 bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: strings.Join(cmd_results, "\n"), 41 }) 42 43 bazelContext.QueueBazelRequest(label_foo, cquery.GetOutputFiles, cfg_foo) 44 bazelContext.QueueBazelRequest(label_bar, cquery.GetOutputFiles, cfg_bar) 45 err := bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{}) 46 if err != nil { 47 t.Fatalf("Did not expect error invoking Bazel, but got %s", err) 48 } 49 verifyCqueryResult(t, bazelContext, label_foo, cfg_foo, "out/foo/foo.txt") 50 verifyCqueryResult(t, bazelContext, label_bar, cfg_bar, "out/foo/bar.txt") 51} 52 53func verifyCqueryResult(t *testing.T, ctx *mixedBuildBazelContext, label string, cfg configKey, result string) { 54 g, err := ctx.GetOutputFiles(label, cfg) 55 if err != nil { 56 t.Errorf("Expected cquery results after running InvokeBazel(), but got err %v", err) 57 } else if w := []string{result}; !reflect.DeepEqual(w, g) { 58 t.Errorf("Expected output %s, got %s", w, g) 59 } 60} 61 62func TestInvokeBazelWritesBazelFiles(t *testing.T) { 63 bazelContext, baseDir := testBazelContext(t, map[bazelCommand]string{}) 64 err := bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{}) 65 if err != nil { 66 t.Fatalf("Did not expect error invoking Bazel, but got %s", err) 67 } 68 if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "main.bzl")); os.IsNotExist(err) { 69 t.Errorf("Expected main.bzl to exist, but it does not") 70 } else if err != nil { 71 t.Errorf("Unexpected error stating main.bzl %s", err) 72 } 73 74 if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "BUILD.bazel")); os.IsNotExist(err) { 75 t.Errorf("Expected BUILD.bazel to exist, but it does not") 76 } else if err != nil { 77 t.Errorf("Unexpected error stating BUILD.bazel %s", err) 78 } 79 80 if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "WORKSPACE.bazel")); os.IsNotExist(err) { 81 t.Errorf("Expected WORKSPACE.bazel to exist, but it does not") 82 } else if err != nil { 83 t.Errorf("Unexpected error stating WORKSPACE.bazel %s", err) 84 } 85} 86 87func TestInvokeBazelPopulatesBuildStatements(t *testing.T) { 88 type testCase struct { 89 input string 90 command string 91 } 92 93 var testCases = []testCase{ 94 {` 95{ 96 "artifacts": [ 97 { "id": 1, "path_fragment_id": 1 }, 98 { "id": 2, "path_fragment_id": 2 }], 99 "actions": [{ 100 "target_Id": 1, 101 "action_Key": "x", 102 "mnemonic": "x", 103 "arguments": ["touch", "foo"], 104 "input_dep_set_ids": [1], 105 "output_Ids": [1], 106 "primary_output_id": 1 107 }], 108 "dep_set_of_files": [ 109 { "id": 1, "direct_artifact_ids": [1, 2] }], 110 "path_fragments": [ 111 { "id": 1, "label": "one" }, 112 { "id": 2, "label": "two" }] 113}`, 114 "cd 'test/exec_root' && rm -rf 'one' && touch foo", 115 }, {` 116{ 117 "artifacts": [ 118 { "id": 1, "path_fragment_id": 10 }, 119 { "id": 2, "path_fragment_id": 20 }], 120 "actions": [{ 121 "target_Id": 100, 122 "action_Key": "x", 123 "mnemonic": "x", 124 "arguments": ["bogus", "command"], 125 "output_Ids": [1, 2], 126 "primary_output_id": 1 127 }], 128 "path_fragments": [ 129 { "id": 10, "label": "one", "parent_id": 30 }, 130 { "id": 20, "label": "one.d", "parent_id": 30 }, 131 { "id": 30, "label": "parent" }] 132}`, 133 `cd 'test/exec_root' && rm -rf 'parent/one' && bogus command && sed -i'' -E 's@(^|\s|")bazel-out/@\1test/bazel_out/@g' 'parent/one.d'`, 134 }, 135 } 136 137 for i, testCase := range testCases { 138 data, err := JsonToActionGraphContainer(testCase.input) 139 if err != nil { 140 t.Error(err) 141 } 142 bazelContext, _ := testBazelContext(t, map[bazelCommand]string{ 143 bazelCommand{command: "aquery", expression: "deps(@soong_injection//mixed_builds:buildroot)"}: string(data)}) 144 145 err = bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{}) 146 if err != nil { 147 t.Fatalf("testCase #%d: did not expect error invoking Bazel, but got %s", i+1, err) 148 } 149 150 got := bazelContext.BuildStatementsToRegister() 151 if want := 1; len(got) != want { 152 t.Fatalf("expected %d registered build statements, but got %#v", want, got) 153 } 154 155 cmd := RuleBuilderCommand{} 156 ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))} 157 createCommand(&cmd, got[0], "test/exec_root", "test/bazel_out", ctx) 158 if actual, expected := cmd.buf.String(), testCase.command; expected != actual { 159 t.Errorf("expected: [%s], actual: [%s]", expected, actual) 160 } 161 } 162} 163 164func TestCoverageFlagsAfterInvokeBazel(t *testing.T) { 165 testConfig.productVariables.ClangCoverage = boolPtr(true) 166 167 testConfig.productVariables.NativeCoveragePaths = []string{"foo1", "foo2"} 168 testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1", "bar2"} 169 verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1,+foo2,-bar1,-bar2`) 170 171 testConfig.productVariables.NativeCoveragePaths = []string{"foo1"} 172 testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"} 173 verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1,-bar1`) 174 175 testConfig.productVariables.NativeCoveragePaths = []string{"foo1"} 176 testConfig.productVariables.NativeCoverageExcludePaths = nil 177 verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1`) 178 179 testConfig.productVariables.NativeCoveragePaths = nil 180 testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"} 181 verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=-bar1`) 182 183 testConfig.productVariables.NativeCoveragePaths = []string{"*"} 184 testConfig.productVariables.NativeCoverageExcludePaths = nil 185 verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+.*`) 186 187 testConfig.productVariables.ClangCoverage = boolPtr(false) 188 actual := verifyExtraFlags(t, testConfig, ``) 189 if strings.Contains(actual, "--collect_code_coverage") || 190 strings.Contains(actual, "--instrumentation_filter=") { 191 t.Errorf("Expected code coverage disabled, but got %#v", actual) 192 } 193} 194 195func TestBazelRequestsSorted(t *testing.T) { 196 bazelContext, _ := testBazelContext(t, map[bazelCommand]string{}) 197 198 cfgKeyArm64Android := configKey{arch: "arm64_armv8-a", osType: Android} 199 cfgKeyArm64Linux := configKey{arch: "arm64_armv8-a", osType: Linux} 200 cfgKeyOtherAndroid := configKey{arch: "otherarch", osType: Android} 201 202 bazelContext.QueueBazelRequest("zzz", cquery.GetOutputFiles, cfgKeyArm64Android) 203 bazelContext.QueueBazelRequest("ccc", cquery.GetApexInfo, cfgKeyArm64Android) 204 bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, cfgKeyArm64Android) 205 bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, cfgKeyArm64Android) 206 bazelContext.QueueBazelRequest("xxx", cquery.GetOutputFiles, cfgKeyArm64Linux) 207 bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, cfgKeyArm64Android) 208 bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, cfgKeyOtherAndroid) 209 bazelContext.QueueBazelRequest("bbb", cquery.GetOutputFiles, cfgKeyOtherAndroid) 210 211 if len(bazelContext.requests) != 7 { 212 t.Error("Expected 7 request elements, but got", len(bazelContext.requests)) 213 } 214 215 lastString := "" 216 for _, val := range bazelContext.requests { 217 thisString := val.String() 218 if thisString <= lastString { 219 t.Errorf("Requests are not ordered correctly. '%s' came before '%s'", lastString, thisString) 220 } 221 lastString = thisString 222 } 223} 224 225func TestIsModuleNameAllowed(t *testing.T) { 226 libDisabled := "lib_disabled" 227 libEnabled := "lib_enabled" 228 libDclaWithinApex := "lib_dcla_within_apex" 229 libDclaNonApex := "lib_dcla_non_apex" 230 libNotConverted := "lib_not_converted" 231 232 disabledModules := map[string]bool{ 233 libDisabled: true, 234 } 235 enabledModules := map[string]bool{ 236 libEnabled: true, 237 } 238 dclaEnabledModules := map[string]bool{ 239 libDclaWithinApex: true, 240 libDclaNonApex: true, 241 } 242 243 bazelContext := &mixedBuildBazelContext{ 244 modulesDefaultToBazel: false, 245 bazelEnabledModules: enabledModules, 246 bazelDisabledModules: disabledModules, 247 bazelDclaEnabledModules: dclaEnabledModules, 248 } 249 250 if bazelContext.IsModuleNameAllowed(libDisabled, true) { 251 t.Fatalf("%s shouldn't be allowed for mixed build", libDisabled) 252 } 253 254 if !bazelContext.IsModuleNameAllowed(libEnabled, true) { 255 t.Fatalf("%s should be allowed for mixed build", libEnabled) 256 } 257 258 if !bazelContext.IsModuleNameAllowed(libDclaWithinApex, true) { 259 t.Fatalf("%s should be allowed for mixed build", libDclaWithinApex) 260 } 261 262 if bazelContext.IsModuleNameAllowed(libDclaNonApex, false) { 263 t.Fatalf("%s shouldn't be allowed for mixed build", libDclaNonApex) 264 } 265 266 if bazelContext.IsModuleNameAllowed(libNotConverted, true) { 267 t.Fatalf("%s shouldn't be allowed for mixed build", libNotConverted) 268 } 269} 270 271func verifyExtraFlags(t *testing.T, config Config, expected string) string { 272 bazelContext, _ := testBazelContext(t, map[bazelCommand]string{}) 273 274 err := bazelContext.InvokeBazel(config, &testInvokeBazelContext{}) 275 if err != nil { 276 t.Fatalf("Did not expect error invoking Bazel, but got %s", err) 277 } 278 279 flags := bazelContext.bazelRunner.(*mockBazelRunner).extraFlags 280 if expected := 3; len(flags) != expected { 281 t.Errorf("Expected %d extra flags got %#v", expected, flags) 282 } 283 284 actual := flags[1] 285 if !strings.Contains(actual, expected) { 286 t.Errorf("Expected %#v got %#v", expected, actual) 287 } 288 289 return actual 290} 291 292func testBazelContext(t *testing.T, bazelCommandResults map[bazelCommand]string) (*mixedBuildBazelContext, string) { 293 t.Helper() 294 p := bazelPaths{ 295 soongOutDir: t.TempDir(), 296 outputBase: "outputbase", 297 workspaceDir: "workspace_dir", 298 } 299 aqueryCommand := bazelCommand{command: "aquery", expression: "deps(@soong_injection//mixed_builds:buildroot)"} 300 if _, exists := bazelCommandResults[aqueryCommand]; !exists { 301 bazelCommandResults[aqueryCommand] = "" 302 } 303 runner := &mockBazelRunner{bazelCommandResults: bazelCommandResults} 304 return &mixedBuildBazelContext{ 305 bazelRunner: runner, 306 paths: &p, 307 }, p.soongOutDir 308} 309 310// Transform the json format to ActionGraphContainer 311func JsonToActionGraphContainer(inputString string) ([]byte, error) { 312 var aqueryProtoResult analysis_v2_proto.ActionGraphContainer 313 err := json.Unmarshal([]byte(inputString), &aqueryProtoResult) 314 if err != nil { 315 return []byte(""), err 316 } 317 data, _ := proto.Marshal(&aqueryProtoResult) 318 return data, err 319} 320