Skip to content

Commit

Permalink
Ensure that the target package in the <instrumentation> tag of the in…
Browse files Browse the repository at this point in the history
…strumentation android_binary's AndroidManifest.xml references the correct package name of the instrumented android_binary.

During an instrumentation test, ART will use the targetPackage specified in the instrumentation APK's AndroidManifest to determine the application to be instrumented. We can perform this check in Bazel at execution time, before the apps are loaded onto the device.

See android_instrumentation_test_integration_test.sh for the e2e example.

GITHUB: bazelbuild#903
RELNOTES: None.
PiperOrigin-RevId: 179564246
  • Loading branch information
jin authored and Copybara-Service committed Dec 19, 2017
1 parent 691bd15 commit 741dbc0
Show file tree
Hide file tree
Showing 12 changed files with 524 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder;
import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Expand Down Expand Up @@ -562,17 +563,46 @@ public static RuleConfiguredTargetBuilder createAndroidBinary(

// If this is an instrumentation APK, create the provider for android_instrumentation_test.
if (isInstrumentation(ruleContext)) {
Artifact targetApk =
ruleContext
.getPrerequisite("instruments", Mode.TARGET)
.getProvider(ApkProvider.class)
.getApk();
ApkProvider targetApkProvider =
ruleContext.getPrerequisite("instruments", Mode.TARGET, ApkProvider.class);

Artifact targetApk = targetApkProvider.getApk();
Artifact instrumentationApk = zipAlignedApk;

AndroidInstrumentationInfo instrumentationProvider =
new AndroidInstrumentationInfo(targetApk, instrumentationApk);

builder.addNativeDeclaredProvider(instrumentationProvider);

// At this point, the Android manifests of both target and instrumentation APKs are finalized.
FilesToRunProvider checker =
ruleContext.getExecutablePrerequisite("$instrumentation_test_check", Mode.HOST);
Artifact targetManifest = targetApkProvider.getMergedManifest();
Artifact instrumentationManifest = applicationManifest.getManifest();
Artifact checkOutput =
ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.INSTRUMENTATION_TEST_CHECK_RESULTS);

SpawnAction.Builder checkAction =
new Builder()
.setExecutable(checker)
.addInput(targetManifest)
.addInput(instrumentationManifest)
.addOutput(checkOutput)
.setProgressMessage(
"Validating the merged manifests of the target and instrumentation APKs")
.setMnemonic("AndroidManifestInstrumentationCheck");

CustomCommandLine commandLine =
CustomCommandLine.builder()
.addExecPath("--instrumentation_manifest", instrumentationManifest)
.addExecPath("--target_manifest", targetManifest)
.addExecPath("--output", checkOutput)
.build();

builder.addOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL, checkOutput);
checkAction.addCommandLine(commandLine);
ruleContext.registerAction(checkAction.build(ruleContext));
}

