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_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_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_ListNode_Add(t *testing.T) { nodelist := newListNode() otherlist := types.MakeFuList() actual, err := nodelist.Add(otherlist) testutils.NoError(t, err) if _, ok := actual.(*ListNode); !ok { t.Fatalf("expected object of type *ListNode, but got %T", actual) } assert.Equal(t, 0, len(actual.List())) node0 := NewStubNode("bla") node1 := NewStubNode("pog") otherlist = types.MakeFuList(node0, node1) actual, err = nodelist.Add(otherlist) testutils.NoError(t, err) nodes := actual.(*ListNode).List() if len(nodes) != 2 { t.Errorf("expected ListNode with 2 nodes, but got %v", actual) } else { assert.Equal(t, []types.FuObject{node0, node1}, nodes) } otherlist = types.MakeStringList("foo") actual, err = nodelist.Add(otherlist) assert.Nil(t, actual) assert.Equal(t, "unsupported operation: cannot add list to ListNode "+ "(second operand contains string)", err.Error()) }
func Test_remove(t *testing.T) { cleanup := testutils.Chtemp() defer cleanup() args := RuntimeArgs{ BasicArgs: types.MakeBasicArgs(nil, []types.FuObject{}, nil), } // remove() doesn't care about empty arg list (same reason as mkdir()) result, errs := fn_remove(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) // remove() ignores non-existent files args.SetArgs(types.MakeStringList("foo", "bar/bleep/meep", "qux").List()) result, errs = fn_remove(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) // remove() removes regular files testutils.TouchFiles("foo", "bar/bleep/meep", "bar/bleep/feep", "qux") args.SetArgs(types.MakeStringList("foo", "bar/bleep/meep", "bogus").List()) result, errs = fn_remove(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) assert.Equal(t, []string{"bar", "qux"}, dirContents(".")) assert.Equal(t, []string{"bleep"}, dirContents("bar")) assert.Equal(t, []string{"feep"}, dirContents("bar/bleep")) // remove() removes files and directories too testutils.TouchFiles("foo", "bar/bleep/meep", "qux") args.SetArgs(types.MakeStringList("bogus", "bar", "morebogus", "qux").List()) result, errs = fn_remove(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) assert.Equal(t, []string{"foo"}, dirContents(".")) // remove() fails if it tries to delete from an unwriteable directory testutils.TouchFiles("bar/bong", "qux/bip") testutils.ChmodRO("bar") defer testutils.ChmodOwnerAll("bar") args.SetArgs(types.MakeStringList("bar", "qux").List()) result, errs = fn_remove(args) assert.Nil(t, result) assert.Equal(t, "remove bar/bong: permission denied", errs[0].Error()) }
func Test_println(t *testing.T) { cleanup1 := testutils.Chtemp() defer cleanup1() rfile, err := os.Create("stdout") if err != nil { panic(err) } // save a copy of stdout in another fd stdout_fd := int(os.Stdout.Fd()) save_stdout, err := syscall.Dup(stdout_fd) if err != nil { panic(err) } // redirect stdout to rfile err = syscall.Dup2(int(rfile.Fd()), stdout_fd) if err != nil { panic(err) } cleanup2 := func() { rfile.Close() err = syscall.Dup2(save_stdout, stdout_fd) if err != nil { panic(err) } syscall.Close(save_stdout) } defer cleanup2() args := RuntimeArgs{ BasicArgs: types.MakeBasicArgs(nil, []types.FuObject{}, nil), } result, errs := fn_println(args) assert.Nil(t, result) assert.Equal(t, 0, len(errs)) data, err := ioutil.ReadFile("stdout") assert.Nil(t, err) assert.Equal(t, "\n", string(data)) rfile.Truncate(0) rfile.Seek(0, 0) args.SetArgs(types.MakeStringList("hello", "world").List()) fn_println(args) data, err = ioutil.ReadFile("stdout") assert.Nil(t, err) assert.Equal(t, "hello world\n", string(data)) rfile.Truncate(0) rfile.Seek(0, 0) }
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_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_ActionNode(t *testing.T) { args := RuntimeArgs{ BasicArgs: types.MakeBasicArgs(nil, types.MakeStringList("test/x").List(), nil), runtime: minimalRuntime(), } node0, errs := fn_ActionNode(args) assert.Equal(t, 0, len(errs)) _ = node0.(*dag.ActionNode) assert.Equal(t, "test/x:action", node0.ValueString()) assert.Equal(t, "test/x:action", node0.(dag.Node).Name()) node1, errs := fn_ActionNode(args) assert.True(t, node0 == node1) }
func Test_FileNode(t *testing.T) { args := RuntimeArgs{ BasicArgs: types.MakeBasicArgs(nil, types.MakeStringList("a.txt").List(), nil), runtime: minimalRuntime(), } node0, errs := fn_FileNode(args) assert.Equal(t, 0, len(errs)) node1, errs := fn_FileNode(args) assert.Equal(t, 0, len(errs)) // panic on unexpected type _ = node0.(*dag.FileNode) _ = node1.(*dag.FileNode) assert.Equal(t, "a.txt", node0.(dag.Node).Name()) assert.True(t, node0.Equal(node1)) // FileNode is a factory: it will return existing node objects // rather than create new ones assert.True(t, node0 == node1) }
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) } }