func Test_FileNode_Add(t *testing.T) { node0 := NewFileNode("foo/bar") node1 := NewFileNode("foo/baz") obj0 := types.MakeFuString(".c") obj1 := types.MakeStringList("a", "b") var err error var expect types.FuObject var actual types.FuObject // node + node = list of nodes expect = types.MakeFuList(node0, node1) actual, err = node0.Add(node1) assert.Nil(t, err) assert.True(t, expect.Equal(actual)) // node + string = new node expect = NewFileNode("foo/bar.c") actual, err = node0.Add(obj0) assert.Nil(t, err) assert.True(t, expect.Equal(actual)) // node + list = flattened list expect = types.MakeFuList( node0, types.MakeFuString("a"), types.MakeFuString("b")) actual, err = node0.Add(obj1) assert.Nil(t, err) assert.True(t, expect.Equal(actual)) }
func Test_nodify(t *testing.T) { sval1 := types.MakeFuString("hello.txt") sval2 := types.MakeFuString("foo.c") lval1 := types.MakeFuList(sval1, sval2) finder1 := dag.NewFinderNode("*.c", "*.h") finder2 := dag.NewFinderNode("**/*.java") rt := NewRuntime(build.BuildOptions{}, "", nil) nodes := rt.nodify(sval1) assert.Equal(t, 1, len(nodes)) assert.Equal(t, "hello.txt", nodes[0].(*dag.FileNode).Name()) nodes = rt.nodify(lval1) assert.Equal(t, 2, len(nodes)) assert.Equal(t, "hello.txt", nodes[0].(*dag.FileNode).Name()) assert.Equal(t, "foo.c", nodes[1].(*dag.FileNode).Name()) nodes = rt.nodify(finder1) assert.Equal(t, 1, len(nodes)) assert.Equal(t, "<*.c *.h>", nodes[0].(*dag.FinderNode).String()) findersum, err := finder1.Add(finder2) assert.Nil(t, err) nodes = rt.nodify(findersum) if len(nodes) == 2 { assert.Equal(t, "<*.c *.h>", nodes[0].(*dag.FinderNode).String()) assert.Equal(t, "<**/*.java>", nodes[1].(*dag.FinderNode).String()) } else { t.Errorf("expected nodify(%s) to return 2 nodes, but got %d: %v", findersum, len(nodes), nodes) } }
// 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_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()) }
func Test_FinderNode_expand_cycle(t *testing.T) { ns := types.NewValueMap() ns.Assign("a", types.MakeFuString("$b")) ns.Assign("b", types.MakeFuString("$c$d")) ns.Assign("c", types.MakeFuString("$a")) var err error finder := NewFinderNode("src/$a/*.h") _, err = finder.ActionExpand(ns, nil) assert.Equal(t, "cyclic variable reference: a -> b -> c -> a", err.Error()) err = finder.NodeExpand(ns) assert.Equal(t, "cyclic variable reference: a -> b -> c -> a", err.Error()) }
func Test_ListNode_expand_cycle(t *testing.T) { ns := types.NewValueMap() ns.Assign("a", types.MakeFuString("$b")) ns.Assign("b", types.MakeFuString("$a")) var err error inner := NewStubNode("foo/$a") list := newListNode(inner) // XXX ooops, ActionExpand() does not expand variable refs! // _, err = list.ActionExpand(ns, nil) // assert.Equal(t, "cyclic variable reference: a -> b -> a", err.Error()) err = list.NodeExpand(ns) assert.Equal(t, "cyclic variable reference: a -> b -> a", err.Error()) }
func (self *Runtime) evaluate( expr_ dsl.ASTExpression) ( result types.FuObject, errs []error) { switch expr := expr_.(type) { case *dsl.ASTString: result = types.MakeFuString(expr.Value()) case *dsl.ASTList: result, errs = self.evaluateList(expr) case *dsl.ASTName: result, errs = self.evaluateName(expr) case *dsl.ASTFileFinder: result = dag.NewFinderNode(expr.Patterns()...) case *dsl.ASTAdd: result, errs = self.evaluateAdd(expr) case *dsl.ASTFunctionCall: var callable types.FuCallable var args RuntimeArgs callable, args, errs = self.prepareCall(expr) if len(errs) == 0 { result, errs = self.evaluateCall(callable, args, nil) } case *dsl.ASTSelection: _, result, errs = self.evaluateLookup(expr) default: return nil, []error{unsupportedAST(expr_)} } for i, err := range errs { errs[i] = MakeLocationError(expr_, err) } return }
func Test_Runtime_runMainPhase_valid(t *testing.T) { script := "" + "main {\n" + " src = \"foo.c\"\n" + " \"foo\": src {\n" + " \"cc -o $TARGET $src\"\n" + " }\n" + "}\n" rt := parseScript(t, "test.fubsy", script) errors := rt.runMainPhase() assert.Equal(t, 0, len(errors)) val, ok := rt.stack.Lookup("src") assert.True(t, ok) assert.Equal(t, types.MakeFuString("foo.c"), val) assert.NotNil(t, rt.dag) // this seems *awfully* detailed and brittle, but DAG doesn't // provide a good way to query what's in it (yet...) expect := `0000: FileNode "foo" (state UNKNOWN) action: "cc -o $TARGET $src" parents: 0001: foo.c 0001: FileNode "foo.c" (state UNKNOWN) ` var buf bytes.Buffer rt.dag.Dump(&buf, "") actual := buf.String() if expect != actual { t.Errorf("dag.Dump(): expected\n%v\nbut got\n%v", expect, actual) } }
func Test_FinderNode_Expand_vars(t *testing.T) { // imagine code like this: // srcdir = "src/stuff" // files = <$srcdir/*.c> // "myapp": "$srcdir/main.c" { // "cc -c $files" // } // ...i.e. a FinderNode that is not in the DAG, so variable // references do not get expanded by DAG.ExpandNodes(). This is // clearly a bogus build script, but that's beside the point. We // need to ensure that the wildcard expanded is not "$srcdir/*.c" // but "src/stuff/*.c". cleanup := testutils.Chtemp() defer cleanup() testutils.TouchFiles( "lib1/foo.c", "lib1/sub/blah.c", "include/bop.h", "include/bip.h") ns := types.NewValueMap() ns.Assign("libsrc", types.MakeFuString("lib1")) finder := NewFinderNode("$libsrc/**/*.c") expect := []string{ "lib1/foo.c", "lib1/sub/blah.c", } assertExpand(t, ns, expect, finder) }
// evaluate simple expressions (no operators) func Test_evaluate_simple(t *testing.T) { // the expression "meep" evaluates to the string "meep" var expect types.FuObject snode := stringnode("meep") rt := minimalRuntime() ns := rt.Namespace() expect = types.MakeFuString("meep") assertEvaluateOK(t, rt, expect, snode) // the expression foo evaluates to the string "meep" if foo is set // to that string ns.Assign("foo", expect) nnode := dsl.NewASTName("foo") assertEvaluateOK(t, rt, expect, nnode) // ... and to an error if the variable is not defined location := dsl.NewStubLocation("hello, sailor") nnode = dsl.NewASTName("boo", location) assertEvaluateFail(t, rt, "hello, sailor: name not defined: 'boo'", nnode) // expression <*.c blah> evaluates to a FinderNode with two // include patterns patterns := []string{"*.c", "blah"} fnode := dsl.NewASTFileFinder(patterns) expect = dag.NewFinderNode("*.c", "blah") assertEvaluateOK(t, rt, expect, fnode) }
func Test_PythonCallable_callPython(t *testing.T) { plugin, err := NewPythonPlugin() testutils.NoError(t, err) // Setup: define a Python function and make sure that Run() finds // it, so it can be added to a Fubsy namespace and used from Fubsy // code. pycode := ` def reverse_strings(*args): '''takes N strings, reverses each one, then returns the reversed strings concatenated into a single string with + between each one''' return '+'.join(''.join(reversed(arg)) for arg in args)` values, err := plugin.Run(pycode) testutils.NoError(t, err) value, ok := values.Lookup("reverse_strings") assert.True(t, ok) pycallable := value.(PythonCallable) assert.Equal(t, "reverse_strings", pycallable.Name()) // Call the Python function with 3 strings. args := types.MakeStringList("foo", "blob", "pingpong").List() argsource := types.MakeBasicArgs(nil, args, nil) value, errs := pycallable.callPython(argsource) testutils.NoErrors(t, errs) // And test the returned value. expect := types.MakeFuString("oof+bolb+gnopgnip") assert.True(t, expect.Equal(value), "expected %s, but got %s", expect, value) }
func Test_mkdir(t *testing.T) { cleanup := testutils.Chtemp() defer cleanup() // mkdir() happily accepts an empty argument list, to allow for // cases where a user-defined list becomes the arg list, and it // just happens to be empty pargs := []types.FuObject{} args := RuntimeArgs{ BasicArgs: types.MakeBasicArgs(nil, pargs, nil), } result, errs := fn_mkdir(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) assert.Equal(t, []string{}, dirContents(".")) // easiest case: create a single dir pargs = types.MakeStringList("foo").List() args.SetArgs(pargs) result, errs = fn_mkdir(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) assert.Equal(t, []string{"foo"}, dirContents(".")) assert.True(t, isDir("foo")) // create multiple dirs, including "foo" which already exists pargs = types.MakeStringList("meep/meep/meep", "foo", "meep/beep").List() args.SetArgs(pargs) result, errs = fn_mkdir(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) assert.Equal(t, []string{"foo", "meep"}, dirContents(".")) assert.True(t, isDir("foo")) assert.True(t, isDir("meep/meep")) assert.True(t, isDir("meep/meep/meep")) assert.True(t, isDir("meep/beep")) // now with an error in the middle of the list (*but* we still // create the other requested dirs!) testutils.TouchFiles("meep/zap") pargs = types.MakeStringList("meep/bap", "meep/zap/zip", "foo/bar").List() args.SetArgs(pargs) result, errs = fn_mkdir(args) assert.Nil(t, result) assert.Equal(t, 1, len(errs)) assert.Equal(t, "mkdir meep/zap: not a directory", errs[0].Error()) assert.True(t, isDir("meep/bap")) assert.True(t, isDir("foo/bar")) // finally, with multiple errors pargs = append(pargs, types.MakeFuString("meep/zap/blop")) args.SetArgs(pargs) result, errs = fn_mkdir(args) assert.Nil(t, result) assert.Equal(t, 2, len(errs)) assert.Equal(t, "mkdir meep/zap: not a directory", errs[0].Error()) assert.Equal(t, "mkdir meep/zap: not a directory", errs[1].Error()) }
func Test_FinderNode_Add(t *testing.T) { finder1 := NewFinderNode("*.c") finder2 := NewFinderNode("*.h") str1 := types.MakeFuString("foo.txt") str2 := types.MakeFuString("bar.txt") file1 := NewFileNode("foo.txt") file2 := NewFileNode("bar.txt") list := types.MakeFuList(str1, str2) stub := types.NewStubObject("bla", nil) sum, err := finder1.Add(finder2) testutils.NoError(t, err) assert.Equal(t, []types.FuObject{finder1, finder2}, sum.List()) sum, err = finder1.Add(file1) testutils.NoError(t, err) assert.Equal(t, []types.FuObject{finder1, file1}, sum.List()) sum, err = finder1.Add(str1) testutils.NoError(t, err) assert.Equal(t, []types.FuObject{finder1, file1}, sum.List()) sum, err = finder1.Add(list) testutils.NoError(t, err) assert.Equal(t, []types.FuObject{finder1, file1, file2}, sum.List()) list = types.MakeFuList(file2, file1) sum, err = finder1.Add(list) testutils.NoError(t, err) assert.Equal(t, []types.FuObject{finder1, file2, file1}, sum.List()) sum, err = finder1.Add(stub) assert.Equal(t, "unsupported operation: cannot add stub to FinderNode", err.Error()) assert.Nil(t, sum) list = types.MakeFuList(str1, stub) sum, err = finder1.Add(list) assert.Equal(t, "unsupported operation: cannot add list to FinderNode "+ "(second operand contains stub)", err.Error()) assert.Nil(t, sum) }
func Test_FinderNode_Add_Expand(t *testing.T) { cleanup := testutils.Chtemp() defer cleanup() testutils.TouchFiles( "src/foo.c", "src/foo.h", "main.c", "include/bop.h", "doc.txt", "doc/stuff.txt", "doc/blahblah.rst") finder1 := NewFinderNode("**/*.c") finder2 := NewFinderNode("doc/*.txt") // sum = <**/*.c> + <doc/*.txt> expect := []string{ "main.c", "src/foo.c", "doc/stuff.txt"} sum, err := finder1.Add(finder2) assert.Nil(t, err) assertExpand(t, nil, expect, sum) // sum = sum + <"*c*/?o?.h"> finder3 := NewFinderNode("*c*/?o?.h") expect = append(expect, "include/bop.h", "src/foo.h") sum, err = sum.Add(finder3) assert.Nil(t, err) assertExpand(t, nil, expect, sum) // sum = <*c*/?o?.h> + <**/*.c> expect = []string{ "include/bop.h", "src/foo.h", "main.c", "src/foo.c"} sum, err = finder3.Add(finder1) assert.Nil(t, err) assertExpand(t, nil, expect, sum) // sum = <doc/*.txt> + sum // (effectively: sum = <doc/*.txt> + (<*c*/?o?.h> + <**/*.c>)) expect = append([]string{"doc/stuff.txt"}, expect...) sum, err = finder2.Add(sum) assert.Nil(t, err) assertExpand(t, nil, expect, sum) // sum = <**/*.c> + "urgh" expect = []string{ "main.c", "src/foo.c", "urgh"} sum, err = finder1.Add(types.MakeFuString("urgh")) assert.Nil(t, err) assertExpand(t, nil, expect, sum) // sum = <**/*.c> + ["a", "b", "c"] expect = []string{ "main.c", "src/foo.c", "a", "b", "c"} list := types.MakeStringList("a", "b", "c") sum, err = finder1.Add(list) assert.Nil(t, err) assertExpand(t, nil, expect, sum) }
func Test_DAG_ExpandNodes(t *testing.T) { ns := types.NewValueMap() ns.Assign("sdir", types.MakeFuString("src/tool1")) ns.Assign("ext", types.MakeFuString("cpp")) tdag := NewTestDAG() tdag.Add("bin/tool1", "$sdir/main.$ext", "$sdir/util.$ext", "$sdir/foo.h") tdag.Add("$sdir/foo.h", "$sdir/foo.h.in") tdag.Add("$sdir/main.$ext") tdag.Add("$sdir/util.$ext", "$sdir/foo.h") tdag.Add("$sdir/foo.h.in") dag := tdag.Finish() expect := []string{ "bin/tool1", "src/tool1/foo.h", "src/tool1/main.cpp", "src/tool1/util.cpp", "src/tool1/foo.h.in", } errs := dag.ExpandNodes(ns) assert.Equal(t, 0, len(errs)) for i, node := range dag.nodes { assert.Equal(t, expect[i], node.Name()) } assert.Equal(t, len(expect), len(dag.nodes)) tdag = NewTestDAG() tdag.Add("foo/$bogus/blah", "bam") tdag.Add("bam", "$flop/bop") tdag.Add("$flop/bop") dag = tdag.Finish() errs = dag.ExpandNodes(ns) if len(errs) == 2 { assert.Equal(t, "undefined variable 'bogus' in string", errs[0].Error()) assert.Equal(t, "undefined variable 'flop' in string", errs[1].Error()) } else { t.Errorf("expected %d errors, but got %d:\n%v", 2, len(errs), errs) } }
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_evaluate_list(t *testing.T) { rt := minimalRuntime() rt.stack.Assign("blop", types.MakeFuString("bar")) elements := []dsl.ASTExpression{ dsl.NewASTString("\"foo\""), dsl.NewASTName("blop"), dsl.NewASTString("\"baz\""), } astlist := dsl.NewASTList(elements) actual, errs := rt.evaluate(astlist) testutils.NoErrors(t, errs) expect := types.MakeStringList("foo", "bar", "baz") assert.Equal(t, expect, actual) }
func Test_assign(t *testing.T) { // AST for a = "foo" node := dsl.NewASTAssignment("a", stringnode("foo")) rt := minimalRuntime() errs := rt.assign(node) assert.Equal(t, 0, len(errs)) expect := types.MakeFuString("foo") assertIn(t, rt.Namespace(), "a", expect) // AST for a = foo (another variable, to provoke an error) node = dsl.NewASTAssignment("b", dsl.NewASTName("foo")) errs = rt.assign(node) assert.Equal(t, "name not defined: 'foo'", errs[0].Error()) _, ok := rt.Lookup("b") assert.False(t, ok) }
func Test_ListNode_ActionExpand(t *testing.T) { ns := types.NewValueMap() assertExpand := func(expect []Node, list *ListNode) { actualobj, err := list.ActionExpand(ns, nil) assert.Nil(t, err) actual := make([]Node, len(actualobj.List())) for i, obj := range actualobj.List() { actual[i] = obj.(Node) } if len(expect) == len(actual) { for i, enode := range expect { anode := actual[i] if !enode.Equal(anode) { t.Errorf("ListNode[%d]: expected <%T: %s> but got <%T: %s>", i, enode, enode, anode, anode) } } } else { t.Errorf( "ListNode %v: expected ActionExpand() to return %d Nodes, "+ "but got %d: %v", list, len(expect), len(actual), actual) } } // a single empty ListNode yields an empty slice of Nodes list := newListNode() assertExpand([]Node{}, list) // a ListNode containing boring ordinary non-expanding nodes just // returns them node0 := NewStubNode("0") node1 := NewStubNode("1") list = newListNode(node0, node1) assertExpand([]Node{node0, node1}, list) // a ListNode with expanding nodes expands them (and flattens the // resulting list) list = newListNode(node1, list, node0) assertExpand([]Node{node1, node0, node1, node0}, list) ns.Assign("a", types.MakeFuString("argghh")) list = newListNode(node1, NewStubNode("say $a"), node0) assertExpand([]Node{node1, NewStubNode("say argghh"), node0}, list) }
func Test_evaluate_list_errors(t *testing.T) { rt := minimalRuntime() rt.stack.Assign("src", types.MakeFuString("foo.c")) elements := []dsl.ASTExpression{ dsl.NewASTString("\"whee!\""), dsl.NewASTName("bogus"), dsl.NewASTName("src"), dsl.NewASTString("\"bop\""), dsl.NewASTName("morebogus"), } astlist := dsl.NewASTList(elements) actual, errs := rt.evaluate(astlist) if len(errs) != 2 { t.Fatalf("expected 2 errors, but got %d: %v", len(errs), errs) } assert.Equal(t, "name not defined: 'bogus'", errs[0].Error()) assert.Equal(t, "name not defined: 'morebogus'", errs[1].Error()) assert.Nil(t, actual) }
// Walk the filesystem for files matching this FinderNode's include // patterns. Return the list of matching filenames as a FuList of // FileNode. func (self *FinderNode) ActionExpand( ns types.Namespace, ctx *types.ExpandContext) ( types.FuObject, error) { // if case this node was not already expanded by // DAG.ExpandNodes(), do it now so variable references are // followed var err error err = self.NodeExpand(ns) if err != nil { return nil, err } filenames, err := self.FindFiles() if err != nil { return nil, err } var values []types.FuObject for _, filename := range filenames { values = append(values, types.MakeFuString(filename)) } return types.MakeFuList(values...), nil }
func (self PythonCallable) callPython(argsource types.ArgSource) (types.FuObject, []error) { args := argsource.Args() // build a slice of strings, which will then be converted to // Python strings in C (this way we only copy the bytes once, at // the cost of C code knowing the internals of Go slices and // strings) sargs := make([]string, len(args)) for i, arg := range args { switch arg.(type) { case types.FuString: sargs[i] = arg.ValueString() default: err := fmt.Errorf( "bad argument type: all arguments must be strings, "+ "but argument %d is %s %s", i+1, arg.Typename(), arg.String()) return nil, []error{err} } } var cerror *C.char var cresult *C.char var result types.FuObject cresult = C.call_python(self.callable, unsafe.Pointer(&sargs), &cerror) if cerror != nil { err := C.GoString(cerror) return nil, []error{errors.New(err)} } if cresult != nil { result = types.MakeFuString(C.GoString(cresult)) } return result, nil }
//export callBuiltin func callBuiltin( pfunc unsafe.Pointer, numargs C.int, cargs unsafe.Pointer) ( *C.char, *C.char) { log.Debug(log.PLUGINS, "callBuiltin: calling Go function at %p", pfunc) var fn types.FuCode fuargs := make([]types.FuObject, numargs) for i := uintptr(0); i < uintptr(numargs); i++ { // cargs is really a C char **, i.e. a pointer to an array of // char *. argp is a pointer to the i'th member of cargs. This // is just C-style array lookup with pointer arithmetic, but // in Go syntax. argp := unsafe.Pointer(uintptr(cargs) + i*unsafe.Sizeof(cargs)) arg := C.GoString(*(**C.char)(argp)) fuargs[i] = types.MakeFuString(arg) } args := types.MakeBasicArgs(nil, fuargs, nil) fn = *(*types.FuCode)(unsafe.Pointer(&pfunc)) log.Debug(log.PLUGINS, "followed unsafe.Pointer to get %p", fn) result, err := fn(args) if len(err) > 0 { errmsgs := make([]string, len(err)) for i, err := range err { errmsgs[i] = err.Error() } return nil, C.CString(strings.Join(errmsgs, "\n")) } var cresult *C.char if result != nil { cresult = C.CString(result.String()) } return cresult, nil }
func (self *SequenceAction) AddCommand(command *dsl.ASTString) { raw := types.MakeFuString(command.Value()) self.AddAction(NewCommandAction(raw)) }
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) }