(* Thorough testing of word expansion *) open Merry module C = Merry.Eval.Make (Merry_posix.State) (Merry_posix.Fd) (Merry_posix.Exec) let expand ctx cst = let v = List.concat_map (fun v -> snd (C.word_expansion ctx v)) cst in List.concat v let fragment = Alcotest.of_pp Merry.Ast.Fragment.pp let fragments = Alcotest.list fragment let frags ?(escaping = true) ?(was_quoted = false) v = List.map (Ast.Fragment.make ~was_quoted ~escaping) v let with_default_ctx ?(args = []) ?(params = []) ?(home = "/home/merry/") env fn = let executor = Merry_posix.Exec.{ mgr = env#process_mgr } in let interactive = false in let pos_zero = "msh" in Eio.Switch.run @@ fun async_switch -> let state = Merry_posix.State.make ~home None (Fpath.v (Merry.Eunix.cwd ())) in let state = List.fold_left (fun s (k, v) -> Merry_posix.State.update s ~param:k [ Ast.Fragment.make v ] |> Result.get_ok) state params in let ctx = C.make_ctx ~interactive state executor ~async_switch ~argv:(Array.of_list args) ~fd_opt:Merry_posix.fd_opt ~program:pos_zero Merry.Fd_table.empty env in fn ctx module W = struct let name s = Ast.WordName s let lit s = Ast.WordLiteral s let dquote c = Ast.WordDoubleQuoted c let glob_all = Ast.WordGlobAll let tilde s = Ast.WordTildePrefix s (* let squote c = Ast.WordSingleQuoted c *) (* let arith a = Ast.WordArithmeticExpression a *) let var v = Ast.WordVariable (Ast.VariableAtom (v, Ast.NoAttribute)) let var_with_prefix ~prefix v = Ast.WordVariable (Ast.VariableAtom (v, Ast.RemoveLargestPrefixPattern prefix)) end let test_no_expansions env () = let args = [ "echo"; "hello" ] in let cargs = W.[ [ name "echo" ]; [ lit "hello" ] ] in with_default_ctx ~args env @@ fun ctx -> let expected = frags args in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_empty_expansion env () = let args = [ "$FOO" ] in let cargs = W.[ [ var "FOO" ] ] in with_default_ctx ~args env @@ fun ctx -> let expected = frags [] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_dquote env () = let args = [ "echo"; "\"hello there\"" ] in let cargs = W.[ [ name "echo" ]; [ dquote [ lit "hello there" ] ] ] in with_default_ctx ~args env @@ fun ctx -> let expected = Ast. [ Fragment.make ~escaping:true "echo"; Fragment.make ~was_quoted:true ~escaping:false ~join:`No "hello there"; ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_dquote_expansion env () = let args = [ "echo"; "\"hello $FOO...\"" ] in let cargs = W.[ [ name "echo" ]; [ dquote [ lit "hello "; var "FOO"; lit "..." ] ] ] in with_default_ctx ~args ~params:[ ("FOO", "there") ] env @@ fun ctx -> let expected = Ast.Fragment. [ make ~escaping:true "echo"; make ~was_quoted:true ~escaping:false "hello there..."; ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_single_expansion env () = let args = [ "echo"; "$FOO" ] in let cargs = W.[ [ name "echo" ]; [ var "FOO" ] ] in with_default_ctx ~args ~params:[ ("FOO", "bar") ] env @@ fun ctx -> let expected = [ Ast.Fragment.make ~escaping:true "echo"; Ast.Fragment.make ~escaping:false "bar"; ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_argv_expansion env () = let cargs = W.[ [ name "echo" ]; [ var "@" ] ] in with_default_ctx ~args:[ "echo"; "a"; "b"; "c d" ] env @@ fun ctx -> (* Escaping? *) let expected = Ast.Fragment.make ~escaping:true "echo" :: frags ~escaping:false [ "a"; "b"; "c"; "d" ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_argv_in_quotes_expansion env () = let cargs = W.[ [ name "echo" ]; [ dquote [ lit "got ["; var "@"; lit "]" ] ] ] in with_default_ctx ~args:[ "echo"; "a"; "b"; "c" ] env @@ fun ctx -> let expected = Ast.Fragment. [ make ~escaping:true "echo"; make ~was_quoted:true "got [a"; make ~was_quoted:true "b"; make ~was_quoted:true "c]"; ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_arg_in_quotes_expansion env () = let cargs = W. [ [ dquote [ lit "use `"; var_with_prefix ~prefix:W.[ lit "*/" ] "0"; lit " --help'"; ]; ]; ] in with_default_ctx ~args:[ "/some/file/test.sh" ] env @@ fun ctx -> let expected = Ast.Fragment.[ make ~was_quoted:true "use `test.sh --help'" ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_glob env () = let cargs = W.[ [ glob_all; lit ".ml" ] ] in with_default_ctx ~args:[ "*.ml" ] env @@ fun ctx -> let expected = Ast.Fragment.[ make "wordexp.ml" ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_tilde env () = let cargs = W.[ [ tilde "~"; lit "documents" ] ] in with_default_ctx ~args:[ "~/documents" ] env @@ fun ctx -> let expected = Ast.Fragment.[ make "/home/merry/documents" ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let simple env = [ ("no expansions", `Quick, test_no_expansions env); ("empty expansion", `Quick, test_empty_expansion env); ("double quote", `Quick, test_dquote env); ("double quote expansion", `Quick, test_dquote_expansion env); ("single expansion", `Quick, test_single_expansion env); ("argv expansion", `Quick, test_argv_expansion env); ("argv expansion dquote", `Quick, test_argv_in_quotes_expansion env); ("arg expansion dquote", `Quick, test_arg_in_quotes_expansion env); ("glob all", `Quick, test_glob env); ("tilde", `Quick, test_tilde env); ] let () = Eio_posix.run @@ fun env -> Alcotest.run "wordexp" [ ("simple", simple env) ]