From 8355237f65842d9df45b51cdfaebda9bb44c3a79 Mon Sep 17 00:00:00 2001 From: ajmichael Date: Thu, 29 Jun 2017 01:29:19 +0200 Subject: Introduce new android_instrumentation_test rule. This rule works with the android_instrumentation, android_device_script_fixture and android_host_service_fixture rules to support testing Android applications. None of these rules are installed yet, because the forthcoming Android test runner is not yet open sourced. https://github.com/bazelbuild/bazel/issues/903. RELNOTES: None PiperOrigin-RevId: 160465920 --- src/main/java/com/google/devtools/build/lib/BUILD | 2 + .../rules/android/AndroidDeviceScriptFixture.java | 8 +- .../rules/android/AndroidHostServiceFixture.java | 1 + .../AndroidHostServiceFixtureInfoProvider.java | 15 +- .../lib/rules/android/AndroidInstrumentation.java | 14 +- .../rules/android/AndroidInstrumentationTest.java | 334 +++++++++++++++++++++ .../android/AndroidInstrumentationTestRule.java | 83 +++++ .../android_instrumentation_test_template.txt | 88 ++++++ .../lib/rules/android/test_suite_property_name.txt | 1 + 9 files changed, 538 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTest.java create mode 100644 src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTestRule.java create mode 100644 src/main/java/com/google/devtools/build/lib/rules/android/android_instrumentation_test_template.txt create mode 100644 src/main/java/com/google/devtools/build/lib/rules/android/test_suite_property_name.txt (limited to 'src/main/java/com/google') diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index 9be7d831d6..3091e7f3bb 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -1058,7 +1058,9 @@ java_library( ), resources = [ "rules/android/android_device_stub_template.txt", + "rules/android/android_instrumentation_test_template.txt", "rules/android/databinding_annotation_template.txt", + "rules/android/test_suite_property_name.txt", ], deps = [ ":build-base", diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDeviceScriptFixture.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDeviceScriptFixture.java index 648b282831..a422e62280 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDeviceScriptFixture.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDeviceScriptFixture.java @@ -20,6 +20,7 @@ import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; @@ -36,7 +37,12 @@ public class AndroidDeviceScriptFixture implements RuleConfiguredTargetFactory { Artifact fixtureScript = getFixtureScript(ruleContext); return new RuleConfiguredTargetBuilder(ruleContext) .setFilesToBuild(NestedSetBuilder.stableOrder().add(fixtureScript).build()) - .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY) + .addProvider( + RunfilesProvider.class, + RunfilesProvider.simple( + new Runfiles.Builder(ruleContext.getWorkspaceName()) + .addArtifact(fixtureScript) + .build())) .addNativeDeclaredProvider( new AndroidDeviceScriptFixtureInfoProvider( fixtureScript, diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixture.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixture.java index 2ae4c81e29..a5f8935852 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixture.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixture.java @@ -51,6 +51,7 @@ public class AndroidHostServiceFixture implements RuleConfiguredTargetFactory { .addProvider(RunfilesProvider.class, RunfilesProvider.simple(runfiles)) .addNativeDeclaredProvider( new AndroidHostServiceFixtureInfoProvider( + executable.getExecutable(), ruleContext.getTokenizedStringListAttr("service_names"), AndroidCommon.getSupportApks(ruleContext), ruleContext.attributes().get("provides_test_args", Type.BOOLEAN), diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixtureInfoProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixtureInfoProvider.java index 715bfbc81b..3acd378695 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixtureInfoProvider.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidHostServiceFixtureInfoProvider.java @@ -34,21 +34,28 @@ public class AndroidHostServiceFixtureInfoProvider extends SkylarkClassObject static final NativeClassObjectConstructor ANDROID_HOST_SERVICE_FIXTURE_INFO = new NativeClassObjectConstructor(SKYLARK_NAME) {}; + private final Artifact executable; private final ImmutableList serviceNames; private final NestedSet supportApks; private final boolean providesTestArgs; - private final boolean isDaemon; + private final boolean daemon; AndroidHostServiceFixtureInfoProvider( + Artifact executable, ImmutableList serviceNames, NestedSet supportApks, boolean providesTestArgs, boolean isDaemon) { super(ANDROID_HOST_SERVICE_FIXTURE_INFO, ImmutableMap.of()); + this.executable = executable; this.serviceNames = serviceNames; this.supportApks = supportApks; this.providesTestArgs = providesTestArgs; - this.isDaemon = isDaemon; + this.daemon = isDaemon; + } + + public Artifact getExecutable() { + return executable; } public ImmutableList getServiceNames() { @@ -63,7 +70,7 @@ public class AndroidHostServiceFixtureInfoProvider extends SkylarkClassObject return providesTestArgs; } - public boolean getIsDaemon() { - return isDaemon; + public boolean getDaemon() { + return daemon; } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java index 75587bc9b6..d281b10134 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java @@ -20,9 +20,11 @@ import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.SymlinkAction; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction; @@ -47,12 +49,18 @@ public class AndroidInstrumentation implements RuleConfiguredTargetFactory { Artifact targetApk = getTargetApk(ruleContext); Artifact instrumentationApk = createInstrumentationApk(ruleContext); + NestedSet filesToBuild = + NestedSetBuilder.stableOrder().add(targetApk).add(instrumentationApk).build(); RuleConfiguredTargetBuilder ruleBuilder = new RuleConfiguredTargetBuilder(ruleContext); return ruleBuilder - .setFilesToBuild( - NestedSetBuilder.stableOrder().add(targetApk).add(instrumentationApk).build()) - .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY) + .setFilesToBuild(filesToBuild) + .addProvider( + RunfilesProvider.class, + RunfilesProvider.simple( + new Runfiles.Builder(ruleContext.getWorkspaceName()) + .addTransitiveArtifacts(filesToBuild) + .build())) .addNativeDeclaredProvider( new AndroidInstrumentationInfoProvider(targetApk, instrumentationApk)) .build(); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTest.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTest.java new file mode 100644 index 0000000000..0e5c7170a7 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTest.java @@ -0,0 +1,334 @@ +// 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. +package com.google.devtools.build.lib.rules.android; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.Runfiles; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; +import com.google.devtools.build.lib.rules.test.ExecutionInfoProvider; +import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.util.ResourceFileLoader; +import java.io.IOException; +import java.util.stream.StreamSupport; +import javax.annotation.Nullable; + +/** An implementation of the {@code android_instrumentation} rule. */ +public class AndroidInstrumentationTest implements RuleConfiguredTargetFactory { + + private static final Template ANDROID_INSTRUMENTATION_TEST_STUB_SCRIPT = + Template.forResource( + AndroidInstrumentationTest.class, "android_instrumentation_test_template.txt"); + private static final String TEST_SUITE_PROPERTY_NAME_FILE = "test_suite_property_name.txt"; + + @Override + public ConfiguredTarget create(RuleContext ruleContext) + throws InterruptedException, RuleErrorException { + // The wrapper script that invokes the test entry point. + Artifact testExecutable = createTestExecutable(ruleContext); + + ImmutableList runfilesDeps = + ImmutableList.builder() + .addAll(ruleContext.getPrerequisites("instrumentations", Mode.TARGET)) + .addAll(ruleContext.getPrerequisites("fixtures", Mode.TARGET)) + .add(ruleContext.getPrerequisite("target_device", Mode.HOST)) + .add(ruleContext.getPrerequisite("$test_entry_point", Mode.HOST)) + .build(); + + Runfiles runfiles = + new Runfiles.Builder(ruleContext.getWorkspaceName()) + .addArtifact(testExecutable) + .addTargets(runfilesDeps, RunfilesProvider.DEFAULT_RUNFILES) + .addTransitiveArtifacts(AndroidCommon.getSupportApks(ruleContext)) + .addTransitiveArtifacts(getAdb(ruleContext).getFilesToRun()) + .addArtifacts(getDataDeps(ruleContext)) + .build(); + + return new RuleConfiguredTargetBuilder(ruleContext) + .setFilesToBuild(NestedSetBuilder.stableOrder().add(testExecutable).build()) + .addProvider(RunfilesProvider.class, RunfilesProvider.simple(runfiles)) + .setRunfilesSupport( + RunfilesSupport.withExecutable(ruleContext, runfiles, testExecutable), testExecutable) + .addNativeDeclaredProvider(getExecutionInfoProvider(ruleContext)) + .build(); + } + + /** Registers a {@link TemplateExpansionAction} to write the test executable. */ + private Artifact createTestExecutable(RuleContext ruleContext) throws RuleErrorException { + Artifact testExecutable = ruleContext.createOutputArtifact(); + ruleContext.registerAction( + new TemplateExpansionAction( + ruleContext.getActionOwner(), + testExecutable, + ANDROID_INSTRUMENTATION_TEST_STUB_SCRIPT, + getTemplateSubstitutions(ruleContext), + /* makeExecutable = */ true)); + return testExecutable; + } + + /** + * This method defines all substitutions need to fill in {@link + * #ANDROID_INSTRUMENTATION_TEST_STUB_SCRIPT}. + */ + private ImmutableList getTemplateSubstitutions(RuleContext ruleContext) + throws RuleErrorException { + return ImmutableList.builder() + .add(Substitution.of("%workspace%", ruleContext.getWorkspaceName())) + .add(Substitution.of("%test_label%", ruleContext.getLabel().getCanonicalForm())) + .add(executableSubstitution("%adb%", getAdb(ruleContext))) + .add(executableSubstitution("%device_script%", getTargetDevice(ruleContext))) + .add(executableSubstitution("%test_entry_point%", getTestEntryPoint(ruleContext))) + .add(artifactListSubstitution("%target_apks%", getTargetApks(ruleContext))) + .add( + artifactListSubstitution("%instrumentation_apks%", getInstrumentationApks(ruleContext))) + .add(artifactListSubstitution("%support_apks%", getAllSupportApks(ruleContext))) + .add(Substitution.ofSpaceSeparatedMap("%test_args%", getTestArgs(ruleContext))) + .add(Substitution.ofSpaceSeparatedMap("%fixture_args%", getFixtureArgs(ruleContext))) + .add(Substitution.ofSpaceSeparatedMap("%log_levels%", getLogLevels(ruleContext))) + .add(deviceScriptFixturesSubstitution(ruleContext)) + .addAll(hostServiceFixturesSubstitutions(ruleContext)) + .add(artifactListSubstitution("%data_deps%", getDataDeps(ruleContext))) + .add(Substitution.of("%device_broker_type%", getDeviceBrokerType(ruleContext))) + .add(Substitution.of("%test_suite_property_name%", getTestSuitePropertyName(ruleContext))) + .build(); + } + + /** + * An ad-hoc substitution to put the information from the {@code android_device_script_fixture}s + * into the bash stub script. + * + *

