assert.Empty: comprehensive doc of "Empty"-ness rules

Document the assert.Empty rules more comprehensively. This exposes our
quirks to the user to avoid wrong expectations.

Add many many many more test cases that document edges cases and will allow
to catch breaking changes and avoid regressions.
This commit is contained in:
Olivier Mengué 2025-05-28 16:08:21 +02:00
parent 0ff4bb43de
commit 6485e376c5
6 changed files with 192 additions and 33 deletions

View File

@ -50,10 +50,19 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
} }
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Emptyf asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// assert.Emptyf(t, obj, "error message %s", "formatted") // assert.Emptyf(t, obj, "error message %s", "formatted")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok { if h, ok := t.(tHelper); ok {
h.Helper() h.Helper()
@ -595,8 +604,7 @@ func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg str
return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
} }
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmptyf asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { // if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1]) // assert.Equal(t, "two", obj[1])

View File

@ -92,10 +92,19 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st
return ElementsMatchf(a.t, listA, listB, msg, args...) return ElementsMatchf(a.t, listA, listB, msg, args...)
} }
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Empty asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// a.Empty(obj) // a.Empty(obj)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok { if h, ok := a.t.(tHelper); ok {
h.Helper() h.Helper()
@ -103,10 +112,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
return Empty(a.t, object, msgAndArgs...) return Empty(a.t, object, msgAndArgs...)
} }
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Emptyf asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// a.Emptyf(obj, "error message %s", "formatted") // a.Emptyf(obj, "error message %s", "formatted")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok { if h, ok := a.t.(tHelper); ok {
h.Helper() h.Helper()
@ -1182,8 +1200,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg
return NotElementsMatchf(a.t, listA, listB, msg, args...) return NotElementsMatchf(a.t, listA, listB, msg, args...)
} }
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmpty asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if a.NotEmpty(obj) { // if a.NotEmpty(obj) {
// assert.Equal(t, "two", obj[1]) // assert.Equal(t, "two", obj[1])
@ -1195,8 +1212,7 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo
return NotEmpty(a.t, object, msgAndArgs...) return NotEmpty(a.t, object, msgAndArgs...)
} }
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmptyf asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if a.NotEmptyf(obj, "error message %s", "formatted") { // if a.NotEmptyf(obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1]) // assert.Equal(t, "two", obj[1])

View File

@ -773,10 +773,19 @@ func isEmpty(object interface{}) bool {
} }
} }
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Empty asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// assert.Empty(t, obj) // assert.Empty(t, obj)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
pass := isEmpty(object) pass := isEmpty(object)
if !pass { if !pass {
@ -789,8 +798,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return pass return pass
} }
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmpty asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if assert.NotEmpty(t, obj) { // if assert.NotEmpty(t, obj) {
// assert.Equal(t, "two", obj[1]) // assert.Equal(t, "two", obj[1])

View File

@ -1728,24 +1728,119 @@ func Test_isEmpty(t *testing.T) {
True(t, isEmpty("")) True(t, isEmpty(""))
True(t, isEmpty(nil)) True(t, isEmpty(nil))
True(t, isEmpty(error(nil)))
True(t, isEmpty((*int)(nil)))
True(t, isEmpty((*string)(nil)))
True(t, isEmpty(new(string)))
True(t, isEmpty([]string{})) True(t, isEmpty([]string{}))
True(t, isEmpty([]string(nil)))
True(t, isEmpty([]byte(nil)))
True(t, isEmpty([]byte{}))
True(t, isEmpty([]byte("")))
True(t, isEmpty([]bool(nil)))
True(t, isEmpty([]bool{}))
True(t, isEmpty([]interface{}(nil)))
True(t, isEmpty([]interface{}{}))
True(t, isEmpty(struct{}{}))
True(t, isEmpty(&struct{}{}))
True(t, isEmpty(struct{ A int }{A: 0}))
True(t, isEmpty(struct{ a int }{a: 0}))
True(t, isEmpty(struct {
a int
B int
}{a: 0, B: 0}))
True(t, isEmpty(0)) True(t, isEmpty(0))
True(t, isEmpty(int(0)))
True(t, isEmpty(int8(0)))
True(t, isEmpty(int16(0)))
True(t, isEmpty(uint16(0)))
True(t, isEmpty(int32(0))) True(t, isEmpty(int32(0)))
True(t, isEmpty(uint32(0)))
True(t, isEmpty(int64(0))) True(t, isEmpty(int64(0)))
True(t, isEmpty(uint64(0)))
True(t, isEmpty('\u0000')) // rune => int32
True(t, isEmpty(float32(0)))
True(t, isEmpty(float64(0)))
True(t, isEmpty(0i)) // complex
True(t, isEmpty(0.0i)) // complex
True(t, isEmpty(false)) True(t, isEmpty(false))
True(t, isEmpty(new(bool)))
True(t, isEmpty(map[string]string{})) True(t, isEmpty(map[string]string{}))
True(t, isEmpty(map[string]string(nil)))
True(t, isEmpty(new(time.Time))) True(t, isEmpty(new(time.Time)))
True(t, isEmpty(time.Time{})) True(t, isEmpty(time.Time{}))
True(t, isEmpty(make(chan struct{}))) True(t, isEmpty(make(chan struct{})))
True(t, isEmpty([1]int{})) True(t, isEmpty(chan struct{}(nil)))
True(t, isEmpty(chan<- struct{}(nil)))
True(t, isEmpty(make(chan struct{})))
True(t, isEmpty(make(chan<- struct{})))
True(t, isEmpty(make(chan struct{}, 1)))
True(t, isEmpty(make(chan<- struct{}, 1)))
True(t, isEmpty([1]int{0}))
True(t, isEmpty([2]int{0, 0}))
True(t, isEmpty([8]int{}))
True(t, isEmpty([...]int{7: 0}))
True(t, isEmpty([...]bool{false, false}))
True(t, isEmpty(errors.New(""))) // BEWARE
True(t, isEmpty([]error{}))
True(t, isEmpty([]error(nil)))
True(t, isEmpty(&[1]int{0}))
True(t, isEmpty(&[2]int{0, 0}))
False(t, isEmpty("something")) False(t, isEmpty("something"))
False(t, isEmpty(errors.New("something"))) False(t, isEmpty(errors.New("something")))
False(t, isEmpty([]string{"something"})) False(t, isEmpty([]string{"something"}))
False(t, isEmpty(1)) False(t, isEmpty(1))
False(t, isEmpty(int(1)))
False(t, isEmpty(uint(1)))
False(t, isEmpty(byte(1)))
False(t, isEmpty(int8(1)))
False(t, isEmpty(uint8(1)))
False(t, isEmpty(int16(1)))
False(t, isEmpty(uint16(1)))
False(t, isEmpty(int32(1)))
False(t, isEmpty(uint32(1)))
False(t, isEmpty(int64(1)))
False(t, isEmpty(uint64(1)))
False(t, isEmpty('A')) // rune => int32
False(t, isEmpty(true)) False(t, isEmpty(true))
False(t, isEmpty(1.0))
False(t, isEmpty(1i)) // complex
False(t, isEmpty([]byte{0})) // elements values are ignored for slices
False(t, isEmpty([]byte{0, 0})) // elements values are ignored for slices
False(t, isEmpty([]string{""})) // elements values are ignored for slices
False(t, isEmpty([]string{"a"})) // elements values are ignored for slices
False(t, isEmpty([]bool{false})) // elements values are ignored for slices
False(t, isEmpty([]bool{true})) // elements values are ignored for slices
False(t, isEmpty([]error{errors.New("xxx")}))
False(t, isEmpty([]error{nil})) // BEWARE
False(t, isEmpty([]error{errors.New("")})) // BEWARE
False(t, isEmpty(map[string]string{"Hello": "World"})) False(t, isEmpty(map[string]string{"Hello": "World"}))
False(t, isEmpty(map[string]string{"": ""}))
False(t, isEmpty(map[string]string{"foo": ""}))
False(t, isEmpty(map[string]string{"": "foo"}))
False(t, isEmpty(chWithValue)) False(t, isEmpty(chWithValue))
False(t, isEmpty([1]bool{true}))
False(t, isEmpty([2]bool{false, true}))
False(t, isEmpty([...]bool{10: true}))
False(t, isEmpty([]int{0}))
False(t, isEmpty([]int{42}))
False(t, isEmpty([1]int{42})) False(t, isEmpty([1]int{42}))
False(t, isEmpty([2]int{0, 42}))
False(t, isEmpty(&[1]int{42}))
False(t, isEmpty(&[2]int{0, 42}))
False(t, isEmpty([1]*int{new(int)})) // array elements must be the zero value, not any Empty value
False(t, isEmpty(struct{ A int }{A: 42}))
False(t, isEmpty(struct{ a int }{a: 42}))
False(t, isEmpty(struct{ a *int }{a: new(int)})) // fields must be the zero value, not any Empty value
False(t, isEmpty(struct{ a []int }{a: []int{}})) // fields must be the zero value, not any Empty value
False(t, isEmpty(struct {
a int
B int
}{a: 0, B: 42}))
False(t, isEmpty(struct {
a int
B int
}{a: 42, B: 0}))
} }
func TestEmpty(t *testing.T) { func TestEmpty(t *testing.T) {

View File

@ -117,10 +117,19 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
t.FailNow() t.FailNow()
} }
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Empty asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// require.Empty(t, obj) // require.Empty(t, obj)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok { if h, ok := t.(tHelper); ok {
h.Helper() h.Helper()
@ -131,10 +140,19 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
t.FailNow() t.FailNow()
} }
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Emptyf asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// require.Emptyf(t, obj, "error message %s", "formatted") // require.Emptyf(t, obj, "error message %s", "formatted")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok { if h, ok := t.(tHelper); ok {
h.Helper() h.Helper()
@ -1495,8 +1513,7 @@ func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg str
t.FailNow() t.FailNow()
} }
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmpty asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if require.NotEmpty(t, obj) { // if require.NotEmpty(t, obj) {
// require.Equal(t, "two", obj[1]) // require.Equal(t, "two", obj[1])
@ -1511,8 +1528,7 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
t.FailNow() t.FailNow()
} }
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmptyf asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if require.NotEmptyf(t, obj, "error message %s", "formatted") { // if require.NotEmptyf(t, obj, "error message %s", "formatted") {
// require.Equal(t, "two", obj[1]) // require.Equal(t, "two", obj[1])

View File

@ -93,10 +93,19 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st
ElementsMatchf(a.t, listA, listB, msg, args...) ElementsMatchf(a.t, listA, listB, msg, args...)
} }
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Empty asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// a.Empty(obj) // a.Empty(obj)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok { if h, ok := a.t.(tHelper); ok {
h.Helper() h.Helper()
@ -104,10 +113,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
Empty(a.t, object, msgAndArgs...) Empty(a.t, object, msgAndArgs...)
} }
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // Emptyf asserts that the given value is "empty".
// a slice or a channel with len == 0. //
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
// //
// a.Emptyf(obj, "error message %s", "formatted") // a.Emptyf(obj, "error message %s", "formatted")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok { if h, ok := a.t.(tHelper); ok {
h.Helper() h.Helper()
@ -1183,8 +1201,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg
NotElementsMatchf(a.t, listA, listB, msg, args...) NotElementsMatchf(a.t, listA, listB, msg, args...)
} }
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmpty asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if a.NotEmpty(obj) { // if a.NotEmpty(obj) {
// assert.Equal(t, "two", obj[1]) // assert.Equal(t, "two", obj[1])
@ -1196,8 +1213,7 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) {
NotEmpty(a.t, object, msgAndArgs...) NotEmpty(a.t, object, msgAndArgs...)
} }
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // NotEmptyf asserts that the specified object is NOT [Empty].
// a slice or a channel with len == 0.
// //
// if a.NotEmptyf(obj, "error message %s", "formatted") { // if a.NotEmptyf(obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1]) // assert.Equal(t, "two", obj[1])