src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/vals/conversion_test.go (about)

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