#1970·testify

Eventually/Never panic on a nil condition, aborting the test binary

Author: feiiiiii5Created Sep 27, 2026Updated Sep 27, 2026

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.

go
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 1

recover() 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:

go
// 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.

All eight paths, each reproduced in its own process
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:

go
// 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:

go
// assert/assertions.go:1711
if rr, ok := rx.(*regexp.Regexp); ok {
	r = rr
}
...
return r.MatchString(v)   // r is nil here

assert.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.