Eventually/Never panic on a nil condition, aborting the test binary
Eventually / Never panic instead of failing, and a nil condition takes the whole binary down
These assertions are supposed to report a failure through the TestingT they are given.
Four of them do not: with a nil condition, or a tick that is zero or negative, they
panic. The nil case is the worse of the two, because the panic happens on a goroutine
Eventually spawns, so the caller cannot recover it and t.Errorf is never reached.
package main
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestNothing(t *testing.T) {
assert.Eventually(t, nil, 10*time.Millisecond, time.Millisecond)
}panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x0]
goroutine 1 [running]:
github.com/stretchr/testify/assert.Eventually.func1()
assert/assertions.go:2013
created by github.com/stretchr/testify/assert.Eventually in goroutine 1recover() in the test cannot catch that, so one nil slips into a helper and the entire
test binary aborts with a stack trace pointing into testify, rather than one test failing
with a message.
The cause is two lines:
// assert/assertions.go:2012-2024
ch := make(chan bool, 1)
checkCond := func() { ch <- condition() }
...
go checkCond()condition is dereferenced on the goroutine with no nil check, and tick goes straight
into time.NewTicker, which panics on a non-positive interval.
| call | result |
|---|---|
assert.Eventually(t, nil, d, d) |
panic, goroutine 1, binary aborts |
assert.Never(t, nil, d, d) |
panic, goroutine 19 |
assert.EventuallyWithT(t, nil, d, d) |
panic, goroutine 19 |
assert.NeverWithT(t, nil, d, d) |
panic, goroutine 19 |
assert.Eventually(t, cond, time.Second, 0) |
panic: non-positive interval for NewTicker |
assert.Never(t, cond, time.Second, 0) |
same |
assert.Eventually(t, cond, time.Second, -time.Second) |
same |
The tick ones are less severe — a recover() in the caller does catch those, and a
caller who passes 0 has arguably written a mistake — but they are still a panic where a
message would do, and tick=0 is easy to reach by computing the interval.
A nil guard for this already exists in the same file
isFunction gets it right, which is why assert.Equal(t, nil, someFunc) reports a failure
instead of crashing:
// assert/assertions.go:1974
func isFunction(arg interface{}) bool {
if arg == nil {
return false
}
return reflect.TypeOf(arg).Kind() == reflect.Func
}Not re-raising the Subset part, but asking about the rest of that family
While sweeping the assertion surface I hit the same shape in eight more assertions, all
from one unguarded reflect.TypeOf(x).Kind() — reflect.TypeOf(nil) is nil, so .Kind()
dereferences it:
| site | assertions affected |
|---|---|
assert/assertions.go:1016, :1084 |
Subset, NotSubset |
assert/assertions.go:1163 (isList) |
ElementsMatch, NotElementsMatch |
assert/assertion_order.go:10 (isOrdered) |
IsIncreasing, IsNonIncreasing, IsDecreasing, IsNonDecreasing |
So assert.IsIncreasing(t, nil), assert.ElementsMatch(t, nil, []int{1}) and
assert.Subset(t, nil, []int{1}) all panic with the same nil dereference.
Subset and NotSubset were reported before, in #1918, closed on 2026-07-29 without a
comment, so I am deliberately not re-filing that part — I would rather ask than
re-litigate. I cannot tell whether that close was about those two functions or about the
approach, so: is a nil guard welcome for the other six, or is passing nil a caller error
across the board? If the latter, a panic with a clear message may be the more honest
outcome than a failure report, and I will drop it.
There is one more in the same family, which is not a reflect issue:
matchRegexp type-asserts and then dereferences, so a typed nil slips past:
// assert/assertions.go:1711
if rr, ok := rx.(*regexp.Regexp); ok {
r = rr
}
...
return r.MatchString(v) // r is nil hereassert.Regexp(t, (*regexp.Regexp)(nil), "x") panics. A malformed pattern also panics,
via regexp.MustCompile, but that one I read as idiomatic for a caller error — and it is
already being dealt with in #1857 — so I am only raising the typed-nil case.
What I would send
Happy to open a PR for the Eventually family and matchRegexp — a nil check on
condition, a tick <= 0 check that fails with a message, and a rx == nil check — with
tests for each. I have not touched the reflect family pending your answer above.
Reproduced against master at 87a7b9d with Go 1.24.3.
Source: stretchr/testify