blob: 00b6e6d1c79f2b3b2d65a9d35cd8c1bb3d973f95 [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"
alandonovanac23acb2020-06-11 17:56:15 -040019 "go.starlark.net/starlarkjson"
20 "go.starlark.net/starlarkstruct"
Alan Donovan6beab7e2018-10-31 17:53:09 -040021 "go.starlark.net/starlarktest"
22 "go.starlark.net/syntax"
Alan Donovan312d1a52017-10-02 10:10:28 -040023)
24
Alan Donovanc0b6b762018-12-18 13:09:56 -050025// A test may enable non-standard options by containing (e.g.) "option:recursion".
26func setOptions(src string) {
Alan Donovanc0b6b762018-12-18 13:09:56 -050027 resolve.AllowGlobalReassign = option(src, "globalreassign")
alandonovan754257e2019-04-03 16:43:05 -040028 resolve.LoadBindsGlobally = option(src, "loadbindsglobally")
Alan Donovanc0b6b762018-12-18 13:09:56 -050029 resolve.AllowLambda = option(src, "lambda")
30 resolve.AllowNestedDef = option(src, "nesteddef")
31 resolve.AllowRecursion = option(src, "recursion")
32 resolve.AllowSet = option(src, "set")
33}
34
35func option(chunk, name string) bool {
36 return strings.Contains(chunk, "option:"+name)
Alan Donovan312d1a52017-10-02 10:10:28 -040037}
38
Nick Santos58de16f2019-10-18 17:42:35 -040039// Wrapper is the type of errors with an Unwrap method; see https://golang.org/pkg/errors.
40type Wrapper interface {
41 Unwrap() error
42}
43
Alan Donovan312d1a52017-10-02 10:10:28 -040044func TestEvalExpr(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -040045 // This is mostly redundant with the new *.star tests.
46 // TODO(adonovan): move checks into *.star files and
47 // reduce this to a mere unit test of starlark.Eval.
48 thread := new(starlark.Thread)
Alan Donovan312d1a52017-10-02 10:10:28 -040049 for _, test := range []struct{ src, want string }{
50 {`123`, `123`},
51 {`-1`, `-1`},
52 {`"a"+"b"`, `"ab"`},
53 {`1+2`, `3`},
54
55 // lists
56 {`[]`, `[]`},
57 {`[1]`, `[1]`},
58 {`[1,]`, `[1]`},
59 {`[1, 2]`, `[1, 2]`},
60 {`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`},
61 {`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`},
62 {`[(x, y) for x in [1, 2] for y in [3, 4]]`,
63 `[(1, 3), (1, 4), (2, 3), (2, 4)]`},
64 {`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`,
65 `[(2, 3), (2, 4)]`},
66 // tuples
67 {`()`, `()`},
68 {`(1)`, `1`},
69 {`(1,)`, `(1,)`},
70 {`(1, 2)`, `(1, 2)`},
71 {`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`},
alandonovanf26cf182019-05-28 16:17:30 -040072 {`1, 2`, `(1, 2)`},
Alan Donovan312d1a52017-10-02 10:10:28 -040073 // dicts
74 {`{}`, `{}`},
75 {`{"a": 1}`, `{"a": 1}`},
76 {`{"a": 1,}`, `{"a": 1}`},
77
78 // conditional
79 {`1 if 3 > 2 else 0`, `1`},
80 {`1 if "foo" else 0`, `1`},
81 {`1 if "" else 0`, `0`},
82
83 // indexing
84 {`["a", "b"][0]`, `"a"`},
85 {`["a", "b"][1]`, `"b"`},
86 {`("a", "b")[0]`, `"a"`},
87 {`("a", "b")[1]`, `"b"`},
88 {`"aΩb"[0]`, `"a"`},
89 {`"aΩb"[1]`, `"\xce"`},
90 {`"aΩb"[3]`, `"b"`},
91 {`{"a": 1}["a"]`, `1`},
92 {`{"a": 1}["b"]`, `key "b" not in dict`},
93 {`{}[[]]`, `unhashable type: list`},
94 {`{"a": 1}[[]]`, `unhashable type: list`},
95 {`[x for x in range(3)]`, "[0, 1, 2]"},
96 } {
97 var got string
Alan Donovane3deafe2018-10-23 11:05:09 -040098 if v, err := starlark.Eval(thread, "<expr>", test.src, nil); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -040099 got = err.Error()
100 } else {
101 got = v.String()
102 }
103 if got != test.want {
104 t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
105 }
106 }
107}
108
109func TestExecFile(t *testing.T) {
Alan Donovanc0b6b762018-12-18 13:09:56 -0500110 defer setOptions("")
Alan Donovane3deafe2018-10-23 11:05:09 -0400111 testdata := starlarktest.DataFile("starlark", ".")
112 thread := &starlark.Thread{Load: load}
113 starlarktest.SetReporter(thread, t)
Alan Donovan312d1a52017-10-02 10:10:28 -0400114 for _, file := range []string{
Alan Donovane3deafe2018-10-23 11:05:09 -0400115 "testdata/assign.star",
116 "testdata/bool.star",
117 "testdata/builtins.star",
118 "testdata/control.star",
119 "testdata/dict.star",
120 "testdata/float.star",
121 "testdata/function.star",
122 "testdata/int.star",
alandonovanac23acb2020-06-11 17:56:15 -0400123 "testdata/json.star",
Alan Donovane3deafe2018-10-23 11:05:09 -0400124 "testdata/list.star",
125 "testdata/misc.star",
126 "testdata/set.star",
127 "testdata/string.star",
128 "testdata/tuple.star",
Alessandro Arzilli58464402018-12-13 17:43:18 +0100129 "testdata/recursion.star",
alandonovan9d977712019-01-04 13:04:59 -0500130 "testdata/module.star",
Alan Donovan312d1a52017-10-02 10:10:28 -0400131 } {
132 filename := filepath.Join(testdata, file)
133 for _, chunk := range chunkedfile.Read(filename, t) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400134 predeclared := starlark.StringDict{
135 "hasfields": starlark.NewBuiltin("hasfields", newHasFields),
Alan Donovan312d1a52017-10-02 10:10:28 -0400136 "fibonacci": fib{},
alandonovanac23acb2020-06-11 17:56:15 -0400137 "struct": starlark.NewBuiltin("struct", starlarkstruct.Make),
Alan Donovan312d1a52017-10-02 10:10:28 -0400138 }
Alessandro Arzilli58464402018-12-13 17:43:18 +0100139
Alan Donovanc0b6b762018-12-18 13:09:56 -0500140 setOptions(chunk.Source)
141 resolve.AllowLambda = true // used extensively
Alessandro Arzilli58464402018-12-13 17:43:18 +0100142
Alan Donovane3deafe2018-10-23 11:05:09 -0400143 _, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared)
Alan Donovan312d1a52017-10-02 10:10:28 -0400144 switch err := err.(type) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400145 case *starlark.EvalError:
Alan Donovan312d1a52017-10-02 10:10:28 -0400146 found := false
alandonovand9868e92019-04-19 14:47:26 -0400147 for i := range err.CallStack {
148 posn := err.CallStack.At(i).Pos
Alan Donovan312d1a52017-10-02 10:10:28 -0400149 if posn.Filename() == filename {
150 chunk.GotError(int(posn.Line), err.Error())
151 found = true
152 break
153 }
154 }
155 if !found {
156 t.Error(err.Backtrace())
157 }
158 case nil:
159 // success
160 default:
Alan Donovanc0b6b762018-12-18 13:09:56 -0500161 t.Errorf("\n%s", err)
Alan Donovan312d1a52017-10-02 10:10:28 -0400162 }
163 chunk.Done()
164 }
165 }
166}
167
168// A fib is an iterable value representing the infinite Fibonacci sequence.
169type fib struct{}
170
alandonovan7a866322018-11-21 14:57:52 -0500171func (t fib) Freeze() {}
172func (t fib) String() string { return "fib" }
173func (t fib) Type() string { return "fib" }
Alan Donovane3deafe2018-10-23 11:05:09 -0400174func (t fib) Truth() starlark.Bool { return true }
alandonovan7a866322018-11-21 14:57:52 -0500175func (t fib) Hash() (uint32, error) { return 0, fmt.Errorf("fib is unhashable") }
Alan Donovane3deafe2018-10-23 11:05:09 -0400176func (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} }
Alan Donovan312d1a52017-10-02 10:10:28 -0400177
178type fibIterator struct{ x, y int }
179
Alan Donovane3deafe2018-10-23 11:05:09 -0400180func (it *fibIterator) Next(p *starlark.Value) bool {
181 *p = starlark.MakeInt(it.x)
Alan Donovan312d1a52017-10-02 10:10:28 -0400182 it.x, it.y = it.y, it.x+it.y
183 return true
184}
185func (it *fibIterator) Done() {}
186
187// load implements the 'load' operation as used in the evaluator tests.
Alan Donovane3deafe2018-10-23 11:05:09 -0400188func load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
189 if module == "assert.star" {
190 return starlarktest.LoadAssertModule()
Alan Donovan312d1a52017-10-02 10:10:28 -0400191 }
alandonovanac23acb2020-06-11 17:56:15 -0400192 if module == "json.star" {
193 return starlark.StringDict{"json": starlarkjson.Module}, nil
194 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400195
196 // TODO(adonovan): test load() using this execution path.
alandonovand9868e92019-04-19 14:47:26 -0400197 filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module)
Alan Donovane3deafe2018-10-23 11:05:09 -0400198 return starlark.ExecFile(thread, filename, nil, nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400199}
200
Alan Donovan557c1f12019-02-04 16:12:53 -0500201func newHasFields(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
202 if len(args)+len(kwargs) > 0 {
203 return nil, fmt.Errorf("%s: unexpected arguments", b.Name())
204 }
Alan Donovane3deafe2018-10-23 11:05:09 -0400205 return &hasfields{attrs: make(map[string]starlark.Value)}, nil
Alan Donovan312d1a52017-10-02 10:10:28 -0400206}
207
208// hasfields is a test-only implementation of HasAttrs.
209// It permits any field to be set.
210// Clients will likely want to provide their own implementation,
211// so we don't have any public implementation.
212type hasfields struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400213 attrs starlark.StringDict
Alan Donovan312d1a52017-10-02 10:10:28 -0400214 frozen bool
215}
216
alandonovan15b1cb92018-02-26 14:38:20 -0500217var (
Alan Donovane3deafe2018-10-23 11:05:09 -0400218 _ starlark.HasAttrs = (*hasfields)(nil)
219 _ starlark.HasBinary = (*hasfields)(nil)
alandonovan15b1cb92018-02-26 14:38:20 -0500220)
Alan Donovan312d1a52017-10-02 10:10:28 -0400221
222func (hf *hasfields) String() string { return "hasfields" }
223func (hf *hasfields) Type() string { return "hasfields" }
alandonovan7a866322018-11-21 14:57:52 -0500224func (hf *hasfields) Truth() starlark.Bool { return true }
Alan Donovan312d1a52017-10-02 10:10:28 -0400225func (hf *hasfields) Hash() (uint32, error) { return 42, nil }
226
227func (hf *hasfields) Freeze() {
228 if !hf.frozen {
229 hf.frozen = true
230 for _, v := range hf.attrs {
231 v.Freeze()
232 }
233 }
234}
235
Alan Donovane3deafe2018-10-23 11:05:09 -0400236func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil }
Alan Donovan312d1a52017-10-02 10:10:28 -0400237
Alan Donovane3deafe2018-10-23 11:05:09 -0400238func (hf *hasfields) SetField(name string, val starlark.Value) error {
Alan Donovan312d1a52017-10-02 10:10:28 -0400239 if hf.frozen {
240 return fmt.Errorf("cannot set field on a frozen hasfields")
241 }
alandonovan6afa1bb2019-02-06 17:49:05 -0500242 if strings.HasPrefix(name, "no") { // for testing
243 return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name))
Alan Donovan557c1f12019-02-04 16:12:53 -0500244 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400245 hf.attrs[name] = val
246 return nil
247}
248
249func (hf *hasfields) AttrNames() []string {
250 names := make([]string, 0, len(hf.attrs))
251 for key := range hf.attrs {
252 names = append(names, key)
253 }
Alan Donovan557c1f12019-02-04 16:12:53 -0500254 sort.Strings(names)
Alan Donovan312d1a52017-10-02 10:10:28 -0400255 return names
256}
257
Alan Donovane3deafe2018-10-23 11:05:09 -0400258func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
alandonovan15b1cb92018-02-26 14:38:20 -0500259 // This method exists so we can exercise 'list += x'
260 // where x is not Iterable but defines list+x.
261 if op == syntax.PLUS {
Alan Donovane3deafe2018-10-23 11:05:09 -0400262 if _, ok := y.(*starlark.List); ok {
263 return starlark.MakeInt(42), nil // list+hasfields is 42
alandonovan15b1cb92018-02-26 14:38:20 -0500264 }
265 }
266 return nil, nil
267}
268
Alan Donovan312d1a52017-10-02 10:10:28 -0400269func TestParameterPassing(t *testing.T) {
270 const filename = "parameters.go"
271 const src = `
272def a():
273 return
274def b(a, b):
275 return a, b
276def c(a, b=42):
277 return a, b
278def d(*args):
279 return args
280def e(**kwargs):
281 return kwargs
282def f(a, b=42, *args, **kwargs):
283 return a, b, args, kwargs
Alan Donovan52153852019-02-13 19:18:15 -0500284def g(a, b=42, *args, c=123, **kwargs):
285 return a, b, args, c, kwargs
286def h(a, b=42, *, c=123, **kwargs):
287 return a, b, c, kwargs
alandonovan8313b542019-02-15 13:17:43 -0500288def i(a, b=42, *, c, d=123, e, **kwargs):
289 return a, b, c, d, e, kwargs
290def j(a, b=42, *args, c, d=123, e, **kwargs):
291 return a, b, args, c, d, e, kwargs
Alan Donovan312d1a52017-10-02 10:10:28 -0400292`
293
Alan Donovane3deafe2018-10-23 11:05:09 -0400294 thread := new(starlark.Thread)
295 globals, err := starlark.ExecFile(thread, filename, src, nil)
alandonovana1b28d82018-03-13 10:59:24 -0400296 if err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400297 t.Fatal(err)
298 }
299
300 for _, test := range []struct{ src, want string }{
alandonovan8313b542019-02-15 13:17:43 -0500301 // a()
Alan Donovan312d1a52017-10-02 10:10:28 -0400302 {`a()`, `None`},
alandonovan8313b542019-02-15 13:17:43 -0500303 {`a(1)`, `function a accepts no arguments (1 given)`},
304
305 // b(a, b)
306 {`b()`, `function b missing 2 arguments (a, b)`},
307 {`b(1)`, `function b missing 1 argument (b)`},
308 {`b(a=1)`, `function b missing 1 argument (b)`},
309 {`b(b=1)`, `function b missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400310 {`b(1, 2)`, `(1, 2)`},
311 {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
alandonovan8313b542019-02-15 13:17:43 -0500312 {`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400313 {`b(1, b=2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500314 {`b(1, a=2)`, `function b got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400315 {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`},
316 {`b(a=1, b=2)`, `(1, 2)`},
317 {`b(b=1, a=2)`, `(2, 1)`},
318 {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`},
319 {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`},
alandonovan8313b542019-02-15 13:17:43 -0500320
321 // c(a, b=42)
322 {`c()`, `function c missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400323 {`c(1)`, `(1, 42)`},
324 {`c(1, 2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500325 {`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400326 {`c(1, b=2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500327 {`c(1, a=2)`, `function c got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400328 {`c(a=1, b=2)`, `(1, 2)`},
329 {`c(b=1, a=2)`, `(2, 1)`},
alandonovan8313b542019-02-15 13:17:43 -0500330
331 // d(*args)
Alan Donovan312d1a52017-10-02 10:10:28 -0400332 {`d()`, `()`},
333 {`d(1)`, `(1,)`},
334 {`d(1, 2)`, `(1, 2)`},
335 {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`},
336 {`d(args=[])`, `function d got an unexpected keyword argument "args"`},
alandonovan8313b542019-02-15 13:17:43 -0500337
338 // e(**kwargs)
Alan Donovan312d1a52017-10-02 10:10:28 -0400339 {`e()`, `{}`},
alandonovan8313b542019-02-15 13:17:43 -0500340 {`e(1)`, `function e accepts 0 positional arguments (1 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400341 {`e(k=1)`, `{"k": 1}`},
342 {`e(kwargs={})`, `{"kwargs": {}}`},
alandonovan8313b542019-02-15 13:17:43 -0500343
344 // f(a, b=42, *args, **kwargs)
345 {`f()`, `function f missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400346 {`f(0)`, `(0, 42, (), {})`},
347 {`f(0)`, `(0, 42, (), {})`},
348 {`f(0, 1)`, `(0, 1, (), {})`},
349 {`f(0, 1, 2)`, `(0, 1, (2,), {})`},
350 {`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`},
351 {`f(a=0)`, `(0, 42, (), {})`},
352 {`f(0, b=1)`, `(0, 1, (), {})`},
alandonovan8313b542019-02-15 13:17:43 -0500353 {`f(0, a=1)`, `function f got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400354 {`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
alandonovan7a866322018-11-21 14:57:52 -0500355 {`f(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, // github.com/google/skylark/issues/135
alandonovan81601212018-10-22 13:41:53 -0400356 `(0, 1, (3, 4), {"x": 2, "y": 5, "z": 6})`},
Alan Donovan52153852019-02-13 19:18:15 -0500357
alandonovan8313b542019-02-15 13:17:43 -0500358 // g(a, b=42, *args, c=123, **kwargs)
359 {`g()`, `function g missing 1 argument (a)`},
Alan Donovan52153852019-02-13 19:18:15 -0500360 {`g(0)`, `(0, 42, (), 123, {})`},
361 {`g(0, 1)`, `(0, 1, (), 123, {})`},
362 {`g(0, 1, 2)`, `(0, 1, (2,), 123, {})`},
363 {`g(0, 1, 2, 3)`, `(0, 1, (2, 3), 123, {})`},
364 {`g(a=0)`, `(0, 42, (), 123, {})`},
365 {`g(0, b=1)`, `(0, 1, (), 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500366 {`g(0, a=1)`, `function g got multiple values for parameter "a"`},
Alan Donovan52153852019-02-13 19:18:15 -0500367 {`g(0, b=1, c=2, d=3)`, `(0, 1, (), 2, {"d": 3})`},
368 {`g(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`,
369 `(0, 1, (3, 4), 123, {"x": 2, "y": 5, "z": 6})`},
370
alandonovan8313b542019-02-15 13:17:43 -0500371 // h(a, b=42, *, c=123, **kwargs)
372 {`h()`, `function h missing 1 argument (a)`},
Alan Donovan52153852019-02-13 19:18:15 -0500373 {`h(0)`, `(0, 42, 123, {})`},
374 {`h(0, 1)`, `(0, 1, 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500375 {`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`},
Alan Donovan52153852019-02-13 19:18:15 -0500376 {`h(a=0)`, `(0, 42, 123, {})`},
377 {`h(0, b=1)`, `(0, 1, 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500378 {`h(0, a=1)`, `function h got multiple values for parameter "a"`},
Alan Donovan52153852019-02-13 19:18:15 -0500379 {`h(0, b=1, c=2)`, `(0, 1, 2, {})`},
380 {`h(0, b=1, d=2)`, `(0, 1, 123, {"d": 2})`},
381 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
alandonovan8313b542019-02-15 13:17:43 -0500382 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
383
384 // i(a, b=42, *, c, d=123, e, **kwargs)
385 {`i()`, `function i missing 3 arguments (a, c, e)`},
386 {`i(0)`, `function i missing 2 arguments (c, e)`},
387 {`i(0, 1)`, `function i missing 2 arguments (c, e)`},
388 {`i(0, 1, 2)`, `function i accepts at most 2 positional arguments (3 given)`},
389 {`i(0, 1, e=2)`, `function i missing 1 argument (c)`},
390 {`i(0, 1, 2, 3)`, `function i accepts at most 2 positional arguments (4 given)`},
391 {`i(a=0)`, `function i missing 2 arguments (c, e)`},
392 {`i(0, b=1)`, `function i missing 2 arguments (c, e)`},
393 {`i(0, a=1)`, `function i got multiple values for parameter "a"`},
394 {`i(0, b=1, c=2)`, `function i missing 1 argument (e)`},
395 {`i(0, b=1, d=2)`, `function i missing 2 arguments (c, e)`},
396 {`i(0, b=1, c=2, d=3)`, `function i missing 1 argument (e)`},
397 {`i(0, b=1, c=2, d=3, e=4)`, `(0, 1, 2, 3, 4, {})`},
398 {`i(0, 1, b=1, c=2, d=3, e=4)`, `function i got multiple values for parameter "b"`},
399
400 // j(a, b=42, *args, c, d=123, e, **kwargs)
401 {`j()`, `function j missing 3 arguments (a, c, e)`},
402 {`j(0)`, `function j missing 2 arguments (c, e)`},
403 {`j(0, 1)`, `function j missing 2 arguments (c, e)`},
404 {`j(0, 1, 2)`, `function j missing 2 arguments (c, e)`},
405 {`j(0, 1, e=2)`, `function j missing 1 argument (c)`},
406 {`j(0, 1, 2, 3)`, `function j missing 2 arguments (c, e)`},
407 {`j(a=0)`, `function j missing 2 arguments (c, e)`},
408 {`j(0, b=1)`, `function j missing 2 arguments (c, e)`},
409 {`j(0, a=1)`, `function j got multiple values for parameter "a"`},
410 {`j(0, b=1, c=2)`, `function j missing 1 argument (e)`},
411 {`j(0, b=1, d=2)`, `function j missing 2 arguments (c, e)`},
412 {`j(0, b=1, c=2, d=3)`, `function j missing 1 argument (e)`},
413 {`j(0, b=1, c=2, d=3, e=4)`, `(0, 1, (), 2, 3, 4, {})`},
414 {`j(0, 1, b=1, c=2, d=3, e=4)`, `function j got multiple values for parameter "b"`},
415 {`j(0, 1, 2, c=3, e=4)`, `(0, 1, (2,), 3, 123, 4, {})`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400416 } {
417 var got string
Alan Donovane3deafe2018-10-23 11:05:09 -0400418 if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400419 got = err.Error()
420 } else {
421 got = v.String()
422 }
423 if got != test.want {
424 t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
425 }
426 }
427}
428
Alan Donovane3deafe2018-10-23 11:05:09 -0400429// TestPrint ensures that the Starlark print function calls
Alan Donovan312d1a52017-10-02 10:10:28 -0400430// Thread.Print, if provided.
431func TestPrint(t *testing.T) {
432 const src = `
433print("hello")
alandonovan88085a42018-12-14 19:09:49 -0500434def f(): print("hello", "world", sep=", ")
Alan Donovan312d1a52017-10-02 10:10:28 -0400435f()
436`
437 buf := new(bytes.Buffer)
Alan Donovane3deafe2018-10-23 11:05:09 -0400438 print := func(thread *starlark.Thread, msg string) {
alandonovand9868e92019-04-19 14:47:26 -0400439 caller := thread.CallFrame(1)
440 fmt.Fprintf(buf, "%s: %s: %s\n", caller.Pos, caller.Name, msg)
Alan Donovan312d1a52017-10-02 10:10:28 -0400441 }
Alan Donovane3deafe2018-10-23 11:05:09 -0400442 thread := &starlark.Thread{Print: print}
alandonovan2c1f3622018-12-17 13:10:16 -0500443 if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400444 t.Fatal(err)
445 }
alandonovan30b85782019-05-28 16:29:08 -0400446 want := "foo.star:2:6: <toplevel>: hello\n" +
447 "foo.star:3:15: f: hello, world\n"
Alan Donovan312d1a52017-10-02 10:10:28 -0400448 if got := buf.String(); got != want {
449 t.Errorf("output was %s, want %s", got, want)
450 }
451}
452
Alan Donovan312d1a52017-10-02 10:10:28 -0400453func reportEvalError(tb testing.TB, err error) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400454 if err, ok := err.(*starlark.EvalError); ok {
Alan Donovan312d1a52017-10-02 10:10:28 -0400455 tb.Fatal(err.Backtrace())
456 }
457 tb.Fatal(err)
458}
459
460// TestInt exercises the Int.Int64 and Int.Uint64 methods.
461// If we can move their logic into math/big, delete this test.
462func TestInt(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400463 one := starlark.MakeInt(1)
Alan Donovan312d1a52017-10-02 10:10:28 -0400464
465 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400466 i starlark.Int
Alan Donovan312d1a52017-10-02 10:10:28 -0400467 wantInt64 string
468 wantUint64 string
469 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400470 {starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"},
471 {starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"},
472 {starlark.MakeInt64(-1), "-1", "error"},
473 {starlark.MakeInt64(0), "0", "0"},
474 {starlark.MakeInt64(1), "1", "1"},
475 {starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"},
476 {starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"},
477 {starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"},
Alan Donovan312d1a52017-10-02 10:10:28 -0400478 } {
479 gotInt64, gotUint64 := "error", "error"
480 if i, ok := test.i.Int64(); ok {
481 gotInt64 = fmt.Sprint(i)
482 }
483 if u, ok := test.i.Uint64(); ok {
484 gotUint64 = fmt.Sprint(u)
485 }
486 if gotInt64 != test.wantInt64 {
487 t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64)
488 }
489 if gotUint64 != test.wantUint64 {
490 t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64)
491 }
492 }
493}
494
Nick Santos58de16f2019-10-18 17:42:35 -0400495func backtrace(t *testing.T, err error) string {
496 switch err := err.(type) {
497 case *starlark.EvalError:
498 return err.Backtrace()
499 case nil:
500 t.Fatalf("ExecFile succeeded unexpectedly")
501 default:
502 t.Fatalf("ExecFile failed with %v, wanted *EvalError", err)
alandonovan2494ae92019-04-04 15:38:05 -0400503 }
Nick Santos58de16f2019-10-18 17:42:35 -0400504 panic("unreachable")
505}
alandonovan2494ae92019-04-04 15:38:05 -0400506
Nick Santos58de16f2019-10-18 17:42:35 -0400507func TestBacktrace(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400508 // This test ensures continuity of the stack of active Starlark
alandonovan3d5a0612019-03-08 16:16:44 -0500509 // functions, including propagation through built-ins such as 'min'.
Alan Donovan312d1a52017-10-02 10:10:28 -0400510 const src = `
511def f(x): return 1//x
alandonovan69054822020-11-11 14:23:03 -0500512def g(x): return f(x)
Alan Donovan312d1a52017-10-02 10:10:28 -0400513def h(): return min([1, 2, 0], key=g)
514def i(): return h()
515i()
516`
Alan Donovane3deafe2018-10-23 11:05:09 -0400517 thread := new(starlark.Thread)
518 _, err := starlark.ExecFile(thread, "crash.star", src, nil)
alandonovan2494ae92019-04-04 15:38:05 -0400519 // Compiled code currently has no column information.
520 const want = `Traceback (most recent call last):
alandonovan30b85782019-05-28 16:29:08 -0400521 crash.star:6:2: in <toplevel>
522 crash.star:5:18: in i
523 crash.star:4:20: in h
alandonovan2494ae92019-04-04 15:38:05 -0400524 <builtin>: in min
alandonovan69054822020-11-11 14:23:03 -0500525 crash.star:3:19: in g
alandonovan30b85782019-05-28 16:29:08 -0400526 crash.star:2:19: in f
Alan Donovan312d1a52017-10-02 10:10:28 -0400527Error: floored division by zero`
Nick Santos58de16f2019-10-18 17:42:35 -0400528 if got := backtrace(t, err); got != want {
alandonovan2494ae92019-04-04 15:38:05 -0400529 t.Errorf("error was %s, want %s", got, want)
530 }
531
532 // Additionally, ensure that errors originating in
533 // Starlark and/or Go each have an accurate frame.
534 //
535 // This program fails in Starlark (f) if x==0,
536 // or in Go (string.join) if x is non-zero.
537 const src2 = `
538def f(): ''.join([1//i])
539f()
540`
541 for i, want := range []string{
542 0: `Traceback (most recent call last):
alandonovan30b85782019-05-28 16:29:08 -0400543 crash.star:3:2: in <toplevel>
544 crash.star:2:20: in f
alandonovan2494ae92019-04-04 15:38:05 -0400545Error: floored division by zero`,
546 1: `Traceback (most recent call last):
alandonovan30b85782019-05-28 16:29:08 -0400547 crash.star:3:2: in <toplevel>
548 crash.star:2:17: in f
alandonovan2494ae92019-04-04 15:38:05 -0400549 <builtin>: in join
550Error: join: in list, want string, got int`,
551 } {
552 globals := starlark.StringDict{"i": starlark.MakeInt(i)}
553 _, err := starlark.ExecFile(thread, "crash.star", src2, globals)
Nick Santos58de16f2019-10-18 17:42:35 -0400554 if got := backtrace(t, err); got != want {
Alan Donovan312d1a52017-10-02 10:10:28 -0400555 t.Errorf("error was %s, want %s", got, want)
556 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400557 }
558}
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400559
Nick Santos58de16f2019-10-18 17:42:35 -0400560func TestLoadBacktrace(t *testing.T) {
561 // This test ensures that load() does NOT preserve stack traces,
562 // but that API callers can get them with Unwrap().
563 // For discussion, see:
564 // https://github.com/google/starlark-go/pull/244
565 const src = `
566load('crash.star', 'x')
567`
568 const loadedSrc = `
569def f(x):
570 return 1 // x
571
572f(0)
573`
574 thread := new(starlark.Thread)
575 thread.Load = func(t *starlark.Thread, module string) (starlark.StringDict, error) {
576 return starlark.ExecFile(new(starlark.Thread), module, loadedSrc, nil)
577 }
578 _, err := starlark.ExecFile(thread, "root.star", src, nil)
579
580 const want = `Traceback (most recent call last):
581 root.star:2:1: in <toplevel>
582Error: cannot load crash.star: floored division by zero`
583 if got := backtrace(t, err); got != want {
584 t.Errorf("error was %s, want %s", got, want)
585 }
586
587 unwrapEvalError := func(err error) *starlark.EvalError {
588 var result *starlark.EvalError
589 for {
590 if evalErr, ok := err.(*starlark.EvalError); ok {
591 result = evalErr
592 }
593
594 // TODO: use errors.Unwrap when go >=1.13 is everywhere.
595 wrapper, isWrapper := err.(Wrapper)
596 if !isWrapper {
597 break
598 }
599 err = wrapper.Unwrap()
600 }
601 return result
602 }
603
604 unwrappedErr := unwrapEvalError(err)
605 const wantUnwrapped = `Traceback (most recent call last):
606 crash.star:5:2: in <toplevel>
607 crash.star:3:12: in f
608Error: floored division by zero`
609 if got := backtrace(t, unwrappedErr); got != wantUnwrapped {
610 t.Errorf("error was %s, want %s", got, wantUnwrapped)
611 }
612
613}
614
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400615// TestRepeatedExec parses and resolves a file syntax tree once then
alandonovana1b28d82018-03-13 10:59:24 -0400616// executes it repeatedly with different values of its predeclared variables.
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400617func TestRepeatedExec(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400618 predeclared := starlark.StringDict{"x": starlark.None}
619 _, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400620 if err != nil {
alandonovan93f3e0c2018-03-30 10:42:28 -0400621 t.Fatal(err)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400622 }
623
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400624 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400625 x, want starlark.Value
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400626 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400627 {x: starlark.MakeInt(42), want: starlark.MakeInt(84)},
628 {x: starlark.String("mur"), want: starlark.String("murmur")},
629 {x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}},
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400630 } {
alandonovan93f3e0c2018-03-30 10:42:28 -0400631 predeclared["x"] = test.x // update the values in dictionary
Alan Donovane3deafe2018-10-23 11:05:09 -0400632 thread := new(starlark.Thread)
alandonovan93f3e0c2018-03-30 10:42:28 -0400633 if globals, err := prog.Init(thread, predeclared); err != nil {
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400634 t.Errorf("x=%v: %v", test.x, err) // exec error
Alan Donovane3deafe2018-10-23 11:05:09 -0400635 } else if eq, err := starlark.Equal(globals["y"], test.want); err != nil {
Jay Conrod7761e752017-10-11 22:13:56 -0400636 t.Errorf("x=%v: %v", test.x, err) // comparison error
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400637 } else if !eq {
alandonovan93f3e0c2018-03-30 10:42:28 -0400638 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400639 }
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400640 }
641}
alandonovan04850cb2017-10-11 14:16:28 -0400642
alandonovan2c1f3622018-12-17 13:10:16 -0500643// TestEmptyFilePosition ensures that even Programs
644// from empty files have a valid position.
645func TestEmptyPosition(t *testing.T) {
646 var predeclared starlark.StringDict
647 for _, content := range []string{"", "empty = False"} {
648 _, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has)
649 if err != nil {
650 t.Fatal(err)
651 }
652 if got, want := prog.Filename(), "hello.star"; got != want {
653 t.Errorf("Program.Filename() = %q, want %q", got, want)
654 }
655 }
656}
657
alandonovan04850cb2017-10-11 14:16:28 -0400658// TestUnpackUserDefined tests that user-defined
Alan Donovane3deafe2018-10-23 11:05:09 -0400659// implementations of starlark.Value may be unpacked.
alandonovan04850cb2017-10-11 14:16:28 -0400660func TestUnpackUserDefined(t *testing.T) {
661 // success
662 want := new(hasfields)
663 var x *hasfields
Alan Donovane3deafe2018-10-23 11:05:09 -0400664 if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil {
alandonovan04850cb2017-10-11 14:16:28 -0400665 t.Errorf("UnpackArgs failed: %v", err)
666 }
667 if x != want {
668 t.Errorf("for x, got %v, want %v", x, want)
669 }
670
671 // failure
Alan Donovane3deafe2018-10-23 11:05:09 -0400672 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x)
alandonovandaf30b62019-05-03 16:35:58 -0400673 if want := "unpack: for parameter x: got int, want hasfields"; fmt.Sprint(err) != want {
alandonovan04850cb2017-10-11 14:16:28 -0400674 t.Errorf("unpack args error = %q, want %q", err, want)
675 }
676}
Alessandro Arzilli3b628ff2018-12-05 15:04:35 +0100677
Nick Santos0aa95692020-05-19 12:54:36 -0400678type optionalStringUnpacker struct {
679 str string
680 isSet bool
681}
682
683func (o *optionalStringUnpacker) Unpack(v starlark.Value) error {
684 s, ok := starlark.AsString(v)
685 if !ok {
686 return fmt.Errorf("got %s, want string", v.Type())
687 }
688 o.str = s
689 o.isSet = ok
690 return nil
691}
692
693func TestUnpackCustomUnpacker(t *testing.T) {
694 a := optionalStringUnpacker{}
695 wantA := optionalStringUnpacker{str: "a", isSet: true}
696 b := optionalStringUnpacker{str: "b"}
697 wantB := optionalStringUnpacker{str: "b"}
698
699 // Success
700 if err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.String("a")}, nil, "a?", &a, "b?", &b); err != nil {
701 t.Errorf("UnpackArgs failed: %v", err)
702 }
703 if a != wantA {
704 t.Errorf("for a, got %v, want %v", a, wantA)
705 }
706 if b != wantB {
707 t.Errorf("for b, got %v, want %v", b, wantB)
708 }
709
710 // failure
711 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "a", &a)
712 if want := "unpack: for parameter a: got int, want string"; fmt.Sprint(err) != want {
713 t.Errorf("unpack args error = %q, want %q", err, want)
714 }
715}
716
Alessandro Arzilli3b628ff2018-12-05 15:04:35 +0100717func TestDocstring(t *testing.T) {
718 globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", `
719def somefunc():
720 "somefunc doc"
721 return 0
722`, nil)
723
724 if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" {
725 t.Fatal("docstring not found")
726 }
727}
alandonovan2c1f3622018-12-17 13:10:16 -0500728
729func TestFrameLocals(t *testing.T) {
730 // trace prints a nice stack trace including argument
731 // values of calls to Starlark functions.
732 trace := func(thread *starlark.Thread) string {
733 buf := new(bytes.Buffer)
alandonovand9868e92019-04-19 14:47:26 -0400734 for i := 0; i < thread.CallStackDepth(); i++ {
735 fr := thread.DebugFrame(i)
alandonovan2c1f3622018-12-17 13:10:16 -0500736 fmt.Fprintf(buf, "%s(", fr.Callable().Name())
737 if fn, ok := fr.Callable().(*starlark.Function); ok {
738 for i := 0; i < fn.NumParams(); i++ {
739 if i > 0 {
740 buf.WriteString(", ")
741 }
742 name, _ := fn.Param(i)
743 fmt.Fprintf(buf, "%s=%s", name, fr.Local(i))
744 }
745 } else {
746 buf.WriteString("...") // a built-in function
747 }
748 buf.WriteString(")\n")
749 }
750 return buf.String()
751 }
752
753 var got string
754 builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
755 got = trace(thread)
756 return starlark.None, nil
757 }
758 predeclared := starlark.StringDict{
759 "builtin": starlark.NewBuiltin("builtin", builtin),
760 }
761 _, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", `
762def f(x, y): builtin()
763def g(z): f(z, z*z)
764g(7)
765`, predeclared)
766 if err != nil {
767 t.Errorf("ExecFile failed: %v", err)
768 }
769
770 var want = `
771builtin(...)
772f(x=7, y=49)
773g(z=7)
774<toplevel>()
775`[1:]
776 if got != want {
777 t.Errorf("got <<%s>>, want <<%s>>", got, want)
778 }
779}
alandonovanc122e652019-01-03 18:11:01 -0500780
781type badType string
782
783func (b *badType) String() string { return "badType" }
784func (b *badType) Type() string { return "badType:" + string(*b) } // panics if b==nil
785func (b *badType) Truth() starlark.Bool { return true }
786func (b *badType) Hash() (uint32, error) { return 0, nil }
787func (b *badType) Freeze() {}
788
789var _ starlark.Value = new(badType)
790
791// TestUnpackErrorBadType verifies that the Unpack functions fail
792// gracefully when a parameter's default value's Type method panics.
793func TestUnpackErrorBadType(t *testing.T) {
794 for _, test := range []struct {
795 x *badType
796 want string
797 }{
798 {new(badType), "got NoneType, want badType"}, // Starlark type name
799 {nil, "got NoneType, want *starlark_test.badType"}, // Go type name
800 } {
801 err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x)
802 if err == nil {
803 t.Errorf("UnpackArgs succeeded unexpectedly")
804 continue
805 }
806 if !strings.Contains(err.Error(), test.want) {
807 t.Errorf("UnpackArgs error %q does not contain %q", err, test.want)
808 }
809 }
810}
alandonovan28350e62019-10-21 14:58:36 -0400811
812// Regression test for github.com/google/starlark-go/issues/233.
813func TestREPLChunk(t *testing.T) {
814 thread := new(starlark.Thread)
815 globals := make(starlark.StringDict)
816 exec := func(src string) {
817 f, err := syntax.Parse("<repl>", src, 0)
818 if err != nil {
819 t.Fatal(err)
820 }
821 if err := starlark.ExecREPLChunk(f, thread, globals); err != nil {
822 t.Fatal(err)
823 }
824 }
825
826 exec("x = 0; y = 0")
827 if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "0 0"; got != want {
828 t.Fatalf("chunk1: got %s, want %s", got, want)
829 }
830
831 exec("x += 1; y = y + 1")
832 if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "1 1"; got != want {
833 t.Fatalf("chunk2: got %s, want %s", got, want)
834 }
835}
alandonovan949cc6f2020-08-21 10:29:38 -0400836
837func TestCancel(t *testing.T) {
838 // A thread cancelled before it begins executes no code.
839 {
840 thread := new(starlark.Thread)
841 thread.Cancel("nope")
842 _, err := starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil)
843 if fmt.Sprint(err) != "Starlark computation cancelled: nope" {
844 t.Errorf("execution returned error %q, want cancellation", err)
845 }
846
847 // cancellation is sticky
848 _, err = starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil)
849 if fmt.Sprint(err) != "Starlark computation cancelled: nope" {
850 t.Errorf("execution returned error %q, want cancellation", err)
851 }
852 }
853 // A thread cancelled during a built-in executes no more code.
854 {
855 thread := new(starlark.Thread)
856 predeclared := starlark.StringDict{
857 "stopit": starlark.NewBuiltin("stopit", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
858 thread.Cancel(fmt.Sprint(args[0]))
859 return starlark.None, nil
860 }),
861 }
862 _, err := starlark.ExecFile(thread, "stopit.star", `msg = 'nope'; stopit(msg); x = 1//0`, predeclared)
863 if fmt.Sprint(err) != `Starlark computation cancelled: "nope"` {
864 t.Errorf("execution returned error %q, want cancellation", err)
865 }
866 }
867}
868
869func TestExecutionSteps(t *testing.T) {
870 // A Thread records the number of computation steps.
871 thread := new(starlark.Thread)
872 countSteps := func(n int) (uint64, error) {
873 predeclared := starlark.StringDict{"n": starlark.MakeInt(n)}
874 steps0 := thread.ExecutionSteps()
875 _, err := starlark.ExecFile(thread, "steps.star", `squares = [x*x for x in range(n)]`, predeclared)
876 return thread.ExecutionSteps() - steps0, err
877 }
878 steps100, err := countSteps(1000)
879 if err != nil {
880 t.Errorf("execution failed: %v", err)
881 }
882 steps10000, err := countSteps(100000)
883 if err != nil {
884 t.Errorf("execution failed: %v", err)
885 }
886 if ratio := float64(steps10000) / float64(steps100); ratio < 99 || ratio > 101 {
887 t.Errorf("computation steps did not increase linearly: f(100)=%d, f(10000)=%d, ratio=%g, want ~100", steps100, steps10000, ratio)
888 }
889
890 // Exceeding the step limit causes cancellation.
891 thread.SetMaxExecutionSteps(1000)
892 _, err = countSteps(1000)
893 if fmt.Sprint(err) != "Starlark computation cancelled: too many steps" {
894 t.Errorf("execution returned error %q, want cancellation", err)
895 }
896}