From 72b1d35b987f5c58d2adcdaf7aa8cf4dc89b4b12 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 27 Oct 2026 11:18:03 +0100 Subject: [PATCH 1/3] refactor(compose): drop the timeout parameter of waitDependencies Its four callers (up --wait, start --wait, run, restart) all pass 0, so the wrapper it built around the context was dead: a wait only gets a timeout of its own through the plan's OpWaitCondition nodes, which bound it with exec.waitTimeout in execBoundedWaitCondition and never go through waitDependencies. A deadline on the caller's context, which is how up and start --wait bound their wait, is untouched. The tests that passed a timeout now exercise the same semantics through the live paths: a deadline on the caller's context for waitDependencies, and execBoundedWaitCondition with exec.waitTimeout for the wait's own timeout (tolerated on an optional dependency, an error on a required one). Part of #14081 Signed-off-by: Nicolas De Loof --- pkg/compose/restart.go | 2 +- pkg/compose/run.go | 2 +- pkg/compose/service_containers.go | 7 +- pkg/compose/service_containers_test.go | 107 ++++++++++++++++--------- pkg/compose/start.go | 2 +- pkg/compose/up.go | 2 +- 6 files changed, 75 insertions(+), 47 deletions(-) diff --git a/pkg/compose/restart.go b/pkg/compose/restart.go index d9a6cf8b37b..5c8c74fc2e4 100644 --- a/pkg/compose/restart.go +++ b/pkg/compose/restart.go @@ -55,7 +55,7 @@ func (s *composeService) restart(ctx context.Context, projectName string, option return InDependencyOrder(ctx, project, func(c context.Context, service string) error { config := project.Services[service] - err := s.waitDependencies(ctx, project, service, config.DependsOn, containers, 0) + err := s.waitDependencies(ctx, project, service, config.DependsOn, containers) if err != nil { return err } diff --git a/pkg/compose/run.go b/pkg/compose/run.go index 177ae6e7810..96b38d54776 100644 --- a/pkg/compose/run.go +++ b/pkg/compose/run.go @@ -182,7 +182,7 @@ func (s *composeService) doPrepareRun(ctx context.Context, project *types.Projec } if !options.NoDeps { - if err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, observedState, 0); err != nil { + if err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, observedState); err != nil { return prepareRunResult{}, err } } diff --git a/pkg/compose/service_containers.go b/pkg/compose/service_containers.go index ea7eba795fc..6de2e8bda75 100644 --- a/pkg/compose/service_containers.go +++ b/pkg/compose/service_containers.go @@ -160,13 +160,8 @@ func containerReasonEvents(containers Containers, eventFunc func(string, string) // ServiceConditionRunningOrHealthy is a service condition on status running or healthy const ServiceConditionRunningOrHealthy = "running_or_healthy" -func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependant string, dependencies types.DependsOnConfig, containers Containers, timeout time.Duration) error { +func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependant string, dependencies types.DependsOnConfig, containers Containers) error { origCtx := ctx - if timeout > 0 { - withTimeout, cancelFunc := context.WithTimeout(ctx, timeout) - defer cancelFunc() - ctx = withTimeout - } eg, ctx := errgroup.WithContext(ctx) for dep, config := range dependencies { if shouldWait, err := shouldWaitForDependency(dep, config, project); err != nil { diff --git a/pkg/compose/service_containers_test.go b/pkg/compose/service_containers_test.go index b654f807ffc..86746ee25c5 100644 --- a/pkg/compose/service_containers_test.go +++ b/pkg/compose/service_containers_test.go @@ -244,7 +244,7 @@ func TestWaitDependencies(t *testing.T) { "db": {Condition: ServiceConditionRunningOrHealthy}, "redis": {Condition: ServiceConditionRunningOrHealthy}, } - assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "", dependencies, nil, 0)) + assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "", dependencies, nil)) }) t.Run("should skip zero-replica dependencies after service hashing", func(t *testing.T) { replicas := 0 @@ -267,7 +267,7 @@ func TestWaitDependencies(t *testing.T) { assert.NilError(t, err) assert.NilError(t, tested.(*composeService).waitDependencies( - t.Context(), &project, "app", project.Services["app"].DependsOn, nil, 0, + t.Context(), &project, "app", project.Services["app"].DependsOn, nil, )) }) t.Run("should skip dependencies with condition service_started", func(t *testing.T) { @@ -281,7 +281,7 @@ func TestWaitDependencies(t *testing.T) { "db": {Condition: types.ServiceConditionStarted, Required: true}, "redis": {Condition: types.ServiceConditionStarted, Required: true}, } - assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "", dependencies, nil, 0)) + assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "", dependencies, nil)) }) t.Run("missing required dependency is an error", func(t *testing.T) { project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{ @@ -290,7 +290,7 @@ func TestWaitDependencies(t *testing.T) { dependencies := types.DependsOnConfig{ "db": {Condition: ServiceConditionRunningOrHealthy, Required: true}, } - err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, nil, 0) + err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, nil) assert.Error(t, err, "app is missing dependency db") }) t.Run("a dependency's pre_start hook runner does not count as the dependency being present", func(t *testing.T) { @@ -308,7 +308,7 @@ func TestWaitDependencies(t *testing.T) { Names: []string{"/db-hook-runner"}, Labels: map[string]string{api.ServiceLabel: "db", api.HookLabel: "pre_start"}, }} - err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers, 0) + err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers) assert.Error(t, err, "app is missing dependency db") }) t.Run("missing optional dependency is only a warning", func(t *testing.T) { @@ -318,7 +318,7 @@ func TestWaitDependencies(t *testing.T) { dependencies := types.DependsOnConfig{ "db": {Condition: ServiceConditionRunningOrHealthy, Required: false}, } - assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, nil, 0)) + assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, nil)) }) t.Run("failing optional dependency is skipped, not an error", func(t *testing.T) { project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{ @@ -341,7 +341,7 @@ func TestWaitDependencies(t *testing.T) { State: &container.State{Status: container.StateExited, ExitCode: 1}, }, }, nil) - assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers, 0)) + assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers)) }) t.Run("dry-run completes service_completed_successfully immediately", func(t *testing.T) { tested.(*composeService).dryRun = true @@ -359,7 +359,7 @@ func TestWaitDependencies(t *testing.T) { }} // no ContainerInspect expectation: dry-run must not inspect a // container that was never created. - assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers, 0)) + assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers)) }) } @@ -819,6 +819,26 @@ var startingDBInspect = client.ContainerInspectResult{ }, } +// waitWithOwnTimeout runs one depends_on wait the way the plan's +// OpWaitCondition nodes do under --wait-timeout: bounded by a timeout of its +// own (planExecutor.waitTimeout), the only way a wait gets one. A watchdog +// ends a wait that would ignore that timeout. +func waitWithOwnTimeout(t *testing.T, svc *composeService, project *types.Project, containers Containers, condition string, required bool, timeout time.Duration) error { + t.Helper() + exec := svc.newPlanExecutor(project, emptyObservedState(project.Name)) + exec.containersByService["db"] = containers + exec.waitTimeout = timeout + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + return exec.execBoundedWaitCondition(ctx, Operation{ + Type: OpWaitCondition, + ResourceID: "wait:db:" + condition, + Name: "db", + Condition: condition, + BestEffort: !required, + }) +} + // TestWaitDependencyDeadline locks the timeout semantics of the dependency // wait: an expired deadline surfaces as "timeout waiting for dependencies", // while a plain user cancellation is not a wait failure. @@ -849,31 +869,39 @@ func TestWaitDependencyDeadline(t *testing.T) { } t.Run("expired deadline is an error", func(t *testing.T) { - // The deadline fires between two checks and must not be swallowed. + // The caller's deadline (--wait-timeout of up/start --wait) fires + // between two checks and must not be swallowed. stillStarting() - err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers, 50*time.Millisecond) + ctx, cancel := context.WithTimeout(t.Context(), 50*time.Millisecond) + defer cancel() + err := tested.(*composeService).waitDependencies(ctx, &project, "app", dependencies, containers) assert.Error(t, err, "timeout waiting for dependencies") }) - // Regression guard: an optional dependency's deadline expiring (new as of - // planExecutor.waitTimeout giving execWaitCondition a real timeout for - // the first time -- see waitDependency's ctx.Done() branch) must be - // tolerated exactly like any other definitive failure of an optional - // dependency, not surfaced as "timeout waiting for dependencies". - t.Run("expired deadline on an optional dependency is skipped, not an error", func(t *testing.T) { - optionalDeps := types.DependsOnConfig{ - "db": {Condition: types.ServiceConditionHealthy, Required: false}, - } + t.Run("expired own timeout of a plan wait is an error", func(t *testing.T) { + // The same, for the timeout of the plan's own wait node. + stillStarting() + err := waitWithOwnTimeout(t, tested.(*composeService), &project, containers, types.ServiceConditionHealthy, true, 50*time.Millisecond) + assert.Assert(t, errors.Is(err, errWaitTimeout), "got %v", err) + assert.ErrorIs(t, err, context.DeadlineExceeded) + }) + + // Regression guard: an optional dependency's deadline expiring (the + // timeout of the plan's own wait node, planExecutor.waitTimeout -- see + // waitDependency's ctx.Done() branch) must be tolerated exactly like any + // other definitive failure of an optional dependency, not surfaced as + // "timeout waiting for dependencies". + t.Run("expired own timeout on an optional dependency is skipped, not an error", func(t *testing.T) { stillStarting() - err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", optionalDeps, containers, 50*time.Millisecond) + err := waitWithOwnTimeout(t, tested.(*composeService), &project, containers, types.ServiceConditionHealthy, false, 50*time.Millisecond) assert.NilError(t, err) }) // Regression guard: the tolerance above must apply only to a deadline - // THIS call's own timeout parameter is responsible for -- an unrelated, - // already-expired deadline the caller brought in (timeout == 0, so this - // call set no deadline of its own) is a different, real failure that - // must still propagate even for an optional dependency. + // THIS wait's own timeout is responsible for -- an unrelated, + // already-expired deadline the caller brought in (waitDependencies sets + // none of its own) is a different, real failure that must still + // propagate even for an optional dependency. t.Run("an inherited parent deadline is not swallowed for an optional dependency", func(t *testing.T) { optionalDeps := types.DependsOnConfig{ "db": {Condition: types.ServiceConditionHealthy, Required: false}, @@ -881,14 +909,14 @@ func TestWaitDependencyDeadline(t *testing.T) { parentCtx, cancel := context.WithTimeout(t.Context(), time.Millisecond) defer cancel() time.Sleep(10 * time.Millisecond) // let the parent's own deadline actually expire first - err := tested.(*composeService).waitDependencies(parentCtx, &project, "app", optionalDeps, containers, 0) + err := tested.(*composeService).waitDependencies(parentCtx, &project, "app", optionalDeps, containers) assert.Error(t, err, "timeout waiting for dependencies") }) t.Run("user cancellation is not a wait failure", func(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) cancel() - err := tested.(*composeService).waitDependencies(ctx, &project, "app", dependencies, containers, 0) + err := tested.(*composeService).waitDependencies(ctx, &project, "app", dependencies, containers) assert.NilError(t, err) }) @@ -899,7 +927,7 @@ func TestWaitDependencyDeadline(t *testing.T) { unsupportedDeps := types.DependsOnConfig{ "db": {Condition: "some_future_condition", Required: true}, } - err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", unsupportedDeps, containers, 2*time.Second) + err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", unsupportedDeps, containers) assert.NilError(t, err) var messages []string @@ -968,7 +996,7 @@ func TestWaitDependencyFirstCheck(t *testing.T) { dependencies := types.DependsOnConfig{"db": {Condition: condition, Required: true}} start := time.Now() - err = tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers, 0) + err = tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers) assert.NilError(t, err) assert.Equal(t, time.Since(start), tc.wait) }) @@ -1039,7 +1067,7 @@ func TestOptionalDependencyInspectFailure(t *testing.T) { tested, apiClient, events := newTested(t) apiClient.EXPECT().ContainerInspect(gomock.Any(), "db-ctr", gomock.Any()). Return(client.ContainerInspectResult{}, errors.New("Error response from daemon: No such container: db-ctr")) - err := tested.waitDependencies(t.Context(), &project, "app", dependenciesOn(condition, false), dbContainers, 0) + err := tested.waitDependencies(t.Context(), &project, "app", dependenciesOn(condition, false), dbContainers) assert.NilError(t, err) assert.Assert(t, wasSkipped(events)) }) @@ -1049,7 +1077,7 @@ func TestOptionalDependencyInspectFailure(t *testing.T) { tested, apiClient, events := newTested(t) apiClient.EXPECT().ContainerInspect(gomock.Any(), "db-ctr", gomock.Any()). Return(client.ContainerInspectResult{}, errors.New("Error response from daemon: No such container: db-ctr")) - err := tested.waitDependencies(t.Context(), &project, "app", dependenciesOn(condition, true), dbContainers, 0) + err := tested.waitDependencies(t.Context(), &project, "app", dependenciesOn(condition, true), dbContainers) assert.ErrorContains(t, err, "No such container: db-ctr") assert.Assert(t, !wasSkipped(events)) }) @@ -1066,7 +1094,7 @@ func TestOptionalDependencyInspectFailure(t *testing.T) { }).MinTimes(1) // a pending poll tick may win the select once more // a plain cancellation is not a wait failure (see // TestWaitDependencyDeadline), but it is no skip either - err := tested.waitDependencies(ctx, &project, "app", dependenciesOn(condition, false), dbContainers, 0) + err := tested.waitDependencies(ctx, &project, "app", dependenciesOn(condition, false), dbContainers) assert.NilError(t, err) assert.Assert(t, !wasSkipped(events)) }) @@ -1081,7 +1109,7 @@ func TestOptionalDependencyInspectFailure(t *testing.T) { cancel() return client.ContainerInspectResult{}, fmt.Errorf("inspect db-ctr: %w", context.Canceled) }) - err := tested.waitDependencies(ctx, &project, "app", dependenciesOn(condition, true), dbContainers, 0) + err := tested.waitDependencies(ctx, &project, "app", dependenciesOn(condition, true), dbContainers) assert.Assert(t, errors.Is(err, context.Canceled), "got %v", err) }) @@ -1094,7 +1122,7 @@ func TestOptionalDependencyInspectFailure(t *testing.T) { // with the deadline ctx, cancel := context.WithTimeout(t.Context(), time.Second) defer cancel() - err := tested.waitDependencies(ctx, &project, "app", dependenciesOn(condition, required), dbContainers, 0) + err := tested.waitDependencies(ctx, &project, "app", dependenciesOn(condition, required), dbContainers) assert.Error(t, err, "timeout waiting for dependencies") assert.Assert(t, !wasSkipped(events)) }) @@ -1109,16 +1137,21 @@ func TestOptionalDependencyInspectFailure(t *testing.T) { t.Parallel() tested, apiClient, _ := newTested(t) inspectFailsWithContext(apiClient) - duringInspect := tested.waitDependencies(t.Context(), &project, "app", dependenciesOn(condition, required), dbContainers, time.Second) + duringInspect := waitWithOwnTimeout(t, tested, &project, dbContainers, condition, required, time.Second) // no check in flight when the timeout expires: at most the one on entry polled, polledClient, _ := newTested(t) polledClient.EXPECT().ContainerInspect(gomock.Any(), "db-ctr", gomock.Any()).Return(startingDBInspect, nil).MaxTimes(1) - betweenPolls := polled.waitDependencies(t.Context(), &project, "app", dependenciesOn(condition, required), dbContainers, 50*time.Millisecond) + betweenPolls := waitWithOwnTimeout(t, polled, &project, dbContainers, condition, required, 50*time.Millisecond) - assert.Equal(t, fmt.Sprint(duringInspect), fmt.Sprint(betweenPolls)) + // the same end, whatever the instant the timeout expired at + assert.Equal(t, duringInspect == nil, betweenPolls == nil) + assert.Equal(t, errors.Is(duringInspect, errWaitTimeout), errors.Is(betweenPolls, errWaitTimeout)) if required { - assert.Error(t, duringInspect, "timeout waiting for dependencies") + assert.Assert(t, errors.Is(duringInspect, errWaitTimeout), "got %v", duringInspect) + assert.ErrorIs(t, duringInspect, context.DeadlineExceeded) + } else { + assert.NilError(t, duringInspect) } }) } diff --git a/pkg/compose/start.go b/pkg/compose/start.go index e67f46493f5..bae7710eb06 100644 --- a/pkg/compose/start.go +++ b/pkg/compose/start.go @@ -134,7 +134,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options Required: true, } } - if err := s.waitDependencies(ctx, project, project.Name, depends, containers, 0); err != nil { + if err := s.waitDependencies(ctx, project, project.Name, depends, containers); err != nil { if options.WaitTimeout > 0 && origCtx.Err() == nil && errors.Is(ctx.Err(), context.DeadlineExceeded) { return fmt.Errorf("application not healthy after %s", options.WaitTimeout) } diff --git a/pkg/compose/up.go b/pkg/compose/up.go index dc07f85548e..91971675640 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -184,7 +184,7 @@ func (s *composeService) upDetached(ctx context.Context, project *types.Project, Required: true, } } - if err := s.waitDependencies(ctx, project, project.Name, depends, containers, 0); err != nil { + if err := s.waitDependencies(ctx, project, project.Name, depends, containers); err != nil { if options.Start.WaitTimeout > 0 && origCtx.Err() == nil && errors.Is(ctx.Err(), context.DeadlineExceeded) { return fmt.Errorf("application not healthy after %s", options.Start.WaitTimeout) } From df6f665a23810724c3e15c17e5b82bdd2f132bff Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 27 Oct 2026 11:19:07 +0100 Subject: [PATCH 2/3] docs(compose): say who gives a dependency wait a timeout of its own Now that waitDependencies has no timeout parameter, the plan's wait nodes are the only ones to wrap the context of a wait: tell so in the comment of waitDependency, which compares the wrapped context with the original. Comments only. Part of #14081 Signed-off-by: Nicolas De Loof --- pkg/compose/service_containers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/compose/service_containers.go b/pkg/compose/service_containers.go index 6de2e8bda75..1754eb66298 100644 --- a/pkg/compose/service_containers.go +++ b/pkg/compose/service_containers.go @@ -195,7 +195,8 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr // condition is satisfied (done), definitively failed (err), or ctx is // cancelled. Each check reports (done, err): (false, nil) means keep polling. // origCtx is ctx as it stood before the caller wrapped it with this wait's -// own timeout (if any) -- comparing origCtx.Err() against ctx.Err() at the +// own timeout (if any: waitDependencies sets none, the plan's wait nodes do, +// see execBoundedWaitCondition) -- comparing origCtx.Err() against ctx.Err() at the // moment ctx.Done() fires is how this tells apart a DeadlineExceeded this // specific call's own wrap is responsible for from one inherited from an // ancestor context the caller never touched. A bare "did the caller From 42ce4b7661bc6194138ebdf403813fc8b4222c17 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 27 Oct 2026 11:21:09 +0100 Subject: [PATCH 3/3] test(compose): bound the unsupported-condition wait, compare the deadline class Review follow-ups of the removal of waitDependencies' timeout parameter: the unsupported-condition test lost the 2s bound the parameter gave it, so a regression that waits would hang instead of failing; and the test comparing a timeout that expires during the inspection with one that expires between polls now also compares whether they are a deadline. Also reword the comment of execWaitCondition, which still said that waitDependencies threads a timeout through its waits. Part of #14081 Signed-off-by: Nicolas De Loof --- pkg/compose/executor_ops.go | 18 ++++++++---------- pkg/compose/service_containers_test.go | 6 +++++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/compose/executor_ops.go b/pkg/compose/executor_ops.go index d087489353e..764ce7a050f 100644 --- a/pkg/compose/executor_ops.go +++ b/pkg/compose/executor_ops.go @@ -220,16 +220,14 @@ func (exec *planExecutor) resolveContainerSummary(op Operation) (container.Summa // warning, not a plan failure — matching waitDependencies' own // optional-dependency handling. // -// Unlike waitDependencies, this has no timeout of its own to apply here -- -// ctx is used exactly as given, already wrapped (or not) by executeNode's -// OpWaitCondition dispatch in executor.go. origCtx is ctx as it stood right -// before that wrap, passed through unchanged to waitDependency, which needs -// it to tell its own wait timeout apart from an inherited deadline (see its -// doc comment). upDetached (up.go) is the caller that needs a timeout here -// at all: planExecutor.waitTimeout gives this node its own fresh per-wait -// window, the same thing start()'s own WaitTimeout already threads through -// every waitDependencies call -- without also bounding unrelated Create or -// Start work the way wrapping the whole plan's ctx would. +// This applies no timeout of its own -- ctx is used exactly as given, +// already wrapped (or not) by executeNode's OpWaitCondition dispatch in +// executor.go. origCtx is ctx as it stood right before that wrap, passed +// through unchanged to waitDependency, which needs it to tell its own wait +// timeout apart from an inherited deadline (see its doc comment). +// planExecutor.waitTimeout (set by upDetached and start) is what gives this +// node its own fresh per-wait window, without also bounding unrelated Create +// or Start work the way wrapping the whole plan's ctx would. func (exec *planExecutor) execWaitCondition(ctx, origCtx context.Context, op Operation) error { s := exec.compose exec.containersMu.Lock() diff --git a/pkg/compose/service_containers_test.go b/pkg/compose/service_containers_test.go index 86746ee25c5..bf8641398a9 100644 --- a/pkg/compose/service_containers_test.go +++ b/pkg/compose/service_containers_test.go @@ -927,7 +927,10 @@ func TestWaitDependencyDeadline(t *testing.T) { unsupportedDeps := types.DependsOnConfig{ "db": {Condition: "some_future_condition", Required: true}, } - err := tested.(*composeService).waitDependencies(t.Context(), &project, "app", unsupportedDeps, containers) + // the bound makes a regression that waits fail instead of hanging + ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second) + defer cancel() + err := tested.(*composeService).waitDependencies(ctx, &project, "app", unsupportedDeps, containers) assert.NilError(t, err) var messages []string @@ -1147,6 +1150,7 @@ func TestOptionalDependencyInspectFailure(t *testing.T) { // the same end, whatever the instant the timeout expired at assert.Equal(t, duringInspect == nil, betweenPolls == nil) assert.Equal(t, errors.Is(duringInspect, errWaitTimeout), errors.Is(betweenPolls, errWaitTimeout)) + assert.Equal(t, errors.Is(duringInspect, context.DeadlineExceeded), errors.Is(betweenPolls, context.DeadlineExceeded)) if required { assert.Assert(t, errors.Is(duringInspect, errWaitTimeout), "got %v", duringInspect) assert.ErrorIs(t, duringInspect, context.DeadlineExceeded)