TODO(ajmichael): Determine an actual protocol to pass this information to the test suite. + */ + private static Substitution deviceScriptFixturesSubstitution(RuleContext ruleContext) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (AndroidDeviceScriptFixtureInfoProvider deviceScriptFixture : + getDeviceScriptFixtures(ruleContext)) { + builder.add( + String.format( + "[%s]=%b,%b", + deviceScriptFixture.getFixtureScript().getRunfilesPathString(), + deviceScriptFixture.getDaemon(), + deviceScriptFixture.getStrictExit())); + } + return Substitution.ofSpaceSeparatedList("%device_script_fixtures%", builder.build()); + } + + /** + * An ad-hoc substitution to put the information from the {@code android_host_service_fixture}s + * into the bash stub script. + * + *

TODO(ajmichael): Determine an actual protocol to pass this information to the test suite. + */ + private static ImmutableList hostServiceFixturesSubstitutions( + RuleContext ruleContext) { + AndroidHostServiceFixtureInfoProvider hostServiceFixture = getHostServiceFixture(ruleContext); + return ImmutableList.of( + Substitution.of( + "%host_service_fixture%", + hostServiceFixture != null + ? hostServiceFixture.getExecutable().getRunfilesPathString() + : ""), + Substitution.of( + "%host_service_fixture_services%", + hostServiceFixture != null + ? Joiner.on(",").join(hostServiceFixture.getServiceNames()) + : "")); + } + + private static Substitution executableSubstitution( + String key, FilesToRunProvider filesToRunProvider) { + return Substitution.of(key, filesToRunProvider.getExecutable().getRunfilesPathString()); + } + + private static Substitution artifactListSubstitution(String key, Iterable artifacts) { + return Substitution.ofSpaceSeparatedList( + key, + StreamSupport.stream(artifacts.spliterator(), false) + .map(Artifact::getRunfilesPathString) + .collect(ImmutableList.toImmutableList())); + } + + /** + * The target APKs from each {@code android_instrumentation} in the {@code instrumentations} + * attribute. + */ + private static Iterable getTargetApks(RuleContext ruleContext) { + return Iterables.transform( + ruleContext.getPrerequisites( + "instrumentations", + Mode.TARGET, + AndroidInstrumentationInfoProvider.ANDROID_INSTRUMENTATION_INFO.getKey(), + AndroidInstrumentationInfoProvider.class), + AndroidInstrumentationInfoProvider::getTargetApk); + } + + /** + * The instrumentation APKs from each {@code android_instrumentation} in the {@code + * instrumentations} attribute. + */ + private static Iterable getInstrumentationApks(RuleContext ruleContext) { + return Iterables.transform( + ruleContext.getPrerequisites( + "instrumentations", + Mode.TARGET, + AndroidInstrumentationInfoProvider.ANDROID_INSTRUMENTATION_INFO.getKey(), + AndroidInstrumentationInfoProvider.class), + AndroidInstrumentationInfoProvider::getInstrumentationApk); + } + + /** The support APKs from the {@code support_apks} and {@code fixtures} attributes. */ + private static NestedSet getAllSupportApks(RuleContext ruleContext) { + NestedSetBuilder allSupportApks = + NestedSetBuilder.stableOrder() + .addTransitive(AndroidCommon.getSupportApks(ruleContext)); + for (AndroidDeviceScriptFixtureInfoProvider fixture : + ruleContext.getPrerequisites( + "fixtures", Mode.TARGET, AndroidDeviceScriptFixtureInfoProvider.class)) { + allSupportApks.addTransitive(fixture.getSupportApks()); + } + for (AndroidHostServiceFixtureInfoProvider fixture : + ruleContext.getPrerequisites( + "fixtures", + Mode.TARGET, + AndroidInstrumentationInfoProvider.ANDROID_INSTRUMENTATION_INFO.getKey(), + AndroidHostServiceFixtureInfoProvider.class)) { + allSupportApks.addTransitive(fixture.getSupportApks()); + } + return allSupportApks.build(); + } + + /** The deploy jar that interacts with the device. */ + private static FilesToRunProvider getTestEntryPoint(RuleContext ruleContext) { + return ruleContext.getExecutablePrerequisite("$test_entry_point", Mode.HOST); + } + + /** The {@code android_device} script to launch an emulator for the test. */ + private static FilesToRunProvider getTargetDevice(RuleContext ruleContext) { + return ruleContext.getExecutablePrerequisite("target_device", Mode.HOST); + } + + /** ADB binary from the Android SDK. */ + private static FilesToRunProvider getAdb(RuleContext ruleContext) { + return AndroidSdkProvider.fromRuleContext(ruleContext).getAdb(); + } + + /** Map of {@code test_args} for the test runner to make available to test test code. */ + private static ImmutableMap getTestArgs(RuleContext ruleContext) { + return ImmutableMap.copyOf(ruleContext.attributes().get("test_args", Type.STRING_DICT)); + } + + /** Map of {@code fixture_args} for the test runner to pass to the {@code fixtures}. */ + private static ImmutableMap getFixtureArgs(RuleContext ruleContext) { + return ImmutableMap.copyOf(ruleContext.attributes().get("fixture_args", Type.STRING_DICT)); + } + + /** Map of {@code log_levels} to enable before the test run. */ + private static ImmutableMap getLogLevels(RuleContext ruleContext) { + return ImmutableMap.copyOf(ruleContext.attributes().get("log_levels", Type.STRING_DICT)); + } + + private static ImmutableList getDataDeps(RuleContext ruleContext) { + return ruleContext.getPrerequisiteArtifacts("data", Mode.DATA).list(); + } + + /** + * Checks for a {@code android_host_service_fixture} in the {@code fixtures} attribute. Returns + * null if there is none, a {@link AndroidHostServiceFixtureInfoProvider} if there is one or + * throws an error if there is more than one. + */ + @Nullable + private static AndroidHostServiceFixtureInfoProvider getHostServiceFixture( + RuleContext ruleContext) { + ImmutableList hostServiceFixtures = + ImmutableList.copyOf( + ruleContext.getPrerequisites( + "fixtures", + Mode.TARGET, + AndroidHostServiceFixtureInfoProvider.ANDROID_HOST_SERVICE_FIXTURE_INFO.getKey(), + AndroidHostServiceFixtureInfoProvider.class)); + if (hostServiceFixtures.size() > 1) { + ruleContext.ruleError( + "android_instrumentation_test accepts at most one android_host_service_fixture"); + } + return Iterables.getFirst(hostServiceFixtures, null); + } + + private static Iterable getDeviceScriptFixtures( + RuleContext ruleContext) { + return ruleContext.getPrerequisites( + "fixtures", + Mode.TARGET, + AndroidDeviceScriptFixtureInfoProvider.ANDROID_DEVICE_SCRIPT_FIXTURE_INFO.getKey(), + AndroidDeviceScriptFixtureInfoProvider.class); + } + + private static String getDeviceBrokerType(RuleContext ruleContext) { + return ruleContext + .getPrerequisite("target_device", Mode.HOST, DeviceBrokerTypeProvider.class) + .getDeviceBrokerType(); + } + + /** + * Returns the name of the test suite property that the test runner uses to determine which test + * suite to run. + * + *

This is stored in a separate resource file to facilitate different runners for internal and + * external Bazel. + */ + private static String getTestSuitePropertyName(RuleContext ruleContext) + throws RuleErrorException { + try { + return ResourceFileLoader.loadResource( + AndroidInstrumentationTest.class, TEST_SUITE_PROPERTY_NAME_FILE); + } catch (IOException e) { + ruleContext.throwWithRuleError("Cannot load test suite property name: " + e.getMessage()); + return null; + } + } + + /** + * Propagates the {@link ExecutionInfoProvider} from the {@code android_device} rule in the {@code + * target_device} attribute. + * + *

This allows the dependent {@code android_device} rule to specify some requirements on the + * machine that the {@code android_instrumentation_test} runs on. + */ + private static ExecutionInfoProvider getExecutionInfoProvider(RuleContext ruleContext) { + ExecutionInfoProvider executionInfoProvider = + (ExecutionInfoProvider) + ruleContext.getPrerequisite( + "target_device", Mode.HOST, ExecutionInfoProvider.SKYLARK_CONSTRUCTOR.getKey()); + ImmutableMap executionRequirements = + (executionInfoProvider != null) + ? executionInfoProvider.getExecutionInfo() + : ImmutableMap.of(); + return new ExecutionInfoProvider(executionRequirements); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTestRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTestRule.java new file mode 100644 index 0000000000..bb65e2e797 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationTestRule.java @@ -0,0 +1,83 @@ +// 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. +package com.google.devtools.build.lib.rules.android; + +import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST; +import static com.google.devtools.build.lib.packages.Attribute.attr; +import static com.google.devtools.build.lib.packages.BuildType.LABEL; +import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; +import static com.google.devtools.build.lib.syntax.Type.STRING_DICT; + +import com.google.devtools.build.lib.analysis.BaseRuleClasses; +import com.google.devtools.build.lib.analysis.RuleDefinition; +import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleClass.Builder; +import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; +import com.google.devtools.build.lib.util.FileTypeSet; + +/** Rule definition for the {@code android_instrumentation_test} rule. */ +public class AndroidInstrumentationTestRule implements RuleDefinition { + + @Override + public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) { + return builder + .setUndocumented() + .add( + attr("instrumentations", LABEL_LIST) + .mandatory() + .allowedFileTypes(FileTypeSet.NO_FILE) + .allowedRuleClasses("android_instrumentation")) + .add( + attr("target_device", LABEL) + .mandatory() + .exec() + .cfg(HOST) + .allowedFileTypes(FileTypeSet.NO_FILE) + .allowedRuleClasses("android_device")) + .add( + attr("support_apks", LABEL_LIST) + .allowedFileTypes(AndroidRuleClasses.APK) + .allowedRuleClasses("android_binary")) + .add(attr("test_args", STRING_DICT)) + .add( + attr("fixtures", LABEL_LIST) + .allowedFileTypes(FileTypeSet.NO_FILE) + .allowedRuleClasses( + "android_device_script_fixture", "android_host_service_fixture")) + .add(attr("fixture_args", STRING_DICT)) + .add(attr("log_levels", STRING_DICT)) + .add( + attr("$test_entry_point", LABEL) + .exec() + .cfg(HOST) + .value( + environment.getToolsLabel("//tools/android:instrumentation_test_entry_point"))) + .removeAttribute("deps") + .removeAttribute("javacopts") + .removeAttribute("plugins") + .removeAttribute(":java_plugins") + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("android_instrumentation_test") + .type(RuleClassType.TEST) + .ancestors(AndroidRuleClasses.AndroidBaseRule.class, BaseRuleClasses.TestBaseRule.class) + .factoryClass(AndroidInstrumentationTest.class) + .build(); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/android_instrumentation_test_template.txt b/src/main/java/com/google/devtools/build/lib/rules/android/android_instrumentation_test_template.txt new file mode 100644 index 0000000000..c4af683ddb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/android_instrumentation_test_template.txt @@ -0,0 +1,88 @@ +#!/bin/bash --posix +# 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. + +set -eux + +# Unset TESTBRIDGE_TEST_ONLY environment variable set by Bazel's --test_filter +# flag so that JUnit3 doesn't filter out the Android test suite class. Instead, +# forward this variable as a Java flag with the same name. +if [ -z "${TESTBRIDGE_TEST_ONLY+1}" ]; then + ANDROID_TESTBRIDGE_TEST_ONLY="" +else + ANDROID_TESTBRIDGE_TEST_ONLY=${TESTBRIDGE_TEST_ONLY} + unset TESTBRIDGE_TEST_ONLY +fi + +function join_paths() { + local base_dir=$1 + local sep=$2 + shift 2 + local paths=$@ + + local result="" + for path in $paths + do + result=${base_dir}/${path}${sep}${result} + done + echo ${result} +} + +test_label="%test_label%" +test_entry_point="%test_entry_point%" +log_levels="%log_levels%" +WORKSPACE_DIR="${TEST_SRCDIR}/%workspace%" +adb="${WORKSPACE_DIR}/%adb%" +device_script="${WORKSPACE_DIR}/%device_script%" + +data_deps="%data_deps%" +data_deps=$(join_paths ${WORKSPACE_DIR} "," ${data_deps}) + +device_broker_type="%device_broker_type%" +test_label="%test_label%" + +target_apks="%target_apks%" +target_apks=$(join_paths ${WORKSPACE_DIR} "," ${target_apks}) + +instrumentation_apks="%instrumentation_apks%" +instrumentation_apks=$(join_paths ${WORKSPACE_DIR} "," ${instrumentation_apks}) + +support_apks="%support_apks%" +support_apks=$(join_paths ${WORKSPACE_DIR} "," ${support_apks}) + +apks_to_install="${support_apks}${target_apks}${instrumentation_apks}" + +declare -A device_script_fixtures=( %device_script_fixtures% ) + +host_service_fixture="%host_service_fixture%" +host_service_fixture_services="%host_service_fixture_services%" + +fixture_args="%fixture_args%" + +test_suite_property_name='%test_suite_property_name%' + +$test_entry_point \ + --wrapper_script_flag=--jvm_flag=-D$test_suite_property_name=com.google.android.apps.common.testing.suite.AndroidDeviceTestSuite \ + --adb="${adb}" \ + --device_broker_type="${device_broker_type}" \ + --device_script="${device_script}" \ + --data_deps="${data_deps}" \ + --test_label="${test_label}" \ + --apks_to_install="${apks_to_install}" \ + --fixture_scripts="$(printf "%s," "${!device_script_fixtures[@]}")" \ + --hermetic_server_script="${host_service_fixture}" \ + --hermetic_servers="${host_service_fixture_services}" \ + --data_deps="$(printf "%s," "${!device_script_fixtures[@]}")" \ + --test_filter="${ANDROID_TESTBRIDGE_TEST_ONLY}" \ + "$@" diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/test_suite_property_name.txt b/src/main/java/com/google/devtools/build/lib/rules/android/test_suite_property_name.txt new file mode 100644 index 0000000000..650406adfe --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/test_suite_property_name.txt @@ -0,0 +1 @@ +bazel.test_suite -- cgit v1.2.3