Merge branch 'master' into doc-update-for-error-fn

This commit is contained in:
Archit Agarwal 2025-05-07 17:26:11 +05:30 committed by GitHub
commit 01b9a87c30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 143 additions and 57 deletions

View File

@ -1949,6 +1949,7 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
} }
ch := make(chan bool, 1) ch := make(chan bool, 1)
checkCond := func() { ch <- condition() }
timer := time.NewTimer(waitFor) timer := time.NewTimer(waitFor)
defer timer.Stop() defer timer.Stop()
@ -1956,18 +1957,23 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
ticker := time.NewTicker(tick) ticker := time.NewTicker(tick)
defer ticker.Stop() defer ticker.Stop()
for tick := ticker.C; ; { var tickC <-chan time.Time
// Check the condition once first on the initial call.
go checkCond()
for {
select { select {
case <-timer.C: case <-timer.C:
return Fail(t, "Condition never satisfied", msgAndArgs...) return Fail(t, "Condition never satisfied", msgAndArgs...)
case <-tick: case <-tickC:
tick = nil tickC = nil
go func() { ch <- condition() }() go checkCond()
case v := <-ch: case v := <-ch:
if v { if v {
return true return true
} }
tick = ticker.C tickC = ticker.C
} }
} }
} }
@ -2037,35 +2043,42 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time
var lastFinishedTickErrs []error var lastFinishedTickErrs []error
ch := make(chan *CollectT, 1) ch := make(chan *CollectT, 1)
checkCond := func() {
collect := new(CollectT)
defer func() {
ch <- collect
}()
condition(collect)
}
timer := time.NewTimer(waitFor) timer := time.NewTimer(waitFor)
defer timer.Stop() defer timer.Stop()
ticker := time.NewTicker(tick) ticker := time.NewTicker(tick)
defer ticker.Stop() defer ticker.Stop()
for tick := ticker.C; ; { var tickC <-chan time.Time
// Check the condition once first on the initial call.
go checkCond()
for {
select { select {
case <-timer.C: case <-timer.C:
for _, err := range lastFinishedTickErrs { for _, err := range lastFinishedTickErrs {
t.Errorf("%v", err) t.Errorf("%v", err)
} }
return Fail(t, "Condition never satisfied", msgAndArgs...) return Fail(t, "Condition never satisfied", msgAndArgs...)
case <-tick: case <-tickC:
tick = nil tickC = nil
go func() { go checkCond()
collect := new(CollectT)
defer func() {
ch <- collect
}()
condition(collect)
}()
case collect := <-ch: case collect := <-ch:
if !collect.failed() { if !collect.failed() {
return true return true
} }
// Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached. // Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached.
lastFinishedTickErrs = collect.errors lastFinishedTickErrs = collect.errors
tick = ticker.C tickC = ticker.C
} }
} }
} }
@ -2080,6 +2093,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D
} }
ch := make(chan bool, 1) ch := make(chan bool, 1)
checkCond := func() { ch <- condition() }
timer := time.NewTimer(waitFor) timer := time.NewTimer(waitFor)
defer timer.Stop() defer timer.Stop()
@ -2087,18 +2101,23 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D
ticker := time.NewTicker(tick) ticker := time.NewTicker(tick)
defer ticker.Stop() defer ticker.Stop()
for tick := ticker.C; ; { var tickC <-chan time.Time
// Check the condition once first on the initial call.
go checkCond()
for {
select { select {
case <-timer.C: case <-timer.C:
return true return true
case <-tick: case <-tickC:
tick = nil tickC = nil
go func() { ch <- condition() }() go checkCond()
case v := <-ch: case v := <-ch:
if v { if v {
return Fail(t, "Condition satisfied", msgAndArgs...) return Fail(t, "Condition satisfied", msgAndArgs...)
} }
tick = ticker.C tickC = ticker.C
} }
} }
} }

View File

