From 34cc1827d9ab1cc4bf5377b0534931ecd144c04b Mon Sep 17 00:00:00 2001 From: Tronje Krop Date: Fri, 19 Dec 2025 05:49:49 +0100 Subject: [PATCH] feat: improve deep copy pattern (#133) Signed-off-by: Tronje Krop --- Makefile | 2 +- VERSION | 2 +- go.mod | 8 +-- go.sum | 12 ++-- test/pattern.go | 24 +++++-- test/pattern_test.go | 168 +++++++++++++++++++++++++++++++++---------- 6 files changed, 159 insertions(+), 57 deletions(-) diff --git a/Makefile b/Makefile index 864c91a..92791ea 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ TMPDIR ?= /tmp # Setup default go-make installation flags. INSTALL_FLAGS ?= -mod=readonly -buildvcs=auto # Setup go-make version to use desired build and config scripts. -GOMAKE_DEP ?= github.com/tkrop/go-make@v0.0.159 +GOMAKE_DEP ?= github.com/tkrop/go-make@v0.0.164 # Request targets from go-make show-targets target. TARGETS := $(shell command -v $(GOBIN)/go-make >/dev/null || \ $(GO) install $(INSTALL_FLAGS) $(GOMAKE_DEP) >&2 && \ diff --git a/VERSION b/VERSION index 31c01c9..1df5b7e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.45 +0.0.46 diff --git a/go.mod b/go.mod index 9e351e0..04e3160 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tkrop/go-testing -go 1.25.4 +go 1.25.5 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc @@ -10,13 +10,13 @@ require ( github.com/stretchr/testify v1.11.1 go.uber.org/mock v0.6.0 golang.org/x/text v0.30.0 - golang.org/x/tools v0.38.0 + golang.org/x/tools v0.40.0 ) require ( github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/sync v0.18.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/sync v0.19.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 087b8d6..51827c7 100644 --- a/go.sum +++ b/go.sum @@ -28,14 +28,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/test/pattern.go b/test/pattern.go index 1142cb0..f2abc56 100644 --- a/test/pattern.go +++ b/test/pattern.go @@ -182,7 +182,7 @@ type DeepCopyParams struct { // typeToTestName converts a reflect.Type into a human readable test case name. // The name is derived from the base (non-pointer) type's CamelCase identifier -// converted to a space separated lower-case string. For unnamed types the +// converted to a hyphen-separated lower-case string. For unnamed types the // string representation of the type is used as a fallback. func typeToTestName(typ reflect.Type) string { raw := typ.Name() @@ -191,14 +191,20 @@ func typeToTestName(typ reflect.Type) string { } runes := []rune(raw) + last := byte('-') var b strings.Builder for i, r := range runes { if i > 0 && unicode.IsUpper(r) && (unicode.IsLower(runes[i-1]) || - (i+1 < len(runes) && unicode.IsLower(runes[i+1]))) { - b.WriteByte(' ') + (i+1 < len(runes) && unicode.IsLower(runes[i+1]))) && + (unicode.IsLetter(runes[i-1]) || unicode.IsDigit(runes[i-1])) { + b.WriteByte('-') } b.WriteRune(unicode.ToLower(r)) + if r == ' ' { + last = byte(' ') + } } + b.WriteByte(last) return b.String() } @@ -226,10 +232,10 @@ func DeepCopyTestCases( ptrType := reflect.PointerTo(base) nilPtr := reflect.Zero(ptrType).Interface() - cases[name+" nil"] = DeepCopyParams{ + cases[name+"nil"] = DeepCopyParams{ Value: nilPtr, } - cases[name+" value"] = DeepCopyParams{ + cases[name+"value"] = DeepCopyParams{ Value: random.Random(nilPtr), } } @@ -273,8 +279,12 @@ func DeepCopy(t Test, p DeepCopyParams) { } // Then - if !reflect.ValueOf(value).IsNil() { - assert.NotSame(t, value, result) + rv := reflect.ValueOf(value) + if !rv.IsNil() { + // Only check NotSame for pointer types + if rv.Kind() == reflect.Ptr { + assert.NotSame(t, value, result) + } assert.Equal(t, value, result) } else { assert.Nil(t, result) diff --git a/test/pattern_test.go b/test/pattern_test.go index 3689266..ea970e1 100644 --- a/test/pattern_test.go +++ b/test/pattern_test.go @@ -518,15 +518,54 @@ func TestMainUnexpected(t *testing.T) { test.Param(t, test.MainParams{}).RunSeq(test.Main(main)) } -type deepCopy struct { +type ( + noCopySlice []int + deepCopySlice []int +) + +func (d deepCopySlice) DeepCopy() deepCopySlice { + if d == nil { + return nil + } + + copied := make(deepCopySlice, len(d)) + copy(copied, d) + + return copied +} + +type ( + noCopyMap map[string]int + deepCopyMap map[string]int +) + +func (d deepCopyMap) DeepCopy() deepCopyMap { + if d == nil { + return nil + } + + copied := make(deepCopyMap) + for k, v := range d { + copied[k] = v + } + + return copied +} + +type noCopyStruct struct { + Value int +} + +type deepCopyStruct struct { Value int } -func (d *deepCopy) DeepCopy() *deepCopy { - if d != nil { - return &deepCopy{Value: d.Value} +func (d *deepCopyStruct) DeepCopy() *deepCopyStruct { + if d == nil { + return nil } - return nil + + return &deepCopyStruct{Value: d.Value} } type deepCopyObject struct { @@ -534,14 +573,11 @@ type deepCopyObject struct { } func (d *deepCopyObject) DeepCopyObject() *deepCopyObject { - if d != nil { - return &deepCopyObject{Value: d.Value} + if d == nil { + return nil } - return nil -} -type noCopyMethod struct { - Value int + return &deepCopyObject{Value: d.Value} } // DeepCopyCasesParams defines parameters for testing DeepCopyTestCases. @@ -552,35 +588,71 @@ type DeepCopyCasesParams struct { var deepCopyTestCasesTestCases = map[string]DeepCopyCasesParams{ "struct types": { - args: []any{&deepCopy{}, &deepCopyObject{}, &noCopyMethod{}}, + args: []any{ + &noCopySlice{}, + &deepCopySlice{}, + &noCopyMap{}, + &deepCopyMap{}, + &noCopyStruct{}, + &deepCopyStruct{}, + &deepCopyObject{}, + }, expect: map[string]test.DeepCopyParams{ - "deep copy nil": { - Value: (*deepCopy)(nil), + "no-copy-slice-nil": { + Value: (*noCopySlice)(nil), }, - "deep copy value": { - Value: &deepCopy{Value: 6}, + "no-copy-slice-value": { + Value: &noCopySlice{8, 9, 1}, }, - "deep copy object nil": { - Value: (*deepCopyObject)(nil), + "deep-copy-slice-nil": { + Value: (*deepCopySlice)(nil), }, - "deep copy object value": { - Value: &deepCopyObject{Value: 8}, + "deep-copy-slice-value": { + Value: &deepCopySlice{6, 8}, + }, + "no-copy-map-nil": { + Value: (*noCopyMap)(nil), + }, + "no-copy-map-value": { + Value: &noCopyMap{ + "6jif9": 5, + "g21qrot5": 10, + "hbl": 6, + }, + }, + "deep-copy-map-nil": { + Value: (*deepCopyMap)(nil), + }, + "deep-copy-map-value": { + Value: &deepCopyMap{"5srwst": 3}, }, - "no copy method nil": { - Value: (*noCopyMethod)(nil), + "no-copy-struct-nil": { + Value: (*noCopyStruct)(nil), }, - "no copy method value": { - Value: &noCopyMethod{Value: 9}, + "no-copy-struct-value": { + Value: &noCopyStruct{Value: 8}, + }, + "deep-copy-struct-nil": { + Value: (*deepCopyStruct)(nil), + }, + "deep-copy-struct-value": { + Value: &deepCopyStruct{Value: 6}, + }, + "deep-copy-object-nil": { + Value: (*deepCopyObject)(nil), + }, + "deep-copy-object-value": { + Value: &deepCopyObject{Value: 8}, }, }, }, "anonymous struct type": { args: []any{&struct{ Value int }{}}, expect: map[string]test.DeepCopyParams{ - "struct { value int } nil": { + "struct { value int } nil": { Value: (*struct{ Value int })(nil), }, - "struct { value int } value": { + "struct { value int } value": { Value: &struct{ Value int }{Value: 6}, }, }, @@ -604,47 +676,67 @@ type DeepCopyParams struct { } var deepCopyTestCases = map[string]DeepCopyParams{ - "deep copy nil": { + "deep-copy-nil": { DeepCopyParams: test.DeepCopyParams{ - Value: (*deepCopy)(nil), + Value: (*deepCopyStruct)(nil), }, }, - "deep-copy value": { + "deep-copy-value": { DeepCopyParams: test.DeepCopyParams{ - Value: &deepCopy{Value: 2}, + Value: &deepCopyStruct{Value: 2}, }, }, - "deep copy object nil": { + "deep-copy-object-nil": { DeepCopyParams: test.DeepCopyParams{ Value: (*deepCopyObject)(nil), }, }, - "deep copy object value": { + "deep-copy-object-value": { DeepCopyParams: test.DeepCopyParams{ Value: &deepCopyObject{Value: 4}, }, }, - "no copy method nil": { + "deep-copy-slice-nil": { + DeepCopyParams: test.DeepCopyParams{ + Value: deepCopySlice(nil), + }, + }, + "deep-copy-slice-value": { + DeepCopyParams: test.DeepCopyParams{ + Value: deepCopySlice{1, 2, 3}, + }, + }, + "deep-copy-map-nil": { + DeepCopyParams: test.DeepCopyParams{ + Value: deepCopyMap(nil), + }, + }, + "deep-copy-map-value": { + DeepCopyParams: test.DeepCopyParams{ + Value: deepCopyMap{"key": 42}, + }, + }, + "no-copy-method-nil": { DeepCopyParams: test.DeepCopyParams{ Value: nil, }, setup: test.Fatalf("no deep copy method [%T]", nil), }, - "no copy method value": { + "no-copy-method-value": { DeepCopyParams: test.DeepCopyParams{ - Value: &noCopyMethod{Value: 6}, + Value: &noCopyStruct{Value: 6}, }, setup: test.Fatalf("no deep copy method [%T]", - &noCopyMethod{Value: 6}), + &noCopyStruct{Value: 6}), }, - "anonymous struct nil": { + "anonymous-struct-nil": { DeepCopyParams: test.DeepCopyParams{ Value: (*struct{ Value int })(nil), }, setup: test.Fatalf("no deep copy method [%T]", (*struct{ Value int })(nil)), }, - "anonymous struct value": { + "anonymous-struct-value": { DeepCopyParams: test.DeepCopyParams{ Value: &struct{ Value int }{Value: 8}, },