xref: /aosp_15_r20/external/starlark-go/starlark/eval_test.go (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
1*4947cdc7SCole Faust// Copyright 2017 The Bazel Authors. All rights reserved.
2*4947cdc7SCole Faust// Use of this source code is governed by a BSD-style
3*4947cdc7SCole Faust// license that can be found in the LICENSE file.
4*4947cdc7SCole Faust
5*4947cdc7SCole Faustpackage starlark_test
6*4947cdc7SCole Faust
7*4947cdc7SCole Faustimport (
8*4947cdc7SCole Faust	"bytes"
9*4947cdc7SCole Faust	"fmt"
10*4947cdc7SCole Faust	"math"
11*4947cdc7SCole Faust	"os/exec"
12*4947cdc7SCole Faust	"path/filepath"
13*4947cdc7SCole Faust	"reflect"
14*4947cdc7SCole Faust	"sort"
15*4947cdc7SCole Faust	"strings"
16*4947cdc7SCole Faust	"testing"
17*4947cdc7SCole Faust
18*4947cdc7SCole Faust	"go.starlark.net/internal/chunkedfile"
19*4947cdc7SCole Faust	"go.starlark.net/resolve"
20*4947cdc7SCole Faust	"go.starlark.net/starlark"
21*4947cdc7SCole Faust	"go.starlark.net/starlarkjson"
22*4947cdc7SCole Faust	"go.starlark.net/starlarkstruct"
23*4947cdc7SCole Faust	"go.starlark.net/starlarktest"
24*4947cdc7SCole Faust	"go.starlark.net/syntax"
25*4947cdc7SCole Faust)
26*4947cdc7SCole Faust
27*4947cdc7SCole Faust// A test may enable non-standard options by containing (e.g.) "option:recursion".
28*4947cdc7SCole Faustfunc setOptions(src string) {
29*4947cdc7SCole Faust	resolve.AllowGlobalReassign = option(src, "globalreassign")
30*4947cdc7SCole Faust	resolve.LoadBindsGlobally = option(src, "loadbindsglobally")
31*4947cdc7SCole Faust	resolve.AllowRecursion = option(src, "recursion")
32*4947cdc7SCole Faust	resolve.AllowSet = option(src, "set")
33*4947cdc7SCole Faust}
34*4947cdc7SCole Faust
35*4947cdc7SCole Faustfunc option(chunk, name string) bool {
36*4947cdc7SCole Faust	return strings.Contains(chunk, "option:"+name)
37*4947cdc7SCole Faust}
38*4947cdc7SCole Faust
39*4947cdc7SCole Faust// Wrapper is the type of errors with an Unwrap method; see https://golang.org/pkg/errors.
40*4947cdc7SCole Fausttype Wrapper interface {
41*4947cdc7SCole Faust	Unwrap() error
42*4947cdc7SCole Faust}
43*4947cdc7SCole Faust
44*4947cdc7SCole Faustfunc TestEvalExpr(t *testing.T) {
45*4947cdc7SCole Faust	// This is mostly redundant with the new *.star tests.
46*4947cdc7SCole Faust	// TODO(adonovan): move checks into *.star files and
47*4947cdc7SCole Faust	// reduce this to a mere unit test of starlark.Eval.
48*4947cdc7SCole Faust	thread := new(starlark.Thread)
49*4947cdc7SCole Faust	for _, test := range []struct{ src, want string }{
50*4947cdc7SCole Faust		{`123`, `123`},
51*4947cdc7SCole Faust		{`-1`, `-1`},
52*4947cdc7SCole Faust		{`"a"+"b"`, `"ab"`},
53*4947cdc7SCole Faust		{`1+2`, `3`},
54*4947cdc7SCole Faust
55*4947cdc7SCole Faust		// lists
56*4947cdc7SCole Faust		{`[]`, `[]`},
57*4947cdc7SCole Faust		{`[1]`, `[1]`},
58*4947cdc7SCole Faust		{`[1,]`, `[1]`},
59*4947cdc7SCole Faust		{`[1, 2]`, `[1, 2]`},
60*4947cdc7SCole Faust		{`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`},
61*4947cdc7SCole Faust		{`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`},
62*4947cdc7SCole Faust		{`[(x, y) for x in [1, 2] for y in [3, 4]]`,
63*4947cdc7SCole Faust			`[(1, 3), (1, 4), (2, 3), (2, 4)]`},
64*4947cdc7SCole Faust		{`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`,
65*4947cdc7SCole Faust			`[(2, 3), (2, 4)]`},
66*4947cdc7SCole Faust		// tuples
67*4947cdc7SCole Faust		{`()`, `()`},
68*4947cdc7SCole Faust		{`(1)`, `1`},
69*4947cdc7SCole Faust		{`(1,)`, `(1,)`},
70*4947cdc7SCole Faust		{`(1, 2)`, `(1, 2)`},
71*4947cdc7SCole Faust		{`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`},
72*4947cdc7SCole Faust		{`1, 2`, `(1, 2)`},
73*4947cdc7SCole Faust		// dicts
74*4947cdc7SCole Faust		{`{}`, `{}`},
75*4947cdc7SCole Faust		{`{"a": 1}`, `{"a": 1}`},
76*4947cdc7SCole Faust		{`{"a": 1,}`, `{"a": 1}`},
77*4947cdc7SCole Faust
78*4947cdc7SCole Faust		// conditional
79*4947cdc7SCole Faust		{`1 if 3 > 2 else 0`, `1`},
80*4947cdc7SCole Faust		{`1 if "foo" else 0`, `1`},
81*4947cdc7SCole Faust		{`1 if "" else 0`, `0`},
82*4947cdc7SCole Faust
83*4947cdc7SCole Faust		// indexing
84*4947cdc7SCole Faust		{`["a", "b"][0]`, `"a"`},
85*4947cdc7SCole Faust		{`["a", "b"][1]`, `"b"`},
86*4947cdc7SCole Faust		{`("a", "b")[0]`, `"a"`},
87*4947cdc7SCole Faust		{`("a", "b")[1]`, `"b"`},
88*4947cdc7SCole Faust		{`"aΩb"[0]`, `"a"`},
89*4947cdc7SCole Faust		{`"aΩb"[1]`, `"\xce"`},
90*4947cdc7SCole Faust		{`"aΩb"[3]`, `"b"`},
91*4947cdc7SCole Faust		{`{"a": 1}["a"]`, `1`},
92*4947cdc7SCole Faust		{`{"a": 1}["b"]`, `key "b" not in dict`},
93*4947cdc7SCole Faust		{`{}[[]]`, `unhashable type: list`},
94*4947cdc7SCole Faust		{`{"a": 1}[[]]`, `unhashable type: list`},
95*4947cdc7SCole Faust		{`[x for x in range(3)]`, "[0, 1, 2]"},
96*4947cdc7SCole Faust	} {
97*4947cdc7SCole Faust		var got string
98*4947cdc7SCole Faust		if v, err := starlark.Eval(thread, "<expr>", test.src, nil); err != nil {
99*4947cdc7SCole Faust			got = err.Error()
100*4947cdc7SCole Faust		} else {
101*4947cdc7SCole Faust			got = v.String()
102*4947cdc7SCole Faust		}
103*4947cdc7SCole Faust		if got != test.want {
104*4947cdc7SCole Faust			t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
105*4947cdc7SCole Faust		}
106*4947cdc7SCole Faust	}
107*4947cdc7SCole Faust}
108*4947cdc7SCole Faust
109*4947cdc7SCole Faustfunc TestExecFile(t *testing.T) {
110*4947cdc7SCole Faust	defer setOptions("")
111*4947cdc7SCole Faust	testdata := starlarktest.DataFile("starlark", ".")
112*4947cdc7SCole Faust	thread := &starlark.Thread{Load: load}
113*4947cdc7SCole Faust	starlarktest.SetReporter(thread, t)
114*4947cdc7SCole Faust	for _, file := range []string{
115*4947cdc7SCole Faust		"testdata/assign.star",
116*4947cdc7SCole Faust		"testdata/bool.star",
117*4947cdc7SCole Faust		"testdata/builtins.star",
118*4947cdc7SCole Faust		"testdata/bytes.star",
119*4947cdc7SCole Faust		"testdata/control.star",
120*4947cdc7SCole Faust		"testdata/dict.star",
121*4947cdc7SCole Faust		"testdata/float.star",
122*4947cdc7SCole Faust		"testdata/function.star",
123*4947cdc7SCole Faust		"testdata/int.star",
124*4947cdc7SCole Faust		"testdata/json.star",
125*4947cdc7SCole Faust		"testdata/list.star",
126*4947cdc7SCole Faust		"testdata/misc.star",
127*4947cdc7SCole Faust		"testdata/set.star",
128*4947cdc7SCole Faust		"testdata/string.star",
129*4947cdc7SCole Faust		"testdata/tuple.star",
130*4947cdc7SCole Faust		"testdata/recursion.star",
131*4947cdc7SCole Faust		"testdata/module.star",
132*4947cdc7SCole Faust	} {
133*4947cdc7SCole Faust		filename := filepath.Join(testdata, file)
134*4947cdc7SCole Faust		for _, chunk := range chunkedfile.Read(filename, t) {
135*4947cdc7SCole Faust			predeclared := starlark.StringDict{
136*4947cdc7SCole Faust				"hasfields": starlark.NewBuiltin("hasfields", newHasFields),
137*4947cdc7SCole Faust				"fibonacci": fib{},
138*4947cdc7SCole Faust				"struct":    starlark.NewBuiltin("struct", starlarkstruct.Make),
139*4947cdc7SCole Faust			}
140*4947cdc7SCole Faust
141*4947cdc7SCole Faust			setOptions(chunk.Source)
142*4947cdc7SCole Faust			resolve.AllowLambda = true // used extensively
143*4947cdc7SCole Faust
144*4947cdc7SCole Faust			_, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared)
145*4947cdc7SCole Faust			switch err := err.(type) {
146*4947cdc7SCole Faust			case *starlark.EvalError:
147*4947cdc7SCole Faust				found := false
148*4947cdc7SCole Faust				for i := range err.CallStack {
149*4947cdc7SCole Faust					posn := err.CallStack.At(i).Pos
150*4947cdc7SCole Faust					if posn.Filename() == filename {
151*4947cdc7SCole Faust						chunk.GotError(int(posn.Line), err.Error())
152*4947cdc7SCole Faust						found = true
153*4947cdc7SCole Faust						break
154*4947cdc7SCole Faust					}
155*4947cdc7SCole Faust				}
156*4947cdc7SCole Faust				if !found {
157*4947cdc7SCole Faust					t.Error(err.Backtrace())
158*4947cdc7SCole Faust				}
159*4947cdc7SCole Faust			case nil:
160*4947cdc7SCole Faust				// success
161*4947cdc7SCole Faust			default:
162*4947cdc7SCole Faust				t.Errorf("\n%s", err)
163*4947cdc7SCole Faust			}
164*4947cdc7SCole Faust			chunk.Done()
165*4947cdc7SCole Faust		}
166*4947cdc7SCole Faust	}
167*4947cdc7SCole Faust}
168*4947cdc7SCole Faust
169*4947cdc7SCole Faust// A fib is an iterable value representing the infinite Fibonacci sequence.
170*4947cdc7SCole Fausttype fib struct{}
171*4947cdc7SCole Faust
172*4947cdc7SCole Faustfunc (t fib) Freeze()                    {}
173*4947cdc7SCole Faustfunc (t fib) String() string             { return "fib" }
174*4947cdc7SCole Faustfunc (t fib) Type() string               { return "fib" }
175*4947cdc7SCole Faustfunc (t fib) Truth() starlark.Bool       { return true }
176*4947cdc7SCole Faustfunc (t fib) Hash() (uint32, error)      { return 0, fmt.Errorf("fib is unhashable") }
177*4947cdc7SCole Faustfunc (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} }
178*4947cdc7SCole Faust
179*4947cdc7SCole Fausttype fibIterator struct{ x, y int }
180*4947cdc7SCole Faust
181*4947cdc7SCole Faustfunc (it *fibIterator) Next(p *starlark.Value) bool {
182*4947cdc7SCole Faust	*p = starlark.MakeInt(it.x)
183*4947cdc7SCole Faust	it.x, it.y = it.y, it.x+it.y
184*4947cdc7SCole Faust	return true
185*4947cdc7SCole Faust}
186*4947cdc7SCole Faustfunc (it *fibIterator) Done() {}
187*4947cdc7SCole Faust
188*4947cdc7SCole Faust// load implements the 'load' operation as used in the evaluator tests.
189*4947cdc7SCole Faustfunc load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
190*4947cdc7SCole Faust	if module == "assert.star" {
191*4947cdc7SCole Faust		return starlarktest.LoadAssertModule()
192*4947cdc7SCole Faust	}
193*4947cdc7SCole Faust	if module == "json.star" {
194*4947cdc7SCole Faust		return starlark.StringDict{"json": starlarkjson.Module}, nil
195*4947cdc7SCole Faust	}
196*4947cdc7SCole Faust
197*4947cdc7SCole Faust	// TODO(adonovan): test load() using this execution path.
198*4947cdc7SCole Faust	filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module)
199*4947cdc7SCole Faust	return starlark.ExecFile(thread, filename, nil, nil)
200*4947cdc7SCole Faust}
201*4947cdc7SCole Faust
202*4947cdc7SCole Faustfunc newHasFields(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
203*4947cdc7SCole Faust	if len(args)+len(kwargs) > 0 {
204*4947cdc7SCole Faust		return nil, fmt.Errorf("%s: unexpected arguments", b.Name())
205*4947cdc7SCole Faust	}
206*4947cdc7SCole Faust	return &hasfields{attrs: make(map[string]starlark.Value)}, nil
207*4947cdc7SCole Faust}
208*4947cdc7SCole Faust
209*4947cdc7SCole Faust// hasfields is a test-only implementation of HasAttrs.
210*4947cdc7SCole Faust// It permits any field to be set.
211*4947cdc7SCole Faust// Clients will likely want to provide their own implementation,
212*4947cdc7SCole Faust// so we don't have any public implementation.
213*4947cdc7SCole Fausttype hasfields struct {
214*4947cdc7SCole Faust	attrs  starlark.StringDict
215*4947cdc7SCole Faust	frozen bool
216*4947cdc7SCole Faust}
217*4947cdc7SCole Faust
218*4947cdc7SCole Faustvar (
219*4947cdc7SCole Faust	_ starlark.HasAttrs  = (*hasfields)(nil)
220*4947cdc7SCole Faust	_ starlark.HasBinary = (*hasfields)(nil)
221*4947cdc7SCole Faust)
222*4947cdc7SCole Faust
223*4947cdc7SCole Faustfunc (hf *hasfields) String() string        { return "hasfields" }
224*4947cdc7SCole Faustfunc (hf *hasfields) Type() string          { return "hasfields" }
225*4947cdc7SCole Faustfunc (hf *hasfields) Truth() starlark.Bool  { return true }
226*4947cdc7SCole Faustfunc (hf *hasfields) Hash() (uint32, error) { return 42, nil }
227*4947cdc7SCole Faust
228*4947cdc7SCole Faustfunc (hf *hasfields) Freeze() {
229*4947cdc7SCole Faust	if !hf.frozen {
230*4947cdc7SCole Faust		hf.frozen = true
231*4947cdc7SCole Faust		for _, v := range hf.attrs {
232*4947cdc7SCole Faust			v.Freeze()
233*4947cdc7SCole Faust		}
234*4947cdc7SCole Faust	}
235*4947cdc7SCole Faust}
236*4947cdc7SCole Faust
237*4947cdc7SCole Faustfunc (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil }
238*4947cdc7SCole Faust
239*4947cdc7SCole Faustfunc (hf *hasfields) SetField(name string, val starlark.Value) error {
240*4947cdc7SCole Faust	if hf.frozen {
241*4947cdc7SCole Faust		return fmt.Errorf("cannot set field on a frozen hasfields")
242*4947cdc7SCole Faust	}
243*4947cdc7SCole Faust	if strings.HasPrefix(name, "no") { // for testing
244*4947cdc7SCole Faust		return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name))
245*4947cdc7SCole Faust	}
246*4947cdc7SCole Faust	hf.attrs[name] = val
247*4947cdc7SCole Faust	return nil
248*4947cdc7SCole Faust}
249*4947cdc7SCole Faust
250*4947cdc7SCole Faustfunc (hf *hasfields) AttrNames() []string {
251*4947cdc7SCole Faust	names := make([]string, 0, len(hf.attrs))
252*4947cdc7SCole Faust	for key := range hf.attrs {
253*4947cdc7SCole Faust		names = append(names, key)
254*4947cdc7SCole Faust	}
255*4947cdc7SCole Faust	sort.Strings(names)
256*4947cdc7SCole Faust	return names
257*4947cdc7SCole Faust}
258*4947cdc7SCole Faust
259*4947cdc7SCole Faustfunc (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
260*4947cdc7SCole Faust	// This method exists so we can exercise 'list += x'
261*4947cdc7SCole Faust	// where x is not Iterable but defines list+x.
262*4947cdc7SCole Faust	if op == syntax.PLUS {
263*4947cdc7SCole Faust		if _, ok := y.(*starlark.List); ok {
264*4947cdc7SCole Faust			return starlark.MakeInt(42), nil // list+hasfields is 42
265*4947cdc7SCole Faust		}
266*4947cdc7SCole Faust	}
267*4947cdc7SCole Faust	return nil, nil
268*4947cdc7SCole Faust}
269*4947cdc7SCole Faust
270*4947cdc7SCole Faustfunc TestParameterPassing(t *testing.T) {
271*4947cdc7SCole Faust	const filename = "parameters.go"
272*4947cdc7SCole Faust	const src = `
273*4947cdc7SCole Faustdef a():
274*4947cdc7SCole Faust	return
275*4947cdc7SCole Faustdef b(a, b):
276*4947cdc7SCole Faust	return a, b
277*4947cdc7SCole Faustdef c(a, b=42):
278*4947cdc7SCole Faust	return a, b
279*4947cdc7SCole Faustdef d(*args):
280*4947cdc7SCole Faust	return args
281*4947cdc7SCole Faustdef e(**kwargs):
282*4947cdc7SCole Faust	return kwargs
283*4947cdc7SCole Faustdef f(a, b=42, *args, **kwargs):
284*4947cdc7SCole Faust	return a, b, args, kwargs
285*4947cdc7SCole Faustdef g(a, b=42, *args, c=123, **kwargs):
286*4947cdc7SCole Faust	return a, b, args, c, kwargs
287*4947cdc7SCole Faustdef h(a, b=42, *, c=123, **kwargs):
288*4947cdc7SCole Faust	return a, b, c, kwargs
289*4947cdc7SCole Faustdef i(a, b=42, *, c, d=123, e, **kwargs):
290*4947cdc7SCole Faust	return a, b, c, d, e, kwargs
291*4947cdc7SCole Faustdef j(a, b=42, *args, c, d=123, e, **kwargs):
292*4947cdc7SCole Faust	return a, b, args, c, d, e, kwargs
293*4947cdc7SCole Faust`
294*4947cdc7SCole Faust
295*4947cdc7SCole Faust	thread := new(starlark.Thread)
296*4947cdc7SCole Faust	globals, err := starlark.ExecFile(thread, filename, src, nil)
297*4947cdc7SCole Faust	if err != nil {
298*4947cdc7SCole Faust		t.Fatal(err)
299*4947cdc7SCole Faust	}
300*4947cdc7SCole Faust
301*4947cdc7SCole Faust	// All errors are dynamic; see resolver for static errors.
302*4947cdc7SCole Faust	for _, test := range []struct{ src, want string }{
303*4947cdc7SCole Faust		// a()
304*4947cdc7SCole Faust		{`a()`, `None`},
305*4947cdc7SCole Faust		{`a(1)`, `function a accepts no arguments (1 given)`},
306*4947cdc7SCole Faust
307*4947cdc7SCole Faust		// b(a, b)
308*4947cdc7SCole Faust		{`b()`, `function b missing 2 arguments (a, b)`},
309*4947cdc7SCole Faust		{`b(1)`, `function b missing 1 argument (b)`},
310*4947cdc7SCole Faust		{`b(a=1)`, `function b missing 1 argument (b)`},
311*4947cdc7SCole Faust		{`b(b=1)`, `function b missing 1 argument (a)`},
312*4947cdc7SCole Faust		{`b(1, 2)`, `(1, 2)`},
313*4947cdc7SCole Faust		{`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
314*4947cdc7SCole Faust		{`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`},
315*4947cdc7SCole Faust		{`b(1, b=2)`, `(1, 2)`},
316*4947cdc7SCole Faust		{`b(1, a=2)`, `function b got multiple values for parameter "a"`},
317*4947cdc7SCole Faust		{`b(1, x=2)`, `function b got an unexpected keyword argument "x"`},
318*4947cdc7SCole Faust		{`b(a=1, b=2)`, `(1, 2)`},
319*4947cdc7SCole Faust		{`b(b=1, a=2)`, `(2, 1)`},
320*4947cdc7SCole Faust		{`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`},
321*4947cdc7SCole Faust		{`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`},
322*4947cdc7SCole Faust
323*4947cdc7SCole Faust		// c(a, b=42)
324*4947cdc7SCole Faust		{`c()`, `function c missing 1 argument (a)`},
325*4947cdc7SCole Faust		{`c(1)`, `(1, 42)`},
326*4947cdc7SCole Faust		{`c(1, 2)`, `(1, 2)`},
327*4947cdc7SCole Faust		{`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`},
328*4947cdc7SCole Faust		{`c(1, b=2)`, `(1, 2)`},
329*4947cdc7SCole Faust		{`c(1, a=2)`, `function c got multiple values for parameter "a"`},
330*4947cdc7SCole Faust		{`c(a=1, b=2)`, `(1, 2)`},
331*4947cdc7SCole Faust		{`c(b=1, a=2)`, `(2, 1)`},
332*4947cdc7SCole Faust
333*4947cdc7SCole Faust		// d(*args)
334*4947cdc7SCole Faust		{`d()`, `()`},
335*4947cdc7SCole Faust		{`d(1)`, `(1,)`},
336*4947cdc7SCole Faust		{`d(1, 2)`, `(1, 2)`},
337*4947cdc7SCole Faust		{`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`},
338*4947cdc7SCole Faust		{`d(args=[])`, `function d got an unexpected keyword argument "args"`},
339*4947cdc7SCole Faust
340*4947cdc7SCole Faust		// e(**kwargs)
341*4947cdc7SCole Faust		{`e()`, `{}`},
342*4947cdc7SCole Faust		{`e(1)`, `function e accepts 0 positional arguments (1 given)`},
343*4947cdc7SCole Faust		{`e(k=1)`, `{"k": 1}`},
344*4947cdc7SCole Faust		{`e(kwargs={})`, `{"kwargs": {}}`},
345*4947cdc7SCole Faust
346*4947cdc7SCole Faust		// f(a, b=42, *args, **kwargs)
347*4947cdc7SCole Faust		{`f()`, `function f missing 1 argument (a)`},
348*4947cdc7SCole Faust		{`f(0)`, `(0, 42, (), {})`},
349*4947cdc7SCole Faust		{`f(0)`, `(0, 42, (), {})`},
350*4947cdc7SCole Faust		{`f(0, 1)`, `(0, 1, (), {})`},
351*4947cdc7SCole Faust		{`f(0, 1, 2)`, `(0, 1, (2,), {})`},
352*4947cdc7SCole Faust		{`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`},
353*4947cdc7SCole Faust		{`f(a=0)`, `(0, 42, (), {})`},
354*4947cdc7SCole Faust		{`f(0, b=1)`, `(0, 1, (), {})`},
355*4947cdc7SCole Faust		{`f(0, a=1)`, `function f got multiple values for parameter "a"`},
356*4947cdc7SCole Faust		{`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
357*4947cdc7SCole Faust
358*4947cdc7SCole Faust		// g(a, b=42, *args, c=123, **kwargs)
359*4947cdc7SCole Faust		{`g()`, `function g missing 1 argument (a)`},
360*4947cdc7SCole Faust		{`g(0)`, `(0, 42, (), 123, {})`},
361*4947cdc7SCole Faust		{`g(0, 1)`, `(0, 1, (), 123, {})`},
362*4947cdc7SCole Faust		{`g(0, 1, 2)`, `(0, 1, (2,), 123, {})`},
363*4947cdc7SCole Faust		{`g(0, 1, 2, 3)`, `(0, 1, (2, 3), 123, {})`},
364*4947cdc7SCole Faust		{`g(a=0)`, `(0, 42, (), 123, {})`},
365*4947cdc7SCole Faust		{`g(0, b=1)`, `(0, 1, (), 123, {})`},
366*4947cdc7SCole Faust		{`g(0, a=1)`, `function g got multiple values for parameter "a"`},
367*4947cdc7SCole Faust		{`g(0, b=1, c=2, d=3)`, `(0, 1, (), 2, {"d": 3})`},
368*4947cdc7SCole Faust
369*4947cdc7SCole Faust		// h(a, b=42, *, c=123, **kwargs)
370*4947cdc7SCole Faust		{`h()`, `function h missing 1 argument (a)`},
371*4947cdc7SCole Faust		{`h(0)`, `(0, 42, 123, {})`},
372*4947cdc7SCole Faust		{`h(0, 1)`, `(0, 1, 123, {})`},
373*4947cdc7SCole Faust		{`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`},
374*4947cdc7SCole Faust		{`h(a=0)`, `(0, 42, 123, {})`},
375*4947cdc7SCole Faust		{`h(0, b=1)`, `(0, 1, 123, {})`},
376*4947cdc7SCole Faust		{`h(0, a=1)`, `function h got multiple values for parameter "a"`},
377*4947cdc7SCole Faust		{`h(0, b=1, c=2)`, `(0, 1, 2, {})`},
378*4947cdc7SCole Faust		{`h(0, b=1, d=2)`, `(0, 1, 123, {"d": 2})`},
379*4947cdc7SCole Faust		{`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
380*4947cdc7SCole Faust
381*4947cdc7SCole Faust		// i(a, b=42, *, c, d=123, e, **kwargs)
382*4947cdc7SCole Faust		{`i()`, `function i missing 3 arguments (a, c, e)`},
383*4947cdc7SCole Faust		{`i(0)`, `function i missing 2 arguments (c, e)`},
384*4947cdc7SCole Faust		{`i(0, 1)`, `function i missing 2 arguments (c, e)`},
385*4947cdc7SCole Faust		{`i(0, 1, 2)`, `function i accepts at most 2 positional arguments (3 given)`},
386*4947cdc7SCole Faust		{`i(0, 1, e=2)`, `function i missing 1 argument (c)`},
387*4947cdc7SCole Faust		{`i(0, 1, 2, 3)`, `function i accepts at most 2 positional arguments (4 given)`},
388*4947cdc7SCole Faust		{`i(a=0)`, `function i missing 2 arguments (c, e)`},
389*4947cdc7SCole Faust		{`i(0, b=1)`, `function i missing 2 arguments (c, e)`},
390*4947cdc7SCole Faust		{`i(0, a=1)`, `function i got multiple values for parameter "a"`},
391*4947cdc7SCole Faust		{`i(0, b=1, c=2)`, `function i missing 1 argument (e)`},
392*4947cdc7SCole Faust		{`i(0, b=1, d=2)`, `function i missing 2 arguments (c, e)`},
393*4947cdc7SCole Faust		{`i(0, b=1, c=2, d=3)`, `function i missing 1 argument (e)`},
394*4947cdc7SCole Faust		{`i(0, b=1, c=2, d=3, e=4)`, `(0, 1, 2, 3, 4, {})`},
395*4947cdc7SCole Faust		{`i(0, 1, b=1, c=2, d=3, e=4)`, `function i got multiple values for parameter "b"`},
396*4947cdc7SCole Faust
397*4947cdc7SCole Faust		// j(a, b=42, *args, c, d=123, e, **kwargs)
398*4947cdc7SCole Faust		{`j()`, `function j missing 3 arguments (a, c, e)`},
399*4947cdc7SCole Faust		{`j(0)`, `function j missing 2 arguments (c, e)`},
400*4947cdc7SCole Faust		{`j(0, 1)`, `function j missing 2 arguments (c, e)`},
401*4947cdc7SCole Faust		{`j(0, 1, 2)`, `function j missing 2 arguments (c, e)`},
402*4947cdc7SCole Faust		{`j(0, 1, e=2)`, `function j missing 1 argument (c)`},
403*4947cdc7SCole Faust		{`j(0, 1, 2, 3)`, `function j missing 2 arguments (c, e)`},
404*4947cdc7SCole Faust		{`j(a=0)`, `function j missing 2 arguments (c, e)`},
405*4947cdc7SCole Faust		{`j(0, b=1)`, `function j missing 2 arguments (c, e)`},
406*4947cdc7SCole Faust		{`j(0, a=1)`, `function j got multiple values for parameter "a"`},
407*4947cdc7SCole Faust		{`j(0, b=1, c=2)`, `function j missing 1 argument (e)`},
408*4947cdc7SCole Faust		{`j(0, b=1, d=2)`, `function j missing 2 arguments (c, e)`},
409*4947cdc7SCole Faust		{`j(0, b=1, c=2, d=3)`, `function j missing 1 argument (e)`},
410*4947cdc7SCole Faust		{`j(0, b=1, c=2, d=3, e=4)`, `(0, 1, (), 2, 3, 4, {})`},
411*4947cdc7SCole Faust		{`j(0, 1, b=1, c=2, d=3, e=4)`, `function j got multiple values for parameter "b"`},
412*4947cdc7SCole Faust		{`j(0, 1, 2, c=3, e=4)`, `(0, 1, (2,), 3, 123, 4, {})`},
413*4947cdc7SCole Faust	} {
414*4947cdc7SCole Faust		var got string
415*4947cdc7SCole Faust		if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil {
416*4947cdc7SCole Faust			got = err.Error()
417*4947cdc7SCole Faust		} else {
418*4947cdc7SCole Faust			got = v.String()
419*4947cdc7SCole Faust		}
420*4947cdc7SCole Faust		if got != test.want {
421*4947cdc7SCole Faust			t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
422*4947cdc7SCole Faust		}
423*4947cdc7SCole Faust	}
424*4947cdc7SCole Faust}
425*4947cdc7SCole Faust
426*4947cdc7SCole Faust// TestPrint ensures that the Starlark print function calls
427*4947cdc7SCole Faust// Thread.Print, if provided.
428*4947cdc7SCole Faustfunc TestPrint(t *testing.T) {
429*4947cdc7SCole Faust	const src = `
430*4947cdc7SCole Faustprint("hello")
431*4947cdc7SCole Faustdef f(): print("hello", "world", sep=", ")
432*4947cdc7SCole Faustf()
433*4947cdc7SCole Faust`
434*4947cdc7SCole Faust	buf := new(bytes.Buffer)
435*4947cdc7SCole Faust	print := func(thread *starlark.Thread, msg string) {
436*4947cdc7SCole Faust		caller := thread.CallFrame(1)
437*4947cdc7SCole Faust		fmt.Fprintf(buf, "%s: %s: %s\n", caller.Pos, caller.Name, msg)
438*4947cdc7SCole Faust	}
439*4947cdc7SCole Faust	thread := &starlark.Thread{Print: print}
440*4947cdc7SCole Faust	if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil {
441*4947cdc7SCole Faust		t.Fatal(err)
442*4947cdc7SCole Faust	}
443*4947cdc7SCole Faust	want := "foo.star:2:6: <toplevel>: hello\n" +
444*4947cdc7SCole Faust		"foo.star:3:15: f: hello, world\n"
445*4947cdc7SCole Faust	if got := buf.String(); got != want {
446*4947cdc7SCole Faust		t.Errorf("output was %s, want %s", got, want)
447*4947cdc7SCole Faust	}
448*4947cdc7SCole Faust}
449*4947cdc7SCole Faust
450*4947cdc7SCole Faustfunc reportEvalError(tb testing.TB, err error) {
451*4947cdc7SCole Faust	if err, ok := err.(*starlark.EvalError); ok {
452*4947cdc7SCole Faust		tb.Fatal(err.Backtrace())
453*4947cdc7SCole Faust	}
454*4947cdc7SCole Faust	tb.Fatal(err)
455*4947cdc7SCole Faust}
456*4947cdc7SCole Faust
457*4947cdc7SCole Faust// TestInt exercises the Int.Int64 and Int.Uint64 methods.
458*4947cdc7SCole Faust// If we can move their logic into math/big, delete this test.
459*4947cdc7SCole Faustfunc TestInt(t *testing.T) {
460*4947cdc7SCole Faust	one := starlark.MakeInt(1)
461*4947cdc7SCole Faust
462*4947cdc7SCole Faust	for _, test := range []struct {
463*4947cdc7SCole Faust		i          starlark.Int
464*4947cdc7SCole Faust		wantInt64  string
465*4947cdc7SCole Faust		wantUint64 string
466*4947cdc7SCole Faust	}{
467*4947cdc7SCole Faust		{starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"},
468*4947cdc7SCole Faust		{starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"},
469*4947cdc7SCole Faust		{starlark.MakeInt64(-1), "-1", "error"},
470*4947cdc7SCole Faust		{starlark.MakeInt64(0), "0", "0"},
471*4947cdc7SCole Faust		{starlark.MakeInt64(1), "1", "1"},
472*4947cdc7SCole Faust		{starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"},
473*4947cdc7SCole Faust		{starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"},
474*4947cdc7SCole Faust		{starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"},
475*4947cdc7SCole Faust	} {
476*4947cdc7SCole Faust		gotInt64, gotUint64 := "error", "error"
477*4947cdc7SCole Faust		if i, ok := test.i.Int64(); ok {
478*4947cdc7SCole Faust			gotInt64 = fmt.Sprint(i)
479*4947cdc7SCole Faust		}
480*4947cdc7SCole Faust		if u, ok := test.i.Uint64(); ok {
481*4947cdc7SCole Faust			gotUint64 = fmt.Sprint(u)
482*4947cdc7SCole Faust		}
483*4947cdc7SCole Faust		if gotInt64 != test.wantInt64 {
484*4947cdc7SCole Faust			t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64)
485*4947cdc7SCole Faust		}
486*4947cdc7SCole Faust		if gotUint64 != test.wantUint64 {
487*4947cdc7SCole Faust			t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64)
488*4947cdc7SCole Faust		}
489*4947cdc7SCole Faust	}
490*4947cdc7SCole Faust}
491*4947cdc7SCole Faust
492*4947cdc7SCole Faustfunc backtrace(t *testing.T, err error) string {
493*4947cdc7SCole Faust	switch err := err.(type) {
494*4947cdc7SCole Faust	case *starlark.EvalError:
495*4947cdc7SCole Faust		return err.Backtrace()
496*4947cdc7SCole Faust	case nil:
497*4947cdc7SCole Faust		t.Fatalf("ExecFile succeeded unexpectedly")
498*4947cdc7SCole Faust	default:
499*4947cdc7SCole Faust		t.Fatalf("ExecFile failed with %v, wanted *EvalError", err)
500*4947cdc7SCole Faust	}
501*4947cdc7SCole Faust	panic("unreachable")
502*4947cdc7SCole Faust}
503*4947cdc7SCole Faust
504*4947cdc7SCole Faustfunc TestBacktrace(t *testing.T) {
505*4947cdc7SCole Faust	// This test ensures continuity of the stack of active Starlark
506*4947cdc7SCole Faust	// functions, including propagation through built-ins such as 'min'.
507*4947cdc7SCole Faust	const src = `
508*4947cdc7SCole Faustdef f(x): return 1//x
509*4947cdc7SCole Faustdef g(x): return f(x)
510*4947cdc7SCole Faustdef h(): return min([1, 2, 0], key=g)
511*4947cdc7SCole Faustdef i(): return h()
512*4947cdc7SCole Fausti()
513*4947cdc7SCole Faust`
514*4947cdc7SCole Faust	thread := new(starlark.Thread)
515*4947cdc7SCole Faust	_, err := starlark.ExecFile(thread, "crash.star", src, nil)
516*4947cdc7SCole Faust	const want = `Traceback (most recent call last):
517*4947cdc7SCole Faust  crash.star:6:2: in <toplevel>
518*4947cdc7SCole Faust  crash.star:5:18: in i
519*4947cdc7SCole Faust  crash.star:4:20: in h
520*4947cdc7SCole Faust  <builtin>: in min
521*4947cdc7SCole Faust  crash.star:3:19: in g
522*4947cdc7SCole Faust  crash.star:2:19: in f
523*4947cdc7SCole FaustError: floored division by zero`
524*4947cdc7SCole Faust	if got := backtrace(t, err); got != want {
525*4947cdc7SCole Faust		t.Errorf("error was %s, want %s", got, want)
526*4947cdc7SCole Faust	}
527*4947cdc7SCole Faust
528*4947cdc7SCole Faust	// Additionally, ensure that errors originating in
529*4947cdc7SCole Faust	// Starlark and/or Go each have an accurate frame.
530*4947cdc7SCole Faust	// The topmost frame, if built-in, is not shown,
531*4947cdc7SCole Faust	// but the name of the built-in function is shown
532*4947cdc7SCole Faust	// as "Error in fn: ...".
533*4947cdc7SCole Faust	//
534*4947cdc7SCole Faust	// This program fails in Starlark (f) if x==0,
535*4947cdc7SCole Faust	// or in Go (string.join) if x is non-zero.
536*4947cdc7SCole Faust	const src2 = `
537*4947cdc7SCole Faustdef f(): ''.join([1//i])
538*4947cdc7SCole Faustf()
539*4947cdc7SCole Faust`
540*4947cdc7SCole Faust	for i, want := range []string{
541*4947cdc7SCole Faust		0: `Traceback (most recent call last):
542*4947cdc7SCole Faust  crash.star:3:2: in <toplevel>
543*4947cdc7SCole Faust  crash.star:2:20: in f
544*4947cdc7SCole FaustError: floored division by zero`,
545*4947cdc7SCole Faust		1: `Traceback (most recent call last):
546*4947cdc7SCole Faust  crash.star:3:2: in <toplevel>
547*4947cdc7SCole Faust  crash.star:2:17: in f
548*4947cdc7SCole FaustError in join: join: in list, want string, got int`,
549*4947cdc7SCole Faust	} {
550*4947cdc7SCole Faust		globals := starlark.StringDict{"i": starlark.MakeInt(i)}
551*4947cdc7SCole Faust		_, err := starlark.ExecFile(thread, "crash.star", src2, globals)
552*4947cdc7SCole Faust		if got := backtrace(t, err); got != want {
553*4947cdc7SCole Faust			t.Errorf("error was %s, want %s", got, want)
554*4947cdc7SCole Faust		}
555*4947cdc7SCole Faust	}
556*4947cdc7SCole Faust}
557*4947cdc7SCole Faust
558*4947cdc7SCole Faustfunc TestLoadBacktrace(t *testing.T) {
559*4947cdc7SCole Faust	// This test ensures that load() does NOT preserve stack traces,
560*4947cdc7SCole Faust	// but that API callers can get them with Unwrap().
561*4947cdc7SCole Faust	// For discussion, see:
562*4947cdc7SCole Faust	// https://github.com/google/starlark-go/pull/244
563*4947cdc7SCole Faust	const src = `
564*4947cdc7SCole Faustload('crash.star', 'x')
565*4947cdc7SCole Faust`
566*4947cdc7SCole Faust	const loadedSrc = `
567*4947cdc7SCole Faustdef f(x):
568*4947cdc7SCole Faust  return 1 // x
569*4947cdc7SCole Faust
570*4947cdc7SCole Faustf(0)
571*4947cdc7SCole Faust`
572*4947cdc7SCole Faust	thread := new(starlark.Thread)
573*4947cdc7SCole Faust	thread.Load = func(t *starlark.Thread, module string) (starlark.StringDict, error) {
574*4947cdc7SCole Faust		return starlark.ExecFile(new(starlark.Thread), module, loadedSrc, nil)
575*4947cdc7SCole Faust	}
576*4947cdc7SCole Faust	_, err := starlark.ExecFile(thread, "root.star", src, nil)
577*4947cdc7SCole Faust
578*4947cdc7SCole Faust	const want = `Traceback (most recent call last):
579*4947cdc7SCole Faust  root.star:2:1: in <toplevel>
580*4947cdc7SCole FaustError: cannot load crash.star: floored division by zero`
581*4947cdc7SCole Faust	if got := backtrace(t, err); got != want {
582*4947cdc7SCole Faust		t.Errorf("error was %s, want %s", got, want)
583*4947cdc7SCole Faust	}
584*4947cdc7SCole Faust
585*4947cdc7SCole Faust	unwrapEvalError := func(err error) *starlark.EvalError {
586*4947cdc7SCole Faust		var result *starlark.EvalError
587*4947cdc7SCole Faust		for {
588*4947cdc7SCole Faust			if evalErr, ok := err.(*starlark.EvalError); ok {
589*4947cdc7SCole Faust				result = evalErr
590*4947cdc7SCole Faust			}
591*4947cdc7SCole Faust
592*4947cdc7SCole Faust			// TODO: use errors.Unwrap when go >=1.13 is everywhere.
593*4947cdc7SCole Faust			wrapper, isWrapper := err.(Wrapper)
594*4947cdc7SCole Faust			if !isWrapper {
595*4947cdc7SCole Faust				break
596*4947cdc7SCole Faust			}
597*4947cdc7SCole Faust			err = wrapper.Unwrap()
598*4947cdc7SCole Faust		}
599*4947cdc7SCole Faust		return result
600*4947cdc7SCole Faust	}
601*4947cdc7SCole Faust
602*4947cdc7SCole Faust	unwrappedErr := unwrapEvalError(err)
603*4947cdc7SCole Faust	const wantUnwrapped = `Traceback (most recent call last):
604*4947cdc7SCole Faust  crash.star:5:2: in <toplevel>
605*4947cdc7SCole Faust  crash.star:3:12: in f
606*4947cdc7SCole FaustError: floored division by zero`
607*4947cdc7SCole Faust	if got := backtrace(t, unwrappedErr); got != wantUnwrapped {
608*4947cdc7SCole Faust		t.Errorf("error was %s, want %s", got, wantUnwrapped)
609*4947cdc7SCole Faust	}
610*4947cdc7SCole Faust
611*4947cdc7SCole Faust}
612*4947cdc7SCole Faust
613*4947cdc7SCole Faust// TestRepeatedExec parses and resolves a file syntax tree once then
614*4947cdc7SCole Faust// executes it repeatedly with different values of its predeclared variables.
615*4947cdc7SCole Faustfunc TestRepeatedExec(t *testing.T) {
616*4947cdc7SCole Faust	predeclared := starlark.StringDict{"x": starlark.None}
617*4947cdc7SCole Faust	_, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has)
618*4947cdc7SCole Faust	if err != nil {
619*4947cdc7SCole Faust		t.Fatal(err)
620*4947cdc7SCole Faust	}
621*4947cdc7SCole Faust
622*4947cdc7SCole Faust	for _, test := range []struct {
623*4947cdc7SCole Faust		x, want starlark.Value
624*4947cdc7SCole Faust	}{
625*4947cdc7SCole Faust		{x: starlark.MakeInt(42), want: starlark.MakeInt(84)},
626*4947cdc7SCole Faust		{x: starlark.String("mur"), want: starlark.String("murmur")},
627*4947cdc7SCole Faust		{x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}},
628*4947cdc7SCole Faust	} {
629*4947cdc7SCole Faust		predeclared["x"] = test.x // update the values in dictionary
630*4947cdc7SCole Faust		thread := new(starlark.Thread)
631*4947cdc7SCole Faust		if globals, err := prog.Init(thread, predeclared); err != nil {
632*4947cdc7SCole Faust			t.Errorf("x=%v: %v", test.x, err) // exec error
633*4947cdc7SCole Faust		} else if eq, err := starlark.Equal(globals["y"], test.want); err != nil {
634*4947cdc7SCole Faust			t.Errorf("x=%v: %v", test.x, err) // comparison error
635*4947cdc7SCole Faust		} else if !eq {
636*4947cdc7SCole Faust			t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
637*4947cdc7SCole Faust		}
638*4947cdc7SCole Faust	}
639*4947cdc7SCole Faust}
640*4947cdc7SCole Faust
641*4947cdc7SCole Faust// TestEmptyFilePosition ensures that even Programs
642*4947cdc7SCole Faust// from empty files have a valid position.
643*4947cdc7SCole Faustfunc TestEmptyPosition(t *testing.T) {
644*4947cdc7SCole Faust	var predeclared starlark.StringDict
645*4947cdc7SCole Faust	for _, content := range []string{"", "empty = False"} {
646*4947cdc7SCole Faust		_, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has)
647*4947cdc7SCole Faust		if err != nil {
648*4947cdc7SCole Faust			t.Fatal(err)
649*4947cdc7SCole Faust		}
650*4947cdc7SCole Faust		if got, want := prog.Filename(), "hello.star"; got != want {
651*4947cdc7SCole Faust			t.Errorf("Program.Filename() = %q, want %q", got, want)
652*4947cdc7SCole Faust		}
653*4947cdc7SCole Faust	}
654*4947cdc7SCole Faust}
655*4947cdc7SCole Faust
656*4947cdc7SCole Faust// TestUnpackUserDefined tests that user-defined
657*4947cdc7SCole Faust// implementations of starlark.Value may be unpacked.
658*4947cdc7SCole Faustfunc TestUnpackUserDefined(t *testing.T) {
659*4947cdc7SCole Faust	// success
660*4947cdc7SCole Faust	want := new(hasfields)
661*4947cdc7SCole Faust	var x *hasfields
662*4947cdc7SCole Faust	if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil {
663*4947cdc7SCole Faust		t.Errorf("UnpackArgs failed: %v", err)
664*4947cdc7SCole Faust	}
665*4947cdc7SCole Faust	if x != want {
666*4947cdc7SCole Faust		t.Errorf("for x, got %v, want %v", x, want)
667*4947cdc7SCole Faust	}
668*4947cdc7SCole Faust
669*4947cdc7SCole Faust	// failure
670*4947cdc7SCole Faust	err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x)
671*4947cdc7SCole Faust	if want := "unpack: for parameter x: got int, want hasfields"; fmt.Sprint(err) != want {
672*4947cdc7SCole Faust		t.Errorf("unpack args error = %q, want %q", err, want)
673*4947cdc7SCole Faust	}
674*4947cdc7SCole Faust}
675*4947cdc7SCole Faust
676*4947cdc7SCole Fausttype optionalStringUnpacker struct {
677*4947cdc7SCole Faust	str   string
678*4947cdc7SCole Faust	isSet bool
679*4947cdc7SCole Faust}
680*4947cdc7SCole Faust
681*4947cdc7SCole Faustfunc (o *optionalStringUnpacker) Unpack(v starlark.Value) error {
682*4947cdc7SCole Faust	s, ok := starlark.AsString(v)
683*4947cdc7SCole Faust	if !ok {
684*4947cdc7SCole Faust		return fmt.Errorf("got %s, want string", v.Type())
685*4947cdc7SCole Faust	}
686*4947cdc7SCole Faust	o.str = s
687*4947cdc7SCole Faust	o.isSet = ok
688*4947cdc7SCole Faust	return nil
689*4947cdc7SCole Faust}
690*4947cdc7SCole Faust
691*4947cdc7SCole Faustfunc TestUnpackCustomUnpacker(t *testing.T) {
692*4947cdc7SCole Faust	a := optionalStringUnpacker{}
693*4947cdc7SCole Faust	wantA := optionalStringUnpacker{str: "a", isSet: true}
694*4947cdc7SCole Faust	b := optionalStringUnpacker{str: "b"}
695*4947cdc7SCole Faust	wantB := optionalStringUnpacker{str: "b"}
696*4947cdc7SCole Faust
697*4947cdc7SCole Faust	// Success
698*4947cdc7SCole Faust	if err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.String("a")}, nil, "a?", &a, "b?", &b); err != nil {
699*4947cdc7SCole Faust		t.Errorf("UnpackArgs failed: %v", err)
700*4947cdc7SCole Faust	}
701*4947cdc7SCole Faust	if a != wantA {
702*4947cdc7SCole Faust		t.Errorf("for a, got %v, want %v", a, wantA)
703*4947cdc7SCole Faust	}
704*4947cdc7SCole Faust	if b != wantB {
705*4947cdc7SCole Faust		t.Errorf("for b, got %v, want %v", b, wantB)
706*4947cdc7SCole Faust	}
707*4947cdc7SCole Faust
708*4947cdc7SCole Faust	// failure
709*4947cdc7SCole Faust	err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "a", &a)
710*4947cdc7SCole Faust	if want := "unpack: for parameter a: got int, want string"; fmt.Sprint(err) != want {
711*4947cdc7SCole Faust		t.Errorf("unpack args error = %q, want %q", err, want)
712*4947cdc7SCole Faust	}
713*4947cdc7SCole Faust}
714*4947cdc7SCole Faust
715*4947cdc7SCole Faustfunc TestAsInt(t *testing.T) {
716*4947cdc7SCole Faust	for _, test := range []struct {
717*4947cdc7SCole Faust		val  starlark.Value
718*4947cdc7SCole Faust		ptr  interface{}
719*4947cdc7SCole Faust		want string
720*4947cdc7SCole Faust	}{
721*4947cdc7SCole Faust		{starlark.MakeInt(42), new(int32), "42"},
722*4947cdc7SCole Faust		{starlark.MakeInt(-1), new(int32), "-1"},
723*4947cdc7SCole Faust		// Use Lsh not 1<<40 as the latter exceeds int if GOARCH=386.
724*4947cdc7SCole Faust		{starlark.MakeInt(1).Lsh(40), new(int32), "1099511627776 out of range (want value in signed 32-bit range)"},
725*4947cdc7SCole Faust		{starlark.MakeInt(-1).Lsh(40), new(int32), "-1099511627776 out of range (want value in signed 32-bit range)"},
726*4947cdc7SCole Faust
727*4947cdc7SCole Faust		{starlark.MakeInt(42), new(uint16), "42"},
728*4947cdc7SCole Faust		{starlark.MakeInt(0xffff), new(uint16), "65535"},
729*4947cdc7SCole Faust		{starlark.MakeInt(0x10000), new(uint16), "65536 out of range (want value in unsigned 16-bit range)"},
730*4947cdc7SCole Faust		{starlark.MakeInt(-1), new(uint16), "-1 out of range (want value in unsigned 16-bit range)"},
731*4947cdc7SCole Faust	} {
732*4947cdc7SCole Faust		var got string
733*4947cdc7SCole Faust		if err := starlark.AsInt(test.val, test.ptr); err != nil {
734*4947cdc7SCole Faust			got = err.Error()
735*4947cdc7SCole Faust		} else {
736*4947cdc7SCole Faust			got = fmt.Sprint(reflect.ValueOf(test.ptr).Elem().Interface())
737*4947cdc7SCole Faust		}
738*4947cdc7SCole Faust		if got != test.want {
739*4947cdc7SCole Faust			t.Errorf("AsInt(%s, %T): got %q, want %q", test.val, test.ptr, got, test.want)
740*4947cdc7SCole Faust		}
741*4947cdc7SCole Faust	}
742*4947cdc7SCole Faust}
743*4947cdc7SCole Faust
744*4947cdc7SCole Faustfunc TestDocstring(t *testing.T) {
745*4947cdc7SCole Faust	globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", `
746*4947cdc7SCole Faustdef somefunc():
747*4947cdc7SCole Faust	"somefunc doc"
748*4947cdc7SCole Faust	return 0
749*4947cdc7SCole Faust`, nil)
750*4947cdc7SCole Faust
751*4947cdc7SCole Faust	if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" {
752*4947cdc7SCole Faust		t.Fatal("docstring not found")
753*4947cdc7SCole Faust	}
754*4947cdc7SCole Faust}
755*4947cdc7SCole Faust
756*4947cdc7SCole Faustfunc TestFrameLocals(t *testing.T) {
757*4947cdc7SCole Faust	// trace prints a nice stack trace including argument
758*4947cdc7SCole Faust	// values of calls to Starlark functions.
759*4947cdc7SCole Faust	trace := func(thread *starlark.Thread) string {
760*4947cdc7SCole Faust		buf := new(bytes.Buffer)
761*4947cdc7SCole Faust		for i := 0; i < thread.CallStackDepth(); i++ {
762*4947cdc7SCole Faust			fr := thread.DebugFrame(i)
763*4947cdc7SCole Faust			fmt.Fprintf(buf, "%s(", fr.Callable().Name())
764*4947cdc7SCole Faust			if fn, ok := fr.Callable().(*starlark.Function); ok {
765*4947cdc7SCole Faust				for i := 0; i < fn.NumParams(); i++ {
766*4947cdc7SCole Faust					if i > 0 {
767*4947cdc7SCole Faust						buf.WriteString(", ")
768*4947cdc7SCole Faust					}
769*4947cdc7SCole Faust					name, _ := fn.Param(i)
770*4947cdc7SCole Faust					fmt.Fprintf(buf, "%s=%s", name, fr.Local(i))
771*4947cdc7SCole Faust				}
772*4947cdc7SCole Faust			} else {
773*4947cdc7SCole Faust				buf.WriteString("...") // a built-in function
774*4947cdc7SCole Faust			}
775*4947cdc7SCole Faust			buf.WriteString(")\n")
776*4947cdc7SCole Faust		}
777*4947cdc7SCole Faust		return buf.String()
778*4947cdc7SCole Faust	}
779*4947cdc7SCole Faust
780*4947cdc7SCole Faust	var got string
781*4947cdc7SCole Faust	builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
782*4947cdc7SCole Faust		got = trace(thread)
783*4947cdc7SCole Faust		return starlark.None, nil
784*4947cdc7SCole Faust	}
785*4947cdc7SCole Faust	predeclared := starlark.StringDict{
786*4947cdc7SCole Faust		"builtin": starlark.NewBuiltin("builtin", builtin),
787*4947cdc7SCole Faust	}
788*4947cdc7SCole Faust	_, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", `
789*4947cdc7SCole Faustdef f(x, y): builtin()
790*4947cdc7SCole Faustdef g(z): f(z, z*z)
791*4947cdc7SCole Faustg(7)
792*4947cdc7SCole Faust`, predeclared)
793*4947cdc7SCole Faust	if err != nil {
794*4947cdc7SCole Faust		t.Errorf("ExecFile failed: %v", err)
795*4947cdc7SCole Faust	}
796*4947cdc7SCole Faust
797*4947cdc7SCole Faust	var want = `
798*4947cdc7SCole Faustbuiltin(...)
799*4947cdc7SCole Faustf(x=7, y=49)
800*4947cdc7SCole Faustg(z=7)
801*4947cdc7SCole Faust<toplevel>()
802*4947cdc7SCole Faust`[1:]
803*4947cdc7SCole Faust	if got != want {
804*4947cdc7SCole Faust		t.Errorf("got <<%s>>, want <<%s>>", got, want)
805*4947cdc7SCole Faust	}
806*4947cdc7SCole Faust}
807*4947cdc7SCole Faust
808*4947cdc7SCole Fausttype badType string
809*4947cdc7SCole Faust
810*4947cdc7SCole Faustfunc (b *badType) String() string        { return "badType" }
811*4947cdc7SCole Faustfunc (b *badType) Type() string          { return "badType:" + string(*b) } // panics if b==nil
812*4947cdc7SCole Faustfunc (b *badType) Truth() starlark.Bool  { return true }
813*4947cdc7SCole Faustfunc (b *badType) Hash() (uint32, error) { return 0, nil }
814*4947cdc7SCole Faustfunc (b *badType) Freeze()               {}
815*4947cdc7SCole Faust
816*4947cdc7SCole Faustvar _ starlark.Value = new(badType)
817*4947cdc7SCole Faust
818*4947cdc7SCole Faust// TestUnpackErrorBadType verifies that the Unpack functions fail
819*4947cdc7SCole Faust// gracefully when a parameter's default value's Type method panics.
820*4947cdc7SCole Faustfunc TestUnpackErrorBadType(t *testing.T) {
821*4947cdc7SCole Faust	for _, test := range []struct {
822*4947cdc7SCole Faust		x    *badType
823*4947cdc7SCole Faust		want string
824*4947cdc7SCole Faust	}{
825*4947cdc7SCole Faust		{new(badType), "got NoneType, want badType"},       // Starlark type name
826*4947cdc7SCole Faust		{nil, "got NoneType, want *starlark_test.badType"}, // Go type name
827*4947cdc7SCole Faust	} {
828*4947cdc7SCole Faust		err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x)
829*4947cdc7SCole Faust		if err == nil {
830*4947cdc7SCole Faust			t.Errorf("UnpackArgs succeeded unexpectedly")
831*4947cdc7SCole Faust			continue
832*4947cdc7SCole Faust		}
833*4947cdc7SCole Faust		if !strings.Contains(err.Error(), test.want) {
834*4947cdc7SCole Faust			t.Errorf("UnpackArgs error %q does not contain %q", err, test.want)
835*4947cdc7SCole Faust		}
836*4947cdc7SCole Faust	}
837*4947cdc7SCole Faust}
838*4947cdc7SCole Faust
839*4947cdc7SCole Faust// Regression test for github.com/google/starlark-go/issues/233.
840*4947cdc7SCole Faustfunc TestREPLChunk(t *testing.T) {
841*4947cdc7SCole Faust	thread := new(starlark.Thread)
842*4947cdc7SCole Faust	globals := make(starlark.StringDict)
843*4947cdc7SCole Faust	exec := func(src string) {
844*4947cdc7SCole Faust		f, err := syntax.Parse("<repl>", src, 0)
845*4947cdc7SCole Faust		if err != nil {
846*4947cdc7SCole Faust			t.Fatal(err)
847*4947cdc7SCole Faust		}
848*4947cdc7SCole Faust		if err := starlark.ExecREPLChunk(f, thread, globals); err != nil {
849*4947cdc7SCole Faust			t.Fatal(err)
850*4947cdc7SCole Faust		}
851*4947cdc7SCole Faust	}
852*4947cdc7SCole Faust
853*4947cdc7SCole Faust	exec("x = 0; y = 0")
854*4947cdc7SCole Faust	if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "0 0"; got != want {
855*4947cdc7SCole Faust		t.Fatalf("chunk1: got %s, want %s", got, want)
856*4947cdc7SCole Faust	}
857*4947cdc7SCole Faust
858*4947cdc7SCole Faust	exec("x += 1; y = y + 1")
859*4947cdc7SCole Faust	if got, want := fmt.Sprintf("%v %v", globals["x"], globals["y"]), "1 1"; got != want {
860*4947cdc7SCole Faust		t.Fatalf("chunk2: got %s, want %s", got, want)
861*4947cdc7SCole Faust	}
862*4947cdc7SCole Faust}
863*4947cdc7SCole Faust
864*4947cdc7SCole Faustfunc TestCancel(t *testing.T) {
865*4947cdc7SCole Faust	// A thread cancelled before it begins executes no code.
866*4947cdc7SCole Faust	{
867*4947cdc7SCole Faust		thread := new(starlark.Thread)
868*4947cdc7SCole Faust		thread.Cancel("nope")
869*4947cdc7SCole Faust		_, err := starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil)
870*4947cdc7SCole Faust		if fmt.Sprint(err) != "Starlark computation cancelled: nope" {
871*4947cdc7SCole Faust			t.Errorf("execution returned error %q, want cancellation", err)
872*4947cdc7SCole Faust		}
873*4947cdc7SCole Faust
874*4947cdc7SCole Faust		// cancellation is sticky
875*4947cdc7SCole Faust		_, err = starlark.ExecFile(thread, "precancel.star", `x = 1//0`, nil)
876*4947cdc7SCole Faust		if fmt.Sprint(err) != "Starlark computation cancelled: nope" {
877*4947cdc7SCole Faust			t.Errorf("execution returned error %q, want cancellation", err)
878*4947cdc7SCole Faust		}
879*4947cdc7SCole Faust	}
880*4947cdc7SCole Faust	// A thread cancelled during a built-in executes no more code.
881*4947cdc7SCole Faust	{
882*4947cdc7SCole Faust		thread := new(starlark.Thread)
883*4947cdc7SCole Faust		predeclared := starlark.StringDict{
884*4947cdc7SCole Faust			"stopit": starlark.NewBuiltin("stopit", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
885*4947cdc7SCole Faust				thread.Cancel(fmt.Sprint(args[0]))
886*4947cdc7SCole Faust				return starlark.None, nil
887*4947cdc7SCole Faust			}),
888*4947cdc7SCole Faust		}
889*4947cdc7SCole Faust		_, err := starlark.ExecFile(thread, "stopit.star", `msg = 'nope'; stopit(msg); x = 1//0`, predeclared)
890*4947cdc7SCole Faust		if fmt.Sprint(err) != `Starlark computation cancelled: "nope"` {
891*4947cdc7SCole Faust			t.Errorf("execution returned error %q, want cancellation", err)
892*4947cdc7SCole Faust		}
893*4947cdc7SCole Faust	}
894*4947cdc7SCole Faust}
895*4947cdc7SCole Faust
896*4947cdc7SCole Faustfunc TestExecutionSteps(t *testing.T) {
897*4947cdc7SCole Faust	// A Thread records the number of computation steps.
898*4947cdc7SCole Faust	thread := new(starlark.Thread)
899*4947cdc7SCole Faust	countSteps := func(n int) (uint64, error) {
900*4947cdc7SCole Faust		predeclared := starlark.StringDict{"n": starlark.MakeInt(n)}
901*4947cdc7SCole Faust		steps0 := thread.ExecutionSteps()
902*4947cdc7SCole Faust		_, err := starlark.ExecFile(thread, "steps.star", `squares = [x*x for x in range(n)]`, predeclared)
903*4947cdc7SCole Faust		return thread.ExecutionSteps() - steps0, err
904*4947cdc7SCole Faust	}
905*4947cdc7SCole Faust	steps100, err := countSteps(1000)
906*4947cdc7SCole Faust	if err != nil {
907*4947cdc7SCole Faust		t.Errorf("execution failed: %v", err)
908*4947cdc7SCole Faust	}
909*4947cdc7SCole Faust	steps10000, err := countSteps(100000)
910*4947cdc7SCole Faust	if err != nil {
911*4947cdc7SCole Faust		t.Errorf("execution failed: %v", err)
912*4947cdc7SCole Faust	}
913*4947cdc7SCole Faust	if ratio := float64(steps10000) / float64(steps100); ratio < 99 || ratio > 101 {
914*4947cdc7SCole Faust		t.Errorf("computation steps did not increase linearly: f(100)=%d, f(10000)=%d, ratio=%g, want ~100", steps100, steps10000, ratio)
915*4947cdc7SCole Faust	}
916*4947cdc7SCole Faust
917*4947cdc7SCole Faust	// Exceeding the step limit causes cancellation.
918*4947cdc7SCole Faust	thread.SetMaxExecutionSteps(1000)
919*4947cdc7SCole Faust	_, err = countSteps(1000)
920*4947cdc7SCole Faust	if fmt.Sprint(err) != "Starlark computation cancelled: too many steps" {
921*4947cdc7SCole Faust		t.Errorf("execution returned error %q, want cancellation", err)
922*4947cdc7SCole Faust	}
923*4947cdc7SCole Faust}
924*4947cdc7SCole Faust
925*4947cdc7SCole Faust// TestDeps fails if the interpreter proper (not the REPL, etc) sprouts new external dependencies.
926*4947cdc7SCole Faust// We may expand the list of permitted dependencies, but should do so deliberately, not casually.
927*4947cdc7SCole Faustfunc TestDeps(t *testing.T) {
928*4947cdc7SCole Faust	cmd := exec.Command("go", "list", "-deps")
929*4947cdc7SCole Faust	out, err := cmd.Output()
930*4947cdc7SCole Faust	if err != nil {
931*4947cdc7SCole Faust		t.Skipf("'go list' failed: %s", err)
932*4947cdc7SCole Faust	}
933*4947cdc7SCole Faust	for _, pkg := range strings.Split(string(out), "\n") {
934*4947cdc7SCole Faust		// Does pkg have form "domain.name/dir"?
935*4947cdc7SCole Faust		slash := strings.IndexByte(pkg, '/')
936*4947cdc7SCole Faust		dot := strings.IndexByte(pkg, '.')
937*4947cdc7SCole Faust		if 0 < dot && dot < slash {
938*4947cdc7SCole Faust			if strings.HasPrefix(pkg, "go.starlark.net/") ||
939*4947cdc7SCole Faust				strings.HasPrefix(pkg, "golang.org/x/sys/") {
940*4947cdc7SCole Faust				continue // permitted dependencies
941*4947cdc7SCole Faust			}
942*4947cdc7SCole Faust			t.Errorf("new interpreter dependency: %s", pkg)
943*4947cdc7SCole Faust		}
944*4947cdc7SCole Faust	}
945*4947cdc7SCole Faust}
946