func Test_BuiltinList(t *testing.T) { blist := BuiltinList{} fn, ok := blist.Lookup("foo") assert.False(t, ok) assert.Nil(t, fn) callable := types.NewFixedFunction("foo", 3, nil) blist.builtins = append(blist.builtins, callable) fn, ok = blist.Lookup("foo") assert.True(t, ok) assert.Equal(t, callable, fn) blist.builtins = append( blist.builtins, types.NewFixedFunction("bar", 0, nil)) blist.builtins = append( blist.builtins, types.NewFixedFunction("bop", 0, nil)) blist.builtins = append( blist.builtins, types.NewFixedFunction("bam", 0, nil)) blist.builtins = append( blist.builtins, types.NewFixedFunction("pow", 0, nil)) assert.Equal(t, 5, blist.NumBuiltins()) actual := make([]string, 0, 5) visit := func(name string, code types.FuObject) error { actual = append(actual, name) if name == "bam" { return errors.New("bam!") } return nil } err := blist.ForEach(visit) assert.Equal(t, []string{"foo", "bar", "bop", "bam"}, actual) assert.Equal(t, "bam!", err.Error()) }
func Test_prepareCall(t *testing.T) { // this is never going to be called, so it's OK that it's nil var fn_dummy func(argsource types.ArgSource) (types.FuObject, []error) var dummy1, dummy2 types.FuCallable dummy1 = types.NewFixedFunction("dummy1", 0, fn_dummy) dummy2 = types.NewFixedFunction("dummy1", 1, fn_dummy) rt := minimalRuntime() ns := rt.Namespace() ns.Assign("dummy1", dummy1) ns.Assign("dummy2", dummy2) ns.Assign("x", types.MakeFuString("whee!")) noargs := []dsl.ASTExpression{} onearg := []dsl.ASTExpression{dsl.NewASTString("\"meep\"")} var astcall *dsl.ASTFunctionCall var callable types.FuCallable var args RuntimeArgs var errs []error // correct (no args) call to dummy1() astcall = dsl.NewASTFunctionCall(dsl.NewASTName("dummy1"), noargs) callable, args, errs = rt.prepareCall(astcall) assert.Equal(t, 0, len(errs)) assert.Equal(t, dummy1, callable) assert.Equal(t, []types.FuObject{}, args.Args()) // and to dummy2() astcall = dsl.NewASTFunctionCall(dsl.NewASTName("dummy2"), onearg) callable, args, errs = rt.prepareCall(astcall) assert.Equal(t, 0, len(errs)) assert.Equal(t, dummy2, callable) assert.Equal(t, []types.FuObject{types.MakeFuString("meep")}, args.Args()) // attempt to call dummy2() incorrectly (1 arg, but it's an undefined name) astcall = dsl.NewASTFunctionCall( dsl.NewASTName("dummy2"), []dsl.ASTExpression{dsl.NewASTName("bogus")}) callable, _, errs = rt.prepareCall(astcall) assert.Equal(t, 1, len(errs)) assert.Equal(t, "name not defined: 'bogus'", errs[0].Error()) // attempt to call non-existent function astcall = dsl.NewASTFunctionCall(dsl.NewASTName("bogus"), noargs) callable, _, errs = rt.prepareCall(astcall) assert.Nil(t, callable) assert.Equal(t, 1, len(errs)) assert.Equal(t, "name not defined: 'bogus'", errs[0].Error()) // attempt to call something that is not a function astcall = dsl.NewASTFunctionCall(dsl.NewASTName("x"), noargs) callable, _, errs = rt.prepareCall(astcall) assert.Nil(t, callable) assert.Equal(t, 1, len(errs)) assert.Equal(t, "not a function or method: 'x'", errs[0].Error()) }
// Return a new namespace containing all builtin functions. func defineBuiltins() BuiltinList { builtins := []types.FuCallable{ types.NewVariadicFunction("println", 0, -1, fn_println), types.NewVariadicFunction("mkdir", 0, -1, fn_mkdir), types.NewVariadicFunction("remove", 0, -1, fn_remove), types.NewFixedFunction("build", 3, fn_build), // node factories types.NewFixedFunction("FileNode", 1, fn_FileNode), types.NewFixedFunction("ActionNode", 1, fn_ActionNode), } return BuiltinList{builtins} }
// evaluate more complex expressions func Test_evaluate_complex(t *testing.T) { // a + b evaluates to various things, depending on the value // of those two variables addnode := dsl.NewASTAdd( dsl.NewASTName("a", dsl.NewStubLocation("loc1")), dsl.NewASTName("b", dsl.NewStubLocation("loc2"))) // case 1: two strings just get concatenated rt := minimalRuntime() ns := rt.Namespace() ns.Assign("a", types.MakeFuString("foo")) ns.Assign("b", types.MakeFuString("bar")) expect := types.MakeFuString("foobar") assertEvaluateOK(t, rt, expect, addnode) // case 2: adding a function to a string fails ns.Assign("b", types.NewFixedFunction("b", 0, nil)) assertEvaluateFail(t, rt, "loc1loc2: unsupported operation: cannot add function to string", addnode) // case 3: undefined name delete((*ns.(*types.ValueStack))[0].(types.ValueMap), "b") assertEvaluateFail(t, rt, "loc2: name not defined: 'b'", addnode) }
func Test_PythonPlugin_builtins(t *testing.T) { cleanup := testutils.Chtemp() defer cleanup() // this isn't really the builtin Fubsy println() function: we can't // use it because it's in the runtime package, and we don't want // to because it has side-effects... but we're stuck with a // hardcoded set of builtin function names for now, so we have to // reuse one of them calls := []string{} fn_println := func(args types.ArgSource) (types.FuObject, []error) { s := args.Args()[0].ValueString() calls = append(calls, s) return nil, nil } builtins := StubBuiltinList{types.NewFixedFunction("println", 1, fn_println)} pp, err := LoadMetaPlugin("python2", builtins) testutils.NoError(t, err) values, err := pp.Run(` fubsy.println("ding") fubsy.println("dong") `) _ = values expect := []string{"ding", "dong"} testutils.NoError(t, err) assert.Equal(t, expect, calls) }
func Test_evaluateCall_no_expand(t *testing.T) { calls := 0 fn_foo := func(argsource types.ArgSource) (types.FuObject, []error) { calls++ return types.MakeFuString("arg: " + argsource.Args()[0].ValueString()), nil } foo := types.NewFixedFunction("foo", 1, fn_foo) rt := minimalRuntime() args := RuntimeArgs{runtime: rt} // call bar() with an arg that needs to be expanded to test that // expansion does *not* happen -- evaluateCall() doesn't know // which phase it's in, so it has to rely on someone else to // ActionExpand() each value in the build phase args.SetArgs([]types.FuObject{types.MakeFuString(">$src<")}) result, errs := rt.evaluateCall(foo, args, nil) assert.Equal(t, 1, calls) assert.Equal(t, types.MakeFuString("arg: >$src<"), result) if len(errs) != 0 { t.Errorf("expected no errors, but got: %v", errs) } // now make a value that expands to three values expansion := types.MakeStringList("a", "b", "c") var val types.FuObject = types.NewStubObject("val", expansion) valexp, _ := val.ActionExpand(nil, nil) assert.Equal(t, expansion, valexp) // this actually tests StubObject // call foo() with that expandable value, and make sure it is // really called with the unexpanded value args.SetArgs([]types.FuObject{val}) result, errs = rt.evaluateCall(foo, args, nil) assert.Equal(t, 2, calls) assert.Equal(t, types.MakeFuString("arg: val"), result) if len(errs) != 0 { t.Errorf("expected no errors, but got: %v", errs) } }
func Test_evaluateCall_method(t *testing.T) { // construct AST for "a.b.c(x)" astargs := []dsl.ASTExpression{dsl.NewASTName("x")} astcall := dsl.NewASTFunctionCall( dsl.NewASTSelection( dsl.NewASTSelection(dsl.NewASTName("a"), "b"), "c"), astargs) // make sure a is an object with attributes, and b is one of them // (N.B. having FileNodes be attributes of one another is weird // and would never happen in a real Fubsy script, but it's a // convenient way to setup this method call) aobj := dag.NewFileNode("a.txt") bobj := dag.NewFileNode("b.txt") aobj.ValueMap = types.NewValueMap() aobj.Assign("b", bobj) // make sure a.b.c is a method calls := make([]string, 0) // list of function names var meth_c types.FuCode meth_c = func(argsource types.ArgSource) (types.FuObject, []error) { args := argsource.Args() if len(args) != 1 { panic("c() called with wrong number of args") } calls = append(calls, "c") robj := argsource.Receiver() return nil, []error{ fmt.Errorf("c failed: receiver: %s %v, arg: %s %v", robj.Typename(), robj, args[0].Typename(), args[0])} } bobj.ValueMap = types.NewValueMap() bobj.Assign("c", types.NewFixedFunction("c", 1, meth_c)) rt := minimalRuntime() ns := rt.Namespace() ns.Assign("a", aobj) ns.Assign("x", types.MakeFuString("hello")) // what the hell, let's test the precall feature too var precalledCallable types.FuCallable var precalledArgs types.ArgSource precall := func(callable types.FuCallable, argsource types.ArgSource) { precalledCallable = callable precalledArgs = argsource } callable, args, errs := rt.prepareCall(astcall) assert.Equal(t, "c", callable.(*types.FuFunction).Name()) assert.True(t, args.Receiver() == bobj) assert.Equal(t, 0, len(errs)) result, errs := rt.evaluateCall(callable, args, precall) assert.Equal(t, precalledCallable, callable) assert.Equal(t, precalledArgs.Args(), types.MakeStringList("hello").List()) assert.Nil(t, result) if len(errs) == 1 { assert.Equal(t, "c failed: receiver: FileNode \"b.txt\", arg: string \"hello\"", errs[0].Error()) } else { t.Errorf("expected exactly 1 error, but got: %v", errs) } }
func Test_evaluateCall(t *testing.T) { // foo() takes no args and always succeeds; // bar() takes exactly one arg and always fails calls := make([]string, 0) // list of function names fn_foo := func(argsource types.ArgSource) (types.FuObject, []error) { if len(argsource.Args()) != 0 { panic("foo() called with wrong number of args") } calls = append(calls, "foo") return types.MakeFuString("foo!"), nil } fn_bar := func(argsource types.ArgSource) (types.FuObject, []error) { if len(argsource.Args()) != 1 { panic("bar() called with wrong number of args") } calls = append(calls, "bar") return nil, []error{ fmt.Errorf("bar failed (%s)", argsource.Args()[0])} } var foo, bar types.FuCallable foo = types.NewFixedFunction("foo", 0, fn_foo) bar = types.NewFixedFunction("bar", 1, fn_bar) rt := minimalRuntime() args := RuntimeArgs{runtime: rt} var result types.FuObject var errs []error // call foo() correctly (no args) args.SetArgs([]types.FuObject{}) result, errs = rt.evaluateCall(foo, args, nil) assert.Equal(t, types.MakeFuString("foo!"), result) assert.Equal(t, 0, len(errs)) assert.Equal(t, []string{"foo"}, calls) // call foo() incorrectly (1 arg) args.SetArgs([]types.FuObject{types.MakeFuString("meep")}) result, errs = rt.evaluateCall(foo, args, nil) assert.Equal(t, 1, len(errs)) assert.Equal(t, "function foo() takes no arguments (got 1)", errs[0].Error()) assert.Equal(t, []string{"foo"}, calls) // call bar() correctly (1 arg) result, errs = rt.evaluateCall(bar, args, nil) assert.Nil(t, result) assert.Equal(t, 1, len(errs)) assert.Equal(t, "bar failed (\"meep\")", errs[0].Error()) assert.Equal(t, []string{"foo", "bar"}, calls) // call bar() incorrectly (no args) args.SetArgs(nil) result, errs = rt.evaluateCall(bar, args, nil) assert.Nil(t, result) assert.Equal(t, 1, len(errs)) assert.Equal(t, "function bar() takes exactly 1 arguments (got 0)", errs[0].Error()) // check the sequence of calls assert.Equal(t, []string{"foo", "bar"}, calls) }