@ -3058,6 +3058,49 @@ func TestEventuallyWithTFailNow(t *testing.T) {
Len(t, mockT.errors, 1) Len(t, mockT.errors, 1)
} }
// Check that a long running condition doesn't block Eventually.
// See issue 805 (and its long tail of following issues)
func TestEventuallyTimeout(t *testing.T) {
mockT := new(testing.T)
NotPanics(t, func() {
done, done2 := make(chan struct{}), make(chan struct{})
// A condition function that returns after the Eventually timeout
condition := func() bool {
// Wait until Eventually times out and terminates
<-done
close(done2)
return true
}
False(t, Eventually(mockT, condition, time.Millisecond, time.Microsecond))
close(done)
<-done2
})
}
func TestEventuallySucceedQuickly(t *testing.T) {
mockT := new(testing.T)
condition := func() bool { return true }
// By making the tick longer than the total duration, we expect that this test would fail if
// we didn't check the condition before the first tick elapses.
True(t, Eventually(mockT, condition, 100*time.Millisecond, time.Second))
}
func TestEventuallyWithTSucceedQuickly(t *testing.T) {
mockT := new(testing.T)
condition := func(t *CollectT) {}
// By making the tick longer than the total duration, we expect that this test would fail if
// we didn't check the condition before the first tick elapses.
True(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, time.Second))
}
func TestNeverFalse(t *testing.T) { func TestNeverFalse(t *testing.T) {
condition := func() bool { condition := func() bool {
return false return false
@ -3085,27 +3128,13 @@ func TestNeverTrue(t *testing.T) {
False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
} }
// Check that a long running condition doesn't block Eventually. func TestNeverFailQuickly(t *testing.T) {
// See issue 805 (and its long tail of following issues)
func TestEventuallyTimeout(t *testing.T) {
mockT := new(testing.T) mockT := new(testing.T)
NotPanics(t, func() { // By making the tick longer than the total duration, we expect that this test would fail if
done, done2 := make(chan struct{}), make(chan struct{}) // we didn't check the condition before the first tick elapses.
condition := func() bool { return true }
// A condition function that returns after the Eventually timeout False(t, Never(mockT, condition, 100*time.Millisecond, time.Second))
condition := func() bool {
// Wait until Eventually times out and terminates
<-done
close(done2)
return true
}
False(t, Eventually(mockT, condition, time.Millisecond, time.Microsecond))
close(done)
<-done2
})
} }
func Test_validateEqualArgs(t *testing.T) { func Test_validateEqualArgs(t *testing.T) {

View File

@ -948,6 +948,8 @@ func (args Arguments) Is(objects ...interface{}) bool {
return true return true
} }
type outputRenderer func() string
// Diff gets a string describing the differences between the arguments // Diff gets a string describing the differences between the arguments
// and the specified objects. // and the specified objects.
// //
@ -955,7 +957,7 @@ func (args Arguments) Is(objects ...interface{}) bool {
func (args Arguments) Diff(objects []interface{}) (string, int) { func (args Arguments) Diff(objects []interface{}) (string, int) {
// TODO: could return string as error and nil for No difference // TODO: could return string as error and nil for No difference
output := "\n" var outputBuilder strings.Builder
var differences int var differences int
maxArgCount := len(args) maxArgCount := len(args)
@ -963,24 +965,35 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
maxArgCount = len(objects) maxArgCount = len(objects)
} }
outputRenderers := []outputRenderer{}
for i := 0; i < maxArgCount; i++ { for i := 0; i < maxArgCount; i++ {
i := i
var actual, expected interface{} var actual, expected interface{}
var actualFmt, expectedFmt string var actualFmt, expectedFmt func() string
if len(objects) <= i { if len(objects) <= i {
actual = "(Missing)" actual = "(Missing)"
actualFmt = "(Missing)" actualFmt = func() string {
return "(Missing)"
}
} else { } else {
actual = objects[i] actual = objects[i]
actualFmt = fmt.Sprintf("(%[1]T=%[1]v)", actual) actualFmt = func() string {
return fmt.Sprintf("(%[1]T=%[1]v)", actual)
}
} }
if len(args) <= i { if len(args) <= i {
expected = "(Missing)" expected = "(Missing)"
expectedFmt = "(Missing)" expectedFmt = func() string {
return "(Missing)"
}
} else { } else {
expected = args[i] expected = args[i]
expectedFmt = fmt.Sprintf("(%[1]T=%[1]v)", expected) expectedFmt = func() string {
return fmt.Sprintf("(%[1]T=%[1]v)", expected)
}
} }
if matcher, ok := expected.(argumentMatcher); ok { if matcher, ok := expected.(argumentMatcher); ok {
@ -988,16 +1001,22 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
func() { func() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
actualFmt = fmt.Sprintf("panic in argument matcher: %v", r) actualFmt = func() string {
return fmt.Sprintf("panic in argument matcher: %v", r)
}
} }
}() }()
matches = matcher.Matches(actual) matches = matcher.Matches(actual)
}() }()
if matches { if matches {
output = fmt.Sprintf("%s\t%d: PASS: %s matched by %s\n", output, i, actualFmt, matcher) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: PASS: %s matched by %s\n", i, actualFmt(), matcher)
})
} else { } else {
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: FAIL: %s not matched by %s\n", i, actualFmt(), matcher)
})
} }
} else { } else {
switch expected := expected.(type) { switch expected := expected.(type) {
@ -1006,13 +1025,17 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
if reflect.TypeOf(actual).Name() != string(expected) && reflect.TypeOf(actual).String() != string(expected) { if reflect.TypeOf(actual).Name() != string(expected) && reflect.TypeOf(actual).String() != string(expected) {
// not match // not match
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: FAIL: type %s != type %s - %s\n", i, expected, reflect.TypeOf(actual).Name(), actualFmt())
})
} }
case *IsTypeArgument: case *IsTypeArgument:
actualT := reflect.TypeOf(actual) actualT := reflect.TypeOf(actual)
if actualT != expected.t { if actualT != expected.t {
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected.t.Name(), actualT.Name(), actualFmt) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: FAIL: type %s != type %s - %s\n", i, expected.t.Name(), actualT.Name(), actualFmt())
})
} }
case *FunctionalOptionsArgument: case *FunctionalOptionsArgument:
var name string var name string
@ -1023,26 +1046,36 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
const tName = "[]interface{}" const tName = "[]interface{}"
if name != reflect.TypeOf(actual).String() && len(expected.values) != 0 { if name != reflect.TypeOf(actual).String() && len(expected.values) != 0 {
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: FAIL: type %s != type %s - %s\n", i, tName, reflect.TypeOf(actual).Name(), actualFmt())
})
} else { } else {
if ef, af := assertOpts(expected.values, actual); ef == "" && af == "" { if ef, af := assertOpts(expected.values, actual); ef == "" && af == "" {
// match // match
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: PASS: %s == %s\n", i, tName, tName)
})
} else { } else {
// not match // not match
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, af, ef) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: FAIL: %s != %s\n", i, af, ef)
})
} }
} }
default: default:
if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) {
// match // match
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actualFmt, expectedFmt) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: PASS: %s == %s\n", i, actualFmt(), expectedFmt())
})
} else { } else {
// not match // not match
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt) outputRenderers = append(outputRenderers, func() string {
return fmt.Sprintf("\t%d: FAIL: %s != %s\n", i, actualFmt(), expectedFmt())
})
} }
} }
} }
@ -1053,7 +1086,12 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
return "No differences.", differences return "No differences.", differences
} }
return output, differences outputBuilder.WriteString("\n")
for _, r := range outputRenderers {
outputBuilder.WriteString(r())
}
return outputBuilder.String(), differences
} }
// Assert compares the arguments with the specified objects and fails if // Assert compares the arguments with the specified objects and fails if