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 }