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