@@ -50,9 +50,10 @@ const (
5050 // esReadyFallback applies when the binary has no deadline to derive from.
5151 esReadyFallback = 15 * time .Minute
5252
53- // esReadyMargin is the room left before the deadline for the wait to report
54- // its own failure. It does not cover teardown, which can outlast it.
55- esReadyMargin = 30 * time .Second
53+ // esReadyMargin is the room left before the deadline. 30s covered the wait
54+ // reporting its own failure but not teardown, which is a stern dump, a
55+ // kubectl exec plus tar for coverage, and a kind delete.
56+ esReadyMargin = 90 * time .Second
5657)
5758
5859var TestTempDir string
@@ -93,21 +94,24 @@ func logContainerRestarts(t *testing.T, c common.Cluster, ctx context.Context, n
9394 }
9495}
9596
96- // esReadyBudget caps a wait strictly below the enclosing -timeout, so an
97- // Elasticsearch deployment that never becomes ready fails by name instead of
98- // letting the package binary panic and report only a goroutine dump.
99- func esReadyBudget (t * testing.T ) time.Duration {
97+ // esReadyBudget shares what is left of the -timeout between the deployments
98+ // still to check. Handing it all to the first let a slow one spend the
99+ // package's budget, leaving the rest to fail instantly under the wrong name.
100+ // The fallback is already per-wait, so it is not divided.
101+ func esReadyBudget (t * testing.T , waitsLeft int ) time.Duration {
100102 deadline , ok := t .Deadline ()
101103 if ! ok {
102104 return esReadyFallback
103105 }
104- return budgetWithin (time .Until (deadline ))
106+ return budgetWithin (time .Until (deadline ), waitsLeft )
105107}
106108
107- // budgetWithin never returns a non-positive duration: require.Eventually treats
108- // one as already expired, which would fail every run instead of only stalled ones.
109- func budgetWithin (remaining time.Duration ) time.Duration {
110- if budget := remaining - esReadyMargin ; budget > 0 {
109+ // budgetWithin floors after dividing, not before. A third of a second is
110+ // positive but expires on the first tick, so flooring on the undivided
111+ // remainder would put the misattribution this split removes back at the
112+ // boundary: elasticsearch7 blamed for a package that had already run out.
113+ func budgetWithin (remaining time.Duration , waitsLeft int ) time.Duration {
114+ if budget := (remaining - esReadyMargin ) / time .Duration (waitsLeft ); budget > time .Second {
111115 return budget
112116 }
113117 return time .Second
@@ -117,20 +121,35 @@ func TestBudgetWithin(t *testing.T) {
117121 for _ , c := range []struct {
118122 name string
119123 remaining time.Duration
124+ waitsLeft int
120125 want time.Duration
121126 }{
122- {"ample" , 20 * time .Minute , 20 * time .Minute - esReadyMargin },
123- {"just above the margin" , esReadyMargin + time .Second , time .Second },
124- {"exactly the margin" , esReadyMargin , time .Second },
125- {"already past the deadline" , - time .Minute , time .Second },
127+ {"shared between the waits still to come" , 20 * time .Minute , 3 , (20 * time .Minute - esReadyMargin ) / 3 },
128+ {"the last wait gets what is left" , 20 * time .Minute , 1 , 20 * time .Minute - esReadyMargin },
129+ // The floor is a second per wait, not a second shared between them.
130+ {"just above the margin" , esReadyMargin + time .Second , 3 , time .Second },
131+ {"a slice under a second" , esReadyMargin + 2 * time .Second , 3 , time .Second },
132+ {"exactly the margin" , esReadyMargin , 3 , time .Second },
133+ {"already past the deadline" , - time .Minute , 3 , time .Second },
126134 } {
127135 t .Run (c .name , func (t * testing.T ) {
128- require .Equal (t , c .want , budgetWithin (c .remaining ))
129- require .Positive (t , budgetWithin (c .remaining ))
136+ require .Equal (t , c .want , budgetWithin (c .remaining , c . waitsLeft ))
137+ require .Positive (t , budgetWithin (c .remaining , c . waitsLeft ))
130138 })
131139 }
132140}
133141
142+ // The whole point of the split: no single wait may take the budget the others
143+ // still need.
144+ func TestBudgetLeavesRoomForTheRemainingWaits (t * testing.T ) {
145+ const remaining = 20 * time .Minute
146+
147+ first := budgetWithin (remaining , 3 )
148+
149+ require .Less (t , first , remaining - esReadyMargin )
150+ require .LessOrEqual (t , 3 * first , remaining - esReadyMargin )
151+ }
152+
134153func TestElasticsearch_MultiVersion (t * testing.T ) {
135154 common .Initialize (t )
136155 ns := "logging"
@@ -485,15 +504,16 @@ func TestElasticsearch_MultiVersion(t *testing.T) {
485504 }
486505 common .RequireNoError (t , c .GetClient ().Create (ctx , es9Deployment ))
487506
488- for _ , name := range []string {"elasticsearch7" , "elasticsearch8" , "elasticsearch9" } {
507+ deployments := []string {"elasticsearch7" , "elasticsearch8" , "elasticsearch9" }
508+ for i , name := range deployments {
489509 t .Logf ("Waiting for %s deployment to be ready..." , name )
490- require .Eventually (t , func () bool {
510+ require .Eventuallyf (t , func () bool {
491511 if wait .DeploymentAvailable (t , c .GetClient (), ctx , ns , name )() {
492512 return true
493513 }
494514 logContainerRestarts (t , c , ctx , ns , name )
495515 return false
496- }, esReadyBudget (t ) , 10 * time .Second )
516+ }, esReadyBudget (t , len ( deployments ) - i ) , 10 * time .Second , "%s never became ready" , name )
497517 }
498518
499519 logging := v1beta1.Logging {
0 commit comments