Add GoToClownfish/ToGo conversion routines.
Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/55d40ac6 Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/55d40ac6 Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/55d40ac6 Branch: refs/heads/master Commit: 55d40ac67b7a076050c15e652d51f4a5dfc937a6 Parents: 56e4ab9 Author: Marvin Humphrey <[email protected]> Authored: Fri Jul 24 14:23:44 2015 -0700 Committer: Marvin Humphrey <[email protected]> Committed: Fri Jul 31 11:05:42 2015 -0700 ---------------------------------------------------------------------- runtime/go/clownfish/clownfish.go | 383 +++++++++++++++++++++++++++- runtime/go/clownfish/clownfish_test.go | 236 ++++++++++++++++- 2 files changed, 611 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/55d40ac6/runtime/go/clownfish/clownfish.go ---------------------------------------------------------------------- diff --git a/runtime/go/clownfish/clownfish.go b/runtime/go/clownfish/clownfish.go index 0fb7e32..21eb567 100644 --- a/runtime/go/clownfish/clownfish.go +++ b/runtime/go/clownfish/clownfish.go @@ -18,6 +18,8 @@ package clownfish /* +#include <limits.h> + #include "charmony.h" #include "Clownfish/Obj.h" @@ -26,7 +28,9 @@ package clownfish #include "Clownfish/String.h" #include "Clownfish/Blob.h" #include "Clownfish/Hash.h" +#include "Clownfish/HashIterator.h" #include "Clownfish/Vector.h" +#include "Clownfish/Num.h" #include "Clownfish/Boolean.h" #include "Clownfish/Util/Memory.h" #include "Clownfish/Method.h" @@ -63,6 +67,7 @@ import "C" import "runtime" import "unsafe" import "fmt" +import "math" import "sync" const ( @@ -139,7 +144,315 @@ func (o *ObjIMP) TOPTR() uintptr { return o.ref } +func certifyCF(value interface{}, class *C.cfish_Class) { + cfObj, ok := value.(Obj) + if ok { + if C.cfish_Obj_is_a((*C.cfish_Obj)(unsafe.Pointer(cfObj.TOPTR())), class) { + return + } + } + className := StringToGo(unsafe.Pointer(C.CFISH_Class_Get_Name(class))) + panic(NewErr(fmt.Sprintf("Can't convert a %T to %s", value, className))) +} + +// Convert a Go type into an incremented Clownfish object. If the supplied +// object is a Clownfish object wrapped in a Go struct, extract the Clownfish +// object and incref it before returning its address. +func GoToClownfish(value interface{}, class unsafe.Pointer, nullable bool) unsafe.Pointer { + klass := (*C.cfish_Class)(class) + + // Check for nil values. + if value == nil { + if nullable { + return nil + } else if class != nil { + className := StringToGo(unsafe.Pointer(C.CFISH_Class_Get_Name(klass))) + panic(NewErr("Cannot be nil, must be a valid " + className)) + } else { + panic(NewErr("Cannot be nil")) + } + } + + // Default to accepting any type. + if klass == nil { + klass = C.CFISH_OBJ + } + + // Convert the value according to its type if possible. + var converted unsafe.Pointer + switch v := value.(type) { + case string: + if klass == C.CFISH_STRING || klass == C.CFISH_OBJ { + converted = GoToString(value) + } + case []byte: + if klass == C.CFISH_BLOB || klass == C.CFISH_OBJ { + converted = GoToBlob(value) + } + case int: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case uint: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case uintptr: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case int64: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case int32: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case int16: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case int8: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case uint64: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case uint32: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case uint16: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case uint8: + if klass == C.CFISH_INTEGER || klass == C.CFISH_OBJ { + converted = GoToInteger(value) + } + case float32: + if klass == C.CFISH_FLOAT || klass == C.CFISH_OBJ { + converted = GoToFloat(value) + } + case float64: + if klass == C.CFISH_FLOAT || klass == C.CFISH_OBJ { + converted = GoToFloat(value) + } + case []interface{}: + if klass == C.CFISH_VECTOR || klass == C.CFISH_OBJ { + converted = GoToVector(value) + } + case map[string]interface{}: + if klass == C.CFISH_HASH || klass == C.CFISH_OBJ { + converted = GoToHash(value) + } + case Obj: + converted = unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + } + + // Confirm that we got what we were looking for and return. + if converted != nil { + if C.cfish_Obj_is_a((*C.cfish_Obj)(converted), klass) { + return unsafe.Pointer(C.cfish_incref(converted)) + } + } + + // Report a conversion error. + className := StringToGo(unsafe.Pointer(C.CFISH_Class_Get_Name(klass))) + panic(NewErr(fmt.Sprintf("Can't convert a %T to %s", value, className))) +} + +func GoToString(value interface{}) unsafe.Pointer { + switch v := value.(type) { + case string: + size := len(v) + str := C.CString(v) + return unsafe.Pointer(C.cfish_Str_new_steal_utf8(str, C.size_t(size))) + case Obj: + certifyCF(v, C.CFISH_STRING) + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + default: + mess := fmt.Sprintf("Can't convert %T to clownfish.String", v) + panic(NewErr(mess)) + } +} + +func GoToBlob(value interface{}) unsafe.Pointer { + switch v := value.(type) { + case []byte: + size := C.size_t(len(v)) + var buf *C.char = nil + if size > 0 { + buf = ((*C.char)(unsafe.Pointer(&v[0]))) + } + return unsafe.Pointer(C.cfish_Blob_new(buf, size)) + case Obj: + certifyCF(v, C.CFISH_BLOB) + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + default: + mess := fmt.Sprintf("Can't convert %T to clownfish.Blob", v) + panic(NewErr(mess)) + } +} + +func GoToInteger(value interface{}) unsafe.Pointer { + switch v := value.(type) { + case int: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case uint: + if v > math.MaxInt64 { + mess := fmt.Sprintf("uint value too large: %v", v) + panic(NewErr(mess)) + } + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case uintptr: + if v > math.MaxInt64 { + mess := fmt.Sprintf("uintptr value too large: %v", v) + panic(NewErr(mess)) + } + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case uint64: + if v > math.MaxInt64 { + mess := fmt.Sprintf("uint64 value too large: %v", v) + panic(NewErr(mess)) + } + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case uint32: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case uint16: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case uint8: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case int64: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case int32: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case int16: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case int8: + return unsafe.Pointer(C.cfish_Int_new(C.int64_t(v))) + case Obj: + certifyCF(v, C.CFISH_INTEGER) + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + default: + mess := fmt.Sprintf("Can't convert %T to clownfish.Integer", v) + panic(NewErr(mess)) + } +} + +func GoToFloat(value interface{}) unsafe.Pointer { + switch v := value.(type) { + case float32: + return unsafe.Pointer(C.cfish_Float_new(C.double(v))) + case float64: + return unsafe.Pointer(C.cfish_Float_new(C.double(v))) + case Obj: + certifyCF(v, C.CFISH_FLOAT) + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + default: + mess := fmt.Sprintf("Can't convert %T to clownfish.Float", v) + panic(NewErr(mess)) + } +} + +func GoToBoolean(value interface{}) unsafe.Pointer { + switch v := value.(type) { + case bool: + if v { + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(C.CFISH_TRUE))) + } else { + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(C.CFISH_FALSE))) + } + case Obj: + certifyCF(v, C.CFISH_BOOLEAN) + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + default: + mess := fmt.Sprintf("Can't convert %T to clownfish.Boolean", v) + panic(NewErr(mess)) + } +} + +func GoToVector(value interface{}) unsafe.Pointer { + switch v := value.(type) { + case []interface{}: + size := len(v) + vec := C.cfish_Vec_new(C.size_t(size)) + for i := 0; i < size; i++ { + elem := GoToClownfish(v[i], nil, true) + C.CFISH_Vec_Store(vec, C.size_t(i), (*C.cfish_Obj)(elem)) + } + return unsafe.Pointer(vec) + case Obj: + certifyCF(v, C.CFISH_VECTOR) + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + default: + mess := fmt.Sprintf("Can't convert %T to clownfish.Vector", v) + panic(NewErr(mess)) + } +} + +func GoToHash(value interface{}) unsafe.Pointer { + switch v := value.(type) { + case map[string]interface{}: + size := len(v) + hash := C.cfish_Hash_new(C.size_t(size)) + for key, val := range v { + newVal := GoToClownfish(val, nil, true) + keySize := len(key) + keyStr := C.CString(key) + cfKey := C.cfish_Str_new_steal_utf8(keyStr, C.size_t(keySize)) + defer C.cfish_dec_refcount(unsafe.Pointer(cfKey)) + C.CFISH_Hash_Store(hash, cfKey, (*C.cfish_Obj)(newVal)) + } + return unsafe.Pointer(hash) + case Obj: + certifyCF(v, C.CFISH_HASH) + return unsafe.Pointer(C.cfish_incref(unsafe.Pointer(v.TOPTR()))) + default: + mess := fmt.Sprintf("Can't convert %T to clownfish.Hash", v) + panic(NewErr(mess)) + } +} + +func ToGo(ptr unsafe.Pointer) interface{} { + if ptr == nil { + return nil + } + class := C.cfish_Obj_get_class((*C.cfish_Obj)(ptr)) + if class == C.CFISH_STRING { + return CFStringToGo(ptr) + } else if class == C.CFISH_BLOB { + return BlobToGo(ptr) + } else if class == C.CFISH_VECTOR { + return VectorToGo(ptr) + } else if class == C.CFISH_HASH { + return HashToGo(ptr) + } else if class == C.CFISH_BOOLEAN { + if ptr == unsafe.Pointer(C.CFISH_TRUE) { + return true + } else { + return false + } + } else if class == C.CFISH_INTEGER { + val := C.CFISH_Int_Get_Value((*C.cfish_Integer)(ptr)) + return int64(val) + } else if class == C.CFISH_FLOAT { + val := C.CFISH_Float_Get_Value((*C.cfish_Float)(ptr)) + return float64(val) + } else { + // Don't convert to a native Go type, but wrap in a Go struct. + return WRAPAny(ptr) + } +} + func CFStringToGo(ptr unsafe.Pointer) string { + return StringToGo(ptr) +} + +func StringToGo(ptr unsafe.Pointer) string { cfString := (*C.cfish_String)(ptr) if cfString == nil { return "" @@ -149,13 +462,77 @@ func CFStringToGo(ptr unsafe.Pointer) string { defer C.cfish_dec_refcount(unsafe.Pointer(cfString)) } data := C.CFISH_Str_Get_Ptr8(cfString) - size := C.int(C.CFISH_Str_Get_Size(cfString)) - return C.GoStringN(data, size) + size := C.CFISH_Str_Get_Size(cfString) + if size > C.size_t(C.INT_MAX) { + panic(fmt.Sprintf("Overflow: %d > %d", size, C.INT_MAX)) + } + return C.GoStringN(data, C.int(size)) +} + +func BlobToGo(ptr unsafe.Pointer) []byte { + blob := (*C.cfish_Blob)(ptr) + if blob == nil { + return nil + } + class := C.cfish_Obj_get_class((*C.cfish_Obj)(ptr)) + if class != C.CFISH_BLOB { + mess := "Not a Blob: " + StringToGo(unsafe.Pointer(C.CFISH_Class_Get_Name(class))) + panic(NewErr(mess)) + } + data := C.CFISH_Blob_Get_Buf(blob) + size := C.CFISH_Blob_Get_Size(blob) + if size > C.size_t(C.INT_MAX) { + panic(fmt.Sprintf("Overflow: %d > %d", size, C.INT_MAX)) + } + return C.GoBytes(unsafe.Pointer(data), C.int(size)) +} + +func VectorToGo(ptr unsafe.Pointer) []interface{} { + vec := (*C.cfish_Vector)(ptr) + if vec == nil { + return nil + } + class := C.cfish_Obj_get_class((*C.cfish_Obj)(ptr)) + if class != C.CFISH_VECTOR { + mess := "Not a Vector: " + StringToGo(unsafe.Pointer(C.CFISH_Class_Get_Name(class))) + panic(NewErr(mess)) + } + size := C.CFISH_Vec_Get_Size(vec) + if size > C.size_t(maxInt) { + panic(fmt.Sprintf("Overflow: %d > %d", size, maxInt)) + } + slice := make([]interface{}, int(size)) + for i := 0; i < int(size); i++ { + slice[i] = ToGo(unsafe.Pointer(C.CFISH_Vec_Fetch(vec, C.size_t(i)))) + } + return slice +} + +func HashToGo(ptr unsafe.Pointer) map[string]interface{} { + hash := (*C.cfish_Hash)(ptr) + if hash == nil { + return nil + } + class := C.cfish_Obj_get_class((*C.cfish_Obj)(ptr)) + if class != C.CFISH_HASH { + mess := "Not a Hash: " + StringToGo(unsafe.Pointer(C.CFISH_Class_Get_Name(class))) + panic(NewErr(mess)) + } + size := C.CFISH_Hash_Get_Size(hash) + m := make(map[string]interface{}, int(size)) + iter := C.cfish_HashIter_new(hash) + defer C.cfish_dec_refcount(unsafe.Pointer(iter)) + for C.CFISH_HashIter_Next(iter) { + key := C.CFISH_HashIter_Get_Key(iter) + val := C.CFISH_HashIter_Get_Value(iter) + m[StringToGo(unsafe.Pointer(key))] = ToGo(unsafe.Pointer(val)) + } + return m } func (e *ErrIMP) Error() string { mess := C.CFISH_Err_Get_Mess((*C.cfish_Err)(unsafe.Pointer(e.ref))) - return CFStringToGo(unsafe.Pointer(mess)) + return StringToGo(unsafe.Pointer(mess)) } //export GoCfish_PanicErr_internal http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/55d40ac6/runtime/go/clownfish/clownfish_test.go ---------------------------------------------------------------------- diff --git a/runtime/go/clownfish/clownfish_test.go b/runtime/go/clownfish/clownfish_test.go index 6103f24..6b31527 100644 --- a/runtime/go/clownfish/clownfish_test.go +++ b/runtime/go/clownfish/clownfish_test.go @@ -18,11 +18,237 @@ package clownfish import "testing" import "unsafe" +import "reflect" +import "math" -func TestStuff(t *testing.T) { - cfString := NewString("foo") - goString := CFStringToGo(unsafe.Pointer(cfString.TOPTR())) - if goString != "foo" { - t.Error("Round-tripping strings failed") +func deepCheck(t *testing.T, got, expected interface{}) { + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %v got %v", expected, got) } } + +func TestStringToGo(t *testing.T) { + strings := []string{"foo", "", "z\u0000z"} + for _, val := range strings { + got := StringToGo(unsafe.Pointer(NewString(val).TOPTR())) + deepCheck(t, got, val) + } +} + +func TestBlobToGo(t *testing.T) { + strings := []string{"foo", "", "z\u0000z"} + for _, str := range strings { + expected := []byte(str) + got := BlobToGo(unsafe.Pointer(NewBlob(expected).TOPTR())) + deepCheck(t, got, expected) + } +} + +func TestIntegerToGo(t *testing.T) { + values := []int64{math.MaxInt64, math.MinInt64, 0, 1, -1} + for _, val := range values { + got := ToGo(unsafe.Pointer(NewInteger(val).TOPTR())) + deepCheck(t, got, val) + } +} + +func TestFloatToGo(t *testing.T) { + values := []float64{math.MaxFloat64, math.SmallestNonzeroFloat64, + 0.0, -0.0, 0.5, -0.5, math.Inf(1), math.Inf(-1)} + for _, val := range values { + got := ToGo(unsafe.Pointer(NewFloat(val).TOPTR())) + deepCheck(t, got, val) + } + notNum := ToGo(unsafe.Pointer(NewFloat(math.NaN()).TOPTR())) + if !math.IsNaN(notNum.(float64)) { + t.Error("Didn't convert NaN cleanly") + } +} + +func TestBoolToGo(t *testing.T) { + values := []bool{true, false} + for _, val := range values { + got := ToGo(unsafe.Pointer(NewBoolean(val).TOPTR())) + deepCheck(t, got, val) + } +} + +func TestVectorToGo(t *testing.T) { + vec := NewVector(3) + vec.Push(NewString("foo")) + vec.Push(NewInteger(42)) + //vec.Push(nil) + inner := NewVector(1) + inner.Push(NewString("bar")) + vec.Push(inner) + expected := []interface{}{ + "foo", + int64(42), + //nil, + []interface{}{"bar"}, + } + got := VectorToGo(unsafe.Pointer(vec.TOPTR())) + deepCheck(t, got, expected) +} + +func TestHashToGo(t *testing.T) { + hash := NewHash(0) + hash.Store("str", NewString("foo")) + hash.Store("bool", NewBoolean(false)) + hash.Store("float", NewFloat(-0.5)) + hash.Store("int", NewInteger(42)) + //hash.Store("nil", nil) + vec := NewVector(1) + vec.Push(NewString("bar")) + hash.Store("vector", vec) + got := HashToGo(unsafe.Pointer(hash.TOPTR())) + + expected := map[string]interface{}{ + "str": "foo", + "bool": false, + "float": -0.5, + "int": int64(42), + //"nil": nil, + "vector": []interface{}{"bar"}, + } + deepCheck(t, got, expected) +} + +func TestNilToGo(t *testing.T) { + got := ToGo(unsafe.Pointer(uintptr(0))) + if got != nil { + t.Errorf("Expected nil, got %v", got) + } +} + +func TestGoToNil(t *testing.T) { + if GoToClownfish(nil, nil, true) != nil { + t.Error("Convert nullable nil successfully") + } +} + +func TestGoToNilNotNullable(t *testing.T) { + defer func() { recover() }() + GoToClownfish(nil, nil, false) // should panic + t.Error("Non-nullable nil should trigger error") // should be unreachable +} + +func TestGoToString(t *testing.T) { + strings := []string{"foo", "", "z\u0000z"} + for _, val := range strings { + got := WRAPAny(GoToString(val)) + if _, ok := got.(String); !ok { + t.Errorf("Not a String, but a %T", got) + } + if ToGo(unsafe.Pointer(got.TOPTR())).(string) != val { + t.Error("Round trip failed") + } + } +} + +func TestGoToBlob(t *testing.T) { + strings := []string{"foo", "", "z\u0000z"} + for _, str := range strings { + val := []byte(str) + got := WRAPAny(GoToBlob(val)) + if _, ok := got.(Blob); !ok { + t.Errorf("Not a Blob, but a %T", got) + } + if !reflect.DeepEqual(ToGo(unsafe.Pointer(got.TOPTR())), val) { + t.Error("Round trip failed") + } + } +} + +func checkIntConv(t *testing.T, got Obj) { + if _, ok := got.(Integer); !ok { + t.Errorf("Not an Integer, but a %T", got) + } +} + +func TestGoToInteger(t *testing.T) { + checkIntConv(t, WRAPAny(GoToClownfish(int(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(uint(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(int64(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(int32(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(int16(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(int8(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(uint64(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(uint32(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(uint16(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(uint8(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(byte(42), nil, true))) + checkIntConv(t, WRAPAny(GoToClownfish(rune(42), nil, true))) +} + +func TestGoToIntegerRangeError(t *testing.T) { + defer func() { recover() }() + GoToClownfish(uint64(math.MaxUint64), nil, true) + t.Error("Truncation didn't cause error") +} + +func TestGoToFloat(t *testing.T) { + var got Obj + + values := []float64{math.MaxFloat64, math.SmallestNonzeroFloat64, + 0.0, -0.0, 0.5, -0.5, math.Inf(1), math.Inf(-1)} + for _, val := range values { + got := WRAPAny(GoToFloat(val)) + if _, ok := got.(Float); !ok { + t.Errorf("Not a Float, but a %T", got) + } + if !reflect.DeepEqual(ToGo(unsafe.Pointer(got.TOPTR())), val) { + t.Error("Round trip failed") + } + } + + // NaN + got = WRAPAny(GoToFloat(math.NaN())) + if !math.IsNaN(ToGo(unsafe.Pointer(got.TOPTR())).(float64)) { + t.Error("Didn't convert NaN cleanly") + } + + // float32 + expected := float32(0.5) + got = WRAPAny(GoToClownfish(expected, nil, false)) + deepCheck(t, got.(Float).GetValue(), float64(expected)) +} + +func TestGoToBoolean(t *testing.T) { + values := []bool{true, false} + for _, val := range values { + got := WRAPAny(GoToBoolean(val)) + if _, ok := got.(Boolean); !ok { + t.Errorf("Not a Boolean, but a %T", got) + } + if !reflect.DeepEqual(ToGo(unsafe.Pointer(got.TOPTR())), val) { + t.Error("Round trip failed") + } + } +} + +func TestGoToHash(t *testing.T) { + expected := map[string]interface{}{ + "foo": int64(1), + "bar": []interface{}{}, + } + got := WRAPAny(GoToHash(expected)) + if _, ok := got.(Hash); !ok { + t.Errorf("Not a Hash, but a %T", got) + } + deepCheck(t, ToGo(unsafe.Pointer(got.TOPTR())), expected) +} + +func TestGoToVector(t *testing.T) { + expected := []interface{}{ + "foo", + "bar", + []interface{}{}, + int64(-1), + } + got := WRAPAny(GoToVector(expected)) + if _, ok := got.(Vector); !ok { + t.Errorf("Not a Vector, but a %T", got) + } + deepCheck(t, ToGo(unsafe.Pointer(got.TOPTR())), expected) +}
