Skip to content
Open
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
31 changes: 22 additions & 9 deletions assert/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ func ObjectsAreEqual(expected, actual interface{}) bool {

// copyExportedFields iterates downward through nested data structures and creates a copy
// that only contains the exported struct fields.
func copyExportedFields(expected interface{}) interface{} {
//
// To prevent infinite recursion on cyclic data structures, this function
// tracks visited pointers using the provided seen map.
func copyExportedFields(expected interface{}, seen map[uintptr]struct{}) interface{} {
if isNil(expected) {
return expected
}
Expand All @@ -102,15 +105,20 @@ func copyExportedFields(expected interface{}) interface{} {
if isNil(fieldValue) || isNil(fieldValue.Interface()) {
continue
}
newValue := copyExportedFields(fieldValue.Interface())
newValue := copyExportedFields(fieldValue.Interface(), seen)
result.Field(i).Set(reflect.ValueOf(newValue))
}
}
return result.Interface()

case reflect.Ptr:
ptr := expectedValue.Pointer()
if _, ok := seen[ptr]; ok {
return nil
}
seen[ptr] = struct{}{}
result := reflect.New(expectedType.Elem())
unexportedRemoved := copyExportedFields(expectedValue.Elem().Interface())
unexportedRemoved := copyExportedFields(expectedValue.Elem().Interface(), seen)
result.Elem().Set(reflect.ValueOf(unexportedRemoved))
return result.Interface()

Expand All @@ -126,7 +134,7 @@ func copyExportedFields(expected interface{}) interface{} {
if isNil(index) {
continue
}
unexportedRemoved := copyExportedFields(index.Interface())
unexportedRemoved := copyExportedFields(index.Interface(), seen)
result.Index(i).Set(reflect.ValueOf(unexportedRemoved))
}
return result.Interface()
Expand All @@ -135,7 +143,7 @@ func copyExportedFields(expected interface{}) interface{} {
result := reflect.MakeMap(expectedType)
for _, k := range expectedValue.MapKeys() {
index := expectedValue.MapIndex(k)
unexportedRemoved := copyExportedFields(index.Interface())
unexportedRemoved := copyExportedFields(index.Interface(), seen)
result.SetMapIndex(k, reflect.ValueOf(unexportedRemoved))
}
return result.Interface()
Expand All @@ -145,6 +153,11 @@ func copyExportedFields(expected interface{}) interface{} {
}
}

// copyExportedFieldsNoSeen is a wrapper that creates a new seen map for the initial call.
func copyExportedFieldsNoSeen(expected interface{}) interface{} {
return copyExportedFields(expected, make(map[uintptr]struct{}))
}

// ObjectsExportedFieldsAreEqual determines if the exported (public) fields of two objects are
// considered equal. This comparison of only exported fields is applied recursively to nested data
// structures.
Expand All @@ -153,8 +166,8 @@ func copyExportedFields(expected interface{}) interface{} {
//
// Deprecated: Use [EqualExportedValues] instead.
func ObjectsExportedFieldsAreEqual(expected, actual interface{}) bool {
expectedCleaned := copyExportedFields(expected)
actualCleaned := copyExportedFields(actual)
expectedCleaned := copyExportedFieldsNoSeen(expected)
actualCleaned := copyExportedFieldsNoSeen(actual)
return ObjectsAreEqualValues(expectedCleaned, actualCleaned)
}

Expand Down Expand Up @@ -670,8 +683,8 @@ func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs ..
return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...)
}

expected = copyExportedFields(expected)
actual = copyExportedFields(actual)
expected = copyExportedFieldsNoSeen(expected)
actual = copyExportedFieldsNoSeen(actual)

if !ObjectsAreEqualValues(expected, actual) {
diff := diff(expected, actual)
Expand Down
2 changes: 1 addition & 1 deletion assert/assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ func TestCopyExportedFields(t *testing.T) {

for _, c := range cases {
t.Run("", func(t *testing.T) {
output := copyExportedFields(c.input)
output := copyExportedFieldsNoSeen(c.input)
if !ObjectsAreEqualValues(c.expected, output) {
t.Errorf("%#v, %#v should be equal", c.expected, output)
}
Expand Down