| // Copyright 2023 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.testutil; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.base.Function; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.packages.util.MockToolsConfig; |
| import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction; |
| import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting; |
| import java.io.IOException; |
| import java.util.Set; |
| import org.junit.Test; |
| |
| /** Tests for the blaze query implementation, --nokeep_going. */ |
| public abstract class QueryTest extends AbstractQueryTest<Target> { |
| |
| @Override |
| protected QueryHelper<Target> createQueryHelper() { |
| return new SkyframeQueryHelper() { |
| @Override |
| protected String getRootDirectoryNameForSetup() { |
| return "/workspace"; |
| } |
| |
| @Override |
| protected void performAdditionalClientSetup(MockToolsConfig mockToolsConfig) |
| throws IOException {} |
| |
| @Override |
| protected Iterable<QueryFunction> getExtraQueryFunctions() { |
| return ImmutableList.of(); |
| } |
| }; |
| } |
| |
| @Override |
| protected boolean includeCppToolchainDependencies() { |
| return false; |
| } |
| |
| @Test |
| public void testFindsAllTargets_nativeRuleMacro() throws Exception { |
| writeFile( |
| "test/starlark/extension.bzl", |
| "def macro(name):", |
| " native.genrule(name = name, outs = [name + '.txt'], cmd = 'echo hi >$@')"); |
| |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension.bzl', 'macro')", |
| "", |
| "macro(name = 'rule1')", |
| "macro(name = 'rule2')"); |
| |
| assertThat(targetLabels(eval("//test/starlark:*"))) |
| .containsExactly( |
| "//test/starlark:rule1", |
| "//test/starlark:rule2", |
| "//test/starlark:BUILD", |
| "//test/starlark:rule1.txt", |
| "//test/starlark:rule2.txt"); |
| } |
| |
| @Test |
| public void testFindsAllTargets_starlarkRuleMacro() throws Exception { |
| writeFile( |
| "test//starlark/extension.bzl", |
| "def impl(ctx):", |
| " return None", |
| "", |
| "starlark_rule = rule(implementation = impl)", |
| "", |
| "def macro(name):", |
| " starlark_rule(name = name)"); |
| |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension.bzl', 'macro')", |
| "", |
| "macro(name = 'rule1')", |
| "macro(name = 'rule2')"); |
| |
| assertThat(targetLabels(eval("//test/starlark:*"))) |
| .containsExactly("//test/starlark:rule1", "//test/starlark:rule2", "//test/starlark:BUILD"); |
| } |
| |
| @Test |
| public void testBuildfiles_starlarkDep() throws Exception { |
| writeFile( |
| "test//starlark/extension.bzl", |
| "def macro(name):", |
| " native.genrule(name = name, outs = [name + '.txt'], cmd = 'echo hi >$@')"); |
| |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension.bzl', 'macro')", |
| "", |
| "macro(name = 'rule1')"); |
| |
| assertThat(targetLabels(eval("buildfiles(//test/starlark:BUILD)"))) |
| .containsExactly("//test/starlark:extension.bzl", "//test/starlark:BUILD"); |
| } |
| |
| @Test |
| public void testLoadfiles_starlarkDep() throws Exception { |
| writeFile( |
| "test//starlark/extension.bzl", |
| "def macro(name):", |
| " native.genrule(name = name, outs = [name + '.txt'], cmd = 'echo hi >$@')"); |
| |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension.bzl', 'macro')", |
| "", |
| "macro(name = 'rule1')"); |
| |
| assertThat(targetLabels(eval("loadfiles(//test/starlark:BUILD)"))) |
| .containsExactly("//test/starlark:extension.bzl"); |
| } |
| |
| @Test |
| public void testLoadfiles_labelKeyedStringDictDeps() throws Exception { |
| writeFile( |
| "test//starlark/rule.bzl", |
| "def _impl(ctx):", |
| " return", |
| "my_rule = rule(", |
| " implementation = _impl,", |
| " attrs = {", |
| " 'value_dict': attr.label_keyed_string_dict(allow_files=True),", |
| " }", |
| ")"); |
| writeFile("test//starlark/dep.cc"); |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:rule.bzl', 'my_rule')", |
| "filegroup(name='group', srcs=['dep.cc'])", |
| "my_rule(name='rule', value_dict={':group': 'queried'})"); |
| |
| assertThat(targetLabels(eval("deps(//test/starlark:rule)"))) |
| .containsExactly("//test/starlark:rule", "//test/starlark:group", "//test/starlark:dep.cc"); |
| } |
| |
| @Test |
| public void testBuildfiles_transitiveStarlarkDeps() throws Exception { |
| writeFile( |
| "test//starlark/extension1.bzl", |
| "def macro(name):", |
| " native.genrule(name = name, outs = [name + '.txt'], cmd = 'echo hi >$@')"); |
| |
| writeFile( |
| "test//starlark/extension2.bzl", |
| "load('//test/starlark:extension1.bzl', 'macro')", |
| "def func(name):", |
| " macro(name)"); |
| |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension2.bzl', 'func')", |
| "", |
| "func(name = 'rule1')"); |
| |
| assertThat(targetLabels(eval("buildfiles(//test/starlark:BUILD)"))) |
| .containsExactly( |
| "//test/starlark:extension1.bzl", |
| "//test/starlark:extension2.bzl", |
| "//test/starlark:BUILD"); |
| } |
| |
| @Test |
| public void testBuildfiles_diamondStarlarkDeps() throws Exception { |
| writeFile( |
| "test//starlark/extension1.bzl", |
| "my_constant = 'rule1'", |
| "def macro(name):", |
| " native.genrule(name = name, outs = [name + '.txt'], cmd = 'echo hi >$@')"); |
| |
| writeFile( |
| "test//starlark/extension2.bzl", |
| "load('//test/starlark:extension1.bzl', 'macro')", |
| "def func(name):", |
| " macro(name)"); |
| |
| writeFile( |
| "test//starlark/extension3.bzl", |
| "load('//test/starlark:extension1.bzl', 'my_constant')", |
| "my_rule_name = my_constant"); |
| |
| writeFile( |
| "test//starlark/extension4.bzl", |
| "load('//test/starlark:extension2.bzl', 'func')", |
| "load('//test/starlark:extension3.bzl', 'my_rule_name')", |
| "", |
| "my_dummy_name = my_rule_name"); |
| |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension2.bzl', 'func')", |
| "load('//test/starlark:extension3.bzl', 'my_rule_name')", |
| "load('//test/starlark:extension4.bzl', 'my_dummy_name')", |
| "", |
| "func(name = my_rule_name + '-' + my_dummy_name)"); |
| |
| assertThat(targetLabels(eval("buildfiles(//test/starlark:BUILD)"))) |
| .containsExactly( |
| "//test/starlark:extension1.bzl", |
| "//test/starlark:extension2.bzl", |
| "//test/starlark:extension3.bzl", |
| "//test/starlark:extension4.bzl", |
| "//test/starlark:BUILD"); |
| } |
| |
| @Test |
| public void testBuildfiles_starlarkDepPackageBuildfileIncluded() throws Exception { |
| writeFile("test//starlark2/BUILD"); |
| writeFile("test//starlark2/extension.bzl", "file_ext = '.txt'"); |
| |
| writeFile("test//starlark1/BUILD"); |
| writeFile( |
| "test//starlark1/extension.bzl", |
| "load('//test/starlark2:extension.bzl', 'file_ext')", |
| "def macro(name):", |
| " native.genrule(name = name, outs = [name + file_ext], cmd = 'echo hi >$@')"); |
| |
| writeFile( |
| "test/pkg/BUILD", |
| "load('//test/starlark1:extension.bzl', 'macro')", |
| "", |
| "macro(name = 'rule1')"); |
| |
| assertThat(targetLabels(eval("buildfiles(//test/pkg:BUILD)"))) |
| .containsExactly( |
| "//test/pkg:BUILD", |
| "//test/starlark1:extension.bzl", |
| "//test/starlark1:BUILD", |
| "//test/starlark2:extension.bzl", |
| "//test/starlark2:BUILD"); |
| } |
| |
| @Test |
| public void testQueryTimeLoadingWhenPackageDoesNotExist() throws Exception { |
| // Given a workspace containing a package "//a", |
| writeFile("a/BUILD", "sh_library(name = 'a')"); |
| |
| // When the query environment is queried for "//a/b:b" which doesn't exist, |
| String nonExistentPackage = "a/b"; |
| String s = evalThrows("//" + nonExistentPackage + ":itsNotThere", false).getMessage(); |
| |
| // Then an exception is thrown that says that the specified package does not exist. |
| assertThat(s).containsMatch("no such package '" + nonExistentPackage + "'"); |
| } |
| |
| @Test |
| public void testQueryTimeLoadingWhenPackageIsMalformed() throws Exception { |
| // Given a workspace containing a malformed package "//a", |
| writeFile("a/BUILD", "sh_library(name = 'a') BUT WAIT THERE'S MORE"); |
| |
| // When the query environment is queried for "//a:a" which belongs to a malformed package, |
| String s = evalThrows("//a:a", false).getMessage(); |
| |
| // Then an exception is thrown, |
| assertThat(s).isNotNull(); |
| |
| // And then the query output contains a description of the malformed package error. |
| assertContainsEvent("unclosed string literal"); |
| } |
| |
| @Test |
| public void testQueryTimeLoadingOfSymlinkCyclePackage() throws Exception { |
| // Given a workspace containing a symlink cycle that looks like a BUILD file at "//a/BUILD", |
| ensureSymbolicLink("a/BUILD", "a/BUILD"); |
| |
| // When the query environment is queried for "//a:*", |
| String s = evalThrows("//a:*", false).getMessage(); |
| |
| // Then an exception is thrown, |
| assertThat(s).isNotNull(); |
| |
| // And then the query output contains a description of the circular symlink problem. |
| assertContainsEvent("circular symlinks detected"); |
| } |
| |
| @Test |
| public void boundedDepsQueryWithError() throws Exception { |
| writeFile("foo/BUILD", "sh_library(name ='foo', deps = ['//bar'])"); |
| writeFile("bar/BUILD", "sh_library(name ='bar')"); |
| writeFile("errorparent", "sh_library(name = 'errorparent', deps = ['//error'])"); |
| writeFile("error", "has errors"); |
| |
| evalThrows("deps(//foo:all + //errorparent:all, 25)", false); |
| } |
| |
| @Test |
| public void testIgnoredPackagePrefixes() throws Exception { |
| writeFile(helper.getIgnoredPackagePrefixesFile().getPathString(), "a/b", "a/c"); |
| writeFile("a/BUILD", "filegroup(name = 'a')"); |
| writeFile("b/BUILD", "filegroup(name = 'b')"); |
| writeFile("a/b/BUILD", "filegroup(name = 'a_b')"); |
| writeFile("a/c/BUILD", "filegroup(name = 'a_c')"); |
| writeFile("a/d/BUILD", "filegroup(name = 'a_d')"); |
| writeFile("a/e/BUILD", "filegroup(name = 'a_e')"); |
| Iterable<String> result = targetLabels(eval("//...")); |
| assertThat(result).containsAtLeast("//a:a", "//b:b", "//a/d:a_d", "//a/e:a_e"); |
| assertThat(result).containsNoneOf("//a/b:a_b", "//a/c:a_c"); |
| result = targetLabels(eval("//a/...")); |
| assertThat(result).containsExactly("//a:a", "//a/d:a_d", "//a/e:a_e"); |
| } |
| |
| private void writeStarlarkDefinedRuleClassBzlFile() throws java.io.IOException { |
| writeFile( |
| "test//starlark/extension.bzl", |
| "def custom_rule_impl(ctx):", |
| " ftb = depset(ctx.attr._secret_labels)", |
| " return struct(runfiles = ctx.runfiles(), files = ftb)", |
| "", |
| "def secret_labels_func(prefix, suffix):", |
| " return [Label('//test/starlark:' + prefix + '01' + suffix),", |
| " Label('//test/starlark:' + prefix + '02' + suffix)]", |
| "", |
| "custom_rule = rule(implementation = custom_rule_impl,", |
| " attrs = {'prefix': attr.string(default='default_prefix'),", |
| " 'suffix': attr.string(default='default_suffix'),", |
| " '_secret_labels': attr.label_list(default=secret_labels_func)})"); |
| } |
| |
| @Test |
| public void testQueryStarlarkComputedDefault() throws Exception { |
| writeStarlarkDefinedRuleClassBzlFile(); |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension.bzl', 'custom_rule')", |
| "", |
| "custom_rule(", |
| " name = 'custom',", |
| " prefix = 'a',", |
| " suffix = 'b')"); |
| |
| Set<Target> targets = eval("//test/starlark:*"); |
| assertThat(targetLabels(targets)) |
| .containsExactly( |
| "//test/starlark:BUILD", |
| "//test/starlark:custom", |
| "//test/starlark:a01b", |
| "//test/starlark:a02b"); |
| } |
| |
| @Test |
| public void testQueryStarlarkComputedDefaultWithConfigurableDeps() throws Exception { |
| writeStarlarkDefinedRuleClassBzlFile(); |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension.bzl', 'custom_rule')", |
| "", |
| "config_setting(", |
| " name = 'cfg_a',", |
| " values = {'test_arg': 'something'})", |
| "config_setting(", |
| " name = 'cfg_b',", |
| " values = {'test_arg': 'something_else'})", |
| "", |
| "custom_rule(", |
| " name = 'custom',", |
| " prefix = select({", |
| " ':cfg_a':'a',", |
| " ':cfg_b':'b',", |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "':'def'}),", |
| " suffix = select({", |
| " ':cfg_a':'a',", |
| " ':cfg_b':'b',", |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "':'def'}))"); |
| |
| ImmutableList.Builder<String> computedLabelsBuilder = ImmutableList.builder(); |
| for (String prefix : ImmutableList.of("a", "b", "def")) { |
| for (String middle : ImmutableList.of("01", "02")) { |
| for (String suffix : ImmutableList.of("a", "b", "def")) { |
| computedLabelsBuilder.add("//test/starlark:" + prefix + middle + suffix); |
| } |
| } |
| } |
| ImmutableList<String> computedLabels = computedLabelsBuilder.build(); |
| |
| Set<Target> targets = eval("//test/starlark:*"); |
| assertThat(targetLabels(targets)) |
| .containsAtLeastElementsIn( |
| Iterables.concat( |
| ImmutableList.of( |
| "//test/starlark:BUILD", |
| "//test/starlark:cfg_a", |
| "//test/starlark:cfg_b", |
| "//test/starlark:custom"), |
| computedLabels)); |
| } |
| |
| @Test |
| public void testQueryStarlarkComputedDefaultWithConfigurableDepsUsedTwice() throws Exception { |
| writeStarlarkDefinedRuleClassBzlFile(); |
| writeFile( |
| "test//starlark/BUILD", |
| "load('//test/starlark:extension.bzl', 'custom_rule')", |
| "", |
| "config_setting(", |
| " name = 'cfg_a',", |
| " values = {'test_arg': 'something'})", |
| "config_setting(", |
| " name = 'cfg_b',", |
| " values = {'test_arg': 'something_else'})", |
| "", |
| "custom_rule(", |
| " name = 'custom_one',", |
| " prefix = select({", |
| " ':cfg_a':'a_one',", |
| " ':cfg_b':'b_one',", |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "':'def_one'}),", |
| " suffix = select({", |
| " ':cfg_a':'a_one',", |
| " ':cfg_b':'b_one',", |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "':'def_one'}))", |
| "custom_rule(", |
| " name = 'custom_two',", |
| " prefix = select({", |
| " ':cfg_a':'a_two',", |
| " ':cfg_b':'b_two',", |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "':'def_two'}),", |
| " suffix = select({", |
| " ':cfg_a':'a_two',", |
| " ':cfg_b':'b_two',", |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "':'def_two'}))"); |
| |
| ImmutableList.Builder<String> computedLabelsBuilder = ImmutableList.builder(); |
| for (String prefix : ImmutableList.of("a_one", "b_one", "def_one")) { |
| for (String middle : ImmutableList.of("01", "02")) { |
| for (String suffix : ImmutableList.of("a_one", "b_one", "def_one")) { |
| computedLabelsBuilder.add("//test/starlark:" + prefix + middle + suffix); |
| } |
| } |
| } |
| for (String prefix : ImmutableList.of("a_two", "b_two", "def_two")) { |
| for (String middle : ImmutableList.of("01", "02")) { |
| for (String suffix : ImmutableList.of("a_two", "b_two", "def_two")) { |
| computedLabelsBuilder.add("//test/starlark:" + prefix + middle + suffix); |
| } |
| } |
| } |
| ImmutableList<String> computedLabels = computedLabelsBuilder.build(); |
| |
| Set<Target> targets = eval("//test/starlark:*"); |
| assertThat(targetLabels(targets)) |
| .containsExactlyElementsIn( |
| Iterables.concat( |
| ImmutableList.of( |
| "//test/starlark:BUILD", |
| "//test/starlark:cfg_a", |
| "//test/starlark:cfg_b", |
| "//test/starlark:custom_one", |
| "//test/starlark:custom_two"), |
| computedLabels)); |
| } |
| |
| @Test |
| public void testFileTargetLiteralInSubdirectory() throws Exception { |
| writeFile( |
| "foo/BUILD", |
| "exports_files(glob(['**/*.txt']))"); |
| writeFile("foo/bar/file1.txt"); |
| writeFile("foo/bar/file2.txt"); |
| Set<Target> targets = eval("foo/bar/file1.txt + foo/bar/file2.txt"); |
| assertThat(targetLabels(targets)) |
| .containsExactly( |
| "//foo:bar/file1.txt", |
| "//foo:bar/file2.txt"); |
| } |
| |
| @Test |
| public void testShorthandTargetLiteralUnion() throws Exception { |
| writeFile( |
| "foo/bar/BUILD", |
| "sh_library(name = 'bar')"); |
| writeFile( |
| "foo/baz/BUILD", |
| "sh_library(name = 'baz')"); |
| Set<Target> targets = eval("foo/bar + foo/baz"); |
| assertThat(targetLabels(targets)) |
| .containsExactly( |
| "//foo/bar:bar", |
| "//foo/baz:baz"); |
| } |
| |
| @Test |
| public void testShorthandAbsoluteTargetLiteralUnion() throws Exception { |
| writeFile( |
| "foo/bar/BUILD", |
| "sh_library(name = 'bar')"); |
| writeFile( |
| "foo/baz/BUILD", |
| "sh_library(name = 'baz')"); |
| Set<Target> targets = eval("//foo/bar + //foo/baz"); |
| assertThat(targetLabels(targets)) |
| .containsExactly( |
| "//foo/bar:bar", |
| "//foo/baz:baz"); |
| } |
| |
| @Test |
| public void testLoadfilesWithDuplicates() throws Exception { |
| writeFile( |
| "foo/BUILD", |
| "load('//bar:bar.bzl', 'B')", |
| "sh_library(name = 'foo', deps = ['//bar'])"); |
| writeFile( |
| "bar/BUILD", |
| "load('//bar:bar.bzl', 'B')", |
| "sh_library(name = 'bar')"); |
| writeFile( |
| "bar/bar.bzl", |
| "B = []"); |
| assertThat(evalToString("loadfiles(deps(//foo))")).isEqualTo("//bar:bar.bzl"); |
| } |
| |
| protected void runTestRdepsWithNonDefaultDependencyFilter(String query, String expected) |
| throws Exception { |
| writeFile( |
| "foo/BUILD", |
| "genrule(", |
| " name = 'gen',", |
| " tools = [':a'],", |
| " srcs = ['doesntmatter.txt'],", |
| " outs = ['out.txt'],", |
| " cmd = 'blah')", |
| "sh_binary(name = 'a', srcs = ['doesntmatter.sh'])", |
| "sh_binary(name = 'b', srcs = ['doesntmatter.sh'], data = [':a'])", |
| "sh_binary(name = 'c', srcs = ['doesntmatter.sh'], data = [':out.txt'])"); |
| helper.setQuerySettings(Setting.ONLY_TARGET_DEPS); |
| assertThat(evalToString(query)).isEqualTo(expected); |
| } |
| |
| @Test |
| public void testRdepsUnboundedWithNonDefaultDependencyFilter() throws Exception { |
| runTestRdepsWithNonDefaultDependencyFilter("rdeps(//foo:all, //foo:a)", "//foo:a //foo:b"); |
| } |
| |
| @Test |
| public void testRdepsBoundedWithNonDefaultDependencyFilter() throws Exception { |
| runTestRdepsWithNonDefaultDependencyFilter("rdeps(//foo:all, //foo:a, 1)", "//foo:a //foo:b"); |
| } |
| |
| protected Iterable<String> targetLabels(Set<Target> targets) { |
| return Iterables.transform(targets, new Function<Target, String>() { |
| @Override |
| public String apply(Target input) { |
| return input.getLabel().toString(); |
| } |
| }); |
| } |
| } |