1// 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
5package starlark_test
6
7import (
8	"bytes"
9	"fmt"
10	"math"
11	"os/exec"
12	"path/filepath"
13	"reflect"
14	"sort"
15	"strings"
16	"testing"
17
18	"go.starlark.net/internal/chunkedfile"
19	"go.starlark.net/resolve"
20	"go.starlark.net/starlark"
21	"go.starlark.net/starlarkjson"
22	"go.starlark.net/starlarkstruct"
23	"go.starlark.net/starlarktest"
24	"go.starlark.net/syntax"
25)
26
27// A test may enable non-standard options by containing (e.g.) "option:recursion".
28func setOptions(src string) {
29	resolve.AllowGlobalReassign = option(src, "globalreassign")
30	resolve.LoadBindsGlobally = option(src, "loadbindsglobally")
31	resolve.AllowRecursion = option(src, "recursion")
32	resolve.AllowSet = option(src, "set")
33}
34
35func option(chunk, name string) bool {
36	return strings.Contains(chunk, "option:"+name)
37}
38
39// Wrapper is the type of errors with an Unwrap method; see https://golang.org/pkg/errors.
40type Wrapper interface {
41	Unwrap() error
42}
43
44func TestEvalExpr(t *testing.T) {
45	// 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)
49	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)`},
72		{`1, 2`, `(1, 2)`},
73		// 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
98		if v, err := starlark.Eval(thread, "<expr>", test.src, nil); err != nil {
99			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) {
110	defer setOptions("")
111	testdata := starlarktest.DataFile("starlark", ".")
112	thread := &starlark.Thread{Load: load}
113	starlarktest.SetReporter(thread, t)
114	for _, file := range []string{
115		"testdata/assign.star",
116		"testdata/bool.star",
117		"testdata/builtins.star",
118		"testdata/bytes.star",
119		"testdata/control.star",
120		"testdata/dict.star",
121		"testdata/float.star",
122		"testdata/function.star",
123		"testdata/int.star",
124		"testdata/json.star",
125		"testdata/list.star",
126		"testdata/misc.star",
127		"testdata/set.star",
128		"testdata/string.star",
129		"testdata/tuple.star",
130		"testdata/recursion.star",
131		"testdata/module.star",
132	} {
133		filename := filepath.Join(testdata, file)
134		for _, chunk := range chunkedfile.Read(filename, t) {
135			predeclared := starlark.StringDict{
136				"hasfields": starlark.NewBuiltin("hasfields", newHasFields),
137				"fibonacci": fib{},
138				"struct":    starlark.NewBuiltin("struct", starlarkstruct.Make),
139			}
140
141			setOptions(chunk.Source)
142			resolve.AllowLambda = true // used extensively
143
144			_, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared)
145			switch err := err.(type) {
146			case *starlark.EvalError:
147				found := false
148				for i := range err.CallStack {
149					posn := err.CallStack.At(i).Pos
150					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:
162				t.Errorf("\n%s", err)
163			}
164			chunk.Done()
165		}
166	}
167}
168
169// A fib is an iterable value representing the infinite Fibonacci sequence.
170type fib struct{}
171
172func (t fib) Freeze()                    {}
173func (t fib) String() string             { return "fib" }
174func (t fib) Type() string               { return "fib" }
175func (t fib) Truth() starlark.Bool       { return true }
176func (t fib) Hash() (uint32, error)      { return 0, fmt.Errorf("fib is unhashable") }
177func (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} }
178
179type fibIterator struct{ x, y int }
180
181func (it *fibIterator) Next(p *starlark.Value) bool {
182	*p = starlark.MakeInt(it.x)
183	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.
189func load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
190	if module == "assert.star" {
191		return starlarktest.LoadAssertModule()
192	}
193	if module == "json.star" {
194		return starlark.StringDict{"json": starlarkjson.Module}, nil
195	}
196
197	// TODO(adonovan): test load() using this execution path.
198	filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module)
199	return starlark.ExecFile(thread, filename, nil, nil)
200}
201
202func 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	}
206	return &hasfields{attrs: make(map[string]starlark.Value)}, nil
207}
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 {
214	attrs  starlark.StringDict
215	frozen bool
216}
217
218var (
219	_ starlark.HasAttrs  = (*hasfields)(nil)
220	_ starlark.HasBinary = (*hasfields)(nil)
221)
222
223func (hf *hasfields) String() string        { return "hasfields" }
224func (hf *hasfields) Type() string          { return "hasfields" }
225func (hf *hasfields) Truth() starlark.Bool  { return true }
226func (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
237func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil }
238
239func (hf *hasfields) SetField(name string, val starlark.Value) error {
240	if hf.frozen {
241		return fmt.Errorf("cannot set field on a frozen hasfields")
242	}
243	if strings.HasPrefix(name, "no") { // for testing
244		return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name))
245	}
246	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	}
255	sort.Strings(names)
256	return names
257}
258
259func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
260	// This method exists so we can exercise 'list += x'
261	// where x is not Iterable but defines list+x.
262	if op == syntax.PLUS {
263		if _, ok := y.(*starlark.List); ok {
264			return starlark.MakeInt(42), nil // list+hasfields is 42
265		}
266	}
267	return nil, nil
268}
269
270func 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
285def 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
289def 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
293`
294
295	thread := new(starlark.Thread)
296	globals, err := starlark.ExecFile(thread, filename, src, nil)
297	if err != nil {
298		t.Fatal(err)
299	}
300
301	// All errors are dynamic; see resolver for static errors.
302	for _, test := range []struct{ src, want string }{
303		// a()
304		{`a()`, `None`},
305		{`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)`},
312		{`b(1, 2)`, `(1, 2)`},
313		{`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
314		{`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`},
315		{`b(1, b=2)`, `(1, 2)`},
316		{`b(1, a=2)`, `function b got multiple values for parameter "a"`},
317		{`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"`},
322
323		// c(a, b=42)
324		{`c()`, `function c missing 1 argument (a)`},
325		{`c(1)`, `(1, 42)`},
326		{`c(1, 2)`, `(1, 2)`},
327		{`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`},
328		{`c(1, b=2)`, `(1, 2)`},
329		{`c(1, a=2)`, `function c got multiple values for parameter "a"`},
330		{`c(a=1, b=2)`, `(1, 2)`},
331		{`c(b=1, a=2)`, `(2, 1)`},
332
333		// d(*args)
334		{`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"`},
339
340		// e(**kwargs)
341		{`e()`, `{}`},
342		{`e(1)`, `function e accepts 0 positional arguments (1 given)`},
343		{`e(k=1)`, `{"k": 1}`},
344		{`e(kwargs={})`, `{"kwargs": {}}`},
345
346		// f(a, b=42, *args, **kwargs)
347		{`f()`, `function f missing 1 argument (a)`},
348		{`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, (), {})`},
355		{`f(0, a=1)`, `function f got multiple values for parameter "a"`},
356		{`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
357
358		// g(a, b=42, *args, c=123, **kwargs)
359		{`g()`, `function g missing 1 argument (a)`},
360		{`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, {})`},
366		{`g(0, a=1)`, `function g got multiple values for parameter "a"`},
367		{`g(0, b=1, c=2, d=3)`, `(0, 1, (), 2, {"d": 3})`},
368
369		// h(a, b=42, *, c=123, **kwargs)
370		{`h()`, `function h missing 1 argument (a)`},
371		{`h(0)`, `(0, 42, 123, {})`},
372		{`h(0, 1)`, `(0, 1, 123, {})`},
373		{`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`},
374		{`h(a=0)`, `(0, 42, 123, {})`},
375		{`h(0, b=1)`, `(0, 1, 123, {})`},
376		{`h(0, a=1)`, `function h got multiple values for parameter "a"`},
377		{`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})`},
380
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, {})`},
413	} {
414		var got string
415		if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil {
416			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
426// TestPrint ensures that the Starlark print function calls
427// Thread.Print, if provided.
428func TestPrint(t *testing.T) {
429	const src = `
430print("hello")
431def f(): print("hello", "world", sep=", ")
432f()
433`
434	buf := new(bytes.Buffer)
435	print := func(thread *starlark.Thread, msg string) {
436		caller := thread.CallFrame(1)
437		fmt.Fprintf(buf, "%s: %s: %s\n", caller.Pos, caller.Name, msg)
438	}
439	thread := &starlark.Thread{Print: print}
440	if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil {
441		t.Fatal(err)
442	}
443	want := "foo.star:2:6: <toplevel>: hello\n" +
444		"foo.star:3:15: f: hello, world\n"
445	if got := buf.String(); got != want {
446		t.Errorf("output was %s, want %s", got, want)
447	}
448}
449
450func reportEvalError(tb testing.TB, err error) {
451	if err, ok := err.(*starlark.EvalError); ok {
452		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) {
460	one := starlark.MakeInt(1)
461
462	for _, test := range []struct {
463		i          starlark.Int
464		wantInt64  string
465		wantUint64 string
466	}{
467		{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"},
475	} {
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
492func 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)
500	}
501	panic("unreachable")
502}
503
504func TestBacktrace(t *testing.T) {
505	// This test ensures continuity of the stack of active Starlark
506	// functions, including propagation through built-ins such as 'min'.
507	const src = `
508def f(x): return 1//x
509def g(x): return f(x)
510def h(): return min([1, 2, 0], key=g)
511def i(): return h()
512i()
513`
514	thread := new(starlark.Thread)
515	_, err := starlark.ExecFile(thread, "crash.star", src, nil)
516	const want = `Traceback (most recent call last):
517  crash.star:6:2: in <toplevel>
518  crash.star:5:18: in i
519  crash.star:4:20: in h
520  <builtin>: in min
521  crash.star:3:19: in g
522  crash.star:2:19: in f
523Error: floored division by zero`
524	if got := backtrace(t, err); got != want {
525		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.
530	// 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: ...".
533	//
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):
542  crash.star:3:2: in <toplevel>
543  crash.star:2:20: in f
544Error: floored division by zero`,
545		1: `Traceback (most recent call last):
546  crash.star:3:2: in <toplevel>
547  crash.star:2:17: in f
548Error in join: join: in list, want string, got int`,
549	} {
550		globals := starlark.StringDict{"i": starlark.MakeInt(i)}
551		_, err := starlark.ExecFile(thread, "crash.star", src2, globals)
552		if got := backtrace(t, err); got != want {
553			t.Errorf("error was %s, want %s", got, want)
554		}
555	}
556}
557
558func 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
613// TestRepeatedExec parses and resolves a file syntax tree once then
614// executes it repeatedly with different values of its predeclared variables.
615func TestRepeatedExec(t *testing.T) {
616	predeclared := starlark.StringDict{"x": starlark.None}
617	_, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has)
618	if err != nil {
619		t.Fatal(err)
620	}
621
622	for _, test := range []struct {
623		x, want starlark.Value
624	}{
625		{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}},
628	} {
629		predeclared["x"] = test.x // update the values in dictionary
630		thread := new(starlark.Thread)
631		if globals, err := prog.Init(thread, predeclared); err != nil {
632			t.Errorf("x=%v: %v", test.x, err) // exec error
633		} else if eq, err := starlark.Equal(globals["y"], test.want); err != nil {
634			t.Errorf("x=%v: %v", test.x, err) // comparison error
635		} else if !eq {
636			t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
637		}
638	}
639}
640
641// 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
656// TestUnpackUserDefined tests that user-defined
657// implementations of starlark.Value may be unpacked.
658func TestUnpackUserDefined(t *testing.T) {
659	// success
660	want := new(hasfields)
661	var x *hasfields
662	if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil {
663		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
670	err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x)
671	if want := "unpack: for parameter x: got int, want hasfields"; fmt.Sprint(err) != want {
672		t.Errorf("unpack args error = %q, want %q", err, want)
673	}
674}
675
676type 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
715func 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"},
723		// 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)"},
726
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
744func 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}
755
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)
761		for i := 0; i < thread.CallStackDepth(); i++ {
762			fr := thread.DebugFrame(i)
763			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}
807
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}
838
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}
863
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}
924
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}
946