github.com/k14s/starlark-go@v0.0.0-20200720175618-3a5c849cc368/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  	"fmt"
    10  	"math"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/k14s/starlark-go/internal/chunkedfile"
    17  	"github.com/k14s/starlark-go/resolve"
    18  	"github.com/k14s/starlark-go/starlark"
    19  	"github.com/k14s/starlark-go/starlarktest"
    20  	"github.com/k14s/starlark-go/syntax"
    21  )
    22  
    23  // A test may enable non-standard options by containing (e.g.) "option:recursion".
    24  func setOptions(src string) {
    25  	resolve.AllowFloat = option(src, "float")
    26  	resolve.AllowGlobalReassign = option(src, "globalreassign")
    27  	resolve.LoadBindsGlobally = option(src, "loadbindsglobally")
    28  	resolve.AllowLambda = option(src, "lambda")
    29  	resolve.AllowNestedDef = option(src, "nesteddef")
    30  	resolve.AllowRecursion = option(src, "recursion")
    31  	resolve.AllowSet = option(src, "set")
    32  }
    33  
    34  func option(chunk, name string) bool {
    35  	return strings.Contains(chunk, "option:"+name)
    36  }
    37  
    38  func TestEvalExpr(t *testing.T) {
    39  	// This is mostly redundant with the new *.star tests.
    40  	// TODO(adonovan): move checks into *.star files and
    41  	// reduce this to a mere unit test of starlark.Eval.
    42  	thread := new(starlark.Thread)
    43  	for _, test := range []struct{ src, want string }{
    44  		{`123`, `123`},
    45  		{`-1`, `-1`},
    46  		{`"a"+"b"`, `"ab"`},
    47  		{`1+2`, `3`},
    48  
    49  		// lists
    50  		{`[]`, `[]`},
    51  		{`[1]`, `[1]`},
    52  		{`[1,]`, `[1]`},
    53  		{`[1, 2]`, `[1, 2]`},
    54  		{`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`},
    55  		{`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`},
    56  		{`[(x, y) for x in [1, 2] for y in [3, 4]]`,
    57  			`[(1, 3), (1, 4), (2, 3), (2, 4)]`},
    58  		{`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`,
    59  			`[(2, 3), (2, 4)]`},
    60  		// tuples
    61  		{`()`, `()`},
    62  		{`(1)`, `1`},
    63  		{`(1,)`, `(1,)`},
    64  		{`(1, 2)`, `(1, 2)`},
    65  		{`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`},
    66  		{`1, 2`, `(1, 2)`},
    67  		// dicts
    68  		{`{}`, `{}`},
    69  		{`{"a": 1}`, `{"a": 1}`},
    70  		{`{"a": 1,}`, `{"a": 1}`},
    71  
    72  		// conditional
    73  		{`1 if 3 > 2 else 0`, `1`},
    74  		{`1 if "foo" else 0`, `1`},
    75  		{`1 if "" else 0`, `0`},
    76  
    77  		// indexing
    78  		{`["a", "b"][0]`, `"a"`},
    79  		{`["a", "b"][1]`, `"b"`},
    80  		{`("a", "b")[0]`, `"a"`},
    81  		{`("a", "b")[1]`, `"b"`},
    82  		{`"aΩb"[0]`, `"a"`},
    83  		{`"aΩb"[1]`, `"\xce"`},
    84  		{`"aΩb"[3]`, `"b"`},
    85  		{`{"a": 1}["a"]`, `1`},
    86  		{`{"a": 1}["b"]`, `key "b" not in dict`},
    87  		{`{}[[]]`, `unhashable type: list`},
    88  		{`{"a": 1}[[]]`, `unhashable type: list`},
    89  		{`[x for x in range(3)]`, "[0, 1, 2]"},
    90  	} {
    91  		var got string
    92  		if v, err := starlark.Eval(thread, "<expr>", test.src, nil); err != nil {
    93  			got = err.Error()
    94  		} else {
    95  			got = v.String()
    96  		}
    97  		if got != test.want {
    98  			t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
    99  		}
   100  	}
   101  }
   102  
   103  func TestExecFile(t *testing.T) {
   104  	defer setOptions("")
   105  	testdata := starlarktest.DataFile("starlark", ".")
   106  	thread := &starlark.Thread{Load: load}
   107  	starlarktest.SetReporter(thread, t)
   108  	for _, file := range []string{
   109  		"testdata/assign.star",
   110  		"testdata/bool.star",
   111  		"testdata/builtins.star",
   112  		"testdata/control.star",
   113  		"testdata/dict.star",
   114  		"testdata/float.star",
   115  		"testdata/function.star",
   116  		"testdata/int.star",
   117  		"testdata/list.star",
   118  		"testdata/misc.star",
   119  		"testdata/set.star",
   120  		"testdata/string.star",
   121  		"testdata/tuple.star",
   122  		"testdata/recursion.star",
   123  		"testdata/module.star",
   124  	} {
   125  		filename := filepath.Join(testdata, file)
   126  		for _, chunk := range chunkedfile.Read(filename, t) {
   127  			predeclared := starlark.StringDict{
   128  				"hasfields": starlark.NewBuiltin("hasfields", newHasFields),
   129  				"fibonacci": fib{},
   130  			}
   131  
   132  			setOptions(chunk.Source)
   133  			resolve.AllowLambda = true // used extensively
   134  
   135  			_, err := starlark.ExecFile(thread, filename, chunk.Source, predeclared)
   136  			switch err := err.(type) {
   137  			case *starlark.EvalError:
   138  				found := false
   139  				for i := range err.CallStack {
   140  					posn := err.CallStack.At(i).Pos
   141  					if posn.Filename() == filename {
   142  						chunk.GotError(int(posn.Line), err.Error())
   143  						found = true
   144  						break
   145  					}
   146  				}
   147  				if !found {
   148  					t.Error(err.Backtrace())
   149  				}
   150  			case nil:
   151  				// success
   152  			default:
   153  				t.Errorf("\n%s", err)
   154  			}
   155  			chunk.Done()
   156  		}
   157  	}
   158  }
   159  
   160  // A fib is an iterable value representing the infinite Fibonacci sequence.
   161  type fib struct{}
   162  
   163  func (t fib) Freeze()                    {}
   164  func (t fib) String() string             { return "fib" }
   165  func (t fib) Type() string               { return "fib" }
   166  func (t fib) Truth() starlark.Bool       { return true }
   167  func (t fib) Hash() (uint32, error)      { return 0, fmt.Errorf("fib is unhashable") }
   168  func (t fib) Iterate() starlark.Iterator { return &fibIterator{0, 1} }
   169  
   170  type fibIterator struct{ x, y int }
   171  
   172  func (it *fibIterator) Next(p *starlark.Value) bool {
   173  	*p = starlark.MakeInt(it.x)
   174  	it.x, it.y = it.y, it.x+it.y
   175  	return true
   176  }
   177  func (it *fibIterator) Done() {}
   178  
   179  // load implements the 'load' operation as used in the evaluator tests.
   180  func load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
   181  	if module == "assert.star" {
   182  		return starlarktest.LoadAssertModule()
   183  	}
   184  
   185  	// TODO(adonovan): test load() using this execution path.
   186  	filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module)
   187  	return starlark.ExecFile(thread, filename, nil, nil)
   188  }
   189  
   190  func newHasFields(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   191  	if len(args)+len(kwargs) > 0 {
   192  		return nil, fmt.Errorf("%s: unexpected arguments", b.Name())
   193  	}
   194  	return &hasfields{attrs: make(map[string]starlark.Value)}, nil
   195  }
   196  
   197  // hasfields is a test-only implementation of HasAttrs.
   198  // It permits any field to be set.
   199  // Clients will likely want to provide their own implementation,
   200  // so we don't have any public implementation.
   201  type hasfields struct {
   202  	attrs  starlark.StringDict
   203  	frozen bool
   204  }
   205  
   206  var (
   207  	_ starlark.HasAttrs  = (*hasfields)(nil)
   208  	_ starlark.HasBinary = (*hasfields)(nil)
   209  )
   210  
   211  func (hf *hasfields) String() string        { return "hasfields" }
   212  func (hf *hasfields) Type() string          { return "hasfields" }
   213  func (hf *hasfields) Truth() starlark.Bool  { return true }
   214  func (hf *hasfields) Hash() (uint32, error) { return 42, nil }
   215  
   216  func (hf *hasfields) Freeze() {
   217  	if !hf.frozen {
   218  		hf.frozen = true
   219  		for _, v := range hf.attrs {
   220  			v.Freeze()
   221  		}
   222  	}
   223  }
   224  
   225  func (hf *hasfields) Attr(name string) (starlark.Value, error) { return hf.attrs[name], nil }
   226  
   227  func (hf *hasfields) SetField(name string, val starlark.Value) error {
   228  	if hf.frozen {
   229  		return fmt.Errorf("cannot set field on a frozen hasfields")
   230  	}
   231  	if strings.HasPrefix(name, "no") { // for testing
   232  		return starlark.NoSuchAttrError(fmt.Sprintf("no .%s field", name))
   233  	}
   234  	hf.attrs[name] = val
   235  	return nil
   236  }
   237  
   238  func (hf *hasfields) AttrNames() []string {
   239  	names := make([]string, 0, len(hf.attrs))
   240  	for key := range hf.attrs {
   241  		names = append(names, key)
   242  	}
   243  	sort.Strings(names)
   244  	return names
   245  }
   246  
   247  func (hf *hasfields) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
   248  	// This method exists so we can exercise 'list += x'
   249  	// where x is not Iterable but defines list+x.
   250  	if op == syntax.PLUS {
   251  		if _, ok := y.(*starlark.List); ok {
   252  			return starlark.MakeInt(42), nil // list+hasfields is 42
   253  		}
   254  	}
   255  	return nil, nil
   256  }
   257  
   258  func TestParameterPassing(t *testing.T) {
   259  	const filename = "parameters.go"
   260  	const src = `
   261  def a():
   262  	return
   263  def b(a, b):
   264  	return a, b
   265  def c(a, b=42):
   266  	return a, b
   267  def d(*args):
   268  	return args
   269  def e(**kwargs):
   270  	return kwargs
   271  def f(a, b=42, *args, **kwargs):
   272  	return a, b, args, kwargs
   273  def g(a, b=42, *args, c=123, **kwargs):
   274  	return a, b, args, c, kwargs
   275  def h(a, b=42, *, c=123, **kwargs):
   276  	return a, b, c, kwargs
   277  def i(a, b=42, *, c, d=123, e, **kwargs):
   278  	return a, b, c, d, e, kwargs
   279  def j(a, b=42, *args, c, d=123, e, **kwargs):
   280  	return a, b, args, c, d, e, kwargs
   281  `
   282  
   283  	thread := new(starlark.Thread)
   284  	globals, err := starlark.ExecFile(thread, filename, src, nil)
   285  	if err != nil {
   286  		t.Fatal(err)
   287  	}
   288  
   289  	for _, test := range []struct{ src, want string }{
   290  		// a()
   291  		{`a()`, `None`},
   292  		{`a(1)`, `function a accepts no arguments (1 given)`},
   293  
   294  		// b(a, b)
   295  		{`b()`, `function b missing 2 arguments (a, b)`},
   296  		{`b(1)`, `function b missing 1 argument (b)`},
   297  		{`b(a=1)`, `function b missing 1 argument (b)`},
   298  		{`b(b=1)`, `function b missing 1 argument (a)`},
   299  		{`b(1, 2)`, `(1, 2)`},
   300  		{`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
   301  		{`b(1, 2, 3)`, `function b accepts 2 positional arguments (3 given)`},
   302  		{`b(1, b=2)`, `(1, 2)`},
   303  		{`b(1, a=2)`, `function b got multiple values for parameter "a"`},
   304  		{`b(1, x=2)`, `function b got an unexpected keyword argument "x"`},
   305  		{`b(a=1, b=2)`, `(1, 2)`},
   306  		{`b(b=1, a=2)`, `(2, 1)`},
   307  		{`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`},
   308  		{`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`},
   309  
   310  		// c(a, b=42)
   311  		{`c()`, `function c missing 1 argument (a)`},
   312  		{`c(1)`, `(1, 42)`},
   313  		{`c(1, 2)`, `(1, 2)`},
   314  		{`c(1, 2, 3)`, `function c accepts at most 2 positional arguments (3 given)`},
   315  		{`c(1, b=2)`, `(1, 2)`},
   316  		{`c(1, a=2)`, `function c got multiple values for parameter "a"`},
   317  		{`c(a=1, b=2)`, `(1, 2)`},
   318  		{`c(b=1, a=2)`, `(2, 1)`},
   319  
   320  		// d(*args)
   321  		{`d()`, `()`},
   322  		{`d(1)`, `(1,)`},
   323  		{`d(1, 2)`, `(1, 2)`},
   324  		{`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`},
   325  		{`d(args=[])`, `function d got an unexpected keyword argument "args"`},
   326  
   327  		// e(**kwargs)
   328  		{`e()`, `{}`},
   329  		{`e(1)`, `function e accepts 0 positional arguments (1 given)`},
   330  		{`e(k=1)`, `{"k": 1}`},
   331  		{`e(kwargs={})`, `{"kwargs": {}}`},
   332  
   333  		// f(a, b=42, *args, **kwargs)
   334  		{`f()`, `function f missing 1 argument (a)`},
   335  		{`f(0)`, `(0, 42, (), {})`},
   336  		{`f(0)`, `(0, 42, (), {})`},
   337  		{`f(0, 1)`, `(0, 1, (), {})`},
   338  		{`f(0, 1, 2)`, `(0, 1, (2,), {})`},
   339  		{`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`},
   340  		{`f(a=0)`, `(0, 42, (), {})`},
   341  		{`f(0, b=1)`, `(0, 1, (), {})`},
   342  		{`f(0, a=1)`, `function f got multiple values for parameter "a"`},
   343  		{`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
   344  		{`f(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, // github.com/google/skylark/issues/135
   345  			`(0, 1, (3, 4), {"x": 2, "y": 5, "z": 6})`},
   346  
   347  		// g(a, b=42, *args, c=123, **kwargs)
   348  		{`g()`, `function g missing 1 argument (a)`},
   349  		{`g(0)`, `(0, 42, (), 123, {})`},
   350  		{`g(0, 1)`, `(0, 1, (), 123, {})`},
   351  		{`g(0, 1, 2)`, `(0, 1, (2,), 123, {})`},
   352  		{`g(0, 1, 2, 3)`, `(0, 1, (2, 3), 123, {})`},
   353  		{`g(a=0)`, `(0, 42, (), 123, {})`},
   354  		{`g(0, b=1)`, `(0, 1, (), 123, {})`},
   355  		{`g(0, a=1)`, `function g got multiple values for parameter "a"`},
   356  		{`g(0, b=1, c=2, d=3)`, `(0, 1, (), 2, {"d": 3})`},
   357  		{`g(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`,
   358  			`(0, 1, (3, 4), 123, {"x": 2, "y": 5, "z": 6})`},
   359  
   360  		// h(a, b=42, *, c=123, **kwargs)
   361  		{`h()`, `function h missing 1 argument (a)`},
   362  		{`h(0)`, `(0, 42, 123, {})`},
   363  		{`h(0, 1)`, `(0, 1, 123, {})`},
   364  		{`h(0, 1, 2)`, `function h accepts at most 2 positional arguments (3 given)`},
   365  		{`h(a=0)`, `(0, 42, 123, {})`},
   366  		{`h(0, b=1)`, `(0, 1, 123, {})`},
   367  		{`h(0, a=1)`, `function h got multiple values for parameter "a"`},
   368  		{`h(0, b=1, c=2)`, `(0, 1, 2, {})`},
   369  		{`h(0, b=1, d=2)`, `(0, 1, 123, {"d": 2})`},
   370  		{`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
   371  		{`h(0, b=1, c=2, d=3)`, `(0, 1, 2, {"d": 3})`},
   372  
   373  		// i(a, b=42, *, c, d=123, e, **kwargs)
   374  		{`i()`, `function i missing 3 arguments (a, c, e)`},
   375  		{`i(0)`, `function i missing 2 arguments (c, e)`},
   376  		{`i(0, 1)`, `function i missing 2 arguments (c, e)`},
   377  		{`i(0, 1, 2)`, `function i accepts at most 2 positional arguments (3 given)`},
   378  		{`i(0, 1, e=2)`, `function i missing 1 argument (c)`},
   379  		{`i(0, 1, 2, 3)`, `function i accepts at most 2 positional arguments (4 given)`},
   380  		{`i(a=0)`, `function i missing 2 arguments (c, e)`},
   381  		{`i(0, b=1)`, `function i missing 2 arguments (c, e)`},
   382  		{`i(0, a=1)`, `function i got multiple values for parameter "a"`},
   383  		{`i(0, b=1, c=2)`, `function i missing 1 argument (e)`},
   384  		{`i(0, b=1, d=2)`, `function i missing 2 arguments (c, e)`},
   385  		{`i(0, b=1, c=2, d=3)`, `function i missing 1 argument (e)`},
   386  		{`i(0, b=1, c=2, d=3, e=4)`, `(0, 1, 2, 3, 4, {})`},
   387  		{`i(0, 1, b=1, c=2, d=3, e=4)`, `function i got multiple values for parameter "b"`},
   388  
   389  		// j(a, b=42, *args, c, d=123, e, **kwargs)
   390  		{`j()`, `function j missing 3 arguments (a, c, e)`},
   391  		{`j(0)`, `function j missing 2 arguments (c, e)`},
   392  		{`j(0, 1)`, `function j missing 2 arguments (c, e)`},
   393  		{`j(0, 1, 2)`, `function j missing 2 arguments (c, e)`},
   394  		{`j(0, 1, e=2)`, `function j missing 1 argument (c)`},
   395  		{`j(0, 1, 2, 3)`, `function j missing 2 arguments (c, e)`},
   396  		{`j(a=0)`, `function j missing 2 arguments (c, e)`},
   397  		{`j(0, b=1)`, `function j missing 2 arguments (c, e)`},
   398  		{`j(0, a=1)`, `function j got multiple values for parameter "a"`},
   399  		{`j(0, b=1, c=2)`, `function j missing 1 argument (e)`},
   400  		{`j(0, b=1, d=2)`, `function j missing 2 arguments (c, e)`},
   401  		{`j(0, b=1, c=2, d=3)`, `function j missing 1 argument (e)`},
   402  		{`j(0, b=1, c=2, d=3, e=4)`, `(0, 1, (), 2, 3, 4, {})`},
   403  		{`j(0, 1, b=1, c=2, d=3, e=4)`, `function j got multiple values for parameter "b"`},
   404  		{`j(0, 1, 2, c=3, e=4)`, `(0, 1, (2,), 3, 123, 4, {})`},
   405  	} {
   406  		var got string
   407  		if v, err := starlark.Eval(thread, "<expr>", test.src, globals); err != nil {
   408  			got = err.Error()
   409  		} else {
   410  			got = v.String()
   411  		}
   412  		if got != test.want {
   413  			t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
   414  		}
   415  	}
   416  }
   417  
   418  // TestPrint ensures that the Starlark print function calls
   419  // Thread.Print, if provided.
   420  func TestPrint(t *testing.T) {
   421  	const src = `
   422  print("hello")
   423  def f(): print("hello", "world", sep=", ")
   424  f()
   425  `
   426  	buf := new(bytes.Buffer)
   427  	print := func(thread *starlark.Thread, msg string) {
   428  		caller := thread.CallFrame(1)
   429  		fmt.Fprintf(buf, "%s: %s: %s\n", caller.Pos, caller.Name, msg)
   430  	}
   431  	thread := &starlark.Thread{Print: print}
   432  	if _, err := starlark.ExecFile(thread, "foo.star", src, nil); err != nil {
   433  		t.Fatal(err)
   434  	}
   435  	want := "foo.star:2:6: <toplevel>: hello\n" +
   436  		"foo.star:3:15: f: hello, world\n"
   437  	if got := buf.String(); got != want {
   438  		t.Errorf("output was %s, want %s", got, want)
   439  	}
   440  }
   441  
   442  func reportEvalError(tb testing.TB, err error) {
   443  	if err, ok := err.(*starlark.EvalError); ok {
   444  		tb.Fatal(err.Backtrace())
   445  	}
   446  	tb.Fatal(err)
   447  }
   448  
   449  // TestInt exercises the Int.Int64 and Int.Uint64 methods.
   450  // If we can move their logic into math/big, delete this test.
   451  func TestInt(t *testing.T) {
   452  	one := starlark.MakeInt(1)
   453  
   454  	for _, test := range []struct {
   455  		i          starlark.Int
   456  		wantInt64  string
   457  		wantUint64 string
   458  	}{
   459  		{starlark.MakeInt64(math.MinInt64).Sub(one), "error", "error"},
   460  		{starlark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"},
   461  		{starlark.MakeInt64(-1), "-1", "error"},
   462  		{starlark.MakeInt64(0), "0", "0"},
   463  		{starlark.MakeInt64(1), "1", "1"},
   464  		{starlark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"},
   465  		{starlark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"},
   466  		{starlark.MakeUint64(math.MaxUint64).Add(one), "error", "error"},
   467  	} {
   468  		gotInt64, gotUint64 := "error", "error"
   469  		if i, ok := test.i.Int64(); ok {
   470  			gotInt64 = fmt.Sprint(i)
   471  		}
   472  		if u, ok := test.i.Uint64(); ok {
   473  			gotUint64 = fmt.Sprint(u)
   474  		}
   475  		if gotInt64 != test.wantInt64 {
   476  			t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64)
   477  		}
   478  		if gotUint64 != test.wantUint64 {
   479  			t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64)
   480  		}
   481  	}
   482  }
   483  
   484  func TestBacktrace(t *testing.T) {
   485  	getBacktrace := func(err error) string {
   486  		switch err := err.(type) {
   487  		case *starlark.EvalError:
   488  			return err.Backtrace()
   489  		case nil:
   490  			t.Fatalf("ExecFile succeeded unexpectedly")
   491  		default:
   492  			t.Fatalf("ExecFile failed with %v, wanted *EvalError", err)
   493  		}
   494  		panic("unreachable")
   495  	}
   496  
   497  	// This test ensures continuity of the stack of active Starlark
   498  	// functions, including propagation through built-ins such as 'min'.
   499  	const src = `
   500  def f(x): return 1//x
   501  def g(x): f(x)
   502  def h(): return min([1, 2, 0], key=g)
   503  def i(): return h()
   504  i()
   505  `
   506  	thread := new(starlark.Thread)
   507  	_, err := starlark.ExecFile(thread, "crash.star", src, nil)
   508  	// Compiled code currently has no column information.
   509  	const want = `Traceback (most recent call last):
   510    crash.star:6:2: in <toplevel>
   511    crash.star:5:18: in i
   512    crash.star:4:20: in h
   513    <builtin>: in min
   514    crash.star:3:12: in g
   515    crash.star:2:19: in f
   516  Error: floored division by zero`
   517  	if got := getBacktrace(err); got != want {
   518  		t.Errorf("error was %s, want %s", got, want)
   519  	}
   520  
   521  	// Additionally, ensure that errors originating in
   522  	// Starlark and/or Go each have an accurate frame.
   523  	//
   524  	// This program fails in Starlark (f) if x==0,
   525  	// or in Go (string.join) if x is non-zero.
   526  	const src2 = `
   527  def f(): ''.join([1//i])
   528  f()
   529  `
   530  	for i, want := range []string{
   531  		0: `Traceback (most recent call last):
   532    crash.star:3:2: in <toplevel>
   533    crash.star:2:20: in f
   534  Error: floored division by zero`,
   535  		1: `Traceback (most recent call last):
   536    crash.star:3:2: in <toplevel>
   537    crash.star:2:17: in f
   538    <builtin>: in join
   539  Error: join: in list, want string, got int`,
   540  	} {
   541  		globals := starlark.StringDict{"i": starlark.MakeInt(i)}
   542  		_, err := starlark.ExecFile(thread, "crash.star", src2, globals)
   543  		if got := getBacktrace(err); got != want {
   544  			t.Errorf("error was %s, want %s", got, want)
   545  		}
   546  	}
   547  }
   548  
   549  // TestRepeatedExec parses and resolves a file syntax tree once then
   550  // executes it repeatedly with different values of its predeclared variables.
   551  func TestRepeatedExec(t *testing.T) {
   552  	predeclared := starlark.StringDict{"x": starlark.None}
   553  	_, prog, err := starlark.SourceProgram("repeat.star", "y = 2 * x", predeclared.Has)
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  
   558  	for _, test := range []struct {
   559  		x, want starlark.Value
   560  	}{
   561  		{x: starlark.MakeInt(42), want: starlark.MakeInt(84)},
   562  		{x: starlark.String("mur"), want: starlark.String("murmur")},
   563  		{x: starlark.Tuple{starlark.None}, want: starlark.Tuple{starlark.None, starlark.None}},
   564  	} {
   565  		predeclared["x"] = test.x // update the values in dictionary
   566  		thread := new(starlark.Thread)
   567  		if globals, err := prog.Init(thread, predeclared); err != nil {
   568  			t.Errorf("x=%v: %v", test.x, err) // exec error
   569  		} else if eq, err := starlark.Equal(globals["y"], test.want); err != nil {
   570  			t.Errorf("x=%v: %v", test.x, err) // comparison error
   571  		} else if !eq {
   572  			t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
   573  		}
   574  	}
   575  }
   576  
   577  // TestEmptyFilePosition ensures that even Programs
   578  // from empty files have a valid position.
   579  func TestEmptyPosition(t *testing.T) {
   580  	var predeclared starlark.StringDict
   581  	for _, content := range []string{"", "empty = False"} {
   582  		_, prog, err := starlark.SourceProgram("hello.star", content, predeclared.Has)
   583  		if err != nil {
   584  			t.Fatal(err)
   585  		}
   586  		if got, want := prog.Filename(), "hello.star"; got != want {
   587  			t.Errorf("Program.Filename() = %q, want %q", got, want)
   588  		}
   589  	}
   590  }
   591  
   592  // TestUnpackUserDefined tests that user-defined
   593  // implementations of starlark.Value may be unpacked.
   594  func TestUnpackUserDefined(t *testing.T) {
   595  	// success
   596  	want := new(hasfields)
   597  	var x *hasfields
   598  	if err := starlark.UnpackArgs("unpack", starlark.Tuple{want}, nil, "x", &x); err != nil {
   599  		t.Errorf("UnpackArgs failed: %v", err)
   600  	}
   601  	if x != want {
   602  		t.Errorf("for x, got %v, want %v", x, want)
   603  	}
   604  
   605  	// failure
   606  	err := starlark.UnpackArgs("unpack", starlark.Tuple{starlark.MakeInt(42)}, nil, "x", &x)
   607  	if want := "unpack: for parameter x: got int, want hasfields"; fmt.Sprint(err) != want {
   608  		t.Errorf("unpack args error = %q, want %q", err, want)
   609  	}
   610  }
   611  
   612  func TestDocstring(t *testing.T) {
   613  	globals, _ := starlark.ExecFile(&starlark.Thread{}, "doc.star", `
   614  def somefunc():
   615  	"somefunc doc"
   616  	return 0
   617  `, nil)
   618  
   619  	if globals["somefunc"].(*starlark.Function).Doc() != "somefunc doc" {
   620  		t.Fatal("docstring not found")
   621  	}
   622  }
   623  
   624  func TestFrameLocals(t *testing.T) {
   625  	// trace prints a nice stack trace including argument
   626  	// values of calls to Starlark functions.
   627  	trace := func(thread *starlark.Thread) string {
   628  		buf := new(bytes.Buffer)
   629  		for i := 0; i < thread.CallStackDepth(); i++ {
   630  			fr := thread.DebugFrame(i)
   631  			fmt.Fprintf(buf, "%s(", fr.Callable().Name())
   632  			if fn, ok := fr.Callable().(*starlark.Function); ok {
   633  				for i := 0; i < fn.NumParams(); i++ {
   634  					if i > 0 {
   635  						buf.WriteString(", ")
   636  					}
   637  					name, _ := fn.Param(i)
   638  					fmt.Fprintf(buf, "%s=%s", name, fr.Local(i))
   639  				}
   640  			} else {
   641  				buf.WriteString("...") // a built-in function
   642  			}
   643  			buf.WriteString(")\n")
   644  		}
   645  		return buf.String()
   646  	}
   647  
   648  	var got string
   649  	builtin := func(thread *starlark.Thread, _ *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
   650  		got = trace(thread)
   651  		return starlark.None, nil
   652  	}
   653  	predeclared := starlark.StringDict{
   654  		"builtin": starlark.NewBuiltin("builtin", builtin),
   655  	}
   656  	_, err := starlark.ExecFile(&starlark.Thread{}, "foo.star", `
   657  def f(x, y): builtin()
   658  def g(z): f(z, z*z)
   659  g(7)
   660  `, predeclared)
   661  	if err != nil {
   662  		t.Errorf("ExecFile failed: %v", err)
   663  	}
   664  
   665  	var want = `
   666  builtin(...)
   667  f(x=7, y=49)
   668  g(z=7)
   669  <toplevel>()
   670  `[1:]
   671  	if got != want {
   672  		t.Errorf("got <<%s>>, want <<%s>>", got, want)
   673  	}
   674  }
   675  
   676  type badType string
   677  
   678  func (b *badType) String() string        { return "badType" }
   679  func (b *badType) Type() string          { return "badType:" + string(*b) } // panics if b==nil
   680  func (b *badType) Truth() starlark.Bool  { return true }
   681  func (b *badType) Hash() (uint32, error) { return 0, nil }
   682  func (b *badType) Freeze()               {}
   683  
   684  var _ starlark.Value = new(badType)
   685  
   686  // TestUnpackErrorBadType verifies that the Unpack functions fail
   687  // gracefully when a parameter's default value's Type method panics.
   688  func TestUnpackErrorBadType(t *testing.T) {
   689  	for _, test := range []struct {
   690  		x    *badType
   691  		want string
   692  	}{
   693  		{new(badType), "got NoneType, want badType"},       // Starlark type name
   694  		{nil, "got NoneType, want *starlark_test.badType"}, // Go type name
   695  	} {
   696  		err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x)
   697  		if err == nil {
   698  			t.Errorf("UnpackArgs succeeded unexpectedly")
   699  			continue
   700  		}
   701  		if !strings.Contains(err.Error(), test.want) {
   702  			t.Errorf("UnpackArgs error %q does not contain %q", err, test.want)
   703  		}
   704  	}
   705  }