platform_build_soong/java/app_test.go
Sasha Smundak 8539023170 Implement android_app_set module
Bug: 152319766
Test: manual and builtin
Change-Id: Id0877476f9ae23311d92c0b59a9c568140ab4119
Merged-In: Id0877476f9ae23311d92c0b59a9c568140ab4119
2020-06-03 09:49:53 -07:00

1234 lines
33 KiB
Go

// Copyright 2017 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 (
"android/soong/android"
"android/soong/cc"
"fmt"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"github.com/google/blueprint/proptools"
)
var (
resourceFiles = []string{
"res/layout/layout.xml",
"res/values/strings.xml",
"res/values-en-rUS/strings.xml",
}
compiledResourceFiles = []string{
"aapt2/res/layout_layout.xml.flat",
"aapt2/res/values_strings.arsc.flat",
"aapt2/res/values-en-rUS_strings.arsc.flat",
}
)
func testAppContext(config android.Config, bp string, fs map[string][]byte) *android.TestContext {
appFS := map[string][]byte{}
for k, v := range fs {
appFS[k] = v
}
for _, file := range resourceFiles {
appFS[file] = nil
}
return testContext(config, bp, appFS)
}
func testApp(t *testing.T, bp string) *android.TestContext {
config := testConfig(nil)
ctx := testAppContext(config, bp, nil)
run(t, ctx, config)
return ctx
}
func TestApp(t *testing.T) {
for _, moduleType := range []string{"android_app", "android_library"} {
t.Run(moduleType, func(t *testing.T) {
ctx := testApp(t, moduleType+` {
name: "foo",
srcs: ["a.java"],
}
`)
foo := ctx.ModuleForTests("foo", "android_common")
var expectedLinkImplicits []string
manifestFixer := foo.Output("manifest_fixer/AndroidManifest.xml")
expectedLinkImplicits = append(expectedLinkImplicits, manifestFixer.Output.String())
frameworkRes := ctx.ModuleForTests("framework-res", "android_common")
expectedLinkImplicits = append(expectedLinkImplicits,
frameworkRes.Output("package-res.apk").Output.String())
// Test the mapping from input files to compiled output file names
compile := foo.Output(compiledResourceFiles[0])
if !reflect.DeepEqual(resourceFiles, compile.Inputs.Strings()) {
t.Errorf("expected aapt2 compile inputs expected:\n %#v\n got:\n %#v",
resourceFiles, compile.Inputs.Strings())
}
compiledResourceOutputs := compile.Outputs.Strings()
sort.Strings(compiledResourceOutputs)
expectedLinkImplicits = append(expectedLinkImplicits, compiledResourceOutputs...)
list := foo.Output("aapt2/res.list")
expectedLinkImplicits = append(expectedLinkImplicits, list.Output.String())
// Check that the link rule uses
res := ctx.ModuleForTests("foo", "android_common").Output("package-res.apk")
if !reflect.DeepEqual(expectedLinkImplicits, res.Implicits.Strings()) {
t.Errorf("expected aapt2 link implicits expected:\n %#v\n got:\n %#v",
expectedLinkImplicits, res.Implicits.Strings())
}
})
}
}
func TestAppSplits(t *testing.T) {
ctx := testApp(t, `
android_app {
name: "foo",
srcs: ["a.java"],
package_splits: ["v4", "v7,hdpi"],
}`)
foo := ctx.ModuleForTests("foo", "android_common")
expectedOutputs := []string{
filepath.Join(buildDir, ".intermediates/foo/android_common/foo.apk"),
filepath.Join(buildDir, ".intermediates/foo/android_common/foo_v4.apk"),
filepath.Join(buildDir, ".intermediates/foo/android_common/foo_v7_hdpi.apk"),
}
for _, expectedOutput := range expectedOutputs {
foo.Output(expectedOutput)
}
if g, w := foo.Module().(*AndroidApp).Srcs().Strings(), expectedOutputs; !reflect.DeepEqual(g, w) {
t.Errorf("want Srcs() = %q, got %q", w, g)
}
}
func TestAndroidAppSet(t *testing.T) {
config := testConfig(nil)
config.TestProductVariables.DeviceArch = proptools.StringPtr("arm64")
ctx := testAppContext(config, `
android_app_set {
name: "foo",
set: "prebuilts/apks/app.apks",
prerelease: true,
}`, nil)
run(t, ctx, config)
module := ctx.ModuleForTests("foo", "android_common")
const packedSplitApks = "extracted.zip"
params := module.Output(packedSplitApks)
if params.Rule == nil {
t.Errorf("expected output %s is missing", packedSplitApks)
}
if s := params.Args["allow-prereleased"]; s != "true" {
t.Errorf("wrong allow-prereleased value: '%s', expected 'true'", s)
}
}
func TestAndroidAppSet_Variants(t *testing.T) {
bp := `
android_app_set {
name: "foo",
set: "prebuilts/apks/app.apks",
}`
testCases := []struct {
name string
deviceArch *string
deviceSecondaryArch *string
aaptPrebuiltDPI []string
sdkVersion int
expected map[string]string
}{
{
name: "One",
deviceArch: proptools.StringPtr("x86"),
aaptPrebuiltDPI: []string{"ldpi", "xxhdpi"},
sdkVersion: 29,
expected: map[string]string{
"abis": "X86",
"allow-prereleased": "false",
"screen-densities": "LDPI,XXHDPI",
"sdk-version": "29",
"stem": "foo",
},
},
{
name: "Two",
deviceArch: proptools.StringPtr("x86_64"),
deviceSecondaryArch: proptools.StringPtr("x86"),
aaptPrebuiltDPI: nil,
sdkVersion: 30,
expected: map[string]string{
"abis": "X86_64,X86",
"allow-prereleased": "false",
"screen-densities": "all",
"sdk-version": "30",
"stem": "foo",
},
},
}
for _, test := range testCases {
config := testConfig(nil)
config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
config.TestProductVariables.Platform_sdk_version = &test.sdkVersion
config.TestProductVariables.DeviceArch = test.deviceArch
config.TestProductVariables.DeviceSecondaryArch = test.deviceSecondaryArch
ctx := testAppContext(config, bp, nil)
run(t, ctx, config)
module := ctx.ModuleForTests("foo", "android_common")
const packedSplitApks = "extracted.zip"
params := module.Output(packedSplitApks)
for k, v := range test.expected {
if actual := params.Args[k]; actual != v {
t.Errorf("%s: bad build arg value for '%s': '%s', expected '%s'",
test.name, k, actual, v)
}
}
}
}
func TestResourceDirs(t *testing.T) {
testCases := []struct {
name string
prop string
resources []string
}{
{
name: "no resource_dirs",
prop: "",
resources: []string{"res/res/values/strings.xml"},
},
{
name: "resource_dirs",
prop: `resource_dirs: ["res"]`,
resources: []string{"res/res/values/strings.xml"},
},
{
name: "empty resource_dirs",
prop: `resource_dirs: []`,
resources: nil,
},
}
fs := map[string][]byte{
"res/res/values/strings.xml": nil,
}
bp := `
android_app {
name: "foo",
%s
}
`
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
config := testConfig(nil)
ctx := testContext(config, fmt.Sprintf(bp, testCase.prop), fs)
run(t, ctx, config)
module := ctx.ModuleForTests("foo", "android_common")
resourceList := module.MaybeOutput("aapt2/res.list")
var resources []string
if resourceList.Rule != nil {
for _, compiledResource := range resourceList.Inputs.Strings() {
resources = append(resources, module.Output(compiledResource).Inputs.Strings()...)
}
}
if !reflect.DeepEqual(resources, testCase.resources) {
t.Errorf("expected resource files %q, got %q",
testCase.resources, resources)
}
})
}
}
func TestAndroidResources(t *testing.T) {
testCases := []struct {
name string
enforceRROTargets []string
enforceRROExcludedOverlays []string
resourceFiles map[string][]string
overlayFiles map[string][]string
rroDirs map[string][]string
}{
{
name: "no RRO",
enforceRROTargets: nil,
enforceRROExcludedOverlays: nil,
resourceFiles: map[string][]string{
"foo": nil,
"bar": {"bar/res/res/values/strings.xml"},
"lib": nil,
"lib2": {"lib2/res/res/values/strings.xml"},
},
overlayFiles: map[string][]string{
"foo": {
buildDir + "/.intermediates/lib2/android_common/package-res.apk",
buildDir + "/.intermediates/lib/android_common/package-res.apk",
buildDir + "/.intermediates/lib3/android_common/package-res.apk",
"foo/res/res/values/strings.xml",
"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
"device/vendor/blah/overlay/foo/res/values/strings.xml",
"product/vendor/blah/overlay/foo/res/values/strings.xml",
},
"bar": {
"device/vendor/blah/static_overlay/bar/res/values/strings.xml",
"device/vendor/blah/overlay/bar/res/values/strings.xml",
},
"lib": {
buildDir + "/.intermediates/lib2/android_common/package-res.apk",
"lib/res/res/values/strings.xml",
"device/vendor/blah/overlay/lib/res/values/strings.xml",
},
},
rroDirs: map[string][]string{
"foo": nil,
"bar": nil,
},
},
{
name: "enforce RRO on foo",
enforceRROTargets: []string{"foo"},
enforceRROExcludedOverlays: []string{"device/vendor/blah/static_overlay"},
resourceFiles: map[string][]string{
"foo": nil,
"bar": {"bar/res/res/values/strings.xml"},
"lib": nil,
"lib2": {"lib2/res/res/values/strings.xml"},
},
overlayFiles: map[string][]string{
"foo": {
buildDir + "/.intermediates/lib2/android_common/package-res.apk",
buildDir + "/.intermediates/lib/android_common/package-res.apk",
buildDir + "/.intermediates/lib3/android_common/package-res.apk",
"foo/res/res/values/strings.xml",
"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
},
"bar": {
"device/vendor/blah/static_overlay/bar/res/values/strings.xml",
"device/vendor/blah/overlay/bar/res/values/strings.xml",
},
"lib": {
buildDir + "/.intermediates/lib2/android_common/package-res.apk",
"lib/res/res/values/strings.xml",
"device/vendor/blah/overlay/lib/res/values/strings.xml",
},
},
rroDirs: map[string][]string{
"foo": {
"device:device/vendor/blah/overlay/foo/res",
// Enforce RRO on "foo" could imply RRO on static dependencies, but for now it doesn't.
// "device/vendor/blah/overlay/lib/res",
"product:product/vendor/blah/overlay/foo/res",
},
"bar": nil,
"lib": nil,
},
},
{
name: "enforce RRO on all",
enforceRROTargets: []string{"*"},
enforceRROExcludedOverlays: []string{
// Excluding specific apps/res directories also allowed.
"device/vendor/blah/static_overlay/foo",
"device/vendor/blah/static_overlay/bar/res",
},
resourceFiles: map[string][]string{
"foo": nil,
"bar": {"bar/res/res/values/strings.xml"},
"lib": nil,
"lib2": {"lib2/res/res/values/strings.xml"},
},
overlayFiles: map[string][]string{
"foo": {
buildDir + "/.intermediates/lib2/android_common/package-res.apk",
buildDir + "/.intermediates/lib/android_common/package-res.apk",
buildDir + "/.intermediates/lib3/android_common/package-res.apk",
"foo/res/res/values/strings.xml",
"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
},
"bar": {"device/vendor/blah/static_overlay/bar/res/values/strings.xml"},
"lib": {
buildDir + "/.intermediates/lib2/android_common/package-res.apk",
"lib/res/res/values/strings.xml",
},
},
rroDirs: map[string][]string{
"foo": {
"device:device/vendor/blah/overlay/foo/res",
"product:product/vendor/blah/overlay/foo/res",
// Lib dep comes after the direct deps
"device:device/vendor/blah/overlay/lib/res",
},
"bar": {"device:device/vendor/blah/overlay/bar/res"},
"lib": {"device:device/vendor/blah/overlay/lib/res"},
},
},
}
deviceResourceOverlays := []string{
"device/vendor/blah/overlay",
"device/vendor/blah/overlay2",
"device/vendor/blah/static_overlay",
}
productResourceOverlays := []string{
"product/vendor/blah/overlay",
}
fs := map[string][]byte{
"foo/res/res/values/strings.xml": nil,
"bar/res/res/values/strings.xml": nil,
"lib/res/res/values/strings.xml": nil,
"lib2/res/res/values/strings.xml": nil,
"device/vendor/blah/overlay/foo/res/values/strings.xml": nil,
"device/vendor/blah/overlay/bar/res/values/strings.xml": nil,
"device/vendor/blah/overlay/lib/res/values/strings.xml": nil,
"device/vendor/blah/static_overlay/foo/res/values/strings.xml": nil,
"device/vendor/blah/static_overlay/bar/res/values/strings.xml": nil,
"device/vendor/blah/overlay2/res/values/strings.xml": nil,
"product/vendor/blah/overlay/foo/res/values/strings.xml": nil,
}
bp := `
android_app {
name: "foo",
resource_dirs: ["foo/res"],
static_libs: ["lib", "lib3"],
}
android_app {
name: "bar",
resource_dirs: ["bar/res"],
}
android_library {
name: "lib",
resource_dirs: ["lib/res"],
static_libs: ["lib2"],
}
android_library {
name: "lib2",
resource_dirs: ["lib2/res"],
}
// This library has the same resources as lib (should not lead to dupe RROs)
android_library {
name: "lib3",
resource_dirs: ["lib/res"]
}
`
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
config := testConfig(nil)
config.TestProductVariables.DeviceResourceOverlays = deviceResourceOverlays
config.TestProductVariables.ProductResourceOverlays = productResourceOverlays
if testCase.enforceRROTargets != nil {
config.TestProductVariables.EnforceRROTargets = testCase.enforceRROTargets
}
if testCase.enforceRROExcludedOverlays != nil {
config.TestProductVariables.EnforceRROExcludedOverlays = testCase.enforceRROExcludedOverlays
}
ctx := testAppContext(config, bp, fs)
run(t, ctx, config)
resourceListToFiles := func(module android.TestingModule, list []string) (files []string) {
for _, o := range list {
res := module.MaybeOutput(o)
if res.Rule != nil {
// If the overlay is compiled as part of this module (i.e. a .arsc.flat file),
// verify the inputs to the .arsc.flat rule.
files = append(files, res.Inputs.Strings()...)
} else {
// Otherwise, verify the full path to the output of the other module
files = append(files, o)
}
}
return files
}
getResources := func(moduleName string) (resourceFiles, overlayFiles, rroDirs []string) {
module := ctx.ModuleForTests(moduleName, "android_common")
resourceList := module.MaybeOutput("aapt2/res.list")
if resourceList.Rule != nil {
resourceFiles = resourceListToFiles(module, resourceList.Inputs.Strings())
}
overlayList := module.MaybeOutput("aapt2/overlay.list")
if overlayList.Rule != nil {
overlayFiles = resourceListToFiles(module, overlayList.Inputs.Strings())
}
for _, d := range module.Module().(AndroidLibraryDependency).ExportedRRODirs() {
var prefix string
if d.overlayType == device {
prefix = "device:"
} else if d.overlayType == product {
prefix = "product:"
} else {
t.Fatalf("Unexpected overlayType %d", d.overlayType)
}
rroDirs = append(rroDirs, prefix+d.path.String())
}
return resourceFiles, overlayFiles, rroDirs
}
modules := []string{"foo", "bar", "lib", "lib2"}
for _, module := range modules {
resourceFiles, overlayFiles, rroDirs := getResources(module)
if !reflect.DeepEqual(resourceFiles, testCase.resourceFiles[module]) {
t.Errorf("expected %s resource files:\n %#v\n got:\n %#v",
module, testCase.resourceFiles[module], resourceFiles)
}
if !reflect.DeepEqual(overlayFiles, testCase.overlayFiles[module]) {
t.Errorf("expected %s overlay files:\n %#v\n got:\n %#v",
module, testCase.overlayFiles[module], overlayFiles)
}
if !reflect.DeepEqual(rroDirs, testCase.rroDirs[module]) {
t.Errorf("expected %s rroDirs: %#v\n got:\n %#v",
module, testCase.rroDirs[module], rroDirs)
}
}
})
}
}
func TestAppSdkVersion(t *testing.T) {
testCases := []struct {
name string
sdkVersion string
platformSdkInt int
platformSdkCodename string
platformSdkFinal bool
expectedMinSdkVersion string
}{
{
name: "current final SDK",
sdkVersion: "current",
platformSdkInt: 27,
platformSdkCodename: "REL",
platformSdkFinal: true,
expectedMinSdkVersion: "27",
},
{
name: "current non-final SDK",
sdkVersion: "current",
platformSdkInt: 27,
platformSdkCodename: "OMR1",
platformSdkFinal: false,
expectedMinSdkVersion: "OMR1",
},
{
name: "default final SDK",
sdkVersion: "",
platformSdkInt: 27,
platformSdkCodename: "REL",
platformSdkFinal: true,
expectedMinSdkVersion: "27",
},
{
name: "default non-final SDK",
sdkVersion: "",
platformSdkInt: 27,
platformSdkCodename: "OMR1",
platformSdkFinal: false,
expectedMinSdkVersion: "OMR1",
},
{
name: "14",
sdkVersion: "14",
expectedMinSdkVersion: "14",
},
}
for _, moduleType := range []string{"android_app", "android_library"} {
for _, test := range testCases {
t.Run(moduleType+" "+test.name, func(t *testing.T) {
bp := fmt.Sprintf(`%s {
name: "foo",
srcs: ["a.java"],
sdk_version: "%s",
}`, moduleType, test.sdkVersion)
config := testConfig(nil)
config.TestProductVariables.Platform_sdk_version = &test.platformSdkInt
config.TestProductVariables.Platform_sdk_codename = &test.platformSdkCodename
config.TestProductVariables.Platform_sdk_final = &test.platformSdkFinal
ctx := testAppContext(config, bp, nil)
run(t, ctx, config)
foo := ctx.ModuleForTests("foo", "android_common")
link := foo.Output("package-res.apk")
linkFlags := strings.Split(link.Args["flags"], " ")
min := android.IndexList("--min-sdk-version", linkFlags)
target := android.IndexList("--target-sdk-version", linkFlags)
if min == -1 || target == -1 || min == len(linkFlags)-1 || target == len(linkFlags)-1 {
t.Fatalf("missing --min-sdk-version or --target-sdk-version in link flags: %q", linkFlags)
}
gotMinSdkVersion := linkFlags[min+1]
gotTargetSdkVersion := linkFlags[target+1]
if gotMinSdkVersion != test.expectedMinSdkVersion {
t.Errorf("incorrect --min-sdk-version, expected %q got %q",
test.expectedMinSdkVersion, gotMinSdkVersion)
}
if gotTargetSdkVersion != test.expectedMinSdkVersion {
t.Errorf("incorrect --target-sdk-version, expected %q got %q",
test.expectedMinSdkVersion, gotTargetSdkVersion)
}
})
}
}
}
func TestJNIABI(t *testing.T) {
ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
cc_library {
name: "libjni",
system_shared_libs: [],
stl: "none",
}
android_test {
name: "test",
no_framework_libs: true,
jni_libs: ["libjni"],
}
android_test {
name: "test_first",
no_framework_libs: true,
compile_multilib: "first",
jni_libs: ["libjni"],
}
android_test {
name: "test_both",
no_framework_libs: true,
compile_multilib: "both",
jni_libs: ["libjni"],
}
android_test {
name: "test_32",
no_framework_libs: true,
compile_multilib: "32",
jni_libs: ["libjni"],
}
android_test {
name: "test_64",
no_framework_libs: true,
compile_multilib: "64",
jni_libs: ["libjni"],
}
`)
testCases := []struct {
name string
abis []string
}{
{"test", []string{"arm64-v8a"}},
{"test_first", []string{"arm64-v8a"}},
{"test_both", []string{"arm64-v8a", "armeabi-v7a"}},
{"test_32", []string{"armeabi-v7a"}},
{"test_64", []string{"arm64-v8a"}},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
app := ctx.ModuleForTests(test.name, "android_common")
jniLibZip := app.Output("jnilibs.zip")
var abis []string
args := strings.Fields(jniLibZip.Args["jarArgs"])
for i := 0; i < len(args); i++ {
if args[i] == "-P" {
abis = append(abis, filepath.Base(args[i+1]))
i++
}
}
if !reflect.DeepEqual(abis, test.abis) {
t.Errorf("want abis %v, got %v", test.abis, abis)
}
})
}
}
func TestJNIPackaging(t *testing.T) {
ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
cc_library {
name: "libjni",
system_shared_libs: [],
stl: "none",
}
android_app {
name: "app",
jni_libs: ["libjni"],
}
android_app {
name: "app_noembed",
jni_libs: ["libjni"],
use_embedded_native_libs: false,
}
android_app {
name: "app_embed",
jni_libs: ["libjni"],
use_embedded_native_libs: true,
}
android_test {
name: "test",
no_framework_libs: true,
jni_libs: ["libjni"],
}
android_test {
name: "test_noembed",
no_framework_libs: true,
jni_libs: ["libjni"],
use_embedded_native_libs: false,
}
android_test_helper_app {
name: "test_helper",
no_framework_libs: true,
jni_libs: ["libjni"],
}
android_test_helper_app {
name: "test_helper_noembed",
no_framework_libs: true,
jni_libs: ["libjni"],
use_embedded_native_libs: false,
}
`)
testCases := []struct {
name string
packaged bool
compressed bool
}{
{"app", false, false},
{"app_noembed", false, false},
{"app_embed", true, false},
{"test", true, false},
{"test_noembed", true, true},
{"test_helper", true, false},
{"test_helper_noembed", true, true},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
app := ctx.ModuleForTests(test.name, "android_common")
jniLibZip := app.MaybeOutput("jnilibs.zip")
if g, w := (jniLibZip.Rule != nil), test.packaged; g != w {
t.Errorf("expected jni packaged %v, got %v", w, g)
}
if jniLibZip.Rule != nil {
if g, w := !strings.Contains(jniLibZip.Args["jarArgs"], "-L 0"), test.compressed; g != w {
t.Errorf("expected jni compressed %v, got %v", w, g)
}
}
})
}
}
func TestCertificates(t *testing.T) {
testCases := []struct {
name string
bp string
certificateOverride string
expected string
}{
{
name: "default",
bp: `
android_app {
name: "foo",
srcs: ["a.java"],
}
`,
certificateOverride: "",
expected: "build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8",
},
{
name: "module certificate property",
bp: `
android_app {
name: "foo",
srcs: ["a.java"],
certificate: ":new_certificate"
}
android_app_certificate {
name: "new_certificate",
certificate: "cert/new_cert",
}
`,
certificateOverride: "",
expected: "cert/new_cert.x509.pem cert/new_cert.pk8",
},
{
name: "path certificate property",
bp: `
android_app {
name: "foo",
srcs: ["a.java"],
certificate: "expiredkey"
}
`,
certificateOverride: "",
expected: "build/target/product/security/expiredkey.x509.pem build/target/product/security/expiredkey.pk8",
},
{
name: "certificate overrides",
bp: `
android_app {
name: "foo",
srcs: ["a.java"],
certificate: "expiredkey"
}
android_app_certificate {
name: "new_certificate",
certificate: "cert/new_cert",
}
`,
certificateOverride: "foo:new_certificate",
expected: "cert/new_cert.x509.pem cert/new_cert.pk8",
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
config := testConfig(nil)
if test.certificateOverride != "" {
config.TestProductVariables.CertificateOverrides = []string{test.certificateOverride}
}
ctx := testAppContext(config, test.bp, nil)
run(t, ctx, config)
foo := ctx.ModuleForTests("foo", "android_common")
signapk := foo.Output("foo.apk")
signFlags := signapk.Args["certificates"]
if test.expected != signFlags {
t.Errorf("Incorrect signing flags, expected: %q, got: %q", test.expected, signFlags)
}
})
}
}
func TestPackageNameOverride(t *testing.T) {
testCases := []struct {
name string
bp string
packageNameOverride string
expected []string
}{
{
name: "default",
bp: `
android_app {
name: "foo",
srcs: ["a.java"],
}
`,
packageNameOverride: "",
expected: []string{
buildDir + "/.intermediates/foo/android_common/foo.apk",
buildDir + "/target/product/test_device/system/app/foo/foo.apk",
},
},
{
name: "overridden",
bp: `
android_app {
name: "foo",
srcs: ["a.java"],
}
`,
packageNameOverride: "foo:bar",
expected: []string{
// The package apk should be still be the original name for test dependencies.
buildDir + "/.intermediates/foo/android_common/foo.apk",
buildDir + "/target/product/test_device/system/app/bar/bar.apk",
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
config := testConfig(nil)
if test.packageNameOverride != "" {
config.TestProductVariables.PackageNameOverrides = []string{test.packageNameOverride}
}
ctx := testAppContext(config, test.bp, nil)
run(t, ctx, config)
foo := ctx.ModuleForTests("foo", "android_common")
outputs := foo.AllOutputs()
outputMap := make(map[string]bool)
for _, o := range outputs {
outputMap[o] = true
}
for _, e := range test.expected {
if _, exist := outputMap[e]; !exist {
t.Errorf("Can't find %q in output files.\nAll outputs:%v", e, outputs)
}
}
})
}
}
func TestInstrumentationTargetOverridden(t *testing.T) {
bp := `
android_app {
name: "foo",
srcs: ["a.java"],
}
android_test {
name: "bar",
instrumentation_for: "foo",
}
`
config := testConfig(nil)
config.TestProductVariables.ManifestPackageNameOverrides = []string{"foo:org.dandroid.bp"}
ctx := testAppContext(config, bp, nil)
run(t, ctx, config)
bar := ctx.ModuleForTests("bar", "android_common")
res := bar.Output("package-res.apk")
aapt2Flags := res.Args["flags"]
e := "--rename-instrumentation-target-package org.dandroid.bp"
if !strings.Contains(aapt2Flags, e) {
t.Errorf("target package renaming flag, %q is missing in aapt2 link flags, %q", e, aapt2Flags)
}
}
func TestOverrideAndroidApp(t *testing.T) {
ctx := testJava(t, `
android_app {
name: "foo",
srcs: ["a.java"],
certificate: "expiredkey",
overrides: ["baz"],
}
override_android_app {
name: "bar",
base: "foo",
certificate: ":new_certificate",
}
android_app_certificate {
name: "new_certificate",
certificate: "cert/new_cert",
}
override_android_app {
name: "baz",
base: "foo",
package_name: "org.dandroid.bp",
}
`)
expectedVariants := []struct {
variantName string
apkName string
apkPath string
signFlag string
overrides []string
aaptFlag string
}{
{
variantName: "android_common",
apkPath: "/target/product/test_device/system/app/foo/foo.apk",
signFlag: "build/target/product/security/expiredkey.x509.pem build/target/product/security/expiredkey.pk8",
overrides: []string{"baz"},
aaptFlag: "",
},
{
variantName: "bar_android_common",
apkPath: "/target/product/test_device/system/app/bar/bar.apk",
signFlag: "cert/new_cert.x509.pem cert/new_cert.pk8",
overrides: []string{"baz", "foo"},
aaptFlag: "",
},
{
variantName: "baz_android_common",
apkPath: "/target/product/test_device/system/app/baz/baz.apk",
signFlag: "build/target/product/security/expiredkey.x509.pem build/target/product/security/expiredkey.pk8",
overrides: []string{"baz", "foo"},
aaptFlag: "--rename-manifest-package org.dandroid.bp",
},
}
for _, expected := range expectedVariants {
variant := ctx.ModuleForTests("foo", expected.variantName)
// Check the final apk name
outputs := variant.AllOutputs()
expectedApkPath := buildDir + expected.apkPath
found := false
for _, o := range outputs {
if o == expectedApkPath {
found = true
break
}
}
if !found {
t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs)
}
// Check the certificate paths
signapk := variant.Output("foo.apk")
signFlag := signapk.Args["certificates"]
if expected.signFlag != signFlag {
t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected.signFlag, signFlag)
}
// Check if the overrides field values are correctly aggregated.
mod := variant.Module().(*AndroidApp)
if !reflect.DeepEqual(expected.overrides, mod.appProperties.Overrides) {
t.Errorf("Incorrect overrides property value, expected: %q, got: %q",
expected.overrides, mod.appProperties.Overrides)
}
// Check the package renaming flag, if exists.
res := variant.Output("package-res.apk")
aapt2Flags := res.Args["flags"]
if !strings.Contains(aapt2Flags, expected.aaptFlag) {
t.Errorf("package renaming flag, %q is missing in aapt2 link flags, %q", expected.aaptFlag, aapt2Flags)
}
}
}
func TestEmbedNotice(t *testing.T) {
ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
android_app {
name: "foo",
srcs: ["a.java"],
static_libs: ["javalib"],
jni_libs: ["libjni"],
notice: "APP_NOTICE",
embed_notices: true,
}
// No embed_notice flag
android_app {
name: "bar",
srcs: ["a.java"],
jni_libs: ["libjni"],
notice: "APP_NOTICE",
}
// No NOTICE files
android_app {
name: "baz",
srcs: ["a.java"],
embed_notices: true,
}
cc_library {
name: "libjni",
system_shared_libs: [],
stl: "none",
notice: "LIB_NOTICE",
}
java_library {
name: "javalib",
srcs: [
":gen",
],
}
genrule {
name: "gen",
tools: ["gentool"],
out: ["gen.java"],
notice: "GENRULE_NOTICE",
}
java_binary_host {
name: "gentool",
srcs: ["b.java"],
notice: "TOOL_NOTICE",
}
`)
// foo has NOTICE files to process, and embed_notices is true.
foo := ctx.ModuleForTests("foo", "android_common")
// verify merge notices rule.
mergeNotices := foo.Rule("mergeNoticesRule")
noticeInputs := mergeNotices.Inputs.Strings()
// TOOL_NOTICE should be excluded as it's a host module.
if len(mergeNotices.Inputs) != 3 {
t.Errorf("number of input notice files: expected = 3, actual = %q", noticeInputs)
}
if !inList("APP_NOTICE", noticeInputs) {
t.Errorf("APP_NOTICE is missing from notice files, %q", noticeInputs)
}
if !inList("LIB_NOTICE", noticeInputs) {
t.Errorf("LIB_NOTICE is missing from notice files, %q", noticeInputs)
}
if !inList("GENRULE_NOTICE", noticeInputs) {
t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs)
}
// aapt2 flags should include -A <NOTICE dir> so that its contents are put in the APK's /assets.
res := foo.Output("package-res.apk")
aapt2Flags := res.Args["flags"]
e := "-A " + buildDir + "/.intermediates/foo/android_common/NOTICE"
if !strings.Contains(aapt2Flags, e) {
t.Errorf("asset dir flag for NOTICE, %q is missing in aapt2 link flags, %q", e, aapt2Flags)
}
// bar has NOTICE files to process, but embed_notices is not set.
bar := ctx.ModuleForTests("bar", "android_common")
mergeNotices = bar.MaybeRule("mergeNoticesRule")
if mergeNotices.Rule != nil {
t.Errorf("mergeNotices shouldn't have run for bar")
}
// baz's embed_notice is true, but it doesn't have any NOTICE files.
baz := ctx.ModuleForTests("baz", "android_common")
mergeNotices = baz.MaybeRule("mergeNoticesRule")
if mergeNotices.Rule != nil {
t.Errorf("mergeNotices shouldn't have run for baz")
}
}
func TestUncompressDex(t *testing.T) {
testCases := []struct {
name string
bp string
uncompressedPlatform bool
uncompressedUnbundled bool
}{
{
name: "normal",
bp: `
android_app {
name: "foo",
srcs: ["a.java"],
}
`,
uncompressedPlatform: true,
uncompressedUnbundled: false,
},
{
name: "use_embedded_dex",
bp: `
android_app {
name: "foo",
use_embedded_dex: true,
srcs: ["a.java"],
}
`,
uncompressedPlatform: true,
uncompressedUnbundled: true,
},
{
name: "privileged",
bp: `
android_app {
name: "foo",
privileged: true,
srcs: ["a.java"],
}
`,
uncompressedPlatform: true,
uncompressedUnbundled: true,
},
}
test := func(t *testing.T, bp string, want bool, unbundled bool) {
t.Helper()
config := testConfig(nil)
if unbundled {
config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
}
ctx := testAppContext(config, bp, nil)
run(t, ctx, config)
foo := ctx.ModuleForTests("foo", "android_common")
dex := foo.Rule("r8")
uncompressedInDexJar := strings.Contains(dex.Args["zipFlags"], "-L 0")
aligned := foo.MaybeRule("zipalign").Rule != nil
if uncompressedInDexJar != want {
t.Errorf("want uncompressed in dex %v, got %v", want, uncompressedInDexJar)
}
if aligned != want {
t.Errorf("want aligned %v, got %v", want, aligned)
}
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
t.Run("platform", func(t *testing.T) {
test(t, tt.bp, tt.uncompressedPlatform, false)
})
t.Run("unbundled", func(t *testing.T) {
test(t, tt.bp, tt.uncompressedUnbundled, true)
})
})
}
}