1// Copyright 2021 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package java
16
17import (
18	"fmt"
19	"reflect"
20	"regexp"
21	"strings"
22	"testing"
23
24	"android/soong/android"
25
26	"github.com/google/blueprint/proptools"
27)
28
29func TestDroidstubs(t *testing.T) {
30	ctx, _ := testJavaWithFS(t, `
31		droiddoc_exported_dir {
32			name: "droiddoc-templates-sdk",
33			path: ".",
34		}
35
36		droidstubs {
37			name: "bar-stubs",
38			srcs: ["bar-doc/a.java"],
39			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
40			api_levels_annotations_enabled: true,
41		}
42
43		droidstubs {
44			name: "bar-stubs-other",
45			srcs: ["bar-doc/a.java"],
46			high_mem: true,
47			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
48			api_levels_annotations_enabled: true,
49			api_levels_jar_filename: "android.other.jar",
50		}
51
52		droidstubs {
53			name: "stubs-applying-api-versions",
54			srcs: ["bar-doc/a.java"],
55			api_levels_module: "bar-stubs-other",
56		}
57		`,
58		map[string][]byte{
59			"bar-doc/a.java": nil,
60		})
61	testcases := []struct {
62		moduleName          string
63		expectedJarFilename string
64		generate_xml        bool
65		high_mem            bool
66	}{
67		{
68			moduleName:          "bar-stubs",
69			generate_xml:        true,
70			expectedJarFilename: "android.jar",
71			high_mem:            false,
72		},
73		{
74			moduleName:          "bar-stubs-other",
75			generate_xml:        true,
76			expectedJarFilename: "android.other.jar",
77			high_mem:            true,
78		},
79		{
80			moduleName:   "stubs-applying-api-versions",
81			generate_xml: false,
82		},
83	}
84	for _, c := range testcases {
85		m := ctx.ModuleForTests(c.moduleName, "android_common")
86		manifest := m.Output("metalava.sbox.textproto")
87		sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, manifest)
88		cmdline := String(sboxProto.Commands[0].Command)
89		android.AssertStringContainsEquals(t, "api-versions generation flag", cmdline, "--generate-api-levels", c.generate_xml)
90		if c.expectedJarFilename != "" {
91			expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
92			if !strings.Contains(cmdline, expected) {
93				t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, cmdline)
94			}
95		}
96
97		metalava := m.Rule("metalava")
98		rp := metalava.RuleParams
99		if actual := rp.Pool != nil && strings.Contains(rp.Pool.String(), "highmem"); actual != c.high_mem {
100			t.Errorf("Expected %q high_mem to be %v, was %v", c.moduleName, c.high_mem, actual)
101		}
102	}
103}
104
105// runs a test for droidstubs with a customizable sdkType argument and returns
106// the list of jar patterns that is passed as `--android-jar-pattern`
107func getAndroidJarPatternsForDroidstubs(t *testing.T, sdkType string) []string {
108	ctx, _ := testJavaWithFS(t, fmt.Sprintf(`
109		droiddoc_exported_dir {
110			name: "some-exported-dir",
111			path: "somedir",
112		}
113
114		droiddoc_exported_dir {
115			name: "some-other-exported-dir",
116			path: "someotherdir",
117		}
118
119		droidstubs {
120			name: "foo-stubs",
121			srcs: ["foo-doc/a.java"],
122			api_levels_annotations_dirs: [
123				"some-exported-dir",
124				"some-other-exported-dir",
125			],
126			api_levels_annotations_enabled: true,
127			api_levels_sdk_type: "%s",
128		}
129		`, sdkType),
130		map[string][]byte{
131			"foo-doc/a.java": nil,
132		})
133
134	m := ctx.ModuleForTests("foo-stubs", "android_common")
135	manifest := m.Output("metalava.sbox.textproto")
136	cmd := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command)
137	r := regexp.MustCompile(`--android-jar-pattern [^ ]+/android.jar`)
138	return r.FindAllString(cmd, -1)
139}
140
141func TestPublicDroidstubs(t *testing.T) {
142	patterns := getAndroidJarPatternsForDroidstubs(t, "public")
143
144	android.AssertArrayString(t, "order of patterns", []string{
145		"--android-jar-pattern somedir/%/public/android.jar",
146		"--android-jar-pattern someotherdir/%/public/android.jar",
147	}, patterns)
148}
149
150func TestSystemDroidstubs(t *testing.T) {
151	patterns := getAndroidJarPatternsForDroidstubs(t, "system")
152
153	android.AssertArrayString(t, "order of patterns", []string{
154		"--android-jar-pattern somedir/%/system/android.jar",
155		"--android-jar-pattern someotherdir/%/system/android.jar",
156		"--android-jar-pattern somedir/%/public/android.jar",
157		"--android-jar-pattern someotherdir/%/public/android.jar",
158	}, patterns)
159}
160
161func TestModuleLibDroidstubs(t *testing.T) {
162	patterns := getAndroidJarPatternsForDroidstubs(t, "module-lib")
163
164	android.AssertArrayString(t, "order of patterns", []string{
165		"--android-jar-pattern somedir/%/module-lib/android.jar",
166		"--android-jar-pattern someotherdir/%/module-lib/android.jar",
167		"--android-jar-pattern somedir/%/system/android.jar",
168		"--android-jar-pattern someotherdir/%/system/android.jar",
169		"--android-jar-pattern somedir/%/public/android.jar",
170		"--android-jar-pattern someotherdir/%/public/android.jar",
171	}, patterns)
172}
173
174func TestSystemServerDroidstubs(t *testing.T) {
175	patterns := getAndroidJarPatternsForDroidstubs(t, "system-server")
176
177	android.AssertArrayString(t, "order of patterns", []string{
178		"--android-jar-pattern somedir/%/system-server/android.jar",
179		"--android-jar-pattern someotherdir/%/system-server/android.jar",
180		"--android-jar-pattern somedir/%/module-lib/android.jar",
181		"--android-jar-pattern someotherdir/%/module-lib/android.jar",
182		"--android-jar-pattern somedir/%/system/android.jar",
183		"--android-jar-pattern someotherdir/%/system/android.jar",
184		"--android-jar-pattern somedir/%/public/android.jar",
185		"--android-jar-pattern someotherdir/%/public/android.jar",
186	}, patterns)
187}
188
189func TestDroidstubsSandbox(t *testing.T) {
190	ctx, _ := testJavaWithFS(t, `
191		genrule {
192			name: "foo",
193			out: ["foo.txt"],
194			cmd: "touch $(out)",
195		}
196
197		droidstubs {
198			name: "bar-stubs",
199			srcs: ["bar-doc/a.java"],
200
201			args: "--reference $(location :foo)",
202			arg_files: [":foo"],
203		}
204		`,
205		map[string][]byte{
206			"bar-doc/a.java": nil,
207		})
208
209	m := ctx.ModuleForTests("bar-stubs", "android_common")
210	metalava := m.Rule("metalava")
211	if g, w := metalava.Inputs.Strings(), []string{"bar-doc/a.java"}; !reflect.DeepEqual(w, g) {
212		t.Errorf("Expected inputs %q, got %q", w, g)
213	}
214
215	manifest := android.RuleBuilderSboxProtoForTests(t, ctx, m.Output("metalava.sbox.textproto"))
216	if g, w := manifest.Commands[0].GetCommand(), "reference __SBOX_SANDBOX_DIR__/out/soong/.intermediates/foo/gen/foo.txt"; !strings.Contains(g, w) {
217		t.Errorf("Expected command to contain %q, got %q", w, g)
218	}
219}
220
221func TestDroidstubsWithSystemModules(t *testing.T) {
222	ctx, _ := testJava(t, `
223		droidstubs {
224		    name: "stubs-source-system-modules",
225		    srcs: [
226		        "bar-doc/a.java",
227		    ],
228				sdk_version: "none",
229				system_modules: "source-system-modules",
230		}
231
232		java_library {
233				name: "source-jar",
234		    srcs: [
235		        "a.java",
236		    ],
237		}
238
239		java_system_modules {
240				name: "source-system-modules",
241				libs: ["source-jar"],
242		}
243
244		droidstubs {
245		    name: "stubs-prebuilt-system-modules",
246		    srcs: [
247		        "bar-doc/a.java",
248		    ],
249				sdk_version: "none",
250				system_modules: "prebuilt-system-modules",
251		}
252
253		java_import {
254				name: "prebuilt-jar",
255				jars: ["a.jar"],
256		}
257
258		java_system_modules_import {
259				name: "prebuilt-system-modules",
260				libs: ["prebuilt-jar"],
261		}
262		`)
263
264	checkSystemModulesUseByDroidstubs(t, ctx, "stubs-source-system-modules", "source-jar.jar")
265
266	checkSystemModulesUseByDroidstubs(t, ctx, "stubs-prebuilt-system-modules", "prebuilt-jar.jar")
267}
268
269func checkSystemModulesUseByDroidstubs(t *testing.T, ctx *android.TestContext, moduleName string, systemJar string) {
270	metalavaRule := ctx.ModuleForTests(moduleName, "android_common").Rule("metalava")
271	var systemJars []string
272	for _, i := range metalavaRule.Implicits {
273		systemJars = append(systemJars, i.Base())
274	}
275	if len(systemJars) < 1 || systemJars[0] != systemJar {
276		t.Errorf("inputs of %q must be []string{%q}, but was %#v.", moduleName, systemJar, systemJars)
277	}
278}
279
280func TestDroidstubsWithSdkExtensions(t *testing.T) {
281	ctx, _ := testJavaWithFS(t, `
282		droiddoc_exported_dir {
283			name: "sdk-dir",
284			path: "sdk",
285		}
286
287		droidstubs {
288			name: "baz-stubs",
289			api_levels_annotations_dirs: ["sdk-dir"],
290			api_levels_annotations_enabled: true,
291			extensions_info_file: ":info-file",
292		}
293
294		filegroup {
295			name: "info-file",
296			srcs: ["sdk/extensions/info.txt"],
297		}
298		`,
299		map[string][]byte{
300			"sdk/extensions/1/public/some-mainline-module-stubs.jar": nil,
301			"sdk/extensions/info.txt":                                nil,
302		})
303	m := ctx.ModuleForTests("baz-stubs", "android_common")
304	manifest := m.Output("metalava.sbox.textproto")
305	cmdline := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command)
306	android.AssertStringDoesContain(t, "sdk-extensions-root present", cmdline, "--sdk-extensions-root sdk/extensions")
307	android.AssertStringDoesContain(t, "sdk-extensions-info present", cmdline, "--sdk-extensions-info sdk/extensions/info.txt")
308}
309
310func TestDroidStubsApiContributionGeneration(t *testing.T) {
311	ctx, _ := testJavaWithFS(t, `
312		droidstubs {
313			name: "foo",
314			srcs: ["A/a.java"],
315			api_surface: "public",
316			check_api: {
317				current: {
318					api_file: "A/current.txt",
319					removed_api_file: "A/removed.txt",
320				}
321			}
322		}
323		`,
324		map[string][]byte{
325			"A/a.java":      nil,
326			"A/current.txt": nil,
327			"A/removed.txt": nil,
328		},
329	)
330
331	ctx.ModuleForTests("foo.api.contribution", "")
332}
333
334func TestGeneratedApiContributionVisibilityTest(t *testing.T) {
335	library_bp := `
336		java_api_library {
337			name: "bar",
338			api_surface: "public",
339			api_contributions: ["foo.api.contribution"],
340			stubs_type: "everything",
341		}
342	`
343	ctx, _ := testJavaWithFS(t, `
344			droidstubs {
345				name: "foo",
346				srcs: ["A/a.java"],
347				api_surface: "public",
348				check_api: {
349					current: {
350						api_file: "A/current.txt",
351						removed_api_file: "A/removed.txt",
352					}
353				},
354				visibility: ["//a", "//b"],
355			}
356		`,
357		map[string][]byte{
358			"a/a.java":      nil,
359			"a/current.txt": nil,
360			"a/removed.txt": nil,
361			"b/Android.bp":  []byte(library_bp),
362		},
363	)
364
365	ctx.ModuleForTests("bar", "android_common")
366}
367
368func TestAconfigDeclarations(t *testing.T) {
369	result := android.GroupFixturePreparers(
370		prepareForJavaTest,
371		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
372		}),
373		android.FixtureMergeMockFs(map[string][]byte{
374			"a/A.java":      nil,
375			"a/current.txt": nil,
376			"a/removed.txt": nil,
377		}),
378	).RunTestWithBp(t, `
379	aconfig_declarations {
380		name: "bar",
381		package: "com.example.package",
382		container: "com.android.foo",
383		srcs: [
384			"bar.aconfig",
385		],
386	}
387	droidstubs {
388		name: "foo",
389		srcs: ["a/A.java"],
390		api_surface: "public",
391		check_api: {
392			current: {
393				api_file: "a/current.txt",
394				removed_api_file: "a/removed.txt",
395			}
396		},
397		aconfig_declarations: [
398			"bar",
399		],
400	}
401	`)
402
403	// Check that droidstubs depend on aconfig_declarations
404	android.AssertBoolEquals(t, "foo expected to depend on bar",
405		CheckModuleHasDependency(t, result.TestContext, "foo", "android_common", "bar"), true)
406
407	m := result.ModuleForTests("foo", "android_common")
408	android.AssertStringDoesContain(t, "foo generates revert annotations file",
409		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
410
411	// revert-annotations.txt passed to exportable stubs generation metalava command
412	manifest := m.Output("metalava_exportable.sbox.textproto")
413	cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
414	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
415
416	android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
417		strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
418}
419
420func TestReleaseExportRuntimeApis(t *testing.T) {
421	result := android.GroupFixturePreparers(
422		prepareForJavaTest,
423		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
424			variables.BuildFlags = map[string]string{
425				"RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true",
426			}
427			variables.ExportRuntimeApis = proptools.BoolPtr(true)
428		}),
429		android.FixtureMergeMockFs(map[string][]byte{
430			"a/A.java":      nil,
431			"a/current.txt": nil,
432			"a/removed.txt": nil,
433		}),
434	).RunTestWithBp(t, `
435	aconfig_declarations {
436		name: "bar",
437		package: "com.example.package",
438		container: "com.android.foo",
439		srcs: [
440			"bar.aconfig",
441		],
442	}
443	droidstubs {
444		name: "foo",
445		srcs: ["a/A.java"],
446		api_surface: "public",
447		check_api: {
448			current: {
449				api_file: "a/current.txt",
450				removed_api_file: "a/removed.txt",
451			}
452		},
453		aconfig_declarations: [
454			"bar",
455		],
456	}
457	`)
458
459	m := result.ModuleForTests("foo", "android_common")
460
461	rule := m.Output("released-flagged-apis-exportable.txt")
462	exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
463	android.AssertStringEquals(t, "Filter argument expected to contain READ_WRITE permissions", exposeWritableApisFilter, rule.Args["filter_args"])
464}
465