blob: 7173b981ccad43c7c3ecc6d38569fe51a1d7a90d [file] [log] [blame]
// 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.common.truth.Truth.assertThat;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Abstract base class for tests of {@link BazelAndroidLocalTest}. These tests are for the java
* related portions of the rule. Be sure to use writeFile() and overwriteFile() (instead of
* scratch.writeFile() and scratch.overwriteFile()).
*/
@RunWith(JUnit4.class)
public abstract class AbstractAndroidLocalTestTestBase extends AndroidBuildViewTestCase {
@Before
public void setUp() throws Exception {
writeFile("java/bar/BUILD", "");
writeFile("java/bar/foo.bzl", "extra_deps = []");
}
@Test
public void testSimpleAndroidRobolectricConfiguredTarget() throws Exception {
writeFile(
"java/test/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"android_local_test(name = 'dummyTest',",
" srcs = ['test.java'],",
" deps = extra_deps)");
ConfiguredTarget target = getConfiguredTarget("//java/test:dummyTest");
assertThat(target).isNotNull();
checkMainClass(target, "dummyTest", false);
}
@Test
public void testDataDependency() throws Exception {
writeFile(
"java/test/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"android_local_test(name = 'dummyTest',",
" srcs = ['test.java'],",
" deps = extra_deps,",
" data = ['data.dat'])");
writeFile("java/test/data.dat", "this is a dummy data file");
ConfiguredTarget target = getConfiguredTarget("//java/test:dummyTest");
Artifact data = getFileConfiguredTarget("//java/test:data.dat").getArtifact();
RunfilesProvider runfiles = target.getProvider(RunfilesProvider.class);
assertThat(runfiles.getDataRunfiles().getAllArtifacts().toSet()).contains(data);
assertThat(runfiles.getDefaultRunfiles().getAllArtifacts().toSet()).contains(data);
assertThat(target).isNotNull();
}
@Test
public void testNeverlinkRuntimeDepsExclusionReportsError() throws Exception {
useConfiguration("--noexperimental_allow_runtime_deps_on_neverlink");
checkError(
"java/test",
"test",
"neverlink dep //java/test:neverlink_lib not allowed in runtime deps",
String.format("%s(name = 'test',", getRuleName()),
" srcs = ['test.java'],",
" runtime_deps = [':neverlink_lib'])",
"android_library(name = 'neverlink_lib',",
" srcs = ['dummyNeverlink.java'],",
" neverlink = 1,)");
}
@Test
public void testFeatureFlagsAttributeSetsSelectInDependency() throws Exception {
useConfiguration(
"--experimental_dynamic_configs=on",
"--enforce_transitive_configs_for_config_feature_flag");
writeFile(
"java/com/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"config_feature_flag(",
" name = 'flag1',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"config_setting(",
" name = 'flag1@on',",
" flag_values = {':flag1': 'on'},",
" transitive_configs = [':flag1'],",
")",
"config_feature_flag(",
" name = 'flag2',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"config_setting(",
" name = 'flag2@on',",
" flag_values = {':flag2': 'on'},",
" transitive_configs = [':flag2'],",
")",
"android_library(",
" name = 'lib',",
" srcs = select({",
" ':flag1@on': ['Flag1On.java'],",
" '//conditions:default': ['Flag1Off.java'],",
" }) + select({",
" ':flag2@on': ['Flag2On.java'],",
" '//conditions:default': ['Flag2Off.java'],",
" }),",
" transitive_configs = [':flag1', ':flag2'],",
")",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java'],",
" deps = [':lib'] + extra_deps,",
" feature_flags = {",
" 'flag1': 'on',",
" },",
" transitive_configs = [':flag1', ':flag2'],",
")");
ConfiguredTarget binary = getConfiguredTarget("//java/com/foo");
List<String> inputs =
ActionsTestUtil.prettyArtifactNames(
actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)));
assertThat(inputs).containsAtLeast("java/com/foo/Flag1On.java", "java/com/foo/Flag2Off.java");
assertThat(inputs).containsNoneOf("java/com/foo/Flag1Off.java", "java/com/foo/Flag2On.java");
}
@Test
public void testFeatureFlagsAttributeSetsSelectInTest() throws Exception {
useConfiguration(
"--experimental_dynamic_configs=on",
"--enforce_transitive_configs_for_config_feature_flag");
writeFile(
"java/com/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"config_feature_flag(",
" name = 'flag1',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"config_setting(",
" name = 'flag1@on',",
" flag_values = {':flag1': 'on'},",
" transitive_configs = [':flag1'],",
")",
"config_feature_flag(",
" name = 'flag2',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"config_setting(",
" name = 'flag2@on',",
" flag_values = {':flag2': 'on'},",
" transitive_configs = [':flag2'],",
")",
"android_local_test(",
" name = 'foo',",
" deps = extra_deps,",
" srcs = ['Test.java'] + select({",
" ':flag1@on': ['Flag1On.java'],",
" '//conditions:default': ['Flag1Off.java'],",
" }) + select({",
" ':flag2@on': ['Flag2On.java'],",
" '//conditions:default': ['Flag2Off.java'],",
" }),",
" feature_flags = {",
" 'flag1': 'on',",
" },",
" transitive_configs = [':flag1', ':flag2'],",
")");
ConfiguredTarget binary = getConfiguredTarget("//java/com/foo");
List<String> inputs =
actionsTestUtil()
.prettyArtifactNames(actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)));
assertThat(inputs).containsAtLeast("java/com/foo/Flag1On.java", "java/com/foo/Flag2Off.java");
assertThat(inputs).containsNoneOf("java/com/foo/Flag1Off.java", "java/com/foo/Flag2On.java");
}
@Test
public void testFeatureFlagsAttributeFailsAnalysisIfFlagValueIsInvalid() throws Exception {
reporter.removeHandler(failFastHandler);
useConfiguration(
"--experimental_dynamic_configs=on",
"--enforce_transitive_configs_for_config_feature_flag");
writeFile(
"java/com/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"config_feature_flag(",
" name = 'flag1',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"config_setting(",
" name = 'flag1@on',",
" flag_values = {':flag1': 'on'},",
" transitive_configs = [':flag1'],",
")",
"android_library(",
" name = 'lib',",
" srcs = select({",
" ':flag1@on': ['Flag1On.java'],",
" '//conditions:default': ['Flag1Off.java'],",
" }),",
" transitive_configs = [':flag1'],",
")",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java'],",
" deps = [':lib',] + extra_deps,",
" feature_flags = {",
" 'flag1': 'invalid',",
" },",
" transitive_configs = [':flag1'],",
")");
assertThat(getConfiguredTarget("//java/com/foo")).isNull();
assertContainsEvent(
"in config_feature_flag rule //java/com/foo:flag1: "
+ "value must be one of [\"off\", \"on\"], but was \"invalid\"");
}
@Test
public void testFeatureFlagsAttributeFailsAnalysisIfFlagValueIsInvalidEvenIfNotUsed()
throws Exception {
reporter.removeHandler(failFastHandler);
useConfiguration(
"--experimental_dynamic_configs=on",
"--enforce_transitive_configs_for_config_feature_flag");
writeFile(
"java/com/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"config_feature_flag(",
" name = 'flag1',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"config_setting(",
" name = 'flag1@on',",
" flag_values = {':flag1': 'on'},",
")",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java'],",
" deps = extra_deps,",
" feature_flags = {",
" 'flag1': 'invalid',",
" }",
")");
assertThat(getConfiguredTarget("//java/com/foo")).isNull();
assertContainsEvent(
"in config_feature_flag rule //java/com/foo:flag1: "
+ "value must be one of [\"off\", \"on\"], but was \"invalid\"");
}
@Test
public void testFeatureFlagsAttributeSetsFeatureFlagProviderValues() throws Exception {
useConfiguration(
"--experimental_dynamic_configs=on",
"--enforce_transitive_configs_for_config_feature_flag");
writeFile(
"java/com/foo/reader.bzl",
"def _impl(ctx):",
" ctx.actions.write(",
" ctx.outputs.java,",
" '\\n'.join([",
" str(target.label) + ': ' + target[config_common.FeatureFlagInfo].value",
" for target in ctx.attr.flags]))",
" return struct(files=depset([ctx.outputs.java]))",
"flag_reader = rule(",
" implementation=_impl,",
" attrs={'flags': attr.label_list(providers=[config_common.FeatureFlagInfo])},",
" outputs={'java': '%{name}.java'},",
")");
writeFile(
"java/com/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"load('//java/com/foo:reader.bzl', 'flag_reader')",
"config_feature_flag(",
" name = 'flag1',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"config_feature_flag(",
" name = 'flag2',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"flag_reader(",
" name = 'FooFlags',",
" flags = [':flag1', ':flag2'],",
" transitive_configs = [':flag1', ':flag2'],",
")",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java', ':FooFlags.java'],",
" deps = extra_deps,",
" feature_flags = {",
" 'flag1': 'on',",
" },",
" transitive_configs = [':flag1', ':flag2'],",
")");
Artifact flagList =
ActionsTestUtil.getFirstArtifactEndingWith(
actionsTestUtil()
.artifactClosureOf(getFilesToBuild(getConfiguredTarget("//java/com/foo"))),
"/FooFlags.java");
FileWriteAction action = (FileWriteAction) getGeneratingAction(flagList);
assertThat(action.getFileContents())
.isEqualTo("//java/com/foo:flag1: on\n//java/com/foo:flag2: off");
}
@Test
public void testFeatureFlagsAttributeFailsAnalysisIfFlagIsAliased() throws Exception {
reporter.removeHandler(failFastHandler);
useConfiguration(
"--experimental_dynamic_configs=on",
"--enforce_transitive_configs_for_config_feature_flag");
writeFile(
"java/com/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"config_feature_flag(",
" name = 'flag1',",
" allowed_values = ['on', 'off'],",
" default_value = 'off',",
")",
"alias(",
" name = 'alias',",
" actual = 'flag1',",
" transitive_configs = [':flag1'],",
")",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java'],",
" deps = extra_deps,",
" feature_flags = {",
" 'alias': 'on',",
" },",
" transitive_configs = [':flag1'],",
")");
assertThat(getConfiguredTarget("//java/com/foo")).isNull();
assertContainsEvent(
String.format(
"in feature_flags attribute of %s rule //java/com/foo:foo: "
+ "Feature flags must be named directly, not through aliases; "
+ "use '//java/com/foo:flag1', not '//java/com/foo:alias'",
getRuleName()));
}
@Test
public void testFeatureFlagPolicyMustBeVisibleToRuleToUseFeatureFlags() throws Exception {
reporter.removeHandler(failFastHandler); // expecting an error
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
overwriteFile(
"tools/whitelists/config_feature_flag/BUILD",
"package_group(",
" name = 'config_feature_flag',",
" packages = ['//flag'])");
writeFile(
"flag/BUILD",
"config_feature_flag(",
" name = 'flag',",
" allowed_values = ['right', 'wrong'],",
" default_value = 'right',",
" visibility = ['//java/com/google/android/foo:__pkg__'],",
")");
writeFile(
"java/com/google/android/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java', ':FooFlags.java'],",
" deps = extra_deps,",
" feature_flags = {",
" '//flag:flag': 'right',",
" },",
" transitive_configs = ['//flag:flag'],",
")");
assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNull();
assertContainsEvent(
String.format(
"in feature_flags attribute of %s rule //java/com/google/android/foo:foo: the"
+ " feature_flags attribute is not available in package"
+ " 'java/com/google/android/foo'",
getRuleName()));
}
@Test
public void testFeatureFlagPolicyDoesNotBlockRuleIfInPolicy() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
overwriteFile(
"tools/whitelists/config_feature_flag/BUILD",
"package_group(",
" name = 'config_feature_flag',",
" packages = ['//flag', '//java/com/google/android/foo'])");
writeFile(
"flag/BUILD",
"config_feature_flag(",
" name = 'flag',",
" allowed_values = ['right', 'wrong'],",
" default_value = 'right',",
" visibility = ['//java/com/google/android/foo:__pkg__'],",
")");
writeFile(
"java/com/google/android/foo/BUILD",
"load('//java/bar:foo.bzl', 'extra_deps')",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java', ':FooFlags.java'],",
" deps = extra_deps,",
" feature_flags = {",
" '//flag:flag': 'right',",
" },",
" transitive_configs = ['//flag:flag'],",
")");
assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNotNull();
assertNoEvents();
}
@Test
public void testFeatureFlagPolicyIsNotUsedIfFlagValuesNotUsed() throws Exception {
overwriteFile(
"tools/whitelists/config_feature_flag/BUILD",
"package_group(",
" name = 'config_feature_flag',",
" packages = ['*super* busted package group'])");
writeFile(
"java/com/google/android/foo/BUILD",
"android_local_test(",
" name = 'foo',",
" srcs = ['Test.java', ':FooFlags.java'],",
")");
assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNotNull();
// the package_group is busted, so we would have failed to get this far if we depended on it
assertNoEvents();
// sanity check time: does this test actually test what we're testing for?
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//tools/whitelists/config_feature_flag:config_feature_flag"))
.isNull();
assertContainsEvent("*super* busted package group");
}
@Test
public void androidManifestMergerOrderAlphabeticalByConfiguration_MergeesSortedByPathInBinOrGen()
throws Exception {
useConfiguration(
"--fat_apk_cpu=k8", "--android_manifest_merger_order=alphabetical_by_configuration");
scratch.overwriteFile(
"java/android/BUILD",
"android_library(",
" name = 'core',",
" manifest = 'core/AndroidManifest.xml',",
" exports_manifest = 1,",
" resource_files = ['core/res/values/strings.xml'],",
")",
"android_library(",
" name = 'utility',",
" manifest = 'utility/AndroidManifest.xml',",
" exports_manifest = 1,",
" resource_files = ['utility/res/values/values.xml'],",
" deps = ['//java/common:common'],",
")");
scratch.file(
"java/binary/BUILD",
"android_binary(",
" name = 'application',",
" srcs = ['App.java'],",
" manifest = 'app/AndroidManifest.xml',",
" deps = [':library'],",
")",
"android_library(",
" name = 'library',",
" manifest = 'library/AndroidManifest.xml',",
" exports_manifest = 1,",
" deps = ['//java/common:theme', '//java/android:utility'],",
")");
scratch.file(
"java/test/BUILD",
"android_local_test(",
" name = 'test',",
" srcs = ['Test.java'],",
" manifest = 'testing/AndroidManifest.xml',",
" deps = ['//java/binary:application', '//java/binary:library'],",
")");
scratch.file(
"java/common/BUILD",
"android_library(",
" name = 'common',",
" manifest = 'common/AndroidManifest.xml',",
" exports_manifest = 1,",
" resource_files = ['common/res/values/common.xml'],",
" deps = ['//java/android:core'],",
")",
"android_library(",
" name = 'theme',",
" manifest = 'theme/AndroidManifest.xml',",
" exports_manifest = 1,",
" resource_files = ['theme/res/values/values.xml'],",
")");
ConfiguredTarget test = getConfiguredTarget("//java/test:test");
assertNoEvents();
assertThat(test).isNotNull();
// each entry appears twice because the application duplicates them all into another config;
// this is partially testing that doing so does not cause a crash because of matching
// root-relative paths.
assertThat(getLocalTestMergeeManifests(test).values())
.containsExactly(
"//java/android:core",
"//java/android:core",
"//java/android:utility",
"//java/android:utility",
"//java/binary:library",
"//java/binary:library",
"//java/common:common",
"//java/common:common",
"//java/common:theme",
"//java/common:theme")
.inOrder();
}
@Test
public void testDeployJar() throws Exception {
writeFile(
"java/com/google/android/foo/BUILD",
"android_local_test(name = 'test',",
" srcs =['test.java'],",
" )",
"android_library(name = 'lib',",
" data = [':test_deploy.jar'],",
" srcs =['lib.java'],",
" )");
Action deployJarAction =
getGeneratingAction(
getFileConfiguredTarget("//java/com/google/android/foo:test_deploy.jar").getArtifact());
List<String> inputs = ActionsTestUtil.baseArtifactNames(deployJarAction.getInputs());
assertThat(inputs).containsAtLeast("test_resources.jar", "test.jar");
}
public abstract void checkMainClass(
ConfiguredTarget target, String targetName, boolean coverageEnabled) throws Exception;
protected abstract String getRuleName();
protected abstract void writeFile(String path, String... lines) throws Exception;
protected abstract void overwriteFile(String path, String... lines) throws Exception;
}