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 5package starlark_test 6 7import ( 8 "bytes" 9 "fmt" 10 "math" 11 "os/exec" 12 "path/filepath" 13 "reflect" 14 "sort" 15 "strings" 16 "testing" 17 18 "go.starlark.net/internal/chunkedfile" 19 "go.starlark.net/resolve" 20 "go.starlark.net/starlark" 21 "go.starlark.net/starlarkjson" 22 "go.starlark.net/starlarkstruct" 23 "go.starlark.net/starlarktest" 24 "go.starlark.net/syntax" 25) 26 27// A test may enable non-standard options by containing (e.g.) "option:recursion". 28func setOptions(src string) { 29 resolve.AllowGlobalReassign = option(src, "globalreassign") 30 resolve.LoadBindsGlobally = option(src, "loadbindsglobally") 31 resolve.AllowRecursion = option(src, "recursion") 32 resolve.AllowSet = option(src, "set") 33} 34 35func option(chunk, name string) bool { 36 return strings.Contains(chunk, "option:"+name) 37} 38 39// Wrapper is the type of errors with an Unwrap method; see https://golang.org/pkg/errors. 40type Wrapper interface { 41 Unwrap() error 42} 43 44func TestEvalExpr(t *testing.T) { 45 // This is mostly redundant with the new *.star tests. 46 // TODO(adonovan): move checks into *.star files and 47 // reduce this to a mere unit test of starlark.Eval. 48 thread := new(starlark.Thread) 49 for _, test := range []struct{ src, want string }{ 50 {`123`, `123`}, 51 {`-1`, `-1`}, 52 {`"a"+"b"`, `"ab"`}, 53 {`1+2`, `3`}, 54 55 // lists 56 {`[]`, `[]`}, 57 {`[1]`, `[1]`}, 58 {`[1,]`, `[1]`}, 59 {`[1, 2]`, `[1, 2]`}, 60 {`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`}, 61 {`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`}, 62 {`[(x, y) for x in [1, 2] for y in [3, 4]]`, 63 `[(1, 3), (1, 4), (2, 3), (2, 4)]`}, 64 {`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`, 65 `[(2, 3), (2, 4)]`}, 66 // tuples 67 {`()`, `()`}, 68 {`(1)`, `1`}, 69 {`(1,)`, `(1,)`}, 70 {`(1, 2)`, `(1, 2)`}, 71 {`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`}, 72 {`1, 2`, `(1, 2)`}, 73 // dicts 74 {`{}`, `{}`}, 75 {`{"a": 1}`, `{"a": 1}`}, 76 {`{"a": 1,}`, `{"a": 1}`}, 77 78 // conditional 79 {`1 if 3 > 2 else 0`, `1`}, 80 {`1 if "foo" else 0`, `1`}, 81 {`1 if "" else 0`, `0`}, 82 83 // indexing 84 {`["a", "b"][0]`, `"a"`}, 85 {`["a", "b"][1]`, `"b"`}, 86 {`("a", "b")[0]`, `"a"`}, 87 {`("a", "b")[1]`, `"b"`}, 88 {`"aΩb"[0]`, `"a"`}, 89 {`"aΩb"[1]`, `"\xce"`}, 90 {`"aΩb"[3]`, `"b"`}, 91 {`{"a": 1}["a"]`, `1`}, 92 {`{"a": 1}["b"]`, `key "b" not in dict`}, 93 {`{}[[]]`, `unhashable type: list`}, 94 {`{"a": 1}[[]]`, `unhashable type: list`}, 95 {`[x for x in range(3)]`, "[0, 1, 2]"}, 96 } { 97 var got string 98 if v, err := starlark.Eval(thread, "<expr>", test.src, nil); err != nil { 99 got = err.Error() 100 } else { 101 got = v.String() 102 } 103 if got != test.want { 104 t.Errorf("eval %s = %s, want %s", test.src, got, test.want) 105 } 106 } 107} 108 109func TestExecFile(t *testing.T) { 110 defer setOptions("") 111 testdata := starlarktest.DataFile("starlark", ".") 112 thread := &starlark.Thread{Load: load} 113 starlarktest.SetReporter(thread, t) 114 for _, file := range []string{ 115 "testdata/assign.star", 116 "testdata/bool.star", 117 "testdata/builtins.star", 118 "testdata/bytes.star", 119 "testdata/control.star", 120 "testdata/dict.star", 121 "testdata/float.star", 122 "testdata/function.star", 123 "testdata/int.star", 124 "testdata/json.star", 125 "testdata/list.star", 126 "testdata/misc.star", 127 "testdata/set.star", 128 "testdata/string.star", 129 "testdata/tuple.star", 130 "testdata/recursion.star", 131 "testdata/module.star", 132 } { 133 filename := filepath.Join(testdata, file) 134 for _, chunk := range chunkedfile.Read(filename, t) { 135 predeclared := starlark.StringDict{ 136 "hasfields": starlark.NewBuiltin("hasfields", newHasFields), 137 "fibonacci": fib{}, 138 "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), 139 } 140 141 setOptions(chunk.Source) 142 resolve.AllowLambda = true // used extensively 143 144 _, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared) 145 switch err := err.(type) { 146 case *starlark.EvalError: 147 found := false 148 for i := range err.CallStack { 149 posn := err.CallStack.At(i).Pos 150 if posn.Filename() == filename { 151 chunk.GotError(int(posn.Line), err.Error()) 152 found = true 153 break 154 } 155 } 156 if !found { 157 t.Error(err.Backtrace()) 158 } 159 case nil: 160 // success 161 default: 162 t.Errorf("\n%s", err) 163 } 164 chunk.Done() 165 } 166 } 167} 168 169// A fib is an iterable value representing the infinite Fibonacci sequence. 170type fib struct{} 171 172func (t fib) Freeze() {} 173func (t fib) String() string { return "fib" } 174func (t fib) Type() string { return "fib" } 175func (t fib) Truth() starlark.Bool { return true } 176func (t fib) Hash() (uint32, error) { return 0, fmt.Errorf("fib is unhashable") } 177func (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} } 178 179type fibIterator struct{ x, y int } 180 181func (it *fibIterator) Next(p *starlark.Value) bool { 182 *p = starlark.MakeInt(it.x) 183 it.x, it.y = it.y, it.x+it.y 184 return true 185} 186func (it *fibIterator) Done() {} 187 188// load implements the 'load' operation as used in the evaluator tests. 189func load(thread *starlark.Thread, module string) (starlark.StringDict, error) { 190 if module == "assert.star" { 191 return starlarktest.LoadAssertModule() 192 } 193 if module == "json.star" { 194 return starlark.StringDict{"json": starlarkjson.Module}, nil 195 } 196 197 // TODO(adonovan): test load() using this execution path. 198 filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module) 199 return starlark.ExecFile(thread, filename, nil, nil) 200} 201 202func newHasFields(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 203 if len(args)+len(kwargs) > 0 { 204 return nil, fmt.Errorf("%s: unexpected arguments", b.Name()) 205 } 206 return &hasfields{attrs: make(map[string]starlark.Value)}, nil 207} 208 209// hasfields is a test-only implementation of HasAttrs. 210// It permits any field to be set. 211// Clients will likely want to provide their own implementation, 212// so we don't have any public implementation. 213type hasfields struct { 214 attrs starlark.StringDict 215 frozen bool 216} 217 218var ( 219 _ starlark.HasAttrs = (*hasfields)(nil) 220 _ starlark.HasBinary = (*hasfields)(nil) 221) 222 223func (hf *hasfields) String() string { return "hasfields" } 224func (hf *hasfields) Type() string { return "hasfields" } 225func (hf *hasfields) Truth() starlark.Bool { return true } 226func (hf *hasfields) Hash() (uint32, error) { return 42, nil } 227 228func (hf *hasfields) Freeze() { 229 if !hf.frozen { 230 hf.frozen = true 231 for _, v := range hf.attrs { 232 v.Freeze() 233 } 234 } 235} 236 237func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil } 238 239func (hf *hasfields) SetField(name string, val starlark.Value) error { 240 if hf.frozen { 241 return fmt.Errorf("cannot set field on a frozen hasfields") 242 } 243 if strings.HasPrefix(name, "no") { // for testing 244 return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name)) 245 } 246 hf.attrs[name] = val 247 return nil 248} 249 250func (hf *hasfields) AttrNames() []string { 251 names := make([]string, 0, len(hf.attrs)) 252 for key := range hf.attrs { 253 names = append(names, key) 254 } 255 sort.Strings(names) 256 return names 257} 258 259func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) { 260 // This method exists so we can exercise 'list += x' 261 // where x is not Iterable but defines list+x. 262 if op == syntax.PLUS { 263 if _, ok := y.(*starlark.List); ok { 264 return starlark.MakeInt(42), nil // list+hasfields is 42 265 } 266 } 267 return nil, nil 268} 269 270func TestParameterPassing(t *testing.T) { 271 const filename = "parameters.go" 272 const src = ` 273def a(): 274 return 275def b(a, b): 276 return a, b 277def c(a, b=42): 278 return a, b 279def d(*args): 280 return args 281def e(**kwargs): 282 return kwargs 283def f(a, b=42, *args, **kwargs): 284 return a, b, args, kwargs 285def g(a, b=42, *args, c=123, **kwargs): 286 return a, b, args, c, kwargs 287def h(a, b=42, *, c=123, **kwargs): 288 return a, b, c, kwargs 289def i(a, b=42, *, c, d=123, e, **kwargs): 290 return a, b, c, d, e, kwargs 291def j(a, b=42, *args, c, d=123, e, **kwargs): 292 return a, b, args, c, d, e, kwargs 293` 294 295 thread := new(starlark.Thread) 296 globals, err := starlark.ExecFile(thread, filename, src, nil) 297 if err != nil { 298 t.Fatal(err) 299 } 300 301 // All errors are dynamic; see resolver for static errors. 302 for _, test := range []struct{ src, want string }{ 303 // a() 304 {`a()`, `None`}, 305 {`a(1)`, `function a accepts no arguments (1 given)`}, 306 307 // b(a, b) 308 {`b()`, `function b missing 2 arguments (a, b)`}, 309 {`b(1)`, `function b missing 1 argument (b)`}, 310 {`b(a=1)`, `function b missing 1 argument (b)`}, 311 {`b(b=1)`, `function b missing 1 argument (a)`}, 312 {`b(1, 2)`, `(1, 2)`}, 313 {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable 314 {`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`}, 315 {`b(1, b=2)`, `(1, 2)`}, 316 {`b(1, a=2)`, `function b got multiple values for parameter "a"`}, 317 {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`}, 318 {`b(a=1, b=2)`, `(1, 2)`}, 319 {`b(b=1, a=2)`, `(2, 1)`}, 320 {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`}, 321 {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`}, 322 323 // c(a, b=42) 324 {`c()`, `function c missing 1 argument (a)`}, 325 {`c(1)`, `(1, 42)`}, 326 {`c(1, 2)`, `(1, 2)`}, 327 {`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`}, 328 {`c(1, b=2)`, `(1, 2)`}, 329 {`c(1, a=2)`, `function c got multiple values for parameter "a"`}, 330 {`c(a=1, b=2)`, `(1, 2)`}, 331 {`c(b=1, a=2)`, `(2, 1)`}, 332 333 // d(*args) 334 {`d()`, `()`}, 335 {`d(1)`, `(1,)`}, 336 {`d(1, 2)`, `(1, 2)`}, 337 {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`}, 338 {`d(args=[])`, `function d got an unexpected keyword argument "args"`}, 339 340 // e(**kwargs) 341 {`e()`, `{}`}, 342 {`e(1)`, `function e accepts 0 positional arguments (1 given)`}, 343 {`e(k=1)`, `{"k": 1}`}, 344 {`e(kwargs={})`, `{"kwargs": {}}`}, 345 346 // f(a, b=42, *args, **kwargs) 347 {`f()`, `function f missing 1 argument (a)`}, 348 {`f(0)`, `(0, 42, (), {})`}, 349 {`f(0)`, `(0, 42, (), {})`}, 350 {`f(0, 1)`, `(0, 1, (), {})`}, 351 {`f(0, 1, 2)`, `(0, 1, (2,), {})`}, 352 {`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`}, 353 {`f(a=0)`, `(0, 42, (), {})`}, 354 {`f(0, b=1)`, `(0, 1, (), {})`}, 355 {`f(0, a=1)`, `function f got multiple values for parameter "a"`}, 356 {`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`}, 357 358 // g(a, b=42, *args, c=123, **kwargs) 359 {`g()`, `function g missing 1 argument (a)`}, 360 {`g(0)`, `(0, 42, (), 123, {})`}, 361 {`g(0, 1)`, `(0, 1, (), 123, {})`}, 362 {`g(0, 1, 2)`, `(0, 1, (2,), 123, {})`}, 363 {`g(0, 1, 2, 3)`, `(0, 1, (2, 3), 123, {})`}, 364 {`g(a=0)`, `(0, 42, (), 123, {})`}, 365 {`g(0, b=1)`, `(0, 1, (), 123, {})`}, 366 {`g(0, a=1)`, `function g got multiple values for parameter "a"`}, 367 {`g(0, b=1, c=2, d=3)`, `(0, 1, (), 2, {"d": 3})`}, 368 369 // h(a, b=42, *, c=123, **kwargs) 370 {`h()`, `function h missing 1 argument (a)`}, 371 {`h(0)`, `(0, 42, 123, {})`}, 372 {`h(0, 1)`, `(0, 1, 123, {})`}, 373 {`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`}, 374 {`h(a=0)`, `(0, 42, 123, {})`}, 375 {`h(0, b=1)`, `(0, 1, 123, {})`}, 376 {`h(0, a=1)`, `function h got multiple values for parameter "a"`}, 377 {`h(0, b=1, c=2)`, `(0, 1, 2, {})`}, 378 {`h(0, b=1, d=2)`, `(0, 1, 123, {"d": 2})`}, 379 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`}, 380 381 // i(a, b=42, *, c, d=123, e, **kwargs) 382 {`i()`, `function i missing 3 arguments (a, c, e)`}, 383 {`i(0)`, `function i missing 2 arguments (c, e)`}, 384 {`i(0, 1)`, `function i missing 2 arguments (c, e)`}, 385 {`i(0, 1, 2)`, `function i accepts at most 2 positional arguments (3 given)`}, 386 {`i(0, 1, e=2)`, `function i missing 1 argument (c)`}, 387 {`i(0, 1, 2, 3)`, `function i accepts at most 2 positional arguments (4 given)`}, 388 {`i(a=0)`, `function i missing 2 arguments (c, e)`}, 389 {`i(0, b=1)`, `function i missing 2 arguments (c, e)`}, 390 {`i(0, a=1)`, `function i got multiple values for parameter "a"`}, 391 {`i(0, b=1, c=2)`, `function i missing 1 argument (e)`}, 392 {`i(0, b=1, d=2)`, `function i missing 2 arguments (c, e)`}, 393 {`i(0, b=1, c=2, d=3)`, `function i missing 1 argument (e)`}, 394 {`i(0, b=1, c=2, d=3, e=4)`, `(0, 1, 2, 3, 4, {})`}, 395 {`i(0, 1, b=1, c=2, d=3, e=4)`, `function i got multiple values for parameter "b"`}, 396 397 // j(a, b=42, *args, c, d=123, e, **kwargs) 398 {`j()`, `function j missing 3 arguments (a, c, e)`}, 399 {`j(0)`, `function j missing 2 arguments (c, e)`}, 400 {`j(0, 1)`, `function j missing 2 arguments (c, e)`}, 401 {`j(0, 1, 2)`, `function j missing 2 arguments (c, e)`}, 402 {`j(0, 1, e=2)`, `function j missing 1 argument (c)`}, 403 {`j(0, 1, 2, 3)`, `function j missing 2 arguments (c, e)`}, 404 {`j(a=0)`, `function j missing 2 arguments (c, e)`}, 405 {`j(0, b=1)`, `function j missing 2 arguments (c, e)`}, 406 {`j(0, a=1)`, `function j got multiple values for parameter "a"`}, 407 {`j(0, b=1, c=2)`, `function j missing 1 argument (e)`}, 408 {`j(0, b=1, d=2)`, `function j missing 2 arguments (c, e)`}, 409 {`j(0, b=1, c=2, d=3)`, `function j missing 1 argument (e)`}, 410 {`j(0, b=1, c=2, d=3, e=4)`, `(0, 1, (), 2, 3, 4, {})`}, 411 {`j(0, 1, b=1, c=2, d=3, e=4)`, `function j got multiple values for parameter "b"`}, 412 {`j(0, 1, 2, c=3, e=4)`, `(0, 1, (2,), 3, 123, 4, {})`}, 413 } { 414 var got string 415 if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil { 416 got = err.Error() 417 } else { 418 got = v.String() 419 } 420 if got != test.want { 421 t.Errorf("eval %s = %s, want %s", test.src, got, test.want) 422 } 423 } 424} 425 426// TestPrint ensures that the Starlark print function calls 427// Thread.Print, if provided. 428func TestPrint(t *testing.T) { 429 const src = ` 430print("hello") 431def f(): print("hello", "world", sep=", ") 432f() 433` 434 buf := new(bytes.Buffer) 435 print := func(thread *starlark.Thread, msg string) { 436 caller := thread.CallFrame(1) 437 fmt.Fprintf(buf, "%s: %s: %s\n", caller.Pos, caller.Name, msg) 438 } 439 thread := &starlark.Thread{Print: print} 440 if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil { 441 t.Fatal(err) 442 } 443 want := "foo.star:2:6: <toplevel>: hello\n" + 444 "foo.star:3:15: f: hello, world\n" 445 if got := buf.String(); got != want { 446 t.Errorf("output was %s, want %s", got, want) 447 } 448} 449 450func reportEvalError(tb testing.TB, err error) { 451 if err, ok := err.(*starlark.EvalError); ok { 452 tb.Fatal(err.Backtrace()) 453 } 454 tb.Fatal(err) 455} 456 457// TestInt exercises the Int.Int64 and Int.Uint64 methods. 458// If we can move their logic into math/big, delete this test. 459func TestInt(t *testing.T) { 460 one := starlark.MakeInt(1) 461 462 for _, test := range []struct { 463 i starlark.Int 464 wantInt64 string 465 wantUint64 string 466 }{ 467 {starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"}, 468 {starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"}, 469 {starlark.MakeInt64(-1), "-1", "error"}, 470 {starlark.MakeInt64(0), "0", "0"}, 471 {starlark.MakeInt64(1), "1", "1"}, 472 {starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"}, 473 {starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"}, 474 {starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"}, 475 } { 476 gotInt64, gotUint64 := "error", "error" 477 if i, ok := test.i.Int64(); ok { 478 gotInt64 = fmt.Sprint(i) 479 } 480 if u, ok := test.i.Uint64(); ok { 481 gotUint64 = fmt.Sprint(u) 482 } 483 if gotInt64 != test.wantInt64 { 484 t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64) 485 } 486 if gotUint64 != test.wantUint64 { 487 t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64) 488 } 489 } 490} 491 492func backtrace(t *testing.T, err error) string { 493 switch err := err.(type) { 494 case *starlark.EvalError: 495 return err.Backtrace() 496 case nil: 497 t.Fatalf("ExecFile succeeded unexpectedly") 498 default: 499 t.Fatalf("ExecFile failed with %v, wanted *EvalError", err) 500 } 501 panic("unreachable") 502} 503 504func TestBacktrace(t *testing.T) { 505 // This test ensures continuity of the stack of active Starlark 506 // functions, including propagation through built-ins such as 'min'. 507 const src = ` 508def f(x): return 1//x 509def g(x): return f(x) 510def h(): return min([1, 2, 0], key=g) 511def i(): return h() 512i() 513` 514 thread := new(starlark.Thread) 515 _, err := starlark.ExecFile(thread, "crash.star", src, nil) 516 const want = `Traceback (most recent call last): 517 crash.star:6:2: in <toplevel> 518 crash.star:5:18: in i 519 crash.star:4:20: in h 520 <builtin>: in min 521 crash.star:3:19: in g 522 crash.star:2:19: in f 523Error: floored division by zero` 524 if got := backtrace(t, err); got != want { 525 t.Errorf("error was %s, want %s", got, want) 526 } 527 528 // Additionally, ensure that errors originating in 529 // Starlark and/or Go each have an accurate frame. 530 // The topmost frame, if built-in, is not shown, 531 // but the name of the built-in function is shown 532 // as "Error in fn: ...". 533 // 534 // This program fails in Starlark (f) if x==0, 535 // or in Go (string.join) if x is non-zero. 536 const src2 = ` 537def f(): ''.join([1//i]) 538f() 539` 540 for i, want := range []string{ 541 0: `Traceback (most recent call last): 542 crash.star:3:2: in <toplevel> 543 crash.star:2:20: in f 544Error: floored division by zero`, 545 1: `Traceback (most recent call last): 546 crash.star:3:2: in <toplevel> 547 crash.star:2:17: in f 548Error in join: join: in list, want string, got int`, 549 } { 550 globals := starlark.StringDict{"i": starlark.MakeInt(i)} 551 _, err := starlark.ExecFile(thread, "crash.star", src2, globals) 552 if got := backtrace(t, err); got != want { 553 t.Errorf("error was %s, want %s", got, want) 554 } 555 } 556} 557 558func TestLoadBacktrace(t *testing.T) { 559 // This test ensures that load() does NOT preserve stack traces, 560 // but that API callers can get them with Unwrap(). 561 // For discussion, see: 562 // https://github.com/google/starlark-go/pull/244 563 const src = ` 564load('crash.star', 'x') 565` 566 const loadedSrc = ` 567def f(x): 568 return 1 // x 569 570f(0) 571` 572 thread := new(starlark.Thread) 573 thread.Load = func(t *starlark.Thread, module string) (starlark.StringDict, error) { 574 return starlark.ExecFile(new(starlark.Thread), module, loadedSrc, nil) 575 } 576 _, err := starlark.ExecFile(thread, "root.star", src, nil) 577 578 const want = `Traceback (most recent call last): 579 root.star:2:1: in <toplevel> 580Error: cannot load crash.star: floored division by zero` 581 if got := backtrace(t, err); got != want { 582 t.Errorf("error was %s, want %s", got, want) 583 } 584 585 unwrapEvalError := func(err error) *starlark.EvalError { 586 var result *starlark.EvalError 587 for { 588 if evalErr, ok := err.(*starlark.EvalError); ok { 589 result = evalErr 590 } 591 592 // TODO: use errors.Unwrap when go >=1.13 is everywhere. 593 wrapper, isWrapper := err.(Wrapper) 594 if !isWrapper { 595 break 596 } 597 err = wrapper.Unwrap() 598 } 599 return result 600 } 601 602 unwrappedErr := unwrapEvalError(err) 603 const wantUnwrapped = `Traceback (most recent call last): 604 crash.star:5:2: in <toplevel> 605 crash.star:3:12: in f 606Error: floored division by zero` 607 if got := backtrace(t, unwrappedErr); got != wantUnwrapped { 608 t.Errorf("error was %s, want %s", got, wantUnwrapped) 609 } 610 611} 612 613// TestRepeatedExec parses and resolves a file syntax tree once then 614// executes it repeatedly with different values of its predeclared variables. 615func TestRepeatedExec(t *testing.T) { 616 predeclared := starlark.StringDict{"x": starlark.None} 617 _, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has) 618 if err != nil { 619 t.Fatal(err) 620 } 621 622 for _, test := range []struct { 623 x, want starlark.Value 624 }{ 625 {x: starlark.MakeInt(42), want: starlark.MakeInt(84)}, 626 {x: starlark.String("mur"), want: starlark.String("murmur")}, 627 {x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}}, 628 } { 629 predeclared["x"] = test.x // update the values in dictionary 630 thread := new(starlark.Thread) 631 if globals, err := prog.Init(thread, predeclared); err != nil { 632 t.Errorf("x=%v: %v", test.x, err) // exec error 633 } else if eq, err := starlark.Equal(globals["y"], test.want); err != nil { 634 t.Errorf("x=%v: %v", test.x, err) // comparison error 635 } else if !eq { 636 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want) 637 } 638 } 639} 640 641// TestEmptyFilePosition ensures that even Programs 642// from empty files have a valid position. 643func TestEmptyPosition(t *testing.T) { 644 var predeclared starlark.StringDict 645 for _, content := range []string{"", "empty = False"} { 646 _, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has) 647 if err != nil { 648 t.Fatal(err) 649 } 650 if got, want := prog.Filename(), "hello.star"; got != want { 651 t.Errorf("Program.Filename() = %q, want %q", got, want) 652 } 653 } 654} 655 656// TestUnpackUserDefined tests that user-defined 657// implementations of starlark.Value may be unpacked. 658func TestUnpackUserDefined(t *testing.T) { 659 // success 660 want := new(hasfields) 661 var x *hasfields 662 if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil { 663 t.Errorf("UnpackArgs failed: %v", err) 664 } 665 if x != want { 666 t.Errorf("for x, got %v, want %v", x, want) 667 } 668 669 // failure 670 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x) 671 if want := "unpack: for parameter x: got int, want hasfields"; fmt.Sprint(err) != want { 672 t.Errorf("unpack args error = %q, want %q", err, want) 673 } 674} 675 676type optionalStringUnpacker struct { 677 str string 678 isSet bool 679} 680 681func (o *optionalStringUnpacker) Unpack(v starlark.Value) error { 682 s, ok := starlark.AsString(v) 683 if !ok { 684 return fmt.Errorf("got %s, want string", v.Type()) 685 } 686 o.str = s 687 o.isSet = ok 688 return nil 689} 690 691func TestUnpackCustomUnpacker(t *testing.T) { 692 a := optionalStringUnpacker{} 693 wantA := optionalStringUnpacker{str: "a", isSet: true} 694 b := optionalStringUnpacker{str: "b"} 695 wantB := optionalStringUnpacker{str: "b"} 696 697 // Success 698 if err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.String("a")}, nil, "a?", &a, "b?", &b); err != nil { 699 t.Errorf("UnpackArgs failed: %v", err) 700 } 701 if a != wantA { 702 t.Errorf("for a, got %v, want %v", a, wantA) 703 } 704 if b != wantB { 705 t.Errorf("for b, got %v, want %v", b, wantB) 706 } 707 708 // failure 709 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "a", &a) 710 if want := "unpack: for parameter a: got int, want string"; fmt.Sprint(err) != want { 711 t.Errorf("unpack args error = %q, want %q", err, want) 712 } 713} 714 715func TestAsInt(t *testing.T) { 716 for _, test := range []struct { 717 val starlark.Value 718 ptr interface{} 719 want string 720 }{ 721 {starlark.MakeInt(42), new(int32), "42"}, 722 {starlark.MakeInt(-1), new(int32), "-1"}, 723 // Use Lsh not 1<<40 as the latter exceeds int if GOARCH=386. 724 {starlark.MakeInt(1).Lsh(40), new(int32), "1099511627776 out of range (want value in signed 32-bit range)"}, 725 {starlark.MakeInt(-1).Lsh(40), new(int32), "-1099511627776 out of range (want value in signed 32-bit range)"}, 726 727 {starlark.MakeInt(42), new(uint16), "42"}, 728 {starlark.MakeInt(0xffff), new(uint16), "65535"}, 729 {starlark.MakeInt(0x10000), new(uint16), "65536 out of range (want value in unsigned 16-bit range)"}, 730 {starlark.MakeInt(-1), new(uint16), "-1 out of range (want value in unsigned 16-bit range)"}, 731 } { 732 var got string 733 if err := starlark.AsInt(test.val, test.ptr); err != nil { 734 got = err.Error() 735 } else { 736 got = fmt.Sprint(reflect.ValueOf(test.ptr).Elem().Interface()) 737 } 738 if got != test.want { 739 t.Errorf("AsInt(%s, %T): got %q, want %q", test.val, test.ptr, got, test.want) 740 } 741 } 742} 743 744func TestDocstring(t *testing.T) { 745 globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", ` 746def somefunc(): 747 "somefunc doc" 748 return 0 749`, nil) 750 751 if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" { 752 t.Fatal("docstring not found") 753 } 754} 755 756func TestFrameLocals(t *testing.T) { 757 // trace prints a nice stack trace including argument 758 // values of calls to Starlark functions. 759 trace := func(thread *starlark.Thread) string { 760 buf := new(bytes.Buffer) 761 for i := 0; i < thread.CallStackDepth(); i++ { 762 fr := thread.DebugFrame(i) 763 fmt.Fprintf(buf, "%s(", fr.Callable().Name()) 764 if fn, ok := fr.Callable().(*starlark.Function); ok { 765 for i := 0; i < fn.NumParams(); i++ { 766 if i > 0 { 767 buf.WriteString(", ") 768 } 769 name, _ := fn.Param(i) 770 fmt.Fprintf(buf, "%s=%s", name, fr.Local(i)) 771 } 772 } else { 773 buf.WriteString("...") // a built-in function 774 } 775 buf.WriteString(")\n") 776 } 777 return buf.String() 778 } 779 780 var got string 781 builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) { 782 got = trace(thread) 783 return starlark.None, nil 784 } 785 predeclared := starlark.StringDict{ 786 "builtin": starlark.NewBuiltin("builtin", builtin), 787 } 788 _, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", ` 789def f(x, y): builtin() 790def g(z): f(z, z*z) 791g(7) 792`, predeclared) 793 if err != nil { 794 t.Errorf("ExecFile failed: %v", err) 795 } 796 797 var want = ` 798builtin(...) 799f(x=7, y=49) 800g(z=7) 801<toplevel>() 802`[1:] 803 if got != want { 804 t.Errorf("got <<%s>>, want <<%s>>", got, want) 805 } 806} 807 808type badType string 809 810func (b *badType) String() string { return "badType" } 811func (b *badType) Type() string { return "badType:" + string(*b) } // panics if b==nil 812func (b *badType) Truth() starlark.Bool { return true } 813func (b *badType) Hash() (uint32, error) { return 0, nil } 814func (b *badType) Freeze() {} 815 816var _ starlark.Value = new(badType) 817 818// TestUnpackErrorBadType verifies that the Unpack functions fail 819// gracefully when a parameter's default value's Type method panics. 820func TestUnpackErrorBadType(t *testing.T) { 821 for _, test := range []struct { 822 x *badType 823 want string 824 }{ 825 {new(badType), "got NoneType, want badType"}, // Starlark type name 826 {nil, "got NoneType, want *starlark_test.badType"}, // Go type name 827 } { 828 err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x) 829 if err == nil { 830 t.Errorf("UnpackArgs succeeded unexpectedly") 831 continue 832 } 833 if !strings.Contains(err.Error(), test.want) { 834 t.Errorf("UnpackArgs error %q does not contain %q", err, test.want) 835 } 836 } 837} 838 839// Regression test for github.com/google/starlark-go/issues/233. 840func TestREPLChunk(t *testing.T) { 841 thread := new(starlark.Thread) 842 globals := make(starlark.StringDict) 843 exec := func(src string) { 844 f, err := syntax.Parse("<repl>", src, 0) 845 if err != nil { 846 t.Fatal(err) 847 } 848 if err := starlark.ExecREPLChunk(f, thread, globals); err != nil { 849 t.Fatal(err) 850 } 851 } 852 853 exec("x = 0; y = 0") 854 if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "0 0"; got != want { 855 t.Fatalf("chunk1: got %s, want %s", got, want) 856 } 857 858 exec("x += 1; y = y + 1") 859 if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "1 1"; got != want { 860 t.Fatalf("chunk2: got %s, want %s", got, want) 861 } 862} 863 864func TestCancel(t *testing.T) { 865 // A thread cancelled before it begins executes no code. 866 { 867 thread := new(starlark.Thread) 868 thread.Cancel("nope") 869 _, err := starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil) 870 if fmt.Sprint(err) != "Starlark computation cancelled: nope" { 871 t.Errorf("execution returned error %q, want cancellation", err) 872 } 873 874 // cancellation is sticky 875 _, err = starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil) 876 if fmt.Sprint(err) != "Starlark computation cancelled: nope" { 877 t.Errorf("execution returned error %q, want cancellation", err) 878 } 879 } 880 // A thread cancelled during a built-in executes no more code. 881 { 882 thread := new(starlark.Thread) 883 predeclared := starlark.StringDict{ 884 "stopit": starlark.NewBuiltin("stopit", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 885 thread.Cancel(fmt.Sprint(args[0])) 886 return starlark.None, nil 887 }), 888 } 889 _, err := starlark.ExecFile(thread, "stopit.star", `msg = 'nope'; stopit(msg); x = 1//0`, predeclared) 890 if fmt.Sprint(err) != `Starlark computation cancelled: "nope"` { 891 t.Errorf("execution returned error %q, want cancellation", err) 892 } 893 } 894} 895 896func TestExecutionSteps(t *testing.T) { 897 // A Thread records the number of computation steps. 898 thread := new(starlark.Thread) 899 countSteps := func(n int) (uint64, error) { 900 predeclared := starlark.StringDict{"n": starlark.MakeInt(n)} 901 steps0 := thread.ExecutionSteps() 902 _, err := starlark.ExecFile(thread, "steps.star", `squares = [x*x for x in range(n)]`, predeclared) 903 return thread.ExecutionSteps() - steps0, err 904 } 905 steps100, err := countSteps(1000) 906 if err != nil { 907 t.Errorf("execution failed: %v", err) 908 } 909 steps10000, err := countSteps(100000) 910 if err != nil { 911 t.Errorf("execution failed: %v", err) 912 } 913 if ratio := float64(steps10000) / float64(steps100); ratio < 99 || ratio > 101 { 914 t.Errorf("computation steps did not increase linearly: f(100)=%d, f(10000)=%d, ratio=%g, want ~100", steps100, steps10000, ratio) 915 } 916 917 // Exceeding the step limit causes cancellation. 918 thread.SetMaxExecutionSteps(1000) 919 _, err = countSteps(1000) 920 if fmt.Sprint(err) != "Starlark computation cancelled: too many steps" { 921 t.Errorf("execution returned error %q, want cancellation", err) 922 } 923} 924 925// TestDeps fails if the interpreter proper (not the REPL, etc) sprouts new external dependencies. 926// We may expand the list of permitted dependencies, but should do so deliberately, not casually. 927func TestDeps(t *testing.T) { 928 cmd := exec.Command("go", "list", "-deps") 929 out, err := cmd.Output() 930 if err != nil { 931 t.Skipf("'go list' failed: %s", err) 932 } 933 for _, pkg := range strings.Split(string(out), "\n") { 934 // Does pkg have form "domain.name/dir"? 935 slash := strings.IndexByte(pkg, '/') 936 dot := strings.IndexByte(pkg, '.') 937 if 0 < dot && dot < slash { 938 if strings.HasPrefix(pkg, "go.starlark.net/") || 939 strings.HasPrefix(pkg, "golang.org/x/sys/") { 940 continue // permitted dependencies 941 } 942 t.Errorf("new interpreter dependency: %s", pkg) 943 } 944 } 945} 946