Skip to content

Commit

Permalink
Add android_local_test rule to Bazel.
Browse files Browse the repository at this point in the history
This rule enables testing android_librarys locally in the jvm (as opposed to on a device). To use this rule with robolectric (robolectric.org), add the following to your WORKSPACE file:

http_archive(
  name = "bazel_android",
  url = "...",
)
load("@bazel_android//:setup_robolectric.bzl", "setup_robolectric")
setup_robolectric()

and then an android_local_test rule would need to add:
"@bazel_android//:robolectric",
to its dependencies.

android_local_test(
    name = "MyTest",
    srcs = ["MyTest.java"],
    deps = [
        "//java/app:lib",
        "@bazel_android//:robolectric",
    ],
)

RELNOTES[NEW]: New android test rule, android_local_test.

PiperOrigin-RevId: 180438995
  • Loading branch information
dkelmer authored and Copybara-Service committed Dec 31, 2017
1 parent 192583d commit 2ef5d21
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 41 deletions.
1 change: 1 addition & 0 deletions src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,7 @@ java_library(
"rules/android/android_device_stub_template.txt",
"rules/android/android_instrumentation_test_template.txt",
"rules/android/databinding_annotation_template.txt",
"rules/android/robolectric_properties_template.txt",
"rules/android/test_suite_property_name.txt",
],
deps = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.devtools.build.lib.bazel.rules.android.BazelAarImportRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidBinaryRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidLibraryRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidLocalTestRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidSemantics;
import com.google.devtools.build.lib.bazel.rules.common.BazelFilegroupRule;
import com.google.devtools.build.lib.bazel.rules.cpp.BazelCcBinaryRule;
Expand Down Expand Up @@ -485,6 +486,7 @@ public void init(Builder builder) {
builder.addRuleDefinition(new BazelAarImportRule());
builder.addRuleDefinition(new AndroidDeviceRule());
builder.addRuleDefinition(new AndroidLocalTestBaseRule());
builder.addRuleDefinition(new BazelAndroidLocalTestRule());

builder.addSkylarkAccessibleTopLevels("android_common", new AndroidSkylarkCommon());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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.bazel.rules.android;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
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.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.bazel.rules.java.BazelJavaSemantics;
import com.google.devtools.build.lib.rules.android.AndroidLocalTestBase;
import com.google.devtools.build.lib.rules.android.AndroidSdkProvider;
import com.google.devtools.build.lib.rules.android.AndroidSemantics;
import com.google.devtools.build.lib.rules.java.JavaCommon;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgs.ClasspathType;
import com.google.devtools.build.lib.rules.java.JavaCompilationArtifacts.Builder;
import com.google.devtools.build.lib.rules.java.JavaCompilationHelper;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
import com.google.devtools.build.lib.util.ShellEscaper;

/** An implementation for the "android_local_test" rule. */
public class BazelAndroidLocalTest extends AndroidLocalTestBase {

Artifact androidAllJarsPropFile;

@Override
protected AndroidSemantics createAndroidSemantics() {
return BazelAndroidSemantics.INSTANCE;
}

@Override
protected JavaSemantics createJavaSemantics() {
return BazelJavaSemantics.INSTANCE;
}

@Override
protected ImmutableList<String> getJvmFlags(RuleContext ruleContext, String testClass)
throws RuleErrorException {
Artifact androidAllJarsPropertiesFile = getAndroidAllJarsPropertiesFile(ruleContext);

return ImmutableList.<String>builder()
.addAll(JavaCommon.getJvmFlags(ruleContext))
.add("-ea")
.add("-Dbazel.test_suite=" + ShellEscaper.escapeString(testClass))
.add("-Drobolectric.offline=true")
.add(
"-Drobolectric-deps.properties=" + androidAllJarsPropertiesFile.getRunfilesPathString())
.build();
}

@Override
protected String getMainClass(
RuleContext ruleContext,
JavaSemantics javaSemantics,
JavaCompilationHelper helper,
Artifact executable,
Artifact instrumentationMetadata,
Builder javaArtifactsBuilder,
JavaTargetAttributes.Builder attributesBuilder)
throws InterruptedException, RuleErrorException {
// coverage does not yet work with android_local_test
if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
ruleContext.throwWithRuleError("android_local_test does not yet support coverage");
}
return "com.google.testing.junit.runner.BazelTestRunner";
}

@Override
protected JavaCompilationHelper getJavaCompilationHelperWithDependencies(
RuleContext ruleContext,
JavaSemantics javaSemantics,
JavaCommon javaCommon,
JavaTargetAttributes.Builder javaTargetAttributesBuilder) {

JavaCompilationHelper javaCompilationHelper =
new JavaCompilationHelper(
ruleContext, javaSemantics, javaCommon.getJavacOpts(), javaTargetAttributesBuilder);
javaCompilationHelper.addLibrariesToAttributes(
ImmutableList.copyOf(javaCommon.targetsTreatedAsDeps(ClasspathType.COMPILE_ONLY)));

javaCompilationHelper.addLibrariesToAttributes(
ImmutableList.of(getAndCheckTestSupport(ruleContext)));

javaTargetAttributesBuilder.setBootClassPath(
ImmutableList.of(AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar()));
javaTargetAttributesBuilder.addRuntimeClassPathEntry(
AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar());

return javaCompilationHelper;
}

@Override
protected TransitiveInfoCollection getAndCheckTestSupport(RuleContext ruleContext) {
// Add the unit test support to the list of dependencies.
return Iterables.getOnlyElement(ruleContext.getPrerequisites("$testsupport", Mode.TARGET));
}

@Override
// Bazel needs the android-all jars properties file in order for robolectric to
// run. If it does not find it in the deps of the android_local_test rule, it will
// throw an error.
protected Artifact getAndroidAllJarsPropertiesFile(RuleContext ruleContext)
throws RuleErrorException {
if (androidAllJarsPropFile == null) {
androidAllJarsPropFile = getAndroidAllJarsPropertiesFileHelper(ruleContext);
}
return androidAllJarsPropFile;
}

private Artifact getAndroidAllJarsPropertiesFileHelper(RuleContext ruleContext)
throws RuleErrorException {
Iterable<RunfilesProvider> runfilesProviders =
ruleContext.getPrerequisites("deps", Mode.TARGET, RunfilesProvider.class);
for (RunfilesProvider runfilesProvider : runfilesProviders) {
Runfiles dataRunfiles = runfilesProvider.getDataRunfiles();
for (Artifact artifact : dataRunfiles.getAllArtifacts()) {
if (artifact.getFilename().equals("robolectric-deps.properties")) {
return artifact;
}
}
}
ruleContext.throwWithRuleError(
"'robolectric-deps.properties' not found in" + " the deps of the rule.");
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.bazel.rules.android;

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.packages.BuildType.TRISTATE;
import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fromFunctions;

import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
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.bazel.rules.java.BazelJavaRuleClasses.BaseJavaBinaryRule;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
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.packages.SkylarkProviderIdentifier;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.rules.android.AndroidFeatureFlagSetProvider;
import com.google.devtools.build.lib.rules.android.AndroidLocalTestBaseRule;
import com.google.devtools.build.lib.rules.config.ConfigFeatureFlagTransitionFactory;
import com.google.devtools.build.lib.rules.java.JavaConfiguration;
import com.google.devtools.build.lib.rules.java.JavaInfo;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
import com.google.devtools.build.lib.rules.java.Jvm;

/** Rule definition for Bazel android_local_test */
public class BazelAndroidLocalTestRule implements RuleDefinition {

protected static final String JUNIT_TESTRUNNER = "//tools/jdk:TestRunner_deploy.jar";

private static final ImmutableCollection<String> ALLOWED_RULES_IN_DEPS =
ImmutableSet.of(
"aar_import",
"android_library",
"java_import",
"java_library",
"java_lite_proto_library");

static final ImplicitOutputsFunction ANDROID_ROBOLECTRIC_IMPLICIT_OUTPUTS =
fromFunctions(JavaSemantics.JAVA_BINARY_CLASS_JAR, JavaSemantics.JAVA_BINARY_SOURCE_JAR);

@Override
public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
return builder
.requiresConfigurationFragments(JavaConfiguration.class, Jvm.class)
.setImplicitOutputsFunction(ANDROID_ROBOLECTRIC_IMPLICIT_OUTPUTS)
.override(
attr("deps", LABEL_LIST)
.allowedFileTypes()
.allowedRuleClasses(ALLOWED_RULES_IN_DEPS)
.mandatoryProvidersList(
ImmutableList.of(
ImmutableList.of(
SkylarkProviderIdentifier.forKey(JavaInfo.PROVIDER.getKey())))))
.override(attr("$testsupport", LABEL).value(environment.getToolsLabel(JUNIT_TESTRUNNER)))
.override(attr("stamp", TRISTATE).value(TriState.NO))
.removeAttribute("$experimental_testsupport")
.removeAttribute("classpath_resources")
.removeAttribute("create_executable")
.removeAttribute("deploy_manifest_lines")
.removeAttribute("distribs")
.removeAttribute("launcher")
.removeAttribute("main_class")
.removeAttribute("resources")
.removeAttribute("use_testrunner")
.removeAttribute(":java_launcher")
.cfg(
new ConfigFeatureFlagTransitionFactory(AndroidFeatureFlagSetProvider.FEATURE_FLAG_ATTR))
.build();
}

@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
.name("android_local_test")
.type(RuleClassType.TEST)
.ancestors(
AndroidLocalTestBaseRule.class,
BaseJavaBinaryRule.class,
BaseRuleClasses.TestBaseRule.class)
.factoryClass(BazelAndroidLocalTest.class)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;

/** A base implementation for the "android_local_test" rule. */
public abstract class AndroidLocalTestBase implements RuleConfiguredTargetFactory {
Expand All @@ -82,8 +83,7 @@ public ConfiguredTarget create(RuleContext ruleContext)
// Use the regular Java javacopts. Enforcing android-compatible Java
// (-source 7 -target 7 and no TWR) is unnecessary for robolectric tests
// since they run on a JVM, not an android device.
JavaTargetAttributes.Builder attributesBuilder =
getJavaTargetAttributes(ruleContext, javaCommon);
JavaTargetAttributes.Builder attributesBuilder = javaCommon.initCommon();

// Create the final merged manifest
ResourceDependencies resourceDependencies =
Expand Down Expand Up @@ -351,7 +351,6 @@ public ConfiguredTarget create(RuleContext ruleContext)
private void compileResourceJar(
RuleContext ruleContext, ResourceApk resourceApk, Artifact resourceClassJar)
throws InterruptedException, RuleErrorException {

if (resourceApk.getResourceJavaClassJar() == null) {
new RClassGeneratorActionBuilder(ruleContext)
.targetAaptVersion(AndroidAaptVersion.chooseTargetAaptVersion(ruleContext))
Expand Down Expand Up @@ -435,6 +434,12 @@ private Runfiles collectDefaultRunfiles(
if (ruleContext.isAttrDefined("$robolectric", LABEL_LIST)) {
depsForRunfiles.addAll(ruleContext.getPrerequisites("$robolectric", Mode.TARGET));
}

Artifact androidAllJarsPropertiesFile = getAndroidAllJarsPropertiesFile(ruleContext);
if (androidAllJarsPropertiesFile != null) {
builder.addArtifact(androidAllJarsPropertiesFile);
}

depsForRunfiles.addAll(ruleContext.getPrerequisites("runtime_deps", Mode.TARGET));

builder.addArtifacts(getRuntimeJarsForTargets(getAndCheckTestSupport(ruleContext)));
Expand Down Expand Up @@ -520,12 +525,9 @@ private static NestedSet<Artifact> getLibraryResourceJars(RuleContext ruleContex
/** Get AndroidSemantics */
protected abstract AndroidSemantics createAndroidSemantics();

/** Get JavaTargetAttributes Builder */
protected abstract JavaTargetAttributes.Builder getJavaTargetAttributes(
RuleContext ruleContext, JavaCommon javaCommon);

/** Set test and robolectric specific jvm flags */
protected abstract ImmutableList<String> getJvmFlags(RuleContext ruleContext, String testClass);
protected abstract ImmutableList<String> getJvmFlags(RuleContext ruleContext, String testClass)
throws RuleErrorException;

/** Return the testrunner main class */
protected abstract String getMainClass(
Expand All @@ -536,7 +538,7 @@ protected abstract String getMainClass(
Artifact instrumentationMetadata,
JavaCompilationArtifacts.Builder javaArtifactsBuilder,
JavaTargetAttributes.Builder attributesBuilder)
throws InterruptedException;
throws InterruptedException, RuleErrorException;

/**
* Add compilation dependencies to the java compilation helper.
Expand All @@ -553,4 +555,9 @@ protected abstract JavaCompilationHelper getJavaCompilationHelperWithDependencie
/** Get the testrunner from the rule */
protected abstract TransitiveInfoCollection getAndCheckTestSupport(RuleContext ruleContext)
throws RuleErrorException;

/** Get the android-all jars properties file from the deps */
@Nullable
protected abstract Artifact getAndroidAllJarsPropertiesFile(RuleContext ruleContext)
throws RuleErrorException;
}
1 change: 1 addition & 0 deletions src/test/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@ java_test(
"//src/main/protobuf:crosstool_config_java_proto",
"//src/test/java/com/google/devtools/build/lib:actions_testutil",
"//src/test/java/com/google/devtools/build/lib:packages_testutil",
"//src/test/java/com/google/devtools/build/lib/rules/android:AndroidLocalTestTest",
"//third_party:guava",
"//third_party:junit4",
"//third_party:truth",
Expand Down
Loading

0 comments on commit 2ef5d21

Please sign in to comment.