Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions internal/assertions/equal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,109 @@ func TestEqualBytes(t *testing.T) {
}
}

func TestEqualValuePanics(t *testing.T) {
t.Parallel()

for tt := range panicCases() {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

mock := new(mockT)
NotPanics(t, func() {
Equal(mock, tt.value1, tt.value2)
}, "should not panic")

if !tt.expectEqual {
True(t, mock.Failed(), "should have failed")
Contains(t, mock.errorString(), "Not equal:", "error message should mention inequality")

return
}

False(t, mock.Failed(), "should have been successful")
Empty(t, mock.errorString())
})
}
}

type panicCase struct {
name string
value1 any
value2 any
expectEqual bool
}

func panicCases() iter.Seq[panicCase] {
type structWithUnexportedMapWithArrayKey struct {
m any
}

return slices.Values([]panicCase{
{
// from issue https://github.com/stretchr/testify/pull/1816
name: "panic behavior on struct with array key and unexported field (some keys vs none)",
value1: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{1}: nil,
{2}: nil,
},
},
value2: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{},
},
expectEqual: false,
},
{
name: "panic behavior on struct with array key and unexported field (same keys)",
value1: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{1}: nil,
{2}: nil,
},
},
value2: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{2}: nil,
{1}: nil,
},
},
expectEqual: true,
},
{
name: "panic behavior on struct with array key and unexported field (non-nil values)",
value1: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{1}: {},
{2}: nil,
},
},
value2: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{1}: {},
{2}: nil,
},
},
expectEqual: true,
},
{
name: "panic behavior on struct with array key and unexported field (different, non-nil values)",
value1: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{1}: {},
{2}: nil,
},
},
value2: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{1}: nil,
{2}: {},
},
},
expectEqual: false,
},
})
}

type equalCase struct {
expected any
actual any
Expand Down
6 changes: 6 additions & 0 deletions internal/spew/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ func valueSortLess(a, b reflect.Value) bool {
for i := range l {
av := a.Index(i)
bv := b.Index(i)

if !av.CanInterface() || !bv.CanInterface() {
// Unexported fields would panic on Interface() call.
continue
}

if av.Interface() == bv.Interface() {
continue
}
Expand Down
55 changes: 55 additions & 0 deletions internal/spew/common_impl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package spew

import (
"iter"
"reflect"
"slices"
"testing"
)

func TestPanicUnexportedFields(t *testing.T) {
t.Parallel()

for tt := range panicCases() {
t.Run(tt.name, func(t *testing.T) {
v1 := reflect.ValueOf(tt.value1)
v2 := reflect.ValueOf(tt.value2)
isLess := valueSortLess(v1, v2)

if isLess {
t.Error("expected an ordered set")
}
})
}
}

type panicCase struct {
name string
value1 any
value2 any
}

func panicCases() iter.Seq[panicCase] {
type structWithUnexportedMapWithArrayKey struct {
m any
}

return slices.Values([]panicCase{
{
// from issue https://github.com/stretchr/testify/pull/1816
name: "panic behavior on struct with array key and unexported field",
value1: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{1}: nil,
{2}: nil,
},
},
value2: structWithUnexportedMapWithArrayKey{
map[[1]byte]*struct{}{
{2}: nil,
{1}: nil,
},
},
},
})
}
Loading