| // Copyright 2020 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.analysis; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.events.Event; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * A test for rule ConfiguredTargets. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public final class RuleConfiguredTargetTest extends BuildViewTestCase { | |
| private ConfiguredTarget configure(String ruleLabel) throws Exception { | |
| return getConfiguredTarget(ruleLabel); | |
| } | |
| @Test | |
| public void smokeNonexistentFailure() throws Exception { | |
| scratch.file("a/BUILD", ""); | |
| reporter.removeHandler(failFastHandler); | |
| getConfiguredTarget("//a:a"); | |
| assertContainsEvent("target 'a' not declared in package 'a'"); | |
| } | |
| @Test | |
| public void testFeatureEnabledOnCommandLine() throws Exception { | |
| useConfiguration("--features=feature"); | |
| scratch.file("a/BUILD", | |
| "cc_library(name = 'a')"); | |
| ImmutableSet<String> features = getRuleContext(configure("//a")).getFeatures(); | |
| assertThat(features).contains("feature"); | |
| assertThat(features).doesNotContain("other"); | |
| } | |
| @Test | |
| public void testFeatureDisabledOnCommandLine() throws Exception { | |
| useConfiguration("--features=-feature"); | |
| scratch.file("a/BUILD", "cc_library(name = 'a')"); | |
| ImmutableSet<String> disabledFeatures = getRuleContext(configure("//a")).getDisabledFeatures(); | |
| assertThat(disabledFeatures).contains("feature"); | |
| assertThat(disabledFeatures).doesNotContain("other"); | |
| } | |
| @Test | |
| public void testFeatureEnabledInPackage() throws Exception { | |
| scratch.file("a/BUILD", "package(features = ['feature'])", "cc_library(name = 'a')"); | |
| ImmutableSet<String> features = getRuleContext(configure("//a")).getFeatures(); | |
| assertThat(features).contains("feature"); | |
| assertThat(features).doesNotContain("other"); | |
| } | |
| @Test | |
| public void testFeatureDisableddInPackage() throws Exception { | |
| scratch.file("a/BUILD", "package(features = ['-feature'])", "cc_library(name = 'a')"); | |
| ImmutableSet<String> disabledFeatures = getRuleContext(configure("//a")).getDisabledFeatures(); | |
| assertThat(disabledFeatures).contains("feature"); | |
| assertThat(disabledFeatures).doesNotContain("other"); | |
| } | |
| @Test | |
| public void testFeatureEnabledInRule() throws Exception { | |
| scratch.file("a/BUILD", | |
| "cc_library(name = 'a', features = ['feature'])"); | |
| ImmutableSet<String> features = getRuleContext(configure("//a")).getFeatures(); | |
| assertThat(features).contains("feature"); | |
| assertThat(features).doesNotContain("other"); | |
| } | |
| @Test | |
| public void testFeatureDisabledInRule() throws Exception { | |
| scratch.file("a/BUILD", "cc_library(name = 'a', features = ['-feature'])"); | |
| ImmutableSet<String> disabledFeatures = getRuleContext(configure("//a")).getDisabledFeatures(); | |
| assertThat(disabledFeatures).contains("feature"); | |
| assertThat(disabledFeatures).doesNotContain("other"); | |
| } | |
| @Test | |
| public void testFeaturesInPackageOverrideFeaturesFromCommandLine() throws Exception { | |
| useConfiguration("--features=feature"); | |
| scratch.file("a/BUILD", "package(features = ['-feature'])", "cc_library(name = 'a')"); | |
| RuleContext ruleContext = getRuleContext(configure("//a")); | |
| ImmutableSet<String> features = ruleContext.getFeatures(); | |
| ImmutableSet<String> disabledFeatures = ruleContext.getDisabledFeatures(); | |
| assertThat(features).doesNotContain("feature"); | |
| assertThat(disabledFeatures).contains("feature"); | |
| } | |
| @Test | |
| public void testFeaturesInRuleOverrideFeaturesFromCommandLine() throws Exception { | |
| useConfiguration("--features=feature"); | |
| scratch.file("a/BUILD", "cc_library(name = 'a', features = ['-feature'])"); | |
| RuleContext ruleContext = getRuleContext(configure("//a")); | |
| ImmutableSet<String> features = ruleContext.getFeatures(); | |
| ImmutableSet<String> disabledFeatures = ruleContext.getDisabledFeatures(); | |
| assertThat(features).doesNotContain("feature"); | |
| assertThat(disabledFeatures).contains("feature"); | |
| } | |
| @Test | |
| public void testFeaturesInRuleOverrideFeaturesFromPackage() throws Exception { | |
| scratch.file("a/BUILD", | |
| "package(features = ['a', '-b', 'c'])", | |
| "cc_library(name = 'a', features = ['b', '-c', 'd'])"); | |
| RuleContext ruleContext = getRuleContext(configure("//a")); | |
| ImmutableSet<String> features = ruleContext.getFeatures(); | |
| ImmutableSet<String> disabledFeatures = ruleContext.getDisabledFeatures(); | |
| assertThat(features).containsAtLeast("a", "b", "d"); | |
| assertThat(disabledFeatures).contains("c"); | |
| } | |
| @Test | |
| public void testFeaturesDisabledFromCommandLineOverrideAll() throws Exception { | |
| useConfiguration("--features=-package_feature", "--features=-rule_feature"); | |
| scratch.file( | |
| "a/BUILD", | |
| "package(features = ['package_feature'])", | |
| "cc_library(name = 'a', features = ['rule_feature'])"); | |
| RuleContext ruleContext = getRuleContext(configure("//a")); | |
| ImmutableSet<String> features = ruleContext.getFeatures(); | |
| ImmutableSet<String> disabledFeatures = ruleContext.getDisabledFeatures(); | |
| assertThat(features).doesNotContain("package_feature"); | |
| assertThat(features).doesNotContain("rule_feature"); | |
| assertThat(disabledFeatures).contains("package_feature"); | |
| assertThat(disabledFeatures).contains("rule_feature"); | |
| } | |
| @Test | |
| public void testExperimentalDependenciesOnThirdPartyExperimentalAllowed() throws Exception { | |
| scratch.file( | |
| "third_party/experimental/p1/BUILD", | |
| "licenses(['unencumbered'])", | |
| "exports_files(['p1.cc'])", | |
| "cc_library(name = 'p1')"); | |
| scratch.file( | |
| "experimental/p2/BUILD", | |
| "exports_files(['p2.cc'])", | |
| "cc_library(name = 'p2', deps=['//third_party/experimental/p1:p1'])"); | |
| getConfiguredTarget("//experimental/p2:p2"); // No errors. | |
| } | |
| @Test | |
| public void testThirdPartyExperimentalDependenciesOnExperimentalAllowed() throws Exception { | |
| scratch.file("experimental/p1/BUILD", "exports_files(['p1.cc'])", "cc_library(name = 'p1')"); | |
| scratch.file( | |
| "third_party/experimental/p2/BUILD", | |
| "licenses(['unencumbered'])", | |
| "exports_files(['p2.cc'])", | |
| "cc_library(name = 'p2', deps=['//experimental/p1:p1'])"); | |
| getConfiguredTarget("//third_party/experimental/p2:p2"); // No errors. | |
| } | |
| @Test | |
| public void testDependencyOnTestOnlyAllowed() throws Exception { | |
| scratch.file("testonly/BUILD", | |
| "cc_library(name = 'testutil',", | |
| " srcs = ['testutil.cc'],", | |
| " testonly = 1)"); | |
| scratch.file("util/BUILD", | |
| "cc_library(name = 'util',", | |
| " srcs = ['util.cc'])"); | |
| scratch.file("cc/common/BUILD", | |
| // testonly=1 -> testonly=1 | |
| "cc_library(name = 'lib1',", | |
| " srcs = ['foo1.cc'],", | |
| " deps = ['//testonly:testutil'],", | |
| " testonly = 1)", | |
| // testonly=0 -> testonly=0 | |
| "cc_library(name = 'lib2',", | |
| " srcs = ['foo2.cc'],", | |
| " deps = ['//util'],", | |
| " testonly = 0)", | |
| // testonly=1 -> testonly=0 | |
| "cc_library(name = 'lib3',", | |
| " srcs = ['foo3.cc'],", | |
| " deps = [':lib2'],", | |
| " testonly = 1)"); | |
| getConfiguredTarget("//cc/common:lib1"); // No errors. | |
| getConfiguredTarget("//cc/common:lib2"); // No errors. | |
| getConfiguredTarget("//cc/common:lib3"); // No errors. | |
| } | |
| @Test | |
| public void testDependsOnTestOnlyDisallowed() throws Exception { | |
| scratch.file("testonly/BUILD", | |
| "cc_library(name = 'testutil',", | |
| " srcs = ['testutil.cc'],", | |
| " testonly = 1)"); | |
| checkError("cc/error", "cclib", | |
| // error: | |
| "non-test target '//cc/error:cclib' depends on testonly target '//testonly:testutil' and " | |
| + "doesn't have testonly attribute set", | |
| // build file: testonly=0 -> testonly=1 | |
| "cc_library(name = 'cclib',", | |
| " srcs = ['foo.cc'],", | |
| " deps = ['//testonly:testutil'],", | |
| " testonly = 0)"); | |
| } | |
| @Test | |
| public void testDependsOnTestOnlyOutputFileDisallowed() throws Exception { | |
| useConfiguration("--incompatible_check_testonly_for_output_files"); | |
| scratch.file( | |
| "testonly/BUILD", | |
| "genrule(name = 'testutil',", | |
| " outs = ['testutil.cc'],", | |
| " cmd = 'touch testutil.cc',", | |
| " srcs = [],", | |
| " testonly = 1)"); | |
| checkError( | |
| "cc/error", | |
| "cclib", | |
| // error: | |
| "non-test target '//cc/error:cclib' depends on the output file target" | |
| + " '//testonly:testutil.cc' of a testonly rule //testonly:testutil and doesn't have" | |
| + " testonly attribute set", | |
| // build file: testonly=0 -> testonly=1 | |
| "cc_library(name = 'cclib',", | |
| " srcs = ['//testonly:testutil.cc'],", | |
| " testonly = 0)"); | |
| } | |
| @Test | |
| public void testDependenceOnDeprecatedRule() throws Exception { | |
| scratch.file("p/BUILD", | |
| "cc_library(name='p', deps=['//q'])"); | |
| scratch.file("q/BUILD", | |
| "cc_library(name='q', deprecation='Obsolete!')"); | |
| reporter.removeHandler(failFastHandler); // expect errors | |
| ConfiguredTarget p = getConfiguredTarget("//p"); | |
| assertThat(view.hasErrors(p)).isFalse(); | |
| assertContainsEvent("target '//p:p' depends on deprecated target '//q:q':" | |
| + " Obsolete!"); | |
| assertThat(eventCollector.count()).isEqualTo(1); | |
| } | |
| @Test | |
| public void testDependenceOnDeprecatedRuleEmptyExplanation() throws Exception { | |
| scratch.file("p/BUILD", | |
| "cc_library(name='p', deps=['//q'])"); | |
| scratch.file("q/BUILD", | |
| "cc_library(name='q', deprecation='')"); // explicitly specified; still counts! | |
| reporter.removeHandler(failFastHandler); // expect errors | |
| ConfiguredTarget p = getConfiguredTarget("//p"); | |
| assertThat(view.hasErrors(p)).isFalse(); | |
| assertContainsEvent("target '//p:p' depends on deprecated target '//q:q'"); | |
| assertThat(eventCollector.count()).isEqualTo(1); | |
| } | |
| @Test | |
| public void testNoWarningWhenDeprecatedDependsOnDeprecatedRule() throws Exception { | |
| scratch.file("foo/BUILD", | |
| "java_library(name='foo', srcs=['foo.java'], deps=['//bar:bar'])"); | |
| scratch.file("bar/BUILD", | |
| "java_library(name='bar', srcs=['bar.java'], deps=['//baz:baz'], deprecation='BAR')"); | |
| scratch.file("baz/BUILD", | |
| "java_library(name='baz', srcs=['baz.java'], deprecation='BAZ')"); | |
| reporter.removeHandler(failFastHandler); // expect errors | |
| getConfiguredTarget("//foo"); | |
| assertContainsEvent("target '//foo:foo' depends on deprecated " | |
| + "target '//bar:bar': BAR"); | |
| assertDoesNotContainEvent("target '//bar:bar' depends on deprecated " | |
| + "target '//baz:baz': BAZ"); | |
| assertThat(eventCollector.count()).isEqualTo(1); | |
| } | |
| @Test | |
| public void testAttributeErrorContainsLocationOfRule() throws Exception { | |
| Event e = | |
| checkError( | |
| "x", | |
| "x", | |
| // error: | |
| getErrorNonExistingTarget("srcs", "java_library", "//x:x", "//x:a.cc"), | |
| // build file: | |
| "# blank line", | |
| "java_library(name = 'x',", | |
| " srcs = ['a.cc'])"); | |
| assertThat(e.getLocation().toString()).isEqualTo("/workspace/x/BUILD:2:13"); | |
| } | |
| @Test | |
| public void testJavatestsIsTestonly() throws Exception { | |
| scratch.file("java/x/BUILD", | |
| "java_library(name='x', exports=['//javatests/y'])"); | |
| scratch.file("javatests/y/BUILD", | |
| "java_library(name='y')"); | |
| reporter.removeHandler(failFastHandler); // expect warning | |
| ConfiguredTarget target = getConfiguredTarget("//java/x"); | |
| assertContainsEvent("non-test target '//java/x:x' depends on testonly target" | |
| + " '//javatests/y:y' and doesn't have testonly attribute set"); | |
| assertThat(view.hasErrors(target)).isTrue(); | |
| } | |
| @Test | |
| public void testDependenceOfJavaProductionCodeOnTestPackageGroups() throws Exception { | |
| scratch.file("java/banana/BUILD", | |
| "java_library(name='banana',", | |
| " visibility=['//javatests/plantain:chips'])"); | |
| scratch.file("javatests/plantain/BUILD", | |
| "package_group(name='chips',", | |
| " packages=['//javatests/plantain'])"); | |
| getConfiguredTarget("//java/banana"); | |
| assertNoEvents(); | |
| } | |
| @Test | |
| public void testUnexpectedSourceFileInDeps() throws Exception { | |
| scratch.file("x/y.java", "foo"); | |
| checkError("x", "x", getErrorMsgMisplacedFiles( | |
| "deps", "java_library", "//x:x", "//x:y.java"), | |
| "java_library(name='x', srcs=['x.java'], deps=['y.java'])"); | |
| } | |
| @Test | |
| public void testUnexpectedButExistingSourceFileDependency() throws Exception { | |
| scratch.file("x/y.java"); | |
| checkError("x", "x", getErrorMsgMisplacedFiles( | |
| "deps", "java_library", "//x:x", "//x:y.java"), | |
| "java_library(name='x', srcs=['x.java'], deps=['y.java'])"); | |
| } | |
| @Test | |
| public void testGetArtifactForImplicitOutput() throws Exception { | |
| scratch.file("java/x/BUILD", | |
| "java_binary(name='x', srcs=['x.java'])"); | |
| ConfiguredTarget javaBinary = getConfiguredTarget("//java/x:x"); | |
| Artifact classJarArtifact = getFileConfiguredTarget("//java/x:x.jar").getArtifact(); | |
| // Checks if the deploy jar is generated | |
| getFileConfiguredTarget("//java/x:x_deploy.jar").getArtifact(); | |
| assertThat(getOutputGroup(javaBinary, OutputGroupInfo.FILES_TO_COMPILE).toList()) | |
| .containsExactly(classJarArtifact); | |
| } | |
| @Test | |
| public void testSelfEdgeInRule() throws Exception { | |
| scratch.file("x/BUILD", | |
| "genrule(name='x', srcs=['x'], outs=['out'], cmd=':')"); | |
| reporter.removeHandler(failFastHandler); // expect errors | |
| getConfiguredTarget("//x"); | |
| assertContainsSelfEdgeEvent("//x:x"); | |
| } | |
| @Test | |
| public void testNegativeShardCount() throws Exception { | |
| checkError("foo", "bar", "Must not be negative.", | |
| "sh_test(name='bar', srcs=['mockingbird.sh'], shard_count=-1)"); | |
| } | |
| @Test | |
| public void testExcessiveShardCount() throws Exception { | |
| checkError("foo", "bar", "indicative of poor test organization", | |
| "sh_test(name='bar', srcs=['mockingbird.sh'], shard_count=51)"); | |
| } | |
| @Test | |
| public void testNonexistingTargetErrorMsg() throws Exception { | |
| checkError("foo", "foo", getErrorNonExistingTarget( | |
| "deps", "cc_binary", "//foo:foo", "//foo:nonesuch"), | |
| "cc_binary(name = 'foo',", | |
| "srcs = ['foo.cc'],", | |
| "deps = [':nonesuch'])"); | |
| } | |
| @Test | |
| public void testRulesDontProvideRequiredFragmentsByDefault() throws Exception { | |
| scratch.file( | |
| "a/BUILD", | |
| "config_setting(name = 'config', values = {'start_end_lib': '1'})", | |
| "py_library(name = 'pylib', srcs = ['pylib.py'])", | |
| "cc_library(name = 'a', srcs = ['A.cc'], data = [':pylib'])"); | |
| assertThat(getConfiguredTarget("//a:a").getProvider(RequiredConfigFragmentsProvider.class)) | |
| .isNull(); | |
| assertThat(getConfiguredTarget("//a:config").getProvider(RequiredConfigFragmentsProvider.class)) | |
| .isNull(); | |
| } | |
| } |