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
6 changes: 4 additions & 2 deletions assert/assertion_format.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions assert/assertion_forward.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 28 additions & 14 deletions assert/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +532 to +533
Copy link
Collaborator

@brackendawson brackendawson Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's coercible mean? Just say "Both arguments must be of kind pointer, map, or channel".

// 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...)
}
Expand All @@ -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...))
Expand All @@ -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
Expand Down
98 changes: 96 additions & 2 deletions assert/assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Comment on lines +661 to +668
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encode in the tests that c1 and c2 are Same:

c1 := make(chan int)
c2 := (<-chan int)(s)

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) {
Expand All @@ -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{}
Expand Down Expand Up @@ -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,
},
Comment on lines +772 to +777
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test that string and func are disallowed.

{
name: "non-pointer vs pointer (1 != ptr(2))",
args: args{first: 1, second: p},
Expand All @@ -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)
})
Expand Down
12 changes: 8 additions & 4 deletions require/require.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions require/require_forward.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.