Add support for exclusive test with Skymeld.

PiperOrigin-RevId: 440939395
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
index 07d0ea9..917aa98 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -657,12 +657,23 @@
   /**
    * Check for errors in "chronological" order (acknowledge that loading and analysis are
    * interleaved, but sequential on the single target scale).
+   *
+   * <p>For Skymeld: execution errors should take precedence, since those are DetailedExceptions.
    */
   @Nullable
   public static FailureDetail createFailureDetail(
       TargetPatternPhaseValue loadingResult,
       @Nullable SkyframeAnalysisResult skyframeAnalysisResult,
       @Nullable TopLevelTargetsAndConfigsResult topLevelTargetsAndConfigs) {
+    if (skyframeAnalysisResult instanceof SkyframeAnalysisAndExecutionResult) {
+      SkyframeAnalysisAndExecutionResult skyframeAnalysisAndExecutionResult =
+          (SkyframeAnalysisAndExecutionResult) skyframeAnalysisResult;
+      if (skyframeAnalysisAndExecutionResult.getRepresentativeExecutionExitCode() != null) {
+        return skyframeAnalysisAndExecutionResult
+            .getRepresentativeExecutionExitCode()
+            .getFailureDetail();
+      }
+    }
     if (loadingResult.hasError()) {
       return FailureDetail.newBuilder()
           .setMessage("command succeeded, but there were errors parsing the target pattern")
@@ -692,15 +703,6 @@
           .setAnalysis(Analysis.newBuilder().setCode(Analysis.Code.NOT_ALL_TARGETS_ANALYZED))
           .build();
     }
-    if (skyframeAnalysisResult instanceof SkyframeAnalysisAndExecutionResult) {
-      SkyframeAnalysisAndExecutionResult skyframeAnalysisAndExecutionResult =
-          (SkyframeAnalysisAndExecutionResult) skyframeAnalysisResult;
-      if (skyframeAnalysisAndExecutionResult.getRepresentativeExecutionExitCode() != null) {
-        return skyframeAnalysisAndExecutionResult
-            .getRepresentativeExecutionExitCode()
-            .getFailureDetail();
-      }
-    }
     return null;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 4ff1187..f2febef 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -128,6 +128,7 @@
         ":diff_awareness_manager",
         ":directory_listing_function",
         ":directory_listing_state_value",
+        ":exclusive_test_build_driver_value",
         ":execution_finished_event",
         ":file_function",
         ":fileset_entry_function",
@@ -846,6 +847,7 @@
         ":build_driver_key",
         ":build_driver_value",
         ":configured_target_key",
+        ":exclusive_test_build_driver_value",
         ":incremental_artifact_conflict_finder",
         ":precomputed_value",
         ":target_completion_value",
@@ -1242,6 +1244,16 @@
 )
 
 java_library(
+    name = "exclusive_test_build_driver_value",
+    srcs = ["ExclusiveTestBuildDriverValue.java"],
+    deps = [
+        ":build_driver_value",
+        "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
+        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+    ],
+)
+
+java_library(
     name = "execution_finished_event",
     srcs = ["ExecutionFinishedEvent.java"],
     deps = [
@@ -2186,6 +2198,7 @@
         ":detailed_exceptions",
         ":sane_analysis_exception",
         ":sky_functions",
+        ":test_completion_value",
         ":top_level_conflict_exception",
         ":transitive_target_key",
         "//src/main/java/com/google/devtools/build/lib/actions",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BuildDriverFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/BuildDriverFunction.java
index efa6d75..2d90a03 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BuildDriverFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BuildDriverFunction.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
+import static com.google.devtools.build.lib.skyframe.BuildDriverKey.TestType.EXCLUSIVE;
 import static com.google.devtools.build.lib.skyframe.BuildDriverKey.TestType.NOT_TEST;
 import static com.google.devtools.build.lib.skyframe.BuildDriverKey.TestType.PARALLEL;
 
@@ -133,7 +134,17 @@
       requestAspectExecution((TopLevelAspectsValue) topLevelSkyValue, env, topLevelArtifactContext);
     }
 
-    return env.valuesMissing() ? null : new BuildDriverValue(topLevelSkyValue);
+    if (env.valuesMissing()) {
+      return null;
+    }
+
+    if (EXCLUSIVE.equals(buildDriverKey.getTestType())) {
+      Preconditions.checkState(topLevelSkyValue instanceof ConfiguredTargetValue);
+      return new ExclusiveTestBuildDriverValue(
+          topLevelSkyValue, ((ConfiguredTargetValue) topLevelSkyValue).getConfiguredTarget());
+    }
+
+    return new BuildDriverValue(topLevelSkyValue);
   }
 
   private void requestConfiguredTargetExecution(
@@ -147,7 +158,7 @@
     ImmutableSet.Builder<Artifact> artifactsToBuild = ImmutableSet.builder();
     addExtraActionsIfRequested(
         configuredTarget.getProvider(ExtraActionArtifactsProvider.class), artifactsToBuild);
-    if (buildDriverKey.getTestType() == NOT_TEST) {
+    if (NOT_TEST.equals(buildDriverKey.getTestType())) {
       declareDependenciesAndCheckValues(
           env,
           Iterables.concat(
@@ -171,20 +182,22 @@
       state.sentTestAnalysisCompleteEvent = true;
     }
 
-    Preconditions.checkState(
-        PARALLEL.equals(buildDriverKey.getTestType()),
-        "Invalid test type, expect only parallel tests: %s",
-        buildDriverKey);
-    // Only run non-exclusive tests here. Exclusive tests need to be run sequentially later.
-    declareDependenciesAndCheckValues(
-        env,
-        Iterables.concat(
-            artifactsToBuild.build(),
-            Collections.singletonList(
-                TestCompletionValue.key(
-                    (ConfiguredTargetKey) actionLookupKey,
-                    topLevelArtifactContext,
-                    /*exclusiveTesting=*/ false))));
+    if (PARALLEL.equals(buildDriverKey.getTestType())) {
+      // Only run non-exclusive tests here. Exclusive tests need to be run sequentially later.
+      declareDependenciesAndCheckValues(
+          env,
+          Iterables.concat(
+              artifactsToBuild.build(),
+              Collections.singletonList(
+                  TestCompletionValue.key(
+                      (ConfiguredTargetKey) actionLookupKey,
+                      topLevelArtifactContext,
+                      /*exclusiveTesting=*/ false))));
+      return;
+    }
+
+    // Exclusive tests will be run with sequential Skyframe evaluations afterwards.
+    declareDependenciesAndCheckValues(env, artifactsToBuild.build());
   }
 
   private void requestAspectExecution(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ExclusiveTestBuildDriverValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/ExclusiveTestBuildDriverValue.java
new file mode 100644
index 0000000..acc0188
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ExclusiveTestBuildDriverValue.java
@@ -0,0 +1,33 @@
+// Copyright 2022 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.skyframe;
+
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.skyframe.SkyValue;
+
+/** A subclass of BuildDriverValue, meant to represent an exclusive test. */
+public class ExclusiveTestBuildDriverValue extends BuildDriverValue {
+
+  private final ConfiguredTarget exclusiveTestConfiguredTarget;
+
+  ExclusiveTestBuildDriverValue(
+      SkyValue wrappedSkyValue, ConfiguredTarget exclusiveTestConfiguredTarget) {
+    super(wrappedSkyValue);
+    this.exclusiveTestConfiguredTarget = exclusiveTestConfiguredTarget;
+  }
+
+  public ConfiguredTarget getExclusiveTestConfiguredTarget() {
+    return exclusiveTestConfiguredTarget;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
index d744b08..100d58c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
@@ -87,6 +87,7 @@
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor.FailureToRetrieveIntrospectedValueException;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor.TopLevelActionConflictReport;
 import com.google.devtools.build.lib.util.DetailedExitCode;
+import com.google.devtools.build.lib.util.DetailedExitCode.DetailedExitCodeComparator;
 import com.google.devtools.build.lib.util.OrderedSetMultimap;
 import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.skyframe.ErrorInfo;
@@ -98,6 +99,7 @@
 import com.google.devtools.common.options.OptionDefinition;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -642,19 +644,8 @@
     }
 
     List<BuildDriverKey> buildDriverCTKeys = new ArrayList<>(/*initialCapacity=*/ ctKeys.size());
-    List<Label> exclusiveTestsLabels = new ArrayList<>();
 
     for (ConfiguredTargetKey ctKey : ctKeys) {
-      TestType testType =
-          determineTestType(
-              testsToRun,
-              labelTargetMap,
-              ctKey.getLabel(),
-              topLevelArtifactContext.runTestsExclusively());
-      if (TestType.EXCLUSIVE.equals(testType)) {
-        exclusiveTestsLabels.add(ctKey.getLabel());
-        continue;
-      }
       buildDriverCTKeys.add(
           BuildDriverKey.ofConfiguredTarget(
               ctKey,
@@ -667,14 +658,6 @@
                   topLevelArtifactContext.runTestsExclusively())));
     }
 
-    if (!exclusiveTestsLabels.isEmpty()) {
-      // TODO(b/223558573) Make exclusive tests work.
-      eventHandler.handle(
-          Event.warn(
-              "--experimental_merged_skyframe_analysis_execution is currently incompatible with"
-                  + " exclusive tests. The following exclusive tests won't be run: "
-                  + exclusiveTestsLabels));
-    }
     List<BuildDriverKey> buildDriverAspectKeys =
         topLevelAspectsKeys.stream()
             .map(
@@ -699,7 +682,37 @@
       skyframeExecutor.resetIncrementalArtifactConflictFindingStates();
     }
 
-    if (!evaluationResult.hasError()) {
+    // The exclusive tests whose analysis succeeded i.e. those that can be run.
+    ImmutableSet<ConfiguredTarget> exclusiveTestsToRun = getExclusiveTests(evaluationResult);
+    boolean continueWithExclusiveTests = !evaluationResult.hasError() || keepGoing;
+    List<DetailedExitCode> detailedExitCodes = new ArrayList<>();
+
+    if (continueWithExclusiveTests && !exclusiveTestsToRun.isEmpty()) {
+      // Run exclusive tests sequentially.
+      Iterable<SkyKey> testCompletionKeys =
+          TestCompletionValue.keys(
+              exclusiveTestsToRun, topLevelArtifactContext, /*exclusiveTesting=*/ true);
+      for (SkyKey testCompletionKey : testCompletionKeys) {
+        EvaluationResult<SkyValue> testRunResult =
+            skyframeExecutor.evaluateSkyKeys(
+                eventHandler, ImmutableSet.of(testCompletionKey), keepGoing);
+        if (testRunResult.hasError()) {
+          detailedExitCodes.add(
+              SkyframeErrorProcessor.processErrors(
+                      testRunResult,
+                      configurationLookupSupplier,
+                      skyframeExecutor.getCyclesReporter(),
+                      eventHandler,
+                      keepGoing,
+                      eventBus,
+                      bugReporter,
+                      /*includeExecutionPhase=*/ true)
+                  .executionDetailedExitCode());
+        }
+      }
+    }
+
+    if (!evaluationResult.hasError() && detailedExitCodes.isEmpty()) {
       Map<AspectKey, ConfiguredAspect> successfulAspects =
           getSuccessfulAspectMap(
               topLevelAspectsKeys.size(),
@@ -730,6 +743,7 @@
             eventBus,
             bugReporter,
             /*includeExecutionPhase=*/ true);
+    detailedExitCodes.add(errorProcessingResult.executionDetailedExitCode());
 
     foundActionConflictInLatestCheck = !errorProcessingResult.actionConflicts().isEmpty();
     TopLevelActionConflictReport topLevelActionConflictReport =
@@ -763,7 +777,7 @@
         evaluationResult.getWalkableGraph(),
         ImmutableMap.copyOf(successfulAspects),
         /*packageRoots=*/ null,
-        errorProcessingResult.executionDetailedExitCode());
+        Collections.max(detailedExitCodes, DetailedExitCodeComparator.INSTANCE));
   }
 
   /**
@@ -877,6 +891,18 @@
     }
   }
 
+  private static ImmutableSet<ConfiguredTarget> getExclusiveTests(
+      EvaluationResult<BuildDriverValue> evaluationResult) {
+    ImmutableSet.Builder<ConfiguredTarget> exclusiveTests = ImmutableSet.builder();
+    for (BuildDriverValue value : evaluationResult.values()) {
+      if (value instanceof ExclusiveTestBuildDriverValue) {
+        exclusiveTests.add(
+            ((ExclusiveTestBuildDriverValue) value).getExclusiveTestConfiguredTarget());
+      }
+    }
+    return exclusiveTests.build();
+  }
+
   private static TestType determineTestType(
       ImmutableSet<Label> testsToRun,
       ImmutableMap<Label, Target> labelTargetMap,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessor.java
index d1f0c81..29b0df0 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeErrorProcessor.java
@@ -53,6 +53,7 @@
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.ArtifactConflictFinder.ConflictException;
 import com.google.devtools.build.lib.skyframe.AspectKeyCreator.TopLevelAspectsKey;
+import com.google.devtools.build.lib.skyframe.TestCompletionValue.TestCompletionKey;
 import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.DetailedExitCode.DetailedExitCodeComparator;
 import com.google.devtools.build.skyframe.CycleInfo;
@@ -336,6 +337,10 @@
     if (errorEntry.getKey().argument() instanceof BuildDriverKey) {
       return ((BuildDriverKey) errorEntry.getKey().argument()).getActionLookupKey();
     }
+    // For exclusive tests.
+    if (errorEntry.getKey().argument() instanceof TestCompletionKey) {
+      return ((TestCompletionKey) errorEntry.getKey().argument()).configuredTargetKey();
+    }
     return errorEntry.getKey();
   }