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