| // Copyright 2020 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.buildtool; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.devtools.build.lib.testutil.MoreAsserts.assertContainsEvent; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static org.junit.Assert.assertThrows; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.Iterables; | |
| import com.google.common.io.ByteStreams; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.actions.BuildFailedException; | |
| import com.google.devtools.build.lib.analysis.ViewCreationFailedException; | |
| import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; | |
| import com.google.devtools.build.lib.skyframe.TransitiveTargetKey; | |
| import com.google.protobuf.ByteString; | |
| import com.google.testing.junit.testparameterinjector.TestParameter; | |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; | |
| import java.io.ByteArrayOutputStream; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.regex.Pattern; | |
| import java.util.zip.GZIPInputStream; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| /** Integration tests for the 'genquery' rule. */ | |
| @RunWith(TestParameterInjector.class) | |
| public class GenQueryIntegrationTest extends BuildIntegrationTestCase { | |
| @TestParameter private boolean ttvFree; | |
| @TestParameter private boolean keepGoing; | |
| @Override | |
| protected void setupOptions() throws Exception { | |
| super.setupOptions(); | |
| runtimeWrapper.addOptions( | |
| ttvFree | |
| ? "--experimental_skip_ttvs_for_genquery" | |
| : "--noexperimental_skip_ttvs_for_genquery"); | |
| runtimeWrapper.addOptions(keepGoing ? "--keep_going" : "--nokeep_going"); | |
| } | |
| @Test | |
| public void testDoesNotFailHorribly() throws Exception { | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='melon', deps=[':papaya'])", | |
| "sh_library(name='papaya')", | |
| "genquery(name='q',", | |
| " scope=[':melon'],", | |
| " expression='deps(//fruits:melon)')"); | |
| assertQueryResult("//fruits:q", "//fruits:melon", "//fruits:papaya"); | |
| } | |
| @Test | |
| public void testDeterministic() throws Exception { | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='melon', deps=[':papaya', ':apple'])", | |
| "sh_library(name='papaya', deps=[':banana'])", | |
| "sh_library(name='banana', deps=[':apple'])", | |
| "sh_library(name='apple', deps=[':cherry'])", | |
| "sh_library(name='cherry')", | |
| "genquery(name='q',", | |
| " scope=[':melon'],", | |
| " expression='deps(//fruits:melon)')"); | |
| String firstResult = getQueryResult("//fruits:q"); | |
| for (int i = 0; i < 10; i++) { | |
| createFilesAndMocks(); // Do a clean. | |
| assertThat(getQueryResult("//fruits:q")).isEqualTo(firstResult); | |
| } | |
| } | |
| @Test | |
| public void testDuplicateName() throws Exception { | |
| write("one/BUILD", "sh_library(name='foo')"); | |
| write("two/BUILD", "sh_library(name='foo')"); | |
| write( | |
| "query/BUILD", | |
| "sh_library(name='common', deps=['//one:foo', '//two:foo'])", | |
| "genquery(name='q',", | |
| " scope=['//query:common'],", | |
| " expression='deps(//query:common)')"); | |
| assertThat(getQueryResult("//query:q").split("\n")).hasLength(3); | |
| } | |
| @Test | |
| public void testFailsIfGoesOutOfScope() throws Exception { | |
| write( | |
| "vegetables/BUILD", | |
| "sh_library(name='tomato', deps=[':cabbage'])", | |
| "sh_library(name='cabbage')", | |
| "genquery(name='q',", | |
| " scope=[':cabbage'],", | |
| " expression='deps(//vegetables:tomato)')"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//vegetables:q")); | |
| assertContainsEvent(events.collector(), "is not within the scope of the query"); | |
| } | |
| // Regression test for http://b/29964062. | |
| @Test | |
| public void testFailsIfGoesOutOfScopeViaSelect() throws Exception { | |
| write( | |
| "q/BUILD", | |
| "genquery(name='q', expression='deps(//q:f)', scope=['f'])", | |
| "config_setting(name='cs', values={'define':'D=1'})", | |
| "filegroup(name='f', srcs=select({'cs':[], '//conditions:default':['//dne']}))"); | |
| addOptions("--define=D=1"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//q")); | |
| events.assertContainsError( | |
| "in genquery rule //q:q: errors were encountered while computing transitive closure of the" | |
| + " scope"); | |
| events.assertContainsError( | |
| Pattern.compile( | |
| "no such package 'dne': BUILD file not found in any of the following directories. Add a" | |
| + " BUILD file to a directory to mark it as a package.\n" | |
| + " - .*/dne")); | |
| } | |
| // Regression test for http://b/34132681 | |
| @Test | |
| public void testFailsIfBrokenDependencyViaSelect() throws Exception { | |
| write( | |
| "q/BUILD", | |
| "genquery(name='q', expression='deps(//q:f)', scope=['f'])", | |
| "config_setting(name='cs', values={'define':'D=1'})", | |
| "filegroup(name='f', srcs=select({'cs':[], '//conditions:default':['//d:d']}))"); | |
| // d exists but is missing "srcs" | |
| write("d/BUILD", "sh_binary(name = 'd')"); | |
| addOptions("--define=D=1"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//q")); | |
| events.assertContainsError( | |
| "in genquery rule //q:q: errors were encountered while computing transitive closure of the" | |
| + " scope"); | |
| events.assertContainsError("Target '//d:d' contains an error and its package is in error"); | |
| } | |
| @Test | |
| public void testResultsAlphabetized() throws Exception { | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='melon', deps=[':a', ':z', ':c', ':1', '//z:a', '//a:z', '//c:c'])", | |
| "sh_library(name='a')", | |
| "sh_library(name='z')", | |
| "sh_library(name='1')", | |
| "sh_library(name='c')", | |
| "genquery(name='q',", | |
| " scope=[':melon'],", | |
| " expression='deps(//fruits:melon)')"); | |
| write("z/BUILD", "sh_library(name = 'a')"); | |
| write("a/BUILD", "sh_library(name = 'z')"); | |
| write("c/BUILD", "sh_library(name = 'c', deps = ['//z:a'])"); | |
| assertQueryResult( | |
| "//fruits:q", | |
| // Results are ordered in lexicographical order (uses graphless genquery by default). | |
| "//a:z", | |
| "//c:c", | |
| "//fruits:1", | |
| "//fruits:a", | |
| "//fruits:c", | |
| "//fruits:melon", | |
| "//fruits:z", | |
| "//z:a"); | |
| } | |
| @Test | |
| public void testQueryReexecutedIfDepsChange() throws Exception { | |
| write( | |
| "food/BUILD", | |
| "sh_library(name='fruit_salad', deps=['//fruits:tropical'])", | |
| "genquery(name='q',", | |
| " scope=[':fruit_salad'],", | |
| " expression='deps(//food:fruit_salad)')"); | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='tropical', deps=[':papaya'])", | |
| "sh_library(name='papaya')"); | |
| assertQueryResult("//food:q", "//food:fruit_salad", "//fruits:papaya", "//fruits:tropical"); | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='tropical', deps=[':papaya', ':coconut'])", | |
| "sh_library(name='papaya')", | |
| "sh_library(name='coconut')"); | |
| assertQueryResult( | |
| "//food:q", | |
| "//food:fruit_salad", | |
| "//fruits:coconut", | |
| "//fruits:papaya", | |
| "//fruits:tropical"); | |
| } | |
| @Test | |
| public void testGenQueryEncountersAnotherGenQuery() throws Exception { | |
| write( | |
| "spices/BUILD", | |
| "sh_library(name='cinnamon', deps=[':nutmeg'])", | |
| "sh_library(name='nutmeg')", | |
| "genquery(name='q',", | |
| " scope=[':cinnamon'],", | |
| " expression='deps(//spices:cinnamon)')"); | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='pear', deps=[':plum'])", | |
| "sh_library(name='plum')", | |
| "genquery(name='q',", | |
| " scope=[':pear', '//spices:q'],", | |
| " expression='deps(//fruits:pear) + deps(//spices:q)')"); | |
| assertQueryResult( | |
| "//fruits:q", | |
| "//fruits:pear", | |
| "//fruits:plum", | |
| "//spices:cinnamon", | |
| "//spices:nutmeg", | |
| "//spices:q"); | |
| } | |
| /** | |
| * Regression test for b/14227750: genquery referring to non-existent target crashes on skyframe. | |
| */ | |
| @Test | |
| public void testHandlesMissingTargetGracefully() throws Exception { | |
| write( | |
| "a/BUILD", | |
| "genquery(name='query', scope=['//b:target'], expression='deps(//b:nosuchtarget)')"); | |
| write("b/BUILD", "sh_library(name = 'target')"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//a:query")); | |
| events.assertContainsError( | |
| "in genquery rule //a:query: query failed: no such target '//b:nosuchtarget'"); | |
| } | |
| @Test | |
| public void testReportsMissingScopeTarget() throws Exception { | |
| write("a/BUILD", "genquery(name='query', scope=['//b:target'], expression='set()')"); | |
| write("b/BUILD"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//a:query")); | |
| events.assertContainsError( | |
| "in genquery rule //a:query: errors were encountered while computing transitive closure of" | |
| + " the scope"); | |
| events.assertContainsError( | |
| Pattern.compile( | |
| "no such target '//b:target': target 'target' not declared in package 'b' defined by" | |
| + " .*/b/BUILD")); | |
| } | |
| @Test | |
| public void testReportsMissingTransitiveScopeTarget() throws Exception { | |
| write( | |
| "a/BUILD", | |
| "genquery(name='query', scope=[':missingdep'], expression='set()')", | |
| "sh_library(name='missingdep', deps=['//b:target'])"); | |
| write("b/BUILD"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//a:query")); | |
| events.assertContainsError( | |
| "in genquery rule //a:query: errors were encountered while computing transitive closure of" | |
| + " the scope"); | |
| events.assertContainsError( | |
| Pattern.compile( | |
| "no such target '//b:target': target 'target' not declared in package 'b' defined by" | |
| + " .*/b/BUILD")); | |
| } | |
| @Test | |
| public void testReportsMissingScopePackage() throws Exception { | |
| write("a/BUILD", "genquery(name='query', scope=['//b:target'], expression='set()')"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//a:query")); | |
| events.assertContainsError( | |
| "in genquery rule //a:query: errors were encountered while computing transitive closure of" | |
| + " the scope"); | |
| events.assertContainsError( | |
| Pattern.compile( | |
| "no such package 'b': BUILD file not found in any of the following directories. Add a" | |
| + " BUILD file to a directory to mark it as a package.\n" | |
| + " - .*/b")); | |
| } | |
| @Test | |
| public void testReportsMissingTransitiveScopePackage() throws Exception { | |
| write( | |
| "a/BUILD", | |
| "genquery(name='query', scope=[':missingdep'], expression='set()')", | |
| "sh_library(name='missingdep', deps=['//b:target'])"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//a:query")); | |
| events.assertContainsError( | |
| "in genquery rule //a:query: errors were encountered while computing transitive closure" | |
| + " of the scope"); | |
| events.assertContainsError( | |
| Pattern.compile( | |
| "no such package 'b': BUILD file not found in any of the following" | |
| + " directories. Add a BUILD file to a directory to mark it as a package.\n" | |
| + " - .*/b")); | |
| } | |
| @Test | |
| public void testMultiplePatternsInQuery() throws Exception { | |
| String buildFile = ""; | |
| String genQuery = | |
| "genquery(name = 'q', scope = [':top'], expression = 'deps(//spices:top) ' + \n"; | |
| String topTarget = "sh_library(name = 'top', deps = [\n"; | |
| for (int i = 0; i < 20; i++) { | |
| String targetName = (i % 2 == 0 ? "in" : "out") + i; | |
| buildFile += "sh_library(name = '" + targetName + "')\n"; | |
| if (i % 2 != 0) { | |
| genQuery += "' - //spices:" + targetName + " ' + \n"; | |
| } | |
| topTarget += " ':" + targetName + "',\n"; | |
| } | |
| topTarget += "]\n)\n"; | |
| genQuery += "'')"; | |
| write("spices/BUILD", buildFile, topTarget, genQuery); | |
| List<String> expected = new ArrayList<>(11); | |
| for (int i = 0; i < 20; i += 2) { | |
| expected.add(i / 2, "//spices:in" + i); | |
| } | |
| expected.add(0, "//spices:top"); | |
| Collections.sort(expected); | |
| assertQueryResult("//spices:q", expected.toArray(new String[0])); | |
| } | |
| @Test | |
| public void testGraphOutput_factored() throws Exception { | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='melon', deps=[':papaya', ':coconut', ':mango'])", | |
| "sh_library(name='papaya')", | |
| "sh_library(name = 'mango')", | |
| "sh_library(name = 'coconut')", | |
| "genquery(name='q',", | |
| " scope=[':melon'],", | |
| " opts = ['--output=graph'],", | |
| " expression='deps(//fruits:melon)')"); | |
| assertPartialQueryResult( | |
| "//fruits:q", | |
| " \"//fruits:melon\"", | |
| " \"//fruits:melon\" -> \"//fruits:coconut\\n//fruits:mango\\n//fruits:papaya\"", | |
| " \"//fruits:coconut\\n//fruits:mango\\n//fruits:papaya\""); | |
| } | |
| @Test | |
| public void testGraphOutput_unfactored() throws Exception { | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='melon', deps=[':papaya', ':coconut', ':mango'])", | |
| "sh_library(name='papaya')", | |
| "sh_library(name = 'mango')", | |
| "sh_library(name = 'coconut')", | |
| "genquery(name='q',", | |
| " scope=[':melon'],", | |
| " opts = ['--output=graph', '--nograph:factored'],", | |
| " expression='deps(//fruits:melon)')"); | |
| assertPartialQueryResult( | |
| "//fruits:q", | |
| " \"//fruits:melon\"", | |
| " \"//fruits:melon\" -> \"//fruits:coconut\"", | |
| " \"//fruits:melon\" -> \"//fruits:mango\"", | |
| " \"//fruits:melon\" -> \"//fruits:papaya\"", | |
| " \"//fruits:papaya\"", | |
| " \"//fruits:mango\"", | |
| " \"//fruits:coconut\""); | |
| } | |
| @Test | |
| public void testDoesntAllowLocationOutputWithLoadfiles() throws Exception { | |
| write("foo/bzl.bzl", "x = 2"); | |
| write( | |
| "foo/BUILD", | |
| "load('//foo:bzl.bzl', 'x')", | |
| "sh_library(name='foo')", | |
| "genquery(", | |
| " name = 'gen-loadfiles',", | |
| " expression = 'loadfiles(//foo:foo)',", | |
| " scope = ['//foo:foo'],", | |
| ")", | |
| "genquery(", | |
| " name = 'gen-loadfiles-location',", | |
| " expression = 'loadfiles(//foo:foo)',", | |
| " opts = ['--output=location'],", | |
| " scope = ['//foo:foo'],", | |
| ")"); | |
| assertQueryResult("//foo:gen-loadfiles", "//foo:bzl.bzl"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//foo:gen-loadfiles-location")); | |
| events.assertContainsError( | |
| "in genquery rule //foo:gen-loadfiles-location: query failed: Query expressions " | |
| + "involving 'buildfiles' or 'loadfiles' cannot be used with --output=location"); | |
| } | |
| @Test | |
| public void testDoesntAllowLocationOutputWithBuildfiles() throws Exception { | |
| write("foo/bzl.bzl", "x = 2"); | |
| write( | |
| "foo/BUILD", | |
| "load('//foo:bzl.bzl', 'x')", | |
| "sh_library(name='foo')", | |
| "genquery(", | |
| " name = 'gen-buildfiles',", | |
| " expression = 'buildfiles(//foo:foo)',", | |
| " scope = ['//foo:foo'],", | |
| ")", | |
| "genquery(", | |
| " name = 'gen-buildfiles-location',", | |
| " expression = 'buildfiles(//foo:foo)',", | |
| " opts = ['--output=location'],", | |
| " scope = ['//foo:foo'],", | |
| ")"); | |
| assertQueryResult("//foo:gen-buildfiles", "//foo:BUILD", "//foo:bzl.bzl"); | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//foo:gen-buildfiles-location")); | |
| events.assertContainsError( | |
| "in genquery rule //foo:gen-buildfiles-location: query failed: Query expressions " | |
| + "involving 'buildfiles' or 'loadfiles' cannot be used with --output=location"); | |
| } | |
| /** Regression test for b/127644784. */ | |
| @Test | |
| public void somepathOutputDeterministic() throws Exception { | |
| /* | |
| * This graph structure routinely reproduces the bug within 10 iterations: | |
| * | |
| * ----------top------------ | |
| * | | | | | |
| * mid1 mid2 mid3 mid4 | |
| * | | | | | |
| * --lower-- | | | |
| * | | | | |
| * -----bottom---------- | |
| */ | |
| write( | |
| "query/BUILD", | |
| "genquery(", | |
| " name = 'query',", | |
| " expression = 'somepath(//top, //bottom)',", | |
| " scope = ['//top', '//bottom'],", | |
| ")"); | |
| write("top/BUILD", "sh_library(name = 'top', deps = ['//mid1', '//mid2', '//mid3', '//mid4'])"); | |
| write("mid1/BUILD", "sh_library(name = 'mid1', deps = ['//lower'])"); | |
| write("mid2/BUILD", "sh_library(name = 'mid2', deps = ['//lower'])"); | |
| write("mid3/BUILD", "sh_library(name = 'mid3', deps = ['//bottom'])"); | |
| write("mid4/BUILD", "sh_library(name = 'mid4', deps = ['//bottom'])"); | |
| write("lower/BUILD", "sh_library(name = 'lower', deps = ['//bottom'])"); | |
| write("bottom/BUILD", "sh_library(name = 'bottom')"); | |
| String firstResult = getQueryResult("//query"); | |
| for (int i = 0; i < 10; i++) { | |
| createFilesAndMocks(); // Do a clean. | |
| assertThat(getQueryResult("//query")).isEqualTo(firstResult); | |
| } | |
| } | |
| private void runNodepDepsTest(String optsStringValue, boolean expectVisibilityDep) | |
| throws Exception { | |
| write( | |
| "foo/BUILD", | |
| "sh_library(name = 't1', deps = [':t2'], visibility = [':pg', '//query:__pkg__'])", | |
| "sh_library(name = 't2')", | |
| "package_group(name = 'pg')"); | |
| write( | |
| "query/BUILD", | |
| "genquery(", | |
| " name = 'gen',", | |
| " expression = 'deps(//foo:t1)',", | |
| " scope = ['//foo:t1'],", | |
| " opts = " + optsStringValue, | |
| ")"); | |
| List<String> queryResultStrings = | |
| ImmutableList.copyOf(getQueryResult("//query:gen").split("\n")); | |
| if (expectVisibilityDep) { | |
| assertThat(queryResultStrings).contains("//foo:pg"); | |
| } else { | |
| assertThat(queryResultStrings).doesNotContain("//foo:pg"); | |
| } | |
| } | |
| @Test | |
| public void testNodepDeps_defaultIsFalse() throws Exception { | |
| runNodepDepsTest(/* optsStringValue= */ "[]", /* expectVisibilityDep= */ false); | |
| } | |
| @Test | |
| public void testNodepDeps_false() throws Exception { | |
| runNodepDepsTest( | |
| /* optsStringValue= */ "['--nodep_deps=false']", /* expectVisibilityDep= */ false); | |
| } | |
| @Test | |
| public void testNodepDeps_true() throws Exception { | |
| runNodepDepsTest( | |
| /* optsStringValue= */ "['--nodep_deps=true']", /* expectVisibilityDep= */ true); | |
| } | |
| @Test | |
| public void testLoadingPhaseCycle() throws Exception { | |
| // This test uses a target in a self-cycle to demonstrate that a genquery rule having a cycle in | |
| // its scope causes it to fail, unless --experimental_skip_ttvs_for_genquery is used. | |
| write( | |
| "cycle/BUILD", | |
| "genquery(", | |
| " name = 'gen',", | |
| " expression = '//cycle',", | |
| " scope = [':cycle'],", | |
| ")", | |
| "sh_library(name = 'cycle', deps = [':cycle'])"); | |
| if (ttvFree) { | |
| assertQueryResult("//cycle:gen", "//cycle:cycle"); | |
| } else { | |
| assertThrows(expectedExceptionClass(), () -> buildTarget("//cycle:gen")); | |
| assertContainsEvent( | |
| events.collector(), "in sh_library rule //cycle:cycle: cycle in dependency graph"); | |
| } | |
| } | |
| protected void writeAspectDefinition(String aspectPackage, String extraDep) throws Exception { | |
| write(aspectPackage + "/BUILD"); | |
| write( | |
| aspectPackage + "/aspect.bzl", | |
| "def _aspect_impl(target, ctx):", | |
| " return struct()", | |
| "def _rule_impl(ctx):", | |
| " return struct()", | |
| "MyAspect = aspect(", | |
| " implementation=_aspect_impl,", | |
| " attr_aspects=['deps'],", | |
| " attrs = {'_extra_deps': attr.label(default = Label('" + extraDep + "'))})", | |
| "aspect_rule = rule(", | |
| " implementation=_rule_impl,", | |
| " attrs = { 'attr' : ", | |
| " attr.label_list(mandatory=True, allow_files=True, aspects = [MyAspect]),", | |
| " 'param' : attr.string(),", | |
| " },", | |
| ")"); | |
| } | |
| @Test | |
| public void testAspectDepChain() throws Exception { | |
| writeAspectDefinition("aspect1", "//middle"); | |
| writeAspectDefinition("aspect2", "//end"); | |
| write( | |
| "start/BUILD", | |
| "load('//aspect1:aspect.bzl', 'aspect_rule')", | |
| "genquery(", | |
| " name = 'gen',", | |
| " expression = 'deps(//start)',", | |
| " scope = [':start'],", | |
| ")", | |
| "aspect_rule(name = 'start', attr = [':startdep'])", | |
| "sh_library(name = 'startdep')"); | |
| write( | |
| "middle/BUILD", | |
| "load('//aspect2:aspect.bzl', 'aspect_rule')", | |
| "aspect_rule(name = 'middle', attr = [':middledep'])", | |
| "sh_library(name = 'middledep')"); | |
| write( | |
| "end/BUILD", "sh_library(name = 'end', deps = [':enddep'])", "sh_library(name = 'enddep')"); | |
| assertQueryResult( | |
| "//start:gen", | |
| "//end:end", | |
| "//end:enddep", | |
| "//middle:middle", | |
| "//middle:middledep", | |
| "//start:start", | |
| "//start:startdep"); | |
| } | |
| @Test | |
| public void testGenQueryOutputCompressed() throws Exception { | |
| write( | |
| "fruits/BUILD", | |
| "sh_library(name='melon', deps=[':papaya'])", | |
| "sh_library(name='papaya')", | |
| "genquery(name='q',", | |
| " scope=[':melon'],", | |
| " compressed_output=True,", | |
| " expression='deps(//fruits:melon)')"); | |
| buildTarget("//fruits:q"); | |
| Artifact output = Iterables.getOnlyElement(getArtifacts("//fruits:q")); | |
| ByteString compressedContent = readContentAsByteArray(output); | |
| ByteArrayOutputStream decompressedOut = new ByteArrayOutputStream(); | |
| try (GZIPInputStream gzipIn = new GZIPInputStream(compressedContent.newInput())) { | |
| ByteStreams.copy(gzipIn, decompressedOut); | |
| } | |
| assertThat(decompressedOut.toString(UTF_8)).isEqualTo("//fruits:melon\n//fruits:papaya\n"); | |
| } | |
| private void assertQueryResult(String queryTarget, String... expected) throws Exception { | |
| assertThat(getQueryResult(queryTarget).split("\n")) | |
| .asList() | |
| .containsExactlyElementsIn(ImmutableList.copyOf(expected)) | |
| .inOrder(); | |
| } | |
| private void assertPartialQueryResult(String queryTarget, String... expected) throws Exception { | |
| assertThat(getQueryResult(queryTarget).split("\n")) | |
| .asList() | |
| .containsAtLeastElementsIn(ImmutableList.copyOf(expected)) | |
| .inOrder(); | |
| } | |
| private String getQueryResult(String queryTarget) throws Exception { | |
| var unused = buildTarget(queryTarget); | |
| Artifact output = Iterables.getOnlyElement(getArtifacts(queryTarget)); | |
| assertThat( | |
| getSkyframeExecutor().getEvaluator().getValues().keySet().stream() | |
| .anyMatch(key -> key instanceof TransitiveTargetKey)) | |
| .isEqualTo(!ttvFree); | |
| return readContentAsLatin1String(output); | |
| } | |
| private Class<? extends Throwable> expectedExceptionClass() { | |
| return keepGoing ? BuildFailedException.class : ViewCreationFailedException.class; | |
| } | |
| } |