Change:
attemptsExpected: 0, errExpected:      context.DeadlineExceeded, }, <ins> { name:      "context canceled before the second execution and immediate", immediate: true, context: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Second) }, callback: func(attempts int) (bool, error) { return false, nil }, attemptsExpected: 1, errExpected:      context.DeadlineExceeded, timer:            Backoff{Duration: 2 * time.Second}.Timer(), }, { name:      "immediate and long duration of condition and sliding false", immediate: true, sliding:   false, context: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Second) }, callback: func(attempts int) (bool, error) { if attempts >= 4 { return true, nil } time.Sleep(time.Second / 5) return false, nil }, attemptsExpected: 4, timer:            Backoff{Duration: time.Second / 5, Jitter: 0.001}.Timer(), }, { name:      "immediate and long duration of condition and sliding true", immediate: true, sliding:   true, context: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Second) }, callback: func(attempts int) (bool, error) { if attempts >= 4 { return true, nil } time.Sleep(time.Second / 5) return false, nil }, errExpected:      context.DeadlineExceeded, attemptsExpected: 3, timer:            Backoff{Duration: time.Second / 5, Jitter: 0.001}.Timer(), }, </ins> } for _, test := range tests { <del> for _, immediate := range []bool{true, false} { t.Run(fmt.Sprintf("immediate=%t", immediate), func(t *testing.T) { for _, sliding := range []bool{true, false} { t.Run(fmt.Sprintf("sliding=%t", sliding), func(t *testing.T) { t.Run(test.name, func(t *testing.T) { contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() timer := Backoff{Duration: time.Microsecond}.Timer() attempts := 0 err := loopConditionUntilContext(ctx, timer, test.immediate, test.sliding, func(_ context.Context) (bool, error) { attempts++ defer func() { if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { cancel() } }() return test.callback(attempts) }) if test.errExpected != err { t.Errorf("expected error: %v but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) } }) }) } </del> <ins> t.Run(test.name, func(t *testing.T) { contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() timer := test.timer if timer == nil { timer = Backoff{Duration: time.Microsecond}.Timer() } attempts := 0 err := loopConditionUntilContext(ctx, timer, test.immediate, test.sliding, func(_ context.Context) (bool, error) { attempts++ defer func() { if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { cancel() } }() return test.callback(attempts) </ins> }) <del> } </del> <ins> if test.errExpected != err { t.Errorf("expected error: %v but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) } }) </ins> } }

Discussion:
While trying to update to use latest version of package and to migrate away from deprecated , function, our tests fail as it seems new function immediately executes given condition twice, which is not mentioned anywhere. Result of executed test specified below: Since has been deprecated and it recommends using , I expected it to have the equivalent semantics to allow easy migration from deprecated functions, as no changelog or documentation mentions new semantics. Playground: Local: file with the following content: as . No response_ <details</details<details</details<details</details<details</details<details</details<details</details
/sig api-machinery
CC since you were involved in
/assign /triage accepted
I'd like to work on this one if it's allowed I think this is a simple fix with few lines removed: i.e.: instead of only wait a loop if immediate is false, we wait a time loop after special execution also if the immediate is true.
/assign
/assign