From b86886f1dcd9273fef7791f38d628767315fff14 Mon Sep 17 00:00:00 2001 From: Yingzi Zhang Date: Tue, 29 Sep 2026 08:46:35 -0400 Subject: [PATCH] Fix timeout overflow when waiting for end of output --- .../containers/output/WaitingConsumer.java | 10 ++-- .../output/WaitingConsumerTest.java | 55 +++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 core/src/test/java/org/testcontainers/containers/output/WaitingConsumerTest.java diff --git a/core/src/main/java/org/testcontainers/containers/output/WaitingConsumer.java b/core/src/main/java/org/testcontainers/containers/output/WaitingConsumer.java index e79c234058e..db7963a4a95 100644 --- a/core/src/main/java/org/testcontainers/containers/output/WaitingConsumer.java +++ b/core/src/main/java/org/testcontainers/containers/output/WaitingConsumer.java @@ -132,13 +132,15 @@ public void waitUntilEnd() { * @param limitUnit maximum time to wait (units) */ public void waitUntilEnd(long limit, TimeUnit limitUnit) throws TimeoutException { - long expiry = limitUnit.toNanos(limit) + System.nanoTime(); + long timeoutLimitInNanos = limitUnit.toNanos(limit); - waitUntilEnd(expiry); + waitUntilEnd(timeoutLimitInNanos); } - private void waitUntilEnd(Long expiry) throws TimeoutException { - while (System.nanoTime() < expiry) { + private void waitUntilEnd(long timeoutLimitInNanos) throws TimeoutException { + final long startTime = System.nanoTime(); + + while (System.nanoTime() - startTime < timeoutLimitInNanos) { try { OutputFrame frame = frames.pollLast(100, TimeUnit.MILLISECONDS); diff --git a/core/src/test/java/org/testcontainers/containers/output/WaitingConsumerTest.java b/core/src/test/java/org/testcontainers/containers/output/WaitingConsumerTest.java new file mode 100644 index 00000000000..4379ac81be5 --- /dev/null +++ b/core/src/test/java/org/testcontainers/containers/output/WaitingConsumerTest.java @@ -0,0 +1,55 @@ +package org.testcontainers.containers.output; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@Timeout(5) +class WaitingConsumerTest { + + @Test + void waitsUntilEndWithMaximumTimeout() { + WaitingConsumer consumer = new WaitingConsumer(); + consumer.accept(OutputFrame.END); + + assertThatCode(() -> consumer.waitUntilEnd(Long.MAX_VALUE, TimeUnit.NANOSECONDS)).doesNotThrowAnyException(); + } + + @Test + void waitsUntilEndWithSaturatedTimeout() { + WaitingConsumer consumer = new WaitingConsumer(); + consumer.accept(OutputFrame.END); + + assertThatCode(() -> consumer.waitUntilEnd(Long.MAX_VALUE, TimeUnit.SECONDS)).doesNotThrowAnyException(); + } + + @Test + void waitsUntilEndWithoutTimeout() { + WaitingConsumer consumer = new WaitingConsumer(); + consumer.accept(OutputFrame.END); + + assertThatCode(consumer::waitUntilEnd).doesNotThrowAnyException(); + } + + @Test + void waitsUntilEndWithFiniteTimeout() { + WaitingConsumer consumer = new WaitingConsumer(); + consumer.accept(OutputFrame.END); + + assertThatCode(() -> consumer.waitUntilEnd(1, TimeUnit.SECONDS)).doesNotThrowAnyException(); + } + + @Test + void timesOutWhenEndIsNotReceived() { + WaitingConsumer consumer = new WaitingConsumer(); + + assertThatThrownBy(() -> consumer.waitUntilEnd(1, TimeUnit.MILLISECONDS)) + .isInstanceOf(TimeoutException.class) + .hasMessage("Expiry time reached before end of output"); + } +}