aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/test/interactive_shell_SUITE.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/kernel/test/interactive_shell_SUITE.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/kernel/test/interactive_shell_SUITE.erl')
-rw-r--r--lib/kernel/test/interactive_shell_SUITE.erl616
1 files changed, 616 insertions, 0 deletions
diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl
new file mode 100644
index 0000000000..c0db292ba5
--- /dev/null
+++ b/lib/kernel/test/interactive_shell_SUITE.erl
@@ -0,0 +1,616 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(interactive_shell_SUITE).
+-include("test_server.hrl").
+-export([all/1, get_columns_and_rows/1, exit_initial/1, job_control_local/1,
+ job_control_remote/1,
+ job_control_remote_noshell/1]).
+
+-export([init_per_testcase/2, end_per_testcase/2]).
+%% For spawn
+-export([toerl_server/3]).
+
+init_per_testcase(_Func, Config) ->
+ Dog = test_server:timetrap(test_server:seconds(60)),
+ Term = case os:getenv("TERM") of
+ List when is_list(List) ->
+ List;
+ _ ->
+ "dumb"
+ end,
+ os:putenv("TERM","vt100"),
+ [{watchdog,Dog},{term,Term}|Config].
+
+end_per_testcase(_Func, Config) ->
+ Dog = ?config(watchdog, Config),
+ Term = ?config(term,Config),
+ os:putenv("TERM",Term),
+ test_server:timetrap_cancel(Dog).
+
+
+all(suite) ->
+ [get_columns_and_rows, exit_initial, job_control_local,
+ job_control_remote, job_control_remote_noshell].
+
+%-define(DEBUG,1).
+-ifdef(DEBUG).
+-define(dbg(Data),erlang:display(Data)).
+-else.
+-define(dbg(Data),noop).
+-endif.
+
+get_columns_and_rows(suite) -> [];
+get_columns_and_rows(doc) -> ["Test that the shell can access columns and rows"];
+get_columns_and_rows(Config) when is_list(Config) ->
+ ?line 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}"}
+ ],[]),
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline, "2"},
+ {putline,"io:columns()."},
+ {getline,"{ok,90}"},
+ {putline,"io:rows()."},
+ {getline,"{ok,40}"}],
+ [],
+ "stty rows 40; stty columns 90; ").
+
+
+
+exit_initial(suite) -> [];
+exit_initial(doc) -> ["Tests that exit of initial shell restarts shell"];
+exit_initial(Config) when is_list(Config) ->
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline, "2"},
+ {putline,"exit()."},
+ {getline,""},
+ {getline,"Eshell"},
+ {putline,""},
+ {putline,"35."},
+ {getline,"35"}],[]).
+
+job_control_local(suite) -> [];
+job_control_local(doc) -> [ "Tests that local shell can be "
+ "started by means of job control" ];
+job_control_local(Config) when is_list(Config) ->
+ ?line rtnode([{putline,""},
+ {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"}],[]).
+
+job_control_remote(suite) -> [];
+job_control_remote(doc) -> [ "Tests that remote shell can be "
+ "started by means of job control" ];
+job_control_remote(Config) when is_list(Config) ->
+ case node() of
+ nonode@nohost ->
+ ?line exit(not_distributed);
+ _ ->
+ ?line RNode = create_nodename(),
+ ?line MyNode = atom_to_list(node()),
+ ?line Pid = spawn_link(fun() ->
+ receive die ->
+ ok
+ end
+ end),
+ ?line PidStr = pid_to_list(Pid),
+ ?line register(kalaskula,Pid),
+ ?line CookieString = lists:flatten(
+ io_lib:format("~w",
+ [erlang:get_cookie()])),
+ ?line 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),
+ ?line Pid ! die,
+ ?line Res
+ end.
+job_control_remote_noshell(suite) -> [];
+job_control_remote_noshell(doc) ->
+ [ "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() of
+ nonode@nohost ->
+ ?line exit(not_distributed);
+ _ ->
+ ?line RNode = create_nodename(),
+ ?line NSNode = start_noshell_node(interactive_shell_noshell),
+ ?line Pid = spawn_link(NSNode, fun() ->
+ receive die ->
+ ok
+ end
+ end),
+ ?line PidStr = rpc:call(NSNode,erlang,pid_to_list,[Pid]),
+ ?line true = rpc:call(NSNode,erlang,register,[kalaskula,Pid]),
+ ?line NSNodeStr = atom_to_list(NSNode),
+ ?line CookieString = lists:flatten(
+ io_lib:format("~w",
+ [erlang:get_cookie()])),
+ ?line 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),
+ ?line Pid ! die,
+ ?line stop_noshell_node(NSNode),
+ ?line Res
+ end.
+
+rtnode(C,N) ->
+ rtnode(C,N,[]).
+rtnode(Commands,Nodename,ErlPrefix) ->
+ ?line case get_progs() of
+ {error,_Reason} ->
+ ?line {skip,"No runerl present"};
+ {RunErl,ToErl,Erl} ->
+ ?line case create_tempdir() of
+ {error, Reason2} ->
+ ?line {skip, Reason2};
+ Tempdir ->
+ ?line SPid =
+ start_runerl_node(RunErl,ErlPrefix++Erl,
+ Tempdir,Nodename),
+ ?line CPid = start_toerl_server(ToErl,Tempdir),
+ ?line erase(getline_skipped),
+ ?line Res =
+ (catch get_and_put(CPid, Commands,1)),
+ ?line case stop_runerl_node(CPid) of
+ {error,_} ->
+ ?line CPid2 =
+ start_toerl_server
+ (ToErl,Tempdir),
+ ?line erase(getline_skipped),
+ ?line ok = get_and_put
+ (CPid2,
+ [{putline,[7]},
+ {sleep,
+ timeout(short)},
+ {putline,""},
+ {getline," -->"},
+ {putline,"s"},
+ {putline,"c"},
+ {putline,""}],1),
+ ?line stop_runerl_node(CPid2);
+ _ ->
+ ?line ok
+ end,
+ ?line wait_for_runerl_server(SPid),
+ ?line ok = rm_rf(Tempdir),
+ ?line ok = Res
+ end
+ end.
+
+timeout(long) ->
+ 2 * timeout(normal);
+timeout(short) ->
+ timeout(normal) div 10;
+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++" "}]),
+ Node.
+stop_noshell_node(Node) ->
+ test_server:stop_node(Node).
+
+
+rm_rf(Dir) ->
+ try
+ {ok,List} = file:list_dir(Dir),
+ Files = [filename:join([Dir,X]) || X <- List],
+ [case file:list_dir(Y) of
+ {error, enotdir} ->
+ ok = file:delete(Y);
+ _ ->
+ ok = rm_rf(Y)
+ end || Y <- Files],
+ ok = file:del_dir(Dir),
+ ok
+ catch
+ _:Exception -> {error, {Exception,Dir}}
+ end.
+
+
+get_and_put(_CPid,[],_) ->
+ ok;
+get_and_put(CPid, [{sleep, X}|T],N) ->
+ ?dbg({sleep, X}),
+ receive
+ after X ->
+ get_and_put(CPid,T,N+1)
+ 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;
+
+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}
+ end;
+
+get_and_put(CPid, [{putline, Line}|T],N) ->
+ ?dbg({putline, Line}),
+ CPid ! {self(), {send_line, Line}},
+ Timeout = timeout(normal),
+ receive
+ {send_line, ok} ->
+ get_and_put(CPid, [{getline, []}|T],N)
+ 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.
+
+wait_for_runerl_server(SPid) ->
+ Ref = erlang:monitor(process, SPid),
+ Timeout = timeout(long),
+ receive
+ {'DOWN', Ref, process, SPid, _} ->
+ ok
+ after Timeout ->
+ {error, timeout}
+ end.
+
+
+
+stop_runerl_node(CPid) ->
+ Ref = erlang:monitor(process, CPid),
+ CPid ! {self(), kill_emulator},
+ Timeout = timeout(long),
+ receive
+ {'DOWN', Ref, process, CPid, noproc} ->
+ ok;
+ {'DOWN', Ref, process, CPid, normal} ->
+ ok;
+ {'DOWN', Ref, process, CPid, {error, Reason}} ->
+ {error, Reason}
+ after Timeout ->
+ {error, timeout}
+ end.
+
+get_progs() ->
+ case os:type() of
+ {unix,freebsd} ->
+ {error,"cant use run_erl on freebsd"};
+ {unix,openbsd} ->
+ {error,"cant 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;
+ _ ->
+ {error, "Not a unix OS"}
+ end.
+
+create_tempdir() ->
+ create_tempdir(filename:join(["/tmp","rtnode"++os:getpid()]),$A).
+
+create_tempdir(Dir,X) when X > $Z, X < $a ->
+ create_tempdir(Dir,$a);
+create_tempdir(Dir,X) when X > $z ->
+ Estr = lists:flatten(
+ io_lib:format("Unable to create ~s, reason eexist",
+ [Dir++[$z]])),
+ {error, Estr};
+create_tempdir(Dir0, Ch) ->
+ % Expect fairly standard unix.
+ Dir = Dir0++[Ch],
+ case file:make_dir(Dir) of
+ {error, eexist} ->
+ create_tempdir(Dir0, Ch+1);
+ {error, Reason} ->
+ Estr = lists:flatten(
+ io_lib:format("Unable to create ~s, reason ~p",
+ [Dir,Reason])),
+ {error,Estr};
+ ok ->
+ Dir
+ end.
+
+create_nodename() ->
+ create_nodename($A).
+
+create_nodename(X) when X > $Z, X < $a ->
+ create_nodename($a);
+create_nodename(X) when X > $z ->
+ {error,out_of_nodenames};
+create_nodename(X) ->
+ NN = "rtnode"++os:getpid()++[X],
+ case file:read_file_info(filename:join(["/tmp",NN])) of
+ {error,enoent} ->
+ Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")),
+ NN++"@"++Host;
+ _ ->
+ create_nodename(X+1)
+ end.
+
+
+start_runerl_node(RunErl,Erl,Tempdir,Nodename) ->
+ XArg = case Nodename of
+ [] ->
+ [];
+ _ ->
+ " -sname "++(if is_atom(Nodename) -> atom_to_list(Nodename);
+ true -> Nodename
+ end)++
+ " -setcookie "++atom_to_list(erlang:get_cookie())
+ end,
+ spawn(fun() ->
+ os:cmd(RunErl++" "++Tempdir++"/ "++Tempdir++" \""++
+ Erl++XArg++"\"")
+ end).
+
+start_toerl_server(ToErl,Tempdir) ->
+ Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir]),
+ receive
+ {Pid,started} ->
+ Pid;
+ {Pid,error,Reason} ->
+ {error,Reason}
+ end.
+
+try_to_erl(_Command, 0) ->
+ {error, cannot_to_erl};
+try_to_erl(Command, N) ->
+ ?dbg({?LINE,N}),
+ Port = open_port({spawn, Command},[eof,{line,1000}]),
+ Timeout = timeout(normal) div 2,
+ receive
+ {Port, eof} ->
+ receive after Timeout ->
+ ok
+ end,
+ try_to_erl(Command, N-1)
+ after Timeout ->
+ ?dbg(Port),
+ Port
+ end.
+
+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};
+ {error,Other} ->
+ Parent ! {self(),error,Other},
+ exit(Other)
+ end,
+ case toerl_loop(Port,[]) of
+ normal ->
+ ok;
+ {error, Reason} ->
+ error_logger:error_msg("toerl_server exit with reason ~p~n",
+ [Reason]),
+ exit(Reason)
+ end.
+
+toerl_loop(Port,Acc) ->
+ ?dbg({toerl_loop, Port, Acc}),
+ receive
+ {Port,{data,{Tag0,Data}}} when is_port(Port) ->
+ ?dbg({?LINE,Port,{data,{Tag0,Data}}}),
+ case Acc of
+ [{noeol,Data0}|T0] ->
+ toerl_loop(Port,[{Tag0, Data0++Data}|T0]);
+ _ ->
+ toerl_loop(Port,[{Tag0,Data}|Acc])
+ end;
+ {Pid,{get_line,Timeout}} ->
+ case Acc of
+ [] ->
+ case get_data_within(Port,Timeout,[]) of
+ timeout ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,[]);
+ {noeol,Data1} ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,[{noeol,Data1}]);
+ {eol,Data2} ->
+ Pid ! {get_line, Data2},
+ toerl_loop(Port,[])
+ end;
+ [{noeol,Data3}] ->
+ case get_data_within(Port,Timeout,Data3) of
+ timeout ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,Acc);
+ {noeol,Data4} ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,[{noeol,Data4}]);
+ {eol,Data5} ->
+ Pid ! {get_line, Data5},
+ toerl_loop(Port,[])
+ end;
+ List ->
+ {NewAcc,[{eol,Data6}]} = lists:split(length(List)-1,List),
+ Pid ! {get_line,Data6},
+ toerl_loop(Port,NewAcc)
+ end;
+ {Pid, {send_line, Data7}} ->
+ Port ! {self(),{command, Data7++"\n"}},
+ Pid ! {send_line, ok},
+ toerl_loop(Port,Acc);
+ {_Pid, kill_emulator} ->
+ Port ! {self(),{command, "init:stop().\n"}},
+ Timeout1 = timeout(long),
+ receive
+ {Port,eof} ->
+ normal
+ after Timeout1 ->
+ {error, kill_timeout}
+ end;
+ {Port, eof} ->
+ {error, unexpected_eof};
+ Other ->
+ {error, {unexpected, Other}}
+ end.
+
+millistamp() ->
+ {Mega, Secs, Micros} = erlang:now(),
+ (Micros div 1000) + Secs * 1000 + Mega * 1000000000.
+
+get_data_within(Port, X, Acc) when X =< 0 ->
+ ?dbg({get_data_within, X, Acc, ?LINE}),
+ receive
+ {Port,{data,{Tag0,Data}}} ->
+ ?dbg({?LINE,Port,{data,{Tag0,Data}}}),
+ {Tag0, Acc++Data}
+ after 0 ->
+ case Acc of
+ [] ->
+ timeout;
+ Noeol ->
+ {noeol,Noeol}
+ end
+ end;
+
+
+get_data_within(Port, Timeout, Acc) ->
+ ?dbg({get_data_within, Timeout, Acc, ?LINE}),
+ T1 = millistamp(),
+ receive
+ {Port,{data,{noeol,Data}}} ->
+ ?dbg({?LINE,Port,{data,{noeol,Data}}}),
+ Elapsed = millistamp() - T1 + 1,
+ get_data_within(Port, Timeout - Elapsed, Acc ++ Data);
+ {Port,{data,{eol,Data1}}} ->
+ ?dbg({?LINE,Port,{data,{eol,Data1}}}),
+ {eol, Acc ++ Data1}
+ after Timeout ->
+ timeout
+ end.
+
+
+
+