diff options
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java')
-rw-r--r-- | src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java new file mode 100644 index 0000000000..d398c10427 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java @@ -0,0 +1,594 @@ +// 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.skyframe; + +import static com.google.common.base.Strings.nullToEmpty; +import static com.google.common.truth.Truth.assertThat; +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.syntax.Type.STRING; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +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.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.ConfigurationResolver; +import com.google.devtools.build.lib.analysis.config.PatchTransition; +import com.google.devtools.build.lib.analysis.test.TestConfiguration; +import com.google.devtools.build.lib.analysis.util.MockRule; +import com.google.devtools.build.lib.analysis.util.TestAspects; +import com.google.devtools.build.lib.analysis.util.TestAspects.DummyRuleFactory; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.packages.Attribute.Transition; +import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleTransitionFactory; +import com.google.devtools.build.lib.testutil.Suite; +import com.google.devtools.build.lib.testutil.TestSpec; +import com.google.devtools.build.lib.util.FileTypeSet; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Runs an expanded set of ConfigurationsForTargetsTest with trimmed configurations. */ +@TestSpec(size = Suite.SMALL_TESTS) +@RunWith(JUnit4.class) +public class ConfigurationsForTargetsWithTrimmedConfigurationsTest + extends ConfigurationsForTargetsTest { + + private ConfigurationResolver configResolver; + + @Before + public void createConfigResolver() { + configResolver = new ConfigurationResolver(ruleClassProvider.getDynamicTransitionMapper()); + } + + @Override + protected FlagBuilder defaultFlags() { + return super.defaultFlags().with(Flag.TRIMMED_CONFIGURATIONS); + } + + private static class EmptySplitTransition implements SplitTransition<BuildOptions> { + @Override + public List<BuildOptions> split(BuildOptions buildOptions) { + return ImmutableList.of(); + } + } + + private static class SetsHostCpuSplitTransition implements SplitTransition<BuildOptions> { + @Override + public List<BuildOptions> split(BuildOptions buildOptions) { + BuildOptions result = buildOptions.clone(); + result.get(BuildConfiguration.Options.class).hostCpu = "SET BY SPLIT"; + return ImmutableList.of(result); + } + } + + private static class SetsCpuSplitTransition implements SplitTransition<BuildOptions> { + + @Override + public List<BuildOptions> split(BuildOptions buildOptions) { + BuildOptions result = buildOptions.clone(); + result.get(BuildConfiguration.Options.class).cpu = "SET BY SPLIT"; + return ImmutableList.of(result); + } + } + + private static class SetsCpuPatchTransition implements PatchTransition { + + @Override + public BuildOptions apply(BuildOptions options) { + BuildOptions result = options.clone(); + result.get(BuildConfiguration.Options.class).cpu = "SET BY PATCH"; + return result; + } + } + + /** Base rule that depends on the test configuration fragment. */ + private static class TestBaseRule implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + return builder.requiresConfigurationFragments(TestConfiguration.class).build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("test_base") + .factoryClass(DummyRuleFactory.class) + .ancestors(TestAspects.BaseRule.class) + .build(); + } + } + + /** A rule with an empty split transition on an attribute. */ + private static class EmptySplitRule implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + return builder + .add( + attr("with_empty_transition", LABEL) + .allowedFileTypes(FileTypeSet.ANY_FILE) + .cfg(new EmptySplitTransition())) + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("empty_split") + .factoryClass(DummyRuleFactory.class) + .ancestors(TestBaseRule.class) + .build(); + } + } + + /** Rule with a split transition on an attribute. */ + private static class AttributeTransitionRule implements RuleDefinition { + + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + return builder + .add(attr("without_transition", LABEL).allowedFileTypes(FileTypeSet.ANY_FILE)) + .add( + attr("with_cpu_transition", LABEL) + .allowedFileTypes(FileTypeSet.ANY_FILE) + .cfg(new SetsCpuSplitTransition())) + .add( + attr("with_host_cpu_transition", LABEL) + .allowedFileTypes(FileTypeSet.ANY_FILE) + .cfg(new SetsHostCpuSplitTransition())) + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("attribute_transition") + .factoryClass(DummyRuleFactory.class) + .ancestors(TestBaseRule.class) + .build(); + } + } + + /** Rule with rule class configuration transition. */ + private static class RuleClassTransitionRule implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + return builder.cfg(new SetsCpuPatchTransition()).build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("rule_class_transition") + .factoryClass(DummyRuleFactory.class) + .ancestors(TestBaseRule.class) + .build(); + } + } + + private static class SetsTestFilterFromAttributePatchTransition implements PatchTransition { + private final String value; + + public SetsTestFilterFromAttributePatchTransition(String value) { + this.value = value; + } + + @Override + public BuildOptions apply(BuildOptions options) { + BuildOptions result = options.clone(); + result.get(TestConfiguration.TestOptions.class).testFilter = "SET BY PATCH FACTORY: " + value; + return result; + } + } + + private static class SetsTestFilterFromAttributeTransitionFactory + implements RuleTransitionFactory { + @Override + public Transition buildTransitionFor(Rule rule) { + NonconfigurableAttributeMapper attributes = NonconfigurableAttributeMapper.of(rule); + String value = attributes.get("sets_test_filter_to", STRING); + if (Strings.isNullOrEmpty(value)) { + return null; + } else { + return new SetsTestFilterFromAttributePatchTransition(value); + } + } + } + + /** + * Rule with a RuleTransitionFactory which sets the --test_filter flag according to its attribute. + */ + private static class UsesRuleTransitionFactoryRule implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + return builder + .cfg(new SetsTestFilterFromAttributeTransitionFactory()) + .add( + attr("sets_test_filter_to", STRING) + .nonconfigurable("used in RuleTransitionFactory") + .value("")) + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("uses_rule_transition_factory") + .factoryClass(DummyRuleFactory.class) + .ancestors(TestBaseRule.class) + .build(); + } + } + + @Test + public void testRuleClassTransition() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), + new TestBaseRule(), + new AttributeTransitionRule(), + new RuleClassTransitionRule()); + scratch.file("a/BUILD", + "attribute_transition(", + " name='attribute',", + " without_transition = ':rule_class',", + ")", + "rule_class_transition(name='rule_class')"); + List<ConfiguredTarget> deps = getConfiguredDeps("//a:attribute", "without_transition"); + BuildConfiguration ruleclass = Iterables.getOnlyElement(deps).getConfiguration(); + assertThat(ruleclass.getCpu()).isEqualTo("SET BY PATCH"); + } + + @Test + public void testNonConflictingAttributeAndRuleClassTransitions() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), + new TestBaseRule(), + new AttributeTransitionRule(), + new RuleClassTransitionRule()); + scratch.file("a/BUILD", + "attribute_transition(", + " name='attribute',", + " with_host_cpu_transition = ':rule_class',", + ")", + "rule_class_transition(name='rule_class')"); + List<ConfiguredTarget> deps = getConfiguredDeps("//a:attribute", "with_host_cpu_transition"); + BuildConfiguration ruleclass = Iterables.getOnlyElement(deps).getConfiguration(); + assertThat(ruleclass.getCpu()).isEqualTo("SET BY PATCH"); + assertThat(ruleclass.getHostCpu()).isEqualTo("SET BY SPLIT"); + } + + @Test + public void testConflictingAttributeAndRuleClassTransitions() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), + new TestBaseRule(), + new AttributeTransitionRule(), + new RuleClassTransitionRule()); + scratch.file("a/BUILD", + "attribute_transition(", + " name='attribute',", + " with_cpu_transition = ':rule_class',", + ")", + "rule_class_transition(name='rule_class')"); + List<ConfiguredTarget> deps = getConfiguredDeps("//a:attribute", "with_cpu_transition"); + BuildConfiguration ruleclass = Iterables.getOnlyElement(deps).getConfiguration(); + assertThat(ruleclass.getCpu()).isEqualTo("SET BY PATCH"); + } + + @Test + public void testEmptySplitDoesNotSuppressRuleClassTransition() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), + new TestBaseRule(), + new EmptySplitRule(), + new RuleClassTransitionRule()); + scratch.file( + "a/BUILD", + "empty_split(", + " name = 'empty',", + " with_empty_transition = ':rule_class',", + ")", + "rule_class_transition(name='rule_class')"); + List<ConfiguredTarget> deps = getConfiguredDeps("//a:empty", "with_empty_transition"); + BuildConfiguration ruleclass = Iterables.getOnlyElement(deps).getConfiguration(); + assertThat(ruleclass.getCpu()).isEqualTo("SET BY PATCH"); + } + + @Test + public void testTopLevelRuleClassTransition() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), new TestBaseRule(), new RuleClassTransitionRule()); + scratch.file( + "a/BUILD", + "rule_class_transition(", + " name = 'rule_class',", + ")"); + ConfiguredTarget target = + Iterables.getOnlyElement(update("//a:rule_class").getTargetsToBuild()); + assertThat(target.getConfiguration().getCpu()).isEqualTo("SET BY PATCH"); + } + + @Test + public void testTopLevelRuleClassTransitionAndNoTransition() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), + new TestBaseRule(), + new RuleClassTransitionRule(), + new TestAspects.SimpleRule()); + scratch.file( + "a/BUILD", + "rule_class_transition(", + " name = 'rule_class',", + ")", + "simple(name='sim')"); + ConfiguredTarget target = + Iterables.getOnlyElement(update("//a:sim").getTargetsToBuild()); + assertThat(target.getConfiguration().getCpu()).isNotEqualTo("SET BY PATCH"); + } + + @Test + public void ruleTransitionFactoryUsesNonconfigurableAttributesToGenerateTransition() + throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), + new TestBaseRule(), + new AttributeTransitionRule(), + new UsesRuleTransitionFactoryRule()); + useConfiguration("--test_filter=SET ON COMMAND LINE: original and best"); + scratch.file( + "a/BUILD", + "attribute_transition(", + " name='top',", + " without_transition=':factory',", + ")", + "uses_rule_transition_factory(", + " name='factory',", + " sets_test_filter_to='funkiest',", + ")"); + List<ConfiguredTarget> deps = getConfiguredDeps("//a:top", "without_transition"); + BuildConfiguration config = Iterables.getOnlyElement(deps).getConfiguration(); + assertThat(config.getFragment(TestConfiguration.class).getTestFilter()) + .isEqualTo("SET BY PATCH FACTORY: funkiest"); + } + + @Test + public void ruleTransitionFactoryCanReturnNullToCauseNoTransition() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), + new TestBaseRule(), + new AttributeTransitionRule(), + new UsesRuleTransitionFactoryRule()); + useConfiguration("--test_filter=SET ON COMMAND LINE: original and best"); + scratch.file( + "a/BUILD", + "attribute_transition(", + " name='top',", + " without_transition=':factory',", + ")", + "uses_rule_transition_factory(", + " name='factory',", + " sets_test_filter_to='',", + ")"); + List<ConfiguredTarget> deps = getConfiguredDeps("//a:top", "without_transition"); + BuildConfiguration config = Iterables.getOnlyElement(deps).getConfiguration(); + assertThat(config.getFragment(TestConfiguration.class).getTestFilter()) + .isEqualTo("SET ON COMMAND LINE: original and best"); + } + + @Test + public void topLevelRuleTransitionFactoryUsesNonconfigurableAttributes() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), new TestBaseRule(), new UsesRuleTransitionFactoryRule()); + useConfiguration("--test_filter=SET ON COMMAND LINE: original and best"); + scratch.file( + "a/BUILD", + "uses_rule_transition_factory(", + " name='factory',", + " sets_test_filter_to='Maximum Dance',", + ")"); + ConfiguredTarget target = Iterables.getOnlyElement(update("//a:factory").getTargetsToBuild()); + assertThat(target.getConfiguration().getFragment(TestConfiguration.class).getTestFilter()) + .isEqualTo("SET BY PATCH FACTORY: Maximum Dance"); + } + + @Test + public void topLevelRuleTransitionFactoryCanReturnNullInTesting() throws Exception { + setRulesAvailableInTests( + new TestAspects.BaseRule(), new TestBaseRule(), new UsesRuleTransitionFactoryRule()); + useConfiguration("--test_filter=SET ON COMMAND LINE: original and best"); + scratch.file( + "a/BUILD", + "uses_rule_transition_factory(", + " name='factory',", + " sets_test_filter_to='',", + ")"); + update("@//a:factory"); + ConfiguredTarget target = getView().getConfiguredTargetForTesting( + reporter, + Label.parseAbsoluteUnchecked("@//a:factory"), + getTargetConfiguration()); + assertThat(target.getConfiguration().getFragment(TestConfiguration.class).getTestFilter()) + .isEqualTo("SET ON COMMAND LINE: original and best"); + } + + /** + * Returns a custom {@link PatchTransition} with the given value added to {@link + * TestConfiguration.TestOptions#testFilter}. + */ + private static PatchTransition newPatchTransition(final String value) { + return new PatchTransition() { + @Override + public BuildOptions apply(BuildOptions options) { + BuildOptions toOptions = options.clone(); + TestConfiguration.TestOptions baseOptions = + toOptions.get(TestConfiguration.TestOptions.class); + baseOptions.testFilter = (nullToEmpty(baseOptions.testFilter)) + value; + return toOptions; + } + }; + } + + /** + * Returns a custom {@link Attribute.SplitTransition} that splits {@link + * TestConfiguration.TestOptions#testFilter} down two paths: {@code += prefix + "1"} and {@code += + * prefix + "2"}. + */ + private static Attribute.SplitTransition<BuildOptions> newSplitTransition(final String prefix) { + return new Attribute.SplitTransition<BuildOptions>() { + @Override + public List<BuildOptions> split(BuildOptions buildOptions) { + ImmutableList.Builder<BuildOptions> result = ImmutableList.builder(); + for (int index = 1; index <= 2; index++) { + BuildOptions toOptions = buildOptions.clone(); + TestConfiguration.TestOptions baseOptions = + toOptions.get(TestConfiguration.TestOptions.class); + baseOptions.testFilter = + (baseOptions.testFilter == null ? "" : baseOptions.testFilter) + prefix + index; + result.add(toOptions); + } + return result.build(); + } + }; + } + + /** + * Returns the value of {@link TestConfiguration.TestOptions#testFilter} for a transition + * applied over the target configuration. + */ + private List<String> getTestFilterOptionValue(Transition transition) + throws Exception { + ImmutableList.Builder<String> outValues = ImmutableList.builder(); + for (BuildOptions toOptions : ConfiguredTargetFunction.getDynamicTransitionOptions( + getTargetConfiguration().getOptions(), transition, + ruleClassProvider.getAllFragments(), ruleClassProvider, false)) { + outValues.add(toOptions.get(TestConfiguration.TestOptions.class).testFilter); + } + return outValues.build(); + } + + @Test + public void composedStraightTransitions() throws Exception { + update(); // Creates the target configuration. + assertThat(getTestFilterOptionValue( + configResolver.composeTransitions( + newPatchTransition("foo"), + newPatchTransition("bar")))) + .containsExactly("foobar"); + } + + @Test + public void composedStraightTransitionThenSplitTransition() throws Exception { + update(); // Creates the target configuration. + assertThat(getTestFilterOptionValue( + configResolver.composeTransitions( + newPatchTransition("foo"), + newSplitTransition("split")))) + .containsExactly("foosplit1", "foosplit2"); + } + + @Test + public void composedSplitTransitionThenStraightTransition() throws Exception { + update(); // Creates the target configuration. + assertThat(getTestFilterOptionValue( + configResolver.composeTransitions( + newSplitTransition("split"), + newPatchTransition("foo")))) + .containsExactly("split1foo", "split2foo"); + } + + @Test + public void composedSplitTransitions() throws Exception { + update(); // Creates the target configuration. + assertThat(getTestFilterOptionValue( + configResolver.composeTransitions( + newSplitTransition("s"), + newSplitTransition("t")))) + .containsExactly("s1t1", "s1t2", "s2t1", "s2t2"); + } + + /** Sets {@link TestConfiguration.TestOptions#testFilter} to the rule class of the given rule. */ + private static final RuleTransitionFactory RULE_BASED_TEST_FILTER = + rule -> + (PatchTransition) + buildOptions -> { + BuildOptions toOptions = buildOptions.clone(); + toOptions.get(TestConfiguration.TestOptions.class).testFilter = rule.getRuleClass(); + return toOptions; + }; + + private static final RuleDefinition RULE_WITH_OUTGOING_TRANSITION = + (MockRule) + () -> + MockRule.define( + "change_deps", + (builder, env) -> + builder + .add(MockRule.DEPS_ATTRIBUTE) + .requiresConfigurationFragments(TestConfiguration.class) + .depsCfg(RULE_BASED_TEST_FILTER)); + + @Test + public void outgoingRuleTransition() throws Exception { + setRulesAvailableInTests( + RULE_WITH_OUTGOING_TRANSITION, + (MockRule) + () -> + MockRule.define( + "foo_rule", + (builder, env) -> + builder.requiresConfigurationFragments(TestConfiguration.class)), + (MockRule) + () -> + MockRule.define( + "bar_rule", + (builder, env) -> + builder.requiresConfigurationFragments(TestConfiguration.class))); + scratch.file("outgoing/BUILD", + "foo_rule(", + " name = 'foolib')", + "bar_rule(", + " name = 'barlib')", + "change_deps(", + " name = 'bin',", + " deps = [':foolib', ':barlib'])"); + + List<ConfiguredTarget> deps = getConfiguredDeps("//outgoing:bin", "deps"); + ImmutableMap<String, String> depLabelToTestFilterString = + ImmutableMap.of( + deps.get(0).getLabel().toString(), + deps.get(0).getConfiguration().getFragment(TestConfiguration.class).getTestFilter(), + deps.get(1).getLabel().toString(), + deps.get(1) + .getConfiguration() + .getFragment(TestConfiguration.class) + .getTestFilter()); + + assertThat(depLabelToTestFilterString).containsExactly( + "//outgoing:foolib", "foo_rule", + "//outgoing:barlib", "bar_rule"); + } +} |