blob: 6876b3530df418aeef810e8335fd0eb71de9faa2 [file] [log] [blame]
// Copyright 2019 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.query2.cquery;
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.packages.BuildType.LABEL_LIST;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.config.TransitionFactories;
import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.analysis.util.MockRule;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.packages.LabelPrinter;
import com.google.devtools.build.lib.packages.RuleTransitionData;
import com.google.devtools.build.lib.query2.PostAnalysisQueryEnvironment;
import com.google.devtools.build.lib.query2.cquery.CqueryOptions.Transitions;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting;
import com.google.devtools.build.lib.query2.engine.QueryExpression;
import com.google.devtools.build.lib.query2.engine.QueryParser;
import com.google.devtools.build.lib.util.FileTypeSet;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for the transitions output format. */
@RunWith(JUnit4.class)
public class TransitionsOutputFormatterTest extends ConfiguredTargetQueryTest {
private CqueryOptions options;
private Reporter reporter;
private final List<Event> events = new ArrayList<>();
private ConfiguredRuleClassProvider ruleClassProvider;
@Before
public final void setUpCqueryOptions() {
this.options = new CqueryOptions();
helper.setQuerySettings(Setting.INCLUDE_ASPECTS);
this.reporter = new Reporter(new EventBus(), events::add);
}
@Test
public void testTransitions_full() throws Exception {
setUpRules();
writeFile(
"test/BUILD",
"my_rule(name = 'rule_with_patch',",
" patched = [':foo', ':foo2', ':trimmed_foo'],",
")",
"my_rule(name = 'rule_with_split',",
" split = ':bar',",
")",
"simple_rule(name = 'foo')",
"simple_rule(name = 'foo2')",
"simple_rule(name = 'trimmed_foo')",
"simple_rule(name = 'bar')");
List<String> result = getOutput("deps(//test:rule_with_patch)", Transitions.FULL);
assertThat(result.get(0)).startsWith("FooPatchTransition -> //test:rule_with_patch");
assertThat(result.get(1)).startsWith(" patched#//test:foo#FooPatch");
assertThat(result.get(2)).isEqualTo(" foo:SET BY RULE CLASS PATCH -> [SET BY PATCH]");
assertThat(result.get(3)).startsWith(" patched#//test:foo2#FooPatch");
assertThat(result.get(4)).isEqualTo(result.get(2));
assertThat(result.get(5))
.startsWith(" patched#//test:trimmed_foo#(FooPatchTransition + FooPatchTransition(trim))");
assertThat(result.get(6)).isEqualTo(" foo:SET BY RULE CLASS PATCH -> [SET BY TRIM]");
result = getOutput("deps(//test:rule_with_split)", Transitions.FULL);
assertThat(result.get(1)).startsWith(" split#//test:bar#FooSplitTransition");
// TODO(shahan): the right hand side of the diff below is in split dep ordering, which is
// dependent on checksum values. It could be brittle.
assertThat(result.get(2))
.isEqualTo(" foo:SET BY RULE CLASS PATCH -> [SET BY SPLIT 2, SET BY SPLIT 1]");
}
@Test
public void testTransitions_lite() throws Exception {
setUpRules();
writeFile(
"test/BUILD",
"my_rule(name = 'rule_with_patch',",
" patched = [':foo', ':foo2'],",
")",
"my_rule(name = 'rule_with_split',",
" split = ':bar',",
")",
"simple_rule(name = 'foo')",
"simple_rule(name = 'foo2')",
"simple_rule(name = 'bar')");
List<String> result = getOutput("deps(//test:rule_with_patch)", Transitions.LITE);
assertThat(result.get(0)).startsWith("FooPatchTransition -> //test:rule_with_patch");
assertThat(result.get(1)).startsWith(" patched#//test:foo#FooPatchTransition");
assertThat(result.get(2)).startsWith(" patched#//test:foo2#FooPatchTransition");
result = getOutput("deps(//test:rule_with_split)", Transitions.LITE);
assertThat(result.get(0)).startsWith("FooPatchTransition -> //test:rule_with_split");
assertThat(result.get(1)).startsWith(" split#//test:bar#FooSplitTransition");
}
@Test
public void testTransitions_getRightConfigurations() throws Exception {
setUpRules();
writeFile(
"test/BUILD",
"my_rule(name = 'rule_with_patch',",
" patched = [':foo'],",
")",
"simple_rule(name = 'foo')");
List<String> result = getOutput("deps(//test:rule_with_patch)", Transitions.LITE);
String depEntry = result.get(2);
// depEntry is "//test:rule_with_path (<config_id>)". This gets just "<config_id>".
String postPatchConfig =
depEntry.substring(depEntry.lastIndexOf("(") + 1, depEntry.length() - 1);
assertThat(result.get(1)).endsWith(postPatchConfig);
}
@Test
public void testTransitions_noTransitions() throws Exception {
setUpRules();
writeFile(
"test/BUILD",
"simple_rule(name = 'foo')");
List<String> result = getOutput("//test:foo", Transitions.NONE);
assertThat(result).isEmpty();
assertThat(events).hasSize(1);
assertThat(events.get(0).getMessage())
.isEqualTo(
"Instead of using --output=transitions, set the --transitions flag explicitly to 'lite'"
+ " or 'full'");
}
@Test
public void nonAttributeDependencySkipped() throws Exception {
setUpRules();
// A visibility dependency on a package_group produces a
// DependencyKind.NonAttributeDependencyKind. This test checks that the existence of those
// attribute types doesn't crash cquery.
writeFile(
"test/BUILD",
"package_group(",
" name = 'custom_visibility',",
" packages = ['//test/...'],",
")",
"simple_rule(",
" name = 'child',",
")",
"simple_rule(",
" name = 'parent',",
" deps = [':child'],",
" visibility = [':custom_visibility'],",
")");
assertThat(getOutput("deps(//test:parent)", Transitions.LITE)).isNotNull();
assertThat(events).isEmpty();
}
private void setUpRules() throws Exception {
TransitionFactory<RuleTransitionData> infixTrimmingTransitionFactory =
(ruleData) -> {
if (!ruleData.rule().getName().contains("trimmed")) {
return NoTransition.INSTANCE;
}
// rename the transition so it's distinguishable from the others in tests
return new FooPatchTransition("SET BY TRIM", "FooPatchTransition(trim)");
};
FooPatchTransition ruleClassTransition = new FooPatchTransition("SET BY RULE CLASS PATCH");
FooPatchTransition attributePatchTransition = new FooPatchTransition("SET BY PATCH");
FooSplitTransition attributeSplitTransitions =
new FooSplitTransition("SET BY SPLIT 1", "SET BY SPLIT 2");
MockRule ruleWithTransitions =
() ->
MockRule.define(
"my_rule",
(builder, env) ->
builder
.cfg(unused -> ruleClassTransition)
.add(
attr("patched", LABEL_LIST)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(TransitionFactories.of(attributePatchTransition)))
.add(
attr("split", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(TransitionFactories.of(attributeSplitTransitions))));
MockRule simpleRule =
() ->
MockRule.define(
"simple_rule",
(builder, env) ->
builder
.add(attr("deps", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE)));
this.ruleClassProvider =
setRuleClassProviders(ruleWithTransitions, simpleRule)
.overrideTrimmingTransitionFactoryForTesting(infixTrimmingTransitionFactory)
.build();
helper.useRuleClassProvider(ruleClassProvider);
}
private List<String> getOutput(String queryExpression, CqueryOptions.Transitions verbosity)
throws Exception {
QueryExpression expression = QueryParser.parse(queryExpression, getDefaultFunctions());
Set<String> targetPatternSet = new LinkedHashSet<>();
expression.collectTargetPatterns(targetPatternSet);
helper.setQuerySettings(Setting.NO_IMPLICIT_DEPS);
PostAnalysisQueryEnvironment<ConfiguredTarget> env =
((ConfiguredTargetQueryHelper) helper).getPostAnalysisQueryEnvironment(targetPatternSet);
options.transitions = verbosity;
// TODO(blaze-configurability): Test late-bound attributes.
TransitionsOutputFormatterCallback callback =
new TransitionsOutputFormatterCallback(
reporter,
options,
/* out= */ null,
getHelper().getSkyframeExecutor(),
env.getAccessor(),
ruleClassProvider,
LabelPrinter.legacy());
env.evaluateQuery(env.transformParsedQuery(QueryParser.parse(queryExpression, env)), callback);
return callback.getResult();
}
}