Skip to content

Commit

Permalink
Merge pull request #75 from inaka/jfacorro.74.ktn_os.command
Browse files Browse the repository at this point in the history
[Closes #74] ktn_os:command/[1,2]
  • Loading branch information
Brujo Benavides committed Sep 2, 2015
2 parents 6312ff4 + bf0fb05 commit 6c066a6
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 3 deletions.
49 changes: 49 additions & 0 deletions src/ktn_os.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
%% @doc Utility functions to run commands in the underlying OS.
-module(ktn_os).

-export([command/1, command/2]).

-type opts() :: #{log_fun => fun((iodata()) -> any()), timeout => integer()}.
-type exit_status() :: integer().

-spec command(iodata()) -> {exit_status(), string()}.
command(Cmd) ->
Opts = #{log_fun => fun error_logger:info_msg/1},
command(Cmd, Opts).

-spec command(iodata(), opts()) -> {exit_status(), string()}.
command(Cmd, Opts) ->
PortOpts = [stream, exit_status, eof],
Port = open_port({spawn, shell_cmd()}, PortOpts),
erlang:port_command(Port, make_cmd(Cmd)),
get_data(Port, Opts, []).

-spec get_data(port(), opts(), [string()]) -> {exit_status(), string()}.
get_data(Port, Opts, Data) ->
%% Get timeout option or an hour if undefined.
Timeout = maps:get(timeout, Opts, 600000),
receive
{Port, {data, NewData}} ->
case maps:get(log_fun, Opts, undefined) of
Fun when is_function(Fun) -> Fun(NewData);
undefined -> ok
end,
get_data(Port, Opts, [NewData | Data]);
{Port, eof} ->
port_close(Port),
receive
{Port, {exit_status, ExitStatus}} ->
{ExitStatus, lists:flatten(lists:reverse(Data))}
end
after
Timeout -> throw(timeout)
end.

-spec make_cmd(string()) -> iodata().
make_cmd(Cmd) ->
%% We insert a new line after the command, in case the command
%% contains a comment character.
[$(, unicode:characters_to_binary(Cmd), "\n) </dev/null; exit\n"].

-spec shell_cmd() -> string().
shell_cmd() -> "sh -s unix:cmd 2>&1".
1 change: 1 addition & 0 deletions test/katana.coverspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ktn_lists,
ktn_maps,
ktn_numbers,
ktn_os,
ktn_random,
ktn_recipe,
ktn_recipe_verify,
Expand Down
19 changes: 16 additions & 3 deletions test/ktn_code_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

-export([
consult/1,
beam_to_string/1
beam_to_string/1,
parse_tree/1
]).

-define(EXCLUDED_FUNS,
Expand Down Expand Up @@ -57,5 +58,17 @@ consult(_Config) ->

-spec beam_to_string(config()) -> ok.
beam_to_string(_Config) ->
{error, beam_lib, _} = ktn_code:beam_to_string(bla),
{ok, _} = ktn_code:beam_to_string("../../ebin/ktn_code.beam").
{error, beam_lib, _} = ktn_code:beam_to_string(bla),
{ok, _} = ktn_code:beam_to_string("../../ebin/ktn_code.beam").

parse_tree(_Config) ->
ModuleNode = #{type => module,
attrs => #{location => {1, 2},
text => "module",
value => x}},

#{type := root,
content := _} = ktn_code:parse_tree("-module(x)."),

#{type := root,
content := [ModuleNode]} = ktn_code:parse_tree("-module(x).").
83 changes: 83 additions & 0 deletions test/ktn_os_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
-module(ktn_os_SUITE).

-export([all/0]).

-export([command/1]).

-define(EXCLUDED_FUNS,
[
module_info,
all,
test,
init_per_suite,
end_per_suite
]).

-type config() :: [{atom(), term()}].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Common test
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec all() -> [atom()].
all() ->
Exports = ?MODULE:module_info(exports),
[F || {F, _} <- Exports, not lists:member(F, ?EXCLUDED_FUNS)].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test Cases
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec command(config()) -> ok.
command(_Config) ->
Opts = #{log_fun => fun(_) -> ok end},

{0, "/\n"} = ktn_os:command("cd /; pwd", Opts),

{ok, Cwd} = file:get_cwd(),
Result = Cwd ++ "\n",
{0, Result} = ktn_os:command("pwd", Opts),

{1, _} = ktn_os:command("pwd; ls w4th3v3r", Opts),

Result2 = Result ++ "Hi\n",
{0, Result2} = ktn_os:command("pwd; echo Hi", #{}),

{0, "/\n"} = ktn_os:command("cd /; pwd"),

ok = try ktn_os:command("sleep 5", #{timeout => 1000})
catch _:timeout -> ok end,

Fun = fun() -> ktn_os:command("cd /; pwd") end,
FilterFun =
fun(Line) ->
case re:run(Line, "=INFO REPORT==== .* ===") of
nomatch -> false;
{match, _}-> true
end
end,
check_some_line_output(Fun, FilterFun),

ct:comment("Check result when process is killed"),
Self = self(),
YesFun = fun() ->
case ktn_os:command("yes > /dev/null") of
{ExitStatus, _} when ExitStatus =/= 0 -> Self ! ok;
_ -> Self ! error
end
end,
erlang:spawn_link(YesFun),
os:cmd("pkill yes"),
ok = receive X -> X after 1000 -> error end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Helper functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

check_some_line_output(Fun, FilterFun) ->
ct:capture_start(),
Fun(),
ct:capture_stop(),
Lines = ct:capture_get([]),
ListFun = fun(Line) -> FilterFun(Line) end,
[_ | _] = lists:filter(ListFun, Lines).

0 comments on commit 6c066a6

Please sign in to comment.