androidCommon.addTransitiveInfoProviders(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ private static void validateRuleContext(RuleContext ruleContext)
ruleContext.throwWithAttributeError(
"instrumentation",
String.format(
"The android_binary target at %s is missing an 'instruments' attribute. Please set "
+ "it as the label of the android_binary under test.",
"The android_binary target %s is missing an 'instruments' attribute. Please set "
+ "it to the label of the android_binary under test.",
ruleContext.attributes().get("instrumentation", BuildType.LABEL)));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ public final class AndroidRuleClasses {
fromTemplates("%{name}_files/deploy_info_split.deployinfo.pb");
public static final SafeImplicitOutputsFunction REX_OUTPUT_PACKAGE_MAP =
fromTemplates("%{name}_rex/rex_output_package.map");
public static final SafeImplicitOutputsFunction INSTRUMENTATION_TEST_CHECK_RESULTS =
fromTemplates("%{name}_files/instrumentation_test_check_results.txt");

// This needs to be in its own directory because ApkBuilder only has a function (-rf) for source
// folders but not source files, and it's easiest to guarantee that nothing gets put beside this
Expand Down Expand Up @@ -991,6 +993,19 @@ is exceeded. Assumes multidex classes are loaded through application code (i.e.
.undocumented("blocked by android_instrumentation_test")
.allowedRuleClasses("android_binary")
.allowedFileTypes(NO_FILE))
.add(
attr("$instrumentation_test_check", LABEL)
.cfg(HOST)
.value(
new Attribute.ComputedDefault() {
@Override
public Object getDefault(AttributeMap rule) {
return rule.isAttributeValueExplicitlySpecified("instruments")
? env.getToolsLabel("//tools/android:instrumentation_test_check")
: null;
}
})
.exec())
.add(
attr("$zip_filter", LABEL)
.cfg(HOST)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ private ImmutableList<String> createAndroidBuildContents() {
.add("java_plugin(name = 'databinding_annotation_processor',")
.add(" processor_class = 'android.databinding.annotationprocessor.ProcessDataBinding')")
.add("sh_binary(name = 'jarjar_bin', srcs = ['empty.sh'])")
.add("sh_binary(name = 'instrumentation_test_check', srcs = ['empty.sh'])")
.add("package_group(name = 'android_device_whitelist', packages = ['//...'])");

return androidBuildContents.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ public void testInstrumentationBinaryIsInstrumenting() throws Exception {
checkError(
"javatests/com/app/instr",
"ait",
"The android_binary target at //javatests/com/app/instr:app "
"The android_binary target //javatests/com/app/instr:app "
+ "is missing an 'instruments' attribute",
"android_binary(",
" name = 'app',",
Expand Down
12 changes: 12 additions & 0 deletions src/test/shell/bazel/android/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,15 @@ sh_test(
# This test builds an android_binary with Java 8 code.
tags = ["jdk8"],
)

sh_test(
name = "android_instrumentation_test_integration_test",
size = "medium",
srcs = ["android_instrumentation_test_integration_test.sh"],
data = [
":android_helper",
"//external:android_sdk_for_testing",
"//src/test/shell/bazel:test-deps",
],
shard_count = 4,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#!/bin/bash
#
# Copyright 2017 The Bazel Authors. 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.

# For these tests to run do the following:
#
# 1. Install an Android SDK from https://developer.android.com
# 2. Set the $ANDROID_HOME environment variable
# 3. Uncomment the line in WORKSPACE containing android_sdk_repository

# Load the test setup defined in the parent directory
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CURRENT_DIR}/android_helper.sh" \
|| { echo "android_helper.sh not found!" >&2; exit 1; }
fail_if_no_android_sdk

source "${CURRENT_DIR}/../../integration_test_setup.sh" \
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; }

function setup_android_instrumentation_test_env() {
mkdir -p java/com/bin/res/values
mkdir -p javatests/com/bin

# Targets for android_binary application under test
cat > java/com/bin/BUILD <<EOF
android_binary(
name = 'target',
manifest = 'AndroidManifest.xml',
deps = [':lib'],
visibility = ["//visibility:public"],
)
android_library(
name = 'lib',
manifest = 'AndroidManifest.xml',
exports_manifest = 0,
resource_files = ['res/values/values.xml'],
srcs = ['Bar.java'],
visibility = ["//visibility:public"],
)
EOF
cat > java/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin' xmlns:android="http://schemas.android.com/apk/res/android" />
EOF
cat > java/com/bin/Bar.java <<EOF
package com.bin;
public class Bar { }
EOF
cat > java/com/bin/res/values/values.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
</resources>
EOF

# Targets for instrumentation android_binary
cat > javatests/com/bin/BUILD <<EOF
android_binary(
name = 'instr',
srcs = ['BarTest.java'],
manifest = 'AndroidManifest.xml',
instruments = '//java/com/bin:target',
deps = ['//java/com/bin:lib'],
)
EOF
cat > javatests/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin' xmlns:android='http://schemas.android.com/apk/res/android'>
<instrumentation android:targetPackage='com.bin' android:name='some.test.runner' />
</manifest>
EOF
cat > javatests/com/bin/BarTest.java <<EOF
package com.bin;
public class BarTest {
public Bar getBar() {
return new Bar();
}
}
EOF
}

function test_correct_target_package_build_succeed() {
create_new_workspace
setup_android_sdk_support
setup_android_instrumentation_test_env
assert_build //javatests/com/bin:instr
}

function test_incorrect_target_package_build_failure() {
create_new_workspace
setup_android_sdk_support
setup_android_instrumentation_test_env

cat > javatests/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin' xmlns:android='http://schemas.android.com/apk/res/android'>
<instrumentation android:targetPackage='not.com.bin' android:name='some.test.runner' />
</manifest>
EOF

assert_build_fails //javatests/com/bin:instr
expect_log "does not match the package name of"
}

function test_multiple_instrumentations_with_different_package_names_build_failure() {
create_new_workspace
setup_android_sdk_support
setup_android_instrumentation_test_env

cat > javatests/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin' xmlns:android='http://schemas.android.com/apk/res/android'>
<instrumentation android:targetPackage='com.bin' android:name='some.test.runner' />
<instrumentation android:targetPackage='not.com.bin' android:name='some.test.runner' />
</manifest>
EOF

assert_build_fails //javatests/com/bin:instr
expect_log "do not reference the same target package"
}

function test_no_target_package_attribute_build_failure() {
create_new_workspace
setup_android_sdk_support
setup_android_instrumentation_test_env

cat > javatests/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin' xmlns:android='http://schemas.android.com/apk/res/android'>
<instrumentation />
</manifest>
EOF

assert_build_fails //javatests/com/bin:instr
expect_log "No <instrumentation> tag containing the targetPackage attribute"
}

function test_target_package_no_package_specified_build_failure() {
create_new_workspace
setup_android_sdk_support
setup_android_instrumentation_test_env

cat > java/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
EOF

assert_build_fails //javatests/com/bin:instr
expect_log "needs to specify the package name"
}

function test_android_instrumentation_binary_class_filtering() {
create_new_workspace
setup_android_sdk_support
mkdir -p java/com/bin
cat > java/com/bin/BUILD <<EOF
android_binary(
name = 'instr',
srcs = ['Foo.java'],
manifest = 'TestAndroidManifest.xml',
instruments = ':target',
deps = [':lib'],
)
android_binary(
name = 'target',
manifest = 'AndroidManifest.xml',
deps = [':lib'],
)
android_library(
name = 'lib',
manifest = 'AndroidManifest.xml',
resource_files = ['res/values/values.xml'],
srcs = ['Bar.java', 'Baz.java'],
)
EOF
cat > java/com/bin/AndroidManifest.xml <<EOF
<manifest package='com.bin' />
EOF
cat > java/com/bin/TestAndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin' xmlns:android='http://schemas.android.com/apk/res/android'>
<instrumentation android:targetPackage='com.bin' android:name='some.test.runner' />
</manifest>
EOF
cat > java/com/bin/Foo.java <<EOF
package com.bin;
public class Foo {
public Bar getBar() {
return new Bar();
}
public Baz getBaz() {
return new Baz();
}
}
EOF
cat > java/com/bin/Bar.java <<EOF
package com.bin;
public class Bar {
public Baz getBaz() {
return new Baz();
}
}
EOF
cat > java/com/bin/Baz.java <<EOF
package com.bin;
public class Baz {}
EOF
mkdir -p java/com/bin/res/values
cat > java/com/bin/res/values/values.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
</resources>
EOF
assert_build //java/com/bin:instr
output_classes=$(zipinfo -1 bazel-bin/java/com/bin/instr_filtered.jar)
assert_one_of $output_classes "META-INF/MANIFEST.MF"
assert_one_of $output_classes "com/bin/Foo.class"
assert_not_one_of $output_classes "com/bin/R.class"
assert_not_one_of $output_classes "com/bin/Bar.class"
assert_not_one_of $output_classes "com/bin/Baz.class"
}

run_suite "android_instrumentation_test integration tests"

Loading

0 comments on commit 741dbc0

Please sign in to comment.