@Test
public void gatherIOShouldApplyTimeouts() throws IOException {

  var executor = Executors.newSingleThreadExecutor();
  // Total execution time for these futures running sequentially is 10 seconds
  var futures = IntStream.range(0, 1000).boxed()
      .map(i -> CompletableFuture.supplyAsync(() -> {
        sleepMillis(10);
        return i;
      }, executor))
      .collect(toList());
  var start = System.nanoTime();
  // Give enough time for ~ 20 futures to complete
  var timeout = CompletableFuture.runAsync(() -> {}, delayedExecutor(200, MILLISECONDS));
  try {
    gatherIO(futures, timeout);
    fail();
  } catch (IOException e) {
    assertThat(e.getCause(), instanceOf(TimeoutException.class));
  }
  var end = System.nanoTime();
  var elapsed = Duration.ofNanos(end - start);
  // Verify that the timeout of 200 ms was properly applied in a non-blocking fashion by checking that the execution
  // duration is well below the expected total of 10 seconds.
  assertThat(elapsed.getSeconds(), is(lessThan(1L)));
}