Print Passed and Failed methods in detailed test summary

Previously, Bazel only printed Failed methods in the detailed test summary when using the `--test_summary=detailed` option. With this change, both Passed and Failed methods are now printed, providing a more comprehensive view of the test results.

Fix #18685

Closes #19099.

RELNOTES:  The `--test_summary=detailed` option now also prints passed test cases.
PiperOrigin-RevId: 553737487
Change-Id: I332b70d916394de7caed7a07667b46087724a6c1
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/TerminalTestResultNotifier.java b/src/main/java/com/google/devtools/build/lib/runtime/TerminalTestResultNotifier.java
index add22ee..4445624 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/TerminalTestResultNotifier.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/TerminalTestResultNotifier.java
@@ -142,13 +142,13 @@
    * @param summaries summaries of tests {@link TestSummary}
    * @param showAllTests if true, print information about each test regardless of its status
    * @param showNoStatusTests if true, print information about not executed tests (no status tests)
-   * @param printFailedTestCases if true, print details about which test cases in a test failed
+   * @param showAllTestCases if true, print all test cases status and detailed information
    */
   private void printSummary(
       Set<TestSummary> summaries,
       boolean showAllTests,
       boolean showNoStatusTests,
-      boolean printFailedTestCases) {
+      boolean showAllTestCases) {
     boolean withConfig = duplicateLabels(summaries);
     int numFailedToBuildReported = 0;
     for (TestSummary summary : summaries) {
@@ -171,7 +171,7 @@
           printer,
           testLogPathFormatter,
           summaryOptions.verboseSummary,
-          printFailedTestCases,
+          showAllTestCases,
           withConfig);
     }
   }
@@ -243,9 +243,9 @@
       case DETAILED:
         printSummary(
             summaries,
-            /* showAllTests= */ false,
+            /* showAllTests= */ true,
             /* showNoStatusTests= */ true,
-            /* printFailedTestCases= */ true);
+            /* showAllTestCases= */ true);
         break;
 
       case SHORT:
@@ -253,7 +253,7 @@
             summaries,
             /* showAllTests= */ true,
             /* showNoStatusTests= */ false,
-            /* printFailedTestCases= */ false);
+            /* showAllTestCases= */ false);
         break;
 
       case TERSE:
@@ -261,7 +261,7 @@
             summaries,
             /* showAllTests= */ false,
             /* showNoStatusTests= */ false,
-            /* printFailedTestCases= */ false);
+            /* showAllTestCases= */ false);
         break;
 
       case TESTCASE:
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/TestSummary.java b/src/main/java/com/google/devtools/build/lib/runtime/TestSummary.java
index 2ddd38e..baf1968 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/TestSummary.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/TestSummary.java
@@ -41,6 +41,7 @@
 import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus;
 import com.google.devtools.build.lib.view.test.TestStatus.FailedTestCasesStatus;
 import com.google.devtools.build.lib.view.test.TestStatus.TestCase;
+import com.google.devtools.build.lib.view.test.TestStatus.TestCase.Status;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.protobuf.util.Durations;
 import com.google.protobuf.util.Timestamps;
@@ -218,9 +219,20 @@
       if (testCase.getStatus() != TestCase.Status.PASSED) {
         this.summary.failedTestCases.add(testCase);
       }
+
+      if (testCase.getStatus() == Status.PASSED) {
+        this.summary.passedTestCases.add(testCase);
+      }
+
       return 1;
     }
 
+    public Builder addPassedTestCases(List<TestCase> testCases) {
+      checkMutation(testCases);
+      summary.passedTestCases.addAll(testCases);
+      return this;
+    }
+
     @CanIgnoreReturnValue
     public Builder addFailedTestCases(List<TestCase> testCases, FailedTestCasesStatus status) {
       checkMutation(status);
@@ -396,6 +408,7 @@
   private boolean ranRemotely;
   private boolean wasUnreportedWrongSize;
   private List<TestCase> failedTestCases = new ArrayList<>();
+  private final List<TestCase> passedTestCases = new ArrayList<>();
   private List<Path> passedLogs = new ArrayList<>();
   private List<Path> failedLogs = new ArrayList<>();
   private List<String> warnings = new ArrayList<>();
@@ -507,6 +520,10 @@
     return failedTestCases;
   }
 
