| // 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.Rule; |
| 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 javax.annotation.Nullable; |
| 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<>(); |
| @Nullable private TransitionFactory<Rule> trimmingTransitionFactory; |
| |
| @Before |
| public final void setUpCqueryOptions() { |
| this.options = new CqueryOptions(); |
| 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("TestArgPatchTransition -> //test:rule_with_patch"); |
| assertThat(result.get(1)).startsWith(" patched#//test:foo#TestArgPatch"); |
| assertThat(result.get(2)) |
| .isEqualTo(" test_arg:[SET BY RULE CLASS PATCH] -> [[SET BY PATCH]]"); |
| assertThat(result.get(3)).startsWith(" patched#//test:foo2#TestArgPatch"); |
| assertThat(result.get(4)).isEqualTo(result.get(2)); |
| assertThat(result.get(5)) |
| .startsWith( |
| " patched#//test:trimmed_foo#(TestArgPatchTransition + TestArgPatchTransition(trim))"); |
| assertThat(result.get(6)) |
| .isEqualTo(" test_arg:[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#TestArgSplitTransition"); |
| assertThat(result.get(2)) |
| .isEqualTo( |
| " test_arg:[SET BY RULE CLASS PATCH] -> [[SET BY SPLIT 1], [SET BY SPLIT 2]]"); |
| } |
| |
| @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("TestArgPatchTransition -> //test:rule_with_patch"); |
| assertThat(result.get(1)).startsWith(" patched#//test:foo#TestArgPatchTransition"); |
| assertThat(result.get(2)).startsWith(" patched#//test:foo2#TestArgPatchTransition"); |
| |
| result = getOutput("deps(//test:rule_with_split)", Transitions.LITE); |
| assertThat(result.get(0)).startsWith("TestArgPatchTransition -> //test:rule_with_split"); |
| assertThat(result.get(1)).startsWith(" split#//test:bar#TestArgSplitTransition"); |
| } |
| |
| @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 postPatchConfig = result.get(2).substring("//test:foo (".length()); |
| 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'"); |
| } |
| |
| private void setUpRules() throws Exception { |
| TransitionFactory<Rule> infixTrimmingTransitionFactory = |
| (rule) -> { |
| if (!rule.getName().contains("trimmed")) { |
| return NoTransition.INSTANCE; |
| } |
| // rename the transition so it's distinguishable from the others in tests |
| return new TestArgPatchTransition("SET BY TRIM", "TestArgPatchTransition(trim)"); |
| }; |
| TestArgPatchTransition ruleClassTransition = |
| new TestArgPatchTransition("SET BY RULE CLASS PATCH"); |
| TestArgPatchTransition attributePatchTransition = new TestArgPatchTransition("SET BY PATCH"); |
| TestArgSplitTransition attributeSplitTransitions = |
| new TestArgSplitTransition("SET BY SPLIT 1", "SET BY SPLIT 2"); |
| |
| MockRule ruleWithTransitions = |
| () -> |
| MockRule.define( |
| "my_rule", |
| (builder, env) -> |
| builder |
| .cfg(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))); |
| |
| ConfiguredRuleClassProvider ruleClassProvider = |
| setRuleClassProviders(ruleWithTransitions, simpleRule) |
| .overrideTrimmingTransitionFactoryForTesting(infixTrimmingTransitionFactory) |
| .build(); |
| this.trimmingTransitionFactory = ruleClassProvider.getTrimmingTransitionFactory(); |
| 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(juliexxia): Test late-bound attributes. |
| TransitionsOutputFormatterCallback callback = |
| new TransitionsOutputFormatterCallback( |
| reporter, |
| options, |
| /*out=*/ null, |
| getHelper().getSkyframeExecutor(), |
| env.getAccessor(), |
| env.getHostConfiguration(), |
| trimmingTransitionFactory); |
| env.evaluateQuery(env.transformParsedQuery(QueryParser.parse(queryExpression, env)), callback); |
| return callback.getResult(); |
| } |
| } |