github.com/k14s/starlark-go@v0.0.0-20200720175618-3a5c849cc368/starlark/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 starlark_test 6 7 import ( 8 "bytes" 9 "fmt" 10 "math" 11 "path/filepath" 12 "sort" 13 "strings" 14 "testing" 15 16 "github.com/k14s/starlark-go/internal/chunkedfile" 17 "github.com/k14s/starlark-go/resolve" 18 "github.com/k14s/starlark-go/starlark" 19 "github.com/k14s/starlark-go/starlarktest" 20 "github.com/k14s/starlark-go/syntax" 21 ) 22 23 // A test may enable non-standard options by containing (e.g.) "option:recursion". 24 func setOptions(src string) { 25 resolve.AllowFloat = option(src, "float") 26 resolve.AllowGlobalReassign = option(src, "globalreassign") 27 resolve.LoadBindsGlobally = option(src, "loadbindsglobally") 28 resolve.AllowLambda = option(src, "lambda") 29 resolve.AllowNestedDef = option(src, "nesteddef") 30 resolve.AllowRecursion = option(src, "recursion") 31 resolve.AllowSet = option(src, "set") 32 } 33 34 func option(chunk, name string) bool { 35 return strings.Contains(chunk, "option:"+name) 36 } 37 38 func TestEvalExpr(t *testing.T) { 39 // This is mostly redundant with the new *.star tests. 40 // TODO(adonovan): move checks into *.star files and 41 // reduce this to a mere unit test of starlark.Eval. 42 thread := new(starlark.Thread) 43 for _, test := range []struct{ src, want string }{ 44 {`123`, `123`}, 45 {`-1`, `-1`}, 46 {`"a"+"b"`, `"ab"`}, 47 {`1+2`, `3`}, 48 49 // lists 50 {`[]`, `[]`}, 51 {`[1]`, `[1]`}, 52 {`[1,]`, `[1]`}, 53 {`[1, 2]`, `[1, 2]`}, 54 {`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`}, 55 {`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`}, 56 {`[(x, y) for x in [1, 2] for y in [3, 4]]`, 57 `[(1, 3), (1, 4), (2, 3), (2, 4)]`}, 58 {`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`, 59 `[(2, 3), (2, 4)]`}, 60 // tuples 61 {`()`, `()`}, 62 {`(1)`, `1`}, 63 {`(1,)`, `(1,)`}, 64 {`(1, 2)`, `(1, 2)`}, 65 {`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`}, 66 {`1, 2`, `(1, 2)`}, 67 // dicts 68 {`{}`, `{}`}, 69 {`{"a": 1}`, `{"a": 1}`}, 70 {`{"a": 1,}`, `{"a": 1}`}, 71 72 // conditional 73 {`1 if 3 > 2 else 0`, `1`}, 74 {`1 if "foo" else 0`, `1`}, 75 {`1 if "" else 0`, `0`}, 76 77 // indexing 78 {`["a", "b"][0]`, `"a"`}, 79 {`["a", "b"][1]`, `"b"`}, 80 {`("a", "b")[0]`, `"a"`}, 81 {`("a", "b")[1]`, `"b"`}, 82 {`"aΩb"[0]`, `"a"`}, 83 {`"aΩb"[1]`, `"\xce"`}, 84 {`"aΩb"[3]`, `"b"`}, 85 {`{"a": 1}["a"]`, `1`}, 86 {`{"a": 1}["b"]`, `key "b" not in dict`}, 87 {`{}[[]]`, `unhashable type: list`}, 88 {`{"a": 1}[[]]`, `unhashable type: list`}, 89 {`[x for x in range(3)]`, "[0, 1, 2]"}, 90 } { 91 var got string 92 if v, err := starlark.Eval(thread, "<expr>", test.src, nil); err != nil { 93 got = err.Error() 94 } else { 95 got = v.String() 96 } 97 if got != test.want { 98 t.Errorf("eval %s = %s, want %s", test.src, got, test.want) 99 } 100 } 101 } 102 103 func TestExecFile(t *testing.T) { 104 defer setOptions("") 105 testdata := starlarktest.DataFile("starlark", ".") 106 thread := &starlark.Thread{Load: load} 107 starlarktest.SetReporter(thread, t) 108 for _, file := range []string{ 109 "testdata/assign.star", 110 "testdata/bool.star", 111 "testdata/builtins.star", 112 "testdata/control.star", 113 "testdata/dict.star", 114 "testdata/float.star", 115 "testdata/function.star", 116 "testdata/int.star", 117 "testdata/list.star", 118 "testdata/misc.star", 119 "testdata/set.star", 120 "testdata/string.star", 121 "testdata/tuple.star", 122 "testdata/recursion.star", 123 "testdata/module.star", 124 } { 125 filename := filepath.Join(testdata, file) 126 for _, chunk := range chunkedfile.Read(filename, t) { 127 predeclared := starlark.StringDict{ 128 "hasfields": starlark.NewBuiltin("hasfields", newHasFields), 129 "fibonacci": fib{}, 130 } 131 132 setOptions(chunk.Source) 133 resolve.AllowLambda = true // used extensively 134 135 _, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared) 136 switch err := err.(type) { 137 case *starlark.EvalError: 138 found := false 139 for i := range err.CallStack { 140 posn := err.CallStack.At(i).Pos 141 if posn.Filename() == filename { 142 chunk.GotError(int(posn.Line), err.Error()) 143 found = true 144 break 145 } 146 } 147 if !found { 148 t.Error(err.Backtrace()) 149 } 150 case nil: 151 // success 152 default: 153 t.Errorf("\n%s", err) 154 } 155 chunk.Done() 156 } 157 } 158 } 159 160 // A fib is an iterable value representing the infinite Fibonacci sequence. 161 type fib struct{} 162 163 func (t fib) Freeze() {} 164 func (t fib) String() string { return "fib" } 165 func (t fib) Type() string { return "fib" } 166 func (t fib) Truth() starlark.Bool { return true } 167 func (t fib) Hash() (uint32, error) { return 0, fmt.Errorf("fib is unhashable") } 168 func (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} } 169 170 type fibIterator struct{ x, y int } 171 172 func (it *fibIterator) Next(p *starlark.Value) bool { 173 *p = starlark.MakeInt(it.x) 174 it.x, it.y = it.y, it.x+it.y 175 return true 176 } 177 func (it *fibIterator) Done() {} 178 179 // load implements the 'load' operation as used in the evaluator tests. 180 func load(thread *starlark.Thread, module string) (starlark.StringDict, error) { 181 if module == "assert.star" { 182 return starlarktest.LoadAssertModule() 183 } 184 185 // TODO(adonovan): test load() using this execution path. 186 filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module) 187 return starlark.ExecFile(thread, filename, nil, nil) 188 } 189 190 func newHasFields(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 191 if len(args)+len(kwargs) > 0 { 192 return nil, fmt.Errorf("%s: unexpected arguments", b.Name()) 193 } 194 return &hasfields{attrs: make(map[string]starlark.Value)}, nil 195 } 196 197 // hasfields is a test-only implementation of HasAttrs. 198 // It permits any field to be set. 199 // Clients will likely want to provide their own implementation, 200 // so we don't have any public implementation. 201 type hasfields struct { 202 attrs starlark.StringDict 203 frozen bool 204 } 205 206 var ( 207 _ starlark.HasAttrs = (*hasfields)(nil) 208 _ starlark.HasBinary = (*hasfields)(nil) 209 ) 210 211 func (hf *hasfields) String() string { return "hasfields" } 212 func (hf *hasfields) Type() string { return "hasfields" } 213 func (hf *hasfields) Truth() starlark.Bool { return true } 214 func (hf *hasfields) Hash() (uint32, error) { return 42, nil } 215 216 func (hf *hasfields) Freeze() { 217 if !hf.frozen { 218 hf.frozen = true 219 for _, v := range hf.attrs { 220 v.Freeze() 221 } 222 } 223 } 224 225 func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil } 226 227 func (hf *hasfields) SetField(name string, val starlark.Value) error { 228 if hf.frozen { 229 return fmt.Errorf("cannot set field on a frozen hasfields") 230 } 231 if strings.HasPrefix(name, "no") { // for testing 232 return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name)) 233 } 234 hf.attrs[name] = val 235 return nil 236 } 237 238 func (hf *hasfields) AttrNames() []string { 239 names := make([]string, 0, len(hf.attrs)) 240 for key := range hf.attrs { 241 names = append(names, key) 242 } 243 sort.Strings(names) 244 return names 245 } 246 247 func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) { 248 // This method exists so we can exercise 'list += x' 249 // where x is not Iterable but defines list+x. 250 if op == syntax.PLUS { 251 if _, ok := y.(*starlark.List); ok { 252 return starlark.MakeInt(42), nil // list+hasfields is 42 253 } 254 } 255 return nil, nil 256 } 257 258 func TestParameterPassing(t *testing.T) { 259 const filename = "parameters.go" 260 const src = ` 261 def a(): 262 return 263 def b(a, b): 264 return a, b 265 def c(a, b=42): 266 return a, b 267 def d(*args): 268 return args 269 def e(**kwargs): 270 return kwargs 271 def f(a, b=42, *args, **kwargs): 272 return a, b, args, kwargs 273 def g(a, b=42, *args, c=123, **kwargs): 274 return a, b, args, c, kwargs 275 def h(a, b=42, *, c=123, **kwargs): 276 return a, b, c, kwargs 277 def i(a, b=42, *, c, d=123, e, **kwargs): 278 return a, b, c, d, e, kwargs 279 def j(a, b=42, *args, c, d=123, e, **kwargs): 280 return a, b, args, c, d, e, kwargs 281 ` 282 283 thread := new(starlark.Thread) 284 globals, err := starlark.ExecFile(thread, filename, src, nil) 285 if err != nil { 286 t.Fatal(err) 287 } 288 289 for _, test := range []struct{ src, want string }{ 290 // a() 291 {`a()`, `None`}, 292 {`a(1)`, `function a accepts no arguments (1 given)`}, 293 294 // b(a, b) 295 {`b()`, `function b missing 2 arguments (a, b)`}, 296 {`b(1)`, `function b missing 1 argument (b)`}, 297 {`b(a=1)`, `function b missing 1 argument (b)`}, 298 {`b(b=1)`, `function b missing 1 argument (a)`}, 299 {`b(1, 2)`, `(1, 2)`}, 300 {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable 301 {`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`}, 302 {`b(1, b=2)`, `(1, 2)`}, 303 {`b(1, a=2)`, `function b got multiple values for parameter "a"`}, 304 {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`}, 305 {`b(a=1, b=2)`, `(1, 2)`}, 306 {`b(b=1, a=2)`, `(2, 1)`}, 307 {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`}, 308 {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`}, 309 310 // c(a, b=42) 311 {`c()`, `function c missing 1 argument (a)`}, 312 {`c(1)`, `(1, 42)`}, 313 {`c(1, 2)`, `(1, 2)`}, 314 {`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`}, 315 {`c(1, b=2)`, `(1, 2)`}, 316 {`c(1, a=2)`, `function c got multiple values for parameter "a"`}, 317 {`c(a=1, b=2)`, `(1, 2)`}, 318 {`c(b=1, a=2)`, `(2, 1)`}, 319 320 // d(*args) 321 {`d()`, `()`}, 322 {`d(1)`, `(1,)`}, 323 {`d(1, 2)`, `(1, 2)`}, 324 {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`}, 325 {`d(args=[])`, `function d got an unexpected keyword argument "args"`}, 326 327 // e(**kwargs) 328 {`e()`, `{}`}, 329 {`e(1)`, `function e accepts 0 positional arguments (1 given)`}, 330 {`e(k=1)`, `{"k": 1}`}, 331 {`e(kwargs={})`, `{"kwargs": {}}`}, 332 333 // f(a, b=42, *args, **kwargs) 334 {`f()`, `function f missing 1 argument (a)`}, 335 {`f(0)`, `(0, 42, (), {})`}, 336 {`f(0)`, `(0, 42, (), {})`}, 337 {`f(0, 1)`, `(0, 1, (), {})`}, 338 {`f(0, 1, 2)`, `(0, 1, (2,), {})`}, 339 {`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`}, 340 {`f(a=0)`, `(0, 42, (), {})`}, 341 {`f(0, b=1)`, `(0, 1, (), {})`}, 342 {`f(0, a=1)`, `function f got multiple values for parameter "a"`}, 343 {`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`}, 344 {`f(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, // github.com/google/skylark/issues/135 345 `(0, 1, (3, 4), {"x": 2, "y": 5, "z": 6})`}, 346 347 // g(a, b=42, *args, c=123, **kwargs) 348 {`g()`, `function g missing 1 argument (a)`}, 349 {`g(0)`, `(0, 42, (), 123, {})`}, 350 {`g(0, 1)`, `(0, 1, (), 123, {})`}, 351 {`g(0, 1, 2)`, `(0, 1, (2,), 123, {})`}, 352 {`g(0, 1, 2, 3)`, `(0, 1, (2, 3), 123, {})`}, 353 {`g(a=0)`, `(0, 42, (), 123, {})`}, 354 {`g(0, b=1)`, `(0, 1, (), 123, {})`}, 355 {`g(0, a=1)`, `function g got multiple values for parameter "a"`}, 356 {`g(0, b=1, c=2, d=3)`, `(0, 1, (), 2, {"d": 3})`}, 357 {`g(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, 358 `(0, 1, (3, 4), 123, {"x": 2, "y": 5, "z": 6})`}, 359 360 // h(a, b=42, *, c=123, **kwargs) 361 {`h()`, `function h missing 1 argument (a)`}, 362 {`h(0)`, `(0, 42, 123, {})`}, 363 {`h(0, 1)`, `(0, 1, 123, {})`}, 364 {`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`}, 365 {`h(a=0)`, `(0, 42, 123, {})`}, 366 {`h(0, b=1)`, `(0, 1, 123, {})`}, 367 {`h(0, a=1)`, `function h got multiple values for parameter "a"`}, 368 {`h(0, b=1, c=2)`, `(0, 1, 2, {})`}, 369 {`h(0, b=1, d=2)`, `(0, 1, 123, {"d": 2})`}, 370 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`}, 371 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`}, 372 373 // i(a, b=42, *, c, d=123, e, **kwargs) 374 {`i()`, `function i missing 3 arguments (a, c, e)`}, 375 {`i(0)`, `function i missing 2 arguments (c, e)`}, 376 {`i(0, 1)`, `function i missing 2 arguments (c, e)`}, 377 {`i(0, 1, 2)`, `function i accepts at most 2 positional arguments (3 given)`}, 378 {`i(0, 1, e=2)`, `function i missing 1 argument (c)`}, 379 {`i(0, 1, 2, 3)`, `function i accepts at most 2 positional arguments (4 given)`}, 380 {`i(a=0)`, `function i missing 2 arguments (c, e)`}, 381 {`i(0, b=1)`, `function i missing 2 arguments (c, e)`}, 382 {`i(0, a=1)`, `function i got multiple values for parameter "a"`}, 383 {`i(0, b=1, c=2)`, `function i missing 1 argument (e)`}, 384 {`i(0, b=1, d=2)`, `function i missing 2 arguments (c, e)`}, 385 {`i(0, b=1, c=2, d=3)`, `function i missing 1 argument (e)`}, 386 {`i(0, b=1, c=2, d=3, e=4)`, `(0, 1, 2, 3, 4, {})`}, 387 {`i(0, 1, b=1, c=2, d=3, e=4)`, `function i got multiple values for parameter "b"`}, 388 389 // j(a, b=42, *args, c, d=123, e, **kwargs) 390 {`j()`, `function j missing 3 arguments (a, c, e)`}, 391 {`j(0)`, `function j missing 2 arguments (c, e)`}, 392 {`j(0, 1)`, `function j missing 2 arguments (c, e)`}, 393 {`j(0, 1, 2)`, `function j missing 2 arguments (c, e)`}, 394 {`j(0, 1, e=2)`, `function j missing 1 argument (c)`}, 395 {`j(0, 1, 2, 3)`, `function j missing 2 arguments (c, e)`}, 396 {`j(a=0)`, `function j missing 2 arguments (c, e)`}, 397 {`j(0, b=1)`, `function j missing 2 arguments (c, e)`}, 398 {`j(0, a=1)`, `function j got multiple values for parameter "a"`}, 399 {`j(0, b=1, c=2)`, `function j missing 1 argument (e)`}, 400 {`j(0, b=1, d=2)`, `function j missing 2 arguments (c, e)`}, 401 {`j(0, b=1, c=2, d=3)`, `function j missing 1 argument (e)`}, 402 {`j(0, b=1, c=2, d=3, e=4)`, `(0, 1, (), 2, 3, 4, {})`}, 403 {`j(0, 1, b=1, c=2, d=3, e=4)`, `function j got multiple values for parameter "b"`}, 404 {`j(0, 1, 2, c=3, e=4)`, `(0, 1, (2,), 3, 123, 4, {})`}, 405 } { 406 var got string 407 if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil { 408 got = err.Error() 409 } else { 410 got = v.String() 411 } 412 if got != test.want { 413 t.Errorf("eval %s = %s, want %s", test.src, got, test.want) 414 } 415 } 416 } 417 418 // TestPrint ensures that the Starlark print function calls 419 // Thread.Print, if provided. 420 func TestPrint(t *testing.T) { 421 const src = ` 422 print("hello") 423 def f(): print("hello", "world", sep=", ") 424 f() 425 ` 426 buf := new(bytes.Buffer) 427 print := func(thread *starlark.Thread, msg string) { 428 caller := thread.CallFrame(1) 429 fmt.Fprintf(buf, "%s: %s: %s\n", caller.Pos, caller.Name, msg) 430 } 431 thread := &starlark.Thread{Print: print} 432 if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil { 433 t.Fatal(err) 434 } 435 want := "foo.star:2:6: <toplevel>: hello\n" + 436 "foo.star:3:15: f: hello, world\n" 437 if got := buf.String(); got != want { 438 t.Errorf("output was %s, want %s", got, want) 439 } 440 } 441 442 func reportEvalError(tb testing.TB, err error) { 443 if err, ok := err.(*starlark.EvalError); ok { 444 tb.Fatal(err.Backtrace()) 445 } 446 tb.Fatal(err) 447 } 448 449 // TestInt exercises the Int.Int64 and Int.Uint64 methods. 450 // If we can move their logic into math/big, delete this test. 451 func TestInt(t *testing.T) { 452 one := starlark.MakeInt(1) 453 454 for _, test := range []struct { 455 i starlark.Int 456 wantInt64 string 457 wantUint64 string 458 }{ 459 {starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"}, 460 {starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"}, 461 {starlark.MakeInt64(-1), "-1", "error"}, 462 {starlark.MakeInt64(0), "0", "0"}, 463 {starlark.MakeInt64(1), "1", "1"}, 464 {starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"}, 465 {starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"}, 466 {starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"}, 467 } { 468 gotInt64, gotUint64 := "error", "error" 469 if i, ok := test.i.Int64(); ok { 470 gotInt64 = fmt.Sprint(i) 471 } 472 if u, ok := test.i.Uint64(); ok { 473 gotUint64 = fmt.Sprint(u) 474 } 475 if gotInt64 != test.wantInt64 { 476 t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64) 477 } 478 if gotUint64 != test.wantUint64 { 479 t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64) 480 } 481 } 482 } 483 484 func TestBacktrace(t *testing.T) { 485 getBacktrace := func(err error) string { 486 switch err := err.(type) { 487 case *starlark.EvalError: 488 return err.Backtrace() 489 case nil: 490 t.Fatalf("ExecFile succeeded unexpectedly") 491 default: 492 t.Fatalf("ExecFile failed with %v, wanted *EvalError", err) 493 } 494 panic("unreachable") 495 } 496 497 // This test ensures continuity of the stack of active Starlark 498 // functions, including propagation through built-ins such as 'min'. 499 const src = ` 500 def f(x): return 1//x 501 def g(x): f(x) 502 def h(): return min([1, 2, 0], key=g) 503 def i(): return h() 504 i() 505 ` 506 thread := new(starlark.Thread) 507 _, err := starlark.ExecFile(thread, "crash.star", src, nil) 508 // Compiled code currently has no column information. 509 const want = `Traceback (most recent call last): 510 crash.star:6:2: in <toplevel> 511 crash.star:5:18: in i 512 crash.star:4:20: in h 513 <builtin>: in min 514 crash.star:3:12: in g 515 crash.star:2:19: in f 516 Error: floored division by zero` 517 if got := getBacktrace(err); got != want { 518 t.Errorf("error was %s, want %s", got, want) 519 } 520 521 // Additionally, ensure that errors originating in 522 // Starlark and/or Go each have an accurate frame. 523 // 524 // This program fails in Starlark (f) if x==0, 525 // or in Go (string.join) if x is non-zero. 526 const src2 = ` 527 def f(): ''.join([1//i]) 528 f() 529 ` 530 for i, want := range []string{ 531 0: `Traceback (most recent call last): 532 crash.star:3:2: in <toplevel> 533 crash.star:2:20: in f 534 Error: floored division by zero`, 535 1: `Traceback (most recent call last): 536 crash.star:3:2: in <toplevel> 537 crash.star:2:17: in f 538 <builtin>: in join 539 Error: join: in list, want string, got int`, 540 } { 541 globals := starlark.StringDict{"i": starlark.MakeInt(i)} 542 _, err := starlark.ExecFile(thread, "crash.star", src2, globals) 543 if got := getBacktrace(err); got != want { 544 t.Errorf("error was %s, want %s", got, want) 545 } 546 } 547 } 548 549 // TestRepeatedExec parses and resolves a file syntax tree once then 550 // executes it repeatedly with different values of its predeclared variables. 551 func TestRepeatedExec(t *testing.T) { 552 predeclared := starlark.StringDict{"x": starlark.None} 553 _, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has) 554 if err != nil { 555 t.Fatal(err) 556 } 557 558 for _, test := range []struct { 559 x, want starlark.Value 560 }{ 561 {x: starlark.MakeInt(42), want: starlark.MakeInt(84)}, 562 {x: starlark.String("mur"), want: starlark.String("murmur")}, 563 {x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}}, 564 } { 565 predeclared["x"] = test.x // update the values in dictionary 566 thread := new(starlark.Thread) 567 if globals, err := prog.Init(thread, predeclared); err != nil { 568 t.Errorf("x=%v: %v", test.x, err) // exec error 569 } else if eq, err := starlark.Equal(globals["y"], test.want); err != nil { 570 t.Errorf("x=%v: %v", test.x, err) // comparison error 571 } else if !eq { 572 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want) 573 } 574 } 575 } 576 577 // TestEmptyFilePosition ensures that even Programs 578 // from empty files have a valid position. 579 func TestEmptyPosition(t *testing.T) { 580 var predeclared starlark.StringDict 581 for _, content := range []string{"", "empty = False"} { 582 _, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has) 583 if err != nil { 584 t.Fatal(err) 585 } 586 if got, want := prog.Filename(), "hello.star"; got != want { 587 t.Errorf("Program.Filename() = %q, want %q", got, want) 588 } 589 } 590 } 591 592 // TestUnpackUserDefined tests that user-defined 593 // implementations of starlark.Value may be unpacked. 594 func TestUnpackUserDefined(t *testing.T) { 595 // success 596 want := new(hasfields) 597 var x *hasfields 598 if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil { 599 t.Errorf("UnpackArgs failed: %v", err) 600 } 601 if x != want { 602 t.Errorf("for x, got %v, want %v", x, want) 603 } 604 605 // failure 606 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x) 607 if want := "unpack: for parameter x: got int, want hasfields"; fmt.Sprint(err) != want { 608 t.Errorf("unpack args error = %q, want %q", err, want) 609 } 610 } 611 612 func TestDocstring(t *testing.T) { 613 globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", ` 614 def somefunc(): 615 "somefunc doc" 616 return 0 617 `, nil) 618 619 if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" { 620 t.Fatal("docstring not found") 621 } 622 } 623 624 func TestFrameLocals(t *testing.T) { 625 // trace prints a nice stack trace including argument 626 // values of calls to Starlark functions. 627 trace := func(thread *starlark.Thread) string { 628 buf := new(bytes.Buffer) 629 for i := 0; i < thread.CallStackDepth(); i++ { 630 fr := thread.DebugFrame(i) 631 fmt.Fprintf(buf, "%s(", fr.Callable().Name()) 632 if fn, ok := fr.Callable().(*starlark.Function); ok { 633 for i := 0; i < fn.NumParams(); i++ { 634 if i > 0 { 635 buf.WriteString(", ") 636 } 637 name, _ := fn.Param(i) 638 fmt.Fprintf(buf, "%s=%s", name, fr.Local(i)) 639 } 640 } else { 641 buf.WriteString("...") // a built-in function 642 } 643 buf.WriteString(")\n") 644 } 645 return buf.String() 646 } 647 648 var got string 649 builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) { 650 got = trace(thread) 651 return starlark.None, nil 652 } 653 predeclared := starlark.StringDict{ 654 "builtin": starlark.NewBuiltin("builtin", builtin), 655 } 656 _, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", ` 657 def f(x, y): builtin() 658 def g(z): f(z, z*z) 659 g(7) 660 `, predeclared) 661 if err != nil { 662 t.Errorf("ExecFile failed: %v", err) 663 } 664 665 var want = ` 666 builtin(...) 667 f(x=7, y=49) 668 g(z=7) 669 <toplevel>() 670 `[1:] 671 if got != want { 672 t.Errorf("got <<%s>>, want <<%s>>", got, want) 673 } 674 } 675 676 type badType string 677 678 func (b *badType) String() string { return "badType" } 679 func (b *badType) Type() string { return "badType:" + string(*b) } // panics if b==nil 680 func (b *badType) Truth() starlark.Bool { return true } 681 func (b *badType) Hash() (uint32, error) { return 0, nil } 682 func (b *badType) Freeze() {} 683 684 var _ starlark.Value = new(badType) 685 686 // TestUnpackErrorBadType verifies that the Unpack functions fail 687 // gracefully when a parameter's default value's Type method panics. 688 func TestUnpackErrorBadType(t *testing.T) { 689 for _, test := range []struct { 690 x *badType 691 want string 692 }{ 693 {new(badType), "got NoneType, want badType"}, // Starlark type name 694 {nil, "got NoneType, want *starlark_test.badType"}, // Go type name 695 } { 696 err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x) 697 if err == nil { 698 t.Errorf("UnpackArgs succeeded unexpectedly") 699 continue 700 } 701 if !strings.Contains(err.Error(), test.want) { 702 t.Errorf("UnpackArgs error %q does not contain %q", err, test.want) 703 } 704 } 705 }