blob: c026abb1a221210800ab6499659e1c49c6ec4a05 [file] [log] [blame]
Alan Donovan312d1a52017-10-02 10:10:28 -04001// 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
5package skylark_test
6
7import (
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"
Alan Donovan7b0e4c32017-10-09 11:03:36 -040019 "github.com/google/skylark/syntax"
Alan Donovan312d1a52017-10-02 10:10:28 -040020)
21
22func 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
Alan Donovan312d1a52017-10-02 10:10:28 -040027 resolve.AllowSet = true
Hittorp0a5e39a2018-08-09 15:02:30 +030028 resolve.AllowBitwise = true
Alan Donovan312d1a52017-10-02 10:10:28 -040029}
30
31func 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
95func 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) {
alandonovana1b28d82018-03-13 10:59:24 -0400116 predeclared := skylark.StringDict{
Alan Donovan312d1a52017-10-02 10:10:28 -0400117 "hasfields": skylark.NewBuiltin("hasfields", newHasFields),
118 "fibonacci": fib{},
119 }
alandonovana1b28d82018-03-13 10:59:24 -0400120 _, err := skylark.ExecFile(thread, filename, chunk.Source, predeclared)
Alan Donovan312d1a52017-10-02 10:10:28 -0400121 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.
146type fib struct{}
147
148func (t fib) Freeze() {}
149func (t fib) String() string { return "fib" }
150func (t fib) Type() string { return "fib" }
151func (t fib) Truth() skylark.Bool { return true }
152func (t fib) Hash() (uint32, error) { return 0, fmt.Errorf("fib is unhashable") }
153func (t fib) Iterate() skylark.Iterator { return &fibIterator{0, 1} }
154
155type fibIterator struct{ x, y int }
156
157func (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}
162func (it *fibIterator) Done() {}
163
164// load implements the 'load' operation as used in the evaluator tests.
165func 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.
Alan Donovan312d1a52017-10-02 10:10:28 -0400171 filename := filepath.Join(filepath.Dir(thread.Caller().Position().Filename()), module)
alandonovana1b28d82018-03-13 10:59:24 -0400172 return skylark.ExecFile(thread, filename, nil, nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400173}
174
175func 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.
183type hasfields struct {
184 attrs skylark.StringDict
185 frozen bool
186}
187
alandonovan15b1cb92018-02-26 14:38:20 -0500188var (
189 _ skylark.HasAttrs = (*hasfields)(nil)
190 _ skylark.HasBinary = (*hasfields)(nil)
191)
Alan Donovan312d1a52017-10-02 10:10:28 -0400192
193func (hf *hasfields) String() string { return "hasfields" }
194func (hf *hasfields) Type() string { return "hasfields" }
195func (hf *hasfields) Truth() skylark.Bool { return true }
196func (hf *hasfields) Hash() (uint32, error) { return 42, nil }
197
198func (hf *hasfields) Freeze() {
199 if !hf.frozen {
200 hf.frozen = true
201 for _, v := range hf.attrs {
202 v.Freeze()
203 }
204 }
205}
206
207func (hf *hasfields) Attr(name string) (skylark.Value, error) { return hf.attrs[name], nil }
208
209func (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
217func (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
alandonovan15b1cb92018-02-26 14:38:20 -0500225func (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
Alan Donovan312d1a52017-10-02 10:10:28 -0400236func TestParameterPassing(t *testing.T) {
237 const filename = "parameters.go"
238 const src = `
239def a():
240 return
241def b(a, b):
242 return a, b
243def c(a, b=42):
244 return a, b
245def d(*args):
246 return args
247def e(**kwargs):
248 return kwargs
249def f(a, b=42, *args, **kwargs):
250 return a, b, args, kwargs
251`
252
253 thread := new(skylark.Thread)
alandonovana1b28d82018-03-13 10:59:24 -0400254 globals, err := skylark.ExecFile(thread, filename, src, nil)
255 if err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400256 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})`},
alandonovan81601212018-10-22 13:41:53 -0400301 {`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})`},
Alan Donovan312d1a52017-10-02 10:10:28 -0400303 } {
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.
318func TestPrint(t *testing.T) {
319 const src = `
320print("hello")
321def f(): print("world")
322f()
323`
324 buf := new(bytes.Buffer)
325 print := func(thread *skylark.Thread, msg string) {
326 caller := thread.Caller()
alandonovan93f3e0c2018-03-30 10:42:28 -0400327 fmt.Fprintf(buf, "%s: %s: %s\n",
alandonovancc7dbc22018-07-02 12:30:24 -0400328 caller.Position(), caller.Callable().Name(), msg)
Alan Donovan312d1a52017-10-02 10:10:28 -0400329 }
330 thread := &skylark.Thread{Print: print}
alandonovana1b28d82018-03-13 10:59:24 -0400331 if _, err := skylark.ExecFile(thread, "foo.go", src, nil); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400332 t.Fatal(err)
333 }
alandonovan93f3e0c2018-03-30 10:42:28 -0400334 want := "foo.go:2: <toplevel>: hello\n" +
335 "foo.go:3: f: world\n"
Alan Donovan312d1a52017-10-02 10:10:28 -0400336 if got := buf.String(); got != want {
337 t.Errorf("output was %s, want %s", got, want)
338 }
339}
340
341func 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.
alandonovana1b28d82018-03-13 10:59:24 -0400351 globals, err := skylark.ExecFile(thread, filename, nil, nil)
352 if err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400353 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
372func 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.
381func 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
414func 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 = `
419def f(x): return 1//x
420def g(x): f(x)
421def h(): return min([1, 2, 0], key=g)
422def i(): return h()
423i()
424`
425 thread := new(skylark.Thread)
alandonovancc7dbc22018-07-02 12:30:24 -0400426 _, err := skylark.ExecFile(thread, "crash.sky", src, nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400427 switch err := err.(type) {
428 case *skylark.EvalError:
429 got := err.Backtrace()
alandonovan93f3e0c2018-03-30 10:42:28 -0400430 // Compiled code currently has no column information.
Alan Donovan312d1a52017-10-02 10:10:28 -0400431 const want = `Traceback (most recent call last):
alandonovancc7dbc22018-07-02 12:30:24 -0400432 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
Alan Donovan312d1a52017-10-02 10:10:28 -0400438Error: 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}
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400448
449// TestRepeatedExec parses and resolves a file syntax tree once then
alandonovana1b28d82018-03-13 10:59:24 -0400450// executes it repeatedly with different values of its predeclared variables.
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400451func TestRepeatedExec(t *testing.T) {
alandonovan93f3e0c2018-03-30 10:42:28 -0400452 predeclared := skylark.StringDict{"x": skylark.None}
453 _, prog, err := skylark.SourceProgram("repeat.sky", "y = 2 * x", predeclared.Has)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400454 if err != nil {
alandonovan93f3e0c2018-03-30 10:42:28 -0400455 t.Fatal(err)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400456 }
457
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400458 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 } {
alandonovan93f3e0c2018-03-30 10:42:28 -0400465 predeclared["x"] = test.x // update the values in dictionary
466 thread := new(skylark.Thread)
467 if globals, err := prog.Init(thread, predeclared); err != nil {
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400468 t.Errorf("x=%v: %v", test.x, err) // exec error
alandonovan93f3e0c2018-03-30 10:42:28 -0400469 } else if eq, err := skylark.Equal(globals["y"], test.want); err != nil {
Jay Conrod7761e752017-10-11 22:13:56 -0400470 t.Errorf("x=%v: %v", test.x, err) // comparison error
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400471 } else if !eq {
alandonovan93f3e0c2018-03-30 10:42:28 -0400472 t.Errorf("x=%v: got y=%v, want %v", test.x, globals["y"], test.want)
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400473 }
Alan Donovan7b0e4c32017-10-09 11:03:36 -0400474 }
475}
alandonovan04850cb2017-10-11 14:16:28 -0400476
477// TestUnpackUserDefined tests that user-defined
478// implementations of skylark.Value may be unpacked.
479func 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}