github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/vals/conversion_test.go (about) 1 package vals 2 3 import ( 4 "math/big" 5 "reflect" 6 "testing" 7 8 "github.com/markusbkk/elvish/pkg/eval/errs" 9 . "github.com/markusbkk/elvish/pkg/tt" 10 ) 11 12 type someType struct { 13 foo string 14 } 15 16 func TestScanToGo_ConcreteTypeDst(t *testing.T) { 17 // A wrapper around ScanToGo, to make it easier to test. Instead of 18 // supplying a pointer to the destination, an initial value to the 19 // destination is supplied and the result is returned. 20 scanToGo := func(src interface{}, dstInit interface{}) (interface{}, error) { 21 ptr := reflect.New(TypeOf(dstInit)) 22 err := ScanToGo(src, ptr.Interface()) 23 return ptr.Elem().Interface(), err 24 } 25 26 Test(t, Fn("ScanToGo", scanToGo), Table{ 27 // int 28 Args("12", 0).Rets(12), 29 Args("0x12", 0).Rets(0x12), 30 Args(12.0, 0).Rets(0, errMustBeInteger), 31 Args(0.5, 0).Rets(0, errMustBeInteger), 32 Args(someType{}, 0).Rets(Any, errMustBeInteger), 33 Args("x", 0).Rets(Any, cannotParseAs{"integer", "x"}), 34 35 // float64 36 Args(23, 0.0).Rets(23.0), 37 Args(big.NewRat(1, 2), 0.0).Rets(0.5), 38 Args(1.2, 0.0).Rets(1.2), 39 Args("23", 0.0).Rets(23.0), 40 Args("0x23", 0.0).Rets(float64(0x23)), 41 Args(someType{}, 0.0).Rets(Any, errMustBeNumber), 42 Args("x", 0.0).Rets(Any, cannotParseAs{"number", "x"}), 43 44 // rune 45 Args("x", ' ').Rets('x'), 46 Args(someType{}, ' ').Rets(Any, errMustBeString), 47 Args("\xc3\x28", ' ').Rets(Any, errMustBeValidUTF8), // Invalid UTF8 48 Args("ab", ' ').Rets(Any, errMustHaveSingleRune), 49 50 // Other types don't undergo any conversion, as long as the types match 51 Args("foo", "").Rets("foo"), 52 Args(someType{"foo"}, someType{}).Rets(someType{"foo"}), 53 Args(nil, nil).Rets(nil), 54 Args("x", someType{}).Rets(Any, WrongType{"!!vals.someType", "string"}), 55 }) 56 } 57 58 func TestScanToGo_NumDst(t *testing.T) { 59 scanToGo := func(src interface{}) (Num, error) { 60 var n Num 61 err := ScanToGo(src, &n) 62 return n, err 63 } 64 65 Test(t, Fn("ScanToGo", scanToGo), Table{ 66 // Strings are automatically converted 67 Args("12").Rets(12), 68 Args(z).Rets(bigInt(z)), 69 Args("1/2").Rets(big.NewRat(1, 2)), 70 Args("12.0").Rets(12.0), 71 // Already numbers 72 Args(12).Rets(12), 73 Args(bigInt(z)).Rets(bigInt(z)), 74 Args(big.NewRat(1, 2)).Rets(big.NewRat(1, 2)), 75 Args(12.0).Rets(12.0), 76 77 Args("bad").Rets(Any, cannotParseAs{"number", "bad"}), 78 Args(EmptyList).Rets(Any, errMustBeNumber), 79 }) 80 } 81 82 func TestScanToGo_InterfaceDst(t *testing.T) { 83 scanToGo := func(src interface{}) (interface{}, error) { 84 var l List 85 err := ScanToGo(src, &l) 86 return l, err 87 } 88 89 Test(t, Fn("ScanToGo", scanToGo), Table{ 90 Args(EmptyList).Rets(EmptyList), 91 92 Args("foo").Rets(Any, WrongType{"!!vector.Vector", "string"}), 93 }) 94 } 95 96 func TestScanToGo_ErrorsWithNonPointerDst(t *testing.T) { 97 err := ScanToGo("", 1) 98 if err == nil { 99 t.Errorf("did not return error") 100 } 101 } 102 103 func TestScanListToGo(t *testing.T) { 104 // A wrapper around ScanListToGo, to make it easier to test. 105 scanListToGo := func(src List, dstInit interface{}) (interface{}, error) { 106 ptr := reflect.New(TypeOf(dstInit)) 107 ptr.Elem().Set(reflect.ValueOf(dstInit)) 108 err := ScanListToGo(src, ptr.Interface()) 109 return ptr.Elem().Interface(), err 110 } 111 112 Test(t, Fn("ScanListToGo", scanListToGo), Table{ 113 Args(MakeList("1", "2"), []int{}).Rets([]int{1, 2}), 114 Args(MakeList("1", "2"), []string{}).Rets([]string{"1", "2"}), 115 116 Args(MakeList("1", "a"), []int{}).Rets([]int{}, cannotParseAs{"integer", "a"}), 117 }) 118 } 119 120 func TestScanListElementsToGo(t *testing.T) { 121 // A wrapper around ScanListElementsToGo, to make it easier to test. 122 scanListElementsToGo := func(src List, inits ...interface{}) ([]interface{}, error) { 123 ptrs := make([]interface{}, len(inits)) 124 for i, init := range inits { 125 if o, ok := init.(optional); ok { 126 // Wrapping the init value with Optional translates to wrapping 127 // the pointer with Optional. 128 ptrs[i] = Optional(reflect.New(TypeOf(o.ptr)).Interface()) 129 } else { 130 ptrs[i] = reflect.New(TypeOf(init)).Interface() 131 } 132 } 133 err := ScanListElementsToGo(src, ptrs...) 134 vals := make([]interface{}, len(ptrs)) 135 for i, ptr := range ptrs { 136 if o, ok := ptr.(optional); ok { 137 vals[i] = reflect.ValueOf(o.ptr).Elem().Interface() 138 } else { 139 vals[i] = reflect.ValueOf(ptr).Elem().Interface() 140 } 141 } 142 return vals, err 143 } 144 145 Test(t, Fn("ScanListElementsToGo", scanListElementsToGo), Table{ 146 Args(MakeList("1", "2"), 0, 0).Rets([]interface{}{1, 2}), 147 Args(MakeList("1", "2"), "", "").Rets([]interface{}{"1", "2"}), 148 Args(MakeList("1", "2"), 0, Optional(0)).Rets([]interface{}{1, 2}), 149 Args(MakeList("1"), 0, Optional(0)).Rets([]interface{}{1, 0}), 150 151 Args(MakeList("a"), 0).Rets([]interface{}{0}, 152 cannotParseAs{"integer", "a"}), 153 Args(MakeList("1"), 0, 0).Rets([]interface{}{0, 0}, 154 errs.ArityMismatch{What: "list elements", 155 ValidLow: 2, ValidHigh: 2, Actual: 1}), 156 Args(MakeList("1"), 0, 0, Optional(0)).Rets([]interface{}{0, 0, 0}, 157 errs.ArityMismatch{What: "list elements", 158 ValidLow: 2, ValidHigh: 3, Actual: 1}), 159 }) 160 } 161 162 type aStruct struct { 163 Foo int 164 bar interface{} 165 } 166 167 func TestScanMapToGo(t *testing.T) { 168 // A wrapper around ScanMapToGo, to make it easier to test. 169 scanMapToGo := func(src Map, dstInit interface{}) (interface{}, error) { 170 ptr := reflect.New(TypeOf(dstInit)) 171 ptr.Elem().Set(reflect.ValueOf(dstInit)) 172 err := ScanMapToGo(src, ptr.Interface()) 173 return ptr.Elem().Interface(), err 174 } 175 176 Test(t, Fn("ScanListToGo", scanMapToGo), Table{ 177 Args(MakeMap("foo", "1"), aStruct{}).Rets(aStruct{Foo: 1}), 178 // More fields is OK 179 Args(MakeMap("foo", "1", "bar", "x"), aStruct{}).Rets(aStruct{Foo: 1}), 180 // Fewer fields is OK 181 Args(MakeMap(), aStruct{}).Rets(aStruct{}), 182 // Unexported fields are ignored 183 Args(MakeMap("bar", 20), aStruct{bar: 10}).Rets(aStruct{bar: 10}), 184 185 // Conversion error 186 Args(MakeMap("foo", "a"), aStruct{}). 187 Rets(aStruct{}, cannotParseAs{"integer", "a"}), 188 }) 189 } 190 191 func TestFromGo(t *testing.T) { 192 Test(t, Fn("FromGo", FromGo), Table{ 193 // BigInt -> int, when in range 194 Args(bigInt(z)).Rets(bigInt(z)), 195 Args(big.NewInt(100)).Rets(100), 196 // BigRat -> BigInt or int, when denominator is 1 197 Args(bigRat(z1 + "/" + z)).Rets(bigRat(z1 + "/" + z)), 198 Args(bigRat(z + "/1")).Rets(bigInt(z)), 199 Args(bigRat("2/1")).Rets(2), 200 // rune -> string 201 Args('x').Rets("x"), 202 203 // Other types don't undergo any conversion 204 Args(nil).Rets(nil), 205 Args(someType{"foo"}).Rets(someType{"foo"}), 206 }) 207 }