blob: 51119e6849b033a8b54e1170bb5b274a045a23fa [file] [log] [blame]
Alan Donovan312d1a52017-10-02 10:10:28 -04001// 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
Alan Donovane3deafe2018-10-23 11:05:09 -04005package starlark_test
Alan Donovan312d1a52017-10-02 10:10:28 -04006
7import (
8 "bytes"
9 "fmt"
10 "math"
11 "path/filepath"
Alessandro Arzilli58464402018-12-13 17:43:18 +010012 "strings"
Alan Donovan312d1a52017-10-02 10:10:28 -040013 "testing"
14
Alan Donovan6beab7e2018-10-31 17:53:09 -040015 "go.starlark.net/internal/chunkedfile"
16 "go.starlark.net/resolve"
alandonovan7a866322018-11-21 14:57:52 -050017 "go.starlark.net/starlark"
Alan Donovan6beab7e2018-10-31 17:53:09 -040018 "go.starlark.net/starlarktest"
19 "go.starlark.net/syntax"
Alan Donovan312d1a52017-10-02 10:10:28 -040020)
21
Alan Donovanc0b6b762018-12-18 13:09:56 -050022// A test may enable non-standard options by containing (e.g.) "option:recursion".
23func setOptions(src string) {
24 resolve.AllowBitwise = option(src, "bitwise")
25 resolve.AllowFloat = option(src, "float")
26 resolve.AllowGlobalReassign = option(src, "globalreassign")
27 resolve.AllowLambda = option(src, "lambda")
28 resolve.AllowNestedDef = option(src, "nesteddef")
29 resolve.AllowRecursion = option(src, "recursion")
30 resolve.AllowSet = option(src, "set")
31}
32
33func option(chunk, name string) bool {
34 return strings.Contains(chunk, "option:"+name)
Alan Donovan312d1a52017-10-02 10:10:28 -040035}
36
37func TestEvalExpr(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -040038 // This is mostly redundant with the new *.star tests.
39 // TODO(adonovan): move checks into *.star files and
40 // reduce this to a mere unit test of starlark.Eval.
41 thread := new(starlark.Thread)
Alan Donovan312d1a52017-10-02 10:10:28 -040042 for _, test := range []struct{ src, want string }{
43 {`123`, `123`},
44 {`-1`, `-1`},
45 {`"a"+"b"`, `"ab"`},
46 {`1+2`, `3`},
47
48 // lists
49 {`[]`, `[]`},
50 {`[1]`, `[1]`},
51 {`[1,]`, `[1]`},
52 {`[1, 2]`, `[1, 2]`},
53 {`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`},
54 {`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`},
55 {`[(x, y) for x in [1, 2] for y in [3, 4]]`,
56 `[(1, 3), (1, 4), (2, 3), (2, 4)]`},
57 {`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`,
58 `[(2, 3), (2, 4)]`},
59 // tuples
60 {`()`, `()`},
61 {`(1)`, `1`},
62 {`(1,)`, `(1,)`},
63 {`(1, 2)`, `(1, 2)`},
64 {`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`},
65 // dicts
66 {`{}`, `{}`},
67 {`{"a": 1}`, `{"a": 1}`},
68 {`{"a": 1,}`, `{"a": 1}`},
69
70 // conditional
71 {`1 if 3 > 2 else 0`, `1`},
72 {`1 if "foo" else 0`, `1`},
73 {`1 if "" else 0`, `0`},
74
75 // indexing
76 {`["a", "b"][0]`, `"a"`},
77 {`["a", "b"][1]`, `"b"`},
78 {`("a", "b")[0]`, `"a"`},
79 {`("a", "b")[1]`, `"b"`},
80 {`"aΩb"[0]`, `"a"`},
81 {`"aΩb"[1]`, `"\xce"`},
82 {`"aΩb"[3]`, `"b"`},
83 {`{"a": 1}["a"]`, `1`},
84 {`{"a": 1}["b"]`, `key "b" not in dict`},
85 {`{}[[]]`, `unhashable type: list`},
86 {`{"a": 1}[[]]`, `unhashable type: list`},
87 {`[x for x in range(3)]`, "[0, 1, 2]"},
88 } {
89 var got string
Alan Donovane3deafe2018-10-23 11:05:09 -040090 if v, err := starlark.Eval(thread, "<expr>", test.src, nil); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -040091 got = err.Error()
92 } else {
93 got = v.String()
94 }
95 if got != test.want {
96 t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
97 }
98 }
99}
100
101func TestExecFile(t *testing.T) {
Alan Donovanc0b6b762018-12-18 13:09:56 -0500102 defer setOptions("")
Alan Donovane3deafe2018-10-23 11:05:09 -0400103 testdata := starlarktest.DataFile("starlark", ".")
104 thread := &starlark.Thread{Load: load}
105 starlarktest.SetReporter(thread, t)
Alan Donovan312d1a52017-10-02 10:10:28 -0400106 for _, file := range []string{
Alan Donovane3deafe2018-10-23 11:05:09 -0400107 "testdata/assign.star",
108 "testdata/bool.star",
109 "testdata/builtins.star",
110 "testdata/control.star",
111 "testdata/dict.star",
112 "testdata/float.star",
113 "testdata/function.star",
114 "testdata/int.star",
115 "testdata/list.star",
116 "testdata/misc.star",
117 "testdata/set.star",
118 "testdata/string.star",
119 "testdata/tuple.star",
Alessandro Arzilli58464402018-12-13 17:43:18 +0100120 "testdata/recursion.star",
alandonovan9d977712019-01-04 13:04:59 -0500121 "testdata/module.star",
Alan Donovan312d1a52017-10-02 10:10:28 -0400122 } {
123 filename := filepath.Join(testdata, file)
124 for _, chunk := range chunkedfile.Read(filename, t) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400125 predeclared := starlark.StringDict{
126 "hasfields": starlark.NewBuiltin("hasfields", newHasFields),
Alan Donovan312d1a52017-10-02 10:10:28 -0400127 "fibonacci": fib{},
128 }
Alessandro Arzilli58464402018-12-13 17:43:18 +0100129
Alan Donovanc0b6b762018-12-18 13:09:56 -0500130 setOptions(chunk.Source)
131 resolve.AllowLambda = true // used extensively
Alessandro Arzilli58464402018-12-13 17:43:18 +0100132
Alan Donovane3deafe2018-10-23 11:05:09 -0400133 _, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared)
Alan Donovan312d1a52017-10-02 10:10:28 -0400134 switch err := err.(type) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400135 case *starlark.EvalError:
Alan Donovan312d1a52017-10-02 10:10:28 -0400136 found := false
137 for _, fr := range err.Stack() {
138 posn := fr.Position()
139 if posn.Filename() == filename {
140 chunk.GotError(int(posn.Line), err.Error())
141 found = true
142 break
143 }
144 }
145 if !found {
146 t.Error(err.Backtrace())
147 }
148 case nil:
149 // success
150 default:
Alan Donovanc0b6b762018-12-18 13:09:56 -0500151 t.Errorf("\n%s", err)
Alan Donovan312d1a52017-10-02 10:10:28 -0400152 }
153 chunk.Done()
154 }
155 }
156}
157
158// A fib is an iterable value representing the infinite Fibonacci sequence.
159type fib struct{}
160
alandonovan7a866322018-11-21 14:57:52 -0500161func (t fib) Freeze() {}
162func (t fib) String() string { return "fib" }
163func (t fib) Type() string { return "fib" }
Alan Donovane3deafe2018-10-23 11:05:09 -0400164func (t fib) Truth() starlark.Bool { return true }
alandonovan7a866322018-11-21 14:57:52 -0500165func (t fib) Hash() (uint32, error) { return 0, fmt.Errorf("fib is unhashable") }
Alan Donovane3deafe2018-10-23 11:05:09 -0400166func (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} }
Alan Donovan312d1a52017-10-02 10:10:28 -0400167
168type fibIterator struct{ x, y int }
169
Alan Donovane3deafe2018-10-23 11:05:09 -0400170func (it *fibIterator) Next(p *starlark.Value) bool {
171 *p = starlark.MakeInt(it.x)
Alan Donovan312d1a52017-10-02 10:10:28 -0400172 it.x, it.y = it.y, it.x+it.y
173 return true
174}
175func (it *fibIterator) Done() {}
176
177// load implements the 'load' operation as used in the evaluator tests.
Alan Donovane3deafe2018-10-23 11:05:09 -0400178func load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
179 if module == "assert.star" {
180 return starlarktest.LoadAssertModule()
Alan Donovan312d1a52017-10-02 10:10:28 -0400181 }
182
183 // TODO(adonovan): test load() using this execution path.
Alan Donovan312d1a52017-10-02 10:10:28 -0400184 filename := filepath.Join(filepath.Dir(thread.Caller().Position().Filename()), module)
Alan Donovane3deafe2018-10-23 11:05:09 -0400185 return starlark.ExecFile(thread, filename, nil, nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400186}
187
Alan Donovane3deafe2018-10-23 11:05:09 -0400188func newHasFields(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
189 return &hasfields{attrs: make(map[string]starlark.Value)}, nil
Alan Donovan312d1a52017-10-02 10:10:28 -0400190}
191
192// hasfields is a test-only implementation of HasAttrs.
193// It permits any field to be set.
194// Clients will likely want to provide their own implementation,
195// so we don't have any public implementation.
196type hasfields struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400197 attrs starlark.StringDict
Alan Donovan312d1a52017-10-02 10:10:28 -0400198 frozen bool
199}
200
alandonovan15b1cb92018-02-26 14:38:20 -0500201var (
Alan Donovane3deafe2018-10-23 11:05:09 -0400202 _ starlark.HasAttrs = (*hasfields)(nil)
203 _ starlark.HasBinary = (*hasfields)(nil)
alandonovan15b1cb92018-02-26 14:38:20 -0500204)
Alan Donovan312d1a52017-10-02 10:10:28 -0400205
206func (hf *hasfields) String() string { return "hasfields" }
207func (hf *hasfields) Type() string { return "hasfields" }
alandonovan7a866322018-11-21 14:57:52 -0500208func (hf *hasfields) Truth() starlark.Bool { return true }
Alan Donovan312d1a52017-10-02 10:10:28 -0400209func (hf *hasfields) Hash() (uint32, error) { return 42, nil }
210
211func (hf *hasfields) Freeze() {
212 if !hf.frozen {
213 hf.frozen = true
214 for _, v := range hf.attrs {
215 v.Freeze()
216 }
217 }
218}
219
Alan Donovane3deafe2018-10-23 11:05:09 -0400220func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil }
Alan Donovan312d1a52017-10-02 10:10:28 -0400221
Alan Donovane3deafe2018-10-23 11:05:09 -0400222func (hf *hasfields) SetField(name string, val starlark.Value) error {
Alan Donovan312d1a52017-10-02 10:10:28 -0400223 if hf.frozen {
224 return fmt.Errorf("cannot set field on a frozen hasfields")
225 }
226 hf.attrs[name] = val
227 return nil
228}
229
230func (hf *hasfields) AttrNames() []string {
231 names := make([]string, 0, len(hf.attrs))
232 for key := range hf.attrs {
233 names = append(names, key)
234 }
235 return names
236}
237
Alan Donovane3deafe2018-10-23 11:05:09 -0400238func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
alandonovan15b1cb92018-02-26 14:38:20 -0500239 // This method exists so we can exercise 'list += x'
240 // where x is not Iterable but defines list+x.
241 if op == syntax.PLUS {
Alan Donovane3deafe2018-10-23 11:05:09 -0400242 if _, ok := y.(*starlark.List); ok {
243 return starlark.MakeInt(42), nil // list+hasfields is 42
alandonovan15b1cb92018-02-26 14:38:20 -0500244 }
245 }
246 return nil, nil
247}
248
Alan Donovan312d1a52017-10-02 10:10:28 -0400249func TestParameterPassing(t *testing.T) {
250 const filename = "parameters.go"
251 const src = `
252def a():
253 return
254def b(a, b):
255 return a, b
256def c(a, b=42):
257 return a, b
258def d(*args):
259 return args
260def e(**kwargs):
261 return kwargs
262def f(a, b=42, *args, **kwargs):
263 return a, b, args, kwargs
264`
265
Alan Donovane3deafe2018-10-23 11:05:09 -0400266 thread := new(starlark.Thread)
267 globals, err := starlark.ExecFile(thread, filename, src, nil)
alandonovana1b28d82018-03-13 10:59:24 -0400268 if err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400269 t.Fatal(err)
270 }
271
272 for _, test := range []struct{ src, want string }{
273 {`a()`, `None`},
274 {`a(1)`, `function a takes no arguments (1 given)`},
alandonovanb7e3b1f2018-12-12 17:14:58 -0500275 {`b()`, `function b takes exactly 2 positional arguments (0 given)`},
276 {`b(1)`, `function b takes exactly 2 positional arguments (1 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400277 {`b(1, 2)`, `(1, 2)`},
278 {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
alandonovanb7e3b1f2018-12-12 17:14:58 -0500279 {`b(1, 2, 3)`, `function b takes exactly 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400280 {`b(1, b=2)`, `(1, 2)`},
281 {`b(1, a=2)`, `function b got multiple values for keyword argument "a"`},
282 {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`},
283 {`b(a=1, b=2)`, `(1, 2)`},
284 {`b(b=1, a=2)`, `(2, 1)`},
285 {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`},
286 {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`},
alandonovanb7e3b1f2018-12-12 17:14:58 -0500287 {`c()`, `function c takes at least 1 positional argument (0 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400288 {`c(1)`, `(1, 42)`},
289 {`c(1, 2)`, `(1, 2)`},
alandonovanb7e3b1f2018-12-12 17:14:58 -0500290 {`c(1, 2, 3)`, `function c takes at most 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400291 {`c(1, b=2)`, `(1, 2)`},
292 {`c(1, a=2)`, `function c got multiple values for keyword argument "a"`},
293 {`c(a=1, b=2)`, `(1, 2)`},
294 {`c(b=1, a=2)`, `(2, 1)`},
295 {`d()`, `()`},
296 {`d(1)`, `(1,)`},
297 {`d(1, 2)`, `(1, 2)`},
298 {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`},
299 {`d(args=[])`, `function d got an unexpected keyword argument "args"`},
300 {`e()`, `{}`},
alandonovanb7e3b1f2018-12-12 17:14:58 -0500301 {`e(1)`, `function e takes exactly 0 positional arguments (1 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400302 {`e(k=1)`, `{"k": 1}`},
303 {`e(kwargs={})`, `{"kwargs": {}}`},
alandonovanb7e3b1f2018-12-12 17:14:58 -0500304 {`f()`, `function f takes at least 1 positional argument (0 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400305 {`f(0)`, `(0, 42, (), {})`},
306 {`f(0)`, `(0, 42, (), {})`},
307 {`f(0, 1)`, `(0, 1, (), {})`},
308 {`f(0, 1, 2)`, `(0, 1, (2,), {})`},
309 {`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`},
310 {`f(a=0)`, `(0, 42, (), {})`},
311 {`f(0, b=1)`, `(0, 1, (), {})`},
312 {`f(0, a=1)`, `function f got multiple values for keyword argument "a"`},
313 {`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
alandonovan7a866322018-11-21 14:57:52 -0500314 {`f(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, // github.com/google/skylark/issues/135
alandonovan81601212018-10-22 13:41:53 -0400315 `(0, 1, (3, 4), {"x": 2, "y": 5, "z": 6})`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400316 } {
317 var got string
Alan Donovane3deafe2018-10-23 11:05:09 -0400318 if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400319 got = err.Error()
320 } else {
321 got = v.String()
322 }
323 if got != test.want {
324 t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
325 }
326 }
327}
328
Alan Donovane3deafe2018-10-23 11:05:09 -0400329// TestPrint ensures that the Starlark print function calls
Alan Donovan312d1a52017-10-02 10:10:28 -0400330// Thread.Print, if provided.
331func TestPrint(t *testing.T) {
332 const src = `
333print("hello")
alandonovan88085a42018-12-14 19:09:49 -0500334def f(): print("hello", "world", sep=", ")
Alan Donovan312d1a52017-10-02 10:10:28 -0400335f()
336`
337 buf := new(bytes.Buffer)
Alan Donovane3deafe2018-10-23 11:05:09 -0400338 print := func(thread *starlark.Thread, msg string) {
Alan Donovan312d1a52017-10-02 10:10:28 -0400339 caller := thread.Caller()
alandonovan93f3e0c2018-03-30 10:42:28 -0400340 fmt.Fprintf(buf, "%s: %s: %s\n",
alandonovancc7dbc22018-07-02 12:30:24 -0400341 caller.Position(), caller.Callable().Name(), msg)
Alan Donovan312d1a52017-10-02 10:10:28 -0400342 }
Alan Donovane3deafe2018-10-23 11:05:09 -0400343 thread := &starlark.Thread{Print: print}
alandonovan2c1f3622018-12-17 13:10:16 -0500344 if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400345 t.Fatal(err)
346 }
alandonovan2c1f3622018-12-17 13:10:16 -0500347 want := "foo.star:2: <toplevel>: hello\n" +
348 "foo.star:3: f: hello, world\n"
Alan Donovan312d1a52017-10-02 10:10:28 -0400349 if got := buf.String(); got != want {
350 t.Errorf("output was %s, want %s", got, want)
351 }
352}
353
Alan Donovan312d1a52017-10-02 10:10:28 -0400354func reportEvalError(tb testing.TB, err error) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400355 if err, ok := err.(*starlark.EvalError); ok {
Alan Donovan312d1a52017-10-02 10:10:28 -0400356 tb.Fatal(err.Backtrace())
357 }
358 tb.Fatal(err)
359}
360
361// TestInt exercises the Int.Int64 and Int.Uint64 methods.
362// If we can move their logic into math/big, delete this test.
363func TestInt(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400364 one := starlark.MakeInt(1)
Alan Donovan312d1a52017-10-02 10:10:28 -0400365
366 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400367 i starlark.Int
Alan Donovan312d1a52017-10-02 10:10:28 -0400368 wantInt64 string
369 wantUint64 string
370 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400371 {starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"},
372 {starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"},
373 {starlark.MakeInt64(-1), "-1", "error"},
374 {starlark.MakeInt64(0), "0", "0"},
375 {starlark.MakeInt64(1), "1", "1"},
376 {starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"},
377 {starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"},
378 {starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"},
Alan Donovan312d1a52017-10-02 10:10:28 -0400379 } {
380 gotInt64, gotUint64 := "error", "error"
381 if i, ok := test.i.Int64(); ok {
382 gotInt64 = fmt.Sprint(i)
383 }
384 if u, ok := test.i.Uint64(); ok {
385 gotUint64 = fmt.Sprint(u)
386 }
387 if gotInt64 != test.wantInt64 {
388 t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64)
389 }
390 if gotUint64 != test.wantUint64 {
391 t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64)
392 }
393 }
394}
395
396func TestBacktrace(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400397 // This test ensures continuity of the stack of active Starlark
Alan Donovan312d1a52017-10-02 10:10:28 -0400398 // functions, including propagation through built-ins such as 'min'
399 // (though min does not itself appear in the stack).
400 const src = `
401def f(x): return 1//x
402def g(x): f(x)
403def h(): return min([1, 2, 0], key=g)
404def i(): return h()
405i()
406`
Alan Donovane3deafe2018-10-23 11:05:09 -0400407 thread := new(starlark.Thread)
408 _, err := starlark.ExecFile(thread, "crash.star", src, nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400409 switch err := err.(type) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400410 case *starlark.EvalError:
Alan Donovan312d1a52017-10-02 10:10:28 -0400411 got := err.Backtrace()
alandonovan93f3e0c2018-03-30 10:42:28 -0400412 // Compiled code currently has no column information.
Alan Donovan312d1a52017-10-02 10:10:28 -0400413 const want = `Traceback (most recent call last):
Alan Donovane3deafe2018-10-23 11:05:09 -0400414 crash.star:6: in <toplevel>
415 crash.star:5: in i
416 crash.star:4: in h
alandonovancc7dbc22018-07-02 12:30:24 -0400417 <builtin>:1: in min
Alan Donovane3deafe2018-10-23 11:05:09 -0400418 crash.star:3: in g
419 crash.star:2: in f
Alan Donovan312d1a52017-10-02 10:10:28 -0400420Error: floored division by zero`
421 if got != want {
422 t.Errorf("error was %s, want %s", got, want)
423 }
424 case nil:
425 t.Error("ExecFile succeeded unexpectedly")
426 default:
427 t.Errorf("ExecFile failed with %v, wanted *EvalError", err)
428 }
429}
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400430
431// TestRepeatedExec parses and resolves a file syntax tree once then
alandonovana1b28d82018-03-13 10:59:24 -0400432// executes it repeatedly with different values of its predeclared variables.
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400433func TestRepeatedExec(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400434 predeclared := starlark.StringDict{"x": starlark.None}
435 _, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400436 if err != nil {
alandonovan93f3e0c2018-03-30 10:42:28 -0400437 t.Fatal(err)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400438 }
439
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400440 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400441 x, want starlark.Value
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400442 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400443 {x: starlark.MakeInt(42), want: starlark.MakeInt(84)},
444 {x: starlark.String("mur"), want: starlark.String("murmur")},
445 {x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}},
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400446 } {
alandonovan93f3e0c2018-03-30 10:42:28 -0400447 predeclared["x"] = test.x // update the values in dictionary
Alan Donovane3deafe2018-10-23 11:05:09 -0400448 thread := new(starlark.Thread)
alandonovan93f3e0c2018-03-30 10:42:28 -0400449 if globals, err := prog.Init(thread, predeclared); err != nil {
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400450 t.Errorf("x=%v: %v", test.x, err) // exec error
Alan Donovane3deafe2018-10-23 11:05:09 -0400451 } else if eq, err := starlark.Equal(globals["y"], test.want); err != nil {
Jay Conrod7761e752017-10-11 22:13:56 -0400452 t.Errorf("x=%v: %v", test.x, err) // comparison error
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400453 } else if !eq {
alandonovan93f3e0c2018-03-30 10:42:28 -0400454 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400455 }
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400456 }
457}
alandonovan04850cb2017-10-11 14:16:28 -0400458
alandonovan2c1f3622018-12-17 13:10:16 -0500459// TestEmptyFilePosition ensures that even Programs
460// from empty files have a valid position.
461func TestEmptyPosition(t *testing.T) {
462 var predeclared starlark.StringDict
463 for _, content := range []string{"", "empty = False"} {
464 _, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has)
465 if err != nil {
466 t.Fatal(err)
467 }
468 if got, want := prog.Filename(), "hello.star"; got != want {
469 t.Errorf("Program.Filename() = %q, want %q", got, want)
470 }
471 }
472}
473
alandonovan04850cb2017-10-11 14:16:28 -0400474// TestUnpackUserDefined tests that user-defined
Alan Donovane3deafe2018-10-23 11:05:09 -0400475// implementations of starlark.Value may be unpacked.
alandonovan04850cb2017-10-11 14:16:28 -0400476func TestUnpackUserDefined(t *testing.T) {
477 // success
478 want := new(hasfields)
479 var x *hasfields
Alan Donovane3deafe2018-10-23 11:05:09 -0400480 if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil {
alandonovan04850cb2017-10-11 14:16:28 -0400481 t.Errorf("UnpackArgs failed: %v", err)
482 }
483 if x != want {
484 t.Errorf("for x, got %v, want %v", x, want)
485 }
486
487 // failure
Alan Donovane3deafe2018-10-23 11:05:09 -0400488 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x)
alandonovan04850cb2017-10-11 14:16:28 -0400489 if want := "unpack: for parameter 1: got int, want hasfields"; fmt.Sprint(err) != want {
490 t.Errorf("unpack args error = %q, want %q", err, want)
491 }
492}
Alessandro Arzilli3b628ff2018-12-05 15:04:35 +0100493
494func TestDocstring(t *testing.T) {
495 globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", `
496def somefunc():
497 "somefunc doc"
498 return 0
499`, nil)
500
501 if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" {
502 t.Fatal("docstring not found")
503 }
504}
alandonovan2c1f3622018-12-17 13:10:16 -0500505
506func TestFrameLocals(t *testing.T) {
507 // trace prints a nice stack trace including argument
508 // values of calls to Starlark functions.
509 trace := func(thread *starlark.Thread) string {
510 buf := new(bytes.Buffer)
511 for fr := thread.TopFrame(); fr != nil; fr = fr.Parent() {
512 fmt.Fprintf(buf, "%s(", fr.Callable().Name())
513 if fn, ok := fr.Callable().(*starlark.Function); ok {
514 for i := 0; i < fn.NumParams(); i++ {
515 if i > 0 {
516 buf.WriteString(", ")
517 }
518 name, _ := fn.Param(i)
519 fmt.Fprintf(buf, "%s=%s", name, fr.Local(i))
520 }
521 } else {
522 buf.WriteString("...") // a built-in function
523 }
524 buf.WriteString(")\n")
525 }
526 return buf.String()
527 }
528
529 var got string
530 builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
531 got = trace(thread)
532 return starlark.None, nil
533 }
534 predeclared := starlark.StringDict{
535 "builtin": starlark.NewBuiltin("builtin", builtin),
536 }
537 _, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", `
538def f(x, y): builtin()
539def g(z): f(z, z*z)
540g(7)
541`, predeclared)
542 if err != nil {
543 t.Errorf("ExecFile failed: %v", err)
544 }
545
546 var want = `
547builtin(...)
548f(x=7, y=49)
549g(z=7)
550<toplevel>()
551`[1:]
552 if got != want {
553 t.Errorf("got <<%s>>, want <<%s>>", got, want)
554 }
555}
alandonovanc122e652019-01-03 18:11:01 -0500556
557type badType string
558
559func (b *badType) String() string { return "badType" }
560func (b *badType) Type() string { return "badType:" + string(*b) } // panics if b==nil
561func (b *badType) Truth() starlark.Bool { return true }
562func (b *badType) Hash() (uint32, error) { return 0, nil }
563func (b *badType) Freeze() {}
564
565var _ starlark.Value = new(badType)
566
567// TestUnpackErrorBadType verifies that the Unpack functions fail
568// gracefully when a parameter's default value's Type method panics.
569func TestUnpackErrorBadType(t *testing.T) {
570 for _, test := range []struct {
571 x *badType
572 want string
573 }{
574 {new(badType), "got NoneType, want badType"}, // Starlark type name
575 {nil, "got NoneType, want *starlark_test.badType"}, // Go type name
576 } {
577 err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x)
578 if err == nil {
579 t.Errorf("UnpackArgs succeeded unexpectedly")
580 continue
581 }
582 if !strings.Contains(err.Error(), test.want) {
583 t.Errorf("UnpackArgs error %q does not contain %q", err, test.want)
584 }
585 }
586}