...

Source file src/go.starlark.net/starlark/eval_test.go

Documentation: go.starlark.net/starlark

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

View as plain text