File 0114-Robustify-and-clean-up-interactive_shell_SUITE.patch of Package erlang
From 0f1d484ad2e4d7edcfb5788f99d040ecc4c32718 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Thu, 6 May 2021 06:17:51 +0200
Subject: [PATCH 2/2] Robustify and clean up interactive_shell_SUITE
Some of the more notable changes are:
* The line-based commands get_data, get_line, and get_line_re were
problematic because there could sometimes be extra newlines depending
on the exact timing. Replace those commands with an expect command
(inspired by the expect command in the Tcl-based expect tool) that
matches a regexpt against the entire input seen so far.
* The putline and putline_raw were confusing. putline would both do
do output and then try to match a CRLF; putline_raw would not do
any matching. putline_raw has been removed and putline has changed
to not do any matching.
* Faster init_per_SUITE/2 when the default shell is the new shell.
* Always print all logs from run_erl. Having the logs from the part
of the test case that succeeded can facilitate debugging.
* Skip the shell_history_custom/1 and shell_history_custom_errors/1
test cases (instead of failing) when the user has settings in
ERL_AFLAGS that prevents a custom shell history module to be used.
* Never let the tested node connect to the test_server node, to avoid
that the test_server node is accidentally killed by "init:stop()".
---
lib/kernel/test/interactive_shell_SUITE.erl | 1325 +++++++++----------
1 file changed, 653 insertions(+), 672 deletions(-)
diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl
index bd0fa755ac..14b48313b6 100644
--- a/lib/kernel/test/interactive_shell_SUITE.erl
+++ b/lib/kernel/test/interactive_shell_SUITE.erl
@@ -18,11 +18,12 @@
%% %CopyrightEnd%
%%
-module(interactive_shell_SUITE).
--include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_group/2,end_per_group/2,
- get_columns_and_rows/1, exit_initial/1, job_control_local/1,
+
+-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
+ init_per_testcase/2, end_per_testcase/2,
+ get_columns_and_rows/1, exit_initial/1, job_control_local/1,
job_control_remote/1,stop_during_init/1,
shell_history/1, shell_history_resize/1, shell_history_eaccess/1,
shell_history_repair/1, shell_history_repair_corrupt/1,
@@ -30,32 +31,26 @@
shell_history_custom/1, shell_history_custom_errors/1,
job_control_remote_noshell/1,ctrl_keys/1,
get_columns_and_rows_escript/1,
- remsh/1, remsh_longnames/1, remsh_no_epmd/1]).
+ remsh_basic/1, remsh_longnames/1, remsh_no_epmd/1]).
--export([init_per_testcase/2, end_per_testcase/2]).
%% For spawn
-export([toerl_server/3]).
%% Exports for custom shell history module
-export([load/0, add/1]).
-init_per_testcase(_Func, Config) ->
- Config.
-
-end_per_testcase(_Func, _Config) ->
- ok.
-
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,3}}].
-all() ->
+all() ->
[get_columns_and_rows_escript,get_columns_and_rows,
exit_initial, job_control_local,
job_control_remote, job_control_remote_noshell,
- ctrl_keys, stop_during_init, {group, shell_history},
- remsh, remsh_longnames, remsh_no_epmd].
+ ctrl_keys, stop_during_init,
+ {group, shell_history},
+ {group, remsh}].
-groups() ->
+groups() ->
[{shell_history, [],
[shell_history,
shell_history_resize,
@@ -63,32 +58,77 @@ groups() ->
shell_history_repair,
shell_history_repair_corrupt,
shell_history_corrupt,
- shell_history_custom,
- shell_history_custom_errors
- ]}].
+ {group, sh_custom}
+ ]},
+ {sh_custom, [],
+ [shell_history_custom,
+ shell_history_custom_errors]},
+ {remsh, [],
+ [remsh_basic,
+ remsh_longnames,
+ remsh_no_epmd]}
+ ].
init_per_suite(Config) ->
- Term = os:getenv("TERM", "dumb"),
- os:putenv("TERM","vt100"),
- DefShell = get_default_shell(),
- [{default_shell,DefShell},{term,Term}|Config].
+ case get_progs() of
+ {error, Error} ->
+ {skip, Error};
+ _ ->
+ Term = os:getenv("TERM", "dumb"),
+ os:putenv("TERM", "vt100"),
+ DefShell = get_default_shell(),
+ [{default_shell,DefShell},{term,Term}|Config]
+ end.
end_per_suite(Config) ->
Term = proplists:get_value(term,Config),
os:putenv("TERM",Term),
ok.
+init_per_group(remsh, Config) ->
+ case proplists:get_value(default_shell, Config) of
+ old -> {skip, "Not supported in old shell"};
+ new -> Config
+ end;
init_per_group(shell_history, Config) ->
case proplists:get_value(default_shell, Config) of
old -> {skip, "Not supported in old shell"};
new -> Config
end;
+init_per_group(sh_custom, Config) ->
+ %% Ensure that ERL_AFLAGS will not override the value of the
+ %% shell_history variable.
+ Name = interactive_shell_sh_custom,
+ Args = "-noshell -kernel shell_history not_overridden",
+ {ok, Node} = test_server:start_node(Name, slave, [{args,Args}]),
+ try erpc:call(Node, application, get_env, [kernel, shell_history], timeout(normal)) of
+ {ok, not_overridden} ->
+ Config;
+ _ ->
+ SkipText = "shell_history variable is overridden (probably by ERL_AFLAGS)",
+ {skip, SkipText}
+ catch
+ C:R:Stk ->
+ io:format("~p\n~p\n~p\n", [C,R,Stk]),
+ {skip, "Unexpected error"}
+ after
+ test_server:stop_node(Node)
+ end;
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
+init_per_testcase(_Func, Config) ->
+ Config.
+
+end_per_testcase(_Case, _Config) ->
+ %% Terminate any connected nodes. They may disturb test cases that follow.
+ lists:foreach(fun(Node) ->
+ catch erpc:call(Node, erlang, halt, [])
+ end, nodes()),
+ ok.
%%-define(DEBUG,1).
-ifdef(DEBUG).
@@ -153,98 +193,107 @@ get_columns_and_rows_escript(Config) when is_list(Config) ->
%% Test that the shell can access columns and rows.
get_columns_and_rows(Config) when is_list(Config) ->
- case proplists:get_value(default_shell,Config) of
+ case proplists:get_value(default_shell, Config) of
old ->
- %% Old shell tests
- ?dbg(old_shell),
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline,"io:columns()."},
- {getline_re,".*{error,enotsup}"},
- {putline,"io:rows()."},
- {getline_re,".*{error,enotsup}"}
-
- ],[]),
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline,"io:columns()."},
- {getline_re,".*{ok,90}"},
- {putline,"io:rows()."},
- {getline_re,".*{ok,40}"}],
- [],
- "stty rows 40; stty columns 90; ");
+ test_columns_and_rows(old, []);
new ->
- %% New shell tests
- ?dbg(new_shell),
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline,"io:columns()."},
- %% Behaviour change in R12B-5, returns 80
- %% {getline,"{error,enotsup}"},
- {getline,"{ok,80}"},
- {putline,"io:rows()."},
- %% Behaviour change in R12B-5, returns 24
- %% {getline,"{error,enotsup}"}
- {getline,"{ok,24}"}
- ],[]),
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline,"io:columns()."},
- {getline,"{ok,90}"},
- {putline,"io:rows()."},
- {getline,"{ok,40}"}],
- [],
- "stty rows 40; stty columns 90; ")
- end.
-
+ test_columns_and_rows(old, ["-oldshell"]),
+ test_columns_and_rows(new, [])
+ end,
+ ok.
+test_columns_and_rows(old, Args) ->
+ rtnode([{putline, ""},
+ {putline, "2."},
+ {expect, "2\r\n"},
+ {putline, "io:columns()."},
+ {expect, "{error,enotsup}\r\n"},
+ {putline, "io:rows()."},
+ {expect, "{error,enotsup}\r\n"}
+ ], [], [], Args),
+
+ rtnode([{putline, ""},
+ {putline, "2."},
+ {expect, "2\r\n"},
+ {putline, "io:columns()."},
+ {expect, "{ok,90}\r\n"},
+ {putline,"io:rows()."},
+ {expect, "{ok,40}\r\n"}],
+ [],
+ "stty rows 40; stty columns 90; ",
+ Args);
+test_columns_and_rows(new, _Args) ->
+ rtnode([{putline, ""},
+ {expect, "1> $"},
+ {putline, "2."},
+ {expect, "\r\n2\r\n"},
+ {expect, "> $"},
+ {putline, "io:columns()."},
+ {expect, "{ok,80}\r\n"},
+ {expect, "> $"},
+ {putline, "io:rows()."},
+ {expect, "\r\n{ok,24}\r\n"}
+ ]),
+
+ rtnode([{putline, ""},
+ {expect, "1> $"},
+ {putline, "2."},
+ {expect, "\r\n2\r\n"},
+ {expect, "> $"},
+ {putline, "io:columns()."},
+ {expect, "\r\n{ok,90}\r\n"},
+ {expect, "> $"},
+ {putline, "io:rows()."},
+ {expect, "\r\n{ok,40}\r\n"}],
+ [],
+ "stty rows 40; stty columns 90; ").
%% Tests that exit of initial shell restarts shell.
exit_initial(Config) when is_list(Config) ->
- case proplists:get_value(default_shell,Config) of
+ case proplists:get_value(default_shell, Config) of
old ->
- rtnode([{putline,""},
- {putline, "2."},
- {getline_re, ".*2"},
- {putline,"exit()."},
- {getline,""},
- {getline,"Eshell"},
- {putline,""},
- {putline,"35."},
- {getline_re,".*35"}],[]);
- new ->
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline,"exit()."},
- {getline,""},
- {getline,"Eshell"},
- {putline,""},
- {putline,"35."},
- {getline_re,"35"}],[])
- end.
+ test_exit_initial(old);
+ new ->
+ test_exit_initial(old),
+ test_exit_initial(new)
+ end,
+ ok.
+
+test_exit_initial(old) ->
+ rtnode([{putline, ""},
+ {putline, "2."},
+ {expect, "2\r\n"},
+ {putline, "exit()."},
+ {expect, "Eshell"},
+ {putline, ""},
+ {putline, "35."},
+ {expect, "35\r\n"}],
+ [], [], ["-oldshell"]);
+test_exit_initial(new) ->
+ rtnode([{putline, ""},
+ {expect, "1> $"},
+ {putline, "2."},
+ {expect, "2"},
+ {putline,"exit()."},
+ {expect, "Eshell"},
+ {expect, "1> $"},
+ {putline, "35."},
+ {expect, "35\r\n"}]).
stop_during_init(Config) when is_list(Config) ->
- case get_progs() of
- {error,_Reason} ->
- {skip,"No runerl present"};
- {RunErl,_ToErl,Erl} ->
- case create_tempdir() of
- {error, Reason2} ->
- {skip, Reason2};
- Tempdir ->
- XArg = " -kernel shell_history enabled -s init stop",
- start_runerl_command(RunErl, Tempdir, "\\\""++Erl++"\\\""++XArg),
- Logs = rtnode_read_logs(Tempdir),
- rtnode_dump_logs(Logs),
- nomatch = binary:match(maps:get("erlang.log.1",Logs),
- <<"*** ERROR: Shell process terminated! ***">>)
- end
- end.
+ {RunErl,_ToErl,Erl} = get_progs(),
+ case create_tempdir() of
+ {error, Reason} ->
+ {skip, Reason};
+ Tempdir ->
+ XArg = " -kernel shell_history enabled -s init stop",
+ start_runerl_command(RunErl, Tempdir, "\\\""++Erl++"\\\""++XArg),
+ Logs = rtnode_read_logs(Tempdir),
+ rtnode_dump_logs(Logs),
+ nomatch = binary:match(map_get("erlang.log.1", Logs),
+ <<"*** ERROR: Shell process terminated! ***">>),
+ ok
+ end.
%% This testcase tests that shell_history works as it should.
%% We use Ctrl + P = Cp=[$\^p] in order to navigate up
@@ -257,50 +306,52 @@ shell_history(Config) when is_list(Config) ->
Path = shell_history_path(Config, "basic"),
rtnode([
{putline, "echo1."},
- {getline, "echo1"},
+ {expect, "echo1\r\n"},
{putline, "echo2."},
- {getline, "echo2"},
+ {expect, "echo2\r\n"},
{putline, "echo3."},
- {getline, "echo3"},
+ {expect, "echo3\r\n"},
{putline, "echo4."},
- {getline, "echo4"},
+ {expect, "echo4\r\n"},
{putline, "echo5."},
- {getline, "echo5"}
+ {expect, "echo5\r\n"}
], [], [], " -kernel shell_history enabled " ++
"-kernel shell_history_drop '[\\\"init:stop().\\\"]' " ++
mk_sh_param(Path)),
+ receive after 1000 -> ok end,
rtnode([
{putline, ""},
%% the init:stop that stopped the node is dropped
- {putdata, [$\^p]}, {getdata, "echo5."},
+ {putdata, [$\^p]}, {expect, "echo5[.]$"},
{putdata, [$\n]},
- {getline, "echo5"},
- {putdata, [$\^p]}, {getdata,"echo5."},
- {putdata, [$\^p]}, {getdata,"echo4."},
- {putdata, [$\^p]}, {getdata,"echo3."},
- {putdata, [$\^p]}, {getdata,"echo2."},
- {putdata, [$\^n]}, {getdata,"echo3."},
- {putdata, [$\^n]}, {getdata,"echo4."},
+ {expect, "echo5\r\n"},
+ {putdata, [$\^p]}, {expect, "echo5[.]$"},
+ {putdata, [$\^p]}, {expect, "echo4[.]$"},
+ {putdata, [$\^p]}, {expect, "echo3[.]$"},
+ {putdata, [$\^p]}, {expect, "echo2[.]$"},
+ {putdata, [$\^n]}, {expect, "echo3[.]$"},
+ {putdata, [$\^n]}, {expect, "echo4[.]$"},
{putdata, [$\^b]}, {sleep,50}, %% the echo4. (cursor moved one left)
- {putline, ["echo"]},
- {getline, "echo4echo"}
- ], [], [], " -kernel shell_history enabled " ++ mk_sh_param(Path)).
+ {putline, ["ECHO"]},
+ {expect, "echo4ECHO\r\n"}
+ ], [], [], " -kernel shell_history enabled " ++ mk_sh_param(Path)),
+ ok.
shell_history_resize(Config) ->
Path = shell_history_path(Config, "resize"),
rtnode([
{putline, "echo."},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], " -kernel shell_history_file_bytes 123456 " ++
"-kernel shell_history enabled " ++ mk_sh_param(Path)),
{ok, Logs} =
rtnode([
{putline, ""},
- {putdata, [$\^p]}, {getdata,"init:stop()."},
- {putdata, [$\^p]}, {getdata,"echo."},
+ {putdata, [$\^p]}, {expect, "init:stop\\(\\)[.]$"},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
{putdata, [$\n]},
- {getline, "echo"}
+ {expect, "echo"}
], [], [], " -kernel shell_history_file_bytes 654321 " ++
"-kernel shell_history enabled " ++ mk_sh_param(Path)),
@@ -324,7 +375,7 @@ shell_history_eaccess(Config) ->
{ok, Logs1} =
rtnode([
{putline, "echo."},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
rtnode_check_logs("erlang.log.1", "Error handling file", Logs1),
@@ -332,10 +383,10 @@ shell_history_eaccess(Config) ->
%% shell_docs recursively creates the folder to store the
%% logs. This test checks that erlang still starts if we
%% cannot create the folders to the path.
- {ok, Logs2} =
+ {ok, Logs2} =
rtnode([
{putline, "echo."},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], "-kernel shell_history enabled " ++
mk_sh_param(filename:join(Path,"logs"))),
@@ -343,34 +394,25 @@ shell_history_eaccess(Config) ->
after
file:write_file_info(Path, Info)
- end.
+ end,
+ ok.
shell_history_repair(Config) ->
Path = shell_history_path(Config, "repair"),
%% We stop a node without closing the log
- try rtnode([
- {putline, "echo."},
- {getline, "echo"},
- {sleep, 2500}, %% disk_log internal cache timer is 2000 ms
- {putline, "erlang:halt(0)."}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)) of
- _ ->
- ok
- catch _:_ ->
- ok
- end,
+ shell_history_halt(Path),
{ok, Logs} =
rtnode([
{putline, ""},
- {putdata, [$\^p]}, {getdata,"echo."},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
{putdata, [$\n]},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
%% The regexp below checks that he string is NOT part of the log
- rtnode_check_logs("erlang.log.1",
+ rtnode_check_logs("erlang.log.1",
"The shell history log file was corrupted and was repaired",
false,
Logs),
@@ -380,17 +422,7 @@ shell_history_repair_corrupt(Config) ->
Path = shell_history_path(Config, "repair_corrupt"),
%% We stop a node without closing the log
- try rtnode([
- {putline, "echo."},
- {getline, "echo"},
- {sleep, 2500}, %% disk_log internal cache timer is 2000 ms
- {putline, "erlang:halt(0)."}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)) of
- _ ->
- ok
- catch _:_ ->
- ok
- end,
+ shell_history_halt(Path),
%% We corrupt the disklog
{ok, D} = file:open(filename:join(Path,"erlang-shell-log.1"), [read,append]),
@@ -400,12 +432,12 @@ shell_history_repair_corrupt(Config) ->
{ok, Logs} =
rtnode([
{putline, ""},
- {putdata, [$\^p]}, {getdata,"echo."},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
{putdata, [$\n]},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
- rtnode_check_logs("erlang.log.1",
+ rtnode_check_logs("erlang.log.1",
"The shell history log file was corrupted and was repaired.",
Logs),
ok.
@@ -413,18 +445,12 @@ shell_history_repair_corrupt(Config) ->
shell_history_corrupt(Config) ->
Path = shell_history_path(Config, "corrupt"),
- %% We stop a node without closing the log
- try rtnode([
- {putline, "echo."},
- {getline, "echo"}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)) of
- _ ->
- ok
- catch _:_ ->
- ok
- end,
+ %% We initialize the shell history log with a known value.
+ rtnode([{putline, "echo."},
+ {expect, "echo\r\n"}
+ ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
- %% We corrupt the disklog
+ %% We corrupt the disklog.
{ok, D} = file:open(filename:join(Path,"erlang-shell-log.1"), [read, append]),
ok = file:write(D, [10, 10]),
ok = file:close(D),
@@ -432,16 +458,29 @@ shell_history_corrupt(Config) ->
{ok, Logs} =
rtnode([
{putline, ""},
- {putdata, [$\^p]}, {getdata,"init:stop()."},
- {putdata, [$\^p]}, {getdata,"echo."},
+ {putdata, [$\^p]}, {expect, "init:stop\\(\\)[.]$"},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
{putdata, [$\n]},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
- rtnode_check_logs("erlang.log.1", "Invalid chunk in the file", Logs),
+ rtnode_check_logs("erlang.log.1", "Invalid chunk in the file", Logs),
ok.
-
+%% Stop the node without closing the log.
+shell_history_halt(Path) ->
+ try
+ rtnode([
+ {putline, "echo."},
+ {expect, "echo\r\n"},
+ {sleep, 2500}, % disk_log internal cache timer is 2000 ms
+ {putline, "halt(0)."}
+ ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path))
+ catch
+ _:_ ->
+ ok
+ end.
+
shell_history_path(Config, TestCase) ->
filename:join([proplists:get_value(priv_dir, Config),
"shell_history", TestCase]).
@@ -451,63 +490,66 @@ mk_sh_param(Path) ->
shell_history_custom(_Config) ->
%% Up key: Ctrl + P = Cp=[$\^p]
- rtnode([
- {putline, ""},
- {putdata, [$\^p]}, {getdata,"0."},
+ rtnode([{expect, "1> $"},
+ %% {putline, ""},
+ {putdata, [$\^p]}, {expect, "0[.]"},
{putdata, [$\n]},
- {getline, "0"},
+ {expect, "0\r\n"},
{putline, "echo."},
- {getline, "!echo"} %% exclamation sign is printed by custom history module
+ {expect, "!echo\r\n"} % exclamation mark is printed by custom history module
], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
- " -pz " ++ filename:dirname(code:which(?MODULE))).
+ " -pz " ++ filename:dirname(code:which(?MODULE))),
+ ok.
shell_history_custom_errors(_Config) ->
%% Check that we can start with a node with an undefined
%% provider module.
- rtnode([
+ rtnode([{expect, "1> $"},
{putline, "echo."},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], " -kernel shell_history very_broken " ++
" -pz " ++ filename:dirname(code:which(?MODULE))),
%% Check that we can start with a node with a provider module
- %% that crashes in load/0
+ %% that crashes in load/0.
rtnode([
{putline, "echo."},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
" -kernel provider_load crash" ++
" -pz " ++ filename:dirname(code:which(?MODULE))),
%% Check that we can start with a node with a provider module
- %% that return incorrect in load/0
+ %% that return incorrect in load/0.
rtnode([
{putline, "echo."},
- {getline, "echo"}
+ {expect, "echo\r\n"}
], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
" -kernel provider_load badreturn" ++
" -pz " ++ filename:dirname(code:which(?MODULE))),
%% Check that we can start with a node with a provider module
- %% that crashes in load/0
+ %% that crashes in load/0.
rtnode([
{putline, "echo."},
- {getline, "Disabling shell history logging."},
- {getline, "echo"}
+ {expect, "Disabling shell history logging.\r\n"},
+ {expect, "echo\r\n"}
], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
" -kernel provider_add crash" ++
" -pz " ++ filename:dirname(code:which(?MODULE))),
%% Check that we can start with a node with a provider module
- %% that return incorrect in load/0
+ %% that return incorrect in load/0.
rtnode([
{putline, "echo."},
- {getline, "It returned {error,badreturn}."},
- {getline, "echo"}
+ {expect, "It returned {error,badreturn}.\r\n"},
+ {expect, "echo\r\n"}
], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
" -kernel provider_add badreturn" ++
- " -pz " ++ filename:dirname(code:which(?MODULE))).
+ " -pz " ++ filename:dirname(code:which(?MODULE))),
+
+ ok.
load() ->
case application:get_env(kernel,provider_load) of
@@ -533,298 +575,273 @@ add(_Line) ->
%% Tests that local shell can be started by means of job control.
job_control_local(Config) when is_list(Config) ->
- case proplists:get_value(default_shell,Config) of
+ case proplists:get_value(default_shell, Config) of
old ->
%% Old shell tests
{skip,"No new shell found"};
new ->
%% New shell tests
- rtnode([{putline,""},
+ rtnode([{putline, ""},
+ {expect, "1> $"},
{putline, "2."},
- {getline, "2"},
- {putline,[7]},
- {sleep,timeout(short)},
- {putline,""},
- {getline," -->"},
- {putline,"s"},
- {putline,"c"},
- {putline_raw,""},
- {getline,"Eshell"},
- {putline_raw,""},
- {getline,"1>"},
- {putline,"35."},
- {getline,"35"}],[])
+ {expect, "\r\n2\r\n"},
+ {putline, "\^g"},
+ {expect, ["--> $"]},
+ {putline, "s"},
+ {expect, ["--> $"]},
+ {putline, "c"},
+ {expect, ["\r\nEshell"]},
+ {expect, ["1> $"]},
+ {putline, "35."},
+ {expect, "\r\n35\r\n2> $"}],
+ []),
+ ok
end.
-job_control_remote(doc) -> [ "Tests that remote shell can be "
- "started by means of job control" ];
+%% Tests that remote shell can be started by means of job control.
job_control_remote(Config) when is_list(Config) ->
- case {node(),proplists:get_value(default_shell,Config)} of
- {nonode@nohost,_} ->
- exit(not_distributed);
- {_,old} ->
+ case proplists:get_value(default_shell, Config) of
+ old ->
{skip,"No new shell found"};
_ ->
- RNode = create_nodename(),
- MyNode = atom2list(node()),
- Pid = spawn_link(fun() ->
- receive die ->
- ok
- end
- end),
- PidStr = pid_to_list(Pid),
- register(kalaskula,Pid),
- CookieString = lists:flatten(
- io_lib:format("~w",
- [erlang:get_cookie()])),
- Res = rtnode([{putline,""},
- {putline, "erlang:get_cookie()."},
- {getline, CookieString},
- {putline,[7]},
- {sleep,timeout(short)},
- {putline,""},
- {getline," -->"},
- {putline,"r '"++MyNode++"'"},
- {putline,"c"},
- {putline_raw,""},
- {getline,"Eshell"},
- {sleep,timeout(short)},
- {putline_raw,""},
- {getline,"("++MyNode++")1>"},
- {putline,"whereis(kalaskula)."},
- {getline,PidStr},
- {sleep,timeout(short)}, % Race, known bug.
- {putline_raw,"exit()."},
- {getline,"***"},
- {putline,[7]},
- {putline,""},
- {getline," -->"},
- {putline,"c 1"},
- {putline,""},
- {sleep,timeout(short)},
- {putline_raw,""},
- {getline,"("++RNode++")"}],RNode),
- Pid ! die,
- Res
+ NSNode = start_node(?FUNCTION_NAME, []),
+ try
+ test_remote_job_control(NSNode)
+ after
+ test_server:stop_node(NSNode)
+ end
end.
-%% Tests that remote shell can be
-%% started by means of job control to -noshell node.
+%% Tests that remote shell can be started by means of job control to
+%% -noshell node.
job_control_remote_noshell(Config) when is_list(Config) ->
- case {node(),proplists:get_value(default_shell,Config)} of
- {nonode@nohost,_} ->
- exit(not_distributed);
- {_,old} ->
+ case proplists:get_value(default_shell, Config) of
+ old ->
{skip,"No new shell found"};
_ ->
- RNode = create_nodename(),
- NSNode = start_noshell_node(interactive_shell_noshell),
- Pid = spawn_link(NSNode, fun() ->
- receive die ->
- ok
- end
- end),
- PidStr = rpc:call(NSNode,erlang,pid_to_list,[Pid]),
- true = rpc:call(NSNode,erlang,register,[kalaskula,Pid]),
- NSNodeStr = atom2list(NSNode),
- CookieString = lists:flatten(
- io_lib:format("~w",
- [erlang:get_cookie()])),
- Res = rtnode([{putline,""},
- {putline, "erlang:get_cookie()."},
- {getline, CookieString},
- {putline,[7]},
- {sleep,timeout(short)},
- {putline,""},
- {getline," -->"},
- {putline,"r '"++NSNodeStr++"'"},
- {putline,"c"},
- {putline_raw,""},
- {getline,"Eshell"},
- {sleep,timeout(short)},
- {putline_raw,""},
- {getline,"("++NSNodeStr++")1>"},
- {putline,"whereis(kalaskula)."},
- {getline,PidStr},
- {sleep,timeout(short)}, % Race, known bug.
- {putline_raw,"exit()."},
- {getline,"***"},
- {putline,[7]},
- {putline,""},
- {getline," -->"},
- {putline,"c 1"},
- {putline,""},
- {sleep,timeout(short)},
- {putline_raw,""},
- {getline,"("++RNode++")"}],RNode),
- Pid ! die,
- stop_noshell_node(NSNode),
- Res
+ NSNode = start_node(?FUNCTION_NAME, ["-noshell"]),
+ try
+ test_remote_job_control(NSNode)
+ after
+ test_server:stop_node(NSNode)
+ end
end.
+test_remote_job_control(Node) ->
+ RemNode = create_nodename(),
+ Pid = spawn_link(Node, fun() ->
+ receive die ->
+ ok
+ end
+ end),
+ PidStr = erpc:call(Node, erlang, pid_to_list, [Pid]),
+ true = erpc:call(Node, erlang, register, [kalaskula,Pid]),
+ PrintedNode = printed_atom(Node),
+ CookieString = printed_atom(erlang:get_cookie()),
+
+ rtnode([{putline, ""},
+ {putline, "erlang:get_cookie()."},
+ {expect, "\r\n\\Q" ++ CookieString ++ "\\E"},
+ {putdata, "\^g"},
+ {expect, " --> $"},
+ {putline, "r " ++ PrintedNode},
+ {expect, "\r\n"},
+ {putline, "c"},
+ {expect, "\r\n"},
+ {expect, "Eshell"},
+ {expect, "\\Q(" ++ atom_to_list(Node) ++")1> \\E$"},
+ {putline, "whereis(kalaskula)."},
+ {expect, PidStr},
+ {putline, "exit()."},
+ {expect, "[*][*][*] Shell process terminated!"},
+ {putdata, "\^g"},
+ {expect, " --> $"},
+ {putline, "c 1"},
+ {expect, "\r\n"},
+ {putline, ""},
+ {expect, "\\Q("++RemNode++")\\E[12]> $"}
+ ], RemNode),
+ Pid ! die,
+ ok.
+
%% Tests various control keys.
-ctrl_keys(_Conf) when is_list(_Conf) ->
- Cu=[$\^u],
- Cw=[$\^w],
- Cy=[$\^y],
- Home=[27,$O,$H],
- End=[27,$O,$F],
+ctrl_keys(_Config) ->
+ Cu = [$\^u],
+ Cw = [$\^w],
+ Cy = [$\^y],
+ Home = [27,$O,$H],
+ End = [27,$O,$F],
rtnode([{putline,""},
{putline,"2."},
- {getline,"2"},
+ {expect,"2"},
{putline,"\"hello "++Cw++"world\"."}, % test <CTRL>+W
- {getline,"\"world\""},
+ {expect,"\"world\""},
{putline,"\"hello "++Cu++"\"world\"."}, % test <CTRL>+U
- {getline,"\"world\""},
+ {expect,"\"world\""},
{putline,"world\"."++Home++"\"hello "}, % test <HOME>
- {getline,"\"hello world\""},
+ {expect,"\"hello world\""},
{putline,"world"++Home++"\"hello "++End++"\"."}, % test <END>
- {getline,"\"hello world\""},
+ {expect,"\"hello world\""},
{putline,"\"hello world\""++Cu++Cy++"."},
- {getline,"\"hello world\""}]
- ++wordLeft()++wordRight(),[]).
-
+ {expect,"\"hello world\""}] ++
+ wordLeft() ++ wordRight(), []),
+ ok.
wordLeft() ->
- L1=[27,27,$[,$D],
- L2=[27]++"[5D",
- L3=[27]++"[1;5D",
- wordLeft(L1)++wordLeft(L2)++wordLeft(L3).
+ L1 = "\e\e[D",
+ L2 = "\e[5D",
+ L3 = "\e[1;5D",
+ wordLeft(L1) ++ wordLeft(L2) ++ wordLeft(L3).
wordLeft(Chars) ->
- End=[27,$O,$F],
+ End = "\eOF",
[{putline,"\"world\""++Chars++"hello "++End++"."},
- {getline,"\"hello world\""}].
+ {expect,"\"hello world\""}].
wordRight() ->
- R1=[27,27,$[,$C],
- R2=[27]++"[5C",
- R3=[27]++"[1;5C",
- wordRight(R1)++wordRight(R2)++wordRight(R3).
+ R1 = "\e\e[C",
+ R2 = "\e[5C",
+ R3 = "\e[1;5C",
+ wordRight(R1) ++ wordRight(R2) ++ wordRight(R3).
wordRight(Chars) ->
- Home=[27,$O,$H],
+ Home = "\eOH",
[{putline,"world"++Home++"\"hello "++Chars++"\"."},
- {getline,"\"hello world\""}].
+ {expect,"\"hello world\""}].
%% Test that -remsh works
-remsh(Config) when is_list(Config) ->
- case proplists:get_value(default_shell,Config) of
- old -> {skip,"Not supported in old shell"};
- new ->
- NodeStr = lists:flatten(io_lib:format("~p",[node()])),
- [_Name,Host] = string:split(atom_to_list(node()),"@"),
- Cmds = [{kill_emulator_command,sigint},
- {putline,""},
- {putline,"node()."},
- {getline,NodeStr}],
+remsh_basic(Config) when is_list(Config) ->
+ TargetNode = start_node(?FUNCTION_NAME, []),
+ TargetNodeStr = printed_atom(TargetNode),
+ [_Name,Host] = string:split(atom_to_list(node()), "@"),
- %% Test that remsh works with explicit -sname
- rtnode(Cmds ++ [{putline,"nodes()."},
- {getline,"['Remshtest@"++Host++"']"}],
- "Remshtest", [], "-remsh " ++ NodeStr),
+ PreCmds = [{putline,""},
+ {putline,"node()."},
+ {expect, "\\Q" ++ TargetNodeStr ++ "\\E\r\n"}],
- %% Test that remsh works without -sname
- rtnode(Cmds, [], [], " -remsh " ++ NodeStr)
+ PostCmds = quit_hosting_node(),
+ %% Test that remsh works with explicit -sname.
+ HostNode = atom_to_list(?FUNCTION_NAME) ++ "_host",
+ HostNodeStr = printed_atom(list_to_atom(HostNode ++ "@" ++ Host)),
+ rtnode(PreCmds ++
+ [{putline,"nodes()."},
+ {expect, "\\Q" ++ HostNodeStr ++ "\\E"}] ++
+ PostCmds,
+ HostNode, [], "-remsh " ++ TargetNodeStr),
- end.
+ %% Test that remsh works without -sname.
+ rtnode(PreCmds ++ PostCmds, [], [], " -remsh " ++ TargetNodeStr),
-%% Test that -remsh works with long names
-remsh_longnames(Config) when is_list(Config) ->
+ test_server:stop_node(TargetNode),
- case proplists:get_value(default_shell,Config) of
- old -> {skip,"Not supported in old shell"};
- new ->
- %% If we cannot resolve the domain, we need to add localhost to the longname
- Domain =
- case inet_db:res_option(domain) of
- [] ->
- "@127.0.0.1";
- _ -> ""
- end,
- case rtstart(" -name " ++ atom_to_list(?FUNCTION_NAME)++Domain) of
- {ok, _SRPid, _STPid, SState} ->
- {ok, _CRPid, CTPid, CState} =
- rtstart("-name undefined" ++ Domain ++
- " -remsh " ++ atom_to_list(?FUNCTION_NAME)),
- try
- ok = get_and_put(
- CTPid,
- [{kill_emulator_command,sigint},
- {putline,""},
- {putline,"node()."},
- {getline_re,atom_to_list(?FUNCTION_NAME)}], 1)
- after
- rtstop(CState), %% Stop client before server
- rtstop(SState)
- end;
- Else ->
- Else
- end
+ ok.
+
+quit_hosting_node() ->
+ %% Command sequence for entering a shell on the hosting node.
+ [{putdata, "\^g"},
+ {expect, "--> $"},
+ {putline, "s"},
+ {expect, "--> $"},
+ {putline, "c"},
+ {expect, ["Eshell"]},
+ {expect, ["1> $"]}].
+
+%% Test that -remsh works with long names.
+remsh_longnames(Config) when is_list(Config) ->
+ %% If we cannot resolve the domain, we need to add localhost to the longname
+ Domain =
+ case inet_db:res_option(domain) of
+ [] ->
+ "@127.0.0.1";
+ _ -> ""
+ end,
+ case rtstart(" -name " ++ atom_to_list(?FUNCTION_NAME)++Domain) of
+ {ok, _SRPid, STPid, SState} ->
+ {ok, _CRPid, CTPid, CState} =
+ rtstart("-name undefined" ++ Domain ++
+ " -remsh " ++ atom_to_list(?FUNCTION_NAME)),
+ ok = send_commands(
+ STPid,
+ [{putline, ""},
+ {putline, "node()."},
+ {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"}], 1),
+ try
+ ok = send_commands(
+ CTPid,
+ [{putline, ""},
+ {putline, "node()."},
+ {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"} | quit_hosting_node()], 1)
+ after
+ rtstop(CState), %% Stop client before server
+ rtstop(SState)
+ end;
+ Else ->
+ Else
end.
-%% Test that -remsh works without epmd
+%% Test that -remsh works without epmd.
remsh_no_epmd(Config) when is_list(Config) ->
-
- case proplists:get_value(default_shell,Config) of
- old -> {skip,"Not supported in old shell"};
- new ->
- EPMD_ARGS = "-start_epmd false -erl_epmd_port 12345 ",
- case rtstart([],"ERL_EPMD_PORT=12345 ",
- EPMD_ARGS ++ " -sname " ++ atom_to_list(?FUNCTION_NAME)) of
- {ok, _SRPid, STPid, SState} ->
- try
- ok = get_and_put(
- STPid,
- [{putline,""},
- {putline,"node()."},
- {getline_re,atom_to_list(?FUNCTION_NAME)}], 1),
- {ok, _CRPid, CTPid, CState} =
- rtstart([],"ERL_EPMD_PORT=12345 ",
- EPMD_ARGS ++ " -remsh "++atom_to_list(?FUNCTION_NAME)),
- try
- ok = get_and_put(
- CTPid,
- [{kill_emulator_command,sigint},
- {putline,""},
- {putline,"node()."},
- {getline_re,atom_to_list(?FUNCTION_NAME)}], 1)
- after
- rtstop(CState)
- end
- after
- rtstop(SState)
- end;
- Else ->
- Else
- end
+ EPMD_ARGS = "-start_epmd false -erl_epmd_port 12345 ",
+ case rtstart([],"ERL_EPMD_PORT=12345 ",
+ EPMD_ARGS ++ " -sname " ++ atom_to_list(?FUNCTION_NAME)) of
+ {ok, _SRPid, STPid, SState} ->
+ try
+ ok = send_commands(
+ STPid,
+ [{putline, ""},
+ {putline, "node()."},
+ {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"}], 1),
+ {ok, _CRPid, CTPid, CState} =
+ rtstart([],"ERL_EPMD_PORT=12345 ",
+ EPMD_ARGS ++ " -remsh "++atom_to_list(?FUNCTION_NAME)),
+ try
+ ok = send_commands(
+ CTPid,
+ [{putline, ""},
+ {putline, "node()."},
+ {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"} | quit_hosting_node()], 1)
+ after
+ rtstop(CState)
+ end
+ after
+ rtstop(SState)
+ end;
+ Else ->
+ Else
end.
-rtnode(C,N) ->
- rtnode(C,N,[]).
-rtnode(Commands,Nodename,ErlPrefix) ->
- rtnode(Commands,Nodename,ErlPrefix,[]).
-rtnode(Commands,Nodename,ErlPrefix,Args) ->
- case rtstart(Nodename,ErlPrefix,Args) of
+rtnode(C) ->
+ rtnode(C, []).
+
+rtnode(C, N) ->
+ rtnode(C, N, []).
+
+rtnode(Commands, Nodename, ErlPrefix) ->
+ rtnode(Commands, Nodename, ErlPrefix, []).
+
+rtnode(Commands, Nodename, ErlPrefix, Args) ->
+ case rtstart(Nodename, ErlPrefix, Args) of
{ok, _SPid, CPid, RTState} ->
- erase(getline_skipped),
- Res = (catch get_and_put(CPid, Commands, 1)),
+ Res = catch send_commands(CPid, Commands, 1),
Logs = rtstop(RTState),
case Res of
ok ->
- {Res, Logs};
- _Else ->
+ rtnode_dump_logs(Logs),
+ ok;
+ _ ->
rtnode_dump_logs(Logs),
ok = Res
- end;
+ end,
+ {ok, Logs};
Skip ->
Skip
end.
rtstart(Args) ->
- rtstart([],[],Args).
-rtstart(Nodename,ErlPrefix,Args) ->
+ rtstart([], [], Args).
+
+rtstart(Nodename, ErlPrefix, Args) ->
case get_progs() of
{error,_Reason} ->
{skip,"No runerl present"};
@@ -844,20 +861,7 @@ rtstart(Nodename,ErlPrefix,Args) ->
rtstop({CPid, SPid, ToErl, Tempdir}) ->
case stop_runerl_node(CPid) of
{error,_} ->
- CPid2 =
- start_toerl_server(ToErl,Tempdir),
- erase(getline_skipped),
- ok = get_and_put
- (CPid2,
- [{putline,[7]},
- {sleep,
- timeout(short)},
- {putline,""},
- {getline," -->"},
- {putline,"s"},
- {putline,"c"},
- {putline,""}],1),
- stop_runerl_node(CPid2);
+ catch rtstop_try_harder(ToErl, Tempdir);
_ ->
ok
end,
@@ -866,6 +870,18 @@ rtstop({CPid, SPid, ToErl, Tempdir}) ->
file:del_dir_r(Tempdir),
Logs.
+rtstop_try_harder(ToErl, Tempdir) ->
+ CPid = start_toerl_server(ToErl, Tempdir),
+ ok = send_commands(CPid,
+ [{putline,[7]},
+ {expect, " --> $"},
+ {putline, "s"},
+ {putline, "c"},
+ {putline, ""}], 1),
+ stop_runerl_node(CPid).
+
+timeout(longest) ->
+ timeout(long) + timeout(normal);
timeout(long) ->
2 * timeout(normal);
timeout(short) ->
@@ -873,154 +889,72 @@ timeout(short) ->
timeout(normal) ->
10000 * test_server:timetrap_scale_factor().
-
-start_noshell_node(Name) ->
- PADir = filename:dirname(code:which(?MODULE)),
- {ok, Node} = test_server:start_node(Name,slave,[{args," -noshell -pa "++
- PADir++" "}]),
+start_node(Name, Args0) ->
+ PaDir = filename:dirname(code:which(?MODULE)),
+ Args1 = ["-pa",PaDir|Args0],
+ Args = lists:append(lists:join(" ", Args1)),
+ {ok, Node} = test_server:start_node(Name, slave, [{args,Args}]),
Node.
-stop_noshell_node(Node) ->
- test_server:stop_node(Node).
-get_and_put(_CPid,[],_) ->
- ok;
-get_and_put(CPid, [{sleep, X}|T],N) ->
+send_commands(CPid, [{sleep, X}|T], N) ->
?dbg({sleep, X}),
receive
after X ->
- get_and_put(CPid,T,N+1)
+ send_commands(CPid, T, N+1)
end;
-get_and_put(CPid, [{kill_emulator_command, Cmd}|T],N) ->
- ?dbg({kill_emulator_command, Cmd}),
- CPid ! {self(), {kill_emulator_command, Cmd}},
- receive
- {kill_emulator_command,_Res} ->
- get_and_put(CPid,T,N)
- end;
-get_and_put(CPid, [{getline, Match}|T],N) ->
- ?dbg({getline, Match}),
- CPid ! {self(), {get_line, timeout(normal)}},
- receive
- {get_line, timeout} ->
- error_logger:error_msg("~p: getline timeout waiting for \"~s\" "
- "(command number ~p, skipped: ~p)~n",
- [?MODULE, Match,N,get(getline_skipped)]),
- {error, timeout};
- {get_line, Data} ->
- ?dbg({data,Data}),
- case lists:prefix(Match, Data) of
- true ->
- erase(getline_skipped),
- get_and_put(CPid, T,N+1);
- false ->
- case get(getline_skipped) of
- undefined ->
- put(getline_skipped,[Data]);
- List ->
- put(getline_skipped,List ++ [Data])
- end,
- get_and_put(CPid, [{getline, Match}|T],N)
- end
- end;
-
-%% Hey ho copy paste from stdlib/io_proto_SUITE
-get_and_put(CPid, [{getline_re, Match}|T],N) ->
- ?dbg({getline_re, Match}),
- CPid ! {self(), {get_line, timeout(normal)}},
- receive
- {get_line, timeout} ->
- error_logger:error_msg("~p: getline_re timeout waiting for \"~s\" "
- "(command number ~p, skipped: ~p)~n",
- [?MODULE, Match,N,get(getline_skipped)]),
- {error, timeout};
- {get_line, Data} ->
- ?dbg({data,Data}),
- case re:run(Data, Match,[{capture,none}]) of
- match ->
- erase(getline_skipped),
- get_and_put(CPid, T,N+1);
- _ ->
- case get(getline_skipped) of
- undefined ->
- put(getline_skipped,[Data]);
- List ->
- put(getline_skipped,List ++ [Data])
- end,
- get_and_put(CPid, [{getline_re, Match}|T],N)
- end
+send_commands(CPid, [{expect, Expect}|T], N) when is_list(Expect) ->
+ ?dbg(Exp),
+ case command(CPid, {expect, [Expect], timeout(normal)}) of
+ ok ->
+ send_commands(CPid, T, N + 1);
+ {expect_timeout, Got} ->
+ ct:pal("expect timed out waiting for ~p\ngot: ~p\n", [Expect,Got]),
+ {error, timeout};
+ Other ->
+ Other
end;
-
-get_and_put(CPid, [{getdata, Match}|T],N) ->
- ?dbg({getdata, Match}),
- CPid ! {self(), {get_data, timeout(normal), Match}},
- receive
- {get_data, timeout} ->
- error_logger:error_msg("~p: getdata timeout waiting for \"~s\" "
- "(command number ~p, skipped: ~p)~n",
- [?MODULE, Match,N,get(getline_skipped)]),
- {error, timeout};
- {get_data, _Data} ->
- ?dbg({CPid,data,_Data}),
- get_and_put(CPid, T, N+1)
- end;
-
-get_and_put(CPid, [{putline_raw, Line}|T],N) ->
- ?dbg({putline_raw, Line}),
- CPid ! {self(), {send_line, Line}},
- Timeout = timeout(normal),
- receive
- {send_line, ok} ->
- get_and_put(CPid, T,N+1)
- after Timeout ->
- error_logger:error_msg("~p: putline_raw timeout (~p) sending "
- "\"~s\" (command number ~p)~n",
- [?MODULE, Timeout, Line, N]),
- {error, timeout}
+send_commands(CPid, [{putline, Line}|T], N) ->
+ send_commands(CPid, [{putdata, Line ++ "\n"}|T], N);
+send_commands(CPid, [{putdata, Data}|T], N) ->
+ ?dbg({putdata, Data}),
+ case command(CPid, {send_data, Data}) of
+ ok ->
+ send_commands(CPid, T, N+1);
+ Error ->
+ Error
end;
+send_commands(_CPid, [], _) ->
+ ok.
-get_and_put(CPid, [{putline, Line}|T],N) ->
- ?dbg({putline, Line}),
- CPid ! {self(), {send_line, Line}},
- Timeout = timeout(normal),
+command(Pid, Req) ->
+ Timeout = timeout(longest),
+ Ref = erlang:monitor(process, Pid),
+ Pid ! {self(), Ref, Req},
receive
- {send_line, ok} ->
- get_and_put(CPid, [{getline, []}|T],N)
+ {Ref, Reply} ->
+ erlang:demonitor(Ref, [flush]),
+ Reply;
+ {'DOWN', Ref, _, _, Reason} ->
+ {error, Reason}
after Timeout ->
- error_logger:error_msg("~p: putline timeout (~p) sending "
- "\"~s\" (command number ~p)~n[~p]~n",
- [?MODULE, Timeout, Line, N,get()]),
- {error, timeout}
- end;
-get_and_put(CPid, [{putdata, Data}|T],N) ->
- ?dbg({putdata, Data}),
- CPid ! {self(), {send_data, Data}},
- Timeout = timeout(normal),
- receive
- {send_data, ok} ->
- get_and_put(CPid, T,N+1)
- after Timeout ->
- error_logger:error_msg("~p: putline_raw timeout (~p) sending "
- "\"~s\" (command number ~p)~n",
- [?MODULE, Timeout, Data, N]),
- {error, timeout}
+ io:format("timeout while executing ~p\n", [Req]),
+ {error, timeout}
end.
wait_for_runerl_server(SPid) ->
- Ref = erlang:monitor(process, SPid),
+ Ref = erlang:monitor(process, SPid),
Timeout = timeout(long),
receive
- {'DOWN', Ref, process, SPid, _} ->
+ {'DOWN', Ref, process, SPid, _Reason} ->
ok
after Timeout ->
- {error, timeout}
+ {error, runerl_server_timeout}
end.
-
-
stop_runerl_node(CPid) ->
Ref = erlang:monitor(process, CPid),
CPid ! {self(), kill_emulator},
- Timeout = timeout(long),
+ Timeout = timeout(longest),
receive
{'DOWN', Ref, process, CPid, noproc} ->
ok;
@@ -1029,34 +963,38 @@ stop_runerl_node(CPid) ->
{'DOWN', Ref, process, CPid, {error, Reason}} ->
{error, Reason}
after Timeout ->
- {error, timeout}
+ {error, toerl_server_timeout}
end.
get_progs() ->
+ try
+ do_get_progs()
+ catch
+ throw:Thrown ->
+ {error, Thrown}
+ end.
+
+do_get_progs() ->
case os:type() of
{unix,freebsd} ->
- {error,"cant use run_erl on freebsd"};
+ throw("Can't use run_erl on FreeBSD");
{unix,openbsd} ->
- {error,"cant use run_erl on openbsd"};
+ throw("Can't use run_erl on OpenBSD");
{unix,_} ->
- case os:find_executable("run_erl") of
- RE when is_list(RE) ->
- case os:find_executable("to_erl") of
- TE when is_list(TE) ->
- case os:find_executable("erl") of
- E when is_list(E) ->
- {RE,TE,E};
- _ ->
- {error, "Could not find erl command"}
- end;
- _ ->
- {error, "Could not find to_erl command"}
- end;
- _ ->
- {error, "Could not find run_erl command"}
- end;
+ RunErl = find_executable("run_erl"),
+ ToErl = find_executable("to_erl"),
+ Erl = find_executable("erl"),
+ {RunErl, ToErl, Erl};
_ ->
- {error, "Not a unix OS"}
+ throw("Not a Unix OS")
+ end.
+
+find_executable(Name) ->
+ case os:find_executable(Name) of
+ Prog when is_list(Prog) ->
+ Prog;
+ false ->
+ throw("Could not find " ++ Name)
end.
create_tempdir() ->
@@ -1143,8 +1081,8 @@ try_to_erl(Command, N) ->
Port
end.
-toerl_server(Parent,ToErl,Tempdir) ->
- Port = try_to_erl("\""++ToErl++"\" "++Tempdir++"/ 2>/dev/null", 8),
+toerl_server(Parent, ToErl, TempDir) ->
+ Port = try_to_erl("\""++ToErl++"\" "++TempDir++"/ 2>/dev/null", 8),
case Port of
P when is_port(P) ->
Parent ! {self(),started};
@@ -1152,7 +1090,9 @@ toerl_server(Parent,ToErl,Tempdir) ->
Parent ! {self(),error,Other},
exit(Other)
end,
- case toerl_loop(#{ port => Port}) of
+
+ State = #{port => Port, acc => [], kill_emulator_command => init_stop},
+ case toerl_loop(State) of
normal ->
ok;
{error, Reason} ->
@@ -1161,100 +1101,133 @@ toerl_server(Parent,ToErl,Tempdir) ->
exit(Reason)
end.
-toerl_loop(#{ port := Port } = State0) ->
- ?dbg({toerl_loop, Port, maps:get(acc,State0,[]),
- maps:get(match,State0,nomatch)}),
+toerl_loop(#{port := Port} = State0) ->
+ ?dbg({toerl_loop, Port, map_get(acc, State0),
+ maps:get(match, State0, nomatch)}),
- State = handle_match(State0),
+ State = handle_expect(State0),
receive
{Port,{data,Data}} when is_port(Port) ->
?dbg({?LINE,Port,{data,Data}}),
- toerl_loop(State#{ acc => lists:flatten([maps:get(acc,State,[]),Data])});
- {Pid, {get_data, Timeout, Match}} ->
- toerl_loop(
- State#{ get =>
- #{ match => Match,
- timer => erlang:start_timer(Timeout, self(), timeout),
- tag => get_data,
- from => Pid }
- });
- {Pid, {get_line, Timeout}} ->
- toerl_loop(
- State#{ get =>
- #{ match => "\r\n",
- timer => erlang:start_timer(Timeout, self(), timeout),
- tag => get_line,
- from => Pid }
- });
- {Pid, {send_line, Data7}} ->
- Port ! {self(),{command, Data7++"\n"}},
- Pid ! {send_line, ok},
- toerl_loop(State);
- {Pid, {send_data, Data}} ->
- Port ! {self(),{command, Data}},
- Pid ! {send_data, ok},
- toerl_loop(State);
- {Pid, {kill_emulator_command, Cmd}} ->
- put(kill_emulator_command, Cmd),
- Pid ! {kill_emulator_command, ok},
+ toerl_loop(State#{acc => map_get(acc, State) ++ Data});
+ {Pid, Ref, {expect, Expect, Timeout}} ->
+ toerl_loop(init_expect(Pid, Ref, Expect, Timeout, State));
+ {Pid, Ref, {send_data, Data}} ->
+ Port ! {self(), {command, Data}},
+ Pid ! {Ref, ok},
toerl_loop(State);
{_Pid, kill_emulator} ->
- case get(kill_emulator_command) of
- undefined ->
- Port ! {self(),{command, "init:stop().\n"}};
- sigint ->
- ?dbg({putdata,[$\^c]}),
- Port ! {self(),{command, [$\^c]}},
- receive
- {Port,{data,_Data}} ->
- ?dbg({exit_data, _Data}),
- ok
- after 2000 ->
- ok
- end,
- ?dbg({putdata,"a\n"}),
- Port ! {self(),{command, "a\n"}}
- end,
- Timeout1 = timeout(long),
- receive
- {Port,eof} ->
- normal
- after Timeout1 ->
- {error, kill_timeout}
- end;
- {timeout,Timer,timeout} ->
- #{ get := #{ tag := Tag, from := Pid, timer := Timer } } = State,
- Pid ! {Tag, timeout},
- toerl_loop(maps:remove(get, State));
+ kill_emulator(State);
+ {timeout,Timer,expect_timeout} ->
+ toerl_loop(handle_expect_timeout(Timer, State));
{Port, eof} ->
{error, unexpected_eof};
Other ->
{error, {unexpected, Other}}
end.
-handle_match(#{ acc := Acc, get := #{ tag := Tag,
- match := Match,
- from := From,
- timer := Timer}} = State) ->
- case string:split(Acc, Match) of
- [Pre,Post] ->
- ?dbg({match,Pre}),
- From ! {Tag, Pre},
- erlang:cancel_timer(Timer),
- receive
- {timeout,Timer,timeout} ->
- ok
- after 0 ->
- ok
- end,
- maps:put(acc, Post, maps:remove(get, State));
- [Acc] ->
- State
+kill_emulator(#{port := Port}) ->
+ %% If the line happens to end in a ".", issuing "init:stop()."
+ %% will result in a syntax error. To avoid that, issue a "\n"
+ %% before "init:stop().".
+ Port ! {self(),{command, "\ninit:stop().\n"}},
+ wait_for_eof(Port).
+
+wait_for_eof(Port) ->
+ receive
+ {Port,eof} ->
+ normal;
+ _Other ->
+ wait_for_eof(Port)
+ after
+ timeout(long) ->
+ {error, kill_timeout}
+ end.
+
+init_expect(Pid, Ref, ExpectList, Timeout, State) ->
+ try compile_expect(ExpectList) of
+ Expect ->
+ Exp = #{expect => Expect,
+ ref => Ref,
+ source => ExpectList,
+ timer => erlang:start_timer(Timeout, self(), expect_timeout),
+ from => Pid},
+ State#{expect => Exp}
+ catch
+ Class:Reason:Stk ->
+ io:put_chars("Compilation of expect pattern failed:"),
+ io:format("~p\n", [ExpectList]),
+ io:put_chars(erl_error:format_exception(Class, Reason, Stk)),
+ exit(expect_pattern_error)
+ end.
+
+handle_expect(#{acc := Acc, expect := Exp} = State) ->
+ #{expect := Expect, from := Pid, ref := Ref} = Exp,
+ case Expect(Acc) of
+ nomatch ->
+ State;
+ {matched, Eaten, Result} ->
+ Pid ! {Ref, Result},
+ finish_expect(Eaten, State)
end;
-handle_match(State) ->
+handle_expect(State) ->
State.
+handle_expect_timeout(Timer, State) ->
+ #{acc := Acc, expect := Exp} = State,
+ #{expect := Expect, timer := Timer, from := Pid, ref := Ref} = Exp,
+ case Expect({timeout, Acc}) of
+ nomatch ->
+ Result = {expect_timeout, Acc},
+ Pid ! {Ref, Result},
+ finish_expect(0, State);
+ {matched, Eaten, Result} ->
+ Pid ! {Ref, Result},
+ finish_expect(Eaten, State)
+ end.
+
+finish_expect(Eaten, #{acc := Acc0,
+ expect := #{timer := Timer}}=State) ->
+ erlang:cancel_timer(Timer),
+ receive
+ {timeout,Timer,timeout} ->
+ ok
+ after 0 ->
+ ok
+ end,
+ Acc = lists:nthtail(Eaten, Acc0),
+ maps:remove(expect, State#{acc := Acc}).
+
+compile_expect([{timeout,Action}|T]) when is_function(Action, 1) ->
+ Next = compile_expect(T),
+ fun({timeout, _}=Tm) ->
+ {matched, 0, Action(Tm)};
+ (Subject) ->
+ Next(Subject)
+ end;
+compile_expect([{{re,RE0},Action}|T]) when is_binary(RE0), is_function(Action, 1) ->
+ {ok, RE} = re:compile(RE0),
+ Next = compile_expect(T),
+ fun({timeout, _}=Subject) ->
+ Next(Subject);
+ (Subject) ->
+ case re:run(Subject, RE, [{capture,first,index}]) of
+ nomatch ->
+ Next(Subject);
+ {match, [{Pos,Len}]} ->
+ Matched = binary:part(list_to_binary(Subject), Pos, Len),
+ {matched, Pos+Len, Action(Matched)}
+ end
+ end;
+compile_expect([RE|T]) when is_list(RE) ->
+ Ok = fun(_) -> ok end,
+ compile_expect([{{re,list_to_binary(RE)},Ok}|T]);
+compile_expect([]) ->
+ fun(_) ->
+ nomatch
+ end.
+
rtnode_check_logs(Logname, Pattern, Logs) ->
rtnode_check_logs(Logname, Pattern, true, Logs).
rtnode_check_logs(Logname, Pattern, Match, Logs) ->
@@ -1275,7 +1248,15 @@ rtnode_dump_logs(Logs) ->
end, Logs).
rtnode_read_logs(Tempdir) ->
- {ok, LogFiles} = file:list_dir(Tempdir),
+ {ok, LogFiles0} = file:list_dir(Tempdir),
+
+ %% Make sure that we only read log files and not any named pipes.
+ LogFiles = [F || F <- LogFiles0,
+ case F of
+ "erlang.log" ++ _ -> true;
+ _ -> false
+ end],
+
lists:foldl(
fun(File, Acc) ->
case file:read_file(filename:join(Tempdir, File)) of
@@ -1289,13 +1270,13 @@ rtnode_read_logs(Tempdir) ->
get_default_shell() ->
try
rtnode([{putline,""},
- {putline, "whereis(user_drv)."},
- {getline, "undefined"}],[]),
- old
+ {putline, "is_pid(whereis(user_drv))."},
+ {expect, "true\r\n"}], []),
+ new
catch _E:_R ->
?dbg({_E,_R}),
- new
+ old
end.
-atom2list(A) ->
- lists:flatten(io_lib:format("~s", [A])).
+printed_atom(A) ->
+ lists:flatten(io_lib:format("~w", [A])).
--
2.26.2