Open-source BuildViewTest.

--
MOS_MIGRATED_REVID=108501464
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 0ce1d00..b8f4cfa 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 @@
         "MOCK_CROSSTOOL",
     ],
     tags = ["analysis"],
+    exports = [
+        "//src/test/java/com/google/devtools/build/skyframe:testutil",
+    ],
     deps = [
         ":actions_testutil",
         ":foundations_testutil",
@@ -314,9 +317,12 @@
         "//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 @@
         "//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 0000000..c725c12
--- /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 0000000..4c124e6
--- /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<>();
+  }
+}