Fix `repository/downloader:DownloaderTestSuite` for windows + JDK21.

Fixes #21593.

PiperOrigin-RevId: 613965386
Change-Id: I436ad3b865b01380a88190f4a58a2205997c73ab
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index aaa8660..623da64 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -356,8 +356,6 @@
       - "-//src/test/java/com/google/devtools/build/lib/remote:RemoteTests"
       - "-//src/test/shell/bazel/remote/..."
       - "-//tools/python:pywrapper_test"
-      # https://github.com/bazelbuild/bazel/issues/21593
-      - "-//src/test/java/com/google/devtools/build/lib/bazel/repository/downloader:DownloaderTestSuite"
     include_json_profile:
       - build
       - test
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java
index 1161158..1328620 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java
@@ -50,6 +50,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CyclicBarrier;
@@ -361,8 +362,23 @@
               ISO_8859_1)) {
         fail("Should have thrown");
       } catch (IOException expected) {
-        assertThat(expected).hasCauseThat().isInstanceOf(SocketTimeoutException.class);
-        assertThat(expected).hasCauseThat().hasMessageThat().ignoringCase().contains("timed out");
+        if (expected.getCause() != null) {
+          // SocketTimeoutException gets wrapped in an IOException and rethrown.
+          assertThat(expected).hasCauseThat().isInstanceOf(SocketTimeoutException.class);
+          assertThat(expected).hasCauseThat().hasMessageThat().ignoringCase().contains("timed out");
+        } else {
+          // For windows, it is possible that the thrown exception is a ConnectException (no cause)
+          // from {@code HttpURLConnection.connect()} rather than a SocketTimeoutException from
+          // {@code HttpURLConnection.getResponseCode()}. In the former case, we expect the
+          // SocketTimeoutException to have already occurred but gets suppressed within the final
+          // ConnectException (upon max retry).
+          ImmutableList<Throwable> suppressed = ImmutableList.copyOf(expected.getSuppressed());
+          Optional<Throwable> ste =
+              suppressed.stream().filter(t -> t instanceof SocketTimeoutException).findFirst();
+          assertThat(ste).isPresent();
+          assertThat(ste.get()).isInstanceOf(SocketTimeoutException.class);
+          assertThat(ste.get()).hasMessageThat().ignoringCase().contains("timed out");
+        }
       }
     }
   }