+  public List<TestCase> getPassedTestCases() {
+    return passedTestCases;
+  }
+
   public List<Path> getCoverageFiles() {
     return coverageFiles;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/TestSummaryPrinter.java b/src/main/java/com/google/devtools/build/lib/runtime/TestSummaryPrinter.java
index eb76c11..8ee4214 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/TestSummaryPrinter.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/TestSummaryPrinter.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus;
 import com.google.devtools.build.lib.view.test.TestStatus.FailedTestCasesStatus;
 import com.google.devtools.build.lib.view.test.TestStatus.TestCase;
+import com.google.devtools.build.lib.view.test.TestStatus.TestCase.Status;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -117,18 +118,13 @@
       AnsiTerminalPrinter terminalPrinter,
       TestLogPathFormatter testLogPathFormatter,
       boolean verboseSummary,
-      boolean printFailedTestCases) {
-    print(
-        summary,
-        terminalPrinter,
-        testLogPathFormatter,
-        verboseSummary,
-        printFailedTestCases,
-        false);
+      boolean showAllTestCases) {
+    print(summary, terminalPrinter, testLogPathFormatter, verboseSummary, showAllTestCases, false);
   }
 
   /**
    * Prints summary status for a single test.
+   *
    * @param terminalPrinter The printer to print to
    */
   public static void print(
@@ -136,7 +132,7 @@
       AnsiTerminalPrinter terminalPrinter,
       TestLogPathFormatter testLogPathFormatter,
       boolean verboseSummary,
-      boolean printFailedTestCases,
+      boolean showAllTestCases,
       boolean withConfigurationName) {
     BlazeTestStatus status = summary.getStatus();
     // Skip output for tests that failed to build.
@@ -158,29 +154,35 @@
             + (verboseSummary ? getAttemptSummary(summary) + getTimeSummary(summary) : "")
             + "\n");
 
-    if (printFailedTestCases && summary.getStatus() == BlazeTestStatus.FAILED) {
-      if (summary.getFailedTestCasesStatus() == FailedTestCasesStatus.NOT_AVAILABLE) {
-        terminalPrinter.print(
-            Mode.WARNING + "    (individual test case information not available) "
-            + Mode.DEFAULT + "\n");
-      } else {
-        for (TestCase testCase : summary.getFailedTestCases()) {
-          if (testCase.getStatus() != TestCase.Status.PASSED) {
-            TestSummaryPrinter.printTestCase(terminalPrinter, testCase);
-          }
-        }
+    if (showAllTestCases) {
+      for (TestCase testCase : summary.getPassedTestCases()) {
+        TestSummaryPrinter.printTestCase(terminalPrinter, testCase);
+      }
 
-        if (summary.getFailedTestCasesStatus() != FailedTestCasesStatus.FULL) {
+      if (summary.getStatus() == BlazeTestStatus.FAILED) {
+        if (summary.getFailedTestCasesStatus() == FailedTestCasesStatus.NOT_AVAILABLE) {
           terminalPrinter.print(
               Mode.WARNING
-              + "    (some shards did not report details, list of failed test"
-              + " cases incomplete)\n"
-              + Mode.DEFAULT);
+                  + "    (individual test case information not available) "
+                  + Mode.DEFAULT
+                  + "\n");
+        } else {
+          for (TestCase testCase : summary.getFailedTestCases()) {
+            if (testCase.getStatus() != TestCase.Status.PASSED) {
+              TestSummaryPrinter.printTestCase(terminalPrinter, testCase);
+            }
+          }
+
+          if (summary.getFailedTestCasesStatus() != FailedTestCasesStatus.FULL) {
+            terminalPrinter.print(
+                Mode.WARNING
+                    + "    (some shards did not report details, list of failed test"
+                    + " cases incomplete)\n"
+                    + Mode.DEFAULT);
+          }
         }
       }
-    }
-
-    if (!printFailedTestCases) {
+    } else {
       for (String warning : summary.getWarnings()) {
         terminalPrinter.print("  " + AnsiTerminalPrinter.Mode.WARNING + "WARNING: "
             + AnsiTerminalPrinter.Mode.DEFAULT + warning + "\n");
@@ -205,12 +207,8 @@
     }
   }
 
-  /**
-   * Prints the result of an individual test case. It is assumed not to have
-   * passed, since passed test cases are not reported.
-   */
-  static void printTestCase(
-      AnsiTerminalPrinter terminalPrinter, TestCase testCase) {
+  /** Prints the result of an individual test case. */
+  static void printTestCase(AnsiTerminalPrinter terminalPrinter, TestCase testCase) {
     String timeSummary;
     if (testCase.hasRunDurationMillis()) {
       timeSummary = " ("
@@ -220,16 +218,17 @@
       timeSummary = "";
     }
 
+    Mode mode = (testCase.getStatus() == Status.PASSED) ? Mode.INFO : Mode.ERROR;
     terminalPrinter.print(
         "    "
-        + Mode.ERROR
-        + Strings.padEnd(testCase.getStatus().toString(), 8, ' ')
-        + Mode.DEFAULT
-        + testCase.getClassName()
-        + "."
-        + testCase.getName()
-        + timeSummary
-        + "\n");
+            + mode
+            + Strings.padEnd(testCase.getStatus().toString(), 8, ' ')
+            + Mode.DEFAULT
+            + testCase.getClassName()
+            + "."
+            + testCase.getName()
+            + timeSummary
+            + "\n");
   }
 
   /**
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/TestSummaryTest.java b/src/test/java/com/google/devtools/build/lib/runtime/TestSummaryTest.java
index 2bb0307..a82242a 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/TestSummaryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/TestSummaryTest.java
@@ -446,6 +446,30 @@
   }
 
   @Test
+  public void testShowTestCaseNames() throws Exception {
+    TestCase detailPassed = newDetail("strawberry", TestCase.Status.PASSED, 1000L);
+    TestCase detailFailed = newDetail("orange", TestCase.Status.FAILED, 1500L);
+
+    TestSummary summaryPassed =
+        createPassedTestSummary(BlazeTestStatus.PASSED, Arrays.asList(detailPassed));
+
+    TestSummary summaryFailed =
+        createTestSummaryWithDetails(
+            BlazeTestStatus.FAILED, Arrays.asList(detailPassed, detailFailed));
+    assertThat(summaryFailed.getStatus()).isEqualTo(BlazeTestStatus.FAILED);
+
+    AnsiTerminalPrinter printerPassed = Mockito.mock(AnsiTerminalPrinter.class);
+    TestSummaryPrinter.print(summaryPassed, printerPassed, Path::getPathString, true, true);
+    verify(printerPassed).print(contains("//package:name"));
+    verify(printerPassed).print(find("PASSED.*strawberry *\\(1\\.0"));
+
+    AnsiTerminalPrinter printerFailed = Mockito.mock(AnsiTerminalPrinter.class);
+    TestSummaryPrinter.print(summaryFailed, printerFailed, Path::getPathString, true, true);
+    verify(printerFailed).print(contains("//package:name"));
+    verify(printerFailed).print(find("FAILED.*orange *\\(1\\.5"));
+  }
+
+  @Test
   public void testTestCaseNamesOrdered() throws Exception {
     TestCase[] details = {
       newDetail("apple", TestCase.Status.FAILED, 1000L),
@@ -500,13 +524,13 @@
 
   @Test
   public void testCollectingFailedDetails() throws Exception {
-    TestCase rootCase = TestCase.newBuilder()
-        .setName("tests")
-        .setRunDurationMillis(5000L)
-        .addChild(newDetail("apple", TestCase.Status.FAILED, 1000L))
-        .addChild(newDetail("banana", TestCase.Status.PASSED, 1000L))
-        .addChild(newDetail("cherry", TestCase.Status.ERROR, 1000L))
-        .build();
+    TestCase rootCase =
+        TestCase.newBuilder()
+            .setName("tests")
+            .setRunDurationMillis(5000L)
+            .addChild(newDetail("apple", TestCase.Status.FAILED, 1000L))
+            .addChild(newDetail("cherry", TestCase.Status.ERROR, 1000L))
+            .build();
 
     TestSummary summary =
         getTemplateBuilder().collectTestCases(rootCase).setStatus(BlazeTestStatus.FAILED).build();
@@ -519,6 +543,46 @@
   }
 
   @Test
+  public void testCollectingAllDetails() throws Exception {
+    TestCase rootCase =
+        TestCase.newBuilder()
+            .setName("tests")
+            .setRunDurationMillis(5000L)
+            .addChild(newDetail("apple", TestCase.Status.FAILED, 1000L))
+            .addChild(newDetail("banana", TestCase.Status.PASSED, 1000L))
+            .addChild(newDetail("cherry", TestCase.Status.ERROR, 1000L))
+            .build();
+
+    TestSummary summary =
+        getTemplateBuilder().collectTestCases(rootCase).setStatus(BlazeTestStatus.FAILED).build();
+
+    AnsiTerminalPrinter printer = Mockito.mock(AnsiTerminalPrinter.class);
+    TestSummaryPrinter.print(summary, printer, Path::getPathString, true, true);
+    verify(printer).print(contains("//package:name"));
+    verify(printer).print(find("FAILED.*apple"));
+    verify(printer).print(find("PASSED.*banana"));
+    verify(printer).print(find("ERROR.*cherry"));
+  }
+
+  @Test
+  public void testCollectingPassedDetails() throws Exception {
+    TestCase rootCase =
+        TestCase.newBuilder()
+            .setName("tests")
+            .setRunDurationMillis(5000L)
+            .addChild(newDetail("apple", TestCase.Status.PASSED, 1000L))
+            .build();
+
+    TestSummary summary =
+        getTemplateBuilder().collectTestCases(rootCase).setStatus(BlazeTestStatus.PASSED).build();
+
+    AnsiTerminalPrinter printer = Mockito.mock(AnsiTerminalPrinter.class);
+    TestSummaryPrinter.print(summary, printer, Path::getPathString, true, true);
+    verify(printer).print(contains("//package:name"));
+    verify(printer).print(find("PASSED.*apple"));
+  }
+
+  @Test
   public void countTotalTestCases() throws Exception {
     TestCase rootCase =
         TestCase.newBuilder()
@@ -605,6 +669,10 @@
     return target(PATH, TARGET_NAME);
   }
 
+  private TestSummary createPassedTestSummary(BlazeTestStatus status, List<TestCase> details) {
+    return getTemplateBuilder().setStatus(status).addPassedTestCases(details).build();
+  }
+
   private TestSummary createTestSummaryWithDetails(BlazeTestStatus status,
       List<TestCase> details) {
     TestSummary summary = getTemplateBuilder()
diff --git a/src/test/shell/bazel/bazel_test_test.sh b/src/test/shell/bazel/bazel_test_test.sh
index b96507d..f65533c 100755
--- a/src/test/shell/bazel/bazel_test_test.sh
+++ b/src/test/shell/bazel/bazel_test_test.sh
@@ -649,7 +649,7 @@
   expect_log "name=\"dir/fail\""
 }
 
-function test_detailed_test_summary() {
+function test_detailed_test_summary_for_failed_test() {
   copy_examples
   create_workspace_with_default_repos WORKSPACE
   setup_javatest_support
@@ -662,6 +662,19 @@
   expect_log 'FAILED.*com\.example\.myproject\.Fail\.testFail'
 }
 
+function test_detailed_test_summary_for_passed_test() {
+  copy_examples
+  create_workspace_with_default_repos WORKSPACE
+  setup_javatest_support
+
+  local java_native_tests=//examples/java-native/src/test/java/com/example/myproject
+
+  bazel test --test_summary=detailed "${java_native_tests}:hello" >& $TEST_log \
+    || fail "expected success"
+  expect_log 'PASSED.*com\.example\.myproject\.TestHello\.testNoArgument'
+  expect_log 'PASSED.*com\.example\.myproject\.TestHello\.testWithArgument'
+}
+
 # This test uses "--ignore_all_rc_files" since outside .bazelrc files can pollute
 # this environment. Just "--bazelrc=/dev/null" is not sufficient to fix.
 function test_flaky_test() {