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...)...)
}
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Emptyf asserts that the given value is "empty".
//
// [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")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
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...)...)
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmptyf asserts that the specified object is NOT [Empty].
//
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
// 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...)
}
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Empty asserts that the given value is "empty".
//
// [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)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -103,10 +112,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
return Empty(a.t, object, msgAndArgs...)
}
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Emptyf asserts that the given value is "empty".
//
// [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")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1182,8 +1200,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg
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
// a slice or a channel with len == 0.
// NotEmpty asserts that the specified object is NOT [Empty].
//
// if a.NotEmpty(obj) {
// 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...)
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmptyf asserts that the specified object is NOT [Empty].
//
// if a.NotEmptyf(obj, "error message %s", "formatted") {
// 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
// a slice or a channel with len == 0.
// Empty asserts that the given value is "empty".
//
// [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)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
pass := isEmpty(object)
if !pass {
@ -789,8 +798,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return pass
}
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmpty asserts that the specified object is NOT [Empty].
//
// if assert.NotEmpty(t, obj) {
// assert.Equal(t, "two", obj[1])

View File

@ -1728,24 +1728,119 @@ func Test_isEmpty(t *testing.T) {
True(t, isEmpty(""))
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(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(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(uint32(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(new(bool)))
True(t, isEmpty(map[string]string{}))
True(t, isEmpty(map[string]string(nil)))
True(t, isEmpty(new(time.Time)))
True(t, isEmpty(time.Time{}))
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(errors.New("something")))
False(t, isEmpty([]string{"something"}))
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(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{"": ""}))
False(t, isEmpty(map[string]string{"foo": ""}))
False(t, isEmpty(map[string]string{"": "foo"}))
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([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) {

View File

@ -117,10 +117,19 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
t.FailNow()
}
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Empty asserts that the given value is "empty".
//
// [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)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -131,10 +140,19 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
t.FailNow()
}
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Emptyf asserts that the given value is "empty".
//
// [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")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -1495,8 +1513,7 @@ func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg str
t.FailNow()
}
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmpty asserts that the specified object is NOT [Empty].
//
// if require.NotEmpty(t, obj) {
// require.Equal(t, "two", obj[1])
@ -1511,8 +1528,7 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
t.FailNow()
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmptyf asserts that the specified object is NOT [Empty].
//
// if require.NotEmptyf(t, obj, "error message %s", "formatted") {
// 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...)
}
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Empty asserts that the given value is "empty".
//
// [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)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -104,10 +113,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
Empty(a.t, object, msgAndArgs...)
}
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Emptyf asserts that the given value is "empty".
//
// [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")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1183,8 +1201,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg
NotElementsMatchf(a.t, listA, listB, msg, args...)
}
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmpty asserts that the specified object is NOT [Empty].
//
// if a.NotEmpty(obj) {
// assert.Equal(t, "two", obj[1])
@ -1196,8 +1213,7 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) {
NotEmpty(a.t, object, msgAndArgs...)
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmptyf asserts that the specified object is NOT [Empty].
//
// if a.NotEmptyf(obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1])