mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-09 22:44:05 +08:00
fix: unittests for CallFunc
This commit is contained in:
@@ -3,24 +3,97 @@ package shared
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CallFunc calls function with arguments
|
// CallFunc calls function with arguments
|
||||||
// it is used when calling go plugin or builtin functions
|
// it is used when calling go plugin or builtin functions
|
||||||
func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) {
|
func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) {
|
||||||
fnArgsNum := fn.Type().NumIn()
|
fnArgsNum := fn.Type().NumIn()
|
||||||
if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice {
|
|
||||||
// last argument is slice, do not check arguments number
|
|
||||||
// e.g. ...interface{}
|
|
||||||
// e.g. a, b string, c ...interface{}
|
|
||||||
} else if fnArgsNum != len(args) {
|
|
||||||
// function arguments not match
|
|
||||||
return nil, fmt.Errorf("function arguments number not match, expect %d, got %d", fnArgsNum, len(args))
|
|
||||||
}
|
|
||||||
// arguments do not have slice, and arguments number matched
|
|
||||||
|
|
||||||
argumentsValue := make([]reflect.Value, len(args))
|
// function expect 0 argument
|
||||||
for index, argument := range args {
|
if fnArgsNum == 0 {
|
||||||
|
if len(args) > 0 {
|
||||||
|
return nil, fmt.Errorf("function expect 0 argument, but got %d", len(args))
|
||||||
|
}
|
||||||
|
return call(fn, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fnArgsNum > 0: function expect more than 0 arguments
|
||||||
|
// function last argument is not slice
|
||||||
|
if fn.Type().In(fnArgsNum-1).Kind() != reflect.Slice {
|
||||||
|
// function arguments should match exactly
|
||||||
|
if fnArgsNum != len(args) {
|
||||||
|
return nil, fmt.Errorf("function expect %d arguments, but got %d", fnArgsNum, len(args))
|
||||||
|
}
|
||||||
|
// prepare arguments
|
||||||
|
argumentsValue, err := convertFirstNArgs(fn, fnArgsNum, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return call(fn, argumentsValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// function last argument is slice
|
||||||
|
// e.g.
|
||||||
|
// ...interface{}
|
||||||
|
// a, b string, c ...interface{}
|
||||||
|
// []int
|
||||||
|
// a, b string, c []int
|
||||||
|
|
||||||
|
// arguments number should >= fnArgsNum-1
|
||||||
|
if len(args) < fnArgsNum-1 {
|
||||||
|
return nil, fmt.Errorf("function expect at least %d arguments, but got %d", fnArgsNum-1, len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare first n-1 arguments
|
||||||
|
argumentsValue, err := convertFirstNArgs(fn, fnArgsNum-1, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare last slice argument
|
||||||
|
fnLastSliceElemType := fn.Type().In(fnArgsNum - 1).Elem() // slice element type
|
||||||
|
for i := fnArgsNum - 1; i < len(args); i++ { // loop last len(args)-fnArgsNum+1 arguments
|
||||||
|
argValue := reflect.ValueOf(args[i])
|
||||||
|
argValueType := reflect.TypeOf(args[i])
|
||||||
|
|
||||||
|
// check if slice element type match
|
||||||
|
if fnLastSliceElemType == argValueType {
|
||||||
|
argumentsValue = append(argumentsValue, argValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// last argument is also slice, e.g. []int
|
||||||
|
if argValueType.Kind() == reflect.Slice {
|
||||||
|
if argValueType.Elem() != fnLastSliceElemType {
|
||||||
|
err := fmt.Errorf("function argument %d's slice element type is not match, expect %v, actual %v",
|
||||||
|
i, fnLastSliceElemType, argValueType)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argumentsValue = append(argumentsValue, argValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// type not match, check if convertible
|
||||||
|
if !argValueType.ConvertibleTo(fnLastSliceElemType) {
|
||||||
|
// function argument type not match and not convertible
|
||||||
|
err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v",
|
||||||
|
i, fnLastSliceElemType, argValueType)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// convert argument to expect type
|
||||||
|
argumentsValue = append(argumentsValue, argValue.Convert(fnLastSliceElemType))
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(fn, argumentsValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect.Value, error) {
|
||||||
|
argumentsValue := make([]reflect.Value, n)
|
||||||
|
for index := 0; index < n; index++ {
|
||||||
|
argument := args[index]
|
||||||
if argument == nil {
|
if argument == nil {
|
||||||
argumentsValue[index] = reflect.Zero(fn.Type().In(index))
|
argumentsValue[index] = reflect.Zero(fn.Type().In(index))
|
||||||
continue
|
continue
|
||||||
@@ -41,13 +114,17 @@ func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) {
|
|||||||
// function argument type not match and not convertible
|
// function argument type not match and not convertible
|
||||||
err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v",
|
err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v",
|
||||||
index, expectArgumentType, actualArgumentType)
|
index, expectArgumentType, actualArgumentType)
|
||||||
|
log.Error().Err(err).Msg("convert arguments failed")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// convert argument to expect type
|
// convert argument to expect type
|
||||||
argumentsValue[index] = argumentValue.Convert(expectArgumentType)
|
argumentsValue[index] = argumentValue.Convert(expectArgumentType)
|
||||||
}
|
}
|
||||||
|
return argumentsValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
resultValues := fn.Call(argumentsValue)
|
func call(fn reflect.Value, args []reflect.Value) (interface{}, error) {
|
||||||
|
resultValues := fn.Call(args)
|
||||||
if resultValues == nil {
|
if resultValues == nil {
|
||||||
// no returns
|
// no returns
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -69,7 +146,7 @@ func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// return more than 2 arguments, unexpected
|
// return more than 2 arguments, unexpected
|
||||||
err := fmt.Errorf("function should return at most 2 arguments")
|
err := fmt.Errorf("function should return at most 2 values")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCallFunc(t *testing.T) {
|
type data struct {
|
||||||
type data struct {
|
f interface{}
|
||||||
f interface{}
|
args []interface{}
|
||||||
args []interface{}
|
expVal interface{}
|
||||||
expVal interface{}
|
expErr error
|
||||||
expErr error
|
}
|
||||||
}
|
|
||||||
|
func TestCallFuncBasic(t *testing.T) {
|
||||||
params := []data{
|
params := []data{
|
||||||
// zero argument, zero return
|
// zero argument, zero return
|
||||||
{f: func() {}, args: []interface{}{}, expVal: nil, expErr: nil},
|
{f: func() {}, args: []interface{}{}, expVal: nil, expErr: nil},
|
||||||
@@ -28,10 +29,16 @@ func TestCallFunc(t *testing.T) {
|
|||||||
// zero argument, return one value and error
|
// zero argument, return one value and error
|
||||||
{f: func() (int, error) { return 1, errors.New("xxx") }, args: []interface{}{}, expVal: 1, expErr: errors.New("xxx")},
|
{f: func() (int, error) { return 1, errors.New("xxx") }, args: []interface{}{}, expVal: 1, expErr: errors.New("xxx")},
|
||||||
{f: func() (interface{}, error) { return 1.23, errors.New("xxx") }, args: []interface{}{}, expVal: 1.23, expErr: errors.New("xxx")},
|
{f: func() (interface{}, error) { return 1.23, errors.New("xxx") }, args: []interface{}{}, expVal: 1.23, expErr: errors.New("xxx")},
|
||||||
|
|
||||||
|
// one argument, zero return
|
||||||
|
{f: func(n int) {}, args: []interface{}{1}, expVal: nil, expErr: nil},
|
||||||
// one argument, return one value
|
// one argument, return one value
|
||||||
{f: func(n int) int { return n * n }, args: []interface{}{2}, expVal: 4},
|
{f: func(n int) int { return n * n }, args: []interface{}{2}, expVal: 4},
|
||||||
{f: func(c string) string { return c + c }, args: []interface{}{"p"}, expVal: "pp"},
|
{f: func(c string) string { return c + c }, args: []interface{}{"p"}, expVal: "pp"},
|
||||||
{f: func(arg interface{}) interface{} { return fmt.Sprintf("%v", arg) }, args: []interface{}{1.23}, expVal: "1.23"},
|
{f: func(arg interface{}) interface{} { return fmt.Sprintf("%v", arg) }, args: []interface{}{1.23}, expVal: "1.23"},
|
||||||
|
// one argument, return one value and error
|
||||||
|
{f: func(arg interface{}) (interface{}, error) { return 1.23, errors.New("xxx") }, args: []interface{}{"a"}, expVal: 1.23, expErr: errors.New("xxx")},
|
||||||
|
|
||||||
// two arguments in same type
|
// two arguments in same type
|
||||||
{f: func(a, b int) int { return a * b }, args: []interface{}{2, 3}, expVal: 6},
|
{f: func(a, b int) int { return a * b }, args: []interface{}{2, 3}, expVal: 6},
|
||||||
// two arguments in different type
|
// two arguments in different type
|
||||||
@@ -46,6 +53,123 @@ func TestCallFunc(t *testing.T) {
|
|||||||
args: []interface{}{3, "p"},
|
args: []interface{}{3, "p"},
|
||||||
expVal: "ppp",
|
expVal: "ppp",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// variable arguments list: ...int, ...interface{}
|
||||||
|
{
|
||||||
|
f: func(n ...int) int {
|
||||||
|
var sum int
|
||||||
|
for _, arg := range n {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
args: []interface{}{1, 2, 3},
|
||||||
|
expVal: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: func(args ...interface{}) (interface{}, error) {
|
||||||
|
var result string
|
||||||
|
for _, arg := range args {
|
||||||
|
result += fmt.Sprintf("%v", arg)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
},
|
||||||
|
args: []interface{}{1, 2.3, "4.5", "p"},
|
||||||
|
expVal: "12.34.5p",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: func(a, b int8, n ...int) int {
|
||||||
|
var sum int
|
||||||
|
for _, arg := range n {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
sum += int(a) + int(b)
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
args: []interface{}{1, 2, 3, 4.5},
|
||||||
|
expVal: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: func(a, b int8, n ...int) int {
|
||||||
|
sum := int(a) + int(b)
|
||||||
|
for _, arg := range n {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
args: []interface{}{1, 2},
|
||||||
|
expVal: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
f: func(a []int, n ...int) int {
|
||||||
|
var sum int
|
||||||
|
for _, arg := range a {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
for _, arg := range n {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
args: []interface{}{[]int{1, 2}, 3, 4},
|
||||||
|
expVal: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range params {
|
||||||
|
fn := reflect.ValueOf(p.f)
|
||||||
|
val, err := CallFunc(fn, p.args...)
|
||||||
|
if !assert.Equal(t, p.expErr, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, p.expVal, val) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallFuncComplex(t *testing.T) {
|
||||||
|
params := []data{
|
||||||
|
// arguments include slice
|
||||||
|
{
|
||||||
|
f: func(a int, n []int, b int) int {
|
||||||
|
sum := a
|
||||||
|
for _, arg := range n {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
sum += b
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
args: []interface{}{1, []int{2, 3}, 4},
|
||||||
|
expVal: 10,
|
||||||
|
},
|
||||||
|
// last argument is slice
|
||||||
|
{
|
||||||
|
f: func(n []int) int {
|
||||||
|
var sum int
|
||||||
|
for _, arg := range n {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
args: []interface{}{[]int{1, 2, 3}},
|
||||||
|
expVal: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: func(a, b int, n []int) int {
|
||||||
|
sum := a + b
|
||||||
|
for _, arg := range n {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
args: []interface{}{1, 2, []int{3, 4}},
|
||||||
|
expVal: 10,
|
||||||
|
},
|
||||||
|
|
||||||
|
// FIXME: return more than 2 values
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
|
|||||||
Reference in New Issue
Block a user