blob: 56f7b368f78c63cf62702c119856762fe6b74085 [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.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.common.CqueryNode;
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",
visibility = [":custom_visibility"],
deps = [":child"],
)
""");
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<CqueryNode> 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();
}
}