blob: 55f18daeae5fd40352a47efa8908c3a4adf12776 [file] [log] [blame]
// Copyright 2018 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 static com.google.devtools.build.lib.packages.Type.STRING;
import static org.junit.Assert.assertThrows;
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.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.HostTransition;
import com.google.devtools.build.lib.analysis.config.TransitionFactories;
import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
import com.google.devtools.build.lib.analysis.util.MockRule;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting;
import com.google.devtools.build.lib.query2.engine.QueryException;
import com.google.devtools.build.lib.query2.testutil.PostAnalysisQueryTest;
import com.google.devtools.build.lib.util.FileTypeSet;
import com.google.devtools.build.lib.vfs.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link ConfiguredTargetQueryEnvironment}.
*
* <p>TODO(juliexxia): separate out tests in this file into one test per tested functionality.
*/
@RunWith(JUnit4.class)
public class ConfiguredTargetQueryTest extends PostAnalysisQueryTest<ConfiguredTarget> {
@Override
protected QueryHelper<ConfiguredTarget> createQueryHelper() {
if (helper != null) {
getHelper().cleanUp();
}
helper = new ConfiguredTargetQueryHelper();
return helper;
}
@Override
public HashMap<String, QueryFunction> getDefaultFunctions() {
ImmutableList<QueryFunction> defaultFunctions =
ImmutableList.copyOf(ConfiguredTargetQueryEnvironment.FUNCTIONS);
HashMap<String, QueryFunction> functions = new HashMap<>();
for (QueryFunction queryFunction : defaultFunctions) {
functions.put(queryFunction.getName(), queryFunction);
}
return functions;
}
@Override
protected final BuildConfiguration getConfiguration(ConfiguredTarget ct) {
return getHelper()
.getSkyframeExecutor()
.getConfiguration(getHelper().getReporter(), ct.getConfigurationKey());
}
@Test
public void testConfigurationRespected() throws Exception {
writeBuildFilesWithConfigurableAttributesUnconditionally();
assertThat(eval("deps(//configurable:main) ^ //configurable:adep")).isEmpty();
assertThat(eval("deps(//configurable:main) ^ //configurable:defaultdep")).hasSize(1);
}
private void setUpLabelsFunctionTests() throws Exception {
MockRule ruleWithTransitions =
() ->
MockRule.define(
"rule_with_transitions",
attr("patch_dep", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(TransitionFactories.of(new TestArgPatchTransition("SET BY PATCH"))),
attr("string_dep", STRING),
attr("split_dep", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(
TransitionFactories.of(
new TestArgSplitTransition("SET BY SPLIT 1", "SET BY SPLIT 2"))),
attr("patch_dep_list", LABEL_LIST)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(TransitionFactories.of(new TestArgPatchTransition("SET BY PATCH 2"))));
MockRule noAttributeRule = () -> MockRule.define("no_attribute_rule");
helper.useRuleClassProvider(
setRuleClassProviders(ruleWithTransitions, noAttributeRule).build());
writeFile(
"test/BUILD",
"rule_with_transitions(name = 'my_rule',",
" patch_dep = ':dep-1',",
" split_dep = ':dep-2',",
" string_dep = 'some string',",
" patch_dep_list = [':dep-3', ':dep-4']",
")",
"no_attribute_rule(name = 'dep-1')",
"no_attribute_rule(name = 'dep-2')",
"no_attribute_rule(name = 'dep-3')",
"no_attribute_rule(name = 'dep-4')");
helper.setUniverseScope("//test:*");
}
@Test
public void testLabelFunction_getsCorrectConfigurations() throws Exception {
setUpLabelsFunctionTests();
// (Test that you can use the labels function without an error (b/112593112)).
// Note - 'labels' as a command for cquery is a slight misnomer since it always prints
// labels AND configurations. But still a helpful function so oh well.
assertThat(Iterables.getOnlyElement(eval("labels('patch_dep', //test:my_rule)"))).isNotNull();
}
@Test
public void testLabelFunction_getCorrectlyConfiguredDeps() throws Exception {
setUpLabelsFunctionTests();
// Test that this retrieves the correctly configured version(s) of the dep(s).
ConfiguredTarget patchDep =
Iterables.getOnlyElement(eval("labels('patch_dep', //test:my_rule)"));
ConfiguredTarget myRule = Iterables.getOnlyElement(eval("//test:my_rule"));
String targetConfiguration = myRule.getConfigurationChecksum();
assertThat(patchDep.getConfigurationChecksum()).doesNotMatch(targetConfiguration);
}
@Test
public void testLabelsFunction_splitTransitionAttribute() throws Exception {
setUpLabelsFunctionTests();
ConfiguredTarget myRule = Iterables.getOnlyElement(eval("//test:my_rule"));
String targetConfiguration = myRule.getConfigurationChecksum();
Collection<ConfiguredTarget> splitDeps = eval("labels('split_dep', //test:my_rule)");
assertThat(splitDeps).hasSize(2);
for (ConfiguredTarget ct : splitDeps) {
assertThat(ct.getConfigurationChecksum()).doesNotMatch(targetConfiguration);
}
}
@Test
public void testLabelsFunction_labelListAttribute() throws Exception {
setUpLabelsFunctionTests();
ConfiguredTarget myRule = Iterables.getOnlyElement(eval("//test:my_rule"));
String targetConfiguration = myRule.getConfigurationChecksum();
// Test that this works for label_lists as well.
Set<ConfiguredTarget> deps = eval("labels('patch_dep_list', //test:my_rule)");
assertThat(deps).hasSize(2);
for (ConfiguredTarget ct : deps) {
assertThat(ct.getConfigurationChecksum()).doesNotMatch(targetConfiguration);
}
}
@Test
public void testLabelsFunction_errorsOnBadAttribute() throws Exception {
setUpLabelsFunctionTests();
ConfiguredTarget myRule = Iterables.getOnlyElement(eval("//test:my_rule"));
String targetConfiguration = myRule.getConfigurationChecksum();
// Test that the proper error is thrown when requesting an attribute that doesn't exist.
assertThat(evalThrows("labels('fake_attr', //test:my_rule)", true))
.isEqualTo(
String.format(
"in 'fake_attr' of rule //test:my_rule: ConfiguredTarget(//test:my_rule, %s) "
+ "of type rule_with_transitions does not have attribute 'fake_attr'",
targetConfiguration));
}
@Test
public void testLabelsFunction_nonLabelAttribute() throws Exception {
setUpLabelsFunctionTests();
assertThat(eval("labels('string_dep', //test:my_rule)")).isEmpty();
}
@Test
public void testAlias_filtering() throws Exception {
MockRule ruleWithHostDep =
() ->
MockRule.define(
"rule_with_host_dep",
attr("host_dep", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(HostTransition.createFactory()),
attr("$impl_dep", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.value(Label.parseAbsoluteUnchecked("//test:other")));
MockRule simpleRule = () -> MockRule.define("simple_rule");
helper.useRuleClassProvider(setRuleClassProviders(ruleWithHostDep, simpleRule).build());
writeFile(
"test/BUILD",
"alias(name = 'other_my_rule', actual = ':my_rule')",
"rule_with_host_dep(name = 'my_rule', host_dep = ':host_dep')",
"alias(name = 'other_host_dep', actual = ':host_dep')",
"simple_rule(name='host_dep')",
"alias(name = 'other_impl_dep', actual = 'impl_dep')",
"simple_rule(name='impl_dep')");
ConfiguredTarget other = Iterables.getOnlyElement(eval("//test:other_my_rule"));
ConfiguredTarget myRule = Iterables.getOnlyElement(eval("//test:my_rule"));
// Note: {@link AliasConfiguredTarget#getLabel} returns the label of the "actual" value not the
// label of the alias.
assertThat(other.getLabel()).isEqualTo(myRule.getLabel());
// Regression test for b/73496081 in which alias-ed configured targets were skipping filtering.
helper.setQuerySettings(Setting.ONLY_TARGET_DEPS, Setting.NO_IMPLICIT_DEPS);
assertThat(evalToListOfStrings("deps(//test:other_my_rule)-//test:other_my_rule"))
.isEqualTo(evalToListOfStrings("//test:my_rule"));
}
@Test
public void testTopLevelTransition() throws Exception {
MockRule ruleClassTransition =
() ->
MockRule.define(
"rule_class_transition",
(builder, env) -> builder.cfg(new TestArgPatchTransition("SET BY PATCH")).build());
helper.useRuleClassProvider(setRuleClassProviders(ruleClassTransition).build());
helper.setUniverseScope("//test:rule_class");
writeFile("test/BUILD", "rule_class_transition(name='rule_class')");
Set<ConfiguredTarget> ruleClass = eval("//test:rule_class");
TestOptions testOptions =
getConfiguration(Iterables.getOnlyElement(ruleClass)).getOptions().get(TestOptions.class);
assertThat(testOptions.testArguments).containsExactly("SET BY PATCH");
}
/** SplitTransition on --test_arg */
public static class TestArgSplitTransition implements SplitTransition {
String toOption1;
String toOption2;
public TestArgSplitTransition(String toOption1, String toOptions2) {
this.toOption1 = toOption1;
this.toOption2 = toOptions2;
}
@Override
public Map<String, BuildOptions> split(BuildOptions options, EventHandler eventHandler) {
BuildOptions result1 = options.clone();
BuildOptions result2 = options.clone();
result1.get(TestOptions.class).testArguments = Collections.singletonList(toOption1);
result2.get(TestOptions.class).testArguments = Collections.singletonList(toOption2);
return ImmutableMap.of("result1", result1, "result2", result2);
}
}
private void createConfigRulesAndBuild() throws Exception {
MockRule ruleWithTransitions =
() ->
MockRule.define(
"my_rule",
attr("target", LABEL).allowedFileTypes(FileTypeSet.ANY_FILE),
attr("host", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(HostTransition.createFactory()),
attr("exec", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(ExecutionTransitionFactory.create()),
attr("deps", BuildType.LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE));
MockRule simpleRule =
() ->
MockRule.define(
"simple_rule", attr("dep", LABEL).allowedFileTypes(FileTypeSet.ANY_FILE));
helper.useRuleClassProvider(setRuleClassProviders(ruleWithTransitions, simpleRule).build());
writeFile(
"test/BUILD",
"my_rule(",
" name = 'my_rule',",
" target = ':target_dep',",
" host = ':host_dep',",
" exec = ':exec_dep',",
" deps = [':dep'],",
")",
"simple_rule(name = 'target_dep', dep=':dep')",
"simple_rule(name = 'host_dep', dep=':dep')",
"simple_rule(name = 'exec_dep', dep=':dep')",
"simple_rule(name = 'dep')");
}
private void createConfigTransitioningRuleClass() throws Exception {
writeFile(
"tools/whitelists/function_transition_whitelist/BUILD",
"package_group(",
" name = 'function_transition_whitelist',",
" packages = [",
" '//test/...',",
" ],",
")");
writeFile(
"test/rules.bzl",
"def _rule_impl(ctx):",
" return []",
"string_flag = rule(",
" implementation = _rule_impl,",
" build_setting = config.string()",
")",
"def _transition_impl(settings, attr):",
" return {'//test:my_flag': 'custom string'}",
"my_transition = transition(",
" implementation = _transition_impl,",
" inputs = [],",
" outputs = ['//test:my_flag'],",
")",
"rule_with_deps_transition = rule(",
" implementation = _rule_impl,",
" attrs = {",
" 'deps': attr.label_list(cfg = my_transition),",
" '_whitelist_function_transition': attr.label(",
" default = '//tools/whitelists/function_transition_whitelist',",
" ),",
" }",
")",
"simple_rule = rule(",
" implementation = _rule_impl,",
" attrs = {}",
")");
}
@Test
public void testConfig_target() throws Exception {
createConfigRulesAndBuild();
assertThat(eval("config(//test:target_dep, target)")).isEqualTo(eval("//test:target_dep"));
getHelper().setWholeTestUniverseScope("test:my_rule");
assertThat(eval("config(//test:target_dep, target)")).isEqualTo(eval("//test:target_dep"));
assertThat(evalThrows("config(//test:host_dep, target)", true))
.isEqualTo("No target (in) //test:host_dep could be found in the 'target' configuration");
assertThat(evalThrows("config(//test:exec_dep, target)", true))
.isEqualTo("No target (in) //test:exec_dep could be found in the 'target' configuration");
BuildConfiguration configuration =
getConfiguration(Iterables.getOnlyElement(eval("config(//test:dep, target)")));
assertThat(configuration).isNotNull();
assertThat(configuration.isHostConfiguration()).isFalse();
assertThat(configuration.isExecConfiguration()).isFalse();
assertThat(configuration.isToolConfiguration()).isFalse();
}
@Test
public void testConfig_hostTransition() throws Exception {
createConfigRulesAndBuild();
getHelper().setWholeTestUniverseScope("test:my_rule");
assertThat(evalThrows("config(//test:target_dep, host)", true))
.isEqualTo("No target (in) //test:target_dep could be found in the 'host' configuration");
assertThat(eval("config(//test:host_dep, host)")).isEqualTo(eval("//test:host_dep"));
assertThat(evalThrows("config(//test:exec_dep, host)", true))
.isEqualTo("No target (in) //test:exec_dep could be found in the 'host' configuration");
BuildConfiguration configuration =
getConfiguration(Iterables.getOnlyElement(eval("config(//test:dep, host)")));
assertThat(configuration).isNotNull();
assertThat(configuration.isHostConfiguration()).isTrue();
assertThat(configuration.isExecConfiguration()).isFalse();
assertThat(configuration.isToolConfiguration()).isTrue();
}
@Test
public void testConfig_nullConfig() throws Exception {
writeFile("test/BUILD", "java_library(name='my_java',", " srcs = ['foo.java'],", ")");
assertThat(getConfiguration(Iterables.getOnlyElement(eval("config(//test:foo.java,null)"))))
.isNull();
}
@Test
public void testConfig_configHash() throws Exception {
createConfigTransitioningRuleClass();
writeFile(
"test/BUILD",
"load('//test:rules.bzl', 'rule_with_deps_transition', 'simple_rule', 'string_flag')",
"string_flag(",
" name = 'my_flag',",
" build_setting_default = '')",
"rule_with_deps_transition(",
" name = 'buildme',",
" deps = [':mydep'])",
"simple_rule(name = 'mydep')");
// If we don't set --universe_scope=//test:buildme, cquery builds both //test:buildme and
// //test:mydep as top-level targets. That means //test:mydep will have two configured targets:
// one under the transitioned configuration and one under the top-level configuration. By
// setting --universe_scope we ensure only the transitioned version exists.
helper.setUniverseScope("//test:buildme");
helper.setQuerySettings(Setting.ONLY_TARGET_DEPS, Setting.NO_IMPLICIT_DEPS);
Set<ConfiguredTarget> result = eval("deps(//test:buildme, 1)");
assertThat(result).hasSize(2);
ImmutableList<ConfiguredTarget> stableOrderList = ImmutableList.copyOf(result);
int myDepIndex = stableOrderList.get(0).getLabel().toString().equals("//test:mydep") ? 0 : 1;
BuildConfiguration myDepConfig = getConfiguration(stableOrderList.get(myDepIndex));
BuildConfiguration stringFlagConfig = getConfiguration(stableOrderList.get(1 - myDepIndex));
// Note: eval() resets the universe scope after each call. We have to xplicitly set it again.
helper.setUniverseScope("//test:buildme");
assertThat(eval("config(//test:mydep, " + myDepConfig.checksum() + ")")).hasSize(1);
helper.setUniverseScope("//test:buildme");
QueryException e =
assertThrows(
QueryException.class,
() -> eval("config(//test:mydep, " + stringFlagConfig.checksum() + ")"));
assertThat(e)
.hasMessageThat()
.contains("No target (in) //test:mydep could be found in the configuration with checksum");
}
@Test
public void testConfig_badConfig() throws Exception {
createConfigRulesAndBuild();
assertThat(evalThrows("config(//test:my_rule,foo)", true))
.isEqualTo(
"Unknown value 'foo'. The second argument of config() must be 'target', 'host', "
+ "'null', or a valid configuration hash (i.e. one of the outputs of "
+ "'blaze config')");
}
@Test
public void testExecTransitionNotFilteredByNoHostDeps() throws Exception {
createConfigRulesAndBuild();
helper.setQuerySettings(Setting.ONLY_TARGET_DEPS, Setting.NO_IMPLICIT_DEPS);
assertThat(evalToListOfStrings("deps(//test:my_rule)"))
.containsExactly("//test:my_rule", "//test:target_dep", "//test:dep");
}
@Test
public void testRecursiveTargetPatternNeverThrowsError() throws Exception {
Path parent =
getHelper()
.getScratch()
.file("parent/BUILD", "sh_library(name = 'parent')")
.getParentDirectory();
Path child = parent.getRelative("child");
child.createDirectory();
Path badBuild = child.getRelative("BUILD");
badBuild.createSymbolicLink(badBuild);
helper.setKeepGoing(true);
assertThat(eval("//parent:all")).isEqualTo(eval("//parent:parent"));
helper.setKeepGoing(false);
getHelper().turnOffFailFast();
assertThat(evalThrows("//parent/...", true))
.isEqualTo(
"no such package 'parent/child': Symlink cycle detected while trying to "
+ "find BUILD file /workspace/parent/child/BUILD");
}
@Override
@Test
public void testMultipleTopLevelConfigurations_nullConfigs() throws Exception {
writeFile("test/BUILD", "java_library(name='my_java',", " srcs = ['foo.java'],", ")");
Set<ConfiguredTarget> result = eval("//test:my_java+//test:foo.java");
assertThat(result).hasSize(2);
Iterator<ConfiguredTarget> resultIterator = result.iterator();
ConfiguredTarget first = resultIterator.next();
if (first.getLabel().toString().equals("//test:foo.java")) {
assertThat(getConfiguration(first)).isNull();
assertThat(getConfiguration(resultIterator.next())).isNotNull();
} else {
assertThat(getConfiguration(first)).isNotNull();
assertThat(getConfiguration(resultIterator.next())).isNull();
}
}
@Test
public void testSomePath_DepInCustomConfiguration() throws Exception {
createConfigTransitioningRuleClass();
writeFile(
"test/BUILD",
"load('//test:rules.bzl', 'rule_with_deps_transition', 'simple_rule', 'string_flag')",
"string_flag(",
" name = 'my_flag',",
" build_setting_default = '')",
"rule_with_deps_transition(",
" name = 'buildme',",
" deps = [':mydep'])",
"simple_rule(name = 'mydep')");
// If we don't set --universe_scope=//test:buildme, then cquery builds both //test:buildme and
// //test:mydep as top-level targets. That means //test:mydep will have two configured targets:
// one under the transitioned configuration and one under the top-level configuration. In these
// cases cquery prefers the top-level configured one, which won't produce a match since that's
// not the one down this dependency path.
helper.setUniverseScope("//test:buildme");
Set<ConfiguredTarget> result = eval("somepath(//test:buildme, //test:mydep)");
assertThat(result.stream().map(ct -> ct.getLabel().toString()).collect(Collectors.toList()))
.contains("//test:mydep");
}
/** Return an empty BuildOptions for testing fragment dropping. * */
public static class RemoveTestOptionsTransition implements PatchTransition {
@Override
public BuildOptions patch(BuildOptions options, EventHandler eventHandler) {
BuildOptions.Builder builder = BuildOptions.builder();
for (FragmentOptions option : options.getNativeOptions()) {
if (!(option instanceof TestOptions)) {
builder.addFragmentOptions(option);
}
}
// This does not copy over Starlark options!!
return builder.build();
}
}
@Test
public void testQueryHandlesDroppingFragments() throws Exception {
MockRule ruleDropOptions =
() ->
MockRule.define(
"rule_drop_options",
attr("dep", LABEL)
.allowedFileTypes(FileTypeSet.ANY_FILE)
.cfg(TransitionFactories.of(new RemoveTestOptionsTransition())));
MockRule simpleRule =
() ->
MockRule.define(
"simple_rule", attr("deps", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE));
helper.useRuleClassProvider(setRuleClassProviders(ruleDropOptions, simpleRule).build());
writeFile(
"test/BUILD",
"rule_drop_options(name = 'top', dep = ':foo')",
"simple_rule(name='foo', deps = [':bar'])",
"simple_rule(name='bar')");
Set<ConfiguredTarget> result =
eval("somepath(//test:top, filter(//test:bar, deps(//test:top)))");
assertThat(result).isNotEmpty();
}
@Override
public void testMultipleTopLevelConfigurations_multipleConfigsPrefersTopLevel() {
// When the same target exists in multiple configurations, cquery doesn't guarantee which
// instance is evaluated first. So disable this test.
}
@Test
public void testLabelExpressionsMatchesAllConfiguredTargetsWithLabel() throws Exception {
createConfigTransitioningRuleClass();
writeFile(
"test/BUILD",
"load('//test:rules.bzl', 'rule_with_deps_transition', 'simple_rule', 'string_flag')",
"string_flag(",
" name = 'my_flag',",
" build_setting_default = '')",
"rule_with_deps_transition(",
" name = 'transitioner',",
" deps = [':simple'])",
"simple_rule(name = 'simple')");
helper.setUniverseScope("//test:transitioner,//test:simple");
Set<ConfiguredTarget> result = eval("//test:simple");
assertThat(result.size()).isEqualTo(2);
}
@Test
public void testConfigFunctionRefinesMultipleMatches() throws Exception {
// Peer to testLabelExpressionsMatchesAllConfiguredTargetsWithLabel. The point of that test is
// to show "cquery //foo:bar" might return multiple configured targets. The point of this test
// is to show that config() can refine the same query to a specific one.
createConfigTransitioningRuleClass();
writeFile(
"test/BUILD",
"load('//test:rules.bzl', 'rule_with_deps_transition', 'simple_rule', 'string_flag')",
"string_flag(",
" name = 'my_flag',",
" build_setting_default = '')",
"rule_with_deps_transition(",
" name = 'transitioner',",
" deps = [':simple'])",
"simple_rule(name = 'simple')");
helper.setUniverseScope("//test:transitioner,//test:simple");
Set<ConfiguredTarget> result = eval("config(//test:simple, target)");
assertThat(result.size()).isEqualTo(1);
}
}