github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/go_fn_test.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"math/big"
     6  	"reflect"
     7  	"testing"
     8  	"unsafe"
     9  
    10  	"src.elv.sh/pkg/eval/errs"
    11  
    12  	"src.elv.sh/pkg/eval/vals"
    13  	"src.elv.sh/pkg/persistent/hash"
    14  )
    15  
    16  func TestGoFnAsValue(t *testing.T) {
    17  	fn1 := NewGoFn("fn1", func() {})
    18  	fn2 := NewGoFn("fn2", func() {})
    19  	vals.TestValue(t, fn1).
    20  		Kind("fn").
    21  		Hash(hash.Pointer(unsafe.Pointer(fn1.(*goFn)))).
    22  		Equal(fn1).
    23  		NotEqual(fn2).
    24  		Repr("<builtin fn1>")
    25  }
    26  
    27  type testOptions struct {
    28  	Foo string
    29  	Bar string
    30  }
    31  
    32  func (o *testOptions) SetDefaultOptions() { o.Bar = "default" }
    33  
    34  // TODO: Break down this test into multiple small ones, and test errors more
    35  // strictly.
    36  
    37  func TestGoFnCall(t *testing.T) {
    38  	theFrame := new(Frame)
    39  	theOptions := map[string]interface{}{}
    40  
    41  	var f Callable
    42  	callGood := func(fm *Frame, args []interface{}, opts map[string]interface{}) {
    43  		t.Helper()
    44  		err := f.Call(fm, args, opts)
    45  		if err != nil {
    46  			t.Errorf("Failed to call f: %v", err)
    47  		}
    48  	}
    49  	callBad := func(fm *Frame, args []interface{}, opts map[string]interface{}, wantErr error) {
    50  		t.Helper()
    51  		err := f.Call(fm, args, opts)
    52  		if !matchErr(wantErr, err) {
    53  			t.Errorf("Calling f didn't return error")
    54  		}
    55  	}
    56  
    57  	// *Frame parameter gets the Frame.
    58  	f = NewGoFn("f", func(f *Frame) {
    59  		if f != theFrame {
    60  			t.Errorf("*Frame parameter doesn't get current frame")
    61  		}
    62  	})
    63  	callGood(theFrame, nil, theOptions)
    64  
    65  	// RawOptions parameter gets options.
    66  	f = NewGoFn("f", func(opts RawOptions) {
    67  		if opts["foo"] != "bar" {
    68  			t.Errorf("RawOptions parameter doesn't get options")
    69  		}
    70  	})
    71  	callGood(theFrame, nil, RawOptions{"foo": "bar"})
    72  
    73  	// ScanOptions parameters gets scanned options.
    74  	f = NewGoFn("f", func(opts testOptions) {
    75  		if opts.Foo != "bar" {
    76  			t.Errorf("ScanOptions parameter doesn't get options")
    77  		}
    78  		if opts.Bar != "default" {
    79  			t.Errorf("ScanOptions parameter doesn't use default value")
    80  		}
    81  	})
    82  	callGood(theFrame, nil, RawOptions{"foo": "bar"})
    83  
    84  	// Combination of Frame and RawOptions.
    85  	f = NewGoFn("f", func(f *Frame, opts RawOptions) {
    86  		if f != theFrame {
    87  			t.Errorf("*Frame parameter doesn't get current frame")
    88  		}
    89  		if opts["foo"] != "bar" {
    90  			t.Errorf("RawOptions parameter doesn't get options")
    91  		}
    92  	})
    93  	callGood(theFrame, nil, RawOptions{"foo": "bar"})
    94  
    95  	// Argument passing.
    96  	f = NewGoFn("f", func(x, y string) {
    97  		if x != "lorem" {
    98  			t.Errorf("Argument x not passed")
    99  		}
   100  		if y != "ipsum" {
   101  			t.Errorf("Argument y not passed")
   102  		}
   103  	})
   104  	callGood(theFrame, []interface{}{"lorem", "ipsum"}, theOptions)
   105  
   106  	// Variadic arguments.
   107  	f = NewGoFn("f", func(x ...string) {
   108  		if len(x) != 2 || x[0] != "lorem" || x[1] != "ipsum" {
   109  			t.Errorf("Variadic argument not passed")
   110  		}
   111  	})
   112  	callGood(theFrame, []interface{}{"lorem", "ipsum"}, theOptions)
   113  
   114  	// Conversion into int and float64.
   115  	f = NewGoFn("f", func(i int, f float64) {
   116  		if i != 314 {
   117  			t.Errorf("Integer argument i not passed")
   118  		}
   119  		if f != 1.25 {
   120  			t.Errorf("Float argument f not passed")
   121  		}
   122  	})
   123  	callGood(theFrame, []interface{}{"314", "1.25"}, theOptions)
   124  
   125  	// Conversion of supplied inputs.
   126  	f = NewGoFn("f", func(i Inputs) {
   127  		var values []interface{}
   128  		i(func(x interface{}) {
   129  			values = append(values, x)
   130  		})
   131  		if len(values) != 2 || values[0] != "foo" || values[1] != "bar" {
   132  			t.Errorf("Inputs parameter didn't get supplied inputs")
   133  		}
   134  	})
   135  	callGood(theFrame, []interface{}{vals.MakeList("foo", "bar")}, theOptions)
   136  
   137  	// Conversion of implicit inputs.
   138  	inFrame := &Frame{ports: make([]*Port, 3)}
   139  	ch := make(chan interface{}, 10)
   140  	ch <- "foo"
   141  	ch <- "bar"
   142  	close(ch)
   143  	inFrame.ports[0] = &Port{Chan: ch}
   144  	f = NewGoFn("f", func(i Inputs) {
   145  		var values []interface{}
   146  		i(func(x interface{}) {
   147  			values = append(values, x)
   148  		})
   149  		if len(values) != 2 || values[0] != "foo" || values[1] != "bar" {
   150  			t.Errorf("Inputs parameter didn't get implicit inputs")
   151  		}
   152  	})
   153  	callGood(inFrame, []interface{}{vals.MakeList("foo", "bar")}, theOptions)
   154  
   155  	// Outputting of return values.
   156  	outFrame := &Frame{ports: make([]*Port, 3)}
   157  	ch = make(chan interface{}, 10)
   158  	outFrame.ports[1] = &Port{Chan: ch}
   159  	f = NewGoFn("f", func() string { return "ret" })
   160  	callGood(outFrame, nil, theOptions)
   161  	select {
   162  	case ret := <-ch:
   163  		if ret != "ret" {
   164  			t.Errorf("Output is not the same as return value")
   165  		}
   166  	default:
   167  		t.Errorf("Return value is not outputted")
   168  	}
   169  
   170  	// Conversion of return values.
   171  	f = NewGoFn("f", func() *big.Int { return big.NewInt(314) })
   172  	callGood(outFrame, nil, theOptions)
   173  	select {
   174  	case ret := <-ch:
   175  		if ret != 314 {
   176  			t.Errorf("Return value is not converted to int")
   177  		}
   178  	default:
   179  		t.Errorf("Return value is not outputted")
   180  	}
   181  
   182  	// Passing of error return value.
   183  	theError := errors.New("the error")
   184  	f = NewGoFn("f", func() (string, error) {
   185  		return "x", theError
   186  	})
   187  	if f.Call(outFrame, nil, theOptions) != theError {
   188  		t.Errorf("Returned error is not passed")
   189  	}
   190  	select {
   191  	case <-ch:
   192  		t.Errorf("Return value is outputted when error is not nil")
   193  	default:
   194  	}
   195  
   196  	// Too many arguments.
   197  	f = NewGoFn("f", func() {
   198  		t.Errorf("Function called when there are too many arguments")
   199  	})
   200  	callBad(theFrame, []interface{}{"x"}, theOptions, errs.ArityMismatch{
   201  		What: "arguments here", ValidLow: 0, ValidHigh: 0, Actual: 1})
   202  
   203  	// Too few arguments.
   204  	f = NewGoFn("f", func(x string) {
   205  		t.Errorf("Function called when there are too few arguments")
   206  	})
   207  	callBad(theFrame, nil, theOptions, errs.ArityMismatch{
   208  		What: "arguments here", ValidLow: 1, ValidHigh: 1, Actual: 0})
   209  	f = NewGoFn("f", func(x string, y ...string) {
   210  		t.Errorf("Function called when there are too few arguments")
   211  	})
   212  	callBad(theFrame, nil, theOptions, errs.ArityMismatch{
   213  		What: "arguments here", ValidLow: 1, ValidHigh: -1, Actual: 0})
   214  
   215  	// Options when the function does not accept options.
   216  	f = NewGoFn("f", func() {
   217  		t.Errorf("Function called when there are extra options")
   218  	})
   219  	callBad(theFrame, nil, RawOptions{"foo": "bar"}, ErrNoOptAccepted)
   220  
   221  	// Wrong argument type.
   222  	f = NewGoFn("f", func(x string) {
   223  		t.Errorf("Function called when arguments have wrong type")
   224  	})
   225  	callBad(theFrame, []interface{}{1}, theOptions, anyError{})
   226  
   227  	// Wrong argument type: cannot convert to int.
   228  	f = NewGoFn("f", func(x int) {
   229  		t.Errorf("Function called when arguments have wrong type")
   230  	})
   231  	callBad(theFrame, []interface{}{"x"}, theOptions, anyError{})
   232  
   233  	// Wrong argument type: cannot convert to float64.
   234  	f = NewGoFn("f", func(x float64) {
   235  		t.Errorf("Function called when arguments have wrong type")
   236  	})
   237  	callBad(theFrame, []interface{}{"x"}, theOptions, anyError{})
   238  
   239  	// Invalid option; regression test for #958.
   240  	f = NewGoFn("f", func(opts testOptions) {})
   241  	callBad(theFrame, nil, RawOptions{"bad": ""}, anyError{})
   242  
   243  	// Invalid option type; regression test for #958.
   244  	f = NewGoFn("f", func(opts testOptions) {})
   245  	callBad(theFrame, nil, RawOptions{"foo": vals.EmptyList}, anyError{})
   246  }
   247  
   248  type anyError struct{}
   249  
   250  func (anyError) Error() string { return "any error" }
   251  
   252  func matchErr(want, got error) bool {
   253  	if (want == anyError{}) {
   254  		return got != nil
   255  	}
   256  	return reflect.DeepEqual(want, got)
   257  }