// 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.config.TransitionResolver; 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 TransitionResolver transitionResolver; @Before public void createTransitionResolver() { transitionResolver = new TransitionResolver(ruleClassProvider.getDynamicTransitionMapper()); } @Override protected FlagBuilder defaultFlags() { return super.defaultFlags().with(Flag.TRIMMED_CONFIGURATIONS); } private static class EmptySplitTransition implements SplitTransition { @Override public List split(BuildOptions buildOptions) { return ImmutableList.of(); } } private static class SetsHostCpuSplitTransition implements SplitTransition { @Override public List 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 { @Override public List 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 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 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 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 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 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 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 newSplitTransition(final String prefix) { return new Attribute.SplitTransition() { @Override public List split(BuildOptions buildOptions) { ImmutableList.Builder 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 getTestFilterOptionValue(Transition transition) throws Exception { ImmutableList.Builder outValues = ImmutableList.builder(); for (BuildOptions toOptions : ConfigurationResolver.applyTransition( 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( transitionResolver.composeTransitions( newPatchTransition("foo"), newPatchTransition("bar")))) .containsExactly("foobar"); } @Test public void composedStraightTransitionThenSplitTransition() throws Exception { update(); // Creates the target configuration. assertThat(getTestFilterOptionValue( transitionResolver.composeTransitions( newPatchTransition("foo"), newSplitTransition("split")))) .containsExactly("foosplit1", "foosplit2"); } @Test public void composedSplitTransitionThenStraightTransition() throws Exception { update(); // Creates the target configuration. assertThat(getTestFilterOptionValue( transitionResolver.composeTransitions( newSplitTransition("split"), newPatchTransition("foo")))) .containsExactly("split1foo", "split2foo"); } @Test public void composedSplitTransitions() throws Exception { update(); // Creates the target configuration. assertThat(getTestFilterOptionValue( transitionResolver.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 deps = getConfiguredDeps("//outgoing:bin", "deps"); ImmutableMap 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"); } }