// 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.ANY_RULE;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.AspectCollection;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.Dependency;
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.PatchTransition;
import com.google.devtools.build.lib.analysis.util.TestAspects;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
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.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Runs an expanded set of ConfigurationsForTargetsTest with trimmed dynamic configurations. */
@TestSpec(size = Suite.SMALL_TESTS)
@RunWith(JUnit4.class)
public class ConfigurationsForTargetsWithDynamicConfigurationsTest
extends ConfigurationsForTargetsTest {
@Override
protected FlagBuilder defaultFlags() {
return super.defaultFlags().with(Flag.DYNAMIC_CONFIGURATIONS);
}
@Test
public void testRuleClassTransition() throws Exception {
setRulesAvailableInTests(new TestAspects.BaseRule(),
new TestAspects.AttributeTransitionRule(),
new TestAspects.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 TestAspects.AttributeTransitionRule(),
new TestAspects.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 TestAspects.AttributeTransitionRule(),
new TestAspects.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 SPLIT");
}
@Test
public void testEmptySplitDoesNotSuppressRuleClassTransition() throws Exception {
setRulesAvailableInTests(
new TestAspects.BaseRule(),
new TestAspects.EmptySplitRule(),
new TestAspects.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 TestAspects.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 TestAspects.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 TestAspects.AttributeTransitionRule(),
new TestAspects.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.getTestFilter()).isEqualTo("SET BY PATCH FACTORY: funkiest");
}
@Test
public void ruleTransitionFactoryCanReturnNullToCauseNoTransition() throws Exception {
setRulesAvailableInTests(
new TestAspects.BaseRule(),
new TestAspects.AttributeTransitionRule(),
new TestAspects.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.getTestFilter()).isEqualTo("SET ON COMMAND LINE: original and best");
}
@Test
public void topLevelRuleTransitionFactoryUsesNonconfigurableAttributes() throws Exception {
setRulesAvailableInTests(
new TestAspects.BaseRule(), new TestAspects.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().getTestFilter())
.isEqualTo("SET BY PATCH FACTORY: Maximum Dance");
}
@Test
public void topLevelRuleTransitionFactoryCanReturnNullInTesting() throws Exception {
setRulesAvailableInTests(
new TestAspects.BaseRule(), new TestAspects.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(true));
assertThat(target.getConfiguration().getTestFilter())
.isEqualTo("SET ON COMMAND LINE: original and best");
}
/**
* Returns a custom {@link PatchTransition} with the given value added to
* {@link BuildConfiguration.Options#testFilter}.
*/
private static PatchTransition newPatchTransition(final String value) {
return new PatchTransition() {
@Override
public BuildOptions apply(BuildOptions options) {
BuildOptions toOptions = options.clone();
BuildConfiguration.Options baseOptions = toOptions.get(BuildConfiguration.Options.class);
baseOptions.testFilter = (nullToEmpty(baseOptions.testFilter)) + value;
return toOptions;
}
@Override
public boolean defaultsToSelf() {
return false;
}
};
}
/**
* Returns a custom {@link Attribute.SplitTransition} that splits
* {@link BuildConfiguration.Options#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();
BuildConfiguration.Options baseOptions = toOptions.get(BuildConfiguration.Options.class);
baseOptions.testFilter =
(baseOptions.testFilter == null ? "" : baseOptions.testFilter) + prefix + index;
result.add(toOptions);
}
return result.build();
}
@Override
public boolean defaultsToSelf() {
return false;
}
};
}
/**
* Returns the value of {@link BuildConfiguration.Options#testFilter} in the output
* {@link BuildOptions} the given transition applier returns in its current state.
*/
private List<String> getTestFilterOptionValue(BuildConfiguration.TransitionApplier applier)
throws Exception {
Dependency dep = Iterables.getOnlyElement(
applier.getDependencies(Label.create("some", "target"), AspectCollection.EMPTY));
ImmutableList.Builder<String> outValues = ImmutableList.builder();
for (BuildOptions toOptions : ConfiguredTargetFunction.getDynamicTransitionOptions(
getTargetConfiguration().getOptions(), dep.getTransition(),
ruleClassProvider.getAllFragments(), ruleClassProvider, false)) {
outValues.add(toOptions.get(BuildConfiguration.Options.class).testFilter);
}
return outValues.build();
}
@Test
public void composedStraightTransitions() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.applyTransition(newPatchTransition("foo"));
applier.applyTransition(newPatchTransition("bar"));
assertThat(getTestFilterOptionValue(applier)).containsExactly("foobar");
}
@Test
public void composedStraightTransitionThenSplitTransition() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.applyTransition(newPatchTransition("foo"));
applier.split(newSplitTransition("split"));
assertThat(getTestFilterOptionValue(applier)).containsExactly("foosplit1", "foosplit2");
}
@Test
public void composedSplitTransitionThenStraightTransition() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.split(newSplitTransition("split"));
applier.applyTransition(newPatchTransition("foo"));
assertThat(getTestFilterOptionValue(applier)).containsExactly("split1foo", "split2foo");
}
@Test
public void composedSplitTransitions() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.split(newSplitTransition("split"));
try {
applier.split(newSplitTransition("disallowed second split"));
fail("expected failure: deps cannot apply more than one split transition each");
} catch (IllegalStateException e) {
assertThat(e.getMessage()).contains("dependency edges may apply at most one split");
}
}
/**
* Returns a new {@link Attribute} definition with the given configurator.
*/
private static Attribute newAttributeWithConfigurator(
final Attribute.Configurator<BuildOptions> configurator) {
return Attribute.attr("foo_attr", BuildType.LABEL)
.allowedRuleClasses(ANY_RULE)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(configurator)
.build();
}
/**
* Returns a new {@link Attribute.Configurator} that appends a given value to
* {@link BuildConfiguration.Options#testFilter}.
*/
private static Attribute.Configurator<BuildOptions> newAttributeWithStaticConfigurator(
final String value) {
return (Attribute.Configurator<BuildOptions>) newAttributeWithConfigurator(
new Attribute.Configurator<BuildOptions>() {
@Override
public Attribute.Transition apply(BuildOptions fromOptions) {
return newPatchTransition(value);
}
}).getConfigurator();
}
@Test
public void attributeConfigurator() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.applyAttributeConfigurator(newAttributeWithStaticConfigurator("from attr"));
assertThat(getTestFilterOptionValue(applier)).containsExactly("from attr");
}
@Test
public void straightTransitionThenAttributeConfigurator() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.applyTransition(newPatchTransition("from patch "));
applier.applyAttributeConfigurator(newAttributeWithStaticConfigurator("from attr"));
assertThat(getTestFilterOptionValue(applier)).containsExactly("from patch from attr");
}
/**
* Returns an {@link Attribute.Configurator} that repeats the existing
* value of {@link BuildConfiguration.Options#testFilter}, plus a signature suffix.
*/
private static final Attribute.Configurator<BuildOptions> ATTRIBUTE_WITH_REPEATING_CONFIGURATOR =
(Attribute.Configurator<BuildOptions>) newAttributeWithConfigurator(
new Attribute.Configurator<BuildOptions>() {
@Override
public Attribute.Transition apply(BuildOptions fromOptions) {
return newPatchTransition(
fromOptions.get(BuildConfiguration.Options.class).testFilter + " (attr)");
}
}).getConfigurator();
@Test
public void splitTransitionThenAttributeConfigurator() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.split(newSplitTransition(" split"));
applier.applyAttributeConfigurator(ATTRIBUTE_WITH_REPEATING_CONFIGURATOR);
assertThat(getTestFilterOptionValue(applier))
.containsExactly(" split1 split1 (attr)", " split2 split2 (attr)");
}
@Test
public void composedAttributeConfigurators() throws Exception {
update(); // Creates the target configuration.
BuildConfiguration.TransitionApplier applier = getTargetConfiguration().getTransitionApplier();
applier.applyAttributeConfigurator(newAttributeWithStaticConfigurator("from attr 1 "));
applier.applyAttributeConfigurator(newAttributeWithStaticConfigurator("from attr 2"));
assertThat(getTestFilterOptionValue(applier)).containsExactly("from attr 1 from attr 2");
}
}