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