Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor group to use gen_statem #8765

Merged
1,465 changes: 754 additions & 711 deletions lib/kernel/src/group.erl

Large diffs are not rendered by default.

86 changes: 49 additions & 37 deletions lib/kernel/src/user_drv.erl
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ init_remote_shell(State, Node, {M, F, A}) ->
end,

Group = group:start(self(), RShell,
[{echo,State#state.shell_started =:= new}] ++
[{dumb, State#state.shell_started =/= new}] ++
group_opts(RemoteNode)),

Gr = gr_add_cur(State#state.groups, Group, RShell),
Expand All @@ -329,7 +329,7 @@ init_local_shell(State, InitialShell) ->

Gr = gr_add_cur(State#state.groups,
group:start(self(), InitialShell,
group_opts() ++ [{echo,State#state.shell_started =:= new}]),
group_opts() ++ [{dumb,State#state.shell_started =/= new}]),
InitialShell),

init_shell(State#state{ groups = Gr }, [Slogan,$\n]).
Expand All @@ -351,10 +351,7 @@ init_shell(State, Slogan) ->
start_user() ->
case whereis(user) of
undefined ->
User = group:start(self(), {}, [{echo,false},
{noshell,true}]),
register(user, User),
User;
group:start(self(), noshell, [{name, user}]);
User ->
User
end.
Expand Down Expand Up @@ -625,7 +622,7 @@ switch_loop(internal, line, State) ->
switch_loop(internal, {line, Line}, State) ->
case erl_scan:string(Line) of
{ok, Tokens, _} ->
case switch_cmd(Tokens, State#state.groups) of
case switch_cmd(Tokens, State#state.groups, State#state.shell_started =/= new) of
{ok, Groups} ->
Curr = gr_cur_pid(Groups),
put(current_group, Curr),
Expand Down Expand Up @@ -676,38 +673,48 @@ switch_loop(info, {Requester, get_terminal_state}, _State) ->
stdout => prim_tty:isatty(stdout),
stderr => prim_tty:isatty(stderr) } },
keep_state_and_data;
switch_loop(info, {Requester, tty_geometry}, {_Cont, #state{ tty = TTYState }}) ->
case prim_tty:window_size(TTYState) of
{ok, Geometry} ->
Requester ! {self(), tty_geometry, Geometry},
ok;
Error ->
Requester ! {self(), tty_geometry, Error},
ok
end,
keep_state_and_data;
switch_loop(timeout, _, {_Cont, State}) ->
{keep_state_and_data,
{next_event, info, {State#state.read,{data,[]}}}};
switch_loop(info, _Unknown, _State) ->
{keep_state_and_data, postpone}.

switch_cmd([{atom,_,Key},{Type,_,Value}], Gr)
switch_cmd([{atom,_,Key},{Type,_,Value}], Gr, Dumb)
when Type =:= atom; Type =:= integer ->
switch_cmd({Key, Value}, Gr);
switch_cmd([{atom,_,Key},{atom,_,V1},{atom,_,V2}], Gr) ->
switch_cmd({Key, V1, V2}, Gr);
switch_cmd([{atom,_,Key}], Gr) ->
switch_cmd(Key, Gr);
switch_cmd([{'?',_}], Gr) ->
switch_cmd(h, Gr);

switch_cmd(Cmd, Gr) when Cmd =:= c; Cmd =:= i; Cmd =:= k ->
switch_cmd({Cmd, gr_cur_index(Gr)}, Gr);
switch_cmd({c, I}, Gr0) ->
switch_cmd({Key, Value}, Gr, Dumb);
switch_cmd([{atom,_,Key},{atom,_,V1},{atom,_,V2}], Gr, Dumb) ->
switch_cmd({Key, V1, V2}, Gr, Dumb);
switch_cmd([{atom,_,Key}], Gr, Dumb) ->
switch_cmd(Key, Gr, Dumb);
switch_cmd([{'?',_}], Gr, Dumb) ->
switch_cmd(h, Gr, Dumb);

switch_cmd(Cmd, Gr, Dumb) when Cmd =:= c; Cmd =:= i; Cmd =:= k ->
switch_cmd({Cmd, gr_cur_index(Gr)}, Gr, Dumb);
switch_cmd({c, I}, Gr0, _Dumb) ->
case gr_set_cur(Gr0, I) of
{ok,Gr} -> {ok, Gr};
undefined -> unknown_group()
end;
switch_cmd({i, I}, Gr) ->
switch_cmd({i, I}, Gr, _Dumb) ->
case gr_get_num(Gr, I) of
{pid,Pid} ->
exit(Pid, interrupt),
{retry, []};
undefined ->
unknown_group()
end;
switch_cmd({k, I}, Gr) ->
switch_cmd({k, I}, Gr, _Dumb) ->
case gr_get_num(Gr, I) of
{pid,Pid} ->
exit(Pid, die),
Expand All @@ -724,15 +731,15 @@ switch_cmd({k, I}, Gr) ->
undefined ->
unknown_group()
end;
switch_cmd(j, Gr) ->
switch_cmd(j, Gr, _Dumb) ->
{retry, gr_list(Gr)};
switch_cmd({s, Shell}, Gr0) when is_atom(Shell) ->
Pid = group:start(self(), {Shell,start,[]}),
switch_cmd({s, Shell}, Gr0, Dumb) when is_atom(Shell) ->
Pid = group:start(self(), {Shell,start,[]}, [{dumb, Dumb} | group_opts()]),
Gr = gr_add_cur(Gr0, Pid, {Shell,start,[]}),
{retry, [], Gr};
switch_cmd(s, Gr) ->
switch_cmd({s, shell}, Gr);
switch_cmd(r, Gr0) ->
switch_cmd(s, Gr, Dumb) ->
switch_cmd({s, shell}, Gr, Dumb);
switch_cmd(r, Gr0, _Dumb) ->
case is_alive() of
true ->
Node = pool:get_node(),
Expand All @@ -742,30 +749,35 @@ switch_cmd(r, Gr0) ->
false ->
{retry, [{put_chars,unicode,<<"Node is not alive\n">>}]}
end;
switch_cmd({r, Node}, Gr) when is_atom(Node)->
switch_cmd({r, Node, shell}, Gr);
switch_cmd({r,Node,Shell}, Gr0) when is_atom(Node), is_atom(Shell) ->
switch_cmd({r, Node}, Gr, Dumb) when is_atom(Node)->
switch_cmd({r, Node, shell}, Gr, Dumb);
switch_cmd({r,Node,Shell}, Gr0, Dumb) when is_atom(Node), is_atom(Shell) ->
case is_alive() of
true ->
Pid = group:start(self(), {Node,Shell,start,[]}, group_opts(Node)),
Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}),
{retry, [], Gr};
case net_kernel:connect_node(Node) of
true ->
Pid = group:start(self(), {Node,Shell,start,[]}, [{dumb, Dumb} | group_opts(Node)]),
Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}),
{retry, [], Gr};
false ->
{retry, [{put_chars,unicode,<<"Could not connect to node\n">>}]}
end;
false ->
{retry, [{put_chars,unicode,"Node is not alive\n"}]}
end;

switch_cmd(q, _Gr) ->
switch_cmd(q, _Gr, _Dumb) ->
case erlang:system_info(break_ignored) of
true -> % noop
{retry, [{put_chars,unicode,<<"Unknown command\n">>}]};
false ->
halt()
end;
switch_cmd(h, _Gr) ->
switch_cmd(h, _Gr, _Dumb) ->
{retry, list_commands()};
switch_cmd([], _Gr) ->
switch_cmd([], _Gr, _Dumb) ->
{retry,[]};
switch_cmd(_Ts, _Gr) ->
switch_cmd(_Ts, _Gr, _Dumb) ->
{retry, [{put_chars,unicode,<<"Unknown command\n">>}]}.

unknown_group() ->
Expand Down
50 changes: 26 additions & 24 deletions lib/kernel/test/interactive_shell_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
shell_update_window_unicode_wrap/1,
shell_receive_standard_out/1,
shell_standard_error_nlcr/1, shell_clear/1,
shell_format/1,
shell_format/1, shell_help/1,
remsh_basic/1, remsh_error/1, remsh_longnames/1, remsh_no_epmd/1,
remsh_expand_compatibility_25/1, remsh_expand_compatibility_later_version/1,
external_editor/1, external_editor_visual/1,
Expand Down Expand Up @@ -117,7 +117,8 @@ groups() ->
shell_full_queue,
external_editor,
external_editor_visual,
shell_ignore_pager_commands
shell_ignore_pager_commands,
shell_help
]},
{tty_unicode,[parallel],
[{group,tty_tests},
Expand Down Expand Up @@ -1094,7 +1095,7 @@ shell_support_ansi_input(Config) ->
ClearText = "\e[0m",

try
send_stdin(Term,["{",BoldText,"a😀b",ClearText,"}"]),
send_tty(Term,["{",BoldText,"a😀b",ClearText,"}"]),
timer:sleep(1000),
try check_location(Term, {0, width("{1ma😀bm}")}) of
_ ->
Expand Down Expand Up @@ -1149,15 +1150,15 @@ shell_expand_location_below(Config) ->
Cols = 80,

%% First check that basic completion works
send_stdin(Term, "escript:"),
send_stdin(Term, "\t"),
send_tty(Term, "escript:"),
send_tty(Term, "\t"),
%% Cursor at correct place
check_location(Term, {-3, width("escript:")}),
%% Nothing after the start( completion
check_content(Term, "start\\($"),

%% Check that completion is cleared when we type
send_stdin(Term, "s"),
send_tty(Term, "s"),
check_location(Term, {-3, width("escript:s")}),
check_content(Term, "escript:s$"),

Expand All @@ -1167,19 +1168,19 @@ shell_expand_location_below(Config) ->
send_tty(Term, "End"),
send_tty(Term, ", test_after]"),
[send_tty(Term, "Left") || _ <- ", test_after]"],
send_stdin(Term, "\t"),
send_tty(Term, "\t"),
check_location(Term, {-3, width("[escript:s")}),
check_content(Term, "script_name\\([ ]+start\\($"),
send_tty(Term, "C-K"),

%% Check that completion works when in the middle of a long term
send_tty(Term, ", "++ lists:duplicate(80*2, $a)++"]"),
[send_tty(Term, "Left") || _ <- ", "++ lists:duplicate(80*2, $a)++"]"],
send_stdin(Term, "\t"),
send_tty(Term, "\t"),
check_location(Term, {-4, width("[escript:s")}),
check_content(Term, "script_name\\([ ]+start\\($"),
send_tty(Term, "End"),
send_stdin(Term, ".\n"),
send_tty(Term, ".\n"),

%% Check that we behave as we should with very long completions
rpc(Term, fun() ->
Expand All @@ -1190,14 +1191,14 @@ shell_expand_location_below(Config) ->
timer:sleep(1000), %% Sleep to make sure window has resized
Result = 61,
Rows1 = 48,
send_stdin(Term, "long_module:" ++ FunctionName),
send_stdin(Term, "\t"),
send_tty(Term, "long_module:" ++ FunctionName),
send_tty(Term, "\t"),
check_content(Term, "3> long_module:" ++ FunctionName ++ "\nfunctions(\n|.)*a_long_function_name0\\("),

%% Check that correct text is printed below expansion
check_content(Term, io_lib:format("rows ~w to ~w of ~w",
[1, 7, Result])),
send_stdin(Term, "\t"),
send_tty(Term, "\t"),
check_content(Term, io_lib:format("rows ~w to ~w of ~w",
[1, Rows1, Result])),
send_tty(Term, "Down"),
Expand Down Expand Up @@ -1273,13 +1274,13 @@ shell_expand_location_above(Config) ->

try
tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
send_stdin(Term, "escript:"),
send_stdin(Term, "\t"),
send_tty(Term, "escript:"),
send_tty(Term, "\t"),
check_location(Term, {0, width("escript:")}),
check_content(Term, "start\\(\n"),
check_content(Term, "escript:$"),
send_stdin(Term, "s"),
send_stdin(Term, "\t"),
send_tty(Term, "s"),
send_tty(Term, "\t"),
check_location(Term, {0, width("escript:s")}),
check_content(Term, "\nscript_name\\([ ]+start\\(\n"),
check_content(Term, "escript:s$"),
Expand All @@ -1292,12 +1293,13 @@ shell_expand_location_above(Config) ->
shell_help(Config) ->
Term = start_tty(Config),
try
send_stdin(Term, "lists"),
send_stdin(Term, "\^[h"),
send_tty(Term, "application:put_env(kernel, shell_docs_ansi, false).\n"),
send_tty(Term, "lists"),
send_tty(Term, "\^[h"),
check_content(Term, "List processing functions."),
send_stdin(Term, ":all"),
send_stdin(Term, "\^[h"),
check_content(Term, "-spec all(Pred, List) -> boolean()"),
send_tty(Term, ":all"),
send_tty(Term, "\^[h"),
check_content(Term, ~S"all\(Pred, List\)"),
ok
after
stop_tty(Term),
Expand Down Expand Up @@ -1337,7 +1339,7 @@ shell_get_password(_Config) ->
rtnode:run(
[{putline,"io:get_password()."},
{putline,"secret\r"},
{expect, "\r\n\r\n\"secret\""}]),
{expect, "\r\n\"secret\""}]),

%% io:get_password only works when run in "newshell"
rtnode:run(
Expand Down Expand Up @@ -1548,7 +1550,7 @@ shell_suspend(Config) ->
%% We test that suspending of `erl` and then resuming restores the shell
shell_full_queue(Config) ->

[throw({skip,"Need unbuffered to run"}) || os:find_executable("unbuffered") =:= false],
[throw({skip,"Need unbuffer (apt install expect) to run"}) || os:find_executable("unbuffer") =:= false],

%% In order to fill the read buffer of the terminal we need to get a
%% bit creative. We first need to start erl in bash in order to be
Expand Down Expand Up @@ -1615,7 +1617,7 @@ shell_full_queue(Config) ->
send_tty(Term, "fg"),
send_tty(Term, "Enter"),
Pid ! stop,
check_content(Term,"b$"),
check_content(Term,"b\\([^)]*\\)2>$"),

send_tty(Term, "."),
send_tty(Term, "Enter"),
Expand Down
7 changes: 4 additions & 3 deletions lib/ssh/src/ssh_cli.erl
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,8 @@ start_shell(ConnectionHandler, State) ->
Shell
end,
State#state{group = group:start(self(), ShellSpawner,
[{dumb, get_dumb(State#state.pty)},{expand_below, false},
[{dumb, get_dumb(State#state.pty)},
{expand_below, false},
{echo, get_echo(State#state.pty)}]),
buf = empty_buf()}.

Expand All @@ -763,7 +764,7 @@ start_exec_shell(ConnectionHandler, Cmd, State) ->
{M, F, A++[Cmd]}
end,
State#state{group = group:start(self(), ExecShellSpawner, [{expand_below, false},
{echo,false}]),
{dumb, true}]),
buf = empty_buf()}.

%%--------------------------------------------------------------------
Expand Down Expand Up @@ -848,7 +849,7 @@ exec_in_self_group(ConnectionHandler, ChannelId, WantReply, State, Fun) ->
end)
end,
{ok, State#state{group = group:start(self(), Exec, [{expand_below, false},
{echo,false}]),
{dumb, true}]),
buf = empty_buf()}}.


Expand Down
2 changes: 0 additions & 2 deletions lib/ssh/src/ssh_client_channel.erl
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,6 @@ enter_loop(State) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
-doc """
init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}

The following options must be present:

- **`{channel_cb, atom()}`** - The module that implements the channel behaviour.
Expand Down
6 changes: 3 additions & 3 deletions lib/ssh/test/ssh_connection_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ start_shell_exec(Config) when is_list(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
{exec, {?MODULE,ssh_exec_echo,[]}} ]),
{exec, {?MODULE,ssh_exec_echo,["foo"]}} ]),

ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
Expand Down Expand Up @@ -1153,8 +1153,8 @@ start_exec_direct_fun1_read_write_advanced(Config) ->
after 5000 -> go_on
end,
receive
X -> ct:fail("remaining messages"),
ct:log("remaining message: ~p",[X])
X -> ct:log("remaining message: ~p",[X]),
ct:fail("remaining messages")
after 0 -> go_on
end,
ssh:stop_daemon(Pid).
Expand Down
Loading
Loading