aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test
diff options
context:
space:
mode:
authorGravatar Dmitry Lomov <dslomov@google.com>2015-11-23 14:55:13 +0000
committerGravatar Philipp Wollermann <philwo@google.com>2015-11-24 14:40:50 +0000
commit021a3657b5dfed6961ec18586b1bdcfebd693f95 (patch)
tree4394483da7e9175763606d4d7f18f723bcf7abba /src/test
parent7c507bebbfc53225d54db07e2f2098054652f4f7 (diff)
Open-source BuildViewTest.
-- MOS_MIGRATED_REVID=108501464
Diffstat (limited to 'src/test')
-rw-r--r--src/test/java/com/google/devtools/build/lib/BUILD8
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java795
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java172
3 files changed, 975 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 0ce1d00fb1..b8f4cfacbe 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -296,6 +296,9 @@ java_library(
"MOCK_CROSSTOOL",
],
tags = ["analysis"],
+ exports = [
+ "//src/test/java/com/google/devtools/build/skyframe:testutil",
+ ],
deps = [
":actions_testutil",
":foundations_testutil",
@@ -314,9 +317,12 @@ java_library(
"//src/main/java/com/google/devtools/build/lib:util",
"//src/main/java/com/google/devtools/build/lib:vfs",
"//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/query2",
+ "//src/main/java/com/google/devtools/build/lib/rules/genquery",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/common/options",
"//src/main/protobuf:extra_actions_base_proto",
+ "//src/test/java/com/google/devtools/build/skyframe:testutil",
"//third_party:guava",
"//third_party:guava-testlib",
"//third_party:jsr305",
@@ -450,8 +456,10 @@ java_test(
"//src/main/java/com/google/devtools/build/lib:collect",
"//src/main/java/com/google/devtools/build/lib:events",
"//src/main/java/com/google/devtools/build/lib:packages",
+ "//src/main/java/com/google/devtools/build/lib:util",
"//src/main/java/com/google/devtools/build/lib:vfs",
"//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/skyframe",
"//third_party:guava",
"//third_party:guava-testlib",
"//third_party:jsr305",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
new file mode 100644
index 0000000000..c725c12731
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
@@ -0,0 +1,795 @@
+// Copyright 2015 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.analysis;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.testutil.MoreAsserts.assertEventCount;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+import com.google.common.truth.Truth;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Actions;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.FailAction;
+import com.google.devtools.build.lib.analysis.DependencyResolver.Dependency;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestBase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Aspect;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.pkgcache.LoadingFailedException;
+import com.google.devtools.build.lib.skyframe.SkyFunctions;
+import com.google.devtools.build.lib.skyframe.TargetPatternValue.TargetPatternKey;
+import com.google.devtools.build.lib.testutil.Suite;
+import com.google.devtools.build.lib.testutil.TestSpec;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.DeterministicInMemoryGraph;
+import com.google.devtools.build.skyframe.NotifyingInMemoryGraph;
+import com.google.devtools.build.skyframe.NotifyingInMemoryGraph.EventType;
+import com.google.devtools.build.skyframe.NotifyingInMemoryGraph.Listener;
+import com.google.devtools.build.skyframe.NotifyingInMemoryGraph.Order;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.TrackingAwaiter;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * Tests for the {@link BuildView}.
+ */
+@TestSpec(size = Suite.SMALL_TESTS)
+public final class BuildViewTest extends BuildViewTestBase {
+
+ public void testRuleConfiguredTarget() throws Exception {
+ scratch.file("pkg/BUILD",
+ "genrule(name='foo', ",
+ " cmd = '',",
+ " srcs=['a.src'],",
+ " outs=['a.out'])");
+ update("//pkg:foo");
+ Rule ruleTarget = (Rule) getTarget("//pkg:foo");
+ assertEquals("genrule", ruleTarget.getRuleClass());
+
+ ConfiguredTarget ruleCT = getConfiguredTarget("//pkg:foo");
+
+ assertSame(ruleTarget, ruleCT.getTarget());
+ }
+
+ public void testFilterByTargets() throws Exception {
+ scratch.file("tests/BUILD",
+ "sh_test(name = 'small_test_1',",
+ " srcs = ['small_test_1.sh'],",
+ " data = [':xUnit'],",
+ " size = 'small',",
+ " tags = ['tag1'])",
+ "",
+ "sh_test(name = 'small_test_2',",
+ " srcs = ['small_test_2.sh'],",
+ " size = 'small',",
+ " tags = ['tag2'])",
+ "",
+ "",
+ "test_suite( name = 'smallTests', tags=['small'])");
+ //scratch.file("tests/small_test_1.py");
+
+ update("//tests:smallTests");
+
+ ConfiguredTarget test1 = getConfiguredTarget("//tests:small_test_1");
+ ConfiguredTarget test2 = getConfiguredTarget("//tests:small_test_2");
+ ConfiguredTarget suite = getConfiguredTarget("//tests:smallTests");
+ assertNoEvents(); // start from a clean slate
+
+
+ Collection<ConfiguredTarget> targets =
+ new LinkedHashSet<>(ImmutableList.of(test1, test2, suite));
+ targets = Lists.newArrayList(
+ BuildView.filterTestsByTargets(targets,
+ Sets.newHashSet(test1.getTarget(), suite.getTarget())));
+ assertThat(targets).containsExactlyElementsIn(Sets.newHashSet(test1, suite));
+ }
+
+ public void testSourceArtifact() throws Exception {
+ setupDummyRule();
+ update("//pkg:a.src");
+ InputFileConfiguredTarget inputCT = getInputFileConfiguredTarget("//pkg:a.src");
+ Artifact inputArtifact = inputCT.getArtifact();
+ assertNull(getGeneratingAction(inputArtifact));
+ assertEquals("pkg/a.src", inputArtifact.getExecPathString());
+ }
+
+ public void testGeneratedArtifact() throws Exception {
+ setupDummyRule();
+ update("//pkg:a.out");
+ OutputFileConfiguredTarget outputCT = (OutputFileConfiguredTarget)
+ getConfiguredTarget("//pkg:a.out");
+ Artifact outputArtifact = outputCT.getArtifact();
+ assertEquals(getTargetConfiguration().getBinDirectory(), outputArtifact.getRoot());
+ assertEquals(getTargetConfiguration().getBinFragment().getRelative("pkg/a.out"),
+ outputArtifact.getExecPath());
+ assertEquals(new PathFragment("pkg/a.out"), outputArtifact.getRootRelativePath());
+
+ Action action = getGeneratingAction(outputArtifact);
+ assertSame(FailAction.class, action.getClass());
+ }
+
+ // TODO(bazel-team): this test is bad, it seems to rely on genrule emitting a warning to make the
+ // analysis fail, this needs a proper way to inject errors/warnings
+ public void disabled_testReportsAnalysisRootCauses() throws Exception {
+ scratch.file("pkg/BUILD",
+ "genrule(name='foo',",
+ " tools=[:missing],",
+ " outs=['foofile'],",
+ " cmd='')",
+ "genrule(name='bar',",
+ " srcs=['foofile'],",
+ " outs=['barfile'],",
+ " cmd='')");
+
+ reporter.removeHandler(failFastHandler);
+ EventBus eventBus = new EventBus();
+ AnalysisFailureRecorder recorder = new AnalysisFailureRecorder();
+ eventBus.register(recorder);
+ update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//pkg:bar");
+ assertThat(recorder.events).hasSize(1);
+ AnalysisFailureEvent event = recorder.events.get(0);
+ assertEquals("//pkg:foo", event.getFailureReason().toString());
+ assertEquals("//pkg:bar", event.getFailedTarget().getLabel().toString());
+ }
+
+ public void testReportsLoadingRootCauses() throws Exception {
+ scratch.file("pkg/BUILD",
+ "genrule(name='foo',",
+ " tools=['//nopackage:missing'],",
+ " cmd='')");
+
+ reporter.removeHandler(failFastHandler);
+ EventBus eventBus = new EventBus();
+ LoadingFailureRecorder recorder = new LoadingFailureRecorder();
+ eventBus.register(recorder);
+ // Note: no need to run analysis for a loading failure.
+ update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//pkg:foo");
+ assertThat(recorder.events)
+ .contains(
+ Pair.of(Label.parseAbsolute("//pkg:foo"), Label.parseAbsolute("//nopackage:missing")));
+ assertContainsEvent("missing value for mandatory attribute 'outs'");
+ assertContainsEvent("no such package 'nopackage'");
+ // Skyframe correctly reports the other root cause as the genrule itself (since it is
+ // missing attributes).
+ assertThat(recorder.events).hasSize(2);
+ assertThat(recorder.events)
+ .contains(Pair.of(Label.parseAbsolute("//pkg:foo"), Label.parseAbsolute("//pkg:foo")));
+ }
+
+ public void testConvolutedLoadRootCauseAnalysis() throws Exception {
+ // You need license declarations in third_party. We use this constraint to
+ // create targets that are loadable, but are in error.
+ scratch.file("third_party/first/BUILD",
+ "sh_library(name='first', deps=['//third_party/second'], licenses=['notice'])");
+ scratch.file("third_party/second/BUILD",
+ "sh_library(name='second', deps=['//third_party/third'], licenses=['notice'])");
+ scratch.file("third_party/third/BUILD",
+ "sh_library(name='third', deps=['//third_party/fourth'], licenses=['notice'])");
+ scratch.file("third_party/fourth/BUILD",
+ "sh_library(name='fourth', deps=['//third_party/fifth'])");
+ scratch.file("third_party/fifth/BUILD",
+ "sh_library(name='fifth', licenses=['notice'])");
+ reporter.removeHandler(failFastHandler);
+ EventBus eventBus = new EventBus();
+ LoadingFailureRecorder recorder = new LoadingFailureRecorder();
+ eventBus.register(recorder);
+ // Note: no need to run analysis for a loading failure.
+ update(eventBus, defaultFlags().with(Flag.KEEP_GOING),
+ "//third_party/first", "//third_party/third");
+ assertThat(recorder.events).hasSize(2);
+ assertTrue(recorder.events.toString(), recorder.events.contains(
+ Pair.of(Label.parseAbsolute("//third_party/first"),
+ Label.parseAbsolute("//third_party/fourth"))));
+ assertThat(recorder.events)
+ .contains(Pair.of(
+ Label.parseAbsolute("//third_party/third"),
+ Label.parseAbsolute("//third_party/fourth")));
+ }
+
+ public void testMultipleRootCauseReporting() throws Exception {
+ scratch.file("gp/BUILD",
+ "sh_library(name = 'gp', deps = ['//p:p'])");
+ scratch.file("p/BUILD",
+ "sh_library(name = 'p', deps = ['//c1:not', '//c2:not'])");
+ scratch.file("c1/BUILD");
+ scratch.file("c2/BUILD");
+ reporter.removeHandler(failFastHandler);
+ EventBus eventBus = new EventBus();
+ LoadingFailureRecorder recorder = new LoadingFailureRecorder();
+ eventBus.register(recorder);
+ update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//gp");
+ assertThat(recorder.events).hasSize(2);
+ assertTrue(recorder.events.toString(), recorder.events.contains(
+ Pair.of(Label.parseAbsolute("//gp"),
+ Label.parseAbsolute("//c1:not"))));
+ assertThat(recorder.events)
+ .contains(Pair.of(Label.parseAbsolute("//gp"), Label.parseAbsolute("//c2:not")));
+ }
+
+ /**
+ * Regression test for: "Package group includes are broken"
+ */
+ public void testTopLevelPackageGroup() throws Exception {
+ scratch.file("tropical/BUILD",
+ "package_group(name='guava', includes=[':mango'])",
+ "package_group(name='mango')");
+
+ // If the analysis phase results in an error, this will throw an exception
+ update("//tropical:guava");
+
+ // Check if the included package group also got analyzed
+ assertNotNull(getConfiguredTarget("//tropical:mango", null));
+ }
+
+ public void testTopLevelInputFile() throws Exception {
+ scratch.file("tropical/BUILD",
+ "exports_files(['file.txt'])");
+ update("//tropical:file.txt");
+ assertNotNull(getConfiguredTarget("//tropical:file.txt", null));
+ }
+
+ public void testGetDirectPrerequisites() throws Exception {
+ scratch.file(
+ "package/BUILD",
+ "filegroup(name='top', srcs=[':inner', 'file'])",
+ "sh_binary(name='inner', srcs=['script.sh'])");
+ update("//package:top");
+ ConfiguredTarget top = getConfiguredTarget("//package:top", getTargetConfiguration());
+ Iterable<ConfiguredTarget> targets = getView().getDirectPrerequisitesForTesting(
+ reporter, top, getBuildConfigurationCollection());
+ Iterable<Label> labels =
+ Iterables.transform(
+ targets,
+ new Function<ConfiguredTarget, Label>() {
+ @Override
+ public Label apply(ConfiguredTarget target) {
+ return target.getLabel();
+ }
+ });
+ assertThat(labels)
+ .containsExactly(
+ Label.parseAbsolute("//package:inner"), Label.parseAbsolute("//package:file"));
+ }
+
+ public void testGetDirectPrerequisiteDependencies() throws Exception {
+ scratch.file(
+ "package/BUILD",
+ "filegroup(name='top', srcs=[':inner', 'file'])",
+ "sh_binary(name='inner', srcs=['script.sh'])");
+ update("//package:top");
+ ConfiguredTarget top = getConfiguredTarget("//package:top", getTargetConfiguration());
+ Iterable<Dependency> targets = getView().getDirectPrerequisiteDependenciesForTesting(
+ reporter, top, null, getBuildConfigurationCollection());
+
+ Dependency innerDependency;
+ Dependency fileDependency;
+ if (top.getConfiguration().useDynamicConfigurations()) {
+ innerDependency =
+ new Dependency(
+ Label.parseAbsolute("//package:inner"),
+ Attribute.ConfigurationTransition.NONE,
+ ImmutableSet.<Aspect>of());
+ fileDependency =
+ new Dependency(
+ Label.parseAbsolute("//package:file"),
+ Attribute.ConfigurationTransition.NONE,
+ ImmutableSet.<Aspect>of());
+ } else {
+ innerDependency =
+ new Dependency(
+ Label.parseAbsolute("//package:inner"),
+ getTargetConfiguration(),
+ ImmutableSet.<Aspect>of());
+ fileDependency =
+ new Dependency(
+ Label.parseAbsolute("//package:file"),
+ (BuildConfiguration) null,
+ ImmutableSet.<Aspect>of());
+ }
+
+ assertThat(targets).containsExactly(innerDependency, fileDependency);
+ }
+
+ /**
+ * Tests that the {@code --configuration short name} option cannot be used on
+ * the command line.
+ */
+ public void testConfigurationShortName() throws Exception {
+ useConfiguration("--output directory name=foo");
+ reporter.removeHandler(failFastHandler);
+ try {
+ update(defaultFlags());
+ fail();
+ } catch (InvalidConfigurationException e) {
+ assertThat(e).hasMessage("Build options are invalid");
+ assertContainsEvent(
+ "The internal '--output directory name' option cannot be used on the command line");
+ }
+ }
+
+ public void testFileTranslations() throws Exception {
+ scratch.file("foo/file");
+ scratch.file("foo/BUILD",
+ "exports_files(['file'])");
+ useConfiguration("--message_translations=//foo:file");
+ scratch.file("bar/BUILD",
+ "sh_library(name = 'bar')");
+ update("//bar");
+ }
+
+ // Regression test: "output_filter broken (but in a different way)"
+ public void testOutputFilterSeeWarning() throws Exception {
+ runAnalysisWithOutputFilter(Pattern.compile(".*"));
+ assertContainsEvent("please do not import '//java/a:A.java'");
+ }
+
+ // Regression test: "output_filter broken (but in a different way)"
+ public void testOutputFilter() throws Exception {
+ runAnalysisWithOutputFilter(Pattern.compile("^//java/c"));
+ assertNoEvents();
+ }
+
+ public void testAnalysisErrorMessageWithKeepGoing() throws Exception {
+ scratch.file("a/BUILD", "sh_binary(name='a', srcs=['a1.sh', 'a2.sh'])");
+ reporter.removeHandler(failFastHandler);
+ update(defaultFlags().with(Flag.KEEP_GOING), "//a");
+ assertContainsEvent("errors encountered while analyzing target '//a:a'");
+ }
+
+ /**
+ * Regression test: Exception in ConfiguredTargetGraph.checkForCycles()
+ * when multiple top-level targets depend on the same cycle.
+ */
+ public void testCircularDependencyBelowTwoTargets() throws Exception {
+ scratch.file("foo/BUILD",
+ "sh_library(name = 'top1', srcs = ['top1.sh'], deps = [':rec1'])",
+ "sh_library(name = 'top2', srcs = ['top2.sh'], deps = [':rec1'])",
+ "sh_library(name = 'rec1', srcs = ['rec1.sh'], deps = [':rec2'])",
+ "sh_library(name = 'rec2', srcs = ['rec2.sh'], deps = [':rec1'])"
+ );
+ reporter.removeHandler(failFastHandler);
+ update(defaultFlags().with(Flag.KEEP_GOING), "//foo:top1", "//foo:top2");
+ assertContainsEvent("in sh_library rule //foo:rec1: cycle in dependency graph:\n");
+ assertContainsEvent("in sh_library rule //foo:top");
+ }
+
+ // Regression test: cycle node depends on error.
+ // Note that this test can have nondeterministic behavior in Skyframe, depending on if the cycle
+ // is detected during the bubbling-up phase.
+ public void testErrorBelowCycle() throws Exception {
+ scratch.file("foo/BUILD",
+ "sh_library(name = 'top', deps = ['mid'])",
+ "sh_library(name = 'mid', deps = ['bad', 'cycle1'])",
+ "sh_library(name = 'bad', srcs = ['//badbuild:isweird'])",
+ "sh_library(name = 'cycle1', deps = ['cycle2', 'mid'])",
+ "sh_library(name = 'cycle2', deps = ['cycle1'])");
+ scratch.file("badbuild/BUILD", "");
+ reporter.removeHandler(failFastHandler);
+ try {
+ update("//foo:top");
+ fail();
+ } catch (LoadingFailedException e) {
+ // Expected.
+ }
+ assertContainsEvent("no such target '//badbuild:isweird': target 'isweird' not declared in "
+ + "package 'badbuild'");
+ assertContainsEvent("and referenced by '//foo:bad'");
+ if (eventCollector.count() > 1) {
+ assertContainsEvent("in sh_library rule //foo");
+ assertContainsEvent("cycle in dependency graph");
+ assertEventCount(3, eventCollector);
+ }
+ }
+
+ public void testAnalysisEntryHasActionsEvenWithError() throws Exception {
+ scratch.file("foo/BUILD",
+ "cc_binary(name = 'foo', linkshared = 1, srcs = ['foo.cc'])");
+ reporter.removeHandler(failFastHandler);
+ try {
+ update("//foo:foo");
+ fail(); // Expected ViewCreationFailedException.
+ } catch (ViewCreationFailedException e) {
+ // ok.
+ }
+ }
+
+ public void testHelpfulErrorForWrongPackageLabels() throws Exception {
+ reporter.removeHandler(failFastHandler);
+
+ scratch.file("x/BUILD",
+ "cc_library(name='x', srcs=['x.cc'])");
+ scratch.file("y/BUILD",
+ "cc_library(name='y', srcs=['y.cc'], deps=['//x:z'])");
+
+ update(defaultFlags().with(Flag.KEEP_GOING), "//y:y");
+ assertContainsEvent("no such target '//x:z': "
+ + "target 'z' not declared in package 'x' "
+ + "defined by /workspace/x/BUILD and referenced by '//y:y'");
+ }
+
+ public void testNewActionsAreDifferentAndDontConflict() throws Exception {
+ scratch.file("pkg/BUILD",
+ "genrule(name='a', ",
+ " cmd='',",
+ " outs=['a.out'])");
+ update("//pkg:a.out");
+ OutputFileConfiguredTarget outputCT = (OutputFileConfiguredTarget)
+ getConfiguredTarget("//pkg:a.out");
+ Artifact outputArtifact = outputCT.getArtifact();
+ Action action = getGeneratingAction(outputArtifact);
+ assertNotNull(action);
+ scratch.overwriteFile("pkg/BUILD",
+ "genrule(name='a', ",
+ " cmd='false',",
+ " outs=['a.out'])");
+ update("//pkg:a.out");
+ assertFalse("Actions should not be compatible",
+ Actions.canBeShared(action, getGeneratingAction(outputArtifact)));
+ }
+
+ /**
+ * This test exercises the case where we invalidate (mark dirty) a node in one build command
+ * invocation and the revalidation (because it did not change) happens in a subsequent build
+ * command call.
+ *
+ * - In the first update call we construct A.
+ *
+ * - Then we construct B and we make the glob get invalidated. We do that by deleting a file
+ * because it depends on the directory listing. Because of that A gets invalidated.
+ *
+ * - Then we construct A again. The glob gets revalidated because it is still matching just A.java
+ * and A configured target gets revalidated too. At the end of the analysis A java action should
+ * be in the action graph.
+ */
+ public void testMultiBuildInvalidationRevalidation() throws Exception {
+ scratch.file("java/a/A.java", "bla1");
+ scratch.file("java/a/C.java", "bla2");
+ scratch.file("java/a/BUILD",
+ "java_test(name = 'A',",
+ " srcs = glob(['A*.java']))",
+ "java_test(name = 'B',",
+ " srcs = ['B.java'])");
+ update("//java/a:A");
+ ConfiguredTarget ct = getConfiguredTarget("//java/a:A");
+ scratch.deleteFile("java/a/C.java");
+ update("//java/a:B");
+ update("//java/a:A");
+ assertNotNull(getGeneratingAction(
+ getBinArtifact("A_deploy.jar", ct)));
+ }
+
+ /**
+ * Regression test: ClassCastException in SkyframeLabelVisitor.updateRootCauses.
+ */
+ public void testDepOnGoodTargetInBadPkgAndTransitivelyBadTarget() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("parent/BUILD",
+ "sh_library(name = 'foo',",
+ " srcs = ['//badpkg1:okay-target', '//okaypkg:transitively-bad-target'])");
+ Path badpkg1BuildFile = scratch.file("badpkg1/BUILD",
+ "exports_files(['okay-target'])",
+ "invalidbuildsyntax");
+ scratch.file("okaypkg/BUILD",
+ "sh_library(name = 'transitively-bad-target',",
+ " srcs = ['//badpkg2:bad-target'])");
+ Path badpkg2BuildFile = scratch.file("badpkg2/BUILD",
+ "sh_library(name = 'bad-target')",
+ "invalidbuildsyntax");
+ update(defaultFlags().with(Flag.KEEP_GOING), "//parent:foo");
+ assertEquals(1, getFrequencyOfErrorsWithLocation(
+ badpkg1BuildFile.asFragment(), eventCollector));
+ assertEquals(1, getFrequencyOfErrorsWithLocation(
+ badpkg2BuildFile.asFragment(), eventCollector));
+ }
+
+ public void testDepOnGoodTargetInBadPkgAndTransitiveCycle_NotIncremental() throws Exception {
+ runTestDepOnGoodTargetInBadPkgAndTransitiveCycle(/*incremental=*/false);
+ }
+
+ public void testDepOnGoodTargetInBadPkgAndTransitiveCycle_Incremental() throws Exception {
+ runTestDepOnGoodTargetInBadPkgAndTransitiveCycle(/*incremental=*/true);
+ }
+
+ /**
+ * Regression test: in keep_going mode, cycles in target graph aren't reported
+ * if package is in error.
+ */
+ public void testCycleReporting_TargetCycleWhenPackageInError() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("cycles/BUILD",
+ "sh_library(name = 'a', deps = [':b'])",
+ "sh_library(name = 'b', deps = [':a'])",
+ "notvalidbuildsyntax");
+ update(defaultFlags().with(Flag.KEEP_GOING), "//cycles:a");
+ assertContainsEvent("'notvalidbuildsyntax'");
+ assertContainsEvent("cycle in dependency graph");
+ }
+
+ public void testTransitiveLoadingDoesntShortCircuitInKeepGoing() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("parent/BUILD",
+ "sh_library(name = 'a', deps = ['//child:b'])",
+ "parentisbad");
+ scratch.file("child/BUILD",
+ "sh_library(name = 'b')",
+ "childisbad");
+ update(defaultFlags().with(Flag.KEEP_GOING), "//parent:a");
+ assertContainsEventWithFrequency("parentisbad", 1);
+ assertContainsEventWithFrequency("childisbad", 1);
+ assertContainsEventWithFrequency("and referenced by '//parent:a'", 1);
+ }
+
+ /**
+ * Smoke test for the Skyframe code path.
+ */
+ public void testSkyframe() throws Exception {
+ setupDummyRule();
+ String aoutLabel = "//pkg:a.out";
+
+ update(aoutLabel);
+
+ // However, a ConfiguredTarget was actually produced.
+ ConfiguredTarget target = Iterables.getOnlyElement(getAnalysisResult().getTargetsToBuild());
+ assertEquals(aoutLabel, target.getLabel().toString());
+
+ Artifact aout = Iterables.getOnlyElement(
+ target.getProvider(FileProvider.class).getFilesToBuild());
+ Action action = getGeneratingAction(aout);
+ assertSame(FailAction.class, action.getClass());
+ }
+
+ /**
+ * ConfiguredTargetFunction should not register actions in legacy Blaze ActionGraph unless
+ * the creation of the node is successful.
+ */
+ public void testActionsNotRegisteredInLegacyWhenError() throws Exception {
+ // First find the artifact we want to make sure is not generated by an action with an error.
+ // Then update the BUILD file and re-analyze.
+ scratch.file("actions_not_registered/BUILD",
+ "cc_binary(name = 'foo', srcs = ['foo.cc'])");
+ update("//actions_not_registered:foo");
+ Artifact fooOut = Iterables.getOnlyElement(
+ getConfiguredTarget("//actions_not_registered:foo")
+ .getProvider(FileProvider.class).getFilesToBuild());
+ assertNotNull(getActionGraph().getGeneratingAction(fooOut));
+ clearAnalysisResult();
+
+ scratch.overwriteFile("actions_not_registered/BUILD",
+ "cc_binary(name = 'foo', linkshared = 1, srcs = ['foo.cc'])");
+
+ reporter.removeHandler(failFastHandler);
+
+ try {
+ update("//actions_not_registered:foo");
+ fail("This build should fail because: 'linkshared' used in non-shared library");
+ } catch (ViewCreationFailedException e) {
+ assertNull(getActionGraph().getGeneratingAction(fooOut));
+ }
+ }
+
+ /**
+ * Regression test:
+ * "skyframe: ArtifactFactory and ConfiguredTargets out of sync".
+ */
+ public void testSkyframeAnalyzeRuleThenItsOutputFile() throws Exception {
+ scratch.file("pkg/BUILD",
+ "testing_dummy_rule(name='foo', ",
+ " srcs=['a.src'],",
+ " outs=['a.out'])");
+
+ scratch.file("pkg2/BUILD",
+ "testing_dummy_rule(name='foo', ",
+ " srcs=['a.src'],",
+ " outs=['a.out'])");
+ String aoutLabel = "//pkg:a.out";
+
+ update("//pkg2:foo");
+ update("//pkg:foo");
+ scratch.overwriteFile("pkg2/BUILD",
+ "testing_dummy_rule(name='foo', ",
+ " srcs=['a.src'],",
+ " outs=['a.out'])",
+ "# Comment");
+
+ update("//pkg:a.out");
+
+ // However, a ConfiguredTarget was actually produced.
+ ConfiguredTarget target = Iterables.getOnlyElement(getAnalysisResult().getTargetsToBuild());
+ assertEquals(aoutLabel, target.getLabel().toString());
+
+ Artifact aout = Iterables.getOnlyElement(
+ target.getProvider(FileProvider.class).getFilesToBuild());
+ Action action = getGeneratingAction(aout);
+ assertSame(FailAction.class, action.getClass());
+ }
+
+ /**
+ * Tests that skyframe reports the root cause as being the target that depended on the symlink
+ * cycle.
+ */
+ public void testRootCauseReportingFileSymlinks() throws Exception {
+ scratch.file("gp/BUILD",
+ "sh_library(name = 'gp', deps = ['//p'])");
+ scratch.file("p/BUILD",
+ "sh_library(name = 'p', deps = ['//c'])");
+ scratch.file("c/BUILD",
+ "sh_library(name = 'c', deps = [':c1', ':c2'])",
+ "sh_library(name = 'c1', deps = ['//cycles1'])",
+ "sh_library(name = 'c2', deps = ['//cycles2'])");
+ Path cycles1BuildFilePath = scratch.file("cycles1/BUILD",
+ "sh_library(name = 'cycles1', srcs = glob(['*.sh']))");
+ Path cycles2BuildFilePath = scratch.file("cycles2/BUILD",
+ "sh_library(name = 'cycles2', srcs = glob(['*.sh']))");
+ cycles1BuildFilePath.getParentDirectory().getRelative("cycles1.sh").createSymbolicLink(
+ new PathFragment("cycles1.sh"));
+ cycles2BuildFilePath.getParentDirectory().getRelative("cycles2.sh").createSymbolicLink(
+ new PathFragment("cycles2.sh"));
+ reporter.removeHandler(failFastHandler);
+ EventBus eventBus = new EventBus();
+ LoadingFailureRecorder recorder = new LoadingFailureRecorder();
+ eventBus.register(recorder);
+ update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//gp");
+ assertThat(recorder.events).hasSize(2);
+ assertTrue(recorder.events.toString(), recorder.events.contains(
+ Pair.of(Label.parseAbsolute("//gp"), Label.parseAbsolute("//cycles1"))));
+ assertTrue(recorder.events.toString(), recorder.events.contains(
+ Pair.of(Label.parseAbsolute("//gp"), Label.parseAbsolute("//cycles2"))));
+ }
+
+ /**
+ * Regression test for bug when a configured target has missing deps, but also depends
+ * transitively on an error. We build //foo:query, which depends on a valid and an invalid target
+ * pattern. We ensure that by the time it requests its dependent target patterns, the invalid one
+ * is ready, and throws (though not before the request is registered). Then, when bubbling the
+ * invalid target pattern error up, we ensure that it bubbles into //foo:query, which must cope
+ * with the combination of an error and a missing dep.
+ */
+ public void testGenQueryWithBadTargetAndUnfinishedTarget() throws Exception {
+ // The target //foo:zquery is used to force evaluation of //foo:nosuchtarget before the target
+ // patterns in //foo:query are enqueued for evaluation. That way, //foo:query will depend on one
+ // invalid target pattern and two target patterns that haven't been evaluated yet.
+ // It is important that 'query' come before 'zquery' alphabetically, so that when the error is
+ // bubbling up, it goes to the //foo:query node -- we use a graph implementation in which the
+ // reverse deps of each entry are ordered alphabetically. It is also important that a missing
+ // target pattern is requested before the exception is thrown, so we have both //foo:b and
+ // //foo:z missing from the deps, in the hopes that at least one of them will come before
+ // //foo:nosuchtarget.
+ scratch.file("foo/BUILD",
+ "genquery(name = 'query',",
+ " expression = 'deps(//foo:b) except //foo:nosuchtarget except //foo:z',",
+ " scope = ['//foo:a'])",
+ "genquery(name = 'zquery',",
+ " expression = 'deps(//foo:nosuchtarget)',",
+ " scope = ['//foo:a'])",
+ "sh_library(name = 'a')",
+ "sh_library(name = 'b')",
+ "sh_library(name = 'z')"
+ );
+ Listener listener =
+ new Listener() {
+ private final CountDownLatch errorDone = new CountDownLatch(1);
+ private final CountDownLatch realQueryStarted = new CountDownLatch(1);
+
+ @Override
+ public void accept(SkyKey key, EventType type, Order order, Object context) {
+ if (!key.functionName().equals(SkyFunctions.TARGET_PATTERN)) {
+ return;
+ }
+ String label = ((TargetPatternKey) key.argument()).getPattern();
+ if (label.equals("//foo:nosuchtarget")) {
+ if (type == EventType.SET_VALUE) {
+ // Inform //foo:query-dep-registering thread that it may proceed.
+ errorDone.countDown();
+ // Wait to make sure //foo:query-dep-registering process has started.
+ TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(
+ realQueryStarted, "//foo:query did not request dep in time");
+ } else if (type == EventType.ADD_REVERSE_DEP
+ && context.toString().contains("foo:query")) {
+ // Make sure that when foo:query requests foo:nosuchtarget, it's already done.
+ TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(
+ errorDone, "//foo:nosuchtarget did not evaluate in time");
+ }
+ } else if ((label.equals("//foo:b") || label.equals("//foo:z"))
+ && type == EventType.CREATE_IF_ABSENT) {
+ // Inform error-evaluating thread that it may throw an exception.
+ realQueryStarted.countDown();
+ TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(
+ errorDone, "//foo:nosuchtarget did not evaluate in time");
+ // Don't let the target pattern //foo:{b,z} get enqueued for evaluation until we
+ // receive an interrupt signal from the threadpool. The interrupt means that
+ // evaluation is shutting down, and so //foo:{b,z} definitely won't get evaluated.
+ CountDownLatch waitForInterrupt = new CountDownLatch(1);
+ try {
+ waitForInterrupt.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ throw new IllegalStateException("node was not interrupted in time");
+ } catch (InterruptedException e) {
+ // Expected.
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ };
+ NotifyingInMemoryGraph graph = new DeterministicInMemoryGraph(listener);
+ setGraphForTesting(graph);
+ reporter.removeHandler(failFastHandler);
+ try {
+ update("//foo:query", "//foo:zquery");
+ fail();
+ } catch (ViewCreationFailedException e) {
+ Truth.assertThat(e.getMessage())
+ .contains("Analysis of target '//foo:query' failed; build aborted");
+ }
+ TrackingAwaiter.INSTANCE.assertNoErrors();
+ graph.assertNoExceptions();
+ }
+
+ /**
+ * Tests that rules with configurable attributes can be accessed through {@link
+ * com.google.devtools.build.lib.skyframe.PostConfiguredTargetFunction}.
+ * This is a regression test for a Bazel crash.
+ */
+ public void testPostProcessedConfigurableAttributes() throws Exception {
+ useConfiguration("--force_pic");
+ reporter.removeHandler(failFastHandler); // Expect errors from action conflicts.
+ scratch.file("conflict/BUILD",
+ "config_setting(name = 'a', values = {'test_arg': 'a'})",
+ "cc_library(name='x', srcs=select({':a': ['a.cc'], '//conditions:default': ['foo.cc']}))",
+ "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])");
+ update(defaultFlags().with(Flag.KEEP_GOING), "//conflict:_objs/x/conflict/foo.pic.o",
+ "//conflict:x");
+ // Expect to reach this line without a Precondition-triggered NullPointerException.
+ assertContainsEvent(
+ "file 'conflict/_objs/x/conflict/foo.pic.o' is generated by these conflicting actions");
+ }
+
+ public void testCycleDueToJavaLauncherConfiguration() throws Exception {
+ scratch.file("foo/BUILD",
+ "java_binary(name = 'java', srcs = ['DoesntMatter.java'])",
+ "cc_binary(name = 'cpp', data = [':java'])");
+ // Everything is fine - the dependency graph is acyclic.
+ update(defaultFlags(), "//foo:java", "//foo:cpp");
+ // Now there will be an analysis-phase cycle because the java_binary now has an implicit dep on
+ // the cc_binary launcher.
+ useConfiguration("--java_launcher=//foo:cpp");
+ reporter.removeHandler(failFastHandler);
+ try {
+ update(defaultFlags(), "//foo:java", "//foo:cpp");
+ fail();
+ } catch (ViewCreationFailedException expected) {
+ Truth.assertThat(expected.getMessage())
+ .matches("Analysis of target '//foo:(java|cpp)' failed; build aborted.*");
+ }
+ assertContainsEvent("cycle in dependency graph");
+ assertContainsEvent("This cycle occurred because of a configuration option");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java
new file mode 100644
index 0000000000..4c124e693e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java
@@ -0,0 +1,172 @@
+// Copyright 2015 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.analysis.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.eventbus.Subscribe;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.AnalysisFailureEvent;
+import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventCollector;
+import com.google.devtools.build.lib.events.OutputFilter.RegexOutputFilter;
+import com.google.devtools.build.lib.pkgcache.LoadingFailureEvent;
+import com.google.devtools.build.lib.query2.output.OutputFormatter;
+import com.google.devtools.build.lib.rules.genquery.GenQuery;
+import com.google.devtools.build.lib.skyframe.PrecomputedValue;
+import com.google.devtools.build.lib.skyframe.PrecomputedValue.Injected;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator;
+import com.google.devtools.build.skyframe.NotifyingInMemoryGraph;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Base class for BuildView test cases.
+ */
+public abstract class BuildViewTestBase extends AnalysisTestCase {
+
+ protected static int getFrequencyOfErrorsWithLocation(
+ PathFragment path, EventCollector eventCollector) {
+ int frequency = 0;
+ for (Event event : eventCollector) {
+ if (event.getLocation() != null) {
+ if (path.equals(event.getLocation().getPath())) {
+ frequency++;
+ }
+ }
+ }
+ return frequency;
+ }
+
+ @Override
+ protected ImmutableList<Injected> getPrecomputedValues() {
+ ImmutableList.Builder<Injected> result = ImmutableList.builder();
+ result.addAll(super.getPrecomputedValues());
+ result.add(PrecomputedValue.injected(
+ GenQuery.QUERY_OUTPUT_FORMATTERS, OutputFormatter.getDefaultFormatters()));
+ return result.build();
+ }
+
+ protected final void setupDummyRule() throws Exception {
+ scratch.file("pkg/BUILD",
+ "testing_dummy_rule(name='foo', ",
+ " srcs=['a.src'],",
+ " outs=['a.out'])");
+ }
+
+ protected void runAnalysisWithOutputFilter(Pattern outputFilter) throws Exception {
+ scratch.file("java/a/BUILD",
+ "exports_files(['A.java'])");
+ scratch.file("java/b/BUILD",
+ "java_library(name = 'b', srcs = ['//java/a:A.java'])");
+ scratch.file("java/c/BUILD",
+ "java_library(name = 'c', exports = ['//java/b:b'])");
+ reporter.setOutputFilter(RegexOutputFilter.forPattern(outputFilter));
+ update("//java/c:c");
+ }
+
+ protected Artifact getNativeDepsLibrary(ConfiguredTarget target) throws Exception {
+ return ActionsTestUtil.getFirstArtifactEndingWith(target
+ .getProvider(RunfilesProvider.class)
+ .getDefaultRunfiles()
+ .getAllArtifacts(), "_swigdeps.so");
+ }
+
+ protected void runTestDepOnGoodTargetInBadPkgAndTransitiveCycle(boolean incremental)
+ throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("parent/BUILD",
+ "sh_library(name = 'foo',",
+ " srcs = ['//badpkg:okay-target', '//okaypkg:transitively-a-cycle'])");
+ Path symlinkcycleBuildFile = scratch.file("symlinkcycle/BUILD",
+ "sh_library(name = 'cycle', srcs = glob(['*.sh']))");
+ Path dirPath = symlinkcycleBuildFile.getParentDirectory();
+ dirPath.getRelative("foo.sh").createSymbolicLink(new PathFragment("foo.sh"));
+ scratch.file("okaypkg/BUILD",
+ "sh_library(name = 'transitively-a-cycle',",
+ " srcs = ['//symlinkcycle:cycle'])");
+ Path badpkgBuildFile = scratch.file("badpkg/BUILD",
+ "exports_files(['okay-target'])",
+ "invalidbuildsyntax");
+ if (incremental) {
+ update(defaultFlags().with(Flag.KEEP_GOING), "//okaypkg:transitively-a-cycle");
+ assertContainsEvent("circular symlinks detected");
+ eventCollector.clear();
+ }
+ update(defaultFlags().with(Flag.KEEP_GOING), "//parent:foo");
+ assertEquals(1, getFrequencyOfErrorsWithLocation(badpkgBuildFile.asFragment(), eventCollector));
+ // TODO(nharmata): This test currently only works because each BuildViewTest#update call
+ // dirties all FileNodes that are in error. There is actually a skyframe bug with cycle
+ // reporting on incremental builds (see b/14622820).
+ assertContainsEvent("circular symlinks detected");
+ }
+
+ protected void setGraphForTesting(NotifyingInMemoryGraph notifyingInMemoryGraph) {
+ InMemoryMemoizingEvaluator memoizingEvaluator =
+ (InMemoryMemoizingEvaluator) skyframeExecutor.getEvaluatorForTesting();
+ memoizingEvaluator.setGraphForTesting(notifyingInMemoryGraph);
+ }
+
+ protected void runTestForMultiCpuAnalysisFailure(String badCpu, String goodCpu) throws Exception {
+ reporter.removeHandler(failFastHandler);
+ useConfiguration("--experimental_multi_cpu=" + badCpu + "," + goodCpu);
+ scratch.file("multi/BUILD",
+ "cc_library(name='cpu', abi='$(TARGET_CPU)', abi_deps={'" + badCpu + "':[':fail']})",
+ "genrule(name='fail', outs=['file1', 'file2'], executable = 1, cmd='touch $@')");
+ update(defaultFlags().with(Flag.KEEP_GOING), "//multi:cpu");
+ AnalysisResult result = getAnalysisResult();
+ assertThat(result.getTargetsToBuild()).hasSize(1);
+ ConfiguredTarget targetA = Iterables.get(result.getTargetsToBuild(), 0);
+ assertEquals(goodCpu, targetA.getConfiguration().getCpu());
+ // Unfortunately, we get the same error twice - we can't distinguish the configurations.
+ assertContainsEvent("if genrules produce executables, they are allowed only one output");
+ }
+
+ /**
+ * Record analysis failures.
+ */
+ public static class AnalysisFailureRecorder {
+ @Subscribe
+ public void analysisFailure(AnalysisFailureEvent event) {
+ events.add(event);
+ }
+
+ public final List<AnalysisFailureEvent> events = new ArrayList<>();
+ }
+
+ /**
+ * Record loading failures.
+ */
+ public static class LoadingFailureRecorder {
+ @Subscribe
+ public void loadingFailure(LoadingFailureEvent event) {
+ events.add(Pair.of(event.getFailedTarget(), event.getFailureReason()));
+ }
+
+ public final List<Pair<Label, Label>> events = new ArrayList<>();
+ }
+}