blob: 49792a1af56f4e0d870c9f1c4b436f06ea29c503 [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"
Alan Donovan557c1f12019-02-04 16:12:53 -050012 "sort"
Alessandro Arzilli58464402018-12-13 17:43:18 +010013 "strings"
Alan Donovan312d1a52017-10-02 10:10:28 -040014 "testing"
15
Alan Donovan6beab7e2018-10-31 17:53:09 -040016 "go.starlark.net/internal/chunkedfile"
17 "go.starlark.net/resolve"
alandonovan7a866322018-11-21 14:57:52 -050018 "go.starlark.net/starlark"
Alan Donovan6beab7e2018-10-31 17:53:09 -040019 "go.starlark.net/starlarktest"
20 "go.starlark.net/syntax"
Alan Donovan312d1a52017-10-02 10:10:28 -040021)
22
Alan Donovanc0b6b762018-12-18 13:09:56 -050023// A test may enable non-standard options by containing (e.g.) "option:recursion".
24func setOptions(src string) {
Alan Donovanc0b6b762018-12-18 13:09:56 -050025 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 Donovan557c1f12019-02-04 16:12:53 -0500188func newHasFields(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
189 if len(args)+len(kwargs) > 0 {
190 return nil, fmt.Errorf("%s: unexpected arguments", b.Name())
191 }
Alan Donovane3deafe2018-10-23 11:05:09 -0400192 return &hasfields{attrs: make(map[string]starlark.Value)}, nil
Alan Donovan312d1a52017-10-02 10:10:28 -0400193}
194
195// hasfields is a test-only implementation of HasAttrs.
196// It permits any field to be set.
197// Clients will likely want to provide their own implementation,
198// so we don't have any public implementation.
199type hasfields struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400200 attrs starlark.StringDict
Alan Donovan312d1a52017-10-02 10:10:28 -0400201 frozen bool
202}
203
alandonovan15b1cb92018-02-26 14:38:20 -0500204var (
Alan Donovane3deafe2018-10-23 11:05:09 -0400205 _ starlark.HasAttrs = (*hasfields)(nil)
206 _ starlark.HasBinary = (*hasfields)(nil)
alandonovan15b1cb92018-02-26 14:38:20 -0500207)
Alan Donovan312d1a52017-10-02 10:10:28 -0400208
209func (hf *hasfields) String() string { return "hasfields" }
210func (hf *hasfields) Type() string { return "hasfields" }
alandonovan7a866322018-11-21 14:57:52 -0500211func (hf *hasfields) Truth() starlark.Bool { return true }
Alan Donovan312d1a52017-10-02 10:10:28 -0400212func (hf *hasfields) Hash() (uint32, error) { return 42, nil }
213
214func (hf *hasfields) Freeze() {
215 if !hf.frozen {
216 hf.frozen = true
217 for _, v := range hf.attrs {
218 v.Freeze()
219 }
220 }
221}
222
Alan Donovane3deafe2018-10-23 11:05:09 -0400223func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil }
Alan Donovan312d1a52017-10-02 10:10:28 -0400224
Alan Donovane3deafe2018-10-23 11:05:09 -0400225func (hf *hasfields) SetField(name string, val starlark.Value) error {
Alan Donovan312d1a52017-10-02 10:10:28 -0400226 if hf.frozen {
227 return fmt.Errorf("cannot set field on a frozen hasfields")
228 }
alandonovan6afa1bb2019-02-06 17:49:05 -0500229 if strings.HasPrefix(name, "no") { // for testing
230 return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name))
Alan Donovan557c1f12019-02-04 16:12:53 -0500231 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400232 hf.attrs[name] = val
233 return nil
234}
235
236func (hf *hasfields) AttrNames() []string {
237 names := make([]string, 0, len(hf.attrs))
238 for key := range hf.attrs {
239 names = append(names, key)
240 }
Alan Donovan557c1f12019-02-04 16:12:53 -0500241 sort.Strings(names)
Alan Donovan312d1a52017-10-02 10:10:28 -0400242 return names
243}
244
Alan Donovane3deafe2018-10-23 11:05:09 -0400245func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
alandonovan15b1cb92018-02-26 14:38:20 -0500246 // This method exists so we can exercise 'list += x'
247 // where x is not Iterable but defines list+x.
248 if op == syntax.PLUS {
Alan Donovane3deafe2018-10-23 11:05:09 -0400249 if _, ok := y.(*starlark.List); ok {
250 return starlark.MakeInt(42), nil // list+hasfields is 42
alandonovan15b1cb92018-02-26 14:38:20 -0500251 }
252 }
253 return nil, nil
254}
255
Alan Donovan312d1a52017-10-02 10:10:28 -0400256func TestParameterPassing(t *testing.T) {
257 const filename = "parameters.go"
258 const src = `
259def a():
260 return
261def b(a, b):
262 return a, b
263def c(a, b=42):
264 return a, b
265def d(*args):
266 return args
267def e(**kwargs):
268 return kwargs
269def f(a, b=42, *args, **kwargs):
270 return a, b, args, kwargs
Alan Donovan52153852019-02-13 19:18:15 -0500271def g(a, b=42, *args, c=123, **kwargs):
272 return a, b, args, c, kwargs
273def h(a, b=42, *, c=123, **kwargs):
274 return a, b, c, kwargs
alandonovan8313b542019-02-15 13:17:43 -0500275def i(a, b=42, *, c, d=123, e, **kwargs):
276 return a, b, c, d, e, kwargs
277def j(a, b=42, *args, c, d=123, e, **kwargs):
278 return a, b, args, c, d, e, kwargs
Alan Donovan312d1a52017-10-02 10:10:28 -0400279`
280
Alan Donovane3deafe2018-10-23 11:05:09 -0400281 thread := new(starlark.Thread)
282 globals, err := starlark.ExecFile(thread, filename, src, nil)
alandonovana1b28d82018-03-13 10:59:24 -0400283 if err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400284 t.Fatal(err)
285 }
286
287 for _, test := range []struct{ src, want string }{
alandonovan8313b542019-02-15 13:17:43 -0500288 // a()
Alan Donovan312d1a52017-10-02 10:10:28 -0400289 {`a()`, `None`},
alandonovan8313b542019-02-15 13:17:43 -0500290 {`a(1)`, `function a accepts no arguments (1 given)`},
291
292 // b(a, b)
293 {`b()`, `function b missing 2 arguments (a, b)`},
294 {`b(1)`, `function b missing 1 argument (b)`},
295 {`b(a=1)`, `function b missing 1 argument (b)`},
296 {`b(b=1)`, `function b missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400297 {`b(1, 2)`, `(1, 2)`},
298 {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
alandonovan8313b542019-02-15 13:17:43 -0500299 {`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400300 {`b(1, b=2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500301 {`b(1, a=2)`, `function b got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400302 {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`},
303 {`b(a=1, b=2)`, `(1, 2)`},
304 {`b(b=1, a=2)`, `(2, 1)`},
305 {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`},
306 {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`},
alandonovan8313b542019-02-15 13:17:43 -0500307
308 // c(a, b=42)
309 {`c()`, `function c missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400310 {`c(1)`, `(1, 42)`},
311 {`c(1, 2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500312 {`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400313 {`c(1, b=2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500314 {`c(1, a=2)`, `function c got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400315 {`c(a=1, b=2)`, `(1, 2)`},
316 {`c(b=1, a=2)`, `(2, 1)`},
alandonovan8313b542019-02-15 13:17:43 -0500317
318 // d(*args)
Alan Donovan312d1a52017-10-02 10:10:28 -0400319 {`d()`, `()`},
320 {`d(1)`, `(1,)`},
321 {`d(1, 2)`, `(1, 2)`},
322 {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`},
323 {`d(args=[])`, `function d got an unexpected keyword argument "args"`},
alandonovan8313b542019-02-15 13:17:43 -0500324
325 // e(**kwargs)
Alan Donovan312d1a52017-10-02 10:10:28 -0400326 {`e()`, `{}`},
alandonovan8313b542019-02-15 13:17:43 -0500327 {`e(1)`, `function e accepts 0 positional arguments (1 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400328 {`e(k=1)`, `{"k": 1}`},
329 {`e(kwargs={})`, `{"kwargs": {}}`},
alandonovan8313b542019-02-15 13:17:43 -0500330
331 // f(a, b=42, *args, **kwargs)
332 {`f()`, `function f missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400333 {`f(0)`, `(0, 42, (), {})`},
334 {`f(0)`, `(0, 42, (), {})`},
335 {`f(0, 1)`, `(0, 1, (), {})`},
336 {`f(0, 1, 2)`, `(0, 1, (2,), {})`},
337 {`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`},
338 {`f(a=0)`, `(0, 42, (), {})`},
339 {`f(0, b=1)`, `(0, 1, (), {})`},
alandonovan8313b542019-02-15 13:17:43 -0500340 {`f(0, a=1)`, `function f got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400341 {`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
alandonovan7a866322018-11-21 14:57:52 -0500342 {`f(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, // github.com/google/skylark/issues/135
alandonovan81601212018-10-22 13:41:53 -0400343 `(0, 1, (3, 4), {"x": 2, "y": 5, "z": 6})`},
Alan Donovan52153852019-02-13 19:18:15 -0500344
alandonovan8313b542019-02-15 13:17:43 -0500345 // g(a, b=42, *args, c=123, **kwargs)
346 {`g()`, `function g missing 1 argument (a)`},
Alan Donovan52153852019-02-13 19:18:15 -0500347 {`g(0)`, `(0, 42, (), 123, {})`},
348 {`g(0, 1)`, `(0, 1, (), 123, {})`},
349 {`g(0, 1, 2)`, `(0, 1, (2,), 123, {})`},
350 {`g(0, 1, 2, 3)`, `(0, 1, (2, 3), 123, {})`},
351 {`g(a=0)`, `(0, 42, (), 123, {})`},
352 {`g(0, b=1)`, `(0, 1, (), 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500353 {`g(0, a=1)`, `function g got multiple values for parameter "a"`},
Alan Donovan52153852019-02-13 19:18:15 -0500354 {`g(0, b=1, c=2, d=3)`, `(0, 1, (), 2, {"d": 3})`},
355 {`g(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`,
356 `(0, 1, (3, 4), 123, {"x": 2, "y": 5, "z": 6})`},
357
alandonovan8313b542019-02-15 13:17:43 -0500358 // h(a, b=42, *, c=123, **kwargs)
359 {`h()`, `function h missing 1 argument (a)`},
Alan Donovan52153852019-02-13 19:18:15 -0500360 {`h(0)`, `(0, 42, 123, {})`},
361 {`h(0, 1)`, `(0, 1, 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500362 {`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`},
Alan Donovan52153852019-02-13 19:18:15 -0500363 {`h(a=0)`, `(0, 42, 123, {})`},
364 {`h(0, b=1)`, `(0, 1, 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500365 {`h(0, a=1)`, `function h got multiple values for parameter "a"`},
Alan Donovan52153852019-02-13 19:18:15 -0500366 {`h(0, b=1, c=2)`, `(0, 1, 2, {})`},
367 {`h(0, b=1, d=2)`, `(0, 1, 123, {"d": 2})`},
368 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
alandonovan8313b542019-02-15 13:17:43 -0500369 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
370
371 // i(a, b=42, *, c, d=123, e, **kwargs)
372 {`i()`, `function i missing 3 arguments (a, c, e)`},
373 {`i(0)`, `function i missing 2 arguments (c, e)`},
374 {`i(0, 1)`, `function i missing 2 arguments (c, e)`},
375 {`i(0, 1, 2)`, `function i accepts at most 2 positional arguments (3 given)`},
376 {`i(0, 1, e=2)`, `function i missing 1 argument (c)`},
377 {`i(0, 1, 2, 3)`, `function i accepts at most 2 positional arguments (4 given)`},
378 {`i(a=0)`, `function i missing 2 arguments (c, e)`},
379 {`i(0, b=1)`, `function i missing 2 arguments (c, e)`},
380 {`i(0, a=1)`, `function i got multiple values for parameter "a"`},
381 {`i(0, b=1, c=2)`, `function i missing 1 argument (e)`},
382 {`i(0, b=1, d=2)`, `function i missing 2 arguments (c, e)`},
383 {`i(0, b=1, c=2, d=3)`, `function i missing 1 argument (e)`},
384 {`i(0, b=1, c=2, d=3, e=4)`, `(0, 1, 2, 3, 4, {})`},
385 {`i(0, 1, b=1, c=2, d=3, e=4)`, `function i got multiple values for parameter "b"`},
386
387 // j(a, b=42, *args, c, d=123, e, **kwargs)
388 {`j()`, `function j missing 3 arguments (a, c, e)`},
389 {`j(0)`, `function j missing 2 arguments (c, e)`},
390 {`j(0, 1)`, `function j missing 2 arguments (c, e)`},
391 {`j(0, 1, 2)`, `function j missing 2 arguments (c, e)`},
392 {`j(0, 1, e=2)`, `function j missing 1 argument (c)`},
393 {`j(0, 1, 2, 3)`, `function j missing 2 arguments (c, e)`},
394 {`j(a=0)`, `function j missing 2 arguments (c, e)`},
395 {`j(0, b=1)`, `function j missing 2 arguments (c, e)`},
396 {`j(0, a=1)`, `function j got multiple values for parameter "a"`},
397 {`j(0, b=1, c=2)`, `function j missing 1 argument (e)`},
398 {`j(0, b=1, d=2)`, `function j missing 2 arguments (c, e)`},
399 {`j(0, b=1, c=2, d=3)`, `function j missing 1 argument (e)`},
400 {`j(0, b=1, c=2, d=3, e=4)`, `(0, 1, (), 2, 3, 4, {})`},
401 {`j(0, 1, b=1, c=2, d=3, e=4)`, `function j got multiple values for parameter "b"`},
402 {`j(0, 1, 2, c=3, e=4)`, `(0, 1, (2,), 3, 123, 4, {})`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400403 } {
404 var got string
Alan Donovane3deafe2018-10-23 11:05:09 -0400405 if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400406 got = err.Error()
407 } else {
408 got = v.String()
409 }
410 if got != test.want {
411 t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
412 }
413 }
414}
415
Alan Donovane3deafe2018-10-23 11:05:09 -0400416// TestPrint ensures that the Starlark print function calls
Alan Donovan312d1a52017-10-02 10:10:28 -0400417// Thread.Print, if provided.
418func TestPrint(t *testing.T) {
419 const src = `
420print("hello")
alandonovan88085a42018-12-14 19:09:49 -0500421def f(): print("hello", "world", sep=", ")
Alan Donovan312d1a52017-10-02 10:10:28 -0400422f()
423`
424 buf := new(bytes.Buffer)
Alan Donovane3deafe2018-10-23 11:05:09 -0400425 print := func(thread *starlark.Thread, msg string) {
Alan Donovan312d1a52017-10-02 10:10:28 -0400426 caller := thread.Caller()
alandonovan93f3e0c2018-03-30 10:42:28 -0400427 fmt.Fprintf(buf, "%s: %s: %s\n",
alandonovancc7dbc22018-07-02 12:30:24 -0400428 caller.Position(), caller.Callable().Name(), msg)
Alan Donovan312d1a52017-10-02 10:10:28 -0400429 }
Alan Donovane3deafe2018-10-23 11:05:09 -0400430 thread := &starlark.Thread{Print: print}
alandonovan2c1f3622018-12-17 13:10:16 -0500431 if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400432 t.Fatal(err)
433 }
alandonovan2c1f3622018-12-17 13:10:16 -0500434 want := "foo.star:2: <toplevel>: hello\n" +
435 "foo.star:3: f: hello, world\n"
Alan Donovan312d1a52017-10-02 10:10:28 -0400436 if got := buf.String(); got != want {
437 t.Errorf("output was %s, want %s", got, want)
438 }
439}
440
Alan Donovan312d1a52017-10-02 10:10:28 -0400441func reportEvalError(tb testing.TB, err error) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400442 if err, ok := err.(*starlark.EvalError); ok {
Alan Donovan312d1a52017-10-02 10:10:28 -0400443 tb.Fatal(err.Backtrace())
444 }
445 tb.Fatal(err)
446}
447
448// TestInt exercises the Int.Int64 and Int.Uint64 methods.
449// If we can move their logic into math/big, delete this test.
450func TestInt(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400451 one := starlark.MakeInt(1)
Alan Donovan312d1a52017-10-02 10:10:28 -0400452
453 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400454 i starlark.Int
Alan Donovan312d1a52017-10-02 10:10:28 -0400455 wantInt64 string
456 wantUint64 string
457 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400458 {starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"},
459 {starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"},
460 {starlark.MakeInt64(-1), "-1", "error"},
461 {starlark.MakeInt64(0), "0", "0"},
462 {starlark.MakeInt64(1), "1", "1"},
463 {starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"},
464 {starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"},
465 {starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"},
Alan Donovan312d1a52017-10-02 10:10:28 -0400466 } {
467 gotInt64, gotUint64 := "error", "error"
468 if i, ok := test.i.Int64(); ok {
469 gotInt64 = fmt.Sprint(i)
470 }
471 if u, ok := test.i.Uint64(); ok {
472 gotUint64 = fmt.Sprint(u)
473 }
474 if gotInt64 != test.wantInt64 {
475 t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64)
476 }
477 if gotUint64 != test.wantUint64 {
478 t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64)
479 }
480 }
481}
482
483func TestBacktrace(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400484 // This test ensures continuity of the stack of active Starlark
alandonovan3d5a0612019-03-08 16:16:44 -0500485 // functions, including propagation through built-ins such as 'min'.
Alan Donovan312d1a52017-10-02 10:10:28 -0400486 const src = `
487def f(x): return 1//x
488def g(x): f(x)
489def h(): return min([1, 2, 0], key=g)
490def i(): return h()
491i()
492`
Alan Donovane3deafe2018-10-23 11:05:09 -0400493 thread := new(starlark.Thread)
494 _, err := starlark.ExecFile(thread, "crash.star", src, nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400495 switch err := err.(type) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400496 case *starlark.EvalError:
Alan Donovan312d1a52017-10-02 10:10:28 -0400497 got := err.Backtrace()
alandonovan93f3e0c2018-03-30 10:42:28 -0400498 // Compiled code currently has no column information.
Alan Donovan312d1a52017-10-02 10:10:28 -0400499 const want = `Traceback (most recent call last):
Alan Donovane3deafe2018-10-23 11:05:09 -0400500 crash.star:6: in <toplevel>
501 crash.star:5: in i
502 crash.star:4: in h
alandonovancc7dbc22018-07-02 12:30:24 -0400503 <builtin>:1: in min
Alan Donovane3deafe2018-10-23 11:05:09 -0400504 crash.star:3: in g
505 crash.star:2: in f
Alan Donovan312d1a52017-10-02 10:10:28 -0400506Error: floored division by zero`
507 if got != want {
508 t.Errorf("error was %s, want %s", got, want)
509 }
510 case nil:
511 t.Error("ExecFile succeeded unexpectedly")
512 default:
513 t.Errorf("ExecFile failed with %v, wanted *EvalError", err)
514 }
515}
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400516
517// TestRepeatedExec parses and resolves a file syntax tree once then
alandonovana1b28d82018-03-13 10:59:24 -0400518// executes it repeatedly with different values of its predeclared variables.
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400519func TestRepeatedExec(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400520 predeclared := starlark.StringDict{"x": starlark.None}
521 _, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400522 if err != nil {
alandonovan93f3e0c2018-03-30 10:42:28 -0400523 t.Fatal(err)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400524 }
525
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400526 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400527 x, want starlark.Value
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400528 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400529 {x: starlark.MakeInt(42), want: starlark.MakeInt(84)},
530 {x: starlark.String("mur"), want: starlark.String("murmur")},
531 {x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}},
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400532 } {
alandonovan93f3e0c2018-03-30 10:42:28 -0400533 predeclared["x"] = test.x // update the values in dictionary
Alan Donovane3deafe2018-10-23 11:05:09 -0400534 thread := new(starlark.Thread)
alandonovan93f3e0c2018-03-30 10:42:28 -0400535 if globals, err := prog.Init(thread, predeclared); err != nil {
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400536 t.Errorf("x=%v: %v", test.x, err) // exec error
Alan Donovane3deafe2018-10-23 11:05:09 -0400537 } else if eq, err := starlark.Equal(globals["y"], test.want); err != nil {
Jay Conrod7761e752017-10-11 22:13:56 -0400538 t.Errorf("x=%v: %v", test.x, err) // comparison error
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400539 } else if !eq {
alandonovan93f3e0c2018-03-30 10:42:28 -0400540 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400541 }
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400542 }
543}
alandonovan04850cb2017-10-11 14:16:28 -0400544
alandonovan2c1f3622018-12-17 13:10:16 -0500545// TestEmptyFilePosition ensures that even Programs
546// from empty files have a valid position.
547func TestEmptyPosition(t *testing.T) {
548 var predeclared starlark.StringDict
549 for _, content := range []string{"", "empty = False"} {
550 _, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has)
551 if err != nil {
552 t.Fatal(err)
553 }
554 if got, want := prog.Filename(), "hello.star"; got != want {
555 t.Errorf("Program.Filename() = %q, want %q", got, want)
556 }
557 }
558}
559
alandonovan04850cb2017-10-11 14:16:28 -0400560// TestUnpackUserDefined tests that user-defined
Alan Donovane3deafe2018-10-23 11:05:09 -0400561// implementations of starlark.Value may be unpacked.
alandonovan04850cb2017-10-11 14:16:28 -0400562func TestUnpackUserDefined(t *testing.T) {
563 // success
564 want := new(hasfields)
565 var x *hasfields
Alan Donovane3deafe2018-10-23 11:05:09 -0400566 if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil {
alandonovan04850cb2017-10-11 14:16:28 -0400567 t.Errorf("UnpackArgs failed: %v", err)
568 }
569 if x != want {
570 t.Errorf("for x, got %v, want %v", x, want)
571 }
572
573 // failure
Alan Donovane3deafe2018-10-23 11:05:09 -0400574 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x)
alandonovan04850cb2017-10-11 14:16:28 -0400575 if want := "unpack: for parameter 1: got int, want hasfields"; fmt.Sprint(err) != want {
576 t.Errorf("unpack args error = %q, want %q", err, want)
577 }
578}
Alessandro Arzilli3b628ff2018-12-05 15:04:35 +0100579
580func TestDocstring(t *testing.T) {
581 globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", `
582def somefunc():
583 "somefunc doc"
584 return 0
585`, nil)
586
587 if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" {
588 t.Fatal("docstring not found")
589 }
590}
alandonovan2c1f3622018-12-17 13:10:16 -0500591
592func TestFrameLocals(t *testing.T) {
593 // trace prints a nice stack trace including argument
594 // values of calls to Starlark functions.
595 trace := func(thread *starlark.Thread) string {
596 buf := new(bytes.Buffer)
597 for fr := thread.TopFrame(); fr != nil; fr = fr.Parent() {
598 fmt.Fprintf(buf, "%s(", fr.Callable().Name())
599 if fn, ok := fr.Callable().(*starlark.Function); ok {
600 for i := 0; i < fn.NumParams(); i++ {
601 if i > 0 {
602 buf.WriteString(", ")
603 }
604 name, _ := fn.Param(i)
605 fmt.Fprintf(buf, "%s=%s", name, fr.Local(i))
606 }
607 } else {
608 buf.WriteString("...") // a built-in function
609 }
610 buf.WriteString(")\n")
611 }
612 return buf.String()
613 }
614
615 var got string
616 builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
617 got = trace(thread)
618 return starlark.None, nil
619 }
620 predeclared := starlark.StringDict{
621 "builtin": starlark.NewBuiltin("builtin", builtin),
622 }
623 _, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", `
624def f(x, y): builtin()
625def g(z): f(z, z*z)
626g(7)
627`, predeclared)
628 if err != nil {
629 t.Errorf("ExecFile failed: %v", err)
630 }
631
632 var want = `
633builtin(...)
634f(x=7, y=49)
635g(z=7)
636<toplevel>()
637`[1:]
638 if got != want {
639 t.Errorf("got <<%s>>, want <<%s>>", got, want)
640 }
641}
alandonovanc122e652019-01-03 18:11:01 -0500642
643type badType string
644
645func (b *badType) String() string { return "badType" }
646func (b *badType) Type() string { return "badType:" + string(*b) } // panics if b==nil
647func (b *badType) Truth() starlark.Bool { return true }
648func (b *badType) Hash() (uint32, error) { return 0, nil }
649func (b *badType) Freeze() {}
650
651var _ starlark.Value = new(badType)
652
653// TestUnpackErrorBadType verifies that the Unpack functions fail
654// gracefully when a parameter's default value's Type method panics.
655func TestUnpackErrorBadType(t *testing.T) {
656 for _, test := range []struct {
657 x *badType
658 want string
659 }{
660 {new(badType), "got NoneType, want badType"}, // Starlark type name
661 {nil, "got NoneType, want *starlark_test.badType"}, // Go type name
662 } {
663 err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x)
664 if err == nil {
665 t.Errorf("UnpackArgs succeeded unexpectedly")
666 continue
667 }
668 if !strings.Contains(err.Error(), test.want) {
669 t.Errorf("UnpackArgs error %q does not contain %q", err, test.want)
670 }
671 }
672}