%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2017. 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(port_SUITE). %%% %%% Author: Bjorn Gustavsson; iter_max_ports contributed by Peter Hogfeldt. %%% %% %% There are a lot of things to test with open_port(Name, Settings). %% %% Name can be %% %% {spawn, Command} %% which according to The Book and the manual page starts an %% external program. That is not true. It might very well be %% a linked-in program (the notion of 'linked-in driver' is %% silly, since any driver is 'linked-in'). %% [Spawn of external program is tested.] %% %% Atom %% Read all contents of Atom, or write to it. %% %% {fd, In, Out} %% Open file descriptors In and Out. [Not tested] %% %% PortSettings can be %% %% {packet, N} %% N is 1, 2 or 4. %% %% stream (default) %% Without packet length. %% %% use_stdio (default for spawned ports) %% The spawned process use file descriptors 0 and 1 for I/O. %% %% nouse_stdio [Not tested] %% Use filedescriptors 3 and 4. This option is probably only %% meaningful on Unix. %% %% in (default for Atom) %% Input only (from Erlang's point of view). %% %% out %% Output only (from Erlang's point of view). %% %% binary %% The port is a binary port, i.e. messages received and sent %% to a port are binaries. %% %% eof %% Port is not closed on eof and will not send an exit signal, %% instead it will send a {Port, eof} to the controlling process %% (output can still be sent to the port (??)). %% -export([all/0, suite/0, groups/0, init_per_testcase/2, end_per_testcase/2, init_per_suite/1, end_per_suite/1]). -export([ bad_args/1, bad_env/1, bad_packet/1, bad_port_messages/1, basic_ping/1, cd/1, cd_relative/1, close_deaf_port/1, count_fds/1, dropped_commands/1, dying_port/1, env/1, eof/1, exit_status/1, exit_status_multi_scheduling_block/1, huge_env/1, pipe_limit_env/1, input_only/1, iter_max_ports/1, line/1, mix_up_ports/1, mon_port_invalid_type/1, mon_port_bad_named/1, mon_port_bad_remote_on_local/1, mon_port_local/1, mon_port_name_demonitor/1, mon_port_named/1, mon_port_origin_dies/1, mon_port_owner_dies/1, mon_port_pid_demonitor/1, mon_port_remote_on_remote/1, mon_port_driver_die/1, mon_port_driver_die_demonitor/1, mul_basic/1, mul_slow_writes/1, name1/1, open_input_file_port/1, open_output_file_port/1, otp_3906/1, otp_4389/1, otp_5112/1, otp_5119/1, otp_6224/1, output_only/1, parallelism_option/1, parallell/1, port_program_with_path/1, port_setget_data/1, ports/1, slow_writes/1, spawn_driver/1, spawn_executable/1, stderr_to_stdout/1, stream_big/1, stream_small/1, t_binary/1, t_exit/1, tps_16_bytes/1, tps_1K/1, unregister_name/1, win_massive/1, win_massive_client/1 ]). -export([do_iter_max_ports/2, relative_cd/0]). %% Internal exports. -export([tps/3]). -export([otp_3906_forker/5, otp_3906_start_forker_starter/4]). -export([env_slave_main/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). -include_lib("eunit/include/eunit.hrl"). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {minutes, 1}}]. all() -> [otp_6224, {group, stream}, basic_ping, slow_writes, bad_packet, bad_port_messages, {group, options}, {group, multiple_packets}, parallell, dying_port, dropped_commands, port_program_with_path, open_input_file_port, open_output_file_port, name1, env, huge_env, bad_env, cd, cd_relative, pipe_limit_env, bad_args, exit_status, iter_max_ports, count_fds, t_exit, {group, tps}, line, stderr_to_stdout, otp_3906, otp_4389, win_massive, mix_up_ports, otp_5112, otp_5119, exit_status_multi_scheduling_block, ports, spawn_driver, spawn_executable, close_deaf_port, unregister_name, port_setget_data, parallelism_option, mon_port_invalid_type, mon_port_local, mon_port_remote_on_remote, mon_port_bad_remote_on_local, mon_port_origin_dies, mon_port_owner_dies, mon_port_named, mon_port_bad_named, mon_port_pid_demonitor, mon_port_name_demonitor, mon_port_driver_die, mon_port_driver_die_demonitor ]. groups() -> [{stream, [], [stream_small, stream_big]}, {options, [], [t_binary, eof, input_only, output_only]}, {multiple_packets, [], [mul_basic, mul_slow_writes]}, {tps, [], [tps_16_bytes, tps_1K]}]. init_per_testcase(Case, Config) when Case =:= mon_port_driver_die; Case =:= mon_port_driver_die_demonitor -> case erlang:system_info(schedulers_online) of 1 -> {skip, "Need 2 schedulers to run testcase"}; _ -> Config end; init_per_testcase(Case, Config) -> [{testcase, Case} |Config]. end_per_testcase(_Case, _Config) -> ok. init_per_suite(Config) when is_list(Config) -> ignore_cores:init(Config). end_per_suite(Config) when is_list(Config) -> ignore_cores:fini(Config). -define(WIN_MASSIVE_PORT, 50000). %% Tests that you can open a massive amount of ports (sockets) %% on a Windows machine given the correct environment. win_massive(Config) when is_list(Config) -> case os:type() of {win32,_} -> do_win_massive(); _ -> {skip,"Only on Windows."} end. do_win_massive() -> ct:timetrap({minutes, 6}), SuiteDir = filename:dirname(code:which(?MODULE)), Ports = " +Q 8192", {ok, Node} = test_server:start_node(win_massive, slave, [{args, " -pa " ++ SuiteDir ++ Ports}]), ok = rpc:call(Node,?MODULE,win_massive_client,[3000]), test_server:stop_node(Node), ok. win_massive_client(N) -> {ok,P}=gen_tcp:listen(?WIN_MASSIVE_PORT,[{reuseaddr,true}]), L = win_massive_loop(P,N), Len = length(L), lists:foreach(fun(E) -> gen_tcp:close(E) end, L), case Len div 2 of N -> ok; _Else -> {too_few, Len} end. win_massive_loop(_,0) -> []; win_massive_loop(P,N) -> case (catch gen_tcp:connect("localhost",?WIN_MASSIVE_PORT,[])) of {ok,A} -> case (catch gen_tcp:accept(P)) of {ok,B} -> %erlang:display(N), [A,B|win_massive_loop(P,N-1)]; _Else -> [A] end; _Else0 -> [] end. %% Test that we can send a stream of bytes and get it back. %% We will send only a small amount of data, to avoid deadlock. stream_small(Config) when is_list(Config) -> stream_ping(Config, 512, "", []), stream_ping(Config, 1777, "", []), stream_ping(Config, 1777, "-s512", []), ok. %% Send big amounts of data (much bigger than the buffer size in port test). %% This will deadlock the emulator if the spawn driver haven't proper %% non-blocking reads and writes. stream_big(Config) when is_list(Config) -> ct:timetrap({seconds, 180}), stream_ping(Config, 43755, "", []), stream_ping(Config, 100000, "", []), stream_ping(Config, 77777, " -s40000", []), ok. %% Sends packet with header size of 1, 2, and 4, with packets of various %% sizes. basic_ping(Config) when is_list(Config) -> ct:timetrap({minutes, 2}), ping(Config, sizes(1), 1, "", []), ping(Config, sizes(2), 2, "", []), ping(Config, sizes(4), 4, "", []), ok. %% Let the port program insert delays between characters sent back to %% Erlang, to test that the Erlang emulator can handle a packet coming in %% small chunks rather than all at once. slow_writes(Config) when is_list(Config) -> ping(Config, [8], 4, "-s1", []), ping(Config, [10], 2, "-s2", []), ok. %% Test that we get {'EXIT', Port, einval} if we try to send a bigger %% packet than the packet header allows. bad_packet(Config) when is_list(Config) -> PortTest = port_test(Config), process_flag(trap_exit, true), bad_packet(PortTest, 1, 256), bad_packet(PortTest, 1, 257), bad_packet(PortTest, 2, 65536), bad_packet(PortTest, 2, 65537), ok. bad_packet(PortTest, HeaderSize, PacketSize) -> P = open_port({spawn, PortTest}, [{packet, HeaderSize}]), P ! {self(), {command, make_zero_packet(PacketSize)}}, receive {'EXIT', P, einval} -> ok; Other -> ct:fail({unexpected_message, Other}) end. make_zero_packet(0) -> []; make_zero_packet(N) when N rem 2 == 0 -> P = make_zero_packet(N div 2), [P|P]; make_zero_packet(N) -> P = make_zero_packet(N div 2), [0, P|P]. %% Test sending bad messages to a port. bad_port_messages(Config) when is_list(Config) -> PortTest = port_test(Config), process_flag(trap_exit, true), bad_message(PortTest, {a,b}), bad_message(PortTest, {a}), bad_message(PortTest, {self(),{command,bad_command}}), bad_message(PortTest, {self(),{connect,no_pid}}), ok. bad_message(PortTest, Message) -> P = open_port({spawn,PortTest}, []), P ! Message, receive {'EXIT',P,badsig} -> ok; Other -> ct:fail({unexpected_message, Other}) end. %% Tests various options (stream and {packet, Number} are implicitly %% tested in other test cases). %% Tests the 'binary' option for a port. t_binary(Config) when is_list(Config) -> ct:timetrap({seconds, 300}), %% Packet mode. ping(Config, sizes(1), 1, "", [binary]), ping(Config, sizes(2), 2, "", [binary]), ping(Config, sizes(4), 4, "", [binary]), %% Stream mode. stream_ping(Config, 435, "", [binary]), stream_ping(Config, 43755, "", [binary]), stream_ping(Config, 100000, "", [binary]), ok. name1(Config) when is_list(Config) -> ct:timetrap({seconds, 100}), PortTest = port_test(Config), Command = lists:concat([PortTest, " "]), P = open_port({spawn, Command}, []), register(myport, P), P = whereis(myport), Text = "hej", myport ! {self(), {command, Text}}, receive {P, {data, Text}} -> ok end, myport ! {self(), close}, receive {P, closed} -> ok end, undefined = whereis(myport), ok. %% Test that the 'eof' option works. eof(Config) when is_list(Config) -> ct:timetrap({seconds, 100}), PortTest = port_test(Config), Command = lists:concat([PortTest, " -h0 -q"]), P = open_port({spawn, Command}, [eof]), receive {P, eof} -> ok end, P ! {self(), close}, receive {P, closed} -> ok end, ok. %% Tests that the 'in' option for a port works. input_only(Config) when is_list(Config) -> ct:timetrap({seconds, 300}), expect_input(Config, [0, 1, 10, 13, 127, 128, 255], 1, "", [in]), expect_input(Config, [0, 1, 255, 2048], 2, "", [in]), expect_input(Config, [0, 1, 255, 2048], 4, "", [in]), expect_input(Config, [0, 1, 10, 13, 127, 128, 255], 1, "", [in, binary]), ok. %% Tests that the 'out' option for a port works. output_only(Config) when is_list(Config) -> ct:timetrap({seconds, 100}), Dir = proplists:get_value(priv_dir, Config), %% First we test that the port program gets the data Filename = filename:join(Dir, "output_only_stream"), Data = random_packet(35777, "echo"), output_and_verify(Config, ["-h0 -o", Filename], Data), Wait_time = 500, test_server:sleep(Wait_time), {ok, Written} = file:read_file(Filename), Data = binary_to_list(Written), %% Then we test that any writes to stdout from %% the port program is not sent to erlang output_and_verify(Config, ["-h0"], Data), ok. output_and_verify(Config, Options, Data) -> PortTest = port_test(Config), Command = lists:concat([PortTest, " " | Options]), Port = open_port({spawn, Command}, [out]), Port ! {self(), {command, Data}}, Port ! {self(), close}, receive {Port, closed} -> ok; Msg -> ct:fail({received_unexpected_message, Msg}) end. %% Test that receiving several packages written in the same %% write operation works. %% Basic test of receiving multiple packages, written in %% one operation by the other end. mul_basic(Config) when is_list(Config) -> ct:timetrap({minutes, 10}), expect_input(Config, [0, 1, 255, 10, 13], 1, "", []), expect_input(Config, [0, 10, 13, 1600, 32767, 65535], 2, "", []), expect_input(Config, [10, 70000], 4, "", []), ok. %% Test reading a buffer consisting of several packets, some %% of which might be incomplete. (The port program builds %% a buffer with several packets, but writes it in chunks with %% delays in between.) mul_slow_writes(Config) when is_list(Config) -> ct:timetrap({minutes, 4}), expect_input(Config, [0, 20, 255, 10, 1], 1, "-s64", []), ok. %% Runs several port tests in parallell. Each individual test %% finishes in about 5 seconds. Running in parallell, all tests %% should also finish in about 5 seconds. parallell(Config) when is_list(Config) -> ct:timetrap({minutes, 5}), Testers = [ fun() -> stream_ping(Config, 1007, "-s100", []) end, fun() -> stream_ping(Config, 10007, "-s1000", []) end, fun() -> stream_ping(Config, 10007, "-s1000", []) end, fun() -> expect_input(Config, [21, 22, 23, 24, 25], 1, "-s10", [in]) end, fun() -> ping(Config, [10], 1, "-d", []) end, fun() -> ping(Config, [20000], 2, "-d", []) end, fun() -> ping(Config, [101], 1, "-s10", []) end, fun() -> ping(Config, [1001], 2, "-s100", []) end, fun() -> ping(Config, [10001], 4, "-s1000", []) end, fun() -> ping(Config, [501, 501], 2, "-s100", []) end, fun() -> ping(Config, [11, 12, 13, 14, 11], 1, "-s5", []) end], process_flag(trap_exit, true), Pids = lists:map(fun fun_spawn/1, Testers), wait_for(Pids), ok. wait_for([]) -> ok; wait_for(Pids) -> io:format("Waiting for ~p", [Pids]), receive {'EXIT', Pid, normal} -> wait_for(lists:delete(Pid, Pids)); Other -> ct:fail({bad_exit, Other}) end. %% Tests starting port programs that terminate by themselves. %% This used to cause problems on Windows. dying_port(Config) when is_list(Config) -> ct:timetrap({minutes, 2}), process_flag(trap_exit, true), P1 = make_dying_port(Config), P2 = make_dying_port(Config), P3 = make_dying_port(Config), P4 = make_dying_port(Config), P5 = make_dying_port(Config), %% This should be big enough to be sure to block in the write. Garbage = random_packet(16384), P1 ! {self(), {command, Garbage}}, P3 ! {self(), {command, Garbage}}, P5 ! {self(), {command, Garbage}}, wait_for_port_exit(P1), wait_for_port_exit(P2), wait_for_port_exit(P3), wait_for_port_exit(P4), wait_for_port_exit(P5), ok. wait_for_port_exit(Port) -> receive {'EXIT', Port, _} -> ok end. make_dying_port(Config) when is_list(Config) -> PortTest = port_test(Config), Command = lists:concat([PortTest, " -h0 -d -q"]), open_port({spawn, Command}, [stream]). %% Test that dropped port_commands work correctly. %% This used to cause a segfault. %% %% This testcase creates a port and then lets many processes %% do parallel commands to it. After a while it closes the %% port and we are trying to catch the race when doing a %% command while the port is closing. dropped_commands(Config) -> %% Test with output callback dropped_commands(Config, false, {self(), {command, "1"}}), %% Test with outputv callback dropped_commands(Config, true, {self(), {command, "1"}}). dropped_commands(Config, Outputv, Cmd) -> Path = proplists:get_value(data_dir, Config), os:putenv("ECHO_DRV_USE_OUTPUTV", atom_to_list(Outputv)), ok = load_driver(Path, "echo_drv"), [dropped_commands_test(Cmd) || _ <- lists:seq(1, 100)], timer:sleep(100), erl_ddll:unload_driver("echo_drv"), os:unsetenv("ECHO_DRV_USE_OUTPUTV"), ok. dropped_commands_test(Cmd) -> spawn_monitor( fun() -> Port = erlang:open_port({spawn_driver, "echo_drv"}, [{parallelism, true}]), [spawn_link(fun() -> spin(Port, Cmd) end) || _ <- lists:seq(1,8)], timer:sleep(5), port_close(Port), timer:sleep(5), exit(nok) end), receive _M -> timer:sleep(5) end. spin(P, Cmd) -> P ! Cmd, spin(P, Cmd). %% Tests that port program with complete path (but without any %% .exe extension) can be started, even if there is a file with %% the same name but without the extension in the same directory. %% (In practice, the file with the same name could be a Unix %% executable.) %% %% This used to failed on Windows (the .exe extension had to be %% explicitly given). %% %% This testcase works on Unix, but is not very useful. port_program_with_path(Config) when is_list(Config) -> ct:timetrap({minutes, 2}), DataDir = proplists:get_value(data_dir, Config), PrivDir = proplists:get_value(priv_dir, Config), %% Create a copy of the port test program in a directory not %% included in PATH (i.e. in priv_dir), with the name 'my_port_test.exe'. %% Also, place a file named 'my_port_test' in the same directory. %% This used to confuse the CreateProcess() call in spawn driver. %% (On Unix, there will be a single file created, which will be %% a copy of the port program.) PortTest = os:find_executable("port_test", DataDir), io:format("os:find_executable(~p, ~p) returned ~p", ["port_test", DataDir, PortTest]), {ok, PortTestPgm} = file:read_file(PortTest), NewName = filename:join(PrivDir, filename:basename(PortTest)), RedHerring = filename:rootname(NewName), ok = file:write_file(RedHerring, "I'm just here to confuse.\n"), ok = file:write_file(NewName, PortTestPgm), ok = file:write_file_info(NewName, #file_info{mode=8#111}), PgmWithPathAndNoExt = filename:rootname(NewName), %% Open the port using the path to the copied port test program, %% but without the .exe extension, and verified that it was started. %% %% If the bug is present the open_port call will fail with badarg. Command = lists:concat([PgmWithPathAndNoExt, " -h2"]), P = open_port({spawn, Command}, [{packet, 2}]), Message = "echo back to me", P ! {self(), {command, Message}}, receive {P, {data, Message}} -> ok end, ok. %% Tests that files can be read using open_port(Filename, [in]). %% This used to fail on Windows. open_input_file_port(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), %% Create a file with the file driver and read it back using %% open_port/2. MyFile1 = filename:join(PrivDir, "my_input_file"), FileData1 = "An input file", ok = file:write_file(MyFile1, FileData1), case open_port(MyFile1, [in]) of InputPort when is_port(InputPort) -> receive {InputPort, {data, FileData1}} -> ok end end, ok. %% Tests that files can be written using open_port(Filename, [out]). open_output_file_port(Config) when is_list(Config) -> ct:timetrap({minutes, 2}), PrivDir = proplists:get_value(priv_dir, Config), %% Create a file with open_port/2 and read it back with %% the file driver. MyFile2 = filename:join(PrivDir, "my_output_file"), FileData2_0 = "A file created ", FileData2_1 = "with open_port/2.\n", FileData2 = FileData2_0 ++ FileData2_1, OutputPort = open_port(MyFile2, [out]), OutputPort ! {self(), {command, FileData2_0}}, OutputPort ! {self(), {command, FileData2_1}}, OutputPort ! {self(), close}, {ok, Bin} = file:read_file(MyFile2), FileData2 = binary_to_list(Bin), ok. %% Tests that all appropriate fd's have been closed in the port program count_fds(Config) when is_list(Config) -> case os:type() of {unix, _} -> PrivDir = proplists:get_value(priv_dir, Config), Filename = filename:join(PrivDir, "my_fd_counter"), RunTest = fun(PortOpts) -> PortTest = port_test(Config), Command = lists:concat([PortTest, " -n -f -o", Filename]), Port = open_port({spawn, Command}, PortOpts), Port ! {self(), close}, receive {Port, closed} -> ok end, test_server:sleep(500), {ok, Written} = file:read_file(Filename), Written end, <<4:32/native>> = RunTest([out, nouse_stdio]), <<4:32/native>> = RunTest([in, nouse_stdio]), <<5:32/native>> = RunTest([in, out, nouse_stdio]), <<3:32/native>> = RunTest([out, use_stdio]), <<3:32/native>> = RunTest([in, use_stdio]), <<3:32/native>> = RunTest([in, out, use_stdio]), <<3:32/native>> = RunTest([in, out, use_stdio, stderr_to_stdout]), <<3:32/native>> = RunTest([out, use_stdio, stderr_to_stdout]); _ -> {skip, "Skipped on windows"} end. %% %% Open as many ports as possible. Do this several times and check %% that we get the same number of ports every time. %% iter_max_ports(Config) when is_list(Config) -> %% The child_setup program might dump core if we get out of memory. %% This is hard to do anything about and is harmless. We run this test %% in a working directory with an ignore_core_files file which will make %% the search for core files ignore cores generated by this test. %% Config2 = ignore_cores:setup(?MODULE, iter_max_ports, Config, true), try iter_max_ports_test(Config2) after ignore_cores:restore(Config2) end. iter_max_ports_test(Config) -> ct:timetrap({minutes, 30}), PortTest = port_test(Config), Command = lists:concat([PortTest, " -h0 -q"]), Iters = case os:type() of {win32,_} -> 4; _ -> 10 end, %% Run on a different node in order to limit the effect if this test fails. Dir = filename:dirname(code:which(?MODULE)), {ok,Node} = test_server:start_node(test_iter_max_socks,slave, [{args,"+Q 2048 -pa " ++ Dir}]), L = rpc:call(Node,?MODULE,do_iter_max_ports,[Iters, Command]), test_server:stop_node(Node), io:format("Result: ~p",[L]), all_equal(L), all_equal(L), {comment, "Max ports: " ++ integer_to_list(hd(L))}. do_iter_max_ports(N, Command) when N > 0 -> [max_ports(Command)| do_iter_max_ports(N-1, Command)]; do_iter_max_ports(_, _) -> []. all_equal([E,E|T]) -> all_equal([E|T]); all_equal([_]) -> ok; all_equal([]) -> ok. max_ports(Command) -> test_server:sleep(500), Ps = open_ports({spawn, Command}, [eof]), N = length(Ps), close_ports(Ps), io:format("Got ~p ports\n",[N]), N. close_ports([P|Ps]) -> P ! {self(), close}, receive {P,closed} -> ok end, close_ports(Ps); close_ports([]) -> ok. open_ports(Name, Settings) -> case os:type() of {unix, freebsd} -> %% FreeBsd has issues with sendmsg/recvmsg in fork %% implementation and we therefor have to spawn %% slower to make sure that we always hit the same %% make roof. test_server:sleep(10); _ -> test_server:sleep(5) end, case catch open_port(Name, Settings) of P when is_port(P) -> [P| open_ports(Name, Settings)]; {'EXIT', {Code, _}} -> case Code of enfile -> []; emfile -> []; system_limit -> []; enomem -> []; Other -> ct:fail({open_ports, Other}) end; Other -> ct:fail({open_ports, Other}) end. %% Tests that exit(Port, Term) works (has been known to crash the emulator). t_exit(Config) when is_list(Config) -> process_flag(trap_exit, true), Pid = fun_spawn(fun suicide_port/1, [Config]), receive {'EXIT', Pid, die} -> ok; Other -> ct:fail({bad_message, Other}) end. suicide_port(Config) when is_list(Config) -> Port = port_expect(Config, [], 0, "", []), exit(Port, die), receive after infinity -> ok end. tps_16_bytes(Config) when is_list(Config) -> tps(16, Config). tps_1K(Config) when is_list(Config) -> tps(1024, Config). tps(Size, Config) -> ct:timetrap({minutes, 5}), PortTest = port_test(Config), Packet = list_to_binary(random_packet(Size, "e")), Port = open_port({spawn, PortTest}, [binary, {packet, 2}]), Transactions = 10000, {Elapsed, ok} = test_server:timecall(?MODULE, tps, [Port, Packet, Transactions]), {comment, integer_to_list(trunc(Transactions/Elapsed+0.5)) ++ " transactions/s"}. tps(_Port, _Packet, 0) -> ok; tps(Port, Packet, N) -> port_command(Port, Packet), receive {Port, {data, Packet}} -> tps(Port, Packet, N-1); Other -> ct:fail({bad_message, Other}) end. %% Line I/O test line(Config) when is_list(Config) -> ct:timetrap({minutes, 5}), Siz = 110, Packet1 = random_packet(Siz), Packet2 = random_packet(Siz div 2), %% Test that packets are split into lines port_expect(Config,[{lists:append([Packet1, io_lib:nl(), Packet2, io_lib:nl()]), [{eol, Packet1}, {eol, Packet2}]}], 0, "", [{line,Siz}]), %% Test the same for binaries port_expect(Config,[{lists:append([Packet1, io_lib:nl(), Packet2, io_lib:nl()]), [{eol, Packet1}, {eol, Packet2}]}], 0, "", [{line,Siz},binary]), %% Test that too long lines get split port_expect(Config,[{lists:append([Packet1, io_lib:nl(), Packet1, Packet2, io_lib:nl()]), [{eol, Packet1}, {noeol, Packet1}, {eol, Packet2}]}], 0, "", [{line,Siz}]), %% Test that last output from closing port program gets received. L1 = lists:append([Packet1, io_lib:nl(), Packet2]), S1 = lists:flatten(io_lib:format("-l~w", [length(L1)])), io:format("S1 = ~w, L1 = ~w~n", [S1,L1]), port_expect(Config,[{L1, [{eol, Packet1}, {noeol, Packet2}, eof]}], 0, S1, [{line,Siz},eof]), %% Test that lonely Don't get treated as newlines port_expect(Config,[{lists:append([Packet1, [13], Packet2, io_lib:nl()]), [{noeol, Packet1}, {eol, [13 |Packet2]}]}], 0, "", [{line,Siz}]), %% Test that packets get built up to lines (delayed output from %% port program) port_expect(Config,[{Packet2,[]}, {lists:append([Packet2, io_lib:nl(), Packet1, io_lib:nl()]), [{eol, lists:append(Packet2, Packet2)}, {eol, Packet1}]}], 0, "-d", [{line,Siz}]), %% Test that we get badarg if trying both packet and line bad_argument(Config, [{packet, 5}, {line, 5}]), ok. %% Test that redirection of standard error to standard output works. stderr_to_stdout(Config) when is_list(Config) -> ct:timetrap({minutes, 1}), %% See that it works Packet = random_packet(10), port_expect(Config,[{Packet,[Packet]}], 0, "-e -l10", [stderr_to_stdout]), %% stream_ping(Config, 10, "-e", [stderr_to_stdout]), %% See that it doesn't always happen (will generate garbage on stderr) port_expect(Config,[{Packet,[eof]}], 0, "-e -l10", [line,eof]), ok. bad_argument(Config, ArgList) -> PortTest = port_test(Config), case catch open_port({spawn, PortTest}, ArgList) of {'EXIT', {badarg, _}} -> ok end. %% 'env' option %% (Can perhaps be made smaller by calling the other utility functions %% in this module.) %% %% Test that the 'env' option works env(Config) when is_list(Config) -> ct:timetrap({minutes, 1}), Priv = proplists:get_value(priv_dir, Config), Temp = filename:join(Priv, "env_fun.bin"), PluppVal = "dirty monkey", env_slave(Temp, [{"plupp",PluppVal}]), Long = "LongAndBoringEnvName", os:putenv(Long, "nisse"), env_slave(Temp, [{"plupp",PluppVal}, {"DIR_PLUPP","###glurfrik"}], fun() -> PluppVal = os:getenv("plupp"), "###glurfrik" = os:getenv("DIR_PLUPP"), "nisse" = os:getenv(Long) end), env_slave(Temp, [{"must_define_something","some_value"}, {"certainly_not_existing",false}, {"ends_with_equal", "value="}, {Long,false}, {"glurf","a glorfy string"}]), %% A lot of non existing variables (mingled with existing) NotExistingList = [{lists:flatten(io_lib:format("V~p_not_existing",[X])),false} || X <- lists:seq(1,150)], ExistingList = [{lists:flatten(io_lib:format("V~p_existing",[X])),"a_value"} || X <- lists:seq(1,150)], env_slave(Temp, lists:sort(ExistingList ++ NotExistingList)), ok. env_slave(File, Env) -> F = fun() -> lists:foreach(fun({Name,Val}) -> Val = os:getenv(Name) end, Env) end, env_slave(File, Env, F). env_slave(File, Env, Body) -> file:write_file(File, term_to_binary(Body)), Program = atom_to_list(lib:progname()), Dir = filename:dirname(code:which(?MODULE)), Cmd = Program ++ " -pz " ++ Dir ++ " -noinput -run " ++ ?MODULE_STRING ++ " env_slave_main " ++ File ++ " -run erlang halt", Port = open_port({spawn, Cmd}, [{env,Env},{line,256}]), receive {Port,{data,{eol,"ok"}}} -> ok; {Port,{data,{eol,Error}}} -> ct:fail("eol error ~p\n", [Error]); Other -> ct:fail(Other) end. env_slave_main([File]) -> {ok,Body0} = file:read_file(File), Body = binary_to_term(Body0), case Body() of {'EXIT',Reason} -> io:format("Error: ~p\n", [Reason]); _ -> io:format("ok\n") end, init:stop(). %% 'env' option %% Test bad environments. bad_env(Config) when is_list(Config) -> try_bad_env([abbb]), try_bad_env([{"key","value"}|{"another","value"}]), try_bad_env([{"key","value","value2"}]), try_bad_env([{"key",[a,b,c]}]), try_bad_env([{"key",value}]), try_bad_env({a,tuple}), try_bad_env(42), try_bad_env([a|b]), try_bad_env(self()), ok. try_bad_env(Env) -> badarg = try open_port({spawn,"ls"}, [{env,Env}]) catch error:badarg -> badarg end. %% Test that we can handle a very very large environment gracefully. huge_env(Config) when is_list(Config) -> ct:timetrap({minutes, 2}), {Vars, Cmd} = case os:type() of {win32,_} -> {500, "cmd /q /c ls"}; _ -> %% We create a huge environment, %% 20000 variables is about 25MB %% which seems to be the limit on Linux. {20000, "ls"} end, Env = [{[$a + I div (25*25*25*25) rem 25, $a + I div (25*25*25) rem 25, $a + I div (25*25) rem 25, $a+I div 25 rem 25, $a+I rem 25], lists:duplicate(100,$a+I rem 25)} || I <- lists:seq(1,Vars)], try erlang:open_port({spawn,Cmd},[exit_status, {env, Env}]) of P -> receive {P, {exit_status,N}} = M -> %% We test that the exit status is an integer, this means %% that the child program has started. If we get an atom %% something went wrong in the driver which is not ok. ct:log("Got ~p",[M]), true = is_integer(N) end catch E:R -> %% Have to catch the error here, as printing the stackdump %% in the ct log is way to heavy for some test machines. ct:fail("Open port failed ~p:~p",[E,R]) end. %% Test to spawn program with command payload buffer %% just around pipe capacity (9f779819f6bda734c5953468f7798) pipe_limit_env(Config) when is_list(Config) -> Cmd = case os:type() of {win32,_} -> "cmd /q /c true"; _ -> "true" end, CmdSize = command_payload_size(Cmd), Limits = [4096, 16384, 65536], % Try a couple of common pipe buffer sizes lists:foreach(fun(Lim) -> lists:foreach(fun(L) -> pipe_limit_env_do(L, Cmd, CmdSize) end, lists:seq(Lim-5, Lim+5)) end, Limits), ok. pipe_limit_env_do(Bytes, Cmd, CmdSize) -> case env_of_bytes(Bytes-CmdSize) of [] -> skip; Env -> try erlang:open_port({spawn,Cmd},[exit_status, {env, Env}]) of P -> receive {P, {exit_status,N}} -> %% Bug caused exit_status 150 (EINVAL+128) 0 = N end catch E:R -> %% Have to catch the error here, as printing the stackdump %% in the ct log is way to heavy for some test machines. ct:fail("Open port failed ~p:~p",[E,R]) end end. %% environ format: KEY=VALUE\0 env_of_bytes(Bytes) when Bytes > 3 -> [{"X",lists:duplicate(Bytes-3, $x)}]; env_of_bytes(_) -> []. %% White box assumption about payload written to pipe %% for Cmd and current environment (see spawn_start in sys_driver.c) command_payload_size(Cmd) -> EnvSize = lists:foldl(fun(E,Acc) -> length(E) + 1 + Acc end, 0, os:getenv()), {ok, PWD} = file:get_cwd(), (4 % buffsz + 4 % flags + 5 + length(Cmd) + 1 % "exec $Cmd" + length(PWD) + 1 % $PWD + 1 % nullbuff + 4 % env_len + EnvSize). %% Test bad 'args' options. bad_args(Config) when is_list(Config) -> try_bad_args({args, [self()]}), try_bad_args({args, ["head" | "tail"]}), try_bad_args({args, ["head", "body" | "tail"]}), try_bad_args({args, [<<"head">>, <<"body">> | <<"tail">>]}), try_bad_args({args, not_a_list}), try_bad_args({args, ["string",<<"binary">>, 1472, "string"]}), try_bad_args({args, ["string",<<"binary">>], "element #3"}), ok. try_bad_args(Args) -> badarg = try open_port({spawn_executable,"ls"}, [Args]) catch error:badarg -> badarg end. %% 'cd' option %% (Can perhaps be made smaller by calling the other utility functions %% in this module.) %% %% Test that the 'cd' option works cd(Config) when is_list(Config) -> ct:timetrap({minutes, 1}), Program = atom_to_list(lib:progname()), DataDir = proplists:get_value(data_dir, Config), TestDir = filename:join(DataDir, "dir"), Cmd = Program ++ " -pz " ++ DataDir ++ " -noshell -s port_test pwd -s erlang halt", _ = open_port({spawn, Cmd}, [{cd, TestDir}, {line, 256}]), receive {_, {data, {eol, String}}} -> case filename_equal(String, TestDir) of true -> ok; false -> ct:fail({cd, String}) end; Other2 -> ct:fail({env, Other2}) end, _ = open_port({spawn, Cmd}, [{cd, unicode:characters_to_binary(TestDir)}, {line, 256}]), receive {_, {data, {eol, String2}}} -> case filename_equal(String2, TestDir) of true -> ok; false -> ct:fail({cd, String2}) end; Other3 -> ct:fail({env, Other3}) end, InvalidDir = filename:join(DataDir, "invaliddir"), try open_port({spawn, Cmd}, [{cd, InvalidDir}, exit_status, {line, 256}]) of _ -> receive {_, {exit_status, _}} -> ok; Other4 -> ct:fail({env, Other4}) end catch error:eacces -> %% This happens on Windows ok end, %% Check that there are no lingering messages receive Other5 -> ct:fail({env, Other5}) after 10 -> ok end. %% Test that an emulator that has set it's cwd to %% something other then when it started, can use %% relative {cd,"./"} to open port and that cd will %% be relative the new cwd and not the original cd_relative(Config) -> Program = atom_to_list(lib:progname()), DataDir = proplists:get_value(data_dir, Config), TestDir = filename:join(DataDir, "dir"), Cmd = Program ++ " -pz " ++ filename:dirname(code:where_is_file("port_SUITE.beam")) ++ " -noshell -s port_SUITE relative_cd -s erlang halt", _ = open_port({spawn, Cmd}, [{line, 256}, {cd, TestDir}]), receive {_, {data, {eol, String}}} -> case filename_equal(String, TestDir) of true -> ok; false -> ct:fail({cd_relative, String}) end; Other -> ct:fail(Other) end. relative_cd() -> Program = atom_to_list(lib:progname()), ok = file:set_cwd(".."), {ok, Cwd} = file:get_cwd(), Cmd = Program ++ " -pz " ++ Cwd ++ " -noshell -s port_test pwd -s erlang halt", _ = open_port({spawn, Cmd}, [{line, 256}, {cd, "./dir"}, exit_status]), receive {_, {data, {eol, String}}} -> io:format("~s~n",[String]); Other -> io:format("ERROR: ~p~n",[Other]) end. filename_equal(A, B) -> case os:type() of {win32, _} -> win_filename_equal(A, B); _ -> A == B end. win_filename_equal([], []) -> true; win_filename_equal([], _) -> false; win_filename_equal(_, []) -> false; win_filename_equal([C1 | Rest1], [C2 | Rest2]) -> case tolower(C1) == tolower(C2) of true -> win_filename_equal(Rest1, Rest2); false -> false end. tolower(C) when C >= $A, C =< $Z -> C + 32; tolower(C) -> C. %% Tests that child process deaths are managed correctly when there are %% a large amount of concurrently dying children. See ticket OTP-3906. otp_3906(Config) when is_list(Config) -> case os:type() of {unix, OSName} -> otp_3906(Config, OSName); _ -> {skipped, "Only run on Unix systems"} end. -define(OTP_3906_CHILDREN, 1000). -define(OTP_3906_EXIT_STATUS, 17). -define(OTP_3906_PROGNAME, "otp_3906"). -define(OTP_3906_TICK_TIMEOUT, 5000). -define(OTP_3906_OSP_P_ERLP, 10). -define(OTP_3906_MAX_CONC_OSP, 50). otp_3906(Config, OSName) -> DataDir = filename:dirname(proplists:get_value(data_dir,Config)), {ok, Variables} = file:consult( filename:join([DataDir,"..","..", "test_server","variables"])), case lists:keysearch('CC', 1, Variables) of {value,{'CC', CC}} -> SuiteDir = filename:dirname(code:which(?MODULE)), PrivDir = proplists:get_value(priv_dir, Config), Prog = otp_3906_make_prog(CC, PrivDir), {ok, Node} = test_server:start_node(otp_3906, slave, [{args, " -pa " ++ SuiteDir}, {linked, false}]), OP = process_flag(priority, max), OTE = process_flag(trap_exit, true), FS = spawn_link(Node, ?MODULE, otp_3906_start_forker_starter, [?OTP_3906_CHILDREN, [], self(), Prog]), Result = receive {'EXIT', _ForkerStarter, Reason} -> {failed, Reason}; {emulator_pid, EmPid} -> case otp_3906_wait_result(FS, 0, 0) of {succeded, ?OTP_3906_CHILDREN, ?OTP_3906_CHILDREN} -> succeded; {succeded, Forked, Exited} -> otp_3906_list_defunct(EmPid, OSName), {failed, {mismatch, {forked, Forked}, {exited, Exited}}}; Res -> otp_3906_list_defunct(EmPid, OSName), Res end end, process_flag(trap_exit, OTE), process_flag(priority, OP), test_server:stop_node(Node), case Result of succeded -> ok; _ -> ct:fail(Result) end; _ -> {skipped, "No C compiler found"} end. otp_3906_list_defunct(EmPid, OSName) -> % Guess ps switches to use and what to grep for (could be improved) {Switches, Zombie} = case OSName of BSD when BSD == darwin; BSD == openbsd; BSD == netbsd; BSD == freebsd -> {"-ajx", "Z"}; _ -> {"-ef", "[dD]efunct"} end, io:format("Emulator pid: ~s~n" "Listing of zombie processes:~n" "~s~n", [EmPid, otp_3906_htmlize(os:cmd("ps " ++ Switches ++ " | grep " ++ Zombie))]). otp_3906_htmlize([]) -> []; otp_3906_htmlize([C | Cs]) -> case [C] of "<" -> "<" ++ otp_3906_htmlize(Cs); ">" -> ">" ++ otp_3906_htmlize(Cs); _ -> [C | otp_3906_htmlize(Cs)] end. otp_3906_make_prog(CC, PrivDir) -> SrcFileName = filename:join(PrivDir, ?OTP_3906_PROGNAME ++ ".c"), TrgtFileName = filename:join(PrivDir, ?OTP_3906_PROGNAME), {ok, SrcFile} = file:open(SrcFileName, write), io:format(SrcFile, "int ~n" "main(void) ~n" "{ ~n" " return ~p; ~n" "} ~n", [?OTP_3906_EXIT_STATUS]), file:close(SrcFile), os:cmd(CC ++ " " ++ SrcFileName ++ " -o " ++ TrgtFileName), TrgtFileName. otp_3906_wait_result(ForkerStarter, F, E) -> receive {'EXIT', ForkerStarter, Reason} -> {failed, {Reason, {forked, F}, {exited, E}}}; forked -> otp_3906_wait_result(ForkerStarter, F+1, E); exited -> otp_3906_wait_result(ForkerStarter, F, E+1); tick -> otp_3906_wait_result(ForkerStarter, F, E); succeded -> {succeded, F, E} after ?OTP_3906_TICK_TIMEOUT -> unlink(ForkerStarter), exit(ForkerStarter, timeout), {failed, {timeout, {forked, F}, {exited, E}}} end. otp_3906_collect([], _) -> done; otp_3906_collect(RefList, Sup) -> otp_3906_collect(otp_3906_collect_one(RefList, Sup), Sup). otp_3906_collect_one(RefList, Sup) -> receive Ref when is_reference(Ref) -> Sup ! tick, lists:delete(Ref, RefList) end. otp_3906_start_forker(N, Sup, Prog) -> Ref = make_ref(), spawn_opt(?MODULE, otp_3906_forker, [N, self(), Ref, Sup, Prog], [link, {priority, max}]), Ref. otp_3906_start_forker_starter(N, RefList, Sup, Prog) -> process_flag(priority, max), EmPid = os:getpid(), Sup ! {emulator_pid, EmPid}, otp_3906_forker_starter(N, RefList, Sup, Prog). otp_3906_forker_starter(0, RefList, Sup, _) -> otp_3906_collect(RefList, Sup), unlink(Sup), Sup ! succeded; otp_3906_forker_starter(N, RefList, Sup, Prog) when length(RefList) >= ?OTP_3906_MAX_CONC_OSP -> otp_3906_forker_starter(N, otp_3906_collect_one(RefList, Sup), Sup, Prog); otp_3906_forker_starter(N, RefList, Sup, Prog) when is_integer(N), N > ?OTP_3906_OSP_P_ERLP -> otp_3906_forker_starter(N-?OTP_3906_OSP_P_ERLP, [otp_3906_start_forker(?OTP_3906_OSP_P_ERLP, Sup, Prog)|RefList], Sup, Prog); otp_3906_forker_starter(N, RefList, Sup, Prog) when is_integer(N) -> otp_3906_forker_starter(0, [otp_3906_start_forker(N, Sup, Prog)|RefList], Sup, Prog). otp_3906_forker(0, Parent, Ref, _, _) -> unlink(Parent), Parent ! Ref; otp_3906_forker(N, Parent, Ref, Sup, Prog) -> Port = erlang:open_port({spawn, Prog}, [exit_status, in]), Sup ! forked, receive {Port, {exit_status, ?OTP_3906_EXIT_STATUS}} -> Sup ! exited, otp_3906_forker(N-1, Parent, Ref, Sup, Prog); {Port, Res} -> exit(Res); Other -> exit(Other) end. otp_4389(Config) when is_list(Config) -> case os:type() of {unix, _} -> ct:timetrap({minutes, 4}), TCR = self(), case get_true_cmd() of True when is_list(True) -> lists:foreach( fun (P) -> receive {P, ok} -> ok; {P, Err} -> ct:fail(Err) end end, lists:map( fun(_) -> spawn_link( fun() -> process_flag(trap_exit, true), case catch open_port({spawn, True}, [stream,exit_status]) of P when is_port(P) -> receive {P,{exit_status,_}} -> TCR ! {self(),ok}; {'EXIT',_,{R2,_}} when R2 == emfile; R2 == eagain; R2 == enomem -> TCR ! {self(),ok}; Err2 -> TCR ! {self(),{msg,Err2}} end; {'EXIT',{R1,_}} when R1 == emfile; R1 == eagain; R1 == enomem -> TCR ! {self(),ok}; Err1 -> TCR ! {self(), {open_port,Err1}} end end) end, lists:duplicate(1000,[]))), {comment, "This test case doesn't always fail when the bug that " "it tests for is present (it is most likely to fail on" " a multi processor machine). If the test case fails it" " will fail by deadlocking the emulator."}; _ -> {skipped, "\"true\" command not found"} end; _ -> {skip,"Only run on Unix"} end. get_true_cmd() -> DoFileExist = fun (FileName) -> case file:read_file_info(FileName) of {ok, _} -> throw(FileName); _ -> not_found end end, catch begin %% First check in /usr/bin and /bin DoFileExist("/usr/bin/true"), DoFileExist("/bin/true"), %% Try which case filename:dirname(os:cmd("which true")) of "." -> not_found; TrueDir -> filename:join(TrueDir, "true") end end. %% 'exit_status' option %% %% Test that the 'exit_status' option works exit_status(Config) when is_list(Config) -> ct:timetrap({minutes, 1}), port_expect(Config, [{"x", [{exit_status, 5}]}], 1, "", [exit_status]), ok. %% Test spawning a driver specifically spawn_driver(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "echo_drv"), Port = erlang:open_port({spawn_driver, "echo_drv"}, []), Port ! {self(), {command, "Hello port!"}}, receive {Port, {data, "Hello port!"}} = Msg1 -> io:format("~p~n", [Msg1]), ok; Other -> ct:fail({unexpected, Other}) end, Port ! {self(), close}, receive {Port, closed} -> ok end, Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, []), receive {Port2, {data, "Hello port?"}} = Msg2 -> io:format("~p~n", [Msg2]), ok; Other2 -> ct:fail({unexpected2, Other2}) end, Port2 ! {self(), close}, receive {Port2, closed} -> ok end, {'EXIT',{badarg,_}} = (catch erlang:open_port({spawn_driver, "ls"}, [])), {'EXIT',{badarg,_}} = (catch erlang:open_port({spawn_driver, "cmd"}, [])), {'EXIT',{badarg,_}} = (catch erlang:open_port({spawn_driver, os:find_executable("erl")}, [])), ok. %% Test parallelism option of open_port parallelism_option(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "echo_drv"), Port = erlang:open_port({spawn_driver, "echo_drv"}, [{parallelism, true}]), {parallelism, true} = erlang:port_info(Port, parallelism), Port ! {self(), {command, "Hello port!"}}, receive {Port, {data, "Hello port!"}} = Msg1 -> io:format("~p~n", [Msg1]), ok; Other -> ct:fail({unexpected, Other}) end, Port ! {self(), close}, receive {Port, closed} -> ok end, Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, [{parallelism, false}]), {parallelism, false} = erlang:port_info(Port2, parallelism), receive {Port2, {data, "Hello port?"}} = Msg2 -> io:format("~p~n", [Msg2]), ok; Other2 -> ct:fail({unexpected2, Other2}) end, Port2 ! {self(), close}, receive {Port2, closed} -> ok end, ok. %% Test spawning an executable specifically spawn_executable(Config) when is_list(Config) -> DataDir = proplists:get_value(data_dir, Config), EchoArgs1 = filename:join([DataDir,"echo_args"]), ExactFile1 = filename:nativename(os:find_executable(EchoArgs1)), [ExactFile1] = run_echo_args(DataDir,[]), [ExactFile1] = run_echo_args(DataDir,[binary]), ["echo_args"] = run_echo_args(DataDir,["echo_args"]), ["echo_args"] = run_echo_args(DataDir,[binary, "echo_args"]), ["echo_arguments"] = run_echo_args(DataDir,["echo_arguments"]), ["echo_arguments"] = run_echo_args(DataDir,[binary, "echo_arguments"]), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[ExactFile1,"hello world","dlrow olleh"]), [ExactFile1] = run_echo_args(DataDir,[default]), [ExactFile1] = run_echo_args(DataDir,[binary, default]), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[switch_order,ExactFile1,"hello world", "dlrow olleh"]), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[binary,switch_order,ExactFile1,"hello world", "dlrow olleh"]), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[default,"hello world","dlrow olleh"]), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args_2("\""++ExactFile1++"\" "++"\"hello world\" \"dlrow olleh\""), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args_2(unicode:characters_to_binary("\""++ExactFile1++"\" "++"\"hello world\" \"dlrow olleh\"")), PrivDir = proplists:get_value(priv_dir, Config), SpaceDir = filename:join([PrivDir,"With Spaces"]), file:make_dir(SpaceDir), Executable = filename:basename(ExactFile1), file:copy(ExactFile1,filename:join([SpaceDir,Executable])), ExactFile2 = filename:nativename(filename:join([SpaceDir,Executable])), chmodplusx(ExactFile2), io:format("|~s|~n",[ExactFile2]), [ExactFile2] = run_echo_args(SpaceDir,[]), ["echo_args"] = run_echo_args(SpaceDir,["echo_args"]), ["echo_arguments"] = run_echo_args(SpaceDir,["echo_arguments"]), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[ExactFile2,"hello world","dlrow olleh"]), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[binary, ExactFile2,"hello world","dlrow olleh"]), [ExactFile2,"hello \"world\"","\"dlrow\" olleh"] = run_echo_args(SpaceDir,[binary, ExactFile2,"hello \"world\"","\"dlrow\" olleh"]), [ExactFile2,"hello \"world\"","\"dlrow\" olleh"] = run_echo_args(SpaceDir,[binary, ExactFile2,"hello \"world\"","\"dlrow\" olleh"]), [ExactFile2] = run_echo_args(SpaceDir,[default]), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[switch_order,ExactFile2,"hello world", "dlrow olleh"]), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[default,"hello world","dlrow olleh"]), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args_2("\""++ExactFile2++"\" "++"\"hello world\" \"dlrow olleh\""), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args_2(unicode:characters_to_binary("\""++ExactFile2++"\" "++"\"hello world\" \"dlrow olleh\"")), ExeExt = filename:extension(ExactFile2), Executable2 = "spoky name"++ExeExt, file:copy(ExactFile1,filename:join([SpaceDir,Executable2])), ExactFile3 = filename:nativename(filename:join([SpaceDir,Executable2])), chmodplusx(ExactFile3), [ExactFile3] = run_echo_args(SpaceDir,Executable2,[]), ["echo_args"] = run_echo_args(SpaceDir,Executable2,["echo_args"]), ["echo_arguments"] = run_echo_args(SpaceDir,Executable2,["echo_arguments"]), [ExactFile3,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,Executable2,[ExactFile3,"hello world","dlrow olleh"]), [ExactFile3] = run_echo_args(SpaceDir,Executable2,[default]), [ExactFile3,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,Executable2, [switch_order,ExactFile3,"hello world", "dlrow olleh"]), [ExactFile3,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,Executable2, [default,"hello world","dlrow olleh"]), [ExactFile3,"hello world","dlrow olleh"] = run_echo_args_2("\""++ExactFile3++"\" "++"\"hello world\" \"dlrow olleh\""), [ExactFile3,"hello world","dlrow olleh"] = run_echo_args_2(unicode:characters_to_binary("\""++ExactFile3++"\" "++"\"hello world\" \"dlrow olleh\"")), {'EXIT',{enoent,_}} = (catch run_echo_args(SpaceDir,"fnurflmonfi", [default,"hello world", "dlrow olleh"])), NonExec = "kronxfrt"++ExeExt, file:write_file(filename:join([SpaceDir,NonExec]), <<"Not an executable">>), {'EXIT',{eacces,_}} = (catch run_echo_args(SpaceDir,NonExec, [default,"hello world", "dlrow olleh"])), {'EXIT',{enoent,_}} = (catch open_port({spawn_executable,"cmd"},[])), {'EXIT',{enoent,_}} = (catch open_port({spawn_executable,"sh"},[])), case os:type() of {win32,_} -> test_bat_file(SpaceDir); {unix,_} -> test_sh_file(SpaceDir) end, ok. unregister_name(Config) when is_list(Config) -> true = register(crash, open_port({spawn, "sleep 100"}, [])), true = unregister(crash). test_bat_file(Dir) -> FN = "tf.bat", Full = filename:join([Dir,FN]), D = [<<"@echo off\r\n">>, <<"echo argv[0]:^|%0^|\r\n">>, <<"if \"%1\" == \"\" goto done\r\n">>, <<"echo argv[1]:^|%1^|\r\n">>, <<"if \"%2\" == \"\" goto done\r\n">>, <<"echo argv[2]:^|%2^|\r\n">>, <<"if \"%3\" == \"\" goto done\r\n">>, <<"echo argv[3]:^|%3^|\r\n">>, <<"if \"%4\" == \"\" goto done\r\n">>, <<"echo argv[4]:^|%4^|\r\n">>, <<"if \"%5\" == \"\" goto done\r\n">>, <<"echo argv[5]:^|%5^|\r\n">>, <<"\r\n">>, <<":done\r\n">>, <<"\r\n">>], file:write_file(Full,list_to_binary(D)), EF = filename:basename(FN), [DN,"hello","world"] = run_echo_args(Dir,FN, [default,"hello","world"]), %% The arg0 argumant should be ignored when running batch files [DN,"hello","world"] = run_echo_args(Dir,FN, ["knaskurt","hello","world"]), EF = filename:basename(DN), ok. test_sh_file(Dir) -> FN = "tf.sh", Full = filename:join([Dir,FN]), D = [<<"#! /bin/sh\n">>, <<"echo 'argv[0]:|'$0'|'\n">>, <<"i=1\n">>, <<"while [ '!' -z \"$1\" ]; do\n">>, <<" echo 'argv['$i']:|'\"$1\"'|'\n">>, <<" shift\n">>, <<" i=`expr $i + 1`\n">>, <<"done\n">>], file:write_file(Full,list_to_binary(D)), chmodplusx(Full), [Full,"hello","world"] = run_echo_args(Dir,FN, [default,"hello","world"]), [Full,"hello","world of spaces"] = run_echo_args(Dir,FN, [default,"hello","world of spaces"]), file:write_file(filename:join([Dir,"testfile1"]),<<"testdata1">>), file:write_file(filename:join([Dir,"testfile2"]),<<"testdata2">>), Pattern = filename:join([Dir,"testfile*"]), L = filelib:wildcard(Pattern), 2 = length(L), [Full,"hello",Pattern] = run_echo_args(Dir,FN, [default,"hello",Pattern]), ok. chmodplusx(Filename) -> case file:read_file_info(Filename) of {ok,FI} -> FI2 = FI#file_info{mode = ((FI#file_info.mode) bor 8#00100)}, file:write_file_info(Filename,FI2); _ -> ok end. run_echo_args_2(FullnameAndArgs) -> Port = open_port({spawn,FullnameAndArgs},[eof]), Data = collect_data(Port), Port ! {self(), close}, receive {Port, closed} -> ok end, parse_echo_args_output(Data). run_echo_args(Where,Args) -> run_echo_args(Where,"echo_args",Args). run_echo_args(Where,Prog,Args) -> {Binary, ArgvArg} = pack_argv(Args), Command0 = filename:join([Where,Prog]), Command = case Binary of true -> unicode:characters_to_binary(Command0); false -> Command0 end, Port = open_port({spawn_executable,Command},ArgvArg++[eof]), Data = collect_data(Port), Port ! {self(), close}, receive {Port, closed} -> ok end, parse_echo_args_output(Data). pack_argv([binary|Args]) -> {true, pack_argv(Args, true)}; pack_argv(Args) -> {false, pack_argv(Args, false)}. pack_argv(Args, Binary) -> case Args of [] -> []; [default|T] -> [{args,[make_bin(Arg,Binary) || Arg <- T]}]; [switch_order,H|T] -> [{args,[make_bin(Arg,Binary) || Arg <- T]},{arg0,make_bin(H,Binary)}]; [H|T] -> [{arg0,make_bin(H,Binary)},{args,[make_bin(Arg,Binary) || Arg <- T]}] end. make_bin(Str, false) -> Str; make_bin(Str, true) -> unicode:characters_to_binary(Str). collect_data(Port) -> receive {Port, {data, Data}} -> Data ++ collect_data(Port); {Port, eof} -> [] end. parse_echo_args_output(Data) -> [lists:last(string:lexemes(S,"|")) || S <- string:lexemes(Data,["\r\n",$\n])]. %% Test that the emulator does not mix up ports when the port table wraps mix_up_ports(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "echo_drv"), Port = erlang:open_port({spawn, "echo_drv"}, []), Port ! {self(), {command, "Hello port!"}}, receive {Port, {data, "Hello port!"}} = Msg1 -> io:format("~p~n", [Msg1]), ok; Other -> ct:fail({unexpected, Other}) end, Port ! {self(), close}, receive {Port, closed} -> ok end, loop(start, done, fun(P) -> Q = (catch erlang:open_port({spawn, "echo_drv"}, [])), %% io:format("~p ", [Q]), if is_port(Q) -> Q; true -> io:format("~p~n", [P]), done end end), Port ! {self(), {command, "Hello again port!"}}, receive Msg2 -> ct:fail({unexpected, Msg2}) after 1000 -> ok end, ok. loop(Stop, Stop, Fun) when is_function(Fun) -> ok; loop(Start, Stop, Fun) when is_function(Fun) -> loop(Fun(Start), Stop, Fun). %% Test that link to connected process is taken away when port calls %% driver_exit() also when the port index has wrapped otp_5112(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "exit_drv"), Port = otp_5112_get_wrapped_port(), io:format("Max ports: ~p~n",[max_ports()]), io:format("Port: ~p~n",[Port]), {links, Links1} = process_info(self(),links), io:format("Links1: ~p~n",[Links1]), true = lists:member(Port, Links1), Port ! {self(), {command, ""}}, wait_until(fun () -> lists:member(Port, erlang:ports()) == false end), {links, Links2} = process_info(self(),links), io:format("Links2: ~p~n",[Links2]), false = lists:member(Port, Links2), %% This used to fail ok. otp_5112_get_wrapped_port() -> P1 = erlang:open_port({spawn, "exit_drv"}, []), case port_ix(P1) < max_ports() of true -> io:format("Need to wrap port index (~p)~n", [P1]), otp_5112_wrap_port_ix([P1]), P2 = erlang:open_port({spawn, "exit_drv"}, []), false = port_ix(P2) < max_ports(), P2; false -> io:format("Port index already wrapped (~p)~n", [P1]), P1 end. otp_5112_wrap_port_ix(Ports) -> case (catch erlang:open_port({spawn, "exit_drv"}, [])) of Port when is_port(Port) -> otp_5112_wrap_port_ix([Port|Ports]); _ -> %% Port table now full; empty port table lists:foreach(fun (P) -> P ! {self(), close} end, Ports), ok end. %% Test that port index is not unnecessarily wrapped otp_5119(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "exit_drv"), PI1 = port_ix(otp_5119_fill_empty_port_tab([])), Port2 = erlang:open_port({spawn, "exit_drv"}, []), PI2 = port_ix(Port2), {PortIx1, PortIx2} = case PI2 > PI1 of true -> {PI1, PI2}; false -> {port_ix(otp_5119_fill_empty_port_tab([Port2])), port_ix(erlang:open_port({spawn, "exit_drv"}, []))} end, MaxPorts = max_ports(), io:format("PortIx1 = ~p ~p~n", [PI1, PortIx1]), io:format("PortIx2 = ~p ~p~n", [PI2, PortIx2]), io:format("MaxPorts = ~p~n", [MaxPorts]), true = PortIx2 > PortIx1, true = PortIx2 =< PortIx1 + MaxPorts, ok. otp_5119_fill_empty_port_tab(Ports) -> case (catch erlang:open_port({spawn, "exit_drv"}, [])) of Port when is_port(Port) -> otp_5119_fill_empty_port_tab([Port|Ports]); _ -> %% Port table now full; empty port table lists:foreach(fun (P) -> P ! {self(), close} end, Ports), [LastPort|_] = Ports, LastPort end. max_ports() -> erlang:system_info(port_limit). port_ix(Port) when is_port(Port) -> ["#Port",_,PortIxStr] = string:lexemes(erlang:port_to_list(Port), "<.>"), list_to_integer(PortIxStr). %% Check that port command failure doesn't crash the emulator otp_6224(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "failure_drv"), Go = make_ref(), Failer = spawn(fun () -> receive Go -> ok end, Port = open_port({spawn, "failure_drv"}, []), Port ! {self(), {command, "Fail, please!"}}, otp_6224_loop() end), Mon = erlang:monitor(process, Failer), Failer ! Go, receive {'DOWN', Mon, process, Failer, Reason} -> case Reason of {driver_failed, _} -> ok; driver_failed -> ok; _ -> ct:fail({unexpected_exit_reason, Reason}) end end, ok. otp_6224_loop() -> receive _ -> ok after 0 -> ok end, otp_6224_loop(). -define(EXIT_STATUS_MSB_MAX_PROCS, 64). -define(EXIT_STATUS_MSB_MAX_PORTS, 300). exit_status_multi_scheduling_block(Config) when is_list(Config) -> Repeat = 3, case os:type() of {unix, _} -> ct:timetrap({minutes, 2*Repeat}), SleepSecs = 6, try lists:foreach(fun (_) -> exit_status_msb_test(Config, SleepSecs) end, lists:seq(1, Repeat)) after %% Wait for the system to recover (regardless %% of success or not) otherwise later testcases %% may unnecessarily fail. receive after SleepSecs+500 -> ok end end; _ -> {skip, "Not implemented for this OS"} end. exit_status_msb_test(Config, SleepSecs) when is_list(Config) -> %% %% We want to start port programs from as many schedulers as possible %% and we want these port programs to terminate while multi-scheduling %% is blocked. %% NoSchedsOnln = erlang:system_info(schedulers_online), Parent = self(), io:format("SleepSecs = ~p~n", [SleepSecs]), PortProg = "sleep " ++ integer_to_list(SleepSecs), Start = erlang:monotonic_time(microsecond), NoProcs = case NoSchedsOnln of NProcs when NProcs < ?EXIT_STATUS_MSB_MAX_PROCS -> NProcs; _ -> ?EXIT_STATUS_MSB_MAX_PROCS end, NoPortsPerProc = case 20*NoProcs of TNPorts when TNPorts < ?EXIT_STATUS_MSB_MAX_PORTS -> 20; _ -> ?EXIT_STATUS_MSB_MAX_PORTS div NoProcs end, io:format("NoProcs = ~p~nNoPortsPerProc = ~p~n", [NoProcs, NoPortsPerProc]), ProcFun = fun () -> PrtSIds = lists:map( fun (_) -> erlang:yield(), case catch open_port({spawn, PortProg}, [exit_status]) of Prt when is_port(Prt) -> {Prt, erlang:system_info(scheduler_id)}; {'EXIT', {Err, _}} when Err == eagain; Err == emfile; Err == enomem -> noop; {'EXIT', Err} when Err == eagain; Err == emfile; Err == enomem -> noop; Error -> ct:fail(Error) end end, lists:seq(1, NoPortsPerProc)), SIds = lists:filter(fun (noop) -> false; (_) -> true end, lists:map(fun (noop) -> noop; ({_, SId}) -> SId end, PrtSIds)), process_flag(scheduler, 0), Parent ! {self(), started, SIds}, lists:foreach( fun (noop) -> noop; ({Port, _}) -> receive {Port, {exit_status, 0}} -> ok; {Port, {exit_status, Status}} when Status > 128 -> %% Sometimes happens when we have created %% too many ports. ok; {Port, {exit_status, _}} = ESMsg -> {Port, {exit_status, 0}} = ESMsg end end, PrtSIds), Parent ! {self(), done} end, Procs = lists:map(fun (N) -> spawn_opt(ProcFun, [link, {scheduler, (N rem NoSchedsOnln)+1}]) end, lists:seq(1, NoProcs)), SIds = lists:map(fun (P) -> receive {P, started, SIds} -> SIds end end, Procs), StartedTime = (erlang:monotonic_time(microsecond) - Start)/1000000, io:format("StartedTime = ~p~n", [StartedTime]), true = StartedTime < SleepSecs, erlang:system_flag(multi_scheduling, block_normal), lists:foreach(fun (P) -> receive {P, done} -> ok end end, Procs), DoneTime = (erlang:monotonic_time(microsecond) - Start)/1000000, io:format("DoneTime = ~p~n", [DoneTime]), true = DoneTime > SleepSecs, ok = verify_multi_scheduling_blocked(), erlang:system_flag(multi_scheduling, unblock_normal), case {length(lists:usort(lists:flatten(SIds))), NoSchedsOnln} of {N, N} -> ok; {N, M} -> ct:fail("Failed to create ports on all ~w available" "schedulers. Only created ports on ~w schedulers.", [M, N]) end. save_sid(SIds) -> SId = erlang:system_info(scheduler_id), case lists:member(SId, SIds) of true -> SIds; false -> [SId|SIds] end. sid_proc(SIds) -> NewSIds = save_sid(SIds), receive {From, want_sids} -> From ! {self(), sids, NewSIds} after 0 -> sid_proc(NewSIds) end. verify_multi_scheduling_blocked() -> Procs = lists:map(fun (_) -> spawn_link(fun () -> sid_proc([]) end) end, lists:seq(1, 3*erlang:system_info(schedulers_online))), receive after 1000 -> ok end, SIds = lists:map(fun (P) -> P ! {self(), want_sids}, receive {P, sids, PSIds} -> PSIds end end, Procs), 1 = length(lists:usort(lists:flatten(SIds))), ok. %%% Pinging functions. stream_ping(Config, Size, CmdLine, Options) -> Data = random_packet(Size), port_expect(Config, [{Data, [Data]}], 0, CmdLine, Options). ping(Config, Sizes, HSize, CmdLine, Options) -> Actions = lists:map(fun(Size) -> [$p|Packet] = random_packet(Size, "ping"), {[$p|Packet], [[$P|Packet]]} end, Sizes), port_expect(Config, Actions, HSize, CmdLine, Options). %% expect_input(Sizes, HSize, CmdLine, Options) %% %% Sizes = Size of packets to generated. %% HSize = Header size: 1, 2, or 4 %% CmdLine = Additional command line options. %% Options = Additional port options. expect_input(Config, Sizes, HSize, CmdLine, Options) -> expect_input1(Config, Sizes, {HSize, CmdLine, Options}, [], []). expect_input1(Config, [0|Rest], Params, Expect, ReplyCommand) -> expect_input1(Config, Rest, Params, [""|Expect], ["x0"|ReplyCommand]); expect_input1(Config, [Size|Rest], Params, Expect, ReplyCommand) -> Packet = random_packet(Size), Fmt = io_lib:format("~c~p", [hd(Packet), Size]), expect_input1(Config, Rest, Params, [Packet|Expect], [Fmt|ReplyCommand]); expect_input1(Config, [], {HSize, CmdLine0, Options}, Expect, ReplyCommand) -> CmdLine = build_cmd_line(CmdLine0, ReplyCommand, []), port_expect(Config, [{false, lists:reverse(Expect)}], HSize, CmdLine, Options). build_cmd_line(FixedCmdLine, [Cmd|Rest], []) -> build_cmd_line(FixedCmdLine, Rest, [Cmd]); build_cmd_line(FixedCmdLine, [Cmd|Rest], Result) -> build_cmd_line(FixedCmdLine, Rest, [Cmd, $:|Result]); build_cmd_line(FixedCmdLine, [], Result) -> lists:flatten([FixedCmdLine, " -r", Result, " -n"]). %% port_expect(Actions, HSize, CmdLine, Options) %% %% Actions = [{Send, ExpectList}|Rest] %% HSize = 0 (stream), or 1, 2, 4 (header size aka "packet bytes") %% CmdLine = Command line for port_test. Don't include -h. %% Options = Options for open_port/2. Don't include {packet, Number} or %% or stream. %% %% Send = false | list() %% ExpectList = List of lists or binaries. %% %% Returns the port. port_expect(Config, Actions, HSize, CmdLine, Options0) -> % io:format("port_expect(~p, ~p, ~p, ~p)", % [Actions, HSize, CmdLine, Options0]), PortTest = port_test(Config), Cmd = lists:concat([PortTest, " -h", HSize, " ", CmdLine]), PortType = case HSize of 0 -> stream; _ -> {packet, HSize} end, Options = [PortType|Options0], io:format("open_port({spawn, ~p}, ~p)", [Cmd, Options]), Port = open_port({spawn, Cmd}, Options), port_expect(Port, Actions, Options), Port. port_expect(Port, [{Send, Expects}|Rest], Options) when is_list(Expects) -> port_send(Port, Send), IsBinaryPort = lists:member(binary, Options), Receiver = case {lists:member(stream, Options), line_option(Options)} of {false, _} -> fun receive_all/2; {true,false} -> fun stream_receive_all/2; {_, true} -> fun receive_all/2 end, Receiver(Port, maybe_to_binary(Expects, IsBinaryPort)), port_expect(Port, Rest, Options); port_expect(_, [], _) -> ok. %%% Check for either line or {line,N} in option list line_option([{line,_}|_]) -> true; line_option([line|_]) -> true; line_option([_|T]) -> line_option(T); line_option([]) -> false. any_list_to_binary({Atom, List}) -> {Atom, list_to_binary(List)}; any_list_to_binary(List) -> list_to_binary(List). maybe_to_binary(Expects, true) -> lists:map(fun any_list_to_binary/1, Expects); maybe_to_binary(Expects, false) -> Expects. port_send(_Port, false) -> ok; port_send(Port, Send) when is_list(Send) -> % io:format("port_send(~p, ~p)", [Port, Send]), Port ! {self(), {command, Send}}. receive_all(Port, [Expect|Rest]) -> % io:format("receive_all(~p, [~p|Rest])", [Port, Expect]), receive {Port, {data, Expect}} -> io:format("Received ~s", [format(Expect)]), ok; {Port, {data, Other}} -> io:format("Received ~s; expected ~s", [format(Other), format(Expect)]), ct:fail(bad_message); Other -> %% (We're not yet prepared for receiving both 'eol' and %% 'exit_status'; remember that they may appear in any order.) case {Expect, Rest, Other} of {eof, [], {Port, eof}} -> io:format("Received soft EOF.",[]), ok; {{exit_status, S}, [], {Port, {exit_status, S}}} -> io:format("Received exit status ~p.",[S]), ok; _ -> %%% io:format("Unexpected message: ~s", [format(Other)]), io:format("Unexpected message: ~w", [Other]), ct:fail(unexpected_message) end end, receive_all(Port, Rest); receive_all(_Port, []) -> ok. stream_receive_all(Port, [Expect]) -> stream_receive_all1(Port, Expect). stream_receive_all1(_Port, Empty) when is_binary(Empty), size(Empty) == 0 -> ok; stream_receive_all1(_Port, []) -> ok; stream_receive_all1(Port, Expect) -> receive {Port, {data, Data}} -> Remaining = compare(Data, Expect), stream_receive_all1(Port, Remaining); Other -> ct:fail({bad_message, Other}) end. compare(B1, B2) when is_binary(B1), is_binary(B2), byte_size(B1) =< byte_size(B2) -> case split_binary(B2, size(B1)) of {B1,Remaining} -> Remaining; _Other -> ct:fail(nomatch) end; compare(B1, B2) when is_binary(B1), is_binary(B2) -> ct:fail(too_much_data); compare([X|Rest1], [X|Rest2]) -> compare(Rest1, Rest2); compare([_|_], [_|_]) -> ct:fail(nomatch); compare([], Remaining) -> Remaining; compare(_Data, []) -> ct:fail(too_much_data). maybe_to_list(Bin) when is_binary(Bin) -> binary_to_list(Bin); maybe_to_list(List) -> List. format({Eol,List}) -> io_lib:format("tuple<~w,~w>",[Eol, maybe_to_list(List)]); format(List) when is_list(List) -> case list_at_least(50, List) of true -> io_lib:format("\"~-50s...\"", [List]); false -> io_lib:format("~p", [List]) end; format(Bin) when is_binary(Bin), size(Bin) >= 50 -> io_lib:format("binary<~-50s...>", [binary_to_list(Bin, 1, 50)]); format(Bin) when is_binary(Bin) -> io_lib:format("binary<~s>", [binary_to_list(Bin)]). list_at_least(Number, [_|Rest]) when Number > 0 -> list_at_least(Number-1, Rest); list_at_least(Number, []) when Number > 0 -> false; list_at_least(0, _List) -> true. %%% Utility functions. random_packet(Size) -> random_packet(Size, ""). random_packet(Size, Prefix) -> build_packet(Size-length(Prefix), lists:reverse(Prefix), random_char()). build_packet(0, Result, _NextChar) -> lists:reverse(Result); build_packet(Left, Result, NextChar0) -> NextChar = if NextChar0 >= 126 -> 33; true -> NextChar0+1 end, build_packet(Left-1, [NextChar0|Result], NextChar). sizes() -> [10, 13, 64, 127, 128, 255, 256, 1023, 1024, 32767, 32768, 65535, 65536]. sizes(Header_Size) -> sizes(Header_Size, sizes(), []). sizes(1, [Packet_Size|Rest], Result) when Packet_Size < 256 -> sizes(1, Rest, [Packet_Size|Result]); sizes(2, [Packet_Size|Rest], Result) when Packet_Size < 65536 -> sizes(2, Rest, [Packet_Size|Result]); sizes(4, [Packet_Size|Rest], Result) -> sizes(4, Rest, [Packet_Size|Result]); sizes(_, _, Result) -> Result. random_char() -> random_char("abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789"). random_char(Chars) -> lists:nth(uniform(length(Chars)), Chars). uniform(N) -> case rand:export_seed() of undefined -> rand:seed(exsplus), io:format("Random seed = ~p\n", [rand:export_seed()]); _ -> ok end, rand:uniform(N). fun_spawn(Fun) -> fun_spawn(Fun, []). fun_spawn(Fun, Args) -> spawn_link(erlang, apply, [Fun, Args]). port_test(Config) when is_list(Config) -> filename:join(proplists:get_value(data_dir, Config), "port_test"). %% Test that erlang:ports/0 returns a consistent snapshot of ports ports(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "exit_drv"), receive after 1000 -> ok end, % Wait for other ports to stabilize OtherPorts = erlang:ports(), io:format("Other ports: ~p\n",[OtherPorts]), MaxPorts = 1024 - length(OtherPorts), TrafficPid = spawn_link(fun() -> ports_traffic(MaxPorts) end), ports_snapshots(100, TrafficPid, OtherPorts), TrafficPid ! {self(),die}, receive {TrafficPid, dead} -> ok end, ok. ports_snapshots(0, _, _) -> ok; ports_snapshots(Iter, TrafficPid, OtherPorts) -> TrafficPid ! start, receive after 1 -> ok end, Snapshot = erlang:ports(), TrafficPid ! {self(), stop}, receive {TrafficPid, EventList, TrafficPorts} -> ok end, %%io:format("Snapshot=~p\n", [Snapshot]), ports_verify(Snapshot, OtherPorts ++ TrafficPorts, EventList), ports_snapshots(Iter-1, TrafficPid, OtherPorts). ports_traffic(MaxPorts) -> ports_traffic_stopped(MaxPorts, {[],0}). ports_traffic_stopped(MaxPorts, {PortList, PortCnt}) -> receive start -> %%io:format("Traffic started in ~p\n",[self()]), ports_traffic_started(MaxPorts, {PortList, PortCnt}, []); {Pid,die} -> lists:foreach(fun(Port)-> erlang:port_close(Port) end, PortList), Pid ! {self(),dead} end. ports_traffic_started(MaxPorts, {PortList, PortCnt}, EventList) -> receive {Pid, stop} -> %%io:format("Traffic stopped in ~p\n",[self()]), Pid ! {self(), EventList, PortList}, ports_traffic_stopped(MaxPorts, {PortList, PortCnt}) after 0 -> ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) end. ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) -> N = uniform(MaxPorts), case N > PortCnt of true -> % Open port P = open_port({spawn, "exit_drv"}, []), %%io:format("Created port ~p\n",[P]), ports_traffic_started(MaxPorts, {[P|PortList], PortCnt+1}, [{open,P}|EventList]); false -> % Close port P = lists:nth(N, PortList), %%io:format("Close port ~p\n",[P]), true = erlang:port_close(P), ports_traffic_started(MaxPorts, {lists:delete(P,PortList), PortCnt-1}, [{close,P}|EventList]) end. ports_verify(Ports, PortsAfter, EventList) -> %%io:format("Candidate=~p\nEvents=~p\n", [PortsAfter, EventList]), case lists:sort(Ports) =:= lists:sort(PortsAfter) of true -> io:format("Snapshot of ~p ports verified ok.\n",[length(Ports)]), ok; false -> %% Note that we track the event list "backwards", undoing open/close: case EventList of [{open,P} | Tail] -> ports_verify(Ports, lists:delete(P,PortsAfter), Tail); [{close,P} | Tail] -> ports_verify(Ports, [P | PortsAfter], Tail); [] -> ct:fail("Inconsistent snapshot from erlang:ports()") end end. load_driver(Dir, Driver) -> case erl_ddll:load_driver(Dir, Driver) of ok -> ok; {error, Error} = Res -> io:format("~s\n", [erl_ddll:format_error(Error)]), Res end. %% Send data to port program that does not read it, then close port. %% Primary targeting Windows to test threaded_handle_closer in sys.c close_deaf_port(Config) when is_list(Config) -> ct:timetrap({minutes, 2}), DataDir = proplists:get_value(data_dir, Config), DeadPort = os:find_executable("dead_port", DataDir), Port = open_port({spawn,DeadPort++" 60"},[]), erlang:port_command(Port,"Hello, can you hear me!?!?"), port_close(Port), Res = close_deaf_port_1(0, DeadPort), io:format("Waiting for OS procs to terminate...\n"), receive after 5*1000 -> ok end, Res. close_deaf_port_1(200, _) -> ok; close_deaf_port_1(N, Cmd) -> Timeout = integer_to_list(rand:uniform(5*1000)), try open_port({spawn_executable,Cmd},[{args,[Timeout]}]) of Port -> erlang:port_command(Port,"Hello, can you hear me!?!?"), port_close(Port), close_deaf_port_1(N+1, Cmd) catch _:eagain -> {comment, "Could not spawn more than " ++ integer_to_list(N) ++ " OS processes."} end. %% Test undocumented port_set_data/2 and port_get_data/1 %% Hammer from multiple processes a while %% and then abrubtly close the port (OTP-12208). port_setget_data(Config) when is_list(Config) -> ok = load_driver(proplists:get_value(data_dir, Config), "echo_drv"), Port = erlang:open_port({spawn_driver, "echo_drv"}, []), NSched = erlang:system_info(schedulers_online), HeapData = {1,2,3,<<"A heap binary">>,fun()->"This is fun"end, list_to_binary(lists:seq(1,100))}, PRs = lists:map(fun(I) -> spawn_opt(fun() -> port_setget_data_hammer(Port,HeapData,false,1) end, [monitor, {scheduler, I rem NSched}]) end, lists:seq(1,10)), receive after 100 -> ok end, Papa = self(), lists:foreach(fun({Pid,_}) -> Pid ! {Papa,prepare_for_close} end, PRs), lists:foreach(fun({Pid,_}) -> receive {Pid,prepare_for_close} -> ok end end, PRs), port_close(Port), lists:foreach(fun({Pid,Ref}) -> receive {'DOWN', Ref, process, Pid, normal} -> ok end end, PRs), ok. port_setget_data_hammer(Port, HeapData, IsSet0, N) -> Rand = rand:uniform(3), IsSet1 = try case Rand of 1 -> true = erlang:port_set_data(Port, atom), true; 2 -> true = erlang:port_set_data(Port, HeapData), true; 3 -> case erlang:port_get_data(Port) of atom -> true; HeapData -> true; undefined -> false=IsSet0 end end catch error:badarg -> true = get(prepare_for_close), io:format("~p did ~p rounds before port closed\n", [self(), N]), exit(normal) end, receive {Papa, prepare_for_close} -> put(prepare_for_close, true), Papa ! {self(),prepare_for_close} after 0 -> ok end, port_setget_data_hammer(Port, HeapData, IsSet1, N+1). wait_until(Fun) -> case catch Fun() of true -> ok; _ -> receive after 100 -> ok end, wait_until(Fun) end. %% Attempt to monitor pid as port, and port as pid mon_port_invalid_type(_Config) -> Port = hd(erlang:ports()), ?assertError(badarg, erlang:monitor(port, self())), ?assertError(badarg, erlang:monitor(process, Port)), ok. %% With local port mon_port_local(Config) -> Port1 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte Ref1 = erlang:monitor(port, Port1), ?assertMatch({proc_monitors, true, port_monitored_by, true}, port_is_monitored(self(), Port1)), Port1 ! {self(), {command, <<"1">>}}, % port test will close self immediately receive ExitP1 -> ?assertMatch({'DOWN', Ref1, port, Port1, _}, ExitP1) after 1000 -> ?assert(false) end, ?assertMatch({proc_monitors, false, port_monitored_by, false}, port_is_monitored(self(), Port1)), %% Trying to re-monitor a port which exists but is not healthy will %% succeed but then will immediately send DOWN Ref2 = erlang:monitor(port, Port1), receive ExitP2 -> ?assertMatch({'DOWN', Ref2, port, Port1, _}, ExitP2) after 1000 -> ?assert(false) end, ok. %% With remote port on remote node (should fail) mon_port_remote_on_remote(_Config) -> Port3 = binary_to_term(<<131, 102, % Ext term format: PORT_EXT 100, 0, 13, "fgsfds@fgsfds", % Node :: ATOM_EXT 1:32/big, % Id 0>>), % Creation ?assertError(badarg, erlang:monitor(port, Port3)), ok. %% Remote port belongs to this node and does not exist %% Port4 produces #Port<0.167772160> which should not exist in a test run mon_port_bad_remote_on_local(_Config) -> Port4 = binary_to_term(<<131, 102, % Ext term format: PORT_EXT 100, 0, 13, "nonode@nohost", % Node 167772160:32/big, % Id 0>>), % Creation ?assertError(badarg, erlang:monitor(port, Port4)), ok. %% Monitor owner (origin) dies before port is closed mon_port_origin_dies(Config) -> Port5 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte Self5 = self(), Proc5 = spawn(fun() -> Self5 ! test5_started, erlang:monitor(port, Port5), receive stop -> ok end end), erlang:monitor(process, Proc5), % we want to sync with its death receive test5_started -> ok after 1000 -> ?assert(false) end, ?assertMatch({proc_monitors, true, port_monitored_by, true}, port_is_monitored(Proc5, Port5)), Proc5 ! stop, % receive from monitor (removing race condition) receive ExitP5 -> ?assertMatch({'DOWN', _, process, Proc5, _}, ExitP5) after 1000 -> ?assert(false) end, ?assertMatch({proc_monitors, false, port_monitored_by, false}, port_is_monitored(Proc5, Port5)), Port5 ! {self(), {command, <<"1">>}}, % make port quit ok. %% Port and Monitor owner dies before port is closed %% This testcase checks for a regression memory leak in erts %% when the controlling and monitoring process is the same process %% and the process dies mon_port_owner_dies(Config) -> Self = self(), Proc = spawn(fun() -> Port = create_port(Config, ["-h1", "-q"]), Self ! {test_started, Port}, erlang:monitor(port, Port), receive stop -> ok end end), erlang:monitor(process, Proc), % we want to sync with its death Port = receive {test_started,P} -> P after 1000 -> ?assert(false) end, ?assertMatch({proc_monitors, true, port_monitored_by, true}, port_is_monitored(Proc, Port)), Proc ! stop, %% receive from monitor receive ExitP5 -> ?assertMatch({'DOWN', _, process, Proc, _}, ExitP5) after 1000 -> ?assert(false) end, ok. %% Monitor a named port mon_port_named(Config) -> Name6 = test_port6, Port6 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte erlang:register(Name6, Port6), erlang:monitor(port, Name6), ?assertMatch({proc_monitors, true, port_monitored_by, true}, port_is_monitored(self(), Name6)), Port6 ! {self(), {command, <<"1">>}}, % port test will close self immediately receive ExitP6 -> ?assertMatch({'DOWN', _, port, {Name6, _}, _}, ExitP6) after 1000 -> ?assert(false) end, ?assertMatch({proc_monitors, false, port_monitored_by, false}, port_is_monitored(self(), Name6)), ok. %% Named does not exist: Should succeed but immediately send 'DOWN' mon_port_bad_named(_Config) -> Name7 = test_port7, erlang:monitor(port, Name7), receive {'DOWN', _, port, {Name7, _}, noproc} -> ok after 1000 -> ?assert(false) end, ok. %% Monitor a pid and demonitor by ref mon_port_pid_demonitor(Config) -> Port8 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte Ref8 = erlang:monitor(port, Port8), ?assertMatch({proc_monitors, true, port_monitored_by, true}, port_is_monitored(self(), Port8)), erlang:demonitor(Ref8), ?assertMatch({proc_monitors, false, port_monitored_by, false}, port_is_monitored(self(), Port8)), Port8 ! {self(), {command, <<"1">>}}, % port test will close self immediately ok. %% Monitor by name and demonitor by ref mon_port_name_demonitor(Config) -> Name9 = test_port9, Port9 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte erlang:register(Name9, Port9), Ref9 = erlang:monitor(port, Name9), ?assertMatch({proc_monitors, true, port_monitored_by, true}, port_is_monitored(self(), Name9)), erlang:demonitor(Ref9), ?assertMatch({proc_monitors, false, port_monitored_by, false}, port_is_monitored(self(), Name9)), Port9 ! {self(), {command, <<"1">>}}, % port test will close self immediately ok. %% 1. Spawn a port which will sleep 3 seconds %% 2. Port driver and dies horribly (via C driver_failure call). This should %% mark port as exiting or something. %% 3. While the command happens, a monitor is requested on the port mon_port_driver_die(Config) -> erlang:process_flag(scheduler, 1), Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "sleep_failure_drv"), Port = open_port({spawn, "sleep_failure_drv"}, []), Self = self(), erlang:spawn_opt(fun() -> timer:sleep(250), Ref = erlang:monitor(port, Port), %% Now check that msg actually arrives receive {'DOWN', Ref, _Port2, _, _} = M -> Self ! M after 3000 -> Self ! no_down_message end end,[{scheduler, 2}]), Port ! {self(), {command, "Fail, please!"}}, receive A when is_atom(A) -> ?assertEqual(A, 'A_should_be_printed'); {'DOWN', _R, port, Port, noproc} -> ok; {'DOWN', _R, _P, _, _} = M -> ct:fail({got_wrong_down,M}) after 5000 -> ?assert(false) end, ok. %% 1. Spawn a port which will sleep 3 seconds %% 2. Monitor port %% 3. Port driver and dies horribly (via C driver_failure call). This should %% mark port as exiting or something. %% 4. While the command happens, a demonitor is requested on the port mon_port_driver_die_demonitor(Config) -> erlang:process_flag(scheduler, 1), Path = proplists:get_value(data_dir, Config), ok = load_driver(Path, "sleep_failure_drv"), Port = open_port({spawn, "sleep_failure_drv"}, []), Self = self(), erlang:spawn_opt( fun() -> Ref = erlang:monitor(port, Port), Self ! Ref, timer:sleep(250), erlang:demonitor(Ref), %% Now check that msg still arrives, %% the demon should have arrived after %% the port exited receive {'DOWN', Ref, _Port2, _, _} = M -> Self ! M after 3000 -> Self ! no_down_message end end,[{scheduler, 2}]), Ref = receive R -> R end, Port ! {self(), {command, "Fail, please!"}}, receive {'DOWN', Ref, port, Port, normal} -> ok; {'DOWN', _R, _P, _, _} = M -> ct:fail({got_wrong_down,M}) after 5000 -> ?assert(false) end, ok. %% @doc Makes a controllable port for testing. Underlying mechanism of this %% port is not important, only important is our ability to close/kill it or %% have it monitored. create_port(Config, Args) -> DataDir = ?config(data_dir, Config), %% Borrow port test utility from port SUITE Program = filename:join([DataDir, "port_test"]), erlang:open_port({spawn_executable, Program}, [{args, Args}]). %% @doc Checks if process Pid exists, and if so, if its monitoring (or not) %% the Port (or if port doesn't exist, we assume answer is no). port_is_monitored(Pid, Port) when is_pid(Pid), is_port(Port) -> %% Variant for when port is a port id (port()) A = case erlang:process_info(Pid, monitors) of undefined -> false; {monitors, ProcMTargets} -> lists:member({port, Port}, ProcMTargets) end, B = case erlang:port_info(Port, monitored_by) of undefined -> false; {monitored_by, PortMonitors} -> lists:member(Pid, PortMonitors) end, {proc_monitors, A, port_monitored_by, B}; port_is_monitored(Pid, PortName) when is_pid(Pid), is_atom(PortName) -> %% Variant for when port is an atom A = case erlang:process_info(Pid, monitors) of undefined -> false; {monitors, ProcMTargets} -> lists:member({port, {PortName, node()}}, ProcMTargets) end, B = case erlang:whereis(PortName) of undefined -> false; % name is not registered or is dead PortId -> case erlang:port_info(PortId, monitored_by) of undefined -> false; % is dead {monitored_by, PortMonitors} -> lists:member(Pid, PortMonitors) end end, {proc_monitors, A, port_monitored_by, B}.