%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(os_SUITE). -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]). -export([space_in_cwd/1, quoting/1, cmd_unicode/1, space_in_name/1, bad_command/1, find_executable/1, unix_comment_in_command/1, deep_list_command/1, large_output_command/1, background_command/0, background_command/1, message_leak/1, close_stdin/0, close_stdin/1, max_size_command/1, perf_counter_api/1]). -include_lib("common_test/include/ct.hrl"). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,1}}]. all() -> [space_in_cwd, quoting, cmd_unicode, space_in_name, bad_command, find_executable, unix_comment_in_command, deep_list_command, large_output_command, background_command, message_leak, close_stdin, max_size_command, perf_counter_api]. groups() -> []. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. init_per_testcase(TC, Config) when TC =:= background_command; TC =:= close_stdin -> case os:type() of {win32, _} -> {skip,"Should not work on windows"}; _ -> Config end; init_per_testcase(_TC,Config) -> Config. end_per_testcase(_,_Config) -> ok. %% Test that executing a command in a current working directory %% with space in its name works. space_in_cwd(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), Dirname = filename:join(PrivDir, "cwd with space"), ok = file:make_dir(Dirname), ok = file:set_cwd(Dirname), %% Using `more' gives the almost the same result on both Unix and Windows. Cmd = case os:type() of {win32, _} -> "more"; {unix, _} -> "more ok; % Unix. "\r\n" -> ok; % Windows. Other -> ct:fail({unexpected, Other}) end, ct:sleep(5), [] = receive_all(), ok. %% Test that various ways of quoting arguments work. quoting(Config) when is_list(Config) -> DataDir = proplists:get_value(data_dir, Config), Echo = filename:join(DataDir, "my_echo"), comp("one", os:cmd(Echo ++ " one")), comp("one::two", os:cmd(Echo ++ " one two")), comp("one two", os:cmd(Echo ++ " \"one two\"")), comp("x::one two::y", os:cmd(Echo ++ " x \"one two\" y")), comp("x::one two", os:cmd(Echo ++ " x \"one two\"")), comp("one two::y", os:cmd(Echo ++ " \"one two\" y")), comp("x::::y", os:cmd(Echo ++ " x \"\" y")), ct:sleep(5), [] = receive_all(), ok. %% Test that unicode arguments work. cmd_unicode(Config) when is_list(Config) -> DataDir = proplists:get_value(data_dir, Config), Echo = filename:join(DataDir, "my_echo"), comp("one", os:cmd(Echo ++ " one")), comp("one::two", os:cmd(Echo ++ " one two")), comp("åäö::ϼΩ", os:cmd(Echo ++ " åäö " ++ [1020, 937])), ct:sleep(5), [] = receive_all(), ok. %% Test that program with a space in its name can be executed. space_in_name(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), DataDir = proplists:get_value(data_dir, Config), Spacedir = filename:join(PrivDir, "program files"), Ext = case os:type() of {win32,_} -> ".exe"; _ -> "" end, OrigEcho = filename:join(DataDir, "my_echo" ++ Ext), Echo0 = filename:join(Spacedir, "my_echo" ++ Ext), %% Copy the `my_echo' program to a directory whose name contains a space. ok = file:make_dir(Spacedir), {ok, Bin} = file:read_file(OrigEcho), ok = file:write_file(Echo0, Bin), Echo = filename:nativename(Echo0), ok = file:change_mode(Echo, 8#777), % Make it executable on Unix. %% Run the echo program. %% Quoting on windows depends on if the full path of the executable %% contains special characters. Paths when running common_tests always %% include @, why Windows would always fail if we do not double the %% quotes (this is the behaviour of cmd.exe, not Erlang's idea). Quote = case os:type() of {win32,_} -> case (Echo -- "&<>()@^|") =:= Echo of true -> "\""; false -> "\"\"" end; _ -> "\"" end, comp("", os:cmd(Quote ++ Echo ++ Quote)), comp("a::b::c", os:cmd(Quote ++ Echo ++ Quote ++ " a b c")), ct:sleep(5), [] = receive_all(), ok. %% Check that a bad command doesn't crasch the server or the emulator (it used to). bad_command(Config) when is_list(Config) -> catch os:cmd([a|b]), catch os:cmd({bad, thing}), %% This should at least not crash (on Unix it typically returns %% a message from the shell). os:cmd("xxxxx"), ok. find_executable(Config) when is_list(Config) -> case os:type() of {win32, _} -> DataDir = filename:join(proplists:get_value(data_dir, Config), "win32"), ok = file:set_cwd(filename:join([DataDir, "current"])), Bin = filename:join(DataDir, "bin"), Abin = filename:join(DataDir, "abin"), UsrBin = filename:join([DataDir, "usr", "bin"]), {ok, Current} = file:get_cwd(), Path = lists:concat([Bin, ";", Abin, ";", UsrBin]), io:format("Path = ~s", [Path]), %% Search for programs in Bin (second element in PATH). find_exe(Abin, "my_ar", ".exe", Path), find_exe(Abin, "my_ascii", ".com", Path), find_exe(Abin, "my_adb", ".bat", Path), %% OTP-3626 find names of executables given with extension find_exe(Abin, "my_ar.exe", "", Path), find_exe(Abin, "my_ascii.com", "", Path), find_exe(Abin, "my_adb.bat", "", Path), find_exe(Abin, "my_ar.EXE", "", Path), find_exe(Abin, "my_ascii.COM", "", Path), find_exe(Abin, "MY_ADB.BAT", "", Path), %% Search for programs in Abin (second element in PATH). find_exe(Abin, "my_ar", ".exe", Path), find_exe(Abin, "my_ascii", ".com", Path), find_exe(Abin, "my_adb", ".bat", Path), %% Search for programs in the current working directory. find_exe(Current, "my_program", ".exe", Path), find_exe(Current, "my_command", ".com", Path), find_exe(Current, "my_batch", ".bat", Path), ok; {unix, _} -> DataDir = proplists:get_value(data_dir, Config), %% Smoke test. case lib:progname() of erl -> ErlPath = os:find_executable("erl"), true = is_list(ErlPath), true = filelib:is_regular(ErlPath); _ -> %% Don't bother -- the progname could include options. ok end, %% Never return a directory name. false = os:find_executable("unix", [DataDir]), ok end. find_exe(Where, Name, Ext, Path) -> Expected = filename:join(Where, Name++Ext), case os:find_executable(Name, Path) of Expected -> ok; Name when is_list(Name) -> case filename:absname(Name) of Expected -> ok; Other -> io:format("Expected ~p; got (converted to absolute) ~p", [Expected, Other]), ct:fail(failed) end; Other -> io:format("Expected ~p; got ~p", [Expected, Other]), ct:fail(failed) end. %% OTP-1805: Test that os:cmd(\ls #\) works correctly (used to hang). unix_comment_in_command(Config) when is_list(Config) -> Priv = proplists:get_value(priv_dir, Config), ok = file:set_cwd(Priv), _ = os:cmd("ls #"), % Any result is ok. ct:sleep(5), [] = receive_all(), ok. %% Check that a deep list in command works equally on unix and on windows. deep_list_command(Config) when is_list(Config) -> %% As a 'io_lib' module description says: "There is no guarantee that the %% character lists returned from some of the functions are flat, they can %% be deep lists." %% That's why os:cmd/1 can have arguments that are deep lists. %% It is not a problem for unix, but for windows it is (in R15B02 for ex.). Echo = os:cmd([$e, $c, "ho"]), true = erlang:is_list(Echo), %% FYI: [$e, $c, "ho"] =:= io_lib:format("ec~s", ["ho"]) ok. %% Test to make sure that the correct data is %% received when doing large commands. large_output_command(Config) when is_list(Config) -> %% Maximum allowed on windows is 8192, so we test well below that AAA = lists:duplicate(7000, $a), comp(AAA,os:cmd("echo " ++ AAA)). %% Test that it is possible on unix to start a background task using os:cmd. background_command() -> [{timetrap, {seconds, 5}}]. background_command(_Config) -> %% This testcase fails when the os:cmd takes %% longer then the 5 second timeout os:cmd("sleep 10&"). %% Test that message does not leak to the calling process message_leak(_Config) -> process_flag(trap_exit, true), os:cmd("echo hello"), [] = receive_all(), case os:type() of {unix, _} -> os:cmd("for i in $(seq 1 100); do echo hello; done&"), [] = receive_all(); _ -> ok % Cannot background on non-unix end, process_flag(trap_exit, false). %% Test that os:cmd closes stdin of the program that is executed close_stdin() -> [{timetrap, {seconds, 5}}]. close_stdin(Config) -> DataDir = proplists:get_value(data_dir, Config), Fds = filename:join(DataDir, "my_fds"), "-1" = os:cmd(Fds). max_size_command(_Config) -> Res20 = os:cmd("cat /dev/zero", #{ max_size => 20 }), 20 = length(Res20), Res0 = os:cmd("cat /dev/zero", #{ max_size => 0 }), 0 = length(Res0), Res32768 = os:cmd("cat /dev/zero", #{ max_size => 32768 }), 32768 = length(Res32768), ResHello = string_trim(os:cmd("echo hello", #{ max_size => 20 })), 5 = length(ResHello). string_trim(S) -> lists:reverse(string_trim_left(lists:reverse(string_trim_left(S)))). string_trim_left([C | T]) when C =:= $\s; C =:= $\n; C =:= $\t; C =:= $\r -> string_trim_left(T); string_trim_left(S) -> S. %% Test that the os:perf_counter api works as expected perf_counter_api(_Config) -> true = is_integer(os:perf_counter()), true = os:perf_counter() > 0, T1 = os:perf_counter(), timer:sleep(100), T2 = os:perf_counter(), TsDiff = erlang:convert_time_unit(T2 - T1, perf_counter, nano_seconds), ct:pal("T1: ~p~n" "T2: ~p~n" "TsDiff: ~p~n", [T1,T2,TsDiff]), %% We allow a 15% diff true = TsDiff < 115000000, true = TsDiff > 85000000, T1Ms = os:perf_counter(1000), timer:sleep(100), T2Ms = os:perf_counter(1000), MsDiff = T2Ms - T1Ms, ct:pal("T1Ms: ~p~n" "T2Ms: ~p~n" "MsDiff: ~p~n", [T1Ms,T2Ms,MsDiff]), %% We allow a 15% diff true = MsDiff < 115, true = MsDiff > 85. %% Util functions comp(Expected, Got) -> case strip_nl(Got) of Expected -> ok; Other -> ok = io:format("Expected: ~ts\n", [Expected]), ok = io:format("Got: ~ts\n", [Other]), ct:fail(failed) end. %% Like lib:nonl/1, but strips \r as well as \n. strip_nl([$\r, $\n]) -> []; strip_nl([$\n]) -> []; strip_nl([H|T]) -> [H|strip_nl(T)]; strip_nl([]) -> []. receive_all() -> receive X -> [X|receive_all()] after 0 -> [] end.