1084 lines
17 KiB
Go
1084 lines
17 KiB
Go
|
package copystructure
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"sync"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func TestCopy_complex(t *testing.T) {
|
||
|
v := map[string]interface{}{
|
||
|
"foo": []string{"a", "b"},
|
||
|
"bar": "baz",
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_interfacePointer(t *testing.T) {
|
||
|
type Nested struct {
|
||
|
Field string
|
||
|
}
|
||
|
|
||
|
type Test struct {
|
||
|
Value *interface{}
|
||
|
}
|
||
|
|
||
|
ifacePtr := func(v interface{}) *interface{} {
|
||
|
return &v
|
||
|
}
|
||
|
|
||
|
v := Test{
|
||
|
Value: ifacePtr(Nested{Field: "111"}),
|
||
|
}
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_primitive(t *testing.T) {
|
||
|
cases := []interface{}{
|
||
|
42,
|
||
|
"foo",
|
||
|
1.2,
|
||
|
}
|
||
|
|
||
|
for _, tc := range cases {
|
||
|
result, err := Copy(tc)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
if result != tc {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_primitivePtr(t *testing.T) {
|
||
|
i := 42
|
||
|
s := "foo"
|
||
|
f := 1.2
|
||
|
cases := []interface{}{
|
||
|
&i,
|
||
|
&s,
|
||
|
&f,
|
||
|
}
|
||
|
|
||
|
for i, tc := range cases {
|
||
|
result, err := Copy(tc)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, tc) {
|
||
|
t.Fatalf("%d exptected: %#v\nbad: %#v", i, tc, result)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_map(t *testing.T) {
|
||
|
v := map[string]interface{}{
|
||
|
"bar": "baz",
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_array(t *testing.T) {
|
||
|
v := [2]string{"bar", "baz"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_pointerToArray(t *testing.T) {
|
||
|
v := &[2]string{"bar", "baz"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_slice(t *testing.T) {
|
||
|
v := []string{"bar", "baz"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_pointerToSlice(t *testing.T) {
|
||
|
v := &[]string{"bar", "baz"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_pointerToMap(t *testing.T) {
|
||
|
v := &map[string]string{"bar": "baz"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_struct(t *testing.T) {
|
||
|
type test struct {
|
||
|
Value string
|
||
|
}
|
||
|
|
||
|
v := test{Value: "foo"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structPtr(t *testing.T) {
|
||
|
type test struct {
|
||
|
Value string
|
||
|
}
|
||
|
|
||
|
v := &test{Value: "foo"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structNil(t *testing.T) {
|
||
|
type test struct {
|
||
|
Value string
|
||
|
}
|
||
|
|
||
|
var v *test
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
if v, ok := result.(*test); !ok {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
} else if v != nil {
|
||
|
t.Fatalf("bad: %#v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structNested(t *testing.T) {
|
||
|
type TestInner struct{}
|
||
|
|
||
|
type Test struct {
|
||
|
Test *TestInner
|
||
|
}
|
||
|
|
||
|
v := Test{}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structWithNestedArray(t *testing.T) {
|
||
|
type TestInner struct {
|
||
|
Value string
|
||
|
}
|
||
|
|
||
|
type Test struct {
|
||
|
Value [2]TestInner
|
||
|
}
|
||
|
|
||
|
v := Test{
|
||
|
Value: [2]TestInner{
|
||
|
{Value: "bar"},
|
||
|
{Value: "baz"},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structWithPointerToSliceField(t *testing.T) {
|
||
|
type Test struct {
|
||
|
Value *[]string
|
||
|
}
|
||
|
|
||
|
v := Test{
|
||
|
Value: &[]string{"bar", "baz"},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structWithPointerToArrayField(t *testing.T) {
|
||
|
type Test struct {
|
||
|
Value *[2]string
|
||
|
}
|
||
|
|
||
|
v := Test{
|
||
|
Value: &[2]string{"bar", "baz"},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structWithPointerToMapField(t *testing.T) {
|
||
|
type Test struct {
|
||
|
Value *map[string]string
|
||
|
}
|
||
|
|
||
|
v := Test{
|
||
|
Value: &map[string]string{"bar": "baz"},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structUnexported(t *testing.T) {
|
||
|
type test struct {
|
||
|
Value string
|
||
|
|
||
|
private string
|
||
|
}
|
||
|
|
||
|
v := test{Value: "foo"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structUnexportedMap(t *testing.T) {
|
||
|
type Sub struct {
|
||
|
Foo map[string]interface{}
|
||
|
}
|
||
|
|
||
|
type test struct {
|
||
|
Value string
|
||
|
|
||
|
private Sub
|
||
|
}
|
||
|
|
||
|
v := test{
|
||
|
Value: "foo",
|
||
|
private: Sub{
|
||
|
Foo: map[string]interface{}{
|
||
|
"yo": 42,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
// private should not be copied
|
||
|
v.private = Sub{}
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad:\n\n%#v\n\n%#v", result, v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structUnexportedArray(t *testing.T) {
|
||
|
type Sub struct {
|
||
|
Foo [2]string
|
||
|
}
|
||
|
|
||
|
type test struct {
|
||
|
Value string
|
||
|
|
||
|
private Sub
|
||
|
}
|
||
|
|
||
|
v := test{
|
||
|
Value: "foo",
|
||
|
private: Sub{
|
||
|
Foo: [2]string{"bar", "baz"},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
// private should not be copied
|
||
|
v.private = Sub{}
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad:\n\n%#v\n\n%#v", result, v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This is testing an unexported field containing a slice of pointers, which
|
||
|
// was a crashing case found in Terraform.
|
||
|
func TestCopy_structUnexportedPtrMap(t *testing.T) {
|
||
|
type Foo interface{}
|
||
|
|
||
|
type Sub struct {
|
||
|
List []Foo
|
||
|
}
|
||
|
|
||
|
type test struct {
|
||
|
Value string
|
||
|
|
||
|
private *Sub
|
||
|
}
|
||
|
|
||
|
v := test{
|
||
|
Value: "foo",
|
||
|
private: &Sub{
|
||
|
List: []Foo{&Sub{}},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
// private should not be copied
|
||
|
v.private = nil
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad:\n\n%#v\n\n%#v", result, v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_nestedStructUnexported(t *testing.T) {
|
||
|
type subTest struct {
|
||
|
mine string
|
||
|
}
|
||
|
|
||
|
type test struct {
|
||
|
Value string
|
||
|
private subTest
|
||
|
}
|
||
|
|
||
|
v := test{Value: "foo"}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_time(t *testing.T) {
|
||
|
type test struct {
|
||
|
Value time.Time
|
||
|
}
|
||
|
|
||
|
v := test{Value: time.Now().UTC()}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_aliased(t *testing.T) {
|
||
|
type (
|
||
|
Int int
|
||
|
Str string
|
||
|
Map map[Int]interface{}
|
||
|
Slice []Str
|
||
|
)
|
||
|
|
||
|
v := Map{
|
||
|
1: Map{10: 20},
|
||
|
2: Map(nil),
|
||
|
3: Slice{"a", "b"},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type EmbeddedLocker struct {
|
||
|
sync.Mutex
|
||
|
Map map[int]int
|
||
|
}
|
||
|
|
||
|
func TestCopy_embeddedLocker(t *testing.T) {
|
||
|
v := &EmbeddedLocker{
|
||
|
Map: map[int]int{42: 111},
|
||
|
}
|
||
|
// start locked to prevent copying
|
||
|
v.Lock()
|
||
|
|
||
|
var result interface{}
|
||
|
var err error
|
||
|
|
||
|
copied := make(chan bool)
|
||
|
|
||
|
go func() {
|
||
|
result, err = Config{Lock: true}.Copy(v)
|
||
|
close(copied)
|
||
|
}()
|
||
|
|
||
|
// pause slightly to make sure copying is blocked
|
||
|
select {
|
||
|
case <-copied:
|
||
|
t.Fatal("copy completed while locked!")
|
||
|
case <-time.After(100 * time.Millisecond):
|
||
|
v.Unlock()
|
||
|
}
|
||
|
|
||
|
<-copied
|
||
|
|
||
|
// test that the mutex is in the correct state
|
||
|
result.(*EmbeddedLocker).Lock()
|
||
|
result.(*EmbeddedLocker).Unlock()
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// this will trigger the race detector, and usually panic if the original
|
||
|
// struct isn't properly locked during Copy
|
||
|
func TestCopy_lockRace(t *testing.T) {
|
||
|
v := &EmbeddedLocker{
|
||
|
Map: map[int]int{},
|
||
|
}
|
||
|
|
||
|
var wg sync.WaitGroup
|
||
|
for i := 0; i < 100; i++ {
|
||
|
wg.Add(1)
|
||
|
go func() {
|
||
|
defer wg.Done()
|
||
|
for i := 0; i < 100; i++ {
|
||
|
v.Lock()
|
||
|
v.Map[i] = i
|
||
|
v.Unlock()
|
||
|
}
|
||
|
}()
|
||
|
wg.Add(1)
|
||
|
go func() {
|
||
|
defer wg.Done()
|
||
|
Config{Lock: true}.Copy(v)
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
wg.Wait()
|
||
|
result, err := Config{Lock: true}.Copy(v)
|
||
|
|
||
|
// test that the mutex is in the correct state
|
||
|
result.(*EmbeddedLocker).Lock()
|
||
|
result.(*EmbeddedLocker).Unlock()
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type LockedField struct {
|
||
|
String string
|
||
|
Locker *EmbeddedLocker
|
||
|
// this should not get locked or have its state copied
|
||
|
Mutex sync.Mutex
|
||
|
nilMutex *sync.Mutex
|
||
|
}
|
||
|
|
||
|
func TestCopy_lockedField(t *testing.T) {
|
||
|
v := &LockedField{
|
||
|
String: "orig",
|
||
|
Locker: &EmbeddedLocker{
|
||
|
Map: map[int]int{42: 111},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// start locked to prevent copying
|
||
|
v.Locker.Lock()
|
||
|
v.Mutex.Lock()
|
||
|
|
||
|
var result interface{}
|
||
|
var err error
|
||
|
|
||
|
copied := make(chan bool)
|
||
|
|
||
|
go func() {
|
||
|
result, err = Config{Lock: true}.Copy(v)
|
||
|
close(copied)
|
||
|
}()
|
||
|
|
||
|
// pause slightly to make sure copying is blocked
|
||
|
select {
|
||
|
case <-copied:
|
||
|
t.Fatal("copy completed while locked!")
|
||
|
case <-time.After(100 * time.Millisecond):
|
||
|
v.Locker.Unlock()
|
||
|
}
|
||
|
|
||
|
<-copied
|
||
|
|
||
|
// test that the mutexes are in the correct state
|
||
|
result.(*LockedField).Locker.Lock()
|
||
|
result.(*LockedField).Locker.Unlock()
|
||
|
result.(*LockedField).Mutex.Lock()
|
||
|
result.(*LockedField).Mutex.Unlock()
|
||
|
|
||
|
// this wasn't blocking, but should be unlocked for DeepEqual
|
||
|
v.Mutex.Unlock()
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("expected:\n%#v\nbad:\n%#v\n", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// test something that doesn't contain a lock internally
|
||
|
type lockedMap map[int]int
|
||
|
|
||
|
var mapLock sync.Mutex
|
||
|
|
||
|
func (m lockedMap) Lock() { mapLock.Lock() }
|
||
|
func (m lockedMap) Unlock() { mapLock.Unlock() }
|
||
|
|
||
|
func TestCopy_lockedMap(t *testing.T) {
|
||
|
v := lockedMap{1: 2}
|
||
|
v.Lock()
|
||
|
|
||
|
var result interface{}
|
||
|
var err error
|
||
|
|
||
|
copied := make(chan bool)
|
||
|
|
||
|
go func() {
|
||
|
result, err = Config{Lock: true}.Copy(&v)
|
||
|
close(copied)
|
||
|
}()
|
||
|
|
||
|
// pause slightly to make sure copying is blocked
|
||
|
select {
|
||
|
case <-copied:
|
||
|
t.Fatal("copy completed while locked!")
|
||
|
case <-time.After(100 * time.Millisecond):
|
||
|
v.Unlock()
|
||
|
}
|
||
|
|
||
|
<-copied
|
||
|
|
||
|
// test that the mutex is in the correct state
|
||
|
result.(*lockedMap).Lock()
|
||
|
result.(*lockedMap).Unlock()
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, &v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Use an RLock if available
|
||
|
type RLocker struct {
|
||
|
sync.RWMutex
|
||
|
Map map[int]int
|
||
|
}
|
||
|
|
||
|
func TestCopy_rLocker(t *testing.T) {
|
||
|
v := &RLocker{
|
||
|
Map: map[int]int{1: 2},
|
||
|
}
|
||
|
v.Lock()
|
||
|
|
||
|
var result interface{}
|
||
|
var err error
|
||
|
|
||
|
copied := make(chan bool)
|
||
|
|
||
|
go func() {
|
||
|
result, err = Config{Lock: true}.Copy(v)
|
||
|
close(copied)
|
||
|
}()
|
||
|
|
||
|
// pause slightly to make sure copying is blocked
|
||
|
select {
|
||
|
case <-copied:
|
||
|
t.Fatal("copy completed while locked!")
|
||
|
case <-time.After(100 * time.Millisecond):
|
||
|
v.Unlock()
|
||
|
}
|
||
|
|
||
|
<-copied
|
||
|
|
||
|
// test that the mutex is in the correct state
|
||
|
vCopy := result.(*RLocker)
|
||
|
vCopy.Lock()
|
||
|
vCopy.Unlock()
|
||
|
vCopy.RLock()
|
||
|
vCopy.RUnlock()
|
||
|
|
||
|
// now make sure we can copy during an RLock
|
||
|
v.RLock()
|
||
|
result, err = Config{Lock: true}.Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
v.RUnlock()
|
||
|
|
||
|
vCopy = result.(*RLocker)
|
||
|
vCopy.Lock()
|
||
|
vCopy.Unlock()
|
||
|
vCopy.RLock()
|
||
|
vCopy.RUnlock()
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Test that we don't panic when encountering nil Lockers
|
||
|
func TestCopy_missingLockedField(t *testing.T) {
|
||
|
v := &LockedField{
|
||
|
String: "orig",
|
||
|
}
|
||
|
|
||
|
result, err := Config{Lock: true}.Copy(v)
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("expected:\n%#v\nbad:\n%#v\n", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type PointerLocker struct {
|
||
|
Mu sync.Mutex
|
||
|
}
|
||
|
|
||
|
func (p *PointerLocker) Lock() { p.Mu.Lock() }
|
||
|
func (p *PointerLocker) Unlock() { p.Mu.Unlock() }
|
||
|
|
||
|
func TestCopy_pointerLockerNil(t *testing.T) {
|
||
|
v := struct {
|
||
|
P *PointerLocker
|
||
|
}{}
|
||
|
|
||
|
_, err := Config{Lock: true}.Copy(&v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_sliceWithNil(t *testing.T) {
|
||
|
v := [](*int){nil}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("expected:\n%#v\ngot:\n%#v", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_mapWithNil(t *testing.T) {
|
||
|
v := map[int](*int){0: nil}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(result, v) {
|
||
|
t.Fatalf("expected:\n%#v\ngot:\n%#v", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// While this is safe to lock and copy directly, copystructure requires a
|
||
|
// pointer to reflect the value safely.
|
||
|
func TestCopy_valueWithLockPointer(t *testing.T) {
|
||
|
v := struct {
|
||
|
*sync.Mutex
|
||
|
X int
|
||
|
}{
|
||
|
Mutex: &sync.Mutex{},
|
||
|
X: 3,
|
||
|
}
|
||
|
|
||
|
_, err := Config{Lock: true}.Copy(v)
|
||
|
|
||
|
if err != errPointerRequired {
|
||
|
t.Fatalf("expected errPointerRequired, got: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_mapWithPointers(t *testing.T) {
|
||
|
type T struct {
|
||
|
S string
|
||
|
}
|
||
|
v := map[string]interface{}{
|
||
|
"a": &T{S: "hello"},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatalf("%#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_structWithMapWithPointers(t *testing.T) {
|
||
|
type T struct {
|
||
|
S string
|
||
|
M map[string]interface{}
|
||
|
}
|
||
|
v := &T{
|
||
|
S: "a",
|
||
|
M: map[string]interface{}{
|
||
|
"b": &T{
|
||
|
S: "b",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatal(result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type testT struct {
|
||
|
N int
|
||
|
Spp **string
|
||
|
X testX
|
||
|
Xp *testX
|
||
|
Xpp **testX
|
||
|
}
|
||
|
|
||
|
type testX struct {
|
||
|
Tp *testT
|
||
|
Tpp **testT
|
||
|
Ip *interface{}
|
||
|
Ep *error
|
||
|
S fmt.Stringer
|
||
|
}
|
||
|
|
||
|
type stringer struct{}
|
||
|
|
||
|
func (s *stringer) String() string {
|
||
|
return "test string"
|
||
|
}
|
||
|
|
||
|
func TestCopy_structWithPointersAndInterfaces(t *testing.T) {
|
||
|
// test that we can copy various nested and chained pointers and interfaces
|
||
|
s := "val"
|
||
|
sp := &s
|
||
|
spp := &sp
|
||
|
i := interface{}(11)
|
||
|
|
||
|
tp := &testT{
|
||
|
N: 2,
|
||
|
}
|
||
|
|
||
|
xp := &testX{
|
||
|
Tp: tp,
|
||
|
Tpp: &tp,
|
||
|
Ip: &i,
|
||
|
S: &stringer{},
|
||
|
}
|
||
|
|
||
|
v := &testT{
|
||
|
N: 1,
|
||
|
Spp: spp,
|
||
|
X: testX{},
|
||
|
Xp: xp,
|
||
|
Xpp: &xp,
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatal(result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Test_pointerInterfacePointer(t *testing.T) {
|
||
|
s := "hi"
|
||
|
si := interface{}(&s)
|
||
|
sip := &si
|
||
|
|
||
|
result, err := Copy(sip)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(sip, result) {
|
||
|
t.Fatalf("%#v != %#v\n", sip, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Test_pointerInterfacePointer2(t *testing.T) {
|
||
|
type T struct {
|
||
|
I *interface{}
|
||
|
J **fmt.Stringer
|
||
|
}
|
||
|
|
||
|
x := 1
|
||
|
y := &stringer{}
|
||
|
|
||
|
i := interface{}(&x)
|
||
|
j := fmt.Stringer(y)
|
||
|
jp := &j
|
||
|
|
||
|
v := &T{
|
||
|
I: &i,
|
||
|
J: &jp,
|
||
|
}
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatalf("%#v != %#v\n", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This test catches a bug that happened when unexported fields were
|
||
|
// first their subsequent fields wouldn't be copied.
|
||
|
func TestCopy_unexportedFieldFirst(t *testing.T) {
|
||
|
type P struct {
|
||
|
mu sync.Mutex
|
||
|
Old, New string
|
||
|
}
|
||
|
|
||
|
type T struct {
|
||
|
M map[string]*P
|
||
|
}
|
||
|
|
||
|
v := &T{
|
||
|
M: map[string]*P{
|
||
|
"a": &P{Old: "", New: "2"},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatalf("\n%#v\n\n%#v", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_nilPointerInSlice(t *testing.T) {
|
||
|
type T struct {
|
||
|
Ps []*int
|
||
|
}
|
||
|
|
||
|
v := &T{
|
||
|
Ps: []*int{nil},
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatalf("\n%#v\n\n%#v", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------
|
||
|
// The tests below all tests various pointer cases around copying
|
||
|
// a structure that uses a defined Copier. This was originally raised
|
||
|
// around issue #26.
|
||
|
|
||
|
func TestCopy_timePointer(t *testing.T) {
|
||
|
type T struct {
|
||
|
Value *time.Time
|
||
|
}
|
||
|
|
||
|
now := time.Now()
|
||
|
v := &T{
|
||
|
Value: &now,
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatalf("\n%#v\n\n%#v", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_timeNonPointer(t *testing.T) {
|
||
|
type T struct {
|
||
|
Value time.Time
|
||
|
}
|
||
|
|
||
|
v := &T{
|
||
|
Value: time.Now(),
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatalf("\n%#v\n\n%#v", v, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCopy_timeDoublePointer(t *testing.T) {
|
||
|
type T struct {
|
||
|
Value **time.Time
|
||
|
}
|
||
|
|
||
|
now := time.Now()
|
||
|
nowP := &now
|
||
|
nowPP := &nowP
|
||
|
v := &T{
|
||
|
Value: nowPP,
|
||
|
}
|
||
|
|
||
|
result, err := Copy(v)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(v, result) {
|
||
|
t.Fatalf("\n%#v\n\n%#v", v, result)
|
||
|
}
|
||
|
}
|