| // 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"], |
| data = ["data.dat"], |
| deps = extra_deps, |
| ) |
| """); |
| |
| 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("--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"], |
| feature_flags = { |
| "flag1": "on", |
| }, |
| transitive_configs = [ |
| ":flag1", |
| ":flag2", |
| ], |
| deps = [":lib"] + extra_deps, |
| ) |
| """); |
| 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("--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", |
| 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", |
| ], |
| deps = extra_deps, |
| ) |
| """); |
| 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("--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"], |
| feature_flags = { |
| "flag1": "invalid", |
| }, |
| transitive_configs = [":flag1"], |
| deps = [":lib"] + extra_deps, |
| ) |
| """); |
| 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("--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"], |
| feature_flags = { |
| "flag1": "invalid", |
| }, |
| deps = extra_deps, |
| ) |
| """); |
| 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("--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", |
| ], |
| feature_flags = { |
| "flag1": "on", |
| }, |
| transitive_configs = [ |
| ":flag1", |
| ":flag2", |
| ], |
| deps = extra_deps, |
| ) |
| """); |
| Artifact flagList = |
| ActionsTestUtil.getFirstArtifactEndingWith( |
| actionsTestUtil() |
| .artifactClosureOf(getFilesToBuild(getConfiguredTarget("//java/com/foo"))), |
| "/FooFlags.java"); |
| FileWriteAction action = (FileWriteAction) getGeneratingAction(flagList); |
| assertThat(action.getFileContents()) |
| .isAnyOf( |
| "//java/com/foo:flag1: on\n//java/com/foo:flag2: off", |
| "@@//java/com/foo:flag1: on\n@@//java/com/foo:flag2: off"); |
| } |
| |
| @Test |
| public void testFeatureFlagsAttributeFailsAnalysisIfFlagIsAliased() throws Exception { |
| reporter.removeHandler(failFastHandler); |
| useConfiguration("--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"], |
| feature_flags = { |
| "alias": "on", |
| }, |
| transitive_configs = [":flag1"], |
| deps = extra_deps, |
| ) |
| """); |
| 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/allowlists/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", |
| ], |
| feature_flags = { |
| "//flag:flag": "right", |
| }, |
| transitive_configs = ["//flag:flag"], |
| deps = extra_deps, |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNull(); |
| assertContainsEvent( |
| String.format( |
| "in %s rule //java/com/google/android/foo:foo:" |
| + " the attribute feature_flags is not available in this package", |
| getRuleName())); |
| } |
| |
| @Test |
| public void testFeatureFlagPolicyDoesNotBlockRuleIfInPolicy() throws Exception { |
| useConfiguration("--enforce_transitive_configs_for_config_feature_flag"); |
| overwriteFile( |
| "tools/allowlists/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", |
| ], |
| feature_flags = { |
| "//flag:flag": "right", |
| }, |
| transitive_configs = ["//flag:flag"], |
| deps = extra_deps, |
| ) |
| """); |
| assertThat(getConfiguredTarget("//java/com/google/android/foo:foo")).isNotNull(); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void testFeatureFlagPolicyIsNotUsedIfFlagValuesNotUsed() throws Exception { |
| overwriteFile( |
| "tools/allowlists/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(); |
| // NOTE: someone should verify that this test actually does what we think it does. |
| reporter.removeHandler(failFastHandler); |
| assertThat(getConfiguredTarget("//tools/allowlists/config_feature_flag:config_feature_flag")) |
| .isNull(); |
| assertContainsEvent("*super* busted package group"); |
| } |
| |
| @Test |
| public void androidManifestMergerOrderAlphabeticalByConfiguration_MergeesSortedByPathInBinOrGen() |
| throws Exception { |
| useConfiguration( |
| "--android_platforms=//test_android_platforms:x86", |
| "--android_manifest_merger_order=alphabetical_by_configuration"); |
| scratch.overwriteFile( |
| "java/android/BUILD", |
| """ |
| android_library( |
| name = "core", |
| exports_manifest = 1, |
| manifest = "core/AndroidManifest.xml", |
| resource_files = ["core/res/values/strings.xml"], |
| ) |
| |
| android_library( |
| name = "utility", |
| exports_manifest = 1, |
| manifest = "utility/AndroidManifest.xml", |
| resource_files = ["utility/res/values/values.xml"], |
| deps = ["//java/common"], |
| ) |
| """); |
| scratch.file( |
| "java/binary/BUILD", |
| """ |
| android_binary( |
| name = "application", |
| srcs = ["App.java"], |
| manifest = "app/AndroidManifest.xml", |
| deps = [":library"], |
| ) |
| |
| android_library( |
| name = "library", |
| exports_manifest = 1, |
| manifest = "library/AndroidManifest.xml", |
| deps = [ |
| "//java/android:utility", |
| "//java/common:theme", |
| ], |
| ) |
| """); |
| 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", |
| exports_manifest = 1, |
| manifest = "common/AndroidManifest.xml", |
| resource_files = ["common/res/values/common.xml"], |
| deps = ["//java/android:core"], |
| ) |
| |
| android_library( |
| name = "theme", |
| exports_manifest = 1, |
| manifest = "theme/AndroidManifest.xml", |
| 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", |
| srcs = ["lib.java"], |
| data = [":test_deploy.jar"], |
| ) |
| """); |
| |
| 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; |
| } |