diff options
Diffstat (limited to 'lib/kernel/test/os_SUITE.erl')
-rw-r--r-- | lib/kernel/test/os_SUITE.erl | 390 |
1 files changed, 228 insertions, 162 deletions
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl index 29d8d10262..710b9b115c 100644 --- a/lib/kernel/test/os_SUITE.erl +++ b/lib/kernel/test/os_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2013. All Rights Reserved. +%% Copyright Ericsson AB 1997-2018. 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. @@ -20,17 +20,27 @@ -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]). --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, evil/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, + null_in_command/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("test_server/include/test_server.hrl"). +-include_lib("common_test/include/ct.hrl"). -suite() -> [{ct_hooks,[ts_install_cth]}]. +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, evil]. + [space_in_cwd, quoting, cmd_unicode, null_in_command, + 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() -> []. @@ -47,16 +57,27 @@ init_per_group(_GroupName, 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. -space_in_cwd(doc) -> - "Test that executing a command in a current working directory " - "with space in its name works."; -space_in_cwd(suite) -> []; +%% Test that executing a command in a current working directory +%% with space in its name works. space_in_cwd(Config) when is_list(Config) -> - ?line PrivDir = ?config(priv_dir, Config), - ?line Dirname = filename:join(PrivDir, "cwd with space"), - ?line ok = file:make_dir(Dirname), - ?line ok = file:set_cwd(Dirname), + 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. @@ -67,70 +88,74 @@ space_in_cwd(Config) when is_list(Config) -> "more </dev/null" end, - ?line case os:cmd(Cmd) of - [] -> ok; % Unix. - "\r\n" -> ok; % Windows. - Other -> - ?line test_server:fail({unexpected, Other}) - end, + case os:cmd(Cmd) of + [] -> ok; % Unix. + "\r\n" -> ok; % Windows. + Other -> + ct:fail({unexpected, Other}) + end, - ?t:sleep(5), - ?line [] = receive_all(), + ct:sleep(5), + [] = receive_all(), ok. -quoting(doc) -> "Test that various ways of quoting arguments work."; -quoting(suite) -> []; +%% Test that various ways of quoting arguments work. quoting(Config) when is_list(Config) -> - ?line DataDir = ?config(data_dir, Config), - ?line Echo = filename:join(DataDir, "my_echo"), - - ?line comp("one", os:cmd(Echo ++ " one")), - ?line comp("one::two", os:cmd(Echo ++ " one two")), - ?line comp("one two", os:cmd(Echo ++ " \"one two\"")), - ?line comp("x::one two::y", os:cmd(Echo ++ " x \"one two\" y")), - ?line comp("x::one two", os:cmd(Echo ++ " x \"one two\"")), - ?line comp("one two::y", os:cmd(Echo ++ " \"one two\" y")), - ?line comp("x::::y", os:cmd(Echo ++ " x \"\" y")), - ?t:sleep(5), - ?line [] = receive_all(), + 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. -cmd_unicode(doc) -> "Test that unicode arguments work."; -cmd_unicode(suite) -> []; +%% Test that unicode arguments work. cmd_unicode(Config) when is_list(Config) -> - ?line DataDir = ?config(data_dir, Config), - ?line Echo = filename:join(DataDir, "my_echo"), - - ?line comp("one", os:cmd(Echo ++ " one")), - ?line comp("one::two", os:cmd(Echo ++ " one two")), - ?line comp("åäö::ϼΩ", os:cmd(Echo ++ " åäö " ++ [1020, 937])), - ?t:sleep(5), - ?line [] = receive_all(), + 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. +null_in_command(Config) -> + {Ok, Error} = case os:type() of + {win32,_} -> {"dir", "di\0r"}; + _ -> {"ls", "l\0s"} + end, + true = is_list(try os:cmd(Ok) catch Class0:_ -> Class0 end), + error = try os:cmd(Error) catch Class1:_ -> Class1 end, + ok. -space_in_name(doc) -> - "Test that program with a space in its name can be executed."; -space_in_name(suite) -> []; +%% Test that program with a space in its name can be executed. space_in_name(Config) when is_list(Config) -> - ?line PrivDir = ?config(priv_dir, Config), - ?line DataDir = ?config(data_dir, Config), - ?line Spacedir = filename:join(PrivDir, "program files"), + 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, - ?line OrigEcho = filename:join(DataDir, "my_echo" ++ Ext), - ?line Echo0 = filename:join(Spacedir, "my_echo" ++ Ext), + 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. - ?line ok = file:make_dir(Spacedir), - ?line {ok, Bin} = file:read_file(OrigEcho), - ?line ok = file:write_file(Echo0, Bin), - ?line Echo = filename:nativename(Echo0), - ?line ok = file:change_mode(Echo, 8#777), % Make it executable on Unix. + 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 @@ -146,78 +171,74 @@ space_in_name(Config) when is_list(Config) -> _ -> "\"" end, - ?line comp("", os:cmd(Quote ++ Echo ++ Quote)), - ?line comp("a::b::c", os:cmd(Quote ++ Echo ++ Quote ++ " a b c")), - ?t:sleep(5), - ?line [] = receive_all(), + comp("", os:cmd(Quote ++ Echo ++ Quote)), + comp("a::b::c", os:cmd(Quote ++ Echo ++ Quote ++ " a b c")), + ct:sleep(5), + [] = receive_all(), ok. -bad_command(doc) -> - "Check that a bad command doesn't crasch the server or the emulator (it used to)."; -bad_command(suite) -> []; +%% Check that a bad command doesn't crasch the server or the emulator (it used to). bad_command(Config) when is_list(Config) -> - ?line catch os:cmd([a|b]), - ?line catch os:cmd({bad, thing}), + 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). - ?line os:cmd("xxxxx"), + os:cmd("xxxxx"), ok. -find_executable(suite) -> []; -find_executable(doc) -> []; find_executable(Config) when is_list(Config) -> case os:type() of {win32, _} -> - ?line DataDir = filename:join(?config(data_dir, Config), "win32"), - ?line ok = file:set_cwd(filename:join([DataDir, "current"])), - ?line Bin = filename:join(DataDir, "bin"), - ?line Abin = filename:join(DataDir, "abin"), - ?line UsrBin = filename:join([DataDir, "usr", "bin"]), - ?line {ok, Current} = file:get_cwd(), + 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(), - ?line Path = lists:concat([Bin, ";", Abin, ";", UsrBin]), - ?line io:format("Path = ~s", [Path]), + Path = lists:concat([Bin, ";", Abin, ";", UsrBin]), + io:format("Path = ~s", [Path]), %% Search for programs in Bin (second element in PATH). - ?line find_exe(Abin, "my_ar", ".exe", Path), - ?line find_exe(Abin, "my_ascii", ".com", Path), - ?line 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), %% OTP-3626 find names of executables given with extension - ?line find_exe(Abin, "my_ar.exe", "", Path), - ?line find_exe(Abin, "my_ascii.com", "", Path), - ?line find_exe(Abin, "my_adb.bat", "", Path), - ?line find_exe(Abin, "my_ar.EXE", "", Path), - ?line find_exe(Abin, "my_ascii.COM", "", Path), - ?line 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), + 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). - ?line find_exe(Abin, "my_ar", ".exe", Path), - ?line find_exe(Abin, "my_ascii", ".com", Path), - ?line 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 the current working directory. - ?line find_exe(Current, "my_program", ".exe", Path), - ?line find_exe(Current, "my_command", ".com", Path), - ?line find_exe(Current, "my_batch", ".bat", Path), + find_exe(Current, "my_program", ".exe", Path), + find_exe(Current, "my_command", ".com", Path), + find_exe(Current, "my_batch", ".bat", Path), ok; {unix, _} -> - DataDir = ?config(data_dir, Config), + DataDir = proplists:get_value(data_dir, Config), %% Smoke test. - case lib:progname() of - erl -> - ?line ErlPath = os:find_executable("erl"), - ?line true = is_list(ErlPath), - ?line true = filelib:is_regular(ErlPath); + case ct:get_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. - ?line false = os:find_executable("unix", [DataDir]), + false = os:find_executable("unix", [DataDir]), ok end. @@ -233,29 +254,23 @@ find_exe(Where, Name, Ext, Path) -> Other -> io:format("Expected ~p; got (converted to absolute) ~p", [Expected, Other]), - test_server:fail() + ct:fail(failed) end; Other -> io:format("Expected ~p; got ~p", [Expected, Other]), - test_server:fail() + ct:fail(failed) end. -unix_comment_in_command(doc) -> - "OTP-1805: Test that os:cmd(\"ls #\") works correctly (used to hang)."; -unix_comment_in_command(suite) -> []; +%% OTP-1805: Test that os:cmd(\ls #\) works correctly (used to hang). unix_comment_in_command(Config) when is_list(Config) -> - ?line Dog = test_server:timetrap(test_server:seconds(20)), - ?line Priv = ?config(priv_dir, Config), - ?line ok = file:set_cwd(Priv), - ?line _ = os:cmd("ls #"), % Any result is ok. - ?t:sleep(5), - ?line [] = receive_all(), - ?line test_server:timetrap_cancel(Dog), + 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. -deep_list_command(doc) -> - "Check that a deep list in command works equally on unix and on windows."; -deep_list_command(suite) -> []; +%% 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 @@ -267,50 +282,101 @@ deep_list_command(Config) when is_list(Config) -> %% 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)). --define(EVIL_PROCS, 100). --define(EVIL_LOOPS, 100). --define(PORT_CREATOR, os_cmd_port_creator). -evil(Config) when is_list(Config) -> - Dog = test_server:timetrap(test_server:minutes(5)), - Parent = self(), - Ps = lists:map(fun (N) -> - spawn_link(fun () -> - evil_loop(Parent, ?EVIL_LOOPS,N) - end) - end, lists:seq(1, ?EVIL_PROCS)), - Devil = spawn_link(fun () -> devil(hd(Ps), hd(lists:reverse(Ps))) end), - lists:foreach(fun (P) -> receive {P, done} -> ok end end, Ps), - unlink(Devil), - exit(Devil, kill), - test_server:timetrap_cancel(Dog), - ok. +%% 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). + +%% 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, + + Conv = fun(T1, T2) -> + erlang:convert_time_unit(T2 - T1, perf_counter, nanosecond) + end, + + do_perf_counter_test([], Conv, 120000000, 80000000), + do_perf_counter_test([1000], fun(T1, T2) -> T2 - T1 end, 120, 80). + +do_perf_counter_test(CntArgs, Conv, Upper, Lower) -> + %% We run the test multiple times to try to get a somewhat + %% stable value... what does this test? That the + %% calculate_perf_counter_unit in sys_time.c works somewhat ok. + do_perf_counter_test(CntArgs, Conv, Upper, Lower, 10). + +do_perf_counter_test(CntArgs, _Conv, Upper, Lower, 0) -> + ct:fail("perf_counter_test ~p ~p ~p",[CntArgs, Upper, Lower]); +do_perf_counter_test(CntArgs, Conv, Upper, Lower, Iters) -> + + T1 = apply(os, perf_counter, CntArgs), + timer:sleep(100), + T2 = apply(os, perf_counter, CntArgs), + TsDiff = Conv(T1, T2), + ct:log("T1: ~p~n" + "T2: ~p~n" + "TsDiff: ~p~n", + [T1,T2,TsDiff]), + + if + TsDiff < Upper, TsDiff > Lower -> + ok; + true -> + do_perf_counter_test(CntArgs, Conv, Upper, Lower, Iters-1) + end. -devil(P1, P2) -> - erlang:display({?PORT_CREATOR, whereis(?PORT_CREATOR)}), - (catch ?PORT_CREATOR ! lists:seq(1,1000000)), - (catch ?PORT_CREATOR ! lists:seq(1,666)), - (catch ?PORT_CREATOR ! grrrrrrrrrrrrrrrr), - (catch ?PORT_CREATOR ! {'EXIT', P1, buhuuu}), - (catch ?PORT_CREATOR ! {'EXIT', hd(erlang:ports()), buhuuu}), - (catch ?PORT_CREATOR ! {'EXIT', P2, arggggggg}), - receive after 500 -> ok end, - (catch exit(whereis(?PORT_CREATOR), kill)), - (catch ?PORT_CREATOR ! ">8|"), - receive after 500 -> ok end, - (catch exit(whereis(?PORT_CREATOR), diiiiiiiiiiiiiiiiiiiie)), - receive after 100 -> ok end, - devil(P1, P2). - -evil_loop(Parent, Loops, N) -> - Res = integer_to_list(N), - evil_loop(Parent, Loops, Res, "echo " ++ Res). - -evil_loop(Parent, 0, _Res, _Cmd) -> - Parent ! {self(), done}; -evil_loop(Parent, Loops, Res, Cmd) -> - comp(Res, os:cmd(Cmd)), - evil_loop(Parent, Loops-1, Res, Cmd). +%% Util functions comp(Expected, Got) -> case strip_nl(Got) of @@ -319,10 +385,10 @@ comp(Expected, Got) -> Other -> ok = io:format("Expected: ~ts\n", [Expected]), ok = io:format("Got: ~ts\n", [Other]), - test_server:fail() + ct:fail(failed) end. -%% Like lib:nonl/1, but strips \r as well as \n. +%% strips \n and \r\n from end of string strip_nl([$\r, $\n]) -> []; strip_nl([$\n]) -> []; |