diff --git a/sdks/go/pkg/beam/core/util/reflectx/call_test.go b/sdks/go/pkg/beam/core/util/reflectx/call_test.go new file mode 100644 index 000000000000..6107011d55bc --- /dev/null +++ b/sdks/go/pkg/beam/core/util/reflectx/call_test.go @@ -0,0 +1,125 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reflectx + +import ( + "reflect" + "strings" + "testing" +) + +type mapperString struct { + fn func(string) string +} + +func mapperStringMaker(fn interface{}) Func { + f := fn.(func(string) string) + return &mapperString{fn: f} +} + +func (c *mapperString) Name() string { + return "testMapperString" +} + +func (c *mapperString) Type() reflect.Type { + return reflect.TypeOf(c.fn) +} + +func (c *mapperString) Call(args []interface{}) []interface{} { + out := c.fn(args[0].(string)) + return []interface{}{out} +} + +func (c *mapperString) Call1x1(v interface{}) interface{} { + return c.fn(v.(string)) +} + +func TestMakeFunc(t *testing.T) { + RegisterFunc(reflect.TypeOf((*func(string) string)(nil)).Elem(), mapperStringMaker) + fn := func(str string) string { + return string(str) + } + madeFn := MakeFunc(fn) + + if got, want := madeFn.Name(), "testMapperString"; got != want { + t.Fatalf("MakeFunc(fn).Name()=%v, want %v", got, want) + } +} + +func TestCallNoPanic(t *testing.T) { + RegisterFunc(reflect.TypeOf((*func(string) string)(nil)).Elem(), mapperStringMaker) + fn := func(str string) string { + return string(str) + } + madeFn := MakeFunc(fn) + + ret, err := CallNoPanic(madeFn, []interface{}{"tester"}) + if err != nil { + t.Fatalf("CallNoPanic(madeFn, [\"tester\"]) - unexpected error %v", err) + } + if got, want := ret[0].(string), string("tester"); got != want { + t.Fatalf("CallNoPanic(madeFn, [\"tester\"]) got %v, want %v", got, want) + } +} + +func TestCallNoPanic_Panic(t *testing.T) { + RegisterFunc(reflect.TypeOf((*func(string) string)(nil)).Elem(), mapperStringMaker) + fn := func(str string) string { + if str == "tester" { + panic("OH NO!") + } + return string(str) + } + madeFn := MakeFunc(fn) + + _, err := CallNoPanic(madeFn, []interface{}{"tester"}) + if err == nil { + t.Fatalf("CallNoPanic(madeFn, [\"tester\"]) didn't error when it should have") + } + if !strings.Contains(err.Error(), "OH NO!") { + t.Fatalf("CallNoPanic(madeFn, [\"tester\"]) error should have contained OH NO! instead returned error %v", err) + } +} + +func TestValue(t *testing.T) { + interfaces := []interface{}{"hi", 42, func() {}} + want := []reflect.Kind{reflect.String, reflect.Int, reflect.Func} + + got := ValueOf(interfaces) + if len(got) != len(want) { + t.Fatalf("ValueOf(interfaces) got slice %v, expected slice of length %v", got, len(want)) + } + for idx, _ := range got { + if got[idx].Kind() != want[idx] { + t.Errorf("ValueOf(interfaces)[%v], got %v of kind %v, want %v", idx, got[idx], got[idx].Kind(), want[idx]) + } + } +} + +func TestInterface(t *testing.T) { + interfaces := []interface{}{"hi", 42} + values := ValueOf(interfaces) + got := Interface(values) + + if len(got) != len(interfaces) { + t.Fatalf("Interface(values) got slice %v, expected slice of length %v", got, len(interfaces)) + } + for idx, _ := range got { + if got[idx] != interfaces[idx] { + t.Errorf("Interface(values)[%v]=%v, want %v", idx, got[idx], interfaces[idx]) + } + } +} diff --git a/sdks/go/pkg/beam/core/util/reflectx/functions_test.go b/sdks/go/pkg/beam/core/util/reflectx/functions_test.go index 8d2e92cfc2c1..40444a151b94 100644 --- a/sdks/go/pkg/beam/core/util/reflectx/functions_test.go +++ b/sdks/go/pkg/beam/core/util/reflectx/functions_test.go @@ -17,6 +17,7 @@ package reflectx import ( "reflect" + "strings" "testing" ) @@ -24,6 +25,12 @@ func testFunction() int64 { return 42 } +func TestFunctionName(t *testing.T) { + if got, want := FunctionName(testFunction), "reflectx.testFunction"; !strings.Contains(got, want) { + t.Fatalf("FunctionName(testFunction)=%v, should contain %v", got, want) + } +} + func TestLoadFunction(t *testing.T) { val := reflect.ValueOf(testFunction) fi := uintptr(val.Pointer()) diff --git a/sdks/go/pkg/beam/core/util/reflectx/structs_test.go b/sdks/go/pkg/beam/core/util/reflectx/structs_test.go new file mode 100644 index 000000000000..f49c1eff39f3 --- /dev/null +++ b/sdks/go/pkg/beam/core/util/reflectx/structs_test.go @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reflectx + +import ( + "reflect" + "testing" +) + +type nameFn struct { + Name string `json:"name,omitempty"` +} + +func (f *nameFn) PrintName() string { + return f.Name +} + +func NameFnMaker(fn interface{}) map[string]Func { + dfn := fn.(*nameFn) + return map[string]Func{ + "PrintName": MakeFunc(func() string { return dfn.PrintName() }), + } +} + +func TestWrapMethods_Registered(t *testing.T) { + RegisterStructWrapper(reflect.TypeOf((*nameFn)(nil)).Elem(), NameFnMaker) + wrapper, exists := WrapMethods(&nameFn{Name: "testName"}) + + if got, want := exists, true; got != want { + t.Errorf("WrapMethods(&nameFn{Name: \"testName\"}), nameFn registered = %v, want %v", got, want) + } + fn, ok := wrapper["PrintName"] + if ok != true { + t.Errorf("WrapMethods(&nameFn{Name: \"testName\"}) doesn't contain PrintName, want a function") + } + if got, want := fn.Call([]interface{}{})[0].(string), "testName"; got != want { + t.Errorf("WrapMethods(&nameFn{Name: \"testName\"}) invoked PrintName, got %v, want %v", got, want) + } +} + +type unregisteredFn struct { + Name string `json:"name,omitempty"` +} + +func TestWrapMethods_Unregistered(t *testing.T) { + wrapper, exists := WrapMethods(&unregisteredFn{Name: "testName"}) + + if got, want := exists, false; got != want { + t.Fatalf("WrapMethods(&unregisteredFn{Name: \"testName\"}), unregisteredFn registered = %v, want %v", got, want) + } + if got := wrapper; got != nil { + t.Fatalf("WrapMethods(&unregisteredFn{Name: \"testName\"}), wrapper = %v, want nil", got) + } +} diff --git a/sdks/go/pkg/beam/core/util/reflectx/types_test.go b/sdks/go/pkg/beam/core/util/reflectx/types_test.go new file mode 100644 index 000000000000..04670fc66689 --- /dev/null +++ b/sdks/go/pkg/beam/core/util/reflectx/types_test.go @@ -0,0 +1,410 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reflectx + +import ( + "reflect" + "testing" +) + +func TestIsNumber(t *testing.T) { + i := 4 + tests := []struct { + val interface{} + out bool + }{ + { + val: 50, + out: true, + }, + { + val: int8(1), + out: true, + }, + { + val: int16(2), + out: true, + }, + { + val: int32(3), + out: true, + }, + { + val: int64(4), + out: true, + }, + { + val: uint(50), + out: true, + }, + { + val: uint8(1), + out: true, + }, + { + val: uint16(2), + out: true, + }, + { + val: uint32(3), + out: true, + }, + { + val: uint64(4), + out: true, + }, + { + val: float32(3), + out: true, + }, + { + val: float64(4), + out: true, + }, + { + val: complex64(3), + out: true, + }, + { + val: complex128(4), + out: true, + }, + { + val: &i, + out: false, + }, + { + val: "10", + out: false, + }, + { + val: func(a int) int { + return a + }, + out: false, + }, + } + for _, test := range tests { + if got, want := IsNumber(reflect.TypeOf(test.val)), test.out; got != want { + t.Errorf("IsNumber(reflect.TypeOf(%v))=%v, want %v", test.val, got, want) + } + } +} + +func TestIsInteger(t *testing.T) { + i := 4 + tests := []struct { + val interface{} + out bool + }{ + { + val: 50, + out: true, + }, + { + val: int8(1), + out: true, + }, + { + val: int16(2), + out: true, + }, + { + val: int32(3), + out: true, + }, + { + val: int64(4), + out: true, + }, + { + val: uint(50), + out: true, + }, + { + val: uint8(1), + out: true, + }, + { + val: uint16(2), + out: true, + }, + { + val: uint32(3), + out: true, + }, + { + val: uint64(4), + out: true, + }, + { + val: float32(3), + out: false, + }, + { + val: float64(4), + out: false, + }, + { + val: complex64(3), + out: false, + }, + { + val: complex128(4), + out: false, + }, + { + val: &i, + out: false, + }, + { + val: "10", + out: false, + }, + { + val: func(a int) int { + return a + }, + out: false, + }, + } + for _, test := range tests { + if got, want := IsInteger(reflect.TypeOf(test.val)), test.out; got != want { + t.Errorf("IsInteger(reflect.TypeOf(%v))=%v, want %v", test.val, got, want) + } + } +} + +func TestIsFloat(t *testing.T) { + i := 4 + tests := []struct { + val interface{} + out bool + }{ + { + val: 50, + out: false, + }, + { + val: int8(1), + out: false, + }, + { + val: int16(2), + out: false, + }, + { + val: int32(3), + out: false, + }, + { + val: int64(4), + out: false, + }, + { + val: uint(50), + out: false, + }, + { + val: uint8(1), + out: false, + }, + { + val: uint16(2), + out: false, + }, + { + val: uint32(3), + out: false, + }, + { + val: uint64(4), + out: false, + }, + { + val: float32(3), + out: true, + }, + { + val: float64(4), + out: true, + }, + { + val: complex64(3), + out: false, + }, + { + val: complex128(4), + out: false, + }, + { + val: &i, + out: false, + }, + { + val: "10", + out: false, + }, + { + val: func(a int) int { + return a + }, + out: false, + }, + } + for _, test := range tests { + if got, want := IsFloat(reflect.TypeOf(test.val)), test.out; got != want { + t.Errorf("IsFloat(reflect.TypeOf(%v))=%v, want %v", test.val, got, want) + } + } +} + +func TestIsComplex(t *testing.T) { + i := 4 + tests := []struct { + val interface{} + out bool + }{ + { + val: 50, + out: false, + }, + { + val: int8(1), + out: false, + }, + { + val: int16(2), + out: false, + }, + { + val: int32(3), + out: false, + }, + { + val: int64(4), + out: false, + }, + { + val: uint(50), + out: false, + }, + { + val: uint8(1), + out: false, + }, + { + val: uint16(2), + out: false, + }, + { + val: uint32(3), + out: false, + }, + { + val: uint64(4), + out: false, + }, + { + val: float32(3), + out: false, + }, + { + val: float64(4), + out: false, + }, + { + val: complex64(3), + out: true, + }, + { + val: complex128(4), + out: true, + }, + { + val: &i, + out: false, + }, + { + val: "10", + out: false, + }, + { + val: func(a int) int { + return a + }, + out: false, + }, + } + for _, test := range tests { + if got, want := IsComplex(reflect.TypeOf(test.val)), test.out; got != want { + t.Errorf("IsComplex(reflect.TypeOf(%v))=%v, want %v", test.val, got, want) + } + } +} + +func TestSkipPtr(t *testing.T) { + i := 4 + tests := []struct { + val interface{} + out reflect.Type + }{ + { + val: i, + out: Int, + }, + { + val: &i, + out: Int, + }, + } + for _, test := range tests { + if got, want := SkipPtr(reflect.TypeOf(test.val)), test.out; got != want { + t.Errorf("SkipPtr(reflect.TypeOf(%v))=%v, want %v", test.val, got, want) + } + } +} + +func TestMakeSlice(t *testing.T) { + madeSlice := MakeSlice(String, reflect.ValueOf("test"), reflect.ValueOf("test2")) + + if got, want := madeSlice.Len(), 2; got != want { + t.Fatalf("madeSlice.Len()=%v, want %v", got, want) + } + if got, want := madeSlice.Type(), reflect.TypeOf((*[]string)(nil)).Elem(); got != want { + t.Fatalf("madeSlice.Type()=%v, want %v", got, want) + } + if got, want := madeSlice.Index(0).String(), "test"; got != want { + t.Errorf("madeSlice.Index(0).String()=%v, want %v", got, want) + } + if got, want := madeSlice.Index(1).String(), "test2"; got != want { + t.Errorf("madeSlice.Index(1).String()=%v, want %v", got, want) + } +} + +type typeWrapper interface{} + +func TestUnderlyingType(t *testing.T) { + var wrapper typeWrapper + wrapper = "test" + underlying := UnderlyingType(reflect.ValueOf(wrapper)) + if got, want := underlying.Type(), reflect.TypeOf((*string)(nil)).Elem(); got != want { + t.Fatalf("UnderlyingType(reflect.ValueOf(wrapper)) returned type of %v, want %v", got, want) + } + if got, want := underlying.String(), "test"; got != want { + t.Errorf("UnderlyingType(reflect.ValueOf(wrapper)).String()=%v, want %v", got, want) + } +}