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