diff --git a/cmp_funcs_test.go b/cmp_funcs_test.go index 3d3d7ebd..27ad0f86 100644 --- a/cmp_funcs_test.go +++ b/cmp_funcs_test.go @@ -1557,6 +1557,42 @@ func ExampleCmpShallow() { // false } +func ExampleCmpShallow_slice() { + t := &testing.T{} + + back := []int{1, 2, 3, 1, 2, 3} + a := back[:3] + b := back[3:] + + ok := CmpShallow(t, a, back) + fmt.Println("are ≠ but share the same area:", ok) + + ok = CmpShallow(t, b, back) + fmt.Println("are = but do not point to same area:", ok) + + // Output: + // are ≠ but share the same area: true + // are = but do not point to same area: false +} + +func ExampleCmpShallow_string() { + t := &testing.T{} + + back := "foobarfoobar" + a := back[:6] + b := back[6:] + + ok := CmpShallow(t, a, back) + fmt.Println("are ≠ but share the same area:", ok) + + ok = CmpShallow(t, b, a) + fmt.Println("are = but do not point to same area:", ok) + + // Output: + // are ≠ but share the same area: true + // are = but do not point to same area: false +} + func ExampleCmpSlice_slice() { t := &testing.T{} diff --git a/example_test.go b/example_test.go index d34fe0ac..6dfebc06 100644 --- a/example_test.go +++ b/example_test.go @@ -1624,6 +1624,42 @@ func ExampleShallow() { // false } +func ExampleShallow_slice() { + t := &testing.T{} + + back := []int{1, 2, 3, 1, 2, 3} + a := back[:3] + b := back[3:] + + ok := CmpDeeply(t, a, Shallow(back)) + fmt.Println("are ≠ but share the same area:", ok) + + ok = CmpDeeply(t, b, Shallow(back)) + fmt.Println("are = but do not point to same area:", ok) + + // Output: + // are ≠ but share the same area: true + // are = but do not point to same area: false +} + +func ExampleShallow_string() { + t := &testing.T{} + + back := "foobarfoobar" + a := back[:6] + b := back[6:] + + ok := CmpDeeply(t, a, Shallow(back)) + fmt.Println("are ≠ but share the same area:", ok) + + ok = CmpDeeply(t, b, Shallow(a)) + fmt.Println("are = but do not point to same area:", ok) + + // Output: + // are ≠ but share the same area: true + // are = but do not point to same area: false +} + func ExampleSlice_slice() { t := &testing.T{} diff --git a/t_test.go b/t_test.go index 7b8f7821..fc6aabc4 100644 --- a/t_test.go +++ b/t_test.go @@ -1557,6 +1557,42 @@ func ExampleT_Shallow() { // false } +func ExampleT_Shallow_slice() { + t := NewT(&testing.T{}) + + back := []int{1, 2, 3, 1, 2, 3} + a := back[:3] + b := back[3:] + + ok := t.Shallow(a, back) + fmt.Println("are ≠ but share the same area:", ok) + + ok = t.Shallow(b, back) + fmt.Println("are = but do not point to same area:", ok) + + // Output: + // are ≠ but share the same area: true + // are = but do not point to same area: false +} + +func ExampleT_Shallow_string() { + t := NewT(&testing.T{}) + + back := "foobarfoobar" + a := back[:6] + b := back[6:] + + ok := t.Shallow(a, back) + fmt.Println("are ≠ but share the same area:", ok) + + ok = t.Shallow(b, a) + fmt.Println("are = but do not point to same area:", ok) + + // Output: + // are ≠ but share the same area: true + // are = but do not point to same area: false +} + func ExampleT_Slice_slice() { t := NewT(&testing.T{}) diff --git a/td_shallow.go b/td_shallow.go index 9214a9d7..3c39a111 100644 --- a/td_shallow.go +++ b/td_shallow.go @@ -9,6 +9,7 @@ package testdeep import ( "fmt" "reflect" + "unsafe" "github.com/maxatome/go-testdeep/internal/ctxerr" "github.com/maxatome/go-testdeep/internal/types" @@ -18,13 +19,18 @@ type tdShallow struct { Base expectedKind reflect.Kind expectedPointer uintptr + expectedStr string // in reflect.String case, to avoid contents GC } var _ TestDeep = &tdShallow{} +func stringPointer(s string) uintptr { + return (*reflect.StringHeader)(unsafe.Pointer(&s)).Data +} + // Shallow operator compares pointers only, not their contents. It // applies on channels, functions (with some restrictions), maps, -// pointers and slices. +// pointers, slices and strings. // // During a match, the compared data must be the same as // "expectedPointer" to succeed. @@ -32,6 +38,25 @@ var _ TestDeep = &tdShallow{} // a, b := 123, 123 // CmpDeeply(t, &a, Shallow(&a)) // succeeds // CmpDeeply(t, &a, Shallow(&b)) // fails even if a == b as &a != &b +// +// back := "foobarfoobar" +// a, b := back[:6], back[6:] +// // a == b but... +// CmpDeeply(t, &a, Shallow(&b)) // fails +// +// Be careful for slices and strings! Shallow can succeed but the +// slices/strings not be identical because of their different +// lengths. For example: +// +// a := "foobar yes!" +// b := a[:1] // aka. "f" +// CmpDeeply(t, &a, Shallow(&b)) // succeeds as both strings point to the same area, even if len() differ +// +// The same behavior occurs for slices: +// +// a := []int{1, 2, 3, 4, 5, 6} +// b := a[:2] // aka. []int{1, 2} +// CmpDeeply(t, &a, Shallow(&b)) // succeeds as both slices point to the same area, even if len() differ func Shallow(expectedPtr interface{}) TestDeep { vptr := reflect.ValueOf(expectedPtr) @@ -56,8 +81,13 @@ func Shallow(expectedPtr interface{}) TestDeep { shallow.expectedPointer = vptr.Pointer() return &shallow + case reflect.String: + shallow.expectedStr = vptr.String() + shallow.expectedPointer = stringPointer(shallow.expectedStr) + return &shallow + default: - panic("usage: Shallow(CHANNEL|FUNC|MAP|PTR|SLICE|UNSAFE_PTR)") + panic("usage: Shallow(CHANNEL|FUNC|MAP|PTR|SLICE|UNSAFE_PTR|STRING)") } } @@ -73,13 +103,22 @@ func (s *tdShallow) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error { }) } - if got.Pointer() != s.expectedPointer { + var ptr uintptr + + // Special case for strings + if s.expectedKind == reflect.String { + ptr = stringPointer(got.String()) + } else { + ptr = got.Pointer() + } + + if ptr != s.expectedPointer { if ctx.BooleanError { return ctxerr.BooleanError } return ctx.CollectError(&ctxerr.Error{ Message: fmt.Sprintf("%s pointer mismatch", s.expectedKind), - Got: types.RawString(fmt.Sprintf("0x%x", got.Pointer())), + Got: types.RawString(fmt.Sprintf("0x%x", ptr)), Expected: types.RawString(fmt.Sprintf("0x%x", s.expectedPointer)), }) } diff --git a/td_shallow_test.go b/td_shallow_test.go index 9258ca40..4498b4af 100644 --- a/td_shallow_test.go +++ b/td_shallow_test.go @@ -17,9 +17,10 @@ import ( func TestShallow(t *testing.T) { // // Slice - gotSlice := []int{1, 2, 3} - expectedSlice := []int{1, 2, 3} - checkError(t, gotSlice, testdeep.Shallow(expectedSlice), + back := [...]int{1, 2, 3, 1, 2, 3} + as := back[:3] + bs := back[3:] + checkError(t, bs, testdeep.Shallow(back[:]), expectedError{ Message: mustBe("slice pointer mismatch"), Path: mustBe("DATA"), @@ -27,8 +28,7 @@ func TestShallow(t *testing.T) { Expected: mustContain("0x"), }) - expectedSlice = gotSlice - checkOK(t, gotSlice, testdeep.Shallow(expectedSlice)) + checkOK(t, as, testdeep.Shallow(back[:])) checkOK(t, ([]byte)(nil), ([]byte)(nil)) // @@ -98,6 +98,30 @@ func TestShallow(t *testing.T) { checkOK(t, gotChan, testdeep.Shallow(expectedChan)) checkOK(t, (chan int)(nil), (chan int)(nil)) + // + // String + backStr := "foobarfoobar!" + a := backStr[:6] + b := backStr[6:12] + checkOK(t, a, testdeep.Shallow(backStr)) + checkOK(t, backStr, testdeep.Shallow(a)) + checkOK(t, b, testdeep.Shallow(backStr[6:7])) + + checkError(t, backStr, testdeep.Shallow(b), + expectedError{ + Message: mustBe("string pointer mismatch"), + Path: mustBe("DATA"), + Got: mustContain("0x"), + Expected: mustContain("0x"), + }) + checkError(t, b, testdeep.Shallow(backStr), + expectedError{ + Message: mustBe("string pointer mismatch"), + Path: mustBe("DATA"), + Got: mustContain("0x"), + Expected: mustContain("0x"), + }) + // // Erroneous mix checkError(t, gotMap, testdeep.Shallow(expectedChan), @@ -110,7 +134,7 @@ func TestShallow(t *testing.T) { // // Bad usage - test.CheckPanic(t, func() { testdeep.Shallow("test") }, "usage: Shallow") + test.CheckPanic(t, func() { testdeep.Shallow(42) }, "usage: Shallow") // //