github.com/google/skylark@v0.0.0-20181101142754-a5f7082aabed/eval_test.go (about) 1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package skylark_test 6 7 import ( 8 "bytes" 9 "fmt" 10 "math" 11 "path/filepath" 12 "strings" 13 "testing" 14 15 "github.com/google/skylark" 16 "github.com/google/skylark/internal/chunkedfile" 17 "github.com/google/skylark/resolve" 18 "github.com/google/skylark/skylarktest" 19 "github.com/google/skylark/syntax" 20 ) 21 22 func init() { 23 // The tests make extensive use of these not-yet-standard features. 24 resolve.AllowLambda = true 25 resolve.AllowNestedDef = true 26 resolve.AllowFloat = true 27 resolve.AllowSet = true 28 resolve.AllowBitwise = true 29 } 30 31 func TestEvalExpr(t *testing.T) { 32 // This is mostly redundant with the new *.sky tests. 33 // TODO(adonovan): move checks into *.sky files and 34 // reduce this to a mere unit test of skylark.Eval. 35 thread := new(skylark.Thread) 36 for _, test := range []struct{ src, want string }{ 37 {`123`, `123`}, 38 {`-1`, `-1`}, 39 {`"a"+"b"`, `"ab"`}, 40 {`1+2`, `3`}, 41 42 // lists 43 {`[]`, `[]`}, 44 {`[1]`, `[1]`}, 45 {`[1,]`, `[1]`}, 46 {`[1, 2]`, `[1, 2]`}, 47 {`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`}, 48 {`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`}, 49 {`[(x, y) for x in [1, 2] for y in [3, 4]]`, 50 `[(1, 3), (1, 4), (2, 3), (2, 4)]`}, 51 {`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`, 52 `[(2, 3), (2, 4)]`}, 53 // tuples 54 {`()`, `()`}, 55 {`(1)`, `1`}, 56 {`(1,)`, `(1,)`}, 57 {`(1, 2)`, `(1, 2)`}, 58 {`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`}, 59 // dicts 60 {`{}`, `{}`}, 61 {`{"a": 1}`, `{"a": 1}`}, 62 {`{"a": 1,}`, `{"a": 1}`}, 63 64 // conditional 65 {`1 if 3 > 2 else 0`, `1`}, 66 {`1 if "foo" else 0`, `1`}, 67 {`1 if "" else 0`, `0`}, 68 69 // indexing 70 {`["a", "b"][0]`, `"a"`}, 71 {`["a", "b"][1]`, `"b"`}, 72 {`("a", "b")[0]`, `"a"`}, 73 {`("a", "b")[1]`, `"b"`}, 74 {`"aΩb"[0]`, `"a"`}, 75 {`"aΩb"[1]`, `"\xce"`}, 76 {`"aΩb"[3]`, `"b"`}, 77 {`{"a": 1}["a"]`, `1`}, 78 {`{"a": 1}["b"]`, `key "b" not in dict`}, 79 {`{}[[]]`, `unhashable type: list`}, 80 {`{"a": 1}[[]]`, `unhashable type: list`}, 81 {`[x for x in range(3)]`, "[0, 1, 2]"}, 82 } { 83 var got string 84 if v, err := skylark.Eval(thread, "<expr>", test.src, nil); err != nil { 85 got = err.Error() 86 } else { 87 got = v.String() 88 } 89 if got != test.want { 90 t.Errorf("eval %s = %s, want %s", test.src, got, test.want) 91 } 92 } 93 } 94 95 func TestExecFile(t *testing.T) { 96 testdata := skylarktest.DataFile("skylark", ".") 97 thread := &skylark.Thread{Load: load} 98 skylarktest.SetReporter(thread, t) 99 for _, file := range []string{ 100 "testdata/assign.sky", 101 "testdata/bool.sky", 102 "testdata/builtins.sky", 103 "testdata/control.sky", 104 "testdata/dict.sky", 105 "testdata/float.sky", 106 "testdata/function.sky", 107 "testdata/int.sky", 108 "testdata/list.sky", 109 "testdata/misc.sky", 110 "testdata/set.sky", 111 "testdata/string.sky", 112 "testdata/tuple.sky", 113 } { 114 filename := filepath.Join(testdata, file) 115 for _, chunk := range chunkedfile.Read(filename, t) { 116 predeclared := skylark.StringDict{ 117 "hasfields": skylark.NewBuiltin("hasfields", newHasFields), 118 "fibonacci": fib{}, 119 } 120 _, err := skylark.ExecFile(thread, filename, chunk.Source, predeclared) 121 switch err := err.(type) { 122 case *skylark.EvalError: 123 found := false 124 for _, fr := range err.Stack() { 125 posn := fr.Position() 126 if posn.Filename() == filename { 127 chunk.GotError(int(posn.Line), err.Error()) 128 found = true 129 break 130 } 131 } 132 if !found { 133 t.Error(err.Backtrace()) 134 } 135 case nil: 136 // success 137 default: 138 t.Error(err) 139 } 140 chunk.Done() 141 } 142 } 143 } 144 145 // A fib is an iterable value representing the infinite Fibonacci sequence. 146 type fib struct{} 147 148 func (t fib) Freeze() {} 149 func (t fib) String() string { return "fib" } 150 func (t fib) Type() string { return "fib" } 151 func (t fib) Truth() skylark.Bool { return true } 152 func (t fib) Hash() (uint32, error) { return 0, fmt.Errorf("fib is unhashable") } 153 func (t fib) Iterate() skylark.Iterator { return &fibIterator{0, 1} } 154 155 type fibIterator struct{ x, y int } 156 157 func (it *fibIterator) Next(p *skylark.Value) bool { 158 *p = skylark.MakeInt(it.x) 159 it.x, it.y = it.y, it.x+it.y 160 return true 161 } 162 func (it *fibIterator) Done() {} 163 164 // load implements the 'load' operation as used in the evaluator tests. 165 func load(thread *skylark.Thread, module string) (skylark.StringDict, error) { 166 if module == "assert.sky" { 167 return skylarktest.LoadAssertModule() 168 } 169 170 // TODO(adonovan): test load() using this execution path. 171 filename := filepath.Join(filepath.Dir(thread.Caller().Position().Filename()), module) 172 return skylark.ExecFile(thread, filename, nil, nil) 173 } 174 175 func newHasFields(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) { 176 return &hasfields{attrs: make(map[string]skylark.Value)}, nil 177 } 178 179 // hasfields is a test-only implementation of HasAttrs. 180 // It permits any field to be set. 181 // Clients will likely want to provide their own implementation, 182 // so we don't have any public implementation. 183 type hasfields struct { 184 attrs skylark.StringDict 185 frozen bool 186 } 187 188 var ( 189 _ skylark.HasAttrs = (*hasfields)(nil) 190 _ skylark.HasBinary = (*hasfields)(nil) 191 ) 192 193 func (hf *hasfields) String() string { return "hasfields" } 194 func (hf *hasfields) Type() string { return "hasfields" } 195 func (hf *hasfields) Truth() skylark.Bool { return true } 196 func (hf *hasfields) Hash() (uint32, error) { return 42, nil } 197 198 func (hf *hasfields) Freeze() { 199 if !hf.frozen { 200 hf.frozen = true 201 for _, v := range hf.attrs { 202 v.Freeze() 203 } 204 } 205 } 206 207 func (hf *hasfields) Attr(name string) (skylark.Value, error) { return hf.attrs[name], nil } 208 209 func (hf *hasfields) SetField(name string, val skylark.Value) error { 210 if hf.frozen { 211 return fmt.Errorf("cannot set field on a frozen hasfields") 212 } 213 hf.attrs[name] = val 214 return nil 215 } 216 217 func (hf *hasfields) AttrNames() []string { 218 names := make([]string, 0, len(hf.attrs)) 219 for key := range hf.attrs { 220 names = append(names, key) 221 } 222 return names 223 } 224 225 func (hf *hasfields) Binary(op syntax.Token, y skylark.Value, side skylark.Side) (skylark.Value, error) { 226 // This method exists so we can exercise 'list += x' 227 // where x is not Iterable but defines list+x. 228 if op == syntax.PLUS { 229 if _, ok := y.(*skylark.List); ok { 230 return skylark.MakeInt(42), nil // list+hasfields is 42 231 } 232 } 233 return nil, nil 234 } 235 236 func TestParameterPassing(t *testing.T) { 237 const filename = "parameters.go" 238 const src = ` 239 def a(): 240 return 241 def b(a, b): 242 return a, b 243 def c(a, b=42): 244 return a, b 245 def d(*args): 246 return args 247 def e(**kwargs): 248 return kwargs 249 def f(a, b=42, *args, **kwargs): 250 return a, b, args, kwargs 251 ` 252 253 thread := new(skylark.Thread) 254 globals, err := skylark.ExecFile(thread, filename, src, nil) 255 if err != nil { 256 t.Fatal(err) 257 } 258 259 for _, test := range []struct{ src, want string }{ 260 {`a()`, `None`}, 261 {`a(1)`, `function a takes no arguments (1 given)`}, 262 {`b()`, `function b takes exactly 2 arguments (0 given)`}, 263 {`b(1)`, `function b takes exactly 2 arguments (1 given)`}, 264 {`b(1, 2)`, `(1, 2)`}, 265 {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable 266 {`b(1, 2, 3)`, `function b takes exactly 2 arguments (3 given)`}, 267 {`b(1, b=2)`, `(1, 2)`}, 268 {`b(1, a=2)`, `function b got multiple values for keyword argument "a"`}, 269 {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`}, 270 {`b(a=1, b=2)`, `(1, 2)`}, 271 {`b(b=1, a=2)`, `(2, 1)`}, 272 {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`}, 273 {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`}, 274 {`c()`, `function c takes at least 1 argument (0 given)`}, 275 {`c(1)`, `(1, 42)`}, 276 {`c(1, 2)`, `(1, 2)`}, 277 {`c(1, 2, 3)`, `function c takes at most 2 arguments (3 given)`}, 278 {`c(1, b=2)`, `(1, 2)`}, 279 {`c(1, a=2)`, `function c got multiple values for keyword argument "a"`}, 280 {`c(a=1, b=2)`, `(1, 2)`}, 281 {`c(b=1, a=2)`, `(2, 1)`}, 282 {`d()`, `()`}, 283 {`d(1)`, `(1,)`}, 284 {`d(1, 2)`, `(1, 2)`}, 285 {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`}, 286 {`d(args=[])`, `function d got an unexpected keyword argument "args"`}, 287 {`e()`, `{}`}, 288 {`e(1)`, `function e takes exactly 0 arguments (1 given)`}, 289 {`e(k=1)`, `{"k": 1}`}, 290 {`e(kwargs={})`, `{"kwargs": {}}`}, 291 {`f()`, `function f takes at least 1 argument (0 given)`}, 292 {`f(0)`, `(0, 42, (), {})`}, 293 {`f(0)`, `(0, 42, (), {})`}, 294 {`f(0, 1)`, `(0, 1, (), {})`}, 295 {`f(0, 1, 2)`, `(0, 1, (2,), {})`}, 296 {`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`}, 297 {`f(a=0)`, `(0, 42, (), {})`}, 298 {`f(0, b=1)`, `(0, 1, (), {})`}, 299 {`f(0, a=1)`, `function f got multiple values for keyword argument "a"`}, 300 {`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`}, 301 {`f(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, // github.com/google/skylark/issues/135 302 `(0, 1, (3, 4), {"x": 2, "y": 5, "z": 6})`}, 303 } { 304 var got string 305 if v, err := skylark.Eval(thread, "<expr>", test.src, globals); err != nil { 306 got = err.Error() 307 } else { 308 got = v.String() 309 } 310 if got != test.want { 311 t.Errorf("eval %s = %s, want %s", test.src, got, test.want) 312 } 313 } 314 } 315 316 // TestPrint ensures that the Skylark print function calls 317 // Thread.Print, if provided. 318 func TestPrint(t *testing.T) { 319 const src = ` 320 print("hello") 321 def f(): print("world") 322 f() 323 ` 324 buf := new(bytes.Buffer) 325 print := func(thread *skylark.Thread, msg string) { 326 caller := thread.Caller() 327 fmt.Fprintf(buf, "%s: %s: %s\n", 328 caller.Position(), caller.Callable().Name(), msg) 329 } 330 thread := &skylark.Thread{Print: print} 331 if _, err := skylark.ExecFile(thread, "foo.go", src, nil); err != nil { 332 t.Fatal(err) 333 } 334 want := "foo.go:2: <toplevel>: hello\n" + 335 "foo.go:3: f: world\n" 336 if got := buf.String(); got != want { 337 t.Errorf("output was %s, want %s", got, want) 338 } 339 } 340 341 func Benchmark(b *testing.B) { 342 testdata := skylarktest.DataFile("skylark", ".") 343 thread := new(skylark.Thread) 344 for _, file := range []string{ 345 "testdata/benchmark.sky", 346 // ... 347 } { 348 filename := filepath.Join(testdata, file) 349 350 // Evaluate the file once. 351 globals, err := skylark.ExecFile(thread, filename, nil, nil) 352 if err != nil { 353 reportEvalError(b, err) 354 } 355 356 // Repeatedly call each global function named bench_* as a benchmark. 357 for name, value := range globals { 358 if fn, ok := value.(*skylark.Function); ok && strings.HasPrefix(name, "bench_") { 359 b.Run(name, func(b *testing.B) { 360 for i := 0; i < b.N; i++ { 361 _, err := skylark.Call(thread, fn, nil, nil) 362 if err != nil { 363 reportEvalError(b, err) 364 } 365 } 366 }) 367 } 368 } 369 } 370 } 371 372 func reportEvalError(tb testing.TB, err error) { 373 if err, ok := err.(*skylark.EvalError); ok { 374 tb.Fatal(err.Backtrace()) 375 } 376 tb.Fatal(err) 377 } 378 379 // TestInt exercises the Int.Int64 and Int.Uint64 methods. 380 // If we can move their logic into math/big, delete this test. 381 func TestInt(t *testing.T) { 382 one := skylark.MakeInt(1) 383 384 for _, test := range []struct { 385 i skylark.Int 386 wantInt64 string 387 wantUint64 string 388 }{ 389 {skylark.MakeInt64(math.MinInt64).Sub(one), "error", "error"}, 390 {skylark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"}, 391 {skylark.MakeInt64(-1), "-1", "error"}, 392 {skylark.MakeInt64(0), "0", "0"}, 393 {skylark.MakeInt64(1), "1", "1"}, 394 {skylark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"}, 395 {skylark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"}, 396 {skylark.MakeUint64(math.MaxUint64).Add(one), "error", "error"}, 397 } { 398 gotInt64, gotUint64 := "error", "error" 399 if i, ok := test.i.Int64(); ok { 400 gotInt64 = fmt.Sprint(i) 401 } 402 if u, ok := test.i.Uint64(); ok { 403 gotUint64 = fmt.Sprint(u) 404 } 405 if gotInt64 != test.wantInt64 { 406 t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64) 407 } 408 if gotUint64 != test.wantUint64 { 409 t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64) 410 } 411 } 412 } 413 414 func TestBacktrace(t *testing.T) { 415 // This test ensures continuity of the stack of active Skylark 416 // functions, including propagation through built-ins such as 'min' 417 // (though min does not itself appear in the stack). 418 const src = ` 419 def f(x): return 1//x 420 def g(x): f(x) 421 def h(): return min([1, 2, 0], key=g) 422 def i(): return h() 423 i() 424 ` 425 thread := new(skylark.Thread) 426 _, err := skylark.ExecFile(thread, "crash.sky", src, nil) 427 switch err := err.(type) { 428 case *skylark.EvalError: 429 got := err.Backtrace() 430 // Compiled code currently has no column information. 431 const want = `Traceback (most recent call last): 432 crash.sky:6: in <toplevel> 433 crash.sky:5: in i 434 crash.sky:4: in h 435 <builtin>:1: in min 436 crash.sky:3: in g 437 crash.sky:2: in f 438 Error: floored division by zero` 439 if got != want { 440 t.Errorf("error was %s, want %s", got, want) 441 } 442 case nil: 443 t.Error("ExecFile succeeded unexpectedly") 444 default: 445 t.Errorf("ExecFile failed with %v, wanted *EvalError", err) 446 } 447 } 448 449 // TestRepeatedExec parses and resolves a file syntax tree once then 450 // executes it repeatedly with different values of its predeclared variables. 451 func TestRepeatedExec(t *testing.T) { 452 predeclared := skylark.StringDict{"x": skylark.None} 453 _, prog, err := skylark.SourceProgram("repeat.sky", "y = 2 * x", predeclared.Has) 454 if err != nil { 455 t.Fatal(err) 456 } 457 458 for _, test := range []struct { 459 x, want skylark.Value 460 }{ 461 {x: skylark.MakeInt(42), want: skylark.MakeInt(84)}, 462 {x: skylark.String("mur"), want: skylark.String("murmur")}, 463 {x: skylark.Tuple{skylark.None}, want: skylark.Tuple{skylark.None, skylark.None}}, 464 } { 465 predeclared["x"] = test.x // update the values in dictionary 466 thread := new(skylark.Thread) 467 if globals, err := prog.Init(thread, predeclared); err != nil { 468 t.Errorf("x=%v: %v", test.x, err) // exec error 469 } else if eq, err := skylark.Equal(globals["y"], test.want); err != nil { 470 t.Errorf("x=%v: %v", test.x, err) // comparison error 471 } else if !eq { 472 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want) 473 } 474 } 475 } 476 477 // TestUnpackUserDefined tests that user-defined 478 // implementations of skylark.Value may be unpacked. 479 func TestUnpackUserDefined(t *testing.T) { 480 // success 481 want := new(hasfields) 482 var x *hasfields 483 if err := skylark.UnpackArgs("unpack", skylark.Tuple{want}, nil, "x", &x); err != nil { 484 t.Errorf("UnpackArgs failed: %v", err) 485 } 486 if x != want { 487 t.Errorf("for x, got %v, want %v", x, want) 488 } 489 490 // failure 491 err := skylark.UnpackArgs("unpack", skylark.Tuple{skylark.MakeInt(42)}, nil, "x", &x) 492 if want := "unpack: for parameter 1: got int, want hasfields"; fmt.Sprint(err) != want { 493 t.Errorf("unpack args error = %q, want %q", err, want) 494 } 495 }