blob: 65ef8f9e5e5148ce8b2fe0ef8053d86eacfdd627 [file] [log] [blame]
// 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<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 : 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<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");
}
}