From b20343926cb1df877a7ce33b2a1d0c58927632d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:14:23 +0000 Subject: [PATCH 1/3] Initial plan From be18b6028d66e0df8e4a66c681085eb3e3a376d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:25:15 +0000 Subject: [PATCH 2/3] Fix race condition in KubernetesInformerCreatorTest causing Windows CI failure Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> --- .../KubernetesInformerCreatorTest.java | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java index 88a12b4834..5573981596 100644 --- a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java +++ b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java @@ -39,7 +39,7 @@ import io.kubernetes.client.util.ClientBuilder; import io.kubernetes.client.util.generic.GenericKubernetesApi; import java.util.Collections; -import java.util.concurrent.Semaphore; +import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; @@ -58,8 +58,8 @@ public String getName() { @Override public void doAction(ServeEvent serveEvent, Admin admin, Parameters parameters) { - Semaphore count = (Semaphore) parameters.get("semaphore"); - count.release(); + CountDownLatch latch = (CountDownLatch) parameters.get("semaphore"); + latch.countDown(); } } @@ -107,12 +107,18 @@ void informerInjection() throws InterruptedException { assertThat(podInformer).isNotNull(); assertThat(configMapInformer).isNotNull(); - Semaphore getCount = new Semaphore(2); - Semaphore watchCount = new Semaphore(2); - Parameters getParams = new Parameters(); - Parameters watchParams = new Parameters(); - getParams.put("semaphore", getCount); - watchParams.put("semaphore", watchCount); + CountDownLatch podGetLatch = new CountDownLatch(1); + CountDownLatch podWatchLatch = new CountDownLatch(1); + CountDownLatch configMapGetLatch = new CountDownLatch(1); + CountDownLatch configMapWatchLatch = new CountDownLatch(1); + Parameters podGetParams = new Parameters(); + Parameters podWatchParams = new Parameters(); + Parameters configMapGetParams = new Parameters(); + Parameters configMapWatchParams = new Parameters(); + podGetParams.put("semaphore", podGetLatch); + podWatchParams.put("semaphore", podWatchLatch); + configMapGetParams.put("semaphore", configMapGetLatch); + configMapWatchParams.put("semaphore", configMapWatchLatch); V1Pod foo1 = new V1Pod().kind("Pod").metadata(new V1ObjectMeta().namespace("default").name("foo1")); @@ -123,7 +129,7 @@ void informerInjection() throws InterruptedException { apiServer.stubFor( get(urlPathEqualTo("/api/v1/pods")) - .withPostServeAction("semaphore", getParams) + .withPostServeAction("semaphore", podGetParams) .withQueryParam("watch", equalTo("false")) .atPriority(1) .willReturn( @@ -137,7 +143,7 @@ void informerInjection() throws InterruptedException { .items(Collections.singletonList(foo1)))))); apiServer.stubFor( get(urlPathEqualTo("/api/v1/pods")) - .withPostServeAction("semaphore", watchParams) + .withPostServeAction("semaphore", podWatchParams) .withQueryParam("watch", equalTo("true")) .atPriority(2) .willReturn( @@ -148,7 +154,7 @@ void informerInjection() throws InterruptedException { apiServer.stubFor( get(urlPathEqualTo("/api/v1/namespaces/default/configmaps")) - .withPostServeAction("semaphore", getParams) + .withPostServeAction("semaphore", configMapGetParams) .withQueryParam("watch", equalTo("false")) .atPriority(1) .willReturn( @@ -162,7 +168,7 @@ void informerInjection() throws InterruptedException { .items(Collections.singletonList(bar1)))))); apiServer.stubFor( get(urlPathEqualTo("/api/v1/namespaces/default/configmaps")) - .withPostServeAction("semaphore", watchParams) + .withPostServeAction("semaphore", configMapWatchParams) .withQueryParam("watch", equalTo("true")) .atPriority(2) .willReturn( @@ -171,15 +177,13 @@ void informerInjection() throws InterruptedException { .withHeader("Content-Type", "application/json") .withBody("{}"))); - // These will be released for each web call above. - getCount.acquire(2); - watchCount.acquire(2); - informerFactory.startAllRegisteredInformers(); - // Wait for the GETs to complete and the watches to start. - getCount.acquire(2); - watchCount.acquire(2); + // Wait for each informer's list and watch to complete independently. + podGetLatch.await(); + podWatchLatch.await(); + configMapGetLatch.await(); + configMapWatchLatch.await(); apiServer.verify( 1, From 1ccd3630b439bb9987ff330f18bc73ab5b748b64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:54:29 +0000 Subject: [PATCH 3/3] Use prerequisite latches to enforce list-before-watch ordering in test Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> --- .../controller/KubernetesInformerCreatorTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java index 5573981596..67db22dbb4 100644 --- a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java +++ b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java @@ -40,6 +40,7 @@ import io.kubernetes.client.util.generic.GenericKubernetesApi; import java.util.Collections; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; @@ -50,6 +51,10 @@ @SpringBootTest class KubernetesInformerCreatorTest { + // Static so CountRequestAction (a static inner class used by WireMock) can record violations. + // Reset at the start of each test via orderingViolation.set(null). + private static final AtomicReference orderingViolation = new AtomicReference<>(); + public static class CountRequestAction extends PostServeAction { @Override public String getName() { @@ -58,7 +63,12 @@ public String getName() { @Override public void doAction(ServeEvent serveEvent, Admin admin, Parameters parameters) { + Object prereq = parameters.get("prerequisite"); CountDownLatch latch = (CountDownLatch) parameters.get("semaphore"); + if (prereq instanceof CountDownLatch prerequisite && prerequisite.getCount() > 0) { + orderingViolation.compareAndSet( + null, new AssertionError("watch request received before list was completed")); + } latch.countDown(); } } @@ -107,6 +117,8 @@ void informerInjection() throws InterruptedException { assertThat(podInformer).isNotNull(); assertThat(configMapInformer).isNotNull(); + orderingViolation.set(null); + CountDownLatch podGetLatch = new CountDownLatch(1); CountDownLatch podWatchLatch = new CountDownLatch(1); CountDownLatch configMapGetLatch = new CountDownLatch(1); @@ -117,8 +129,10 @@ void informerInjection() throws InterruptedException { Parameters configMapWatchParams = new Parameters(); podGetParams.put("semaphore", podGetLatch); podWatchParams.put("semaphore", podWatchLatch); + podWatchParams.put("prerequisite", podGetLatch); configMapGetParams.put("semaphore", configMapGetLatch); configMapWatchParams.put("semaphore", configMapWatchLatch); + configMapWatchParams.put("prerequisite", configMapGetLatch); V1Pod foo1 = new V1Pod().kind("Pod").metadata(new V1ObjectMeta().namespace("default").name("foo1")); @@ -200,5 +214,6 @@ void informerInjection() throws InterruptedException { assertThat(new Lister<>(podInformer.getIndexer()).list()).hasSize(1); assertThat(new Lister<>(configMapInformer.getIndexer()).list()).hasSize(1); + assertThat(orderingViolation.get()).isNull(); } }