mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-04 10:57:50 +08:00
6e36058d0a
- Use reflection to derive function name. - Take test cases as variadic arguments, instead of requiring them to be wrapped in a Table. - Support naming test cases. - Run test cases as subtests with t.Run.
228 lines
6.6 KiB
Go
228 lines
6.6 KiB
Go
package vals
|
|
|
|
import (
|
|
"math/big"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"src.elv.sh/pkg/eval/errs"
|
|
"src.elv.sh/pkg/testutil"
|
|
"src.elv.sh/pkg/tt"
|
|
)
|
|
|
|
type someType struct {
|
|
Foo string
|
|
}
|
|
|
|
func TestScanToGo_ConcreteTypeDst(t *testing.T) {
|
|
// A wrapper around ScanToGo, to make it easier to test. Instead of
|
|
// supplying a pointer to the destination, an initial value to the
|
|
// destination is supplied and the result is returned.
|
|
scanToGo := func(src any, dstInit any) (any, error) {
|
|
ptr := reflect.New(TypeOf(dstInit))
|
|
err := ScanToGo(src, ptr.Interface())
|
|
return ptr.Elem().Interface(), err
|
|
}
|
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
|
// int
|
|
Args("12", 0).Rets(12),
|
|
Args("0x12", 0).Rets(0x12),
|
|
Args(12.0, 0).Rets(0, errMustBeInteger),
|
|
Args(0.5, 0).Rets(0, errMustBeInteger),
|
|
Args(someType{}, 0).Rets(tt.Any, errMustBeInteger),
|
|
Args("x", 0).Rets(tt.Any, cannotParseAs{"integer", "x"}),
|
|
|
|
// float64
|
|
Args(23, 0.0).Rets(23.0),
|
|
Args(big.NewRat(1, 2), 0.0).Rets(0.5),
|
|
Args(1.2, 0.0).Rets(1.2),
|
|
Args("23", 0.0).Rets(23.0),
|
|
Args("0x23", 0.0).Rets(float64(0x23)),
|
|
Args(someType{}, 0.0).Rets(tt.Any, errMustBeNumber),
|
|
Args("x", 0.0).Rets(tt.Any, cannotParseAs{"number", "x"}),
|
|
|
|
// rune
|
|
Args("x", ' ').Rets('x'),
|
|
Args(someType{}, ' ').Rets(tt.Any, errMustBeString),
|
|
Args("\xc3\x28", ' ').Rets(tt.Any, errMustBeValidUTF8), // Invalid UTF8
|
|
Args("ab", ' ').Rets(tt.Any, errMustHaveSingleRune),
|
|
|
|
// Other types don't undergo any conversion, as long as the types match
|
|
Args("foo", "").Rets("foo"),
|
|
Args(someType{"foo"}, someType{}).Rets(someType{"foo"}),
|
|
Args(nil, nil).Rets(nil),
|
|
Args("x", someType{}).Rets(tt.Any, WrongType{"!!vals.someType", "string"}),
|
|
)
|
|
}
|
|
|
|
func TestScanToGo_NumDst(t *testing.T) {
|
|
scanToGo := func(src any) (Num, error) {
|
|
var n Num
|
|
err := ScanToGo(src, &n)
|
|
return n, err
|
|
}
|
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
|
// Strings are automatically converted
|
|
Args("12").Rets(12),
|
|
Args(z).Rets(bigInt(z)),
|
|
Args("1/2").Rets(big.NewRat(1, 2)),
|
|
Args("12.0").Rets(12.0),
|
|
// Already numbers
|
|
Args(12).Rets(12),
|
|
Args(bigInt(z)).Rets(bigInt(z)),
|
|
Args(big.NewRat(1, 2)).Rets(big.NewRat(1, 2)),
|
|
Args(12.0).Rets(12.0),
|
|
|
|
Args("bad").Rets(tt.Any, cannotParseAs{"number", "bad"}),
|
|
Args(EmptyList).Rets(tt.Any, errMustBeNumber),
|
|
)
|
|
}
|
|
|
|
func TestScanToGo_InterfaceDst(t *testing.T) {
|
|
scanToGo := func(src any) (any, error) {
|
|
var l List
|
|
err := ScanToGo(src, &l)
|
|
return l, err
|
|
}
|
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
|
Args(EmptyList).Rets(EmptyList),
|
|
|
|
Args("foo").Rets(tt.Any, WrongType{"!!vector.Vector", "string"}),
|
|
)
|
|
}
|
|
|
|
func TestScanToGo_CallableDstAdmitsNil(t *testing.T) {
|
|
type mockCallable interface {
|
|
Call()
|
|
}
|
|
testutil.Set(t, &CallableType, reflect.TypeOf((*mockCallable)(nil)).Elem())
|
|
scanToGo := func(src any) (any, error) {
|
|
var c mockCallable
|
|
err := ScanToGo(src, &c)
|
|
return c, err
|
|
}
|
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
|
Args(nil).Rets(mockCallable(nil)),
|
|
)
|
|
}
|
|
|
|
func TestScanToGo_ErrorsWithNonPointerDst(t *testing.T) {
|
|
err := ScanToGo("", 1)
|
|
if err == nil {
|
|
t.Errorf("did not return error")
|
|
}
|
|
}
|
|
|
|
func TestScanListToGo(t *testing.T) {
|
|
// A wrapper around ScanListToGo, to make it easier to test.
|
|
scanListToGo := func(src List, dstInit any) (any, error) {
|
|
ptr := reflect.New(TypeOf(dstInit))
|
|
ptr.Elem().Set(reflect.ValueOf(dstInit))
|
|
err := ScanListToGo(src, ptr.Interface())
|
|
return ptr.Elem().Interface(), err
|
|
}
|
|
|
|
tt.Test(t, tt.Fn(scanListToGo).Named("scanListToGo"),
|
|
Args(MakeList("1", "2"), []int{}).Rets([]int{1, 2}),
|
|
Args(MakeList("1", "2"), []string{}).Rets([]string{"1", "2"}),
|
|
|
|
Args(MakeList("1", "a"), []int{}).Rets([]int{}, cannotParseAs{"integer", "a"}),
|
|
)
|
|
}
|
|
|
|
func TestScanListElementsToGo(t *testing.T) {
|
|
// A wrapper around ScanListElementsToGo, to make it easier to test.
|
|
scanListElementsToGo := func(src List, inits ...any) ([]any, error) {
|
|
ptrs := make([]any, len(inits))
|
|
for i, init := range inits {
|
|
if o, ok := init.(optional); ok {
|
|
// Wrapping the init value with Optional translates to wrapping
|
|
// the pointer with Optional.
|
|
ptrs[i] = Optional(reflect.New(TypeOf(o.ptr)).Interface())
|
|
} else {
|
|
ptrs[i] = reflect.New(TypeOf(init)).Interface()
|
|
}
|
|
}
|
|
err := ScanListElementsToGo(src, ptrs...)
|
|
vals := make([]any, len(ptrs))
|
|
for i, ptr := range ptrs {
|
|
if o, ok := ptr.(optional); ok {
|
|
vals[i] = reflect.ValueOf(o.ptr).Elem().Interface()
|
|
} else {
|
|
vals[i] = reflect.ValueOf(ptr).Elem().Interface()
|
|
}
|
|
}
|
|
return vals, err
|
|
}
|
|
|
|
tt.Test(t, tt.Fn(scanListElementsToGo).Named("scanListElementsToGo"),
|
|
Args(MakeList("1", "2"), 0, 0).Rets([]any{1, 2}),
|
|
Args(MakeList("1", "2"), "", "").Rets([]any{"1", "2"}),
|
|
Args(MakeList("1", "2"), 0, Optional(0)).Rets([]any{1, 2}),
|
|
Args(MakeList("1"), 0, Optional(0)).Rets([]any{1, 0}),
|
|
|
|
Args(MakeList("a"), 0).Rets([]any{0},
|
|
cannotParseAs{"integer", "a"}),
|
|
Args(MakeList("1"), 0, 0).Rets([]any{0, 0},
|
|
errs.ArityMismatch{What: "list elements",
|
|
ValidLow: 2, ValidHigh: 2, Actual: 1}),
|
|
Args(MakeList("1"), 0, 0, Optional(0)).Rets([]any{0, 0, 0},
|
|
errs.ArityMismatch{What: "list elements",
|
|
ValidLow: 2, ValidHigh: 3, Actual: 1}),
|
|
)
|
|
}
|
|
|
|
type aStruct struct {
|
|
Foo int
|
|
bar any
|
|
}
|
|
|
|
// Equal is required by cmp.Diff, since aStruct contains unexported fields.
|
|
func (a aStruct) Equal(b aStruct) bool { return a == b }
|
|
|
|
func TestScanMapToGo(t *testing.T) {
|
|
// A wrapper around ScanMapToGo, to make it easier to test.
|
|
scanMapToGo := func(src Map, dstInit any) (any, error) {
|
|
ptr := reflect.New(TypeOf(dstInit))
|
|
ptr.Elem().Set(reflect.ValueOf(dstInit))
|
|
err := ScanMapToGo(src, ptr.Interface())
|
|
return ptr.Elem().Interface(), err
|
|
}
|
|
|
|
tt.Test(t, tt.Fn(scanMapToGo).Named("scanMapToGo"),
|
|
Args(MakeMap("foo", "1"), aStruct{}).Rets(aStruct{Foo: 1}),
|
|
// More fields is OK
|
|
Args(MakeMap("foo", "1", "bar", "x"), aStruct{}).Rets(aStruct{Foo: 1}),
|
|
// Fewer fields is OK
|
|
Args(MakeMap(), aStruct{}).Rets(aStruct{}),
|
|
// Unexported fields are ignored
|
|
Args(MakeMap("bar", 20), aStruct{bar: 10}).Rets(aStruct{bar: 10}),
|
|
|
|
// Conversion error
|
|
Args(MakeMap("foo", "a"), aStruct{}).
|
|
Rets(aStruct{}, cannotParseAs{"integer", "a"}),
|
|
)
|
|
}
|
|
|
|
func TestFromGo(t *testing.T) {
|
|
tt.Test(t, FromGo,
|
|
// BigInt -> int, when in range
|
|
Args(bigInt(z)).Rets(bigInt(z)),
|
|
Args(big.NewInt(100)).Rets(100),
|
|
// BigRat -> BigInt or int, when denominator is 1
|
|
Args(bigRat(z1+"/"+z)).Rets(bigRat(z1+"/"+z)),
|
|
Args(bigRat(z+"/1")).Rets(bigInt(z)),
|
|
Args(bigRat("2/1")).Rets(2),
|
|
// rune -> string
|
|
Args('x').Rets("x"),
|
|
|
|
// Other types don't undergo any conversion
|
|
Args(nil).Rets(nil),
|
|
Args(someType{"foo"}).Rets(someType{"foo"}),
|
|
)
|
|
}
|