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  }