github.com/google/skylark@v0.0.0-20181101142754-a5f7082aabed/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 skylark_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"math"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/google/skylark"
    16  	"github.com/google/skylark/internal/chunkedfile"
    17  	"github.com/google/skylark/resolve"
    18  	"github.com/google/skylark/skylarktest"
    19  	"github.com/google/skylark/syntax"
    20  )
    21  
    22  func init() {
    23  	// The tests make extensive use of these not-yet-standard features.
    24  	resolve.AllowLambda = true
    25  	resolve.AllowNestedDef = true
    26  	resolve.AllowFloat = true
    27  	resolve.AllowSet = true
    28  	resolve.AllowBitwise = true
    29  }
    30  
    31  func TestEvalExpr(t *testing.T) {
    32  	// This is mostly redundant with the new *.sky tests.
    33  	// TODO(adonovan): move checks into *.sky files and
    34  	// reduce this to a mere unit test of skylark.Eval.
    35  	thread := new(skylark.Thread)
    36  	for _, test := range []struct{ src, want string }{
    37  		{`123`, `123`},
    38  		{`-1`, `-1`},
    39  		{`"a"+"b"`, `"ab"`},
    40  		{`1+2`, `3`},
    41  
    42  		// lists
    43  		{`[]`, `[]`},
    44  		{`[1]`, `[1]`},
    45  		{`[1,]`, `[1]`},
    46  		{`[1, 2]`, `[1, 2]`},
    47  		{`[2 * x for x in [1, 2, 3]]`, `[2, 4, 6]`},
    48  		{`[2 * x for x in [1, 2, 3] if x > 1]`, `[4, 6]`},
    49  		{`[(x, y) for x in [1, 2] for y in [3, 4]]`,
    50  			`[(1, 3), (1, 4), (2, 3), (2, 4)]`},
    51  		{`[(x, y) for x in [1, 2] if x == 2 for y in [3, 4]]`,
    52  			`[(2, 3), (2, 4)]`},
    53  		// tuples
    54  		{`()`, `()`},
    55  		{`(1)`, `1`},
    56  		{`(1,)`, `(1,)`},
    57  		{`(1, 2)`, `(1, 2)`},
    58  		{`(1, 2, 3, 4, 5)`, `(1, 2, 3, 4, 5)`},
    59  		// dicts
    60  		{`{}`, `{}`},
    61  		{`{"a": 1}`, `{"a": 1}`},
    62  		{`{"a": 1,}`, `{"a": 1}`},
    63  
    64  		// conditional
    65  		{`1 if 3 > 2 else 0`, `1`},
    66  		{`1 if "foo" else 0`, `1`},
    67  		{`1 if "" else 0`, `0`},
    68  
    69  		// indexing
    70  		{`["a", "b"][0]`, `"a"`},
    71  		{`["a", "b"][1]`, `"b"`},
    72  		{`("a", "b")[0]`, `"a"`},
    73  		{`("a", "b")[1]`, `"b"`},
    74  		{`"aΩb"[0]`, `"a"`},
    75  		{`"aΩb"[1]`, `"\xce"`},
    76  		{`"aΩb"[3]`, `"b"`},
    77  		{`{"a": 1}["a"]`, `1`},
    78  		{`{"a": 1}["b"]`, `key "b" not in dict`},
    79  		{`{}[[]]`, `unhashable type: list`},
    80  		{`{"a": 1}[[]]`, `unhashable type: list`},
    81  		{`[x for x in range(3)]`, "[0, 1, 2]"},
    82  	} {
    83  		var got string
    84  		if v, err := skylark.Eval(thread, "<expr>", test.src, nil); err != nil {
    85  			got = err.Error()
    86  		} else {
    87  			got = v.String()
    88  		}
    89  		if got != test.want {
    90  			t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
    91  		}
    92  	}
    93  }
    94  
    95  func TestExecFile(t *testing.T) {
    96  	testdata := skylarktest.DataFile("skylark", ".")
    97  	thread := &skylark.Thread{Load: load}
    98  	skylarktest.SetReporter(thread, t)
    99  	for _, file := range []string{
   100  		"testdata/assign.sky",
   101  		"testdata/bool.sky",
   102  		"testdata/builtins.sky",
   103  		"testdata/control.sky",
   104  		"testdata/dict.sky",
   105  		"testdata/float.sky",
   106  		"testdata/function.sky",
   107  		"testdata/int.sky",
   108  		"testdata/list.sky",
   109  		"testdata/misc.sky",
   110  		"testdata/set.sky",
   111  		"testdata/string.sky",
   112  		"testdata/tuple.sky",
   113  	} {
   114  		filename := filepath.Join(testdata, file)
   115  		for _, chunk := range chunkedfile.Read(filename, t) {
   116  			predeclared := skylark.StringDict{
   117  				"hasfields": skylark.NewBuiltin("hasfields", newHasFields),
   118  				"fibonacci": fib{},
   119  			}
   120  			_, err := skylark.ExecFile(thread, filename, chunk.Source, predeclared)
   121  			switch err := err.(type) {
   122  			case *skylark.EvalError:
   123  				found := false
   124  				for _, fr := range err.Stack() {
   125  					posn := fr.Position()
   126  					if posn.Filename() == filename {
   127  						chunk.GotError(int(posn.Line), err.Error())
   128  						found = true
   129  						break
   130  					}
   131  				}
   132  				if !found {
   133  					t.Error(err.Backtrace())
   134  				}
   135  			case nil:
   136  				// success
   137  			default:
   138  				t.Error(err)
   139  			}
   140  			chunk.Done()
   141  		}
   142  	}
   143  }
   144  
   145  // A fib is an iterable value representing the infinite Fibonacci sequence.
   146  type fib struct{}
   147  
   148  func (t fib) Freeze()                   {}
   149  func (t fib) String() string            { return "fib" }
   150  func (t fib) Type() string              { return "fib" }
   151  func (t fib) Truth() skylark.Bool       { return true }
   152  func (t fib) Hash() (uint32, error)     { return 0, fmt.Errorf("fib is unhashable") }
   153  func (t fib) Iterate() skylark.Iterator { return &fibIterator{0, 1} }
   154  
   155  type fibIterator struct{ x, y int }
   156  
   157  func (it *fibIterator) Next(p *skylark.Value) bool {
   158  	*p = skylark.MakeInt(it.x)
   159  	it.x, it.y = it.y, it.x+it.y
   160  	return true
   161  }
   162  func (it *fibIterator) Done() {}
   163  
   164  // load implements the 'load' operation as used in the evaluator tests.
   165  func load(thread *skylark.Thread, module string) (skylark.StringDict, error) {
   166  	if module == "assert.sky" {
   167  		return skylarktest.LoadAssertModule()
   168  	}
   169  
   170  	// TODO(adonovan): test load() using this execution path.
   171  	filename := filepath.Join(filepath.Dir(thread.Caller().Position().Filename()), module)
   172  	return skylark.ExecFile(thread, filename, nil, nil)
   173  }
   174  
   175  func newHasFields(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) {
   176  	return &hasfields{attrs: make(map[string]skylark.Value)}, nil
   177  }
   178  
   179  // hasfields is a test-only implementation of HasAttrs.
   180  // It permits any field to be set.
   181  // Clients will likely want to provide their own implementation,
   182  // so we don't have any public implementation.
   183  type hasfields struct {
   184  	attrs  skylark.StringDict
   185  	frozen bool
   186  }
   187  
   188  var (
   189  	_ skylark.HasAttrs  = (*hasfields)(nil)
   190  	_ skylark.HasBinary = (*hasfields)(nil)
   191  )
   192  
   193  func (hf *hasfields) String() string        { return "hasfields" }
   194  func (hf *hasfields) Type() string          { return "hasfields" }
   195  func (hf *hasfields) Truth() skylark.Bool   { return true }
   196  func (hf *hasfields) Hash() (uint32, error) { return 42, nil }
   197  
   198  func (hf *hasfields) Freeze() {
   199  	if !hf.frozen {
   200  		hf.frozen = true
   201  		for _, v := range hf.attrs {
   202  			v.Freeze()
   203  		}
   204  	}
   205  }
   206  
   207  func (hf *hasfields) Attr(name string) (skylark.Value, error) { return hf.attrs[name], nil }
   208  
   209  func (hf *hasfields) SetField(name string, val skylark.Value) error {
   210  	if hf.frozen {
   211  		return fmt.Errorf("cannot set field on a frozen hasfields")
   212  	}
   213  	hf.attrs[name] = val
   214  	return nil
   215  }
   216  
   217  func (hf *hasfields) AttrNames() []string {
   218  	names := make([]string, 0, len(hf.attrs))
   219  	for key := range hf.attrs {
   220  		names = append(names, key)
   221  	}
   222  	return names
   223  }
   224  
   225  func (hf *hasfields) Binary(op syntax.Token, y skylark.Value, side skylark.Side) (skylark.Value, error) {
   226  	// This method exists so we can exercise 'list += x'
   227  	// where x is not Iterable but defines list+x.
   228  	if op == syntax.PLUS {
   229  		if _, ok := y.(*skylark.List); ok {
   230  			return skylark.MakeInt(42), nil // list+hasfields is 42
   231  		}
   232  	}
   233  	return nil, nil
   234  }
   235  
   236  func TestParameterPassing(t *testing.T) {
   237  	const filename = "parameters.go"
   238  	const src = `
   239  def a():
   240  	return
   241  def b(a, b):
   242  	return a, b
   243  def c(a, b=42):
   244  	return a, b
   245  def d(*args):
   246  	return args
   247  def e(**kwargs):
   248  	return kwargs
   249  def f(a, b=42, *args, **kwargs):
   250  	return a, b, args, kwargs
   251  `
   252  
   253  	thread := new(skylark.Thread)
   254  	globals, err := skylark.ExecFile(thread, filename, src, nil)
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  
   259  	for _, test := range []struct{ src, want string }{
   260  		{`a()`, `None`},
   261  		{`a(1)`, `function a takes no arguments (1 given)`},
   262  		{`b()`, `function b takes exactly 2 arguments (0 given)`},
   263  		{`b(1)`, `function b takes exactly 2 arguments (1 given)`},
   264  		{`b(1, 2)`, `(1, 2)`},
   265  		{`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable
   266  		{`b(1, 2, 3)`, `function b takes exactly 2 arguments (3 given)`},
   267  		{`b(1, b=2)`, `(1, 2)`},
   268  		{`b(1, a=2)`, `function b got multiple values for keyword argument "a"`},
   269  		{`b(1, x=2)`, `function b got an unexpected keyword argument "x"`},
   270  		{`b(a=1, b=2)`, `(1, 2)`},
   271  		{`b(b=1, a=2)`, `(2, 1)`},
   272  		{`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`},
   273  		{`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`},
   274  		{`c()`, `function c takes at least 1 argument (0 given)`},
   275  		{`c(1)`, `(1, 42)`},
   276  		{`c(1, 2)`, `(1, 2)`},
   277  		{`c(1, 2, 3)`, `function c takes at most 2 arguments (3 given)`},
   278  		{`c(1, b=2)`, `(1, 2)`},
   279  		{`c(1, a=2)`, `function c got multiple values for keyword argument "a"`},
   280  		{`c(a=1, b=2)`, `(1, 2)`},
   281  		{`c(b=1, a=2)`, `(2, 1)`},
   282  		{`d()`, `()`},
   283  		{`d(1)`, `(1,)`},
   284  		{`d(1, 2)`, `(1, 2)`},
   285  		{`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`},
   286  		{`d(args=[])`, `function d got an unexpected keyword argument "args"`},
   287  		{`e()`, `{}`},
   288  		{`e(1)`, `function e takes exactly 0 arguments (1 given)`},
   289  		{`e(k=1)`, `{"k": 1}`},
   290  		{`e(kwargs={})`, `{"kwargs": {}}`},
   291  		{`f()`, `function f takes at least 1 argument (0 given)`},
   292  		{`f(0)`, `(0, 42, (), {})`},
   293  		{`f(0)`, `(0, 42, (), {})`},
   294  		{`f(0, 1)`, `(0, 1, (), {})`},
   295  		{`f(0, 1, 2)`, `(0, 1, (2,), {})`},
   296  		{`f(0, 1, 2, 3)`, `(0, 1, (2, 3), {})`},
   297  		{`f(a=0)`, `(0, 42, (), {})`},
   298  		{`f(0, b=1)`, `(0, 1, (), {})`},
   299  		{`f(0, a=1)`, `function f got multiple values for keyword argument "a"`},
   300  		{`f(0, b=1, c=2)`, `(0, 1, (), {"c": 2})`},
   301  		{`f(0, 1, x=2, *[3, 4], y=5, **dict(z=6))`, // github.com/google/skylark/issues/135
   302  			`(0, 1, (3, 4), {"x": 2, "y": 5, "z": 6})`},
   303  	} {
   304  		var got string
   305  		if v, err := skylark.Eval(thread, "<expr>", test.src, globals); err != nil {
   306  			got = err.Error()
   307  		} else {
   308  			got = v.String()
   309  		}
   310  		if got != test.want {
   311  			t.Errorf("eval %s = %s, want %s", test.src, got, test.want)
   312  		}
   313  	}
   314  }
   315  
   316  // TestPrint ensures that the Skylark print function calls
   317  // Thread.Print, if provided.
   318  func TestPrint(t *testing.T) {
   319  	const src = `
   320  print("hello")
   321  def f(): print("world")
   322  f()
   323  `
   324  	buf := new(bytes.Buffer)
   325  	print := func(thread *skylark.Thread, msg string) {
   326  		caller := thread.Caller()
   327  		fmt.Fprintf(buf, "%s: %s: %s\n",
   328  			caller.Position(), caller.Callable().Name(), msg)
   329  	}
   330  	thread := &skylark.Thread{Print: print}
   331  	if _, err := skylark.ExecFile(thread, "foo.go", src, nil); err != nil {
   332  		t.Fatal(err)
   333  	}
   334  	want := "foo.go:2: <toplevel>: hello\n" +
   335  		"foo.go:3: f: world\n"
   336  	if got := buf.String(); got != want {
   337  		t.Errorf("output was %s, want %s", got, want)
   338  	}
   339  }
   340  
   341  func Benchmark(b *testing.B) {
   342  	testdata := skylarktest.DataFile("skylark", ".")
   343  	thread := new(skylark.Thread)
   344  	for _, file := range []string{
   345  		"testdata/benchmark.sky",
   346  		// ...
   347  	} {
   348  		filename := filepath.Join(testdata, file)
   349  
   350  		// Evaluate the file once.
   351  		globals, err := skylark.ExecFile(thread, filename, nil, nil)
   352  		if err != nil {
   353  			reportEvalError(b, err)
   354  		}
   355  
   356  		// Repeatedly call each global function named bench_* as a benchmark.
   357  		for name, value := range globals {
   358  			if fn, ok := value.(*skylark.Function); ok && strings.HasPrefix(name, "bench_") {
   359  				b.Run(name, func(b *testing.B) {
   360  					for i := 0; i < b.N; i++ {
   361  						_, err := skylark.Call(thread, fn, nil, nil)
   362  						if err != nil {
   363  							reportEvalError(b, err)
   364  						}
   365  					}
   366  				})
   367  			}
   368  		}
   369  	}
   370  }
   371  
   372  func reportEvalError(tb testing.TB, err error) {
   373  	if err, ok := err.(*skylark.EvalError); ok {
   374  		tb.Fatal(err.Backtrace())
   375  	}
   376  	tb.Fatal(err)
   377  }
   378  
   379  // TestInt exercises the Int.Int64 and Int.Uint64 methods.
   380  // If we can move their logic into math/big, delete this test.
   381  func TestInt(t *testing.T) {
   382  	one := skylark.MakeInt(1)
   383  
   384  	for _, test := range []struct {
   385  		i          skylark.Int
   386  		wantInt64  string
   387  		wantUint64 string
   388  	}{
   389  		{skylark.MakeInt64(math.MinInt64).Sub(one), "error", "error"},
   390  		{skylark.MakeInt64(math.MinInt64), "-9223372036854775808", "error"},
   391  		{skylark.MakeInt64(-1), "-1", "error"},
   392  		{skylark.MakeInt64(0), "0", "0"},
   393  		{skylark.MakeInt64(1), "1", "1"},
   394  		{skylark.MakeInt64(math.MaxInt64), "9223372036854775807", "9223372036854775807"},
   395  		{skylark.MakeUint64(math.MaxUint64), "error", "18446744073709551615"},
   396  		{skylark.MakeUint64(math.MaxUint64).Add(one), "error", "error"},
   397  	} {
   398  		gotInt64, gotUint64 := "error", "error"
   399  		if i, ok := test.i.Int64(); ok {
   400  			gotInt64 = fmt.Sprint(i)
   401  		}
   402  		if u, ok := test.i.Uint64(); ok {
   403  			gotUint64 = fmt.Sprint(u)
   404  		}
   405  		if gotInt64 != test.wantInt64 {
   406  			t.Errorf("(%s).Int64() = %s, want %s", test.i, gotInt64, test.wantInt64)
   407  		}
   408  		if gotUint64 != test.wantUint64 {
   409  			t.Errorf("(%s).Uint64() = %s, want %s", test.i, gotUint64, test.wantUint64)
   410  		}
   411  	}
   412  }
   413  
   414  func TestBacktrace(t *testing.T) {
   415  	// This test ensures continuity of the stack of active Skylark
   416  	// functions, including propagation through built-ins such as 'min'
   417  	// (though min does not itself appear in the stack).
   418  	const src = `
   419  def f(x): return 1//x
   420  def g(x): f(x)
   421  def h(): return min([1, 2, 0], key=g)
   422  def i(): return h()
   423  i()
   424  `
   425  	thread := new(skylark.Thread)
   426  	_, err := skylark.ExecFile(thread, "crash.sky", src, nil)
   427  	switch err := err.(type) {
   428  	case *skylark.EvalError:
   429  		got := err.Backtrace()
   430  		// Compiled code currently has no column information.
   431  		const want = `Traceback (most recent call last):
   432    crash.sky:6: in <toplevel>
   433    crash.sky:5: in i
   434    crash.sky:4: in h
   435    <builtin>:1: in min
   436    crash.sky:3: in g
   437    crash.sky:2: in f
   438  Error: floored division by zero`
   439  		if got != want {
   440  			t.Errorf("error was %s, want %s", got, want)
   441  		}
   442  	case nil:
   443  		t.Error("ExecFile succeeded unexpectedly")
   444  	default:
   445  		t.Errorf("ExecFile failed with %v, wanted *EvalError", err)
   446  	}
   447  }
   448  
   449  // TestRepeatedExec parses and resolves a file syntax tree once then
   450  // executes it repeatedly with different values of its predeclared variables.
   451  func TestRepeatedExec(t *testing.T) {
   452  	predeclared := skylark.StringDict{"x": skylark.None}
   453  	_, prog, err := skylark.SourceProgram("repeat.sky", "y = 2 * x", predeclared.Has)
   454  	if err != nil {
   455  		t.Fatal(err)
   456  	}
   457  
   458  	for _, test := range []struct {
   459  		x, want skylark.Value
   460  	}{
   461  		{x: skylark.MakeInt(42), want: skylark.MakeInt(84)},
   462  		{x: skylark.String("mur"), want: skylark.String("murmur")},
   463  		{x: skylark.Tuple{skylark.None}, want: skylark.Tuple{skylark.None, skylark.None}},
   464  	} {
   465  		predeclared["x"] = test.x // update the values in dictionary
   466  		thread := new(skylark.Thread)
   467  		if globals, err := prog.Init(thread, predeclared); err != nil {
   468  			t.Errorf("x=%v: %v", test.x, err) // exec error
   469  		} else if eq, err := skylark.Equal(globals["y"], test.want); err != nil {
   470  			t.Errorf("x=%v: %v", test.x, err) // comparison error
   471  		} else if !eq {
   472  			t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
   473  		}
   474  	}
   475  }
   476  
   477  // TestUnpackUserDefined tests that user-defined
   478  // implementations of skylark.Value may be unpacked.
   479  func TestUnpackUserDefined(t *testing.T) {
   480  	// success
   481  	want := new(hasfields)
   482  	var x *hasfields
   483  	if err := skylark.UnpackArgs("unpack", skylark.Tuple{want}, nil, "x", &x); err != nil {
   484  		t.Errorf("UnpackArgs failed: %v", err)
   485  	}
   486  	if x != want {
   487  		t.Errorf("for x, got %v, want %v", x, want)
   488  	}
   489  
   490  	// failure
   491  	err := skylark.UnpackArgs("unpack", skylark.Tuple{skylark.MakeInt(42)}, nil, "x", &x)
   492  	if want := "unpack: for parameter 1: got int, want hasfields"; fmt.Sprint(err) != want {
   493  		t.Errorf("unpack args error = %q, want %q", err, want)
   494  	}
   495  }