diff --git a/assert/assertion_format.go b/assert/assertion_format.go index c592f6ad5..3fd68b5c3 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -702,7 +702,8 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // // assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { @@ -794,7 +795,8 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // // assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 58db92845..39f159ee5 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1392,7 +1392,8 @@ func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, arg // // a.NotSame(ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { @@ -1405,7 +1406,8 @@ func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArg // // a.NotSamef(ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { @@ -1576,7 +1578,8 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args . // // a.Same(ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { @@ -1589,7 +1592,8 @@ func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs . // // a.Samef(ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { diff --git a/assert/assertions.go b/assert/assertions.go index de8de0cb6..03189791e 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -529,14 +529,15 @@ func validateEqualArgs(expected, actual interface{}) error { // // assert.Same(t, ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - same, ok := samePointers(expected, actual) + same, ok := sameReferences(expected, actual) if !ok { return Fail(t, "Both arguments must be pointers", msgAndArgs...) } @@ -556,14 +557,15 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b // // assert.NotSame(t, ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - same, ok := samePointers(expected, actual) + same, ok := sameReferences(expected, actual) if !ok { // fails when the arguments are not pointers return !(Fail(t, "Both arguments must be pointers", msgAndArgs...)) @@ -577,23 +579,35 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} return true } -// samePointers checks if two generic interface objects are pointers of the same -// type pointing to the same object. It returns two values: same indicating if -// they are the same type and point to the same object, and ok indicating that -// both inputs are pointers. -func samePointers(first, second interface{}) (same bool, ok bool) { - firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second) - if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr { - return false, false // not both are pointers +// sameReferences checks if two generic interface objects are either pointers, +// maps, or channels that have the same type and point to the same underlying +// value. It returns two values: same indicating if they are the same type and +// point to the same object, and ok indicating that both inputs are pointer-like. +// +// ok will be false for objects such as slices or functions, since these values +// are not represented as a single direct reference: +// https://go.dev/ref/spec#Representation_of_values +func sameReferences(first, second interface{}) (same bool, ok bool) { + firstValue, secondValue := reflect.ValueOf(first), reflect.ValueOf(second) + firstKind, secondKind := firstValue.Kind(), secondValue.Kind() + if firstKind != secondKind { + return false, false + } + var firstPtr, secondPtr uintptr + switch firstKind { + case reflect.Chan, reflect.Map, reflect.Ptr: + firstPtr, secondPtr = firstValue.Pointer(), secondValue.Pointer() + default: + return false, false } firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second) if firstType != secondType { - return false, true // both are pointers, but of different types + return false, true // both are pointer-like, but of different types } // compare pointer addresses - return first == second, true + return firstPtr == secondPtr, true } // formatUnequalValues takes two values of arbitrary types and returns string diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 76ae12f96..8224ec243 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -650,6 +650,31 @@ func TestSame(t *testing.T) { if !Same(mockT, p, p) { t.Error("Same should return true") } + m1 := map[int]int{} + m2 := map[int]int{} + if Same(mockT, m1, m2) { + t.Error("Same should return false") + } + if !Same(mockT, m1, m1) { + t.Error("Same should return true") + } + c1 := make(chan int) + c2 := make(chan int) + if Same(mockT, c1, c2) { + t.Error("Same should return false") + } + if !Same(mockT, c1, c1) { + t.Error("Same should return true") + } + s1 := []int{} + s2 := []int{} + if Same(mockT, s1, s2) { + t.Error("Same should return false") + } + if Same(mockT, s1, s1) { + // Slices are not pointer-like + t.Error("Same should return false") + } } func TestNotSame(t *testing.T) { @@ -670,12 +695,39 @@ func TestNotSame(t *testing.T) { if NotSame(mockT, p, p) { t.Error("NotSame should return false") } + m1 := map[int]int{} + m2 := map[int]int{} + if !NotSame(mockT, m1, m2) { + t.Error("NotSame should return true; different maps") + } + if NotSame(mockT, m1, m1) { + t.Error("NotSame should return false; same maps") + } + c1 := make(chan int) + c2 := make(chan int) + if !NotSame(mockT, c1, c2) { + t.Error("NotSame should return true; different chans") + } + if NotSame(mockT, c1, c1) { + t.Error("NotSame should return false; same chans") + } + s1 := []int{} + s2 := []int{} + if !NotSame(mockT, s1, s1) { + t.Error("NotSame should return true; slices are not pointer-like") + } + if !NotSame(mockT, s1, s2) { + t.Error("NotSame should return true; slices are not pointer-like") + } } -func Test_samePointers(t *testing.T) { +func Test_sameReferences(t *testing.T) { t.Parallel() p := ptr(2) + m := map[int]int{} + c := make(chan int) + s := []int{} type args struct { first interface{} @@ -717,6 +769,12 @@ func Test_samePointers(t *testing.T) { same: False, ok: False, }, + { + name: "slice disallowed", + args: args{first: s, second: s}, + same: False, + ok: False, + }, { name: "non-pointer vs pointer (1 != ptr(2))", args: args{first: 1, second: p}, @@ -729,10 +787,46 @@ func Test_samePointers(t *testing.T) { same: False, ok: False, }, + { + name: "map1 == map1", + args: args{first: m, second: m}, + same: True, + ok: True, + }, + { + name: "map1 != map2", + args: args{first: m, second: map[int]int{}}, + same: False, + ok: True, + }, + { + name: "map1 != map2 (different types)", + args: args{first: m, second: map[int]string{}}, + same: False, + ok: True, + }, + { + name: "chan1 == chan1", + args: args{first: c, second: c}, + same: True, + ok: True, + }, + { + name: "chan1 != chan2", + args: args{first: c, second: make(chan int)}, + same: False, + ok: True, + }, + { + name: "chan1 != chan2 (different types)", + args: args{first: c, second: make(chan string)}, + same: False, + ok: True, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - same, ok := samePointers(tt.args.first, tt.args.second) + same, ok := sameReferences(tt.args.first, tt.args.second) tt.same(t, same) tt.ok(t, ok) }) diff --git a/require/require.go b/require/require.go index 2d02f9bce..cda3c6f50 100644 --- a/require/require.go +++ b/require/require.go @@ -1759,7 +1759,8 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // // require.NotSame(t, ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1775,7 +1776,8 @@ func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs .. // // require.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1991,7 +1993,8 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // // require.Same(t, ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -2007,7 +2010,8 @@ func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...in // // require.Samef(t, ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { diff --git a/require/require_forward.go b/require/require_forward.go index e6f7e9446..339fbf783 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1393,7 +1393,8 @@ func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, arg // // a.NotSame(ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { @@ -1406,7 +1407,8 @@ func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArg // // a.NotSamef(ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { @@ -1577,7 +1579,8 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args . // // a.Same(ptr1, ptr2) // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { @@ -1590,7 +1593,8 @@ func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs . // // a.Samef(ptr1, ptr2, "error message %s", "formatted") // -// Both arguments must be pointer variables. Pointer variable sameness is +// Both arguments must be pointer variables or something directly coercible +// to a pointer such as a map or channel. Pointer variable sameness is // determined based on the equality of both type and value. func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok {