go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/starlark/eval_test.go (about)

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