// Copyright 2021 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package java import ( "fmt" "reflect" "regexp" "strings" "testing" "android/soong/android" "github.com/google/blueprint/proptools" ) func TestDroidstubs(t *testing.T) { ctx, _ := testJavaWithFS(t, ` droiddoc_exported_dir { name: "droiddoc-templates-sdk", path: ".", } droidstubs { name: "bar-stubs", srcs: ["bar-doc/a.java"], api_levels_annotations_dirs: ["droiddoc-templates-sdk"], api_levels_annotations_enabled: true, } droidstubs { name: "bar-stubs-other", srcs: ["bar-doc/a.java"], high_mem: true, api_levels_annotations_dirs: ["droiddoc-templates-sdk"], api_levels_annotations_enabled: true, api_levels_jar_filename: "android.other.jar", } droidstubs { name: "stubs-applying-api-versions", srcs: ["bar-doc/a.java"], api_levels_module: "bar-stubs-other", } `, map[string][]byte{ "bar-doc/a.java": nil, }) testcases := []struct { moduleName string expectedJarFilename string generate_xml bool high_mem bool }{ { moduleName: "bar-stubs", generate_xml: true, expectedJarFilename: "android.jar", high_mem: false, }, { moduleName: "bar-stubs-other", generate_xml: true, expectedJarFilename: "android.other.jar", high_mem: true, }, { moduleName: "stubs-applying-api-versions", generate_xml: false, }, } for _, c := range testcases { m := ctx.ModuleForTests(c.moduleName, "android_common") manifest := m.Output("metalava.sbox.textproto") sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, manifest) cmdline := String(sboxProto.Commands[0].Command) android.AssertStringContainsEquals(t, "api-versions generation flag", cmdline, "--generate-api-levels", c.generate_xml) if c.expectedJarFilename != "" { expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename if !strings.Contains(cmdline, expected) { t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, cmdline) } } metalava := m.Rule("metalava") rp := metalava.RuleParams if actual := rp.Pool != nil && strings.Contains(rp.Pool.String(), "highmem"); actual != c.high_mem { t.Errorf("Expected %q high_mem to be %v, was %v", c.moduleName, c.high_mem, actual) } } } // runs a test for droidstubs with a customizable sdkType argument and returns // the list of jar patterns that is passed as `--android-jar-pattern` func getAndroidJarPatternsForDroidstubs(t *testing.T, sdkType string) []string { ctx, _ := testJavaWithFS(t, fmt.Sprintf(` droiddoc_exported_dir { name: "some-exported-dir", path: "somedir", } droiddoc_exported_dir { name: "some-other-exported-dir", path: "someotherdir", } droidstubs { name: "foo-stubs", srcs: ["foo-doc/a.java"], api_levels_annotations_dirs: [ "some-exported-dir", "some-other-exported-dir", ], api_levels_annotations_enabled: true, api_levels_sdk_type: "%s", } `, sdkType), map[string][]byte{ "foo-doc/a.java": nil, }) m := ctx.ModuleForTests("foo-stubs", "android_common") manifest := m.Output("metalava.sbox.textproto") cmd := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command) r := regexp.MustCompile(`--android-jar-pattern [^ ]+/android.jar`) return r.FindAllString(cmd, -1) } func TestPublicDroidstubs(t *testing.T) { patterns := getAndroidJarPatternsForDroidstubs(t, "public") android.AssertArrayString(t, "order of patterns", []string{ "--android-jar-pattern somedir/%/public/android.jar", "--android-jar-pattern someotherdir/%/public/android.jar", }, patterns) } func TestSystemDroidstubs(t *testing.T) { patterns := getAndroidJarPatternsForDroidstubs(t, "system") android.AssertArrayString(t, "order of patterns", []string{ "--android-jar-pattern somedir/%/system/android.jar", "--android-jar-pattern someotherdir/%/system/android.jar", "--android-jar-pattern somedir/%/public/android.jar", "--android-jar-pattern someotherdir/%/public/android.jar", }, patterns) } func TestModuleLibDroidstubs(t *testing.T) { patterns := getAndroidJarPatternsForDroidstubs(t, "module-lib") android.AssertArrayString(t, "order of patterns", []string{ "--android-jar-pattern somedir/%/module-lib/android.jar", "--android-jar-pattern someotherdir/%/module-lib/android.jar", "--android-jar-pattern somedir/%/system/android.jar", "--android-jar-pattern someotherdir/%/system/android.jar", "--android-jar-pattern somedir/%/public/android.jar", "--android-jar-pattern someotherdir/%/public/android.jar", }, patterns) } func TestSystemServerDroidstubs(t *testing.T) { patterns := getAndroidJarPatternsForDroidstubs(t, "system-server") android.AssertArrayString(t, "order of patterns", []string{ "--android-jar-pattern somedir/%/system-server/android.jar", "--android-jar-pattern someotherdir/%/system-server/android.jar", "--android-jar-pattern somedir/%/module-lib/android.jar", "--android-jar-pattern someotherdir/%/module-lib/android.jar", "--android-jar-pattern somedir/%/system/android.jar", "--android-jar-pattern someotherdir/%/system/android.jar", "--android-jar-pattern somedir/%/public/android.jar", "--android-jar-pattern someotherdir/%/public/android.jar", }, patterns) } func TestDroidstubsSandbox(t *testing.T) { ctx, _ := testJavaWithFS(t, ` genrule { name: "foo", out: ["foo.txt"], cmd: "touch $(out)", } droidstubs { name: "bar-stubs", srcs: ["bar-doc/a.java"], args: "--reference $(location :foo)", arg_files: [":foo"], } `, map[string][]byte{ "bar-doc/a.java": nil, }) m := ctx.ModuleForTests("bar-stubs", "android_common") metalava := m.Rule("metalava") if g, w := metalava.Inputs.Strings(), []string{"bar-doc/a.java"}; !reflect.DeepEqual(w, g) { t.Errorf("Expected inputs %q, got %q", w, g) } manifest := android.RuleBuilderSboxProtoForTests(t, ctx, m.Output("metalava.sbox.textproto")) if g, w := manifest.Commands[0].GetCommand(), "reference __SBOX_SANDBOX_DIR__/out/soong/.intermediates/foo/gen/foo.txt"; !strings.Contains(g, w) { t.Errorf("Expected command to contain %q, got %q", w, g) } } func TestDroidstubsWithSystemModules(t *testing.T) { ctx, _ := testJava(t, ` droidstubs { name: "stubs-source-system-modules", srcs: [ "bar-doc/a.java", ], sdk_version: "none", system_modules: "source-system-modules", } java_library { name: "source-jar", srcs: [ "a.java", ], } java_system_modules { name: "source-system-modules", libs: ["source-jar"], } droidstubs { name: "stubs-prebuilt-system-modules", srcs: [ "bar-doc/a.java", ], sdk_version: "none", system_modules: "prebuilt-system-modules", } java_import { name: "prebuilt-jar", jars: ["a.jar"], } java_system_modules_import { name: "prebuilt-system-modules", libs: ["prebuilt-jar"], } `) checkSystemModulesUseByDroidstubs(t, ctx, "stubs-source-system-modules", "source-jar.jar") checkSystemModulesUseByDroidstubs(t, ctx, "stubs-prebuilt-system-modules", "prebuilt-jar.jar") } func checkSystemModulesUseByDroidstubs(t *testing.T, ctx *android.TestContext, moduleName string, systemJar string) { metalavaRule := ctx.ModuleForTests(moduleName, "android_common").Rule("metalava") var systemJars []string for _, i := range metalavaRule.Implicits { systemJars = append(systemJars, i.Base()) } if len(systemJars) < 1 || systemJars[0] != systemJar { t.Errorf("inputs of %q must be []string{%q}, but was %#v.", moduleName, systemJar, systemJars) } } func TestDroidstubsWithSdkExtensions(t *testing.T) { ctx, _ := testJavaWithFS(t, ` droiddoc_exported_dir { name: "sdk-dir", path: "sdk", } droidstubs { name: "baz-stubs", api_levels_annotations_dirs: ["sdk-dir"], api_levels_annotations_enabled: true, extensions_info_file: ":info-file", } filegroup { name: "info-file", srcs: ["sdk/extensions/info.txt"], } `, map[string][]byte{ "sdk/extensions/1/public/some-mainline-module-stubs.jar": nil, "sdk/extensions/info.txt": nil, }) m := ctx.ModuleForTests("baz-stubs", "android_common") manifest := m.Output("metalava.sbox.textproto") cmdline := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command) android.AssertStringDoesContain(t, "sdk-extensions-root present", cmdline, "--sdk-extensions-root sdk/extensions") android.AssertStringDoesContain(t, "sdk-extensions-info present", cmdline, "--sdk-extensions-info sdk/extensions/info.txt") } func TestDroidStubsApiContributionGeneration(t *testing.T) { ctx, _ := testJavaWithFS(t, ` droidstubs { name: "foo", srcs: ["A/a.java"], api_surface: "public", check_api: { current: { api_file: "A/current.txt", removed_api_file: "A/removed.txt", } } } `, map[string][]byte{ "A/a.java": nil, "A/current.txt": nil, "A/removed.txt": nil, }, ) ctx.ModuleForTests("foo.api.contribution", "") } func TestGeneratedApiContributionVisibilityTest(t *testing.T) { library_bp := ` java_api_library { name: "bar", api_surface: "public", api_contributions: ["foo.api.contribution"], stubs_type: "everything", } ` ctx, _ := testJavaWithFS(t, ` droidstubs { name: "foo", srcs: ["A/a.java"], api_surface: "public", check_api: { current: { api_file: "A/current.txt", removed_api_file: "A/removed.txt", } }, visibility: ["//a", "//b"], } `, map[string][]byte{ "a/a.java": nil, "a/current.txt": nil, "a/removed.txt": nil, "b/Android.bp": []byte(library_bp), }, ) ctx.ModuleForTests("bar", "android_common") } func TestAconfigDeclarations(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { }), android.FixtureMergeMockFs(map[string][]byte{ "a/A.java": nil, "a/current.txt": nil, "a/removed.txt": nil, }), ).RunTestWithBp(t, ` aconfig_declarations { name: "bar", package: "com.example.package", container: "com.android.foo", srcs: [ "bar.aconfig", ], } droidstubs { name: "foo", srcs: ["a/A.java"], api_surface: "public", check_api: { current: { api_file: "a/current.txt", removed_api_file: "a/removed.txt", } }, aconfig_declarations: [ "bar", ], } `) // Check that droidstubs depend on aconfig_declarations android.AssertBoolEquals(t, "foo expected to depend on bar", CheckModuleHasDependency(t, result.TestContext, "foo", "android_common", "bar"), true) m := result.ModuleForTests("foo", "android_common") android.AssertStringDoesContain(t, "foo generates revert annotations file", strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt") // revert-annotations.txt passed to exportable stubs generation metalava command manifest := m.Output("metalava_exportable.sbox.textproto") cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command) android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt") android.AssertStringDoesContain(t, "foo generates exportable stubs jar", strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar") } func TestReleaseExportRuntimeApis(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true", } variables.ExportRuntimeApis = proptools.BoolPtr(true) }), android.FixtureMergeMockFs(map[string][]byte{ "a/A.java": nil, "a/current.txt": nil, "a/removed.txt": nil, }), ).RunTestWithBp(t, ` aconfig_declarations { name: "bar", package: "com.example.package", container: "com.android.foo", srcs: [ "bar.aconfig", ], } droidstubs { name: "foo", srcs: ["a/A.java"], api_surface: "public", check_api: { current: { api_file: "a/current.txt", removed_api_file: "a/removed.txt", } }, aconfig_declarations: [ "bar", ], } `) m := result.ModuleForTests("foo", "android_common") rule := m.Output("released-flagged-apis-exportable.txt") exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'" android.AssertStringEquals(t, "Filter argument expected to contain READ_WRITE permissions", exposeWritableApisFilter, rule.Args["filter_args"]) }