elvish/pkg/eval/vals/conversion_test.go
Kurtis Rader 71cd3835bc Don't dot import pkg/tt
Qualified imports of pkg/tt outnumber unqualified (27 to 24). Improve
consistency, and clarity, by changing the dot (unqualified) imports of
that package symbols to qualified.
2022-06-04 23:39:19 +01:00

211 lines
6.2 KiB
Go

package vals
import (
"math/big"
"reflect"
"testing"
"src.elv.sh/pkg/eval/errs"
"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", scanToGo), tt.Table{
// 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", scanToGo), tt.Table{
// 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", scanToGo), tt.Table{
Args(EmptyList).Rets(EmptyList),
Args("foo").Rets(tt.Any, WrongType{"!!vector.Vector", "string"}),
})
}
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", scanListToGo), tt.Table{
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", scanListElementsToGo), tt.Table{
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("ScanListToGo", scanMapToGo), tt.Table{
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, tt.Fn("FromGo", FromGo), tt.Table{
// 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"}),
})
}