blob: 9752fe8e21da2ebcf0af7a51866b26c01603b7be [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"
alandonovane55f6032020-11-18 13:34:35 -050011 "os/exec"
Alan Donovan312d1a52017-10-02 10:10:28 -040012 "path/filepath"
alandonovane81fc952020-12-10 10:18:46 -050013 "reflect"
Alan Donovan557c1f12019-02-04 16:12:53 -050014 "sort"
Alessandro Arzilli58464402018-12-13 17:43:18 +010015 "strings"
Alan Donovan312d1a52017-10-02 10:10:28 -040016 "testing"
17
Alan Donovan6beab7e2018-10-31 17:53:09 -040018 "go.starlark.net/internal/chunkedfile"
19 "go.starlark.net/resolve"
alandonovan7a866322018-11-21 14:57:52 -050020 "go.starlark.net/starlark"
alandonovanac23acb2020-06-11 17:56:15 -040021 "go.starlark.net/starlarkjson"
22 "go.starlark.net/starlarkstruct"
Alan Donovan6beab7e2018-10-31 17:53:09 -040023 "go.starlark.net/starlarktest"
24 "go.starlark.net/syntax"
Alan Donovan312d1a52017-10-02 10:10:28 -040025)
26
Alan Donovanc0b6b762018-12-18 13:09:56 -050027// A test may enable non-standard options by containing (e.g.) "option:recursion".
28func setOptions(src string) {
Alan Donovanc0b6b762018-12-18 13:09:56 -050029 resolve.AllowGlobalReassign = option(src, "globalreassign")
alandonovan754257e2019-04-03 16:43:05 -040030 resolve.LoadBindsGlobally = option(src, "loadbindsglobally")
Alan Donovanc0b6b762018-12-18 13:09:56 -050031 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",
alandonovanebe61bd2021-02-12 16:57:32 -0500118 "testdata/bytes.star",
Alan Donovane3deafe2018-10-23 11:05:09 -0400119 "testdata/control.star",
120 "testdata/dict.star",
121 "testdata/float.star",
122 "testdata/function.star",
123 "testdata/int.star",
alandonovanac23acb2020-06-11 17:56:15 -0400124 "testdata/json.star",
Alan Donovane3deafe2018-10-23 11:05:09 -0400125 "testdata/list.star",
126 "testdata/misc.star",
127 "testdata/set.star",
128 "testdata/string.star",
129 "testdata/tuple.star",
Alessandro Arzilli58464402018-12-13 17:43:18 +0100130 "testdata/recursion.star",
alandonovan9d977712019-01-04 13:04:59 -0500131 "testdata/module.star",
Alan Donovan312d1a52017-10-02 10:10:28 -0400132 } {
133 filename := filepath.Join(testdata, file)
134 for _, chunk := range chunkedfile.Read(filename, t) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400135 predeclared := starlark.StringDict{
136 "hasfields": starlark.NewBuiltin("hasfields", newHasFields),
Alan Donovan312d1a52017-10-02 10:10:28 -0400137 "fibonacci": fib{},
alandonovanac23acb2020-06-11 17:56:15 -0400138 "struct": starlark.NewBuiltin("struct", starlarkstruct.Make),
Alan Donovan312d1a52017-10-02 10:10:28 -0400139 }
Alessandro Arzilli58464402018-12-13 17:43:18 +0100140
Alan Donovanc0b6b762018-12-18 13:09:56 -0500141 setOptions(chunk.Source)
142 resolve.AllowLambda = true // used extensively
Alessandro Arzilli58464402018-12-13 17:43:18 +0100143
Alan Donovane3deafe2018-10-23 11:05:09 -0400144 _, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared)
Alan Donovan312d1a52017-10-02 10:10:28 -0400145 switch err := err.(type) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400146 case *starlark.EvalError:
Alan Donovan312d1a52017-10-02 10:10:28 -0400147 found := false
alandonovand9868e92019-04-19 14:47:26 -0400148 for i := range err.CallStack {
149 posn := err.CallStack.At(i).Pos
Alan Donovan312d1a52017-10-02 10:10:28 -0400150 if posn.Filename() == filename {
151 chunk.GotError(int(posn.Line), err.Error())
152 found = true
153 break
154 }
155 }
156 if !found {
157 t.Error(err.Backtrace())
158 }
159 case nil:
160 // success
161 default:
Alan Donovanc0b6b762018-12-18 13:09:56 -0500162 t.Errorf("\n%s", err)
Alan Donovan312d1a52017-10-02 10:10:28 -0400163 }
164 chunk.Done()
165 }
166 }
167}
168
169// A fib is an iterable value representing the infinite Fibonacci sequence.
170type fib struct{}
171
alandonovan7a866322018-11-21 14:57:52 -0500172func (t fib) Freeze() {}
173func (t fib) String() string { return "fib" }
174func (t fib) Type() string { return "fib" }
Alan Donovane3deafe2018-10-23 11:05:09 -0400175func (t fib) Truth() starlark.Bool { return true }
alandonovan7a866322018-11-21 14:57:52 -0500176func (t fib) Hash() (uint32, error) { return 0, fmt.Errorf("fib is unhashable") }
Alan Donovane3deafe2018-10-23 11:05:09 -0400177func (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} }
Alan Donovan312d1a52017-10-02 10:10:28 -0400178
179type fibIterator struct{ x, y int }
180
Alan Donovane3deafe2018-10-23 11:05:09 -0400181func (it *fibIterator) Next(p *starlark.Value) bool {
182 *p = starlark.MakeInt(it.x)
Alan Donovan312d1a52017-10-02 10:10:28 -0400183 it.x, it.y = it.y, it.x+it.y
184 return true
185}
186func (it *fibIterator) Done() {}
187
188// load implements the 'load' operation as used in the evaluator tests.
Alan Donovane3deafe2018-10-23 11:05:09 -0400189func load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
190 if module == "assert.star" {
191 return starlarktest.LoadAssertModule()
Alan Donovan312d1a52017-10-02 10:10:28 -0400192 }
alandonovanac23acb2020-06-11 17:56:15 -0400193 if module == "json.star" {
194 return starlark.StringDict{"json": starlarkjson.Module}, nil
195 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400196
197 // TODO(adonovan): test load() using this execution path.
alandonovand9868e92019-04-19 14:47:26 -0400198 filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module)
Alan Donovane3deafe2018-10-23 11:05:09 -0400199 return starlark.ExecFile(thread, filename, nil, nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400200}
201
Alan Donovan557c1f12019-02-04 16:12:53 -0500202func newHasFields(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
203 if len(args)+len(kwargs) > 0 {
204 return nil, fmt.Errorf("%s: unexpected arguments", b.Name())
205 }
Alan Donovane3deafe2018-10-23 11:05:09 -0400206 return &hasfields{attrs: make(map[string]starlark.Value)}, nil
Alan Donovan312d1a52017-10-02 10:10:28 -0400207}
208
209// hasfields is a test-only implementation of HasAttrs.
210// It permits any field to be set.
211// Clients will likely want to provide their own implementation,
212// so we don't have any public implementation.
213type hasfields struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400214 attrs starlark.StringDict
Alan Donovan312d1a52017-10-02 10:10:28 -0400215 frozen bool
216}
217
alandonovan15b1cb92018-02-26 14:38:20 -0500218var (
Alan Donovane3deafe2018-10-23 11:05:09 -0400219 _ starlark.HasAttrs = (*hasfields)(nil)
220 _ starlark.HasBinary = (*hasfields)(nil)
alandonovan15b1cb92018-02-26 14:38:20 -0500221)
Alan Donovan312d1a52017-10-02 10:10:28 -0400222
223func (hf *hasfields) String() string { return "hasfields" }
224func (hf *hasfields) Type() string { return "hasfields" }
alandonovan7a866322018-11-21 14:57:52 -0500225func (hf *hasfields) Truth() starlark.Bool { return true }
Alan Donovan312d1a52017-10-02 10:10:28 -0400226func (hf *hasfields) Hash() (uint32, error) { return 42, nil }
227
228func (hf *hasfields) Freeze() {
229 if !hf.frozen {
230 hf.frozen = true
231 for _, v := range hf.attrs {
232 v.Freeze()
233 }
234 }
235}
236
Alan Donovane3deafe2018-10-23 11:05:09 -0400237func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil }
Alan Donovan312d1a52017-10-02 10:10:28 -0400238
Alan Donovane3deafe2018-10-23 11:05:09 -0400239func (hf *hasfields) SetField(name string, val starlark.Value) error {
Alan Donovan312d1a52017-10-02 10:10:28 -0400240 if hf.frozen {
241 return fmt.Errorf("cannot set field on a frozen hasfields")
242 }
alandonovan6afa1bb2019-02-06 17:49:05 -0500243 if strings.HasPrefix(name, "no") { // for testing
244 return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name))
Alan Donovan557c1f12019-02-04 16:12:53 -0500245 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400246 hf.attrs[name] = val
247 return nil
248}
249
250func (hf *hasfields) AttrNames() []string {
251 names := make([]string, 0, len(hf.attrs))
252 for key := range hf.attrs {
253 names = append(names, key)
254 }
Alan Donovan557c1f12019-02-04 16:12:53 -0500255 sort.Strings(names)
Alan Donovan312d1a52017-10-02 10:10:28 -0400256 return names
257}
258
Alan Donovane3deafe2018-10-23 11:05:09 -0400259func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
alandonovan15b1cb92018-02-26 14:38:20 -0500260 // This method exists so we can exercise 'list += x'
261 // where x is not Iterable but defines list+x.
262 if op == syntax.PLUS {
Alan Donovane3deafe2018-10-23 11:05:09 -0400263 if _, ok := y.(*starlark.List); ok {
264 return starlark.MakeInt(42), nil // list+hasfields is 42
alandonovan15b1cb92018-02-26 14:38:20 -0500265 }
266 }
267 return nil, nil
268}
269
Alan Donovan312d1a52017-10-02 10:10:28 -0400270func TestParameterPassing(t *testing.T) {
271 const filename = "parameters.go"
272 const src = `
273def a():
274 return
275def b(a, b):
276 return a, b
277def c(a, b=42):
278 return a, b
279def d(*args):
280 return args
281def e(**kwargs):
282 return kwargs
283def f(a, b=42, *args, **kwargs):
284 return a, b, args, kwargs
Alan Donovan52153852019-02-13 19:18:15 -0500285def g(a, b=42, *args, c=123, **kwargs):
286 return a, b, args, c, kwargs
287def h(a, b=42, *, c=123, **kwargs):
288 return a, b, c, kwargs
alandonovan8313b542019-02-15 13:17:43 -0500289def i(a, b=42, *, c, d=123, e, **kwargs):
290 return a, b, c, d, e, kwargs
291def j(a, b=42, *args, c, d=123, e, **kwargs):
292 return a, b, args, c, d, e, kwargs
Alan Donovan312d1a52017-10-02 10:10:28 -0400293`
294
Alan Donovane3deafe2018-10-23 11:05:09 -0400295 thread := new(starlark.Thread)
296 globals, err := starlark.ExecFile(thread, filename, src, nil)
alandonovana1b28d82018-03-13 10:59:24 -0400297 if err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400298 t.Fatal(err)
299 }
300
alandonovan501b6c72020-11-13 16:43:45 -0500301 // All errors are dynamic; see resolver for static errors.
Alan Donovan312d1a52017-10-02 10:10:28 -0400302 for _, test := range []struct{ src, want string }{
alandonovan8313b542019-02-15 13:17:43 -0500303 // a()
Alan Donovan312d1a52017-10-02 10:10:28 -0400304 {`a()`, `None`},
alandonovan8313b542019-02-15 13:17:43 -0500305 {`a(1)`, `function a accepts no arguments (1 given)`},
306
307 // b(a, b)
308 {`b()`, `function b missing 2 arguments (a, b)`},
309 {`b(1)`, `function b missing 1 argument (b)`},
310 {`b(a=1)`, `function b missing 1 argument (b)`},
311 {`b(b=1)`, `function b missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400312 {`b(1, 2)`, `(1, 2)`},
313 {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
alandonovan8313b542019-02-15 13:17:43 -0500314 {`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400315 {`b(1, b=2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500316 {`b(1, a=2)`, `function b got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400317 {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`},
318 {`b(a=1, b=2)`, `(1, 2)`},
319 {`b(b=1, a=2)`, `(2, 1)`},
320 {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`},
321 {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`},
alandonovan8313b542019-02-15 13:17:43 -0500322
323 // c(a, b=42)
324 {`c()`, `function c missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400325 {`c(1)`, `(1, 42)`},
326 {`c(1, 2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500327 {`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400328 {`c(1, b=2)`, `(1, 2)`},
alandonovan8313b542019-02-15 13:17:43 -0500329 {`c(1, a=2)`, `function c got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400330 {`c(a=1, b=2)`, `(1, 2)`},
331 {`c(b=1, a=2)`, `(2, 1)`},
alandonovan8313b542019-02-15 13:17:43 -0500332
333 // d(*args)
Alan Donovan312d1a52017-10-02 10:10:28 -0400334 {`d()`, `()`},
335 {`d(1)`, `(1,)`},
336 {`d(1, 2)`, `(1, 2)`},
337 {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`},
338 {`d(args=[])`, `function d got an unexpected keyword argument "args"`},
alandonovan8313b542019-02-15 13:17:43 -0500339
340 // e(**kwargs)
Alan Donovan312d1a52017-10-02 10:10:28 -0400341 {`e()`, `{}`},
alandonovan8313b542019-02-15 13:17:43 -0500342 {`e(1)`, `function e accepts 0 positional arguments (1 given)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400343 {`e(k=1)`, `{"k": 1}`},
344 {`e(kwargs={})`, `{"kwargs": {}}`},
alandonovan8313b542019-02-15 13:17:43 -0500345
346 // f(a, b=42, *args, **kwargs)
347 {`f()`, `function f missing 1 argument (a)`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400348 {`f(0)`, `(0, 42, (), {})`},
349 {`f(0)`, `(0, 42, (), {})`},
350 {`f(0, 1)`, `(0, 1, (), {})`},
351 {`f(0, 1, 2)`, `(0, 1, (2,), {})`},
352 {`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`},
353 {`f(a=0)`, `(0, 42, (), {})`},
354 {`f(0, b=1)`, `(0, 1, (), {})`},
alandonovan8313b542019-02-15 13:17:43 -0500355 {`f(0, a=1)`, `function f got multiple values for parameter "a"`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400356 {`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
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})`},
Alan Donovan52153852019-02-13 19:18:15 -0500368
alandonovan8313b542019-02-15 13:17:43 -0500369 // h(a, b=42, *, c=123, **kwargs)
370 {`h()`, `function h missing 1 argument (a)`},
Alan Donovan52153852019-02-13 19:18:15 -0500371 {`h(0)`, `(0, 42, 123, {})`},
372 {`h(0, 1)`, `(0, 1, 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500373 {`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`},
Alan Donovan52153852019-02-13 19:18:15 -0500374 {`h(a=0)`, `(0, 42, 123, {})`},
375 {`h(0, b=1)`, `(0, 1, 123, {})`},
alandonovan8313b542019-02-15 13:17:43 -0500376 {`h(0, a=1)`, `function h got multiple values for parameter "a"`},
Alan Donovan52153852019-02-13 19:18:15 -0500377 {`h(0, b=1, c=2)`, `(0, 1, 2, {})`},
378 {`h(0, b=1, d=2)`, `(0, 1, 123, {"d": 2})`},
379 {`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
alandonovan8313b542019-02-15 13:17:43 -0500380
381 // i(a, b=42, *, c, d=123, e, **kwargs)
382 {`i()`, `function i missing 3 arguments (a, c, e)`},
383 {`i(0)`, `function i missing 2 arguments (c, e)`},
384 {`i(0, 1)`, `function i missing 2 arguments (c, e)`},
385 {`i(0, 1, 2)`, `function i accepts at most 2 positional arguments (3 given)`},
386 {`i(0, 1, e=2)`, `function i missing 1 argument (c)`},
387 {`i(0, 1, 2, 3)`, `function i accepts at most 2 positional arguments (4 given)`},
388 {`i(a=0)`, `function i missing 2 arguments (c, e)`},
389 {`i(0, b=1)`, `function i missing 2 arguments (c, e)`},
390 {`i(0, a=1)`, `function i got multiple values for parameter "a"`},
391 {`i(0, b=1, c=2)`, `function i missing 1 argument (e)`},
392 {`i(0, b=1, d=2)`, `function i missing 2 arguments (c, e)`},
393 {`i(0, b=1, c=2, d=3)`, `function i missing 1 argument (e)`},
394 {`i(0, b=1, c=2, d=3, e=4)`, `(0, 1, 2, 3, 4, {})`},
395 {`i(0, 1, b=1, c=2, d=3, e=4)`, `function i got multiple values for parameter "b"`},
396
397 // j(a, b=42, *args, c, d=123, e, **kwargs)
398 {`j()`, `function j missing 3 arguments (a, c, e)`},
399 {`j(0)`, `function j missing 2 arguments (c, e)`},
400 {`j(0, 1)`, `function j missing 2 arguments (c, e)`},
401 {`j(0, 1, 2)`, `function j missing 2 arguments (c, e)`},
402 {`j(0, 1, e=2)`, `function j missing 1 argument (c)`},
403 {`j(0, 1, 2, 3)`, `function j missing 2 arguments (c, e)`},
404 {`j(a=0)`, `function j missing 2 arguments (c, e)`},
405 {`j(0, b=1)`, `function j missing 2 arguments (c, e)`},
406 {`j(0, a=1)`, `function j got multiple values for parameter "a"`},
407 {`j(0, b=1, c=2)`, `function j missing 1 argument (e)`},
408 {`j(0, b=1, d=2)`, `function j missing 2 arguments (c, e)`},
409 {`j(0, b=1, c=2, d=3)`, `function j missing 1 argument (e)`},
410 {`j(0, b=1, c=2, d=3, e=4)`, `(0, 1, (), 2, 3, 4, {})`},
411 {`j(0, 1, b=1, c=2, d=3, e=4)`, `function j got multiple values for parameter "b"`},
412 {`j(0, 1, 2, c=3, e=4)`, `(0, 1, (2,), 3, 123, 4, {})`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400413 } {
414 var got string
Alan Donovane3deafe2018-10-23 11:05:09 -0400415 if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400416 got = err.Error()
417 } else {
418 got = v.String()
419 }
420 if got != test.want {
421 t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
422 }
423 }
424}
425
Alan Donovane3deafe2018-10-23 11:05:09 -0400426// TestPrint ensures that the Starlark print function calls
Alan Donovan312d1a52017-10-02 10:10:28 -0400427// Thread.Print, if provided.
428func TestPrint(t *testing.T) {
429 const src = `
430print("hello")
alandonovan88085a42018-12-14 19:09:49 -0500431def f(): print("hello", "world", sep=", ")
Alan Donovan312d1a52017-10-02 10:10:28 -0400432f()
433`
434 buf := new(bytes.Buffer)
Alan Donovane3deafe2018-10-23 11:05:09 -0400435 print := func(thread *starlark.Thread, msg string) {
alandonovand9868e92019-04-19 14:47:26 -0400436 caller := thread.CallFrame(1)
437 fmt.Fprintf(buf, "%s: %s: %s\n", caller.Pos, caller.Name, msg)
Alan Donovan312d1a52017-10-02 10:10:28 -0400438 }
Alan Donovane3deafe2018-10-23 11:05:09 -0400439 thread := &starlark.Thread{Print: print}
alandonovan2c1f3622018-12-17 13:10:16 -0500440 if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400441 t.Fatal(err)
442 }
alandonovan30b85782019-05-28 16:29:08 -0400443 want := "foo.star:2:6: <toplevel>: hello\n" +
444 "foo.star:3:15: f: hello, world\n"
Alan Donovan312d1a52017-10-02 10:10:28 -0400445 if got := buf.String(); got != want {
446 t.Errorf("output was %s, want %s", got, want)
447 }
448}
449
Alan Donovan312d1a52017-10-02 10:10:28 -0400450func reportEvalError(tb testing.TB, err error) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400451 if err, ok := err.(*starlark.EvalError); ok {
Alan Donovan312d1a52017-10-02 10:10:28 -0400452 tb.Fatal(err.Backtrace())
453 }
454 tb.Fatal(err)
455}
456
457// TestInt exercises the Int.Int64 and Int.Uint64 methods.
458// If we can move their logic into math/big, delete this test.
459func TestInt(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400460 one := starlark.MakeInt(1)
Alan Donovan312d1a52017-10-02 10:10:28 -0400461
462 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400463 i starlark.Int
Alan Donovan312d1a52017-10-02 10:10:28 -0400464 wantInt64 string
465 wantUint64 string
466 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400467 {starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"},
468 {starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"},
469 {starlark.MakeInt64(-1), "-1", "error"},
470 {starlark.MakeInt64(0), "0", "0"},
471 {starlark.MakeInt64(1), "1", "1"},
472 {starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"},
473 {starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"},
474 {starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"},
Alan Donovan312d1a52017-10-02 10:10:28 -0400475 } {
476 gotInt64, gotUint64 := "error", "error"
477 if i, ok := test.i.Int64(); ok {
478 gotInt64 = fmt.Sprint(i)
479 }
480 if u, ok := test.i.Uint64(); ok {
481 gotUint64 = fmt.Sprint(u)
482 }
483 if gotInt64 != test.wantInt64 {
484 t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64)
485 }
486 if gotUint64 != test.wantUint64 {
487 t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64)
488 }
489 }
490}
491
Nick Santos58de16f2019-10-18 17:42:35 -0400492func backtrace(t *testing.T, err error) string {
493 switch err := err.(type) {
494 case *starlark.EvalError:
495 return err.Backtrace()
496 case nil:
497 t.Fatalf("ExecFile succeeded unexpectedly")
498 default:
499 t.Fatalf("ExecFile failed with %v, wanted *EvalError", err)
alandonovan2494ae92019-04-04 15:38:05 -0400500 }
Nick Santos58de16f2019-10-18 17:42:35 -0400501 panic("unreachable")
502}
alandonovan2494ae92019-04-04 15:38:05 -0400503
Nick Santos58de16f2019-10-18 17:42:35 -0400504func TestBacktrace(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400505 // This test ensures continuity of the stack of active Starlark
alandonovan3d5a0612019-03-08 16:16:44 -0500506 // functions, including propagation through built-ins such as 'min'.
Alan Donovan312d1a52017-10-02 10:10:28 -0400507 const src = `
508def f(x): return 1//x
alandonovan69054822020-11-11 14:23:03 -0500509def g(x): return f(x)
Alan Donovan312d1a52017-10-02 10:10:28 -0400510def h(): return min([1, 2, 0], key=g)
511def i(): return h()
512i()
513`
Alan Donovane3deafe2018-10-23 11:05:09 -0400514 thread := new(starlark.Thread)
515 _, err := starlark.ExecFile(thread, "crash.star", src, nil)
alandonovan2494ae92019-04-04 15:38:05 -0400516 const want = `Traceback (most recent call last):
alandonovan30b85782019-05-28 16:29:08 -0400517 crash.star:6:2: in <toplevel>
518 crash.star:5:18: in i
519 crash.star:4:20: in h
alandonovan2494ae92019-04-04 15:38:05 -0400520 <builtin>: in min
alandonovan69054822020-11-11 14:23:03 -0500521 crash.star:3:19: in g
alandonovan30b85782019-05-28 16:29:08 -0400522 crash.star:2:19: in f
Alan Donovan312d1a52017-10-02 10:10:28 -0400523Error: floored division by zero`
Nick Santos58de16f2019-10-18 17:42:35 -0400524 if got := backtrace(t, err); got != want {
alandonovan2494ae92019-04-04 15:38:05 -0400525 t.Errorf("error was %s, want %s", got, want)
526 }
527
528 // Additionally, ensure that errors originating in
529 // Starlark and/or Go each have an accurate frame.
alandonovana5c0cc42020-11-18 12:36:49 -0500530 // The topmost frame, if built-in, is not shown,
531 // but the name of the built-in function is shown
532 // as "Error in fn: ...".
alandonovan2494ae92019-04-04 15:38:05 -0400533 //
534 // This program fails in Starlark (f) if x==0,
535 // or in Go (string.join) if x is non-zero.
536 const src2 = `
537def f(): ''.join([1//i])
538f()
539`
540 for i, want := range []string{
541 0: `Traceback (most recent call last):
alandonovan30b85782019-05-28 16:29:08 -0400542 crash.star:3:2: in <toplevel>
543 crash.star:2:20: in f
alandonovan2494ae92019-04-04 15:38:05 -0400544Error: floored division by zero`,
545 1: `Traceback (most recent call last):
alandonovan30b85782019-05-28 16:29:08 -0400546 crash.star:3:2: in <toplevel>
547 crash.star:2:17: in f
alandonovana5c0cc42020-11-18 12:36:49 -0500548Error in join: join: in list, want string, got int`,
alandonovan2494ae92019-04-04 15:38:05 -0400549 } {
550 globals := starlark.StringDict{"i": starlark.MakeInt(i)}
551 _, err := starlark.ExecFile(thread, "crash.star", src2, globals)
Nick Santos58de16f2019-10-18 17:42:35 -0400552 if got := backtrace(t, err); got != want {
Alan Donovan312d1a52017-10-02 10:10:28 -0400553 t.Errorf("error was %s, want %s", got, want)
554 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400555 }
556}
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400557
Nick Santos58de16f2019-10-18 17:42:35 -0400558func TestLoadBacktrace(t *testing.T) {
559 // This test ensures that load() does NOT preserve stack traces,
560 // but that API callers can get them with Unwrap().
561 // For discussion, see:
562 // https://github.com/google/starlark-go/pull/244
563 const src = `
564load('crash.star', 'x')
565`
566 const loadedSrc = `
567def f(x):
568 return 1 // x
569
570f(0)
571`
572 thread := new(starlark.Thread)
573 thread.Load = func(t *starlark.Thread, module string) (starlark.StringDict, error) {
574 return starlark.ExecFile(new(starlark.Thread), module, loadedSrc, nil)
575 }
576 _, err := starlark.ExecFile(thread, "root.star", src, nil)
577
578 const want = `Traceback (most recent call last):
579 root.star:2:1: in <toplevel>
580Error: cannot load crash.star: floored division by zero`
581 if got := backtrace(t, err); got != want {
582 t.Errorf("error was %s, want %s", got, want)
583 }
584
585 unwrapEvalError := func(err error) *starlark.EvalError {
586 var result *starlark.EvalError
587 for {
588 if evalErr, ok := err.(*starlark.EvalError); ok {
589 result = evalErr
590 }
591
592 // TODO: use errors.Unwrap when go >=1.13 is everywhere.
593 wrapper, isWrapper := err.(Wrapper)
594 if !isWrapper {
595 break
596 }
597 err = wrapper.Unwrap()
598 }
599 return result
600 }
601
602 unwrappedErr := unwrapEvalError(err)
603 const wantUnwrapped = `Traceback (most recent call last):
604 crash.star:5:2: in <toplevel>
605 crash.star:3:12: in f
606Error: floored division by zero`
607 if got := backtrace(t, unwrappedErr); got != wantUnwrapped {
608 t.Errorf("error was %s, want %s", got, wantUnwrapped)
609 }
610
611}
612
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400613// TestRepeatedExec parses and resolves a file syntax tree once then
alandonovana1b28d82018-03-13 10:59:24 -0400614// executes it repeatedly with different values of its predeclared variables.
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400615func TestRepeatedExec(t *testing.T) {
Alan Donovane3deafe2018-10-23 11:05:09 -0400616 predeclared := starlark.StringDict{"x": starlark.None}
617 _, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400618 if err != nil {
alandonovan93f3e0c2018-03-30 10:42:28 -0400619 t.Fatal(err)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400620 }
621
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400622 for _, test := range []struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400623 x, want starlark.Value
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400624 }{
Alan Donovane3deafe2018-10-23 11:05:09 -0400625 {x: starlark.MakeInt(42), want: starlark.MakeInt(84)},
626 {x: starlark.String("mur"), want: starlark.String("murmur")},
627 {x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}},
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400628 } {
alandonovan93f3e0c2018-03-30 10:42:28 -0400629 predeclared["x"] = test.x // update the values in dictionary
Alan Donovane3deafe2018-10-23 11:05:09 -0400630 thread := new(starlark.Thread)
alandonovan93f3e0c2018-03-30 10:42:28 -0400631 if globals, err := prog.Init(thread, predeclared); err != nil {
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400632 t.Errorf("x=%v: %v", test.x, err) // exec error
Alan Donovane3deafe2018-10-23 11:05:09 -0400633 } else if eq, err := starlark.Equal(globals["y"], test.want); err != nil {
Jay Conrod7761e752017-10-11 22:13:56 -0400634 t.Errorf("x=%v: %v", test.x, err) // comparison error
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400635 } else if !eq {
alandonovan93f3e0c2018-03-30 10:42:28 -0400636 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400637 }
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400638 }
639}
alandonovan04850cb2017-10-11 14:16:28 -0400640
alandonovan2c1f3622018-12-17 13:10:16 -0500641// TestEmptyFilePosition ensures that even Programs
642// from empty files have a valid position.
643func TestEmptyPosition(t *testing.T) {
644 var predeclared starlark.StringDict
645 for _, content := range []string{"", "empty = False"} {
646 _, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has)
647 if err != nil {
648 t.Fatal(err)
649 }
650 if got, want := prog.Filename(), "hello.star"; got != want {
651 t.Errorf("Program.Filename() = %q, want %q", got, want)
652 }
653 }
654}
655
alandonovan04850cb2017-10-11 14:16:28 -0400656// TestUnpackUserDefined tests that user-defined
Alan Donovane3deafe2018-10-23 11:05:09 -0400657// implementations of starlark.Value may be unpacked.
alandonovan04850cb2017-10-11 14:16:28 -0400658func TestUnpackUserDefined(t *testing.T) {
659 // success
660 want := new(hasfields)
661 var x *hasfields
Alan Donovane3deafe2018-10-23 11:05:09 -0400662 if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil {
alandonovan04850cb2017-10-11 14:16:28 -0400663 t.Errorf("UnpackArgs failed: %v", err)
664 }
665 if x != want {
666 t.Errorf("for x, got %v, want %v", x, want)
667 }
668
669 // failure
Alan Donovane3deafe2018-10-23 11:05:09 -0400670 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x)
alandonovandaf30b62019-05-03 16:35:58 -0400671 if want := "unpack: for parameter x: got int, want hasfields"; fmt.Sprint(err) != want {
alandonovan04850cb2017-10-11 14:16:28 -0400672 t.Errorf("unpack args error = %q, want %q", err, want)
673 }
674}
Alessandro Arzilli3b628ff2018-12-05 15:04:35 +0100675
Nick Santos0aa95692020-05-19 12:54:36 -0400676type optionalStringUnpacker struct {
677 str string
678 isSet bool
679}
680
681func (o *optionalStringUnpacker) Unpack(v starlark.Value) error {
682 s, ok := starlark.AsString(v)
683 if !ok {
684 return fmt.Errorf("got %s, want string", v.Type())
685 }
686 o.str = s
687 o.isSet = ok
688 return nil
689}
690
691func TestUnpackCustomUnpacker(t *testing.T) {
692 a := optionalStringUnpacker{}
693 wantA := optionalStringUnpacker{str: "a", isSet: true}
694 b := optionalStringUnpacker{str: "b"}
695 wantB := optionalStringUnpacker{str: "b"}
696
697 // Success
698 if err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.String("a")}, nil, "a?", &a, "b?", &b); err != nil {
699 t.Errorf("UnpackArgs failed: %v", err)
700 }
701 if a != wantA {
702 t.Errorf("for a, got %v, want %v", a, wantA)
703 }
704 if b != wantB {
705 t.Errorf("for b, got %v, want %v", b, wantB)
706 }
707
708 // failure
709 err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "a", &a)
710 if want := "unpack: for parameter a: got int, want string"; fmt.Sprint(err) != want {
711 t.Errorf("unpack args error = %q, want %q", err, want)
712 }
713}
714
alandonovane81fc952020-12-10 10:18:46 -0500715func TestAsInt(t *testing.T) {
716 for _, test := range []struct {
717 val starlark.Value
718 ptr interface{}
719 want string
720 }{
721 {starlark.MakeInt(42), new(int32), "42"},
722 {starlark.MakeInt(-1), new(int32), "-1"},
alandonovan8756d3e2021-01-13 21:12:56 -0500723 // Use Lsh not 1<<40 as the latter exceeds int if GOARCH=386.
724 {starlark.MakeInt(1).Lsh(40), new(int32), "1099511627776 out of range (want value in signed 32-bit range)"},
725 {starlark.MakeInt(-1).Lsh(40), new(int32), "-1099511627776 out of range (want value in signed 32-bit range)"},
alandonovane81fc952020-12-10 10:18:46 -0500726
727 {starlark.MakeInt(42), new(uint16), "42"},
728 {starlark.MakeInt(0xffff), new(uint16), "65535"},
729 {starlark.MakeInt(0x10000), new(uint16), "65536 out of range (want value in unsigned 16-bit range)"},
730 {starlark.MakeInt(-1), new(uint16), "-1 out of range (want value in unsigned 16-bit range)"},
731 } {
732 var got string
733 if err := starlark.AsInt(test.val, test.ptr); err != nil {
734 got = err.Error()
735 } else {
736 got = fmt.Sprint(reflect.ValueOf(test.ptr).Elem().Interface())
737 }
738 if got != test.want {
739 t.Errorf("AsInt(%s, %T): got %q, want %q", test.val, test.ptr, got, test.want)
740 }
741 }
742}
743
Alessandro Arzilli3b628ff2018-12-05 15:04:35 +0100744func TestDocstring(t *testing.T) {
745 globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", `
746def somefunc():
747 "somefunc doc"
748 return 0
749`, nil)
750
751 if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" {
752 t.Fatal("docstring not found")
753 }
754}
alandonovan2c1f3622018-12-17 13:10:16 -0500755
756func TestFrameLocals(t *testing.T) {
757 // trace prints a nice stack trace including argument
758 // values of calls to Starlark functions.
759 trace := func(thread *starlark.Thread) string {
760 buf := new(bytes.Buffer)
alandonovand9868e92019-04-19 14:47:26 -0400761 for i := 0; i < thread.CallStackDepth(); i++ {
762 fr := thread.DebugFrame(i)
alandonovan2c1f3622018-12-17 13:10:16 -0500763 fmt.Fprintf(buf, "%s(", fr.Callable().Name())
764 if fn, ok := fr.Callable().(*starlark.Function); ok {
765 for i := 0; i < fn.NumParams(); i++ {
766 if i > 0 {
767 buf.WriteString(", ")
768 }
769 name, _ := fn.Param(i)
770 fmt.Fprintf(buf, "%s=%s", name, fr.Local(i))
771 }
772 } else {
773 buf.WriteString("...") // a built-in function
774 }
775 buf.WriteString(")\n")
776 }
777 return buf.String()
778 }
779
780 var got string
781 builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
782 got = trace(thread)
783 return starlark.None, nil
784 }
785 predeclared := starlark.StringDict{
786 "builtin": starlark.NewBuiltin("builtin", builtin),
787 }
788 _, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", `
789def f(x, y): builtin()
790def g(z): f(z, z*z)
791g(7)
792`, predeclared)
793 if err != nil {
794 t.Errorf("ExecFile failed: %v", err)
795 }
796
797 var want = `
798builtin(...)
799f(x=7, y=49)
800g(z=7)
801<toplevel>()
802`[1:]
803 if got != want {
804 t.Errorf("got <<%s>>, want <<%s>>", got, want)
805 }
806}
alandonovanc122e652019-01-03 18:11:01 -0500807
808type badType string
809
810func (b *badType) String() string { return "badType" }
811func (b *badType) Type() string { return "badType:" + string(*b) } // panics if b==nil
812func (b *badType) Truth() starlark.Bool { return true }
813func (b *badType) Hash() (uint32, error) { return 0, nil }
814func (b *badType) Freeze() {}
815
816var _ starlark.Value = new(badType)
817
818// TestUnpackErrorBadType verifies that the Unpack functions fail
819// gracefully when a parameter's default value's Type method panics.
820func TestUnpackErrorBadType(t *testing.T) {
821 for _, test := range []struct {
822 x *badType
823 want string
824 }{
825 {new(badType), "got NoneType, want badType"}, // Starlark type name
826 {nil, "got NoneType, want *starlark_test.badType"}, // Go type name
827 } {
828 err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x)
829 if err == nil {
830 t.Errorf("UnpackArgs succeeded unexpectedly")
831 continue
832 }
833 if !strings.Contains(err.Error(), test.want) {
834 t.Errorf("UnpackArgs error %q does not contain %q", err, test.want)
835 }
836 }
837}
alandonovan28350e62019-10-21 14:58:36 -0400838
839// Regression test for github.com/google/starlark-go/issues/233.
840func TestREPLChunk(t *testing.T) {
841 thread := new(starlark.Thread)
842 globals := make(starlark.StringDict)
843 exec := func(src string) {
844 f, err := syntax.Parse("<repl>", src, 0)
845 if err != nil {
846 t.Fatal(err)
847 }
848 if err := starlark.ExecREPLChunk(f, thread, globals); err != nil {
849 t.Fatal(err)
850 }
851 }
852
853 exec("x = 0; y = 0")
854 if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "0 0"; got != want {
855 t.Fatalf("chunk1: got %s, want %s", got, want)
856 }
857
858 exec("x += 1; y = y + 1")
859 if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "1 1"; got != want {
860 t.Fatalf("chunk2: got %s, want %s", got, want)
861 }
862}
alandonovan949cc6f2020-08-21 10:29:38 -0400863
864func TestCancel(t *testing.T) {
865 // A thread cancelled before it begins executes no code.
866 {
867 thread := new(starlark.Thread)
868 thread.Cancel("nope")
869 _, err := starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil)
870 if fmt.Sprint(err) != "Starlark computation cancelled: nope" {
871 t.Errorf("execution returned error %q, want cancellation", err)
872 }
873
874 // cancellation is sticky
875 _, err = starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil)
876 if fmt.Sprint(err) != "Starlark computation cancelled: nope" {
877 t.Errorf("execution returned error %q, want cancellation", err)
878 }
879 }
880 // A thread cancelled during a built-in executes no more code.
881 {
882 thread := new(starlark.Thread)
883 predeclared := starlark.StringDict{
884 "stopit": starlark.NewBuiltin("stopit", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
885 thread.Cancel(fmt.Sprint(args[0]))
886 return starlark.None, nil
887 }),
888 }
889 _, err := starlark.ExecFile(thread, "stopit.star", `msg = 'nope'; stopit(msg); x = 1//0`, predeclared)
890 if fmt.Sprint(err) != `Starlark computation cancelled: "nope"` {
891 t.Errorf("execution returned error %q, want cancellation", err)
892 }
893 }
894}
895
896func TestExecutionSteps(t *testing.T) {
897 // A Thread records the number of computation steps.
898 thread := new(starlark.Thread)
899 countSteps := func(n int) (uint64, error) {
900 predeclared := starlark.StringDict{"n": starlark.MakeInt(n)}
901 steps0 := thread.ExecutionSteps()
902 _, err := starlark.ExecFile(thread, "steps.star", `squares = [x*x for x in range(n)]`, predeclared)
903 return thread.ExecutionSteps() - steps0, err
904 }
905 steps100, err := countSteps(1000)
906 if err != nil {
907 t.Errorf("execution failed: %v", err)
908 }
909 steps10000, err := countSteps(100000)
910 if err != nil {
911 t.Errorf("execution failed: %v", err)
912 }
913 if ratio := float64(steps10000) / float64(steps100); ratio < 99 || ratio > 101 {
914 t.Errorf("computation steps did not increase linearly: f(100)=%d, f(10000)=%d, ratio=%g, want ~100", steps100, steps10000, ratio)
915 }
916
917 // Exceeding the step limit causes cancellation.
918 thread.SetMaxExecutionSteps(1000)
919 _, err = countSteps(1000)
920 if fmt.Sprint(err) != "Starlark computation cancelled: too many steps" {
921 t.Errorf("execution returned error %q, want cancellation", err)
922 }
923}
alandonovane55f6032020-11-18 13:34:35 -0500924
925// TestDeps fails if the interpreter proper (not the REPL, etc) sprouts new external dependencies.
926// We may expand the list of permitted dependencies, but should do so deliberately, not casually.
927func TestDeps(t *testing.T) {
928 cmd := exec.Command("go", "list", "-deps")
929 out, err := cmd.Output()
930 if err != nil {
931 t.Skipf("'go list' failed: %s", err)
932 }
933 for _, pkg := range strings.Split(string(out), "\n") {
934 // Does pkg have form "domain.name/dir"?
935 slash := strings.IndexByte(pkg, '/')
936 dot := strings.IndexByte(pkg, '.')
937 if 0 < dot && dot < slash {
938 if strings.HasPrefix(pkg, "go.starlark.net/") ||
939 strings.HasPrefix(pkg, "golang.org/x/sys/") {
940 continue // permitted dependencies
941 }
942 t.Errorf("new interpreter dependency: %s", pkg)
943 }
944 }
945}