aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Ulf Adams <ulfjack@google.com>2015-04-23 16:02:57 +0000
committerGravatar Lukacs Berki <lberki@google.com>2015-04-24 10:12:51 +0000
commit70426345b89d5fa6457f4ea531789e152df0f036 (patch)
tree8135e5b7aa1ec3b9e56aff8134de8f1a895410bc
parent08207110c6014851bc90f597cf04d3a658f295c5 (diff)
Open source the analysis/constraints tests.
-- MOS_MIGRATED_REVID=91891070
-rw-r--r--src/test/java/BUILD19
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/BazelAnalysisMock.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java842
3 files changed, 863 insertions, 0 deletions
diff --git a/src/test/java/BUILD b/src/test/java/BUILD
index 79043b5f3c..c3e33e15aa 100644
--- a/src/test/java/BUILD
+++ b/src/test/java/BUILD
@@ -266,6 +266,25 @@ java_test(
],
)
+java_test(
+ name = "analysis_constraints_test",
+ srcs = glob([
+ "com/google/devtools/build/lib/analysis/constraints/*.java",
+ ]),
+ args = ["com.google.devtools.build.lib.AllTests"],
+ deps = [
+ ":actions_testutil",
+ ":analysis_testutil",
+ ":foundations_testutil",
+ ":test_runner",
+ ":testutil",
+ "//src/main/java:bazel-core",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
+
cc_binary(
name = "com/google/devtools/build/lib/shell/killmyself",
srcs = ["com/google/devtools/build/lib/shell/killmyself.cc"],
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/BazelAnalysisMock.java
index b428a2ad6a..28eb2d5667 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/BazelAnalysisMock.java
@@ -72,6 +72,8 @@ public class BazelAnalysisMock extends AnalysisMock {
" objcopy_files = ':empty', static_runtime_libs = [':empty'], strip_files = ':empty',",
")");
config.create("tools/cpp/CROSSTOOL", readFromResources("MOCK_CROSSTOOL"));
+ config.create("tools/genrule/BUILD",
+ "exports_files(['genrule-setup.sh'])");
}
@Override
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java
new file mode 100644
index 0000000000..4d1d2960ea
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java
@@ -0,0 +1,842 @@
+// Copyright 2015 Google Inc. 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.constraints;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import com.google.devtools.build.lib.testutil.UnknownRuleConfiguredTarget;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for the constraint enforcement system.
+ */
+public class ConstraintsTest extends BuildViewTestCase {
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // Support files for RuleClassWithImplicitAndLateBoundDefaults:
+ scratchFile("helpers/BUILD",
+ "sh_library(name = 'implicit', srcs = ['implicit.sh'])",
+ "sh_library(name = 'latebound', srcs = ['latebound.sh'])");
+ }
+
+ /**
+ * Creates an environment group on the scratch filesystem consisting of the specified
+ * environments and specified defaults, set via a builder-style interface. The package name
+ * is the same as the group name.
+ */
+ private class EnvironmentGroupMaker {
+ private final String name;
+ private Set<String> environments;
+ private Set<String> defaults;
+ private final Multimap<String, String> fulfillsMap = HashMultimap.create();
+
+ public EnvironmentGroupMaker(String name) {
+ this.name = name;
+ }
+
+ public EnvironmentGroupMaker setEnvironments(String... environments) {
+ this.environments = ImmutableSet.copyOf(environments);
+ return this;
+ }
+
+ public EnvironmentGroupMaker setDefaults(String... environments) {
+ this.defaults = ImmutableSet.copyOf(environments);
+ return this;
+ }
+
+ /**
+ * Declares that env1 fulfills env2.
+ */
+ public EnvironmentGroupMaker setFulfills(String env1, String env2) {
+ fulfillsMap.put(env1, env2);
+ return this;
+ }
+
+ private final void make() throws Exception {
+ StringBuilder builder = new StringBuilder();
+ for (String env : environments) {
+ builder.append("environment(name = '" + env + "',\n")
+ .append(getAttrDef("fulfills", fulfillsMap.get(env).toArray(new String[0])))
+ .append(")\n");
+ }
+ builder.append("environment_group(\n")
+ .append(" name = '" + name + "',\n")
+ .append(getAttrDef("environments", environments.toArray(new String[0])) + ",\n")
+ .append(getAttrDef("defaults", defaults.toArray(new String[0])) + ",\n")
+ .append(")");
+ scratchFile("" + name + "/BUILD", builder.toString());
+ }
+ }
+
+ /**
+ * Returns a rule definition of the given name, type and custom attribute settings.
+ */
+ private static String getRuleDef(String ruleType, String ruleName, String... customAttributes) {
+ String ruleDef =
+ ruleType + "(\n"
+ + " name = '" + ruleName + "',\n"
+ + " srcs = ['" + ruleName + ".sh'],\n";
+ for (String customAttribute : customAttributes) {
+ ruleDef += " " + customAttribute + ",\n";
+ }
+ ruleDef += ")\n";
+ return ruleDef;
+ }
+
+ /**
+ * Given the inputs, returns the string "attrName = [':label1', ':label2', etc.]"
+ */
+ private static String getAttrDef(String attrName, String... labels) {
+ String attrDef = " " + attrName + " = [";
+ for (String label : labels) {
+ attrDef += "'" + label + "', ";
+ }
+ attrDef += "]";
+ return attrDef;
+ }
+
+ /**
+ * The core constraint semantics check that if rule A depends on rule B, B must support all of
+ * A's environments. To model this in the tests below, we construct two rules: a "depending"
+ * rule (i.e. A) that depends on a "dependency" rule (i.e. B). Each test can construct its
+ * own instance of these rules with its own environments specifications by calling this method
+ * and {@link #getDependencyRule} with appropriate environment settings passed in as custom
+ * attributes.
+ *
+ * <p>This method constructs and returns the depending rule (i.e. A).
+ */
+ private static String getDependingRule(String... customAttributes) {
+ List<String> attrsAsList = Lists.newArrayList(customAttributes);
+ attrsAsList.add(getAttrDef("deps", "dep"));
+ return getRuleDef("sh_library", "main", attrsAsList.toArray(new String[0]));
+ }
+
+ /**
+ * Returns the rule that {@link #getDependingRule} depends on. This rule must support every
+ * environment supported by the other one for their constraint relationship to be considered
+ * valid.
+ */
+ private static String getDependencyRule(String... customAttributes) {
+ return getRuleDef("sh_library", "dep", customAttributes);
+ }
+
+ /**
+ * Returns the attribute definition that constrains a rule to the given environments. Inputs
+ * are expected to be package-relative labels (e.g. {@code "foo_env"}).
+ */
+ private static String constrainedTo(String... environments) {
+ return getAttrDef("restricted_to", environments);
+ }
+
+ /**
+ * Returns the attribute definition that designates a rule compatible with the given environments.
+ */
+ private static String compatibleWith(String... environments) {
+ return getAttrDef("compatible_with", environments);
+ }
+
+ /**
+ * Returns the environments supported by a rule.
+ */
+ private Collection<Label> supportedEnvironments(String ruleName, String ruleDef)
+ throws Exception {
+ return ConstraintSemantics.getSupportedEnvironments(
+ getRuleContext(scratchConfiguredTarget("hello", ruleName, ruleDef))).getEnvironments();
+ }
+
+ /**
+ * Dummy rule class for testing rule class defaults. This class applies valid defaults. Note
+ * that the specified environments must be independently created.
+ */
+ private static final class RuleClassDefaultRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .setUndocumented()
+ .compatibleWith(env.getLabel("//rule_class_compat:b"))
+ .restrictedTo(env.getLabel("//rule_class_restrict:d"))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("rule_class_default")
+ .ancestors(BaseRuleClasses.RuleBase.class)
+ .factoryClass(UnknownRuleConfiguredTarget.class)
+ .build();
+ }
+ }
+
+ /**
+ * Dummy rule class for testing rule class defaults. This class applies invalid defaults. Note
+ * that the specified environments must be independently created.
+ */
+ private static final class BadRuleClassDefaultRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .setUndocumented()
+ // These defaults are invalid since compatibleWith and restrictedTo can't mix
+ // environments from the same group.
+ .compatibleWith(env.getLabel("//rule_class_compat:a"))
+ .restrictedTo(env.getLabel("//rule_class_compat:b"))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("bad_rule_class_default")
+ .ancestors(BaseRuleClasses.RuleBase.class)
+ .factoryClass(UnknownRuleConfiguredTarget.class)
+ .build();
+ }
+ }
+
+ private static final class RuleClassWithImplicitAndLateBoundDefaults implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .setUndocumented()
+ .add(Attribute.attr("$implicit", Type.LABEL)
+ .value(Label.parseAbsoluteUnchecked("//helpers:implicit")))
+ .add(Attribute.attr(":latebound", Type.LABEL)
+ .value(
+ new Attribute.LateBoundLabel<BuildConfiguration>() {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ return Label.parseAbsoluteUnchecked("//helpers:latebound");
+ }
+ }))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("rule_with_implicit_and_latebound_deps")
+ .ancestors(BaseRuleClasses.RuleBase.class)
+ .factoryClass(UnknownRuleConfiguredTarget.class)
+ .build();
+ }
+ }
+
+ /**
+ * Injects the rule class default rules into the default test rule class provider.
+ */
+ @Override
+ protected ConfiguredRuleClassProvider getRuleClassProvider() {
+ ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
+ TestRuleClassProvider.addStandardRules(builder);
+ builder.addRuleDefinition(new RuleClassDefaultRule());
+ builder.addRuleDefinition(new BadRuleClassDefaultRule());
+ builder.addRuleDefinition(new RuleClassWithImplicitAndLateBoundDefaults());
+ return builder.build();
+ }
+
+ /**
+ * Writes the environments and environment groups referred to by the rule class defaults.
+ */
+ private void writeRuleClassDefaultEnvironments() throws Exception {
+ new EnvironmentGroupMaker("rule_class_compat").setEnvironments("a", "b").setDefaults("a")
+ .make();
+ new EnvironmentGroupMaker("rule_class_restrict").setEnvironments("c", "d").setDefaults("c")
+ .make();
+ }
+
+ /**
+ * By default, a rule *implicitly* supports all defaults, meaning the explicitly known
+ * environment set is empty.
+ */
+ public void testDefaultSupportedEnvironments() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ String ruleDef = getDependencyRule();
+ assertThat(supportedEnvironments("dep", ruleDef)).isEmpty();
+ }
+
+ /**
+ * "Constraining" a rule's environments explicitly sets them.
+ */
+ public void testConstrainedSupportedEnvironments() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ String ruleDef = getDependencyRule(constrainedTo("//foo_env:c"));
+ assertThat(supportedEnvironments("dep", ruleDef))
+ .containsExactlyElementsIn(asLabelSet("//foo_env:c"));
+ }
+
+ /**
+ * Specifying compatibility adds the specified environments to the defaults.
+ */
+ public void testCompatibleSupportedEnvironments() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ String ruleDef = getDependencyRule(compatibleWith("//foo_env:c"));
+ assertThat(supportedEnvironments("dep", ruleDef))
+ .containsExactlyElementsIn(asLabelSet("//foo_env:a", "//foo_env:c"));
+ }
+
+ /**
+ * A rule can't support *no* environments.
+ */
+ public void testSupportedEnvironmentsConstrainedtoNothing() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ reporter.removeHandler(failFastHandler);
+ String ruleDef = getDependencyRule(constrainedTo());
+ assertNull(scratchConfiguredTarget("hello", "dep", ruleDef));
+ assertContainsEvent("attribute cannot be empty");
+ }
+
+ /**
+ * Restrict the environments within one group, declare compatibility for another.
+ */
+ public void testSupportedEnvironmentsInMultipleGroups() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ new EnvironmentGroupMaker("bar_env").setEnvironments("c", "d").setDefaults("c").make();
+ String ruleDef = getDependencyRule(constrainedTo("//foo_env:b"), compatibleWith("//bar_env:d"));
+ assertThat(supportedEnvironments("dep", ruleDef))
+ .containsExactlyElementsIn(asLabelSet("//foo_env:b", "//bar_env:c", "//bar_env:d"));
+ }
+
+ /**
+ * The same label can't appear in both a constraint and a compatibility declaration.
+ */
+ public void testSameEnvironmentCompatibleAndRestricted() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ reporter.removeHandler(failFastHandler);
+ String ruleDef = getDependencyRule(constrainedTo("//foo_env:b"), compatibleWith("//foo_env:b"));
+ assertNull(scratchConfiguredTarget("hello", "dep", ruleDef));
+ assertContainsEvent("//foo_env:b cannot appear both here and in restricted_to");
+ }
+
+ /**
+ * Two labels from the same group can't appear in different attributes.
+ */
+ public void testSameGroupCompatibleAndRestricted() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ reporter.removeHandler(failFastHandler);
+ String ruleDef = getDependencyRule(constrainedTo("//foo_env:a"), compatibleWith("//foo_env:b"));
+ assertNull(scratchConfiguredTarget("hello", "dep", ruleDef));
+ assertContainsEvent("//foo_env:b and //foo_env:a belong to the same environment group");
+ }
+
+ /**
+ * Tests that rule class defaults change a rule's default set of environments.
+ */
+ public void testSupportedEnvironmentsRuleClassDefaults() throws Exception {
+ writeRuleClassDefaultEnvironments();
+ String ruleDef = "rule_class_default(name = 'a')";
+ Set<Label> expectedEnvironments =
+ asLabelSet("//rule_class_compat:a", "//rule_class_compat:b", "//rule_class_restrict:d");
+ assertThat(supportedEnvironments("a", ruleDef)).containsExactlyElementsIn(expectedEnvironments);
+ }
+
+ /**
+ * Tests that explicit declarations override rule class defaults.
+ */
+ public void testExplicitAttributesOverrideRuleClassDefaults() throws Exception {
+ writeRuleClassDefaultEnvironments();
+ String ruleDef = "rule_class_default("
+ + " name = 'a',"
+ + " compatible_with = ['//rule_class_restrict:c'],"
+ + " restricted_to = ['//rule_class_compat:a'],"
+ + ")";
+ Set<Label> expectedEnvironments =
+ asLabelSet("//rule_class_compat:a", "//rule_class_restrict:c", "//rule_class_restrict:d");
+ assertThat(supportedEnvironments("a", ruleDef)).containsExactlyElementsIn(expectedEnvironments);
+ }
+
+ /**
+ * Tests that a rule's "known" supported environments includes those from groups referenced
+ * in rule class defaults but not in explicit rule attributes.
+ */
+ public void testKnownEnvironmentsIncludesThoseFromRuleClassDefaults() throws Exception {
+ writeRuleClassDefaultEnvironments();
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ String ruleDef = "rule_class_default("
+ + " name = 'a',"
+ + " restricted_to = ['//foo_env:b'],"
+ + ")";
+ Set<Label> expectedEnvironments = asLabelSet("//rule_class_compat:a", "//rule_class_compat:b",
+ "//rule_class_restrict:d", "//foo_env:b");
+ assertThat(supportedEnvironments("a", ruleDef)).containsExactlyElementsIn(expectedEnvironments);
+ }
+
+ /**
+ * Tests that environments from the same group can't appear in both restriction and
+ * compatibility rule class defaults.
+ */
+ public void testSameEnvironmentRuleClassCompatibleAndRestricted() throws Exception {
+ writeRuleClassDefaultEnvironments();
+ reporter.removeHandler(failFastHandler);
+ String ruleDef = "bad_rule_class_default(name = 'a')";
+ assertNull(scratchConfiguredTarget("hello", "a", ruleDef));
+ assertContainsEvent(
+ "//rule_class_compat:a and //rule_class_compat:b belong to the same environment group");
+ }
+
+ /**
+ * Tests that a dependency is valid if both rules implicitly inherit all default environments.
+ */
+ public void testAllDefaults() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(),
+ getDependingRule());
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that a dependency is valid when both rules explicitly declare the same constraints.
+ */
+ public void testSameConstraintsDeclaredExplicitly() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:b")),
+ getDependingRule(constrainedTo("//foo_env:b")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that a dependency is valid when both the depender and dependency explicitly declare
+ * their constraints and the depender supports a subset of the dependency's environments
+ */
+ public void testValidConstraintsDeclaredExplicitly() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:a", "//foo_env:b")),
+ getDependingRule(constrainedTo("//foo_env:b")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that a dependency is invalid when both the depender and dependency explicitly declare
+ * their constraints and the depender supports an environment the dependency doesn't.
+ */
+ public void testInvalidConstraintsDeclaredExplicitly() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:b")),
+ getDependingRule(constrainedTo("//foo_env:a", "//foo_env:b")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:a");
+ }
+
+ /**
+ * Tests that a dependency is valid when both rules add the same set of environments to their
+ * defaults.
+ */
+ public void testSameCompatibilityConstraints() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(compatibleWith("//foo_env:b", "//foo_env:c")),
+ getDependingRule(compatibleWith("//foo_env:b", "//foo_env:c")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that a dependency is valid when both rules add environments to their defaults and
+ * the depender only adds environments also added by the dependency.
+ */
+ public void testValidCompatibilityConstraints() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(compatibleWith("//foo_env:b", "//foo_env:c")),
+ getDependingRule(compatibleWith("//foo_env:c")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that a dependency is invalid when both rules add environments to their defaults and
+ * the depender adds environments not added by the dependency.
+ */
+ public void testInvalidCompatibilityConstraints() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(compatibleWith("//foo_env:c")),
+ getDependingRule(compatibleWith("//foo_env:b", "//foo_env:c")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:b");
+ }
+
+ /**
+ * Tests the error message when the dependency is missing multiple expected environments.
+ */
+ public void testMultipleMissingEnvironments() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(),
+ getDependingRule(compatibleWith("//foo_env:b", "//foo_env:c")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent(
+ "dependency //hello:dep doesn't support expected environments: //foo_env:b, //foo_env:c");
+ }
+
+ /**
+ * Tests a valid dependency including environments from different groups.
+ */
+ public void testValidMultigroupConstraints() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ new EnvironmentGroupMaker("bar_env").setEnvironments("d", "e", "f").setDefaults("d").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:b", "//foo_env:c"),
+ compatibleWith("//bar_env:e")),
+ getDependingRule(constrainedTo("//foo_env:c"), compatibleWith("//bar_env:e")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests an invalid dependency including environments from different groups.
+ */
+ public void testInvalidMultigroupConstraints() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ new EnvironmentGroupMaker("bar_env").setEnvironments("d", "e", "f").setDefaults("d").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:c"), compatibleWith("//bar_env:e")),
+ getDependingRule(constrainedTo("//foo_env:b", "//foo_env:c"),
+ compatibleWith("//bar_env:e")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:b");
+ }
+
+ /**
+ * Tests a valid dependency where the dependency doesn't "know" about the expected environment's
+ * group, but implicitly supports it because that environment is a default.
+ */
+ public void testValidConstraintsUnknownEnvironmentToDependency() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a", "b")
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(),
+ getDependingRule(constrainedTo("//foo_env:b")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests an invalid dependency where the dependency doesn't "know" about the expected
+ * environment's group and doesn't support it because it isn't a default.
+ */
+ public void testInvalidConstraintsUnknownEnvironmentToDependency() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a", "b")
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(),
+ getDependingRule(constrainedTo("//foo_env:c")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:c");
+ }
+
+ /**
+ * Tests a valid dependency where the depender doesn't "know" about one of the dependency's
+ * groups, the depender implicitly supports that group's defaults, and all of those defaults
+ * are accounted for in the dependency.
+ */
+ public void testValidConstraintsUnknownEnvironmentToDependender() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:a", "//foo_env:b")),
+ getDependingRule());
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests an invalid dependency where the depender doesn't "know" about one of the dependency's
+ * groups, the depender implicitly supports that group's defaults, and one of those defaults
+ * isn't accounted for in the dependency.
+ */
+ public void testInvalidConstraintsUnknownEnvironmentToDependender() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:b")),
+ getDependingRule());
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:a");
+ }
+
+ /**
+ * Tests the case where one dependency is valid and another one isn't.
+ */
+ public void testOneDependencyIsInvalid() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getRuleDef("sh_library", "bad_dep", constrainedTo("//foo_env:b")),
+ getRuleDef("sh_library", "good_dep", compatibleWith("//foo_env:b")),
+ getRuleDef("sh_library", "depender",
+ constrainedTo("//foo_env:a", "//foo_env:b"),
+ getAttrDef("deps", "good_dep", "bad_dep")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:depender"));
+ assertContainsEvent("//hello:bad_dep doesn't support expected environment: //foo_env:a");
+ assertDoesNotContainEvent("//hello:good_dep");
+ }
+
+ public void testConstraintEnforcementDisabled() throws Exception {
+ useConfiguration("--experimental_enforce_constraints=0");
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b", "c").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(),
+ getDependingRule(compatibleWith("//foo_env:b", "//foo_env:c")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that package defaults compatibility produces a valid dependency that would otherwise
+ * be invalid.
+ */
+ public void testCompatibilityPackageDefaults() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ "package(default_compatible_with = ['//foo_env:b'])",
+ getDependencyRule(),
+ getDependingRule(compatibleWith("//foo_env:b")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that a rule's compatibility declaration overrides its package defaults compatibility.
+ */
+ public void testPackageDefaultsCompatibilityOverride() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults().make();
+ // We intentionally create an invalid dependency structure vs. a valid one. If we tested on
+ // a valid one, this test wouldn't be able to distinguish between rule declarations overriding
+ // package defaults and package defaults overriding rule declarations.
+ scratchFile("hello/BUILD",
+ "package(default_compatible_with = ['//foo_env:b'])",
+ getDependencyRule(compatibleWith("//foo_env:a")),
+ getDependingRule(compatibleWith("//foo_env:a", "//foo_env:b")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:b");
+ }
+
+ /**
+ * Tests that package defaults restriction produces an valid dependency that would otherwise
+ * be invalid.
+ */
+ public void testRestrictionPackageDefaults() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a", "b").make();
+ scratchFile("hello/BUILD",
+ "package(default_restricted_to = ['//foo_env:b'])",
+ getDependencyRule(constrainedTo("//foo_env:b")),
+ getDependingRule());
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ /**
+ * Tests that a rule's restriction declaration overrides its package defaults restriction.
+ */
+ public void testPackageDefaultsRestrictionOverride() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults().make();
+ // We intentionally create an invalid dependency structure vs. a valid one. If we tested on
+ // a valid one, this test wouldn't be able to distinguish between rule declarations overriding
+ // package defaults and package defaults overriding rule declarations.
+ scratchFile("hello/BUILD",
+ "package(default_restricted_to = ['//foo_env:b'])",
+ getDependencyRule(constrainedTo("//foo_env:a")),
+ getDependingRule(constrainedTo("//foo_env:a", "//foo_env:b")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:b");
+ }
+
+ /**
+ * Tests that "default_compatible_with" fills in a rule's "compatible_with" when not specified
+ * by the rule. This is different than, e.g., the rule declaration / rule class defaults model,
+ * where the "compatible_with" / "restricted_to" values of rule class defaults are merged together
+ * before being supplied to the rule. See comments in DependencyResolver for more discussion.
+ */
+ public void testPackageDefaultsDirectlyFillRuleAttributes() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults().make();
+ scratchFile("hello/BUILD",
+ "package(default_restricted_to = ['//foo_env:b'])",
+ getDependencyRule(compatibleWith("//foo_env:a")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:dep"));
+ assertContainsEvent("//foo_env:a and //foo_env:b belong to the same environment group. They "
+ + "should be declared together either here or in restricted_to");
+ }
+
+ public void testHostDependenciesAreNotChecked() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ "sh_binary(name = 'host_tool', srcs = ['host_tool.sh'], restricted_to = ['//foo_env:b'])",
+ "genrule(",
+ " name = 'hello',",
+ " srcs = [],",
+ " outs = ['hello.out'],",
+ " cmd = '',",
+ " tools = [':host_tool'],",
+ " compatible_with = ['//foo_env:a'])");
+ assertNotNull(getConfiguredTarget("//hello:hello"));
+ assertNoEvents();
+ }
+
+ public void testImplicitAndLateBoundDependenciesAreNotChecked() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults("a").make();
+ scratchFile("hello/BUILD",
+ "rule_with_implicit_and_latebound_deps(name = 'hi', compatible_with = ['//foo_env:b'])");
+ assertNotNull(getConfiguredTarget("//hello:hi"));
+ // Note that the event "cannot build rule_with_implicit_and_latebound_deps" *does* occur
+ // because of the implementation of UnknownRuleConfiguredTarget.
+ assertDoesNotContainEvent(":implicit doesn't support expected environment");
+ assertDoesNotContainEvent(":latebound doesn't support expected environment");
+ }
+
+ public void testOutputFilesAreChecked() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults().make();
+ scratchFile("hello/BUILD",
+ "genrule(name = 'gen', srcs = [], outs = ['shlib.sh'], cmd = '')",
+ "sh_library(",
+ " name = 'shlib',",
+ " srcs = ['shlib.sh'],",
+ " data = ['whatever.txt'],",
+ " compatible_with = ['//foo_env:a'])");
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:shlib"));
+ assertContainsEvent("dependency //hello:gen doesn't support expected environment: //foo_env:a");
+ }
+
+ public void testConfigSettingRulesAreNotChecked() throws Exception {
+ new EnvironmentGroupMaker("foo_env").setEnvironments("a", "b").setDefaults().make();
+ scratchFile("hello/BUILD",
+ "config_setting(name = 'setting', values = {'compilation_mode': 'fastbuild'})",
+ "sh_library(",
+ " name = 'shlib',",
+ " srcs = select({",
+ " ':setting': ['shlib.sh'],",
+ " }),",
+ " compatible_with = ['//foo_env:a'])");
+ assertNotNull(getConfiguredTarget("//hello:shlib"));
+ assertNoEvents();
+ }
+
+ public void testFulfills() throws Exception {
+ new EnvironmentGroupMaker("foo_env")
+ .setEnvironments("a", "b")
+ .setFulfills("a", "b")
+ .setDefaults()
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:a")),
+ getDependingRule(constrainedTo("//foo_env:b")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ public void testFulfillsIsNotSymmetric() throws Exception {
+ new EnvironmentGroupMaker("foo_env")
+ .setEnvironments("a", "b")
+ .setFulfills("a", "b")
+ .setDefaults()
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:b")),
+ getDependingRule(constrainedTo("//foo_env:a")));
+ reporter.removeHandler(failFastHandler);
+ assertNull(getConfiguredTarget("//hello:main"));
+ assertContainsEvent("dependency //hello:dep doesn't support expected environment: //foo_env:a");
+ }
+
+ public void testFulfillsIsTransitive() throws Exception {
+ new EnvironmentGroupMaker("foo_env")
+ .setEnvironments("a", "b", "c")
+ .setFulfills("a", "b")
+ .setFulfills("b", "c")
+ .setDefaults()
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:a")),
+ getDependingRule(constrainedTo("//foo_env:c")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ public void testDefaultEnvironmentDirectlyFulfills() throws Exception {
+ new EnvironmentGroupMaker("foo_env")
+ .setEnvironments("a", "b")
+ .setFulfills("a", "b")
+ .setDefaults("a")
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(),
+ getDependingRule(constrainedTo("//foo_env:b")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ public void testDefaultEnvironmentIndirectlyFulfills() throws Exception {
+ new EnvironmentGroupMaker("foo_env")
+ .setEnvironments("a", "b", "c")
+ .setFulfills("a", "b")
+ .setFulfills("b", "c")
+ .setDefaults("a")
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(),
+ getDependingRule(constrainedTo("//foo_env:c")));
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+
+ public void testEnvironmentFulfillsExpectedDefault() throws Exception {
+ new EnvironmentGroupMaker("foo_env")
+ .setEnvironments("a", "b")
+ .setFulfills("a", "b")
+ .setDefaults("b")
+ .make();
+ scratchFile("hello/BUILD",
+ getDependencyRule(constrainedTo("//foo_env:a")),
+ getDependingRule());
+ assertNotNull(getConfiguredTarget("//hello:main"));
+ assertNoEvents();
+ }
+}