From f0b06cf216b629c28b239bc930a59e08d38d8328 Mon Sep 17 00:00:00 2001 From: Emmanuel Oppong Date: Fri, 6 Mar 2026 12:04:24 -0600 Subject: [PATCH 1/3] test: replace Thread.sleep with sync primitives in tests Replace flaky Thread.sleep calls with proper synchronization: - SharedProcessorTest: use Semaphore(0).acquire() instead of Thread.sleep(10s) so the worker blocks until interrupted by shutdownNow(), making the test deterministic and instant. - KubernetesReconcilerCreatorTest: use Wait.poll() instead of Thread.sleep(500ms) to actively poll until the work queue is populated, making the test faster and non-flaky. Addresses part of #1223. Co-Authored-By: Oz --- .../controller/KubernetesReconcilerCreatorTest.java | 7 +++++-- .../client/informer/cache/SharedProcessorTest.java | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerCreatorTest.java b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerCreatorTest.java index 15f6991da3..f62817f41b 100644 --- a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerCreatorTest.java +++ b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerCreatorTest.java @@ -22,6 +22,7 @@ import io.kubernetes.client.extended.controller.reconciler.Reconciler; import io.kubernetes.client.extended.controller.reconciler.Request; import io.kubernetes.client.extended.controller.reconciler.Result; +import io.kubernetes.client.extended.wait.Wait; import io.kubernetes.client.extended.workqueue.WorkQueue; import io.kubernetes.client.informer.SharedInformer; import io.kubernetes.client.informer.SharedInformerFactory; @@ -46,6 +47,7 @@ import io.kubernetes.client.spring.extended.controller.annotation.UpdateWatchEventFilter; import io.kubernetes.client.spring.extended.controller.factory.KubernetesControllerFactory; import io.kubernetes.client.util.ClientBuilder; +import java.time.Duration; import java.util.LinkedList; import java.util.function.Function; import jakarta.annotation.Resource; @@ -194,9 +196,10 @@ void simplePodController() throws InterruptedException { } }); - Thread.sleep(500); - WorkQueue workQueue = ((DefaultController) testController).getWorkQueue(); + boolean itemAdded = + Wait.poll(Duration.ofMillis(10), Duration.ofSeconds(5), () -> workQueue.length() >= 1); + assertThat(itemAdded).isTrue(); assertThat(workQueue.length()).isEqualTo(1); assertThat(workQueue.get().getName()).isEqualTo("foo"); sharedInformerFactory.stopAllRegisteredInformers(); diff --git a/util/src/test/java/io/kubernetes/client/informer/cache/SharedProcessorTest.java b/util/src/test/java/io/kubernetes/client/informer/cache/SharedProcessorTest.java index 12a5ccbbbd..22b14205a2 100644 --- a/util/src/test/java/io/kubernetes/client/informer/cache/SharedProcessorTest.java +++ b/util/src/test/java/io/kubernetes/client/informer/cache/SharedProcessorTest.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; import org.junit.jupiter.api.Test; class SharedProcessorTest { @@ -69,11 +70,12 @@ void shutdownGracefully() throws InterruptedException { TestWorker slowWorker = new TestWorker<>(null, 0); final boolean[] interrupted = {false}; CountDownLatch latch = new CountDownLatch(1); + Semaphore blocker = new Semaphore(0); slowWorker.setTask( () -> { try { - // sleep 10s so that it could be interrupted by shutdownNow() - Thread.sleep(10 * 1000); + // block until interrupted by shutdownNow() + blocker.acquire(); } catch (InterruptedException e) { interrupted[0] = true; } finally { From ebe9ea9e33ed9b8234252749e00d6a78bdf58502 Mon Sep 17 00:00:00 2001 From: Emmanuel Oppong Date: Fri, 6 Mar 2026 12:04:31 -0600 Subject: [PATCH 2/3] re-trigger CLA check after signing Co-Authored-By: Oz From 1db41dda582b71c919554f4ceb313732d7d3614c Mon Sep 17 00:00:00 2001 From: Emmanuel Oppong Date: Fri, 6 Mar 2026 12:15:56 -0600 Subject: [PATCH 3/3] test: replace remaining Thread.sleep calls with sync primitives Address the remaining Thread.sleep usages from #1223: - ExecCallbacksTest: replace Thread.sleep(30s) with Semaphore(0).acquire() so the simulated remote process blocks until the exec timeout fires, making the test instant and non-flaky. - CopyTest: replace Thread.sleep(2000) with Awaitility.await() polling WireMock's serve events, so we proceed as soon as the HTTP request is received rather than waiting a fixed 2 seconds. - PortForwardTest: remove Thread.sleep(2000) entirely; handler.close() is called while the main thread holds the monitor lock, so there is no race condition and no need for a timed wait. - DefaultDelayingQueueTest: remove Thread.sleep(10s) after queue.done(); the queue uses an injected (static) time source, so no items will be spontaneously enqueued and the assertion can be made immediately. - DefaultWorkQueueTest: remove Thread.sleep calls used to simulate producer/consumer work; the test correctness is governed by CountDownLatches, not by sleep timing. - EventCorrelatorTest: replace Thread.sleep(100) with an explicit timestamp offset (plusMillis(100)) so event timestamps are guaranteed to differ without any real-time wait. Co-Authored-By: Oz --- .../extended/event/EventCorrelatorTest.java | 10 +++++----- .../workqueue/DefaultDelayingQueueTest.java | 2 +- .../workqueue/DefaultWorkQueueTest.java | 2 -- .../java/io/kubernetes/client/CopyTest.java | 18 +++++++++++++----- .../kubernetes/client/ExecCallbacksTest.java | 5 ++++- .../io/kubernetes/client/PortForwardTest.java | 1 - 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/extended/src/test/java/io/kubernetes/client/extended/event/EventCorrelatorTest.java b/extended/src/test/java/io/kubernetes/client/extended/event/EventCorrelatorTest.java index 49309a9fa9..01f669c9e4 100644 --- a/extended/src/test/java/io/kubernetes/client/extended/event/EventCorrelatorTest.java +++ b/extended/src/test/java/io/kubernetes/client/extended/event/EventCorrelatorTest.java @@ -144,17 +144,17 @@ void eventCorrelate( Boolean expectedSkip) throws Exception { EventCorrelator correlator = new EventCorrelator(); + OffsetDateTime previousEventsTime = OffsetDateTime.now(); for (CoreV1Event event : previousEvents) { - OffsetDateTime now = OffsetDateTime.now(); - event.setFirstTimestamp(now); - event.setLastTimestamp(now); + event.setFirstTimestamp(previousEventsTime); + event.setLastTimestamp(previousEventsTime); Optional> result = correlator.correlate(event); if (result.isEmpty()) { correlator.updateState(event); } } - Thread.sleep(100); - OffsetDateTime now = OffsetDateTime.now(); + // Use a deterministic future timestamp instead of sleeping to ensure timestamps differ. + OffsetDateTime now = previousEventsTime.plusMillis(100); newEvent.setFirstTimestamp(now); newEvent.setLastTimestamp(now); Optional> result = correlator.correlate(newEvent); diff --git a/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultDelayingQueueTest.java b/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultDelayingQueueTest.java index f29791b688..d9e3cfaa87 100644 --- a/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultDelayingQueueTest.java +++ b/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultDelayingQueueTest.java @@ -45,7 +45,7 @@ void simpleDelayingQueue() throws Exception { String item = queue.get(); queue.done(item); - Thread.sleep(10 * 1000L); + // The time source is injected and static, so no new items will be enqueued spontaneously. assertThat(0).isEqualTo(queue.length()); } diff --git a/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultWorkQueueTest.java b/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultWorkQueueTest.java index fef4149064..4cbc125e8b 100644 --- a/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultWorkQueueTest.java +++ b/extended/src/test/java/io/kubernetes/client/extended/workqueue/DefaultWorkQueueTest.java @@ -41,7 +41,6 @@ void multiProducerAndConsumers() throws Exception { try { for (int j = 0; j < 50; j++) { queue.add(String.valueOf(num)); - Thread.sleep(10); } } catch (Exception e) { // empty body @@ -70,7 +69,6 @@ void multiProducerAndConsumers() throws Exception { } LOGGER.info("Worker {}: begin processing {}", num, item); - Thread.sleep(50); LOGGER.info("Worker {}: done processing {}", num, item); queue.done(item); } diff --git a/util/src/test/java/io/kubernetes/client/CopyTest.java b/util/src/test/java/io/kubernetes/client/CopyTest.java index 82fce66f66..e02e890bd8 100644 --- a/util/src/test/java/io/kubernetes/client/CopyTest.java +++ b/util/src/test/java/io/kubernetes/client/CopyTest.java @@ -32,6 +32,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; +import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -121,7 +123,9 @@ public void run() { } }); t.start(); - Thread.sleep(2000); + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until(() -> !apiServer.getAllServeEvents().isEmpty()); t.interrupt(); apiServer.verify( @@ -137,7 +141,7 @@ public void run() { } @Test - void copyBinaryDataToPod() throws InterruptedException { + void copyBinaryDataToPod byte[] testSrc = new byte[0]; @@ -167,7 +171,9 @@ public void run() { } }); t.start(); - Thread.sleep(2000); + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until(() -> !apiServer.getAllServeEvents().isEmpty()); t.interrupt(); apiServer.verify( @@ -183,7 +189,7 @@ public void run() { } @Test - void testCopyDirectoryFromPod(@TempDir Path tempDir) throws Exception { + void testCopyDirectoryFromPod Copy copy = new Copy(client); apiServer.stubFor( @@ -211,7 +217,9 @@ public void run() { } }); t.start(); - Thread.sleep(2000); + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until(() -> !apiServer.getAllServeEvents().isEmpty()); t.interrupt(); apiServer.verify( diff --git a/util/src/test/java/io/kubernetes/client/ExecCallbacksTest.java b/util/src/test/java/io/kubernetes/client/ExecCallbacksTest.java index d150eb0226..602396f522 100644 --- a/util/src/test/java/io/kubernetes/client/ExecCallbacksTest.java +++ b/util/src/test/java/io/kubernetes/client/ExecCallbacksTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; @@ -37,7 +38,9 @@ class ExecCallbacksTest { @Test void maxTimeout() throws Exception { - Exec exec = getExec((future, io) -> Thread.sleep(30_000L)); + // Block indefinitely instead of sleeping so the test isn't time-bounded on the remote side; + // the exec timeout is what terminates execution. + Exec exec = getExec((future, io) -> new Semaphore(0).acquire()); long startTime = System.currentTimeMillis(); Future promise = diff --git a/util/src/test/java/io/kubernetes/client/PortForwardTest.java b/util/src/test/java/io/kubernetes/client/PortForwardTest.java index b341f2b9a8..a790719d55 100644 --- a/util/src/test/java/io/kubernetes/client/PortForwardTest.java +++ b/util/src/test/java/io/kubernetes/client/PortForwardTest.java @@ -183,7 +183,6 @@ void brokenPortPassing() throws IOException, InterruptedException { }); synchronized (block) { t.start(); - Thread.sleep(2000); handler.close(); block.wait(); }