• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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