From e64f64bf870a31c677c9150a084cc687dfcf470f Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 16 Sep 2026 11:00:38 +0200 Subject: [PATCH] fix(e2e): require every listed entry for the service to match state RequireEventuallyServiceState succeeded on the first NDJSON entry for the service matching the expected state, without checking any remaining entries. During a recreation window compose ps can list two containers for the same (scale: 1) service simultaneously (old and new); the old one could still read "running" while the new one is "starting", passing the poll prematurely on stale state. Track whether any entry for the service was seen, and only succeed once every one of them matches -- fail (poll.Continue) on the first mismatch instead. (docker-agent review on #14228) Signed-off-by: Nicolas De Loof --- pkg/e2e/assert.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/e2e/assert.go b/pkg/e2e/assert.go index 6dee64167..c68a64b4f 100644 --- a/pkg/e2e/assert.go +++ b/pkg/e2e/assert.go @@ -64,7 +64,9 @@ func RequireEventuallyServiceState(t testing.TB, cli *CLI, service string, state // --format=json prints one JSON object per line (NDJSON), not a // single object or array: a scaled service, or a transient window // during recreation where the old and new containers are both - // listed, means more than one line for the requested service. + // listed, means more than one line for the requested service — every + // one of them must reach the expected state, not just the first. + var found bool for _, line := range strings.Split(strings.TrimSpace(psRes.Stdout()), "\n") { if line == "" { continue @@ -76,10 +78,14 @@ func RequireEventuallyServiceState(t testing.TB, cli *CLI, service string, state if svc, _ := entry["Service"].(string); !strings.EqualFold(svc, service) { continue } - if current, _ := entry["State"].(string); strings.EqualFold(current, state) { - return poll.Success() + found = true + if current, _ := entry["State"].(string); !strings.EqualFold(current, state) { + return poll.Continue("service %q not in state %q yet (got %q): %s", service, state, current, psRes.Stdout()) } } + if found { + return poll.Success() + } return poll.Continue("service %q not in state %q yet: %s", service, state, psRes.Stdout()) } poll.WaitOn(t, check, poll.WithDelay(250*time.Millisecond), poll.WithTimeout(15*time.Second))