1: %% 2: %% %CopyrightBegin% 3: %% 4: %% Copyright Ericsson AB 1997-2013. All Rights Reserved. 5: %% 6: %% The contents of this file are subject to the Erlang Public License, 7: %% Version 1.1, (the "License"); you may not use this file except in 8: %% compliance with the License. You should have received a copy of the 9: %% Erlang Public License along with this software. If not, it can be 10: %% retrieved online at http://www.erlang.org/. 11: %% 12: %% Software distributed under the License is distributed on an "AS IS" 13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14: %% the License for the specific language governing rights and limitations 15: %% under the License. 16: %% 17: %% %CopyrightEnd% 18: %% 19: 20: -module(port_SUITE). 21: 22: %%% 23: %%% Author: Bjorn Gustavsson; iter_max_ports contributed by Peter Hogfeldt. 24: %%% 25: 26: %% 27: %% There are a lot of things to test with open_port(Name, Settings). 28: %% 29: %% Name can be 30: %% 31: %% {spawn, Command} 32: %% which according to The Book and the manual page starts an 33: %% external program. That is not true. It might very well be 34: %% a linked-in program (the notion of 'linked-in driver' is 35: %% silly, since any driver is 'linked-in'). 36: %% [Spawn of external program is tested.] 37: %% 38: %% Atom 39: %% Read all contents of Atom, or write to it. 40: %% 41: %% {fd, In, Out} 42: %% Open file descriptors In and Out. [Not tested] 43: %% 44: %% PortSettings can be 45: %% 46: %% {packet, N} 47: %% N is 1, 2 or 4. 48: %% 49: %% stream (default) 50: %% Without packet length. 51: %% 52: %% use_stdio (default for spawned ports) 53: %% The spawned process use file descriptors 0 and 1 for I/O. 54: %% 55: %% nouse_stdio [Not tested] 56: %% Use filedescriptors 3 and 4. This option is probably only 57: %% meaningful on Unix. 58: %% 59: %% in (default for Atom) 60: %% Input only (from Erlang's point of view). 61: %% 62: %% out 63: %% Output only (from Erlang's point of view). 64: %% 65: %% binary 66: %% The port is a binary port, i.e. messages received and sent 67: %% to a port are binaries. 68: %% 69: %% eof 70: %% Port is not closed on eof and will not send an exit signal, 71: %% instead it will send a {Port, eof} to the controlling process 72: %% (output can still be sent to the port (??)). 73: %% 74: 75: 76: -export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2, 77: init_per_testcase/2, end_per_testcase/2, 78: init_per_suite/1, end_per_suite/1, 79: stream_small/1, stream_big/1, 80: basic_ping/1, slow_writes/1, bad_packet/1, bad_port_messages/1, 81: mul_basic/1, mul_slow_writes/1, 82: dying_port/1, port_program_with_path/1, 83: open_input_file_port/1, open_output_file_port/1, 84: iter_max_ports/1, eof/1, input_only/1, output_only/1, 85: name1/1, 86: t_binary/1, parallell/1, t_exit/1, 87: env/1, bad_env/1, cd/1, exit_status/1, 88: tps_16_bytes/1, tps_1K/1, line/1, stderr_to_stdout/1, 89: otp_3906/1, otp_4389/1, win_massive/1, win_massive_client/1, 90: mix_up_ports/1, otp_5112/1, otp_5119/1, otp_6224/1, 91: exit_status_multi_scheduling_block/1, ports/1, 92: spawn_driver/1, spawn_executable/1, close_deaf_port/1, 93: unregister_name/1, parallelism_option/1]). 94: 95: -export([do_iter_max_ports/2]). 96: 97: %% Internal exports. 98: -export([tps/3]). 99: -export([otp_3906_forker/5, otp_3906_start_forker_starter/4]). 100: -export([env_slave_main/1]). 101: 102: -include_lib("test_server/include/test_server.hrl"). 103: -include_lib("kernel/include/file.hrl"). 104: 105: suite() -> [{ct_hooks,[ts_install_cth]}]. 106: 107: all() -> 108: [otp_6224, {group, stream}, basic_ping, slow_writes, 109: bad_packet, bad_port_messages, {group, options}, 110: {group, multiple_packets}, parallell, dying_port, 111: port_program_with_path, open_input_file_port, 112: open_output_file_port, name1, env, bad_env, cd, 113: exit_status, iter_max_ports, t_exit, {group, tps}, line, 114: stderr_to_stdout, otp_3906, otp_4389, win_massive, 115: mix_up_ports, otp_5112, otp_5119, 116: exit_status_multi_scheduling_block, ports, spawn_driver, 117: spawn_executable, close_deaf_port, unregister_name, 118: parallelism_option]. 119: 120: groups() -> 121: [{stream, [], [stream_small, stream_big]}, 122: {options, [], [t_binary, eof, input_only, output_only]}, 123: {multiple_packets, [], [mul_basic, mul_slow_writes]}, 124: {tps, [], [tps_16_bytes, tps_1K]}]. 125: 126: init_per_group(_GroupName, Config) -> 127: Config. 128: 129: end_per_group(_GroupName, Config) -> 130: Config. 131: 132: 133: -define(DEFAULT_TIMEOUT, ?t:minutes(5)). 134: 135: init_per_testcase(Case, Config) -> 136: [{testcase, Case} |Config]. 137: 138: end_per_testcase(_Case, _Config) -> 139: ok. 140: 141: init_per_suite(Config) when is_list(Config) -> 142: ignore_cores:init(Config). 143: 144: end_per_suite(Config) when is_list(Config) -> 145: ignore_cores:fini(Config). 146: 147: 148: -define(WIN_MASSIVE_PORT, 50000). 149: 150: %% Tests that you can open a massive amount of ports (sockets) 151: %% on a Windows machine given the correct environment. 152: win_massive(Config) when is_list(Config) -> 153: case os:type() of 154: {win32,_} -> 155: do_win_massive(); 156: _ -> 157: {skip,"Only on Windows."} 158: end. 159: 160: do_win_massive() -> 161: Dog = test_server:timetrap(test_server:seconds(360)), 162: SuiteDir = filename:dirname(code:which(?MODULE)), 163: Ports = " +Q 8192", 164: {ok, Node} = 165: test_server:start_node(win_massive, 166: slave, 167: [{args, " -pa " ++ SuiteDir ++ Ports}]), 168: ok = rpc:call(Node,?MODULE,win_massive_client,[3000]), 169: test_server:stop_node(Node), 170: test_server:timetrap_cancel(Dog), 171: ok. 172: 173: win_massive_client(N) -> 174: {ok,P}=gen_tcp:listen(?WIN_MASSIVE_PORT,[{reuseaddr,true}]), 175: L = win_massive_loop(P,N), 176: Len = length(L), 177: lists:foreach(fun(E) -> 178: gen_tcp:close(E) 179: end, 180: L), 181: case Len div 2 of 182: N -> 183: ok; 184: _Else -> 185: {too_few, Len} 186: end. 187: 188: win_massive_loop(_,0) -> 189: []; 190: win_massive_loop(P,N) -> 191: case (catch gen_tcp:connect("localhost",?WIN_MASSIVE_PORT,[])) of 192: {ok,A} -> 193: case (catch gen_tcp:accept(P)) of 194: {ok,B} -> 195: %erlang:display(N), 196: [A,B|win_massive_loop(P,N-1)]; 197: _Else -> 198: [A] 199: end; 200: _Else0 -> 201: [] 202: end. 203: 204: 205: 206: 207: 208: %% Test that we can send a stream of bytes and get it back. 209: %% We will send only a small amount of data, to avoid deadlock. 210: 211: stream_small(Config) when is_list(Config) -> 212: Dog = test_server:timetrap(test_server:seconds(10)), 213: stream_ping(Config, 512, "", []), 214: stream_ping(Config, 1777, "", []), 215: stream_ping(Config, 1777, "-s512", []), 216: test_server:timetrap_cancel(Dog), 217: ok. 218: 219: %% Send big amounts of data (much bigger than the buffer size in port test). 220: %% This will deadlock the emulator if the spawn driver haven't proper 221: %% non-blocking reads and writes. 222: 223: stream_big(Config) when is_list(Config) -> 224: Dog = test_server:timetrap(test_server:seconds(180)), 225: stream_ping(Config, 43755, "", []), 226: stream_ping(Config, 100000, "", []), 227: stream_ping(Config, 77777, " -s40000", []), 228: test_server:timetrap_cancel(Dog), 229: ok. 230: 231: %% Sends packet with header size of 1, 2, and 4, with packets of various 232: %% sizes. 233: 234: basic_ping(Config) when is_list(Config) -> 235: Dog = test_server:timetrap(test_server:seconds(120)), 236: ping(Config, sizes(1), 1, "", []), 237: ping(Config, sizes(2), 2, "", []), 238: ping(Config, sizes(4), 4, "", []), 239: test_server:timetrap_cancel(Dog), 240: ok. 241: 242: %% Let the port program insert delays between characters sent back to 243: %% Erlang, to test that the Erlang emulator can handle a packet coming in 244: %% small chunks rather than all at once. 245: 246: slow_writes(Config) when is_list(Config) -> 247: Dog = test_server:timetrap(test_server:seconds(20)), 248: ping(Config, [8], 4, "-s1", []), 249: ping(Config, [10], 2, "-s2", []), 250: test_server:timetrap_cancel(Dog), 251: ok. 252: 253: bad_packet(doc) -> 254: ["Test that we get {'EXIT', Port, einval} if we try to send a bigger " 255: "packet than the packet header allows."]; 256: bad_packet(Config) when is_list(Config) -> 257: Dog = test_server:timetrap(test_server:seconds(10)), 258: PortTest = port_test(Config), 259: process_flag(trap_exit, true), 260: 261: bad_packet(PortTest, 1, 256), 262: bad_packet(PortTest, 1, 257), 263: bad_packet(PortTest, 2, 65536), 264: bad_packet(PortTest, 2, 65537), 265: 266: test_server:timetrap_cancel(Dog), 267: ok. 268: 269: bad_packet(PortTest, HeaderSize, PacketSize) -> 270: P = open_port({spawn, PortTest}, [{packet, HeaderSize}]), 271: P ! {self(), {command, make_zero_packet(PacketSize)}}, 272: receive 273: {'EXIT', P, einval} -> ok; 274: Other -> test_server:fail({unexpected_message, Other}) 275: end. 276: 277: make_zero_packet(0) -> []; 278: make_zero_packet(N) when N rem 2 == 0 -> 279: P = make_zero_packet(N div 2), 280: [P|P]; 281: make_zero_packet(N) -> 282: P = make_zero_packet(N div 2), 283: [0, P|P]. 284: 285: %% Test sending bad messages to a port. 286: bad_port_messages(Config) when is_list(Config) -> 287: Dog = test_server:timetrap(test_server:seconds(10)), 288: PortTest = port_test(Config), 289: process_flag(trap_exit, true), 290: 291: bad_message(PortTest, {a,b}), 292: bad_message(PortTest, {a}), 293: bad_message(PortTest, {self(),{command,bad_command}}), 294: bad_message(PortTest, {self(),{connect,no_pid}}), 295: 296: test_server:timetrap_cancel(Dog), 297: ok. 298: 299: bad_message(PortTest, Message) -> 300: P = open_port({spawn,PortTest}, []), 301: P ! Message, 302: receive 303: {'EXIT',P,badsig} -> ok; 304: Other -> test_server:fail({unexpected_message, Other}) 305: end. 306: 307: %% Tests various options (stream and {packet, Number} are implicitly 308: %% tested in other test cases). 309: 310: 311: %% Tests the 'binary' option for a port. 312: 313: t_binary(Config) when is_list(Config) -> 314: Dog = test_server:timetrap(test_server:seconds(300)), 315: 316: %% Packet mode. 317: ping(Config, sizes(1), 1, "", [binary]), 318: ping(Config, sizes(2), 2, "", [binary]), 319: ping(Config, sizes(4), 4, "", [binary]), 320: 321: %% Stream mode. 322: stream_ping(Config, 435, "", [binary]), 323: stream_ping(Config, 43755, "", [binary]), 324: stream_ping(Config, 100000, "", [binary]), 325: 326: test_server:timetrap_cancel(Dog), 327: ok. 328: 329: name1(Config) when is_list(Config) -> 330: Dog = test_server:timetrap(test_server:seconds(100)), 331: PortTest = port_test(Config), 332: Command = lists:concat([PortTest, " "]), 333: P = open_port({spawn, Command}, []), 334: register(myport, P), 335: P = whereis(myport), 336: Text = "hej", 337: myport ! {self(), {command, Text}}, 338: receive 339: {P, {data, Text}} -> 340: ok 341: end, 342: myport ! {self(), close}, 343: receive 344: {P, closed} -> ok 345: end, 346: undefined = whereis(myport), 347: test_server:timetrap_cancel(Dog), 348: ok. 349: 350: %% Test that the 'eof' option works. 351: 352: eof(Config) when is_list(Config) -> 353: Dog = test_server:timetrap(test_server:seconds(100)), 354: PortTest = port_test(Config), 355: Command = lists:concat([PortTest, " -h0 -q"]), 356: P = open_port({spawn, Command}, [eof]), 357: receive 358: {P, eof} -> 359: ok 360: end, 361: P ! {self(), close}, 362: receive 363: {P, closed} -> ok 364: end, 365: test_server:timetrap_cancel(Dog), 366: ok. 367: 368: %% Tests that the 'in' option for a port works. 369: 370: input_only(Config) when is_list(Config) -> 371: Dog = test_server:timetrap(test_server:seconds(300)), 372: expect_input(Config, [0, 1, 10, 13, 127, 128, 255], 1, "", [in]), 373: expect_input(Config, [0, 1, 255, 2048], 2, "", [in]), 374: expect_input(Config, [0, 1, 255, 2048], 4, "", [in]), 375: expect_input(Config, [0, 1, 10, 13, 127, 128, 255], 376: 1, "", [in, binary]), 377: test_server:timetrap_cancel(Dog), 378: ok. 379: 380: %% Tests that the 'out' option for a port works. 381: 382: output_only(Config) when is_list(Config) -> 383: Dog = test_server:timetrap(test_server:seconds(100)), 384: Dir = ?config(priv_dir, Config), 385: Filename = filename:join(Dir, "output_only_stream"), 386: output_and_verify(Config, Filename, "-h0", 387: random_packet(35777, "echo")), 388: test_server:timetrap_cancel(Dog), 389: ok. 390: 391: output_and_verify(Config, Filename, Options, Data) -> 392: PortTest = port_test(Config), 393: Command = lists:concat([PortTest, " ", 394: Options, " -o", Filename]), 395: Port = open_port({spawn, Command}, [out]), 396: Port ! {self(), {command, Data}}, 397: Port ! {self(), close}, 398: receive 399: {Port, closed} -> ok 400: end, 401: Wait_time = 500, 402: test_server:sleep(Wait_time), 403: {ok, Written} = file:read_file(Filename), 404: Data = binary_to_list(Written), 405: ok. 406: 407: %% Test that receiving several packages written in the same 408: %% write operation works. 409: 410: 411: %% Basic test of receiving multiple packages, written in 412: %% one operation by the other end. 413: mul_basic(Config) when is_list(Config) -> 414: Dog = test_server:timetrap(test_server:seconds(600)), 415: expect_input(Config, [0, 1, 255, 10, 13], 1, "", []), 416: expect_input(Config, [0, 10, 13, 1600, 32767, 65535], 2, "", []), 417: expect_input(Config, [10, 70000], 4, "", []), 418: test_server:timetrap_cancel(Dog), 419: ok. 420: 421: %% Test reading a buffer consisting of several packets, some 422: %% of which might be incomplete. (The port program builds 423: %% a buffer with several packets, but writes it in chunks with 424: %% delays in between.) 425: 426: mul_slow_writes(Config) when is_list(Config) -> 427: Dog = test_server:timetrap(test_server:seconds(250)), 428: expect_input(Config, [0, 20, 255, 10, 1], 1, "-s64", []), 429: test_server:timetrap_cancel(Dog), 430: ok. 431: 432: %% Runs several port tests in parallell. Each individual test 433: %% finishes in about 5 seconds. Running in parallell, all tests 434: %% should also finish in about 5 seconds. 435: 436: parallell(Config) when is_list(Config) -> 437: Dog = test_server:timetrap(test_server:seconds(300)), 438: Testers = [ 439: fun() -> stream_ping(Config, 1007, "-s100", []) end, 440: fun() -> stream_ping(Config, 10007, "-s1000", []) end, 441: fun() -> stream_ping(Config, 10007, "-s1000", []) end, 442: 443: fun() -> expect_input(Config, [21, 22, 23, 24, 25], 1, 444: "-s10", [in]) end, 445: 446: fun() -> ping(Config, [10], 1, "-d", []) end, 447: fun() -> ping(Config, [20000], 2, "-d", []) end, 448: fun() -> ping(Config, [101], 1, "-s10", []) end, 449: fun() -> ping(Config, [1001], 2, "-s100", []) end, 450: fun() -> ping(Config, [10001], 4, "-s1000", []) end, 451: 452: fun() -> ping(Config, [501, 501], 2, "-s100", []) end, 453: fun() -> ping(Config, [11, 12, 13, 14, 11], 1, "-s5", []) end], 454: process_flag(trap_exit, true), 455: Pids = lists:map(fun fun_spawn/1, Testers), 456: wait_for(Pids), 457: test_server:timetrap_cancel(Dog), 458: ok. 459: 460: wait_for([]) -> 461: ok; 462: wait_for(Pids) -> 463: io:format("Waiting for ~p", [Pids]), 464: receive 465: {'EXIT', Pid, normal} -> 466: wait_for(lists:delete(Pid, Pids)); 467: Other -> 468: test_server:fail({bad_exit, Other}) 469: end. 470: 471: %% Tests starting port programs that terminate by themselves. 472: %% This used to cause problems on Windows. 473: 474: dying_port(suite) -> []; 475: dying_port(Config) when is_list(Config) -> 476: Dog = test_server:timetrap(test_server:seconds(150)), 477: process_flag(trap_exit, true), 478: 479: P1 = make_dying_port(Config), 480: P2 = make_dying_port(Config), 481: P3 = make_dying_port(Config), 482: P4 = make_dying_port(Config), 483: P5 = make_dying_port(Config), 484: 485: %% This should be big enough to be sure to block in the write. 486: Garbage = random_packet(16384), 487: 488: P1 ! {self(), {command, Garbage}}, 489: P3 ! {self(), {command, Garbage}}, 490: P5 ! {self(), {command, Garbage}}, 491: 492: wait_for_port_exit(P1), 493: wait_for_port_exit(P2), 494: wait_for_port_exit(P3), 495: wait_for_port_exit(P4), 496: wait_for_port_exit(P5), 497: 498: test_server:timetrap_cancel(Dog), 499: ok. 500: 501: wait_for_port_exit(Port) -> 502: receive 503: {'EXIT', Port, _} -> 504: ok 505: end. 506: 507: make_dying_port(Config) when is_list(Config) -> 508: PortTest = port_test(Config), 509: Command = lists:concat([PortTest, " -h0 -d -q"]), 510: open_port({spawn, Command}, [stream]). 511: 512: %% Tests that port program with complete path (but without any 513: %% .exe extension) can be started, even if there is a file with 514: %% the same name but without the extension in the same directory. 515: %% (In practice, the file with the same name could be a Unix 516: %% executable.) 517: %% 518: %% This used to failed on Windows (the .exe extension had to be 519: %% explicitly given). 520: %% 521: %% This testcase works on Unix, but is not very useful. 522: 523: port_program_with_path(suite) -> []; 524: port_program_with_path(Config) when is_list(Config) -> 525: Dog = test_server:timetrap(test_server:seconds(100)), 526: DataDir = ?config(data_dir, Config), 527: PrivDir = ?config(priv_dir, Config), 528: 529: %% Create a copy of the port test program in a directory not 530: %% included in PATH (i.e. in priv_dir), with the name 'my_port_test.exe'. 531: %% Also, place a file named 'my_port_test' in the same directory. 532: %% This used to confuse the CreateProcess() call in spawn driver. 533: %% (On Unix, there will be a single file created, which will be 534: %% a copy of the port program.) 535: 536: PortTest = os:find_executable("port_test", DataDir), 537: io:format("os:find_executable(~p, ~p) returned ~p", 538: ["port_test", DataDir, PortTest]), 539: {ok, PortTestPgm} = file:read_file(PortTest), 540: NewName = filename:join(PrivDir, filename:basename(PortTest)), 541: RedHerring = filename:rootname(NewName), 542: ok = file:write_file(RedHerring, "I'm just here to confuse.\n"), 543: ok = file:write_file(NewName, PortTestPgm), 544: ok = file:write_file_info(NewName, #file_info{mode=8#111}), 545: PgmWithPathAndNoExt = filename:rootname(NewName), 546: 547: %% Open the port using the path to the copied port test program, 548: %% but without the .exe extension, and verified that it was started. 549: %% 550: %% If the bug is present the open_port call will fail with badarg. 551: 552: Command = lists:concat([PgmWithPathAndNoExt, " -h2"]), 553: P = open_port({spawn, Command}, [{packet, 2}]), 554: Message = "echo back to me", 555: P ! {self(), {command, Message}}, 556: receive 557: {P, {data, Message}} -> 558: ok 559: end, 560: test_server:timetrap_cancel(Dog), 561: ok. 562: 563: 564: %% Tests that files can be read using open_port(Filename, [in]). 565: %% This used to fail on Windows. 566: open_input_file_port(suite) -> []; 567: open_input_file_port(Config) when is_list(Config) -> 568: Dog = test_server:timetrap(test_server:seconds(10)), 569: PrivDir = ?config(priv_dir, Config), 570: 571: %% Create a file with the file driver and read it back using 572: %% open_port/2. 573: 574: MyFile1 = filename:join(PrivDir, "my_input_file"), 575: FileData1 = "An input file", 576: ok = file:write_file(MyFile1, FileData1), 577: case open_port(MyFile1, [in]) of 578: InputPort when is_port(InputPort) -> 579: receive 580: {InputPort, {data, FileData1}} -> 581: ok 582: end 583: end, 584: test_server:timetrap_cancel(Dog), 585: ok. 586: 587: %% Tests that files can be written using open_port(Filename, [out]). 588: open_output_file_port(suite) -> []; 589: open_output_file_port(Config) when is_list(Config) -> 590: Dog = test_server:timetrap(test_server:seconds(100)), 591: PrivDir = ?config(priv_dir, Config), 592: 593: %% Create a file with open_port/2 and read it back with 594: %% the file driver. 595: 596: MyFile2 = filename:join(PrivDir, "my_output_file"), 597: FileData2_0 = "A file created ", 598: FileData2_1 = "with open_port/2.\n", 599: FileData2 = FileData2_0 ++ FileData2_1, 600: OutputPort = open_port(MyFile2, [out]), 601: OutputPort ! {self(), {command, FileData2_0}}, 602: OutputPort ! {self(), {command, FileData2_1}}, 603: OutputPort ! {self(), close}, 604: {ok, Bin} = file:read_file(MyFile2), 605: FileData2 = binary_to_list(Bin), 606: 607: test_server:timetrap_cancel(Dog), 608: ok. 609: 610: %% 611: %% Open as many ports as possible. Do this several times and check 612: %% that we get the same number of ports every time. 613: %% 614: 615: iter_max_ports(suite) -> []; 616: iter_max_ports(Config) when is_list(Config) -> 617: %% The child_setup program might dump core if we get out of memory. 618: %% This is hard to do anything about and is harmless. We run this test 619: %% in a working directory with an ignore_core_files file which will make 620: %% the search for core files ignore cores generated by this test. 621: %% 622: Config2 = ignore_cores:setup(?MODULE, iter_max_ports, Config, true), 623: try 624: iter_max_ports_test(Config2) 625: after 626: ignore_cores:restore(Config2) 627: end. 628: 629: 630: iter_max_ports_test(Config) -> 631: Dog = test_server:timetrap(test_server:minutes(30)), 632: PortTest = port_test(Config), 633: Command = lists:concat([PortTest, " -h0 -q"]), 634: Iters = case os:type() of 635: {win32,_} -> 4; 636: _ -> 10 637: end, 638: %% Run on a different node in order to limit the effect if this test fails. 639: Dir = filename:dirname(code:which(?MODULE)), 640: {ok,Node} = test_server:start_node(test_iter_max_socks,slave, 641: [{args,"+Q 2048 -pa " ++ Dir}]), 642: L = rpc:call(Node,?MODULE,do_iter_max_ports,[Iters, Command]), 643: test_server:stop_node(Node), 644: 645: io:format("Result: ~p",[L]), 646: all_equal(L), 647: all_equal(L), 648: test_server:timetrap_cancel(Dog), 649: {comment, "Max ports: " ++ integer_to_list(hd(L))}. 650: 651: do_iter_max_ports(N, Command) when N > 0 -> 652: [max_ports(Command)| do_iter_max_ports(N-1, Command)]; 653: do_iter_max_ports(_, _) -> 654: []. 655: 656: all_equal([E,E|T]) -> 657: all_equal([E|T]); 658: all_equal([_]) -> ok; 659: all_equal([]) -> ok. 660: 661: max_ports(Command) -> 662: test_server:sleep(500), 663: Ps = open_ports({spawn, Command}, [eof]), 664: N = length(Ps), 665: close_ports(Ps), 666: io:format("Got ~p ports\n",[N]), 667: N. 668: 669: close_ports([P|Ps]) -> 670: P ! {self(), close}, 671: receive 672: {P,closed} -> 673: ok 674: end, 675: close_ports(Ps); 676: close_ports([]) -> 677: ok. 678: 679: open_ports(Name, Settings) -> 680: test_server:sleep(5), 681: case catch open_port(Name, Settings) of 682: P when is_port(P) -> 683: [P| open_ports(Name, Settings)]; 684: {'EXIT', {Code, _}} -> 685: case Code of 686: enfile -> 687: []; 688: emfile -> 689: []; 690: system_limit -> 691: []; 692: enomem -> 693: []; 694: Other -> 695: test_server:fail({open_ports, Other}) 696: end; 697: Other -> 698: test_server:fail({open_ports, Other}) 699: end. 700: 701: %% Tests that exit(Port, Term) works (has been known to crash the emulator). 702: 703: t_exit(suite) -> []; 704: t_exit(Config) when is_list(Config) -> 705: process_flag(trap_exit, true), 706: Pid = fun_spawn(fun suicide_port/1, [Config]), 707: receive 708: {'EXIT', Pid, die} -> 709: ok; 710: Other -> 711: test_server:fail({bad_message, Other}) 712: end. 713: 714: suicide_port(Config) when is_list(Config) -> 715: Port = port_expect(Config, [], 0, "", []), 716: exit(Port, die), 717: receive after infinity -> ok end. 718: 719: 720: tps_16_bytes(doc) -> ""; 721: tps_16_bytes(suite) -> []; 722: tps_16_bytes(Config) when is_list(Config) -> 723: tps(16, Config). 724: 725: tps_1K(doc) -> ""; 726: tps_1K(suite) -> []; 727: tps_1K(Config) when is_list(Config) -> 728: tps(1024, Config). 729: 730: tps(Size, Config) -> 731: Dog = test_server:timetrap(test_server:seconds(300)), 732: PortTest = port_test(Config), 733: Packet = list_to_binary(random_packet(Size, "e")), 734: Port = open_port({spawn, PortTest}, [binary, {packet, 2}]), 735: Transactions = 10000, 736: {Elapsed, ok} = test_server:timecall(?MODULE, tps, 737: [Port, Packet, Transactions]), 738: test_server:timetrap_cancel(Dog), 739: {comment, integer_to_list(trunc(Transactions/Elapsed+0.5)) ++ " transactions/s"}. 740: 741: tps(_Port, _Packet, 0) -> ok; 742: tps(Port, Packet, N) -> 743: port_command(Port, Packet), 744: receive 745: {Port, {data, Packet}} -> 746: tps(Port, Packet, N-1); 747: Other -> 748: test_server:fail({bad_message, Other}) 749: end. 750: 751: %% Line I/O test 752: line(Config) when is_list(Config) -> 753: Siz = 110, 754: Dog = test_server:timetrap(test_server:seconds(300)), 755: Packet1 = random_packet(Siz), 756: Packet2 = random_packet(Siz div 2), 757: %% Test that packets are split into lines 758: port_expect(Config,[{lists:append([Packet1, io_lib:nl(), Packet2, 759: io_lib:nl()]), 760: [{eol, Packet1}, {eol, Packet2}]}], 761: 0, "", [{line,Siz}]), 762: %% Test the same for binaries 763: port_expect(Config,[{lists:append([Packet1, io_lib:nl(), Packet2, 764: io_lib:nl()]), 765: [{eol, Packet1}, {eol, Packet2}]}], 766: 0, "", [{line,Siz},binary]), 767: %% Test that too long lines get split 768: port_expect(Config,[{lists:append([Packet1, io_lib:nl(), Packet1, 769: Packet2, io_lib:nl()]), 770: [{eol, Packet1}, {noeol, Packet1}, 771: {eol, Packet2}]}], 0, "", [{line,Siz}]), 772: %% Test that last output from closing port program gets received. 773: L1 = lists:append([Packet1, io_lib:nl(), Packet2]), 774: S1 = lists:flatten(io_lib:format("-l~w", [length(L1)])), 775: io:format("S1 = ~w, L1 = ~w~n", [S1,L1]), 776: port_expect(Config,[{L1, 777: [{eol, Packet1}, {noeol, Packet2}, eof]}], 0, 778: S1, [{line,Siz},eof]), 779: %% Test that lonely <CR> Don't get treated as newlines 780: port_expect(Config,[{lists:append([Packet1, [13], Packet2, 781: io_lib:nl()]), 782: [{noeol, Packet1}, {eol, [13 |Packet2]}]}], 783: 0, "", [{line,Siz}]), 784: %% Test that packets get built up to lines (delayed output from 785: %% port program) 786: port_expect(Config,[{Packet2,[]}, 787: {lists:append([Packet2, io_lib:nl(), 788: Packet1, io_lib:nl()]), 789: [{eol, lists:append(Packet2, Packet2)}, 790: {eol, Packet1}]}], 0, "-d", [{line,Siz}]), 791: %% Test that we get badarg if trying both packet and line 792: bad_argument(Config, [{packet, 5}, {line, 5}]), 793: test_server:timetrap_cancel(Dog), 794: ok. 795: 796: %%% Redirection of stderr test 797: stderr_to_stdout(suite) -> 798: []; 799: stderr_to_stdout(doc) -> 800: "Test that redirection of standard error to standard output works."; 801: stderr_to_stdout(Config) when is_list(Config) -> 802: Dog = test_server:timetrap(test_server:seconds(60)), 803: %% See that it works 804: Packet = random_packet(10), 805: port_expect(Config,[{Packet,[Packet]}], 0, "-e -l10", 806: [stderr_to_stdout]), 807: %% stream_ping(Config, 10, "-e", [stderr_to_stdout]), 808: %% See that it doesn't always happen (will generate garbage on stderr) 809: port_expect(Config,[{Packet,[eof]}], 0, "-e -l10", [line,eof]), 810: test_server:timetrap_cancel(Dog), 811: ok. 812: 813: 814: bad_argument(Config, ArgList) -> 815: PortTest = port_test(Config), 816: case catch open_port({spawn, PortTest}, ArgList) of 817: {'EXIT', {badarg, _}} -> 818: ok 819: end. 820: 821: 822: %% 'env' option 823: %% (Can perhaps be made smaller by calling the other utility functions 824: %% in this module.) 825: env(suite) -> 826: []; 827: env(doc) -> 828: ["Test that the 'env' option works"]; 829: env(Config) when is_list(Config) -> 830: Dog = test_server:timetrap(test_server:seconds(60)), 831: Priv = ?config(priv_dir, Config), 832: Temp = filename:join(Priv, "env_fun.bin"), 833: 834: PluppVal = "dirty monkey", 835: env_slave(Temp, [{"plupp",PluppVal}]), 836: 837: Long = "LongAndBoringEnvName", 838: os:putenv(Long, "nisse"), 839: 840: env_slave(Temp, [{"plupp",PluppVal}, 841: {"DIR_PLUPP","###glurfrik"}], 842: fun() -> 843: PluppVal = os:getenv("plupp"), 844: "###glurfrik" = os:getenv("DIR_PLUPP"), 845: "nisse" = os:getenv(Long) 846: end), 847: 848: 849: env_slave(Temp, [{"must_define_something","some_value"}, 850: {"certainly_not_existing",false}, 851: {"ends_with_equal", "value="}, 852: {Long,false}, 853: {"glurf","a glorfy string"}]), 854: 855: %% A lot of non existing variables (mingled with existing) 856: NotExistingList = [{lists:flatten(io_lib:format("V~p_not_existing",[X])),false} 857: || X <- lists:seq(1,150)], 858: ExistingList = [{lists:flatten(io_lib:format("V~p_existing",[X])),"a_value"} 859: || X <- lists:seq(1,150)], 860: env_slave(Temp, lists:sort(ExistingList ++ NotExistingList)), 861: 862: test_server:timetrap_cancel(Dog), 863: ok. 864: 865: env_slave(File, Env) -> 866: F = fun() -> 867: lists:foreach(fun({Name,Val}) -> 868: Val = os:getenv(Name) 869: end, Env) 870: end, 871: env_slave(File, Env, F). 872: 873: env_slave(File, Env, Body) -> 874: file:write_file(File, term_to_binary(Body)), 875: Program = atom_to_list(lib:progname()), 876: Dir = filename:dirname(code:which(?MODULE)), 877: Cmd = Program ++ " -pz " ++ Dir ++ 878: " -noinput -run " ++ ?MODULE_STRING ++ " env_slave_main " ++ 879: File ++ " -run erlang halt", 880: Port = open_port({spawn, Cmd}, [{env,Env},{line,256}]), 881: receive 882: {Port,{data,{eol,"ok"}}} -> 883: ok; 884: {Port,{data,{eol,Error}}} -> 885: io:format("~p\n", [Error]), 886: test_server:fail(); 887: Other -> 888: test_server:fail(Other) 889: end. 890: 891: env_slave_main([File]) -> 892: {ok,Body0} = file:read_file(File), 893: Body = binary_to_term(Body0), 894: case Body() of 895: {'EXIT',Reason} -> 896: io:format("Error: ~p\n", [Reason]); 897: _ -> 898: io:format("ok\n") 899: end, 900: init:stop(). 901: 902: 903: %% 'env' option 904: %% Test bad environments. 905: bad_env(Config) when is_list(Config) -> 906: try_bad_env([abbb]), 907: try_bad_env([{"key","value"}|{"another","value"}]), 908: try_bad_env([{"key","value","value2"}]), 909: try_bad_env([{"key",[a,b,c]}]), 910: try_bad_env([{"key",value}]), 911: try_bad_env({a,tuple}), 912: try_bad_env(42), 913: try_bad_env([a|b]), 914: try_bad_env(self()), 915: ok. 916: 917: try_bad_env(Env) -> 918: try open_port({spawn,"ls"}, [{env,Env}]) 919: catch 920: error:badarg -> ok 921: end. 922: 923: %% 'cd' option 924: %% (Can perhaps be made smaller by calling the other utility functions 925: %% in this module.) 926: cd(suite) -> 927: []; 928: cd(doc) -> 929: ["Test that the 'cd' option works"]; 930: cd(Config) when is_list(Config) -> 931: Dog = test_server:timetrap(test_server:seconds(60)), 932: 933: Program = atom_to_list(lib:progname()), 934: DataDir = ?config(data_dir, Config), 935: TestDir = filename:join(DataDir, "dir"), 936: Cmd = Program ++ " -pz " ++ DataDir ++ 937: " -noshell -s port_test pwd -s erlang halt", 938: _ = open_port({spawn, Cmd}, 939: [{cd, TestDir}, 940: {line, 256}]), 941: receive 942: {_, {data, {eol, String}}} -> 943: case filename_equal(String, TestDir) of 944: true -> 945: ok; 946: false -> 947: test_server:fail({cd, String}) 948: end; 949: Other2 -> 950: test_server:fail({env, Other2}) 951: end, 952: 953: test_server:timetrap_cancel(Dog), 954: ok. 955: 956: filename_equal(A, B) -> 957: case os:type() of 958: {win32, _} -> 959: win_filename_equal(A, B); 960: _ -> 961: A == B 962: end. 963: 964: win_filename_equal([], []) -> 965: true; 966: win_filename_equal([], _) -> 967: false; 968: win_filename_equal(_, []) -> 969: false; 970: win_filename_equal([C1 | Rest1], [C2 | Rest2]) -> 971: case tolower(C1) == tolower(C2) of 972: true -> 973: win_filename_equal(Rest1, Rest2); 974: false -> 975: false 976: end. 977: 978: tolower(C) when C >= $A, C =< $Z -> 979: C + 32; 980: tolower(C) -> 981: C. 982: 983: otp_3906(suite) -> 984: []; 985: otp_3906(doc) -> 986: ["Tests that child process deaths are managed correctly when there are " 987: " a large amount of concurrently dying children. See ticket OTP-3906."]; 988: otp_3906(Config) when is_list(Config) -> 989: case os:type() of 990: {unix, OSName} -> 991: otp_3906(Config, OSName); 992: _ -> 993: {skipped, "Only run on Unix systems"} 994: end. 995: 996: -define(OTP_3906_CHILDREN, 1000). 997: -define(OTP_3906_EXIT_STATUS, 17). 998: -define(OTP_3906_PROGNAME, "otp_3906"). 999: -define(OTP_3906_TICK_TIMEOUT, 5000). 1000: -define(OTP_3906_OSP_P_ERLP, 10). 1001: -define(OTP_3906_MAX_CONC_OSP, 50). 1002: 1003: otp_3906(Config, OSName) -> 1004: DataDir = filename:dirname(proplists:get_value(data_dir,Config)), 1005: {ok, Variables} = file:consult( 1006: filename:join([DataDir,"..","..", 1007: "test_server","variables"])), 1008: case lists:keysearch('CC', 1, Variables) of 1009: {value,{'CC', CC}} -> 1010: SuiteDir = filename:dirname(code:which(?MODULE)), 1011: PrivDir = ?config(priv_dir, Config), 1012: Prog = otp_3906_make_prog(CC, PrivDir), 1013: {ok, Node} = test_server:start_node(otp_3906, 1014: slave, 1015: [{args, " -pa " ++ SuiteDir}, 1016: {linked, false}]), 1017: OP = process_flag(priority, max), 1018: OTE = process_flag(trap_exit, true), 1019: FS = spawn_link(Node, 1020: ?MODULE, 1021: otp_3906_start_forker_starter, 1022: [?OTP_3906_CHILDREN, [], self(), Prog]), 1023: Result = receive 1024: {'EXIT', _ForkerStarter, Reason} -> 1025: {failed, Reason}; 1026: {emulator_pid, EmPid} -> 1027: case otp_3906_wait_result(FS, 0, 0) of 1028: {succeded, 1029: ?OTP_3906_CHILDREN, 1030: ?OTP_3906_CHILDREN} -> 1031: succeded; 1032: {succeded, Forked, Exited} -> 1033: otp_3906_list_defunct(EmPid, OSName), 1034: {failed, 1035: {mismatch, 1036: {forked, Forked}, 1037: {exited, Exited}}}; 1038: Res -> 1039: otp_3906_list_defunct(EmPid, OSName), 1040: Res 1041: end 1042: end, 1043: process_flag(trap_exit, OTE), 1044: process_flag(priority, OP), 1045: test_server:stop_node(Node), 1046: case Result of 1047: succeded -> 1048: ok; 1049: _ -> 1050: test_server:fail(Result) 1051: end; 1052: _ -> 1053: {skipped, "No C compiler found"} 1054: end. 1055: 1056: otp_3906_list_defunct(EmPid, OSName) -> 1057: % Guess ps switches to use and what to grep for (could be improved) 1058: {Switches, Zombie} = case OSName of 1059: BSD when BSD == darwin; 1060: BSD == openbsd; 1061: BSD == netbsd; 1062: BSD == freebsd -> 1063: {"-ajx", "Z"}; 1064: _ -> 1065: {"-ef", "[dD]efunct"} 1066: end, 1067: test_server:format("Emulator pid: ~s~n" 1068: "Listing of zombie processes:~n" 1069: "~s~n", 1070: [EmPid, 1071: otp_3906_htmlize(os:cmd("ps " 1072: ++ Switches 1073: ++ " | grep " 1074: ++ Zombie))]). 1075: 1076: otp_3906_htmlize([]) -> 1077: []; 1078: otp_3906_htmlize([C | Cs]) -> 1079: case [C] of 1080: "<" -> "<" ++ otp_3906_htmlize(Cs); 1081: ">" -> ">" ++ otp_3906_htmlize(Cs); 1082: _ -> [C | otp_3906_htmlize(Cs)] 1083: end. 1084: 1085: otp_3906_make_prog(CC, PrivDir) -> 1086: SrcFileName = filename:join(PrivDir, ?OTP_3906_PROGNAME ++ ".c"), 1087: TrgtFileName = filename:join(PrivDir, ?OTP_3906_PROGNAME), 1088: {ok, SrcFile} = file:open(SrcFileName, write), 1089: io:format(SrcFile, 1090: "int ~n" 1091: "main(void) ~n" 1092: "{ ~n" 1093: " return ~p; ~n" 1094: "} ~n", 1095: [?OTP_3906_EXIT_STATUS]), 1096: file:close(SrcFile), 1097: os:cmd(CC ++ " " ++ SrcFileName ++ " -o " ++ TrgtFileName), 1098: TrgtFileName. 1099: 1100: 1101: otp_3906_wait_result(ForkerStarter, F, E) -> 1102: receive 1103: {'EXIT', ForkerStarter, Reason} -> 1104: {failed, {Reason, {forked, F}, {exited, E}}}; 1105: forked -> 1106: otp_3906_wait_result(ForkerStarter, F+1, E); 1107: exited -> 1108: otp_3906_wait_result(ForkerStarter, F, E+1); 1109: tick -> 1110: otp_3906_wait_result(ForkerStarter, F, E); 1111: succeded -> 1112: {succeded, F, E} 1113: after 1114: ?OTP_3906_TICK_TIMEOUT -> 1115: unlink(ForkerStarter), 1116: exit(ForkerStarter, timeout), 1117: {failed, {timeout, {forked, F}, {exited, E}}} 1118: end. 1119: 1120: otp_3906_collect([], _) -> 1121: done; 1122: otp_3906_collect(RefList, Sup) -> 1123: otp_3906_collect(otp_3906_collect_one(RefList, Sup), Sup). 1124: 1125: otp_3906_collect_one(RefList, Sup) -> 1126: receive 1127: Ref when is_reference(Ref) -> 1128: Sup ! tick, 1129: lists:delete(Ref, RefList) 1130: end. 1131: 1132: otp_3906_start_forker(N, Sup, Prog) -> 1133: Ref = make_ref(), 1134: spawn_opt(?MODULE, 1135: otp_3906_forker, 1136: [N, self(), Ref, Sup, Prog], 1137: [link, {priority, max}]), 1138: Ref. 1139: 1140: otp_3906_start_forker_starter(N, RefList, Sup, Prog) -> 1141: process_flag(priority, max), 1142: EmPid = os:getpid(), 1143: Sup ! {emulator_pid, EmPid}, 1144: otp_3906_forker_starter(N, RefList, Sup, Prog). 1145: 1146: otp_3906_forker_starter(0, RefList, Sup, _) -> 1147: otp_3906_collect(RefList, Sup), 1148: unlink(Sup), 1149: Sup ! succeded; 1150: otp_3906_forker_starter(N, RefList, Sup, Prog) 1151: when length(RefList) >= ?OTP_3906_MAX_CONC_OSP -> 1152: otp_3906_forker_starter(N, otp_3906_collect_one(RefList, Sup), Sup, Prog); 1153: otp_3906_forker_starter(N, RefList, Sup, Prog) 1154: when is_integer(N), N > ?OTP_3906_OSP_P_ERLP -> 1155: otp_3906_forker_starter(N-?OTP_3906_OSP_P_ERLP, 1156: [otp_3906_start_forker(?OTP_3906_OSP_P_ERLP, 1157: Sup, 1158: Prog)|RefList], 1159: Sup, 1160: Prog); 1161: otp_3906_forker_starter(N, RefList, Sup, Prog) when is_integer(N) -> 1162: otp_3906_forker_starter(0, 1163: [otp_3906_start_forker(N, 1164: Sup, 1165: Prog)|RefList], 1166: Sup, 1167: Prog). 1168: 1169: otp_3906_forker(0, Parent, Ref, _, _) -> 1170: unlink(Parent), 1171: Parent ! Ref; 1172: otp_3906_forker(N, Parent, Ref, Sup, Prog) -> 1173: Port = erlang:open_port({spawn, Prog}, [exit_status, in]), 1174: Sup ! forked, 1175: receive 1176: {Port, {exit_status, ?OTP_3906_EXIT_STATUS}} -> 1177: Sup ! exited, 1178: otp_3906_forker(N-1, Parent, Ref, Sup, Prog); 1179: {Port, Res} -> 1180: exit(Res); 1181: Other -> 1182: exit(Other) 1183: end. 1184: 1185: 1186: otp_4389(suite) -> []; 1187: otp_4389(doc) -> []; 1188: otp_4389(Config) when is_list(Config) -> 1189: case os:type() of 1190: {unix, _} -> 1191: Dog = test_server:timetrap(test_server:seconds(240)), 1192: TCR = self(), 1193: case get_true_cmd() of 1194: True when is_list(True) -> 1195: lists:foreach( 1196: fun (P) -> 1197: receive 1198: {P, ok} -> ok; 1199: {P, Err} -> ?t:fail(Err) 1200: end 1201: end, 1202: lists:map( 1203: fun(_) -> 1204: spawn_link( 1205: fun() -> 1206: process_flag(trap_exit, true), 1207: case catch open_port({spawn, True}, 1208: [stream,exit_status]) of 1209: P when is_port(P) -> 1210: receive 1211: {P,{exit_status,_}} -> 1212: TCR ! {self(),ok}; 1213: {'EXIT',_,{R2,_}} when R2 == emfile; 1214: R2 == eagain -> 1215: TCR ! {self(),ok}; 1216: Err2 -> 1217: TCR ! {self(),{msg,Err2}} 1218: end; 1219: {'EXIT',{R1,_}} when R1 == emfile; 1220: R1 == eagain -> 1221: TCR ! {self(),ok}; 1222: Err1 -> 1223: TCR ! {self(), {open_port,Err1}} 1224: end 1225: end) 1226: end, 1227: lists:duplicate(1000,[]))), 1228: test_server:timetrap_cancel(Dog), 1229: {comment, 1230: "This test case doesn't always fail when the bug that " 1231: "it tests for is present (it is most likely to fail on" 1232: " a multi processor machine). If the test case fails it" 1233: " will fail by deadlocking the emulator."}; 1234: _ -> 1235: {skipped, "\"true\" command not found"} 1236: end; 1237: _ -> 1238: {skip,"Only run on Unix"} 1239: end. 1240: 1241: get_true_cmd() -> 1242: DoFileExist = fun (FileName) -> 1243: case file:read_file_info(FileName) of 1244: {ok, _} -> throw(FileName); 1245: _ -> not_found 1246: end 1247: end, 1248: catch begin 1249: %% First check in /usr/bin and /bin 1250: DoFileExist("/usr/bin/true"), 1251: DoFileExist("/bin/true"), 1252: %% Try which 1253: case filename:dirname(os:cmd("which true")) of 1254: "." -> not_found; 1255: TrueDir -> filename:join(TrueDir, "true") 1256: end 1257: end. 1258: 1259: %% 'exit_status' option 1260: exit_status(suite) -> 1261: []; 1262: exit_status(doc) -> 1263: ["Test that the 'exit_status' option works"]; 1264: exit_status(Config) when is_list(Config) -> 1265: Dog = test_server:timetrap(test_server:seconds(60)), 1266: port_expect(Config,[{"x", 1267: [{exit_status, 5}]}], 1268: 1, "", [exit_status]), 1269: test_server:timetrap_cancel(Dog), 1270: ok. 1271: 1272: spawn_driver(suite) -> 1273: []; 1274: spawn_driver(doc) -> 1275: ["Test spawning a driver specifically"]; 1276: spawn_driver(Config) when is_list(Config) -> 1277: Dog = test_server:timetrap(test_server:seconds(10)), 1278: Path = ?config(data_dir, Config), 1279: ok = load_driver(Path, "echo_drv"), 1280: Port = erlang:open_port({spawn_driver, "echo_drv"}, []), 1281: Port ! {self(), {command, "Hello port!"}}, 1282: receive 1283: {Port, {data, "Hello port!"}} = Msg1 -> 1284: io:format("~p~n", [Msg1]), 1285: ok; 1286: Other -> 1287: test_server:fail({unexpected, Other}) 1288: end, 1289: Port ! {self(), close}, 1290: receive {Port, closed} -> ok end, 1291: 1292: Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, 1293: []), 1294: receive 1295: {Port2, {data, "Hello port?"}} = Msg2 -> 1296: io:format("~p~n", [Msg2]), 1297: ok; 1298: Other2 -> 1299: test_server:fail({unexpected2, Other2}) 1300: end, 1301: Port2 ! {self(), close}, 1302: receive {Port2, closed} -> ok end, 1303: {'EXIT',{badarg,_}} = (catch erlang:open_port({spawn_driver, "ls"}, [])), 1304: {'EXIT',{badarg,_}} = (catch erlang:open_port({spawn_driver, "cmd"}, [])), 1305: {'EXIT',{badarg,_}} = (catch erlang:open_port({spawn_driver, os:find_executable("erl")}, [])), 1306: test_server:timetrap_cancel(Dog), 1307: ok. 1308: 1309: parallelism_option(suite) -> 1310: []; 1311: parallelism_option(doc) -> 1312: ["Test parallelism option of open_port"]; 1313: parallelism_option(Config) when is_list(Config) -> 1314: ?line Dog = test_server:timetrap(test_server:seconds(10)), 1315: ?line Path = ?config(data_dir, Config), 1316: ?line ok = load_driver(Path, "echo_drv"), 1317: ?line Port = erlang:open_port({spawn_driver, "echo_drv"}, 1318: [{parallelism, true}]), 1319: ?line {parallelism, true} = erlang:port_info(Port, parallelism), 1320: ?line Port ! {self(), {command, "Hello port!"}}, 1321: ?line receive 1322: {Port, {data, "Hello port!"}} = Msg1 -> 1323: io:format("~p~n", [Msg1]), 1324: ok; 1325: Other -> 1326: test_server:fail({unexpected, Other}) 1327: end, 1328: ?line Port ! {self(), close}, 1329: ?line receive {Port, closed} -> ok end, 1330: 1331: ?line Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, 1332: [{parallelism, false}]), 1333: ?line {parallelism, false} = erlang:port_info(Port2, parallelism), 1334: ?line receive 1335: {Port2, {data, "Hello port?"}} = Msg2 -> 1336: io:format("~p~n", [Msg2]), 1337: ok; 1338: Other2 -> 1339: test_server:fail({unexpected2, Other2}) 1340: end, 1341: ?line Port2 ! {self(), close}, 1342: ?line receive {Port2, closed} -> ok end, 1343: ?line test_server:timetrap_cancel(Dog), 1344: ok. 1345: 1346: spawn_executable(suite) -> 1347: []; 1348: spawn_executable(doc) -> 1349: ["Test spawning an executable specifically"]; 1350: spawn_executable(Config) when is_list(Config) -> 1351: Dog = test_server:timetrap(test_server:seconds(10)), 1352: DataDir = ?config(data_dir, Config), 1353: EchoArgs1 = filename:join([DataDir,"echo_args"]), 1354: ExactFile1 = filename:nativename(os:find_executable(EchoArgs1)), 1355: [ExactFile1] = run_echo_args(DataDir,[]), 1356: ["echo_args"] = run_echo_args(DataDir,["echo_args"]), 1357: ["echo_arguments"] = run_echo_args(DataDir,["echo_arguments"]), 1358: [ExactFile1,"hello world","dlrow olleh"] = 1359: run_echo_args(DataDir,[ExactFile1,"hello world","dlrow olleh"]), 1360: [ExactFile1] = run_echo_args(DataDir,[default]), 1361: [ExactFile1,"hello world","dlrow olleh"] = 1362: run_echo_args(DataDir,[switch_order,ExactFile1,"hello world", 1363: "dlrow olleh"]), 1364: [ExactFile1,"hello world","dlrow olleh"] = 1365: run_echo_args(DataDir,[default,"hello world","dlrow olleh"]), 1366: 1367: [ExactFile1,"hello world","dlrow olleh"] = 1368: run_echo_args_2("\""++ExactFile1++"\" "++"\"hello world\" \"dlrow olleh\""), 1369: 1370: PrivDir = ?config(priv_dir, Config), 1371: SpaceDir =filename:join([PrivDir,"With Spaces"]), 1372: file:make_dir(SpaceDir), 1373: Executable = filename:basename(ExactFile1), 1374: file:copy(ExactFile1,filename:join([SpaceDir,Executable])), 1375: ExactFile2 = filename:nativename(filename:join([SpaceDir,Executable])), 1376: chmodplusx(ExactFile2), 1377: io:format("|~s|~n",[ExactFile2]), 1378: [ExactFile2] = run_echo_args(SpaceDir,[]), 1379: ["echo_args"] = run_echo_args(SpaceDir,["echo_args"]), 1380: ["echo_arguments"] = run_echo_args(SpaceDir,["echo_arguments"]), 1381: [ExactFile2,"hello world","dlrow olleh"] = 1382: run_echo_args(SpaceDir,[ExactFile2,"hello world","dlrow olleh"]), 1383: [ExactFile2] = run_echo_args(SpaceDir,[default]), 1384: [ExactFile2,"hello world","dlrow olleh"] = 1385: run_echo_args(SpaceDir,[switch_order,ExactFile2,"hello world", 1386: "dlrow olleh"]), 1387: [ExactFile2,"hello world","dlrow olleh"] = 1388: run_echo_args(SpaceDir,[default,"hello world","dlrow olleh"]), 1389: [ExactFile2,"hello world","dlrow olleh"] = 1390: run_echo_args_2("\""++ExactFile2++"\" "++"\"hello world\" \"dlrow olleh\""), 1391: 1392: ExeExt = 1393: case string:to_lower(lists:last(string:tokens(ExactFile2,"."))) of 1394: "exe" -> 1395: ".exe"; 1396: _ -> 1397: "" 1398: end, 1399: Executable2 = "spoky name"++ExeExt, 1400: file:copy(ExactFile1,filename:join([SpaceDir,Executable2])), 1401: ExactFile3 = filename:nativename(filename:join([SpaceDir,Executable2])), 1402: chmodplusx(ExactFile3), 1403: [ExactFile3] = run_echo_args(SpaceDir,Executable2,[]), 1404: ["echo_args"] = run_echo_args(SpaceDir,Executable2,["echo_args"]), 1405: ["echo_arguments"] = run_echo_args(SpaceDir,Executable2,["echo_arguments"]), 1406: [ExactFile3,"hello world","dlrow olleh"] = 1407: run_echo_args(SpaceDir,Executable2,[ExactFile3,"hello world","dlrow olleh"]), 1408: [ExactFile3] = run_echo_args(SpaceDir,Executable2,[default]), 1409: [ExactFile3,"hello world","dlrow olleh"] = 1410: run_echo_args(SpaceDir,Executable2, 1411: [switch_order,ExactFile3,"hello world", 1412: "dlrow olleh"]), 1413: [ExactFile3,"hello world","dlrow olleh"] = 1414: run_echo_args(SpaceDir,Executable2, 1415: [default,"hello world","dlrow olleh"]), 1416: [ExactFile3,"hello world","dlrow olleh"] = 1417: run_echo_args_2("\""++ExactFile3++"\" "++"\"hello world\" \"dlrow olleh\""), 1418: {'EXIT',{enoent,_}} = (catch run_echo_args(SpaceDir,"fnurflmonfi", 1419: [default,"hello world", 1420: "dlrow olleh"])), 1421: NonExec = "kronxfrt"++ExeExt, 1422: file:write_file(filename:join([SpaceDir,NonExec]), 1423: <<"Not an executable">>), 1424: {'EXIT',{eacces,_}} = (catch run_echo_args(SpaceDir,NonExec, 1425: [default,"hello world", 1426: "dlrow olleh"])), 1427: {'EXIT',{enoent,_}} = (catch open_port({spawn_executable,"cmd"},[])), 1428: {'EXIT',{enoent,_}} = (catch open_port({spawn_executable,"sh"},[])), 1429: case os:type() of 1430: {win32,_} -> 1431: test_bat_file(SpaceDir); 1432: {unix,_} -> 1433: test_sh_file(SpaceDir) 1434: end, 1435: test_server:timetrap_cancel(Dog), 1436: ok. 1437: 1438: unregister_name(Config) when is_list(Config) -> 1439: true = register(crash, open_port({spawn, "sleep 100"}, [])), 1440: true = unregister(crash). 1441: 1442: test_bat_file(Dir) -> 1443: FN = "tf.bat", 1444: Full = filename:join([Dir,FN]), 1445: D = [<<"@echo off\r\n">>, 1446: <<"echo argv[0]:^|%0^|\r\n">>, 1447: <<"if \"%1\" == \"\" goto done\r\n">>, 1448: <<"echo argv[1]:^|%1^|\r\n">>, 1449: <<"if \"%2\" == \"\" goto done\r\n">>, 1450: <<"echo argv[2]:^|%2^|\r\n">>, 1451: <<"if \"%3\" == \"\" goto done\r\n">>, 1452: <<"echo argv[3]:^|%3^|\r\n">>, 1453: <<"if \"%4\" == \"\" goto done\r\n">>, 1454: <<"echo argv[4]:^|%4^|\r\n">>, 1455: <<"if \"%5\" == \"\" goto done\r\n">>, 1456: <<"echo argv[5]:^|%5^|\r\n">>, 1457: <<"\r\n">>, 1458: <<":done\r\n">>, 1459: <<"\r\n">>], 1460: file:write_file(Full,list_to_binary(D)), 1461: EF = filename:basename(FN), 1462: [DN,"hello","world"] = 1463: run_echo_args(Dir,FN, 1464: [default,"hello","world"]), 1465: %% The arg0 argumant should be ignored when running batch files 1466: [DN,"hello","world"] = 1467: run_echo_args(Dir,FN, 1468: ["knaskurt","hello","world"]), 1469: EF = filename:basename(DN), 1470: ok. 1471: 1472: test_sh_file(Dir) -> 1473: FN = "tf.sh", 1474: Full = filename:join([Dir,FN]), 1475: D = [<<"#! /bin/sh\n">>, 1476: <<"echo 'argv[0]:|'$0'|'\n">>, 1477: <<"i=1\n">>, 1478: <<"while [ '!' -z \"$1\" ]; do\n">>, 1479: <<" echo 'argv['$i']:|'\"$1\"'|'\n">>, 1480: <<" shift\n">>, 1481: <<" i=`expr $i + 1`\n">>, 1482: <<"done\n">>], 1483: file:write_file(Full,list_to_binary(D)), 1484: chmodplusx(Full), 1485: [Full,"hello","world"] = 1486: run_echo_args(Dir,FN, 1487: [default,"hello","world"]), 1488: [Full,"hello","world of spaces"] = 1489: run_echo_args(Dir,FN, 1490: [default,"hello","world of spaces"]), 1491: file:write_file(filename:join([Dir,"testfile1"]),<<"testdata1">>), 1492: file:write_file(filename:join([Dir,"testfile2"]),<<"testdata2">>), 1493: Pattern = filename:join([Dir,"testfile*"]), 1494: L = filelib:wildcard(Pattern), 1495: 2 = length(L), 1496: [Full,"hello",Pattern] = 1497: run_echo_args(Dir,FN, 1498: [default,"hello",Pattern]), 1499: ok. 1500: 1501: 1502: 1503: chmodplusx(Filename) -> 1504: case file:read_file_info(Filename) of 1505: {ok,FI} -> 1506: FI2 = FI#file_info{mode = ((FI#file_info.mode) bor 8#00100)}, 1507: file:write_file_info(Filename,FI2); 1508: _ -> 1509: ok 1510: end. 1511: 1512: run_echo_args_2(FullnameAndArgs) -> 1513: Port = open_port({spawn,FullnameAndArgs},[eof]), 1514: Data = collect_data(Port), 1515: Port ! {self(), close}, 1516: receive {Port, closed} -> ok end, 1517: parse_echo_args_output(Data). 1518: 1519: 1520: run_echo_args(Where,Args) -> 1521: run_echo_args(Where,"echo_args",Args). 1522: run_echo_args(Where,Prog,Args) -> 1523: ArgvArg = case Args of 1524: [] -> 1525: []; 1526: [default|T] -> 1527: [{args,T}]; 1528: [switch_order,H|T] -> 1529: [{args,T},{arg0,H}]; 1530: [H|T] -> 1531: [{arg0,H},{args,T}] 1532: end, 1533: Command = filename:join([Where,Prog]), 1534: Port = open_port({spawn_executable,Command},ArgvArg++[eof]), 1535: Data = collect_data(Port), 1536: Port ! {self(), close}, 1537: receive {Port, closed} -> ok end, 1538: parse_echo_args_output(Data). 1539: 1540: collect_data(Port) -> 1541: receive 1542: {Port, {data, Data}} -> 1543: Data ++ collect_data(Port); 1544: {Port, eof} -> 1545: [] 1546: end. 1547: 1548: parse_echo_args_output(Data) -> 1549: [lists:last(string:tokens(S,"|")) || S <- string:tokens(Data,"\r\n")]. 1550: 1551: mix_up_ports(suite) -> 1552: []; 1553: mix_up_ports(doc) -> 1554: ["Test that the emulator does not mix up ports when the port table wraps"]; 1555: mix_up_ports(Config) when is_list(Config) -> 1556: Dog = test_server:timetrap(test_server:seconds(10)), 1557: Path = ?config(data_dir, Config), 1558: ok = load_driver(Path, "echo_drv"), 1559: Port = erlang:open_port({spawn, "echo_drv"}, []), 1560: Port ! {self(), {command, "Hello port!"}}, 1561: receive 1562: {Port, {data, "Hello port!"}} = Msg1 -> 1563: io:format("~p~n", [Msg1]), 1564: ok; 1565: Other -> 1566: test_server:fail({unexpected, Other}) 1567: end, 1568: Port ! {self(), close}, 1569: receive {Port, closed} -> ok end, 1570: loop(start, done, 1571: fun(P) -> 1572: Q = 1573: (catch erlang:open_port({spawn, "echo_drv"}, [])), 1574: %% io:format("~p ", [Q]), 1575: if is_port(Q) -> 1576: Q; 1577: true -> 1578: io:format("~p~n", [P]), 1579: done 1580: end 1581: end), 1582: Port ! {self(), {command, "Hello again port!"}}, 1583: receive 1584: Msg2 -> 1585: test_server:fail({unexpected, Msg2}) 1586: after 1000 -> 1587: ok 1588: end, 1589: test_server:timetrap_cancel(Dog), 1590: ok. 1591: 1592: loop(Stop, Stop, Fun) when is_function(Fun) -> 1593: ok; 1594: loop(Start, Stop, Fun) when is_function(Fun) -> 1595: loop(Fun(Start), Stop, Fun). 1596: 1597: 1598: otp_5112(suite) -> 1599: []; 1600: otp_5112(doc) -> 1601: ["Test that link to connected process is taken away when port calls", 1602: "driver_exit() also when the port index has wrapped"]; 1603: otp_5112(Config) when is_list(Config) -> 1604: Dog = test_server:timetrap(test_server:seconds(10)), 1605: Path = ?config(data_dir, Config), 1606: ok = load_driver(Path, "exit_drv"), 1607: Port = otp_5112_get_wrapped_port(), 1608: ?t:format("Max ports: ~p~n",[max_ports()]), 1609: ?t:format("Port: ~p~n",[Port]), 1610: {links, Links1} = process_info(self(),links), 1611: ?t:format("Links1: ~p~n",[Links1]), 1612: true = lists:member(Port, Links1), 1613: Port ! {self(), {command, ""}}, 1614: ?line wait_until(fun () -> lists:member(Port, erlang:ports()) == false end), 1615: {links, Links2} = process_info(self(),links), 1616: ?t:format("Links2: ~p~n",[Links2]), 1617: false = lists:member(Port, Links2), %% This used to fail 1618: test_server:timetrap_cancel(Dog), 1619: ok. 1620: 1621: otp_5112_get_wrapped_port() -> 1622: P1 = erlang:open_port({spawn, "exit_drv"}, []), 1623: case port_ix(P1) < max_ports() of 1624: true -> 1625: ?t:format("Need to wrap port index (~p)~n", [P1]), 1626: otp_5112_wrap_port_ix([P1]), 1627: P2 = erlang:open_port({spawn, "exit_drv"}, []), 1628: false = port_ix(P2) < max_ports(), 1629: P2; 1630: false -> 1631: ?t:format("Port index already wrapped (~p)~n", [P1]), 1632: P1 1633: end. 1634: 1635: otp_5112_wrap_port_ix(Ports) -> 1636: case (catch erlang:open_port({spawn, "exit_drv"}, [])) of 1637: Port when is_port(Port) -> 1638: otp_5112_wrap_port_ix([Port|Ports]); 1639: _ -> 1640: %% Port table now full; empty port table 1641: lists:foreach(fun (P) -> P ! {self(), close} end, 1642: Ports), 1643: ok 1644: end. 1645: 1646: 1647: otp_5119(suite) -> 1648: []; 1649: otp_5119(doc) -> 1650: ["Test that port index is not unnecessarily wrapped"]; 1651: otp_5119(Config) when is_list(Config) -> 1652: Dog = test_server:timetrap(test_server:seconds(10)), 1653: Path = ?config(data_dir, Config), 1654: ok = load_driver(Path, "exit_drv"), 1655: PI1 = port_ix(otp_5119_fill_empty_port_tab([])), 1656: PI2 = port_ix(erlang:open_port({spawn, "exit_drv"}, [])), 1657: {PortIx1, PortIx2} = case PI2 > PI1 of 1658: true -> 1659: {PI1, PI2}; 1660: false -> 1661: {port_ix(otp_5119_fill_empty_port_tab([PI2])), 1662: port_ix(erlang:open_port({spawn, "exit_drv"}, []))} 1663: end, 1664: MaxPorts = max_ports(), 1665: ?t:format("PortIx1 = ~p ~p~n", [PI1, PortIx1]), 1666: ?t:format("PortIx2 = ~p ~p~n", [PI2, PortIx2]), 1667: ?t:format("MaxPorts = ~p~n", [MaxPorts]), 1668: true = PortIx2 > PortIx1, 1669: true = PortIx2 =< PortIx1 + MaxPorts, 1670: test_server:timetrap_cancel(Dog), 1671: ok. 1672: 1673: otp_5119_fill_empty_port_tab(Ports) -> 1674: case (catch erlang:open_port({spawn, "exit_drv"}, [])) of 1675: Port when is_port(Port) -> 1676: otp_5119_fill_empty_port_tab([Port|Ports]); 1677: _ -> 1678: %% Port table now full; empty port table 1679: lists:foreach(fun (P) -> P ! {self(), close} end, 1680: Ports), 1681: [LastPort|_] = Ports, 1682: LastPort 1683: end. 1684: 1685: max_ports() -> 1686: erlang:system_info(port_limit). 1687: 1688: port_ix(Port) when is_port(Port) -> 1689: ["#Port",_,PortIxStr] = string:tokens(erlang:port_to_list(Port), 1690: "<.>"), 1691: list_to_integer(PortIxStr). 1692: 1693: 1694: otp_6224(doc) -> ["Check that port command failure doesn't crash the emulator"]; 1695: otp_6224(suite) -> []; 1696: otp_6224(Config) when is_list(Config) -> 1697: Dog = test_server:timetrap(test_server:seconds(10)), 1698: Path = ?config(data_dir, Config), 1699: ok = load_driver(Path, "failure_drv"), 1700: Go = make_ref(), 1701: Failer = spawn(fun () -> 1702: receive Go -> ok end, 1703: Port = open_port({spawn, "failure_drv"}, 1704: []), 1705: Port ! {self(), {command, "Fail, please!"}}, 1706: otp_6224_loop() 1707: end), 1708: Mon = erlang:monitor(process, Failer), 1709: Failer ! Go, 1710: receive 1711: {'DOWN', Mon, process, Failer, Reason} -> 1712: case Reason of 1713: {driver_failed, _} -> ok; 1714: driver_failed -> ok; 1715: _ -> ?t:fail({unexpected_exit_reason, 1716: Reason}) 1717: end 1718: end, 1719: test_server:timetrap_cancel(Dog), 1720: ok. 1721: 1722: otp_6224_loop() -> 1723: receive _ -> ok after 0 -> ok end, 1724: otp_6224_loop(). 1725: 1726: 1727: -define(EXIT_STATUS_MSB_MAX_PROCS, 64). 1728: -define(EXIT_STATUS_MSB_MAX_PORTS, 300). 1729: 1730: exit_status_multi_scheduling_block(doc) -> []; 1731: exit_status_multi_scheduling_block(suite) -> []; 1732: exit_status_multi_scheduling_block(Config) when is_list(Config) -> 1733: Repeat = 3, 1734: case ?t:os_type() of 1735: {unix, _} -> 1736: Dog = ?t:timetrap(test_server:minutes(2*Repeat)), 1737: SleepSecs = 6, 1738: try 1739: lists:foreach(fun (_) -> 1740: exit_status_msb_test(Config, 1741: SleepSecs) 1742: end, 1743: lists:seq(1, Repeat)) 1744: after 1745: %% Wait for the system to recover (regardless 1746: %% of success or not) otherwise later testcases 1747: %% may unnecessarily fail. 1748: ?t:timetrap_cancel(Dog), 1749: receive after SleepSecs+500 -> ok end 1750: end; 1751: _ -> {skip, "Not implemented for this OS"} 1752: end. 1753: 1754: exit_status_msb_test(Config, SleepSecs) when is_list(Config) -> 1755: %% 1756: %% We want to start port programs from as many schedulers as possible 1757: %% and we want these port programs to terminate while multi-scheduling 1758: %% is blocked. 1759: %% 1760: NoSchedsOnln = erlang:system_info(schedulers_online), 1761: Parent = self(), 1762: ?t:format("SleepSecs = ~p~n", [SleepSecs]), 1763: PortProg = "sleep " ++ integer_to_list(SleepSecs), 1764: Start = now(), 1765: NoProcs = case NoSchedsOnln of 1766: NProcs when NProcs < ?EXIT_STATUS_MSB_MAX_PROCS -> 1767: NProcs; 1768: _ -> 1769: ?EXIT_STATUS_MSB_MAX_PROCS 1770: end, 1771: NoPortsPerProc = case 20*NoProcs of 1772: TNPorts when TNPorts < ?EXIT_STATUS_MSB_MAX_PORTS -> 20; 1773: _ -> ?EXIT_STATUS_MSB_MAX_PORTS div NoProcs 1774: end, 1775: ?t:format("NoProcs = ~p~nNoPortsPerProc = ~p~n", 1776: [NoProcs, NoPortsPerProc]), 1777: ProcFun 1778: = fun () -> 1779: PrtSIds = lists:map( 1780: fun (_) -> 1781: erlang:yield(), 1782: case catch open_port({spawn, PortProg}, 1783: [exit_status]) of 1784: Prt when is_port(Prt) -> 1785: {Prt, 1786: erlang:system_info(scheduler_id)}; 1787: {'EXIT', {Err, _}} when Err == eagain; 1788: Err == emfile -> 1789: noop; 1790: {'EXIT', Err} when Err == eagain; 1791: Err == emfile -> 1792: noop; 1793: Error -> 1794: ?t:fail(Error) 1795: end 1796: end, 1797: lists:seq(1, NoPortsPerProc)), 1798: SIds = lists:filter(fun (noop) -> false; 1799: (_) -> true 1800: end, 1801: lists:map(fun (noop) -> noop; 1802: ({_, SId}) -> SId 1803: end, 1804: PrtSIds)), 1805: process_flag(scheduler, 0), 1806: Parent ! {self(), started, SIds}, 1807: lists:foreach( 1808: fun (noop) -> 1809: noop; 1810: ({Port, _}) -> 1811: receive 1812: {Port, {exit_status, 0}} -> 1813: ok; 1814: {Port, {exit_status, Status}} when Status > 128 -> 1815: %% Sometimes happens when we have created 1816: %% too many ports. 1817: ok; 1818: {Port, {exit_status, _}} = ESMsg -> 1819: {Port, {exit_status, 0}} = ESMsg 1820: end 1821: end, 1822: PrtSIds), 1823: Parent ! {self(), done} 1824: end, 1825: Procs = lists:map(fun (N) -> 1826: spawn_opt(ProcFun, 1827: [link, 1828: {scheduler, 1829: (N rem NoSchedsOnln)+1}]) 1830: end, 1831: lists:seq(1, NoProcs)), 1832: SIds = lists:map(fun (P) -> 1833: receive {P, started, SIds} -> SIds end 1834: end, 1835: Procs), 1836: StartedTime = timer:now_diff(now(), Start)/1000000, 1837: ?t:format("StartedTime = ~p~n", [StartedTime]), 1838: true = StartedTime < SleepSecs, 1839: erlang:system_flag(multi_scheduling, block), 1840: lists:foreach(fun (P) -> receive {P, done} -> ok end end, Procs), 1841: DoneTime = timer:now_diff(now(), Start)/1000000, 1842: ?t:format("DoneTime = ~p~n", [DoneTime]), 1843: true = DoneTime > SleepSecs, 1844: ok = verify_multi_scheduling_blocked(), 1845: erlang:system_flag(multi_scheduling, unblock), 1846: case {length(lists:usort(lists:flatten(SIds))), NoSchedsOnln} of 1847: {N, N} -> 1848: ok; 1849: {N, M} -> 1850: ?t:fail("Failed to create ports on all" 1851: ++ integer_to_list(M) ++ " available" 1852: "schedulers. Only created ports on " 1853: ++ integer_to_list(N) ++ " schedulers.") 1854: end. 1855: 1856: save_sid(SIds) -> 1857: SId = erlang:system_info(scheduler_id), 1858: case lists:member(SId, SIds) of 1859: true -> SIds; 1860: false -> [SId|SIds] 1861: end. 1862: 1863: sid_proc(SIds) -> 1864: NewSIds = save_sid(SIds), 1865: receive 1866: {From, want_sids} -> 1867: From ! {self(), sids, NewSIds} 1868: after 0 -> 1869: sid_proc(NewSIds) 1870: end. 1871: 1872: verify_multi_scheduling_blocked() -> 1873: Procs = lists:map(fun (_) -> 1874: spawn_link(fun () -> sid_proc([]) end) 1875: end, 1876: lists:seq(1, 3*erlang:system_info(schedulers_online))), 1877: receive after 1000 -> ok end, 1878: SIds = lists:map(fun (P) -> 1879: P ! {self(), want_sids}, 1880: receive {P, sids, PSIds} -> PSIds end 1881: end, 1882: Procs), 1883: 1 = length(lists:usort(lists:flatten(SIds))), 1884: ok. 1885: 1886: 1887: %%% Pinging functions. 1888: 1889: stream_ping(Config, Size, CmdLine, Options) -> 1890: Data = random_packet(Size), 1891: port_expect(Config, [{Data, [Data]}], 0, CmdLine, Options). 1892: 1893: ping(Config, Sizes, HSize, CmdLine, Options) -> 1894: Actions = lists:map(fun(Size) -> 1895: [$p|Packet] = random_packet(Size, "ping"), 1896: {[$p|Packet], [[$P|Packet]]} 1897: end, 1898: Sizes), 1899: port_expect(Config, Actions, HSize, CmdLine, Options). 1900: 1901: %% expect_input(Sizes, HSize, CmdLine, Options) 1902: %% 1903: %% Sizes = Size of packets to generated. 1904: %% HSize = Header size: 1, 2, or 4 1905: %% CmdLine = Additional command line options. 1906: %% Options = Addtional port options. 1907: 1908: expect_input(Config, Sizes, HSize, CmdLine, Options) -> 1909: expect_input1(Config, Sizes, {HSize, CmdLine, Options}, [], []). 1910: 1911: expect_input1(Config, [0|Rest], Params, Expect, ReplyCommand) -> 1912: expect_input1(Config, Rest, Params, [""|Expect], ["x0"|ReplyCommand]); 1913: expect_input1(Config, [Size|Rest], Params, Expect, ReplyCommand) -> 1914: Packet = random_packet(Size), 1915: Fmt = io_lib:format("~c~p", [hd(Packet), Size]), 1916: expect_input1(Config, Rest, Params, [Packet|Expect], [Fmt|ReplyCommand]); 1917: expect_input1(Config, [], {HSize, CmdLine0, Options}, Expect, ReplyCommand) -> 1918: CmdLine = build_cmd_line(CmdLine0, ReplyCommand, []), 1919: port_expect(Config, [{false, lists:reverse(Expect)}], 1920: HSize, CmdLine, Options). 1921: 1922: build_cmd_line(FixedCmdLine, [Cmd|Rest], []) -> 1923: build_cmd_line(FixedCmdLine, Rest, [Cmd]); 1924: build_cmd_line(FixedCmdLine, [Cmd|Rest], Result) -> 1925: build_cmd_line(FixedCmdLine, Rest, [Cmd, $:|Result]); 1926: build_cmd_line(FixedCmdLine, [], Result) -> 1927: lists:flatten([FixedCmdLine, " -r", Result, " -n"]). 1928: 1929: %% port_expect(Actions, HSize, CmdLine, Options) 1930: %% 1931: %% Actions = [{Send, ExpectList}|Rest] 1932: %% HSize = 0 (stream), or 1, 2, 4 (header size aka "packet bytes") 1933: %% CmdLine = Command line for port_test. Don't include -h<digit>. 1934: %% Options = Options for open_port/2. Don't include {packet, Number} or 1935: %% or stream. 1936: %% 1937: %% Send = false | list() 1938: %% ExpectList = List of lists or binaries. 1939: %% 1940: %% Returns the port. 1941: 1942: port_expect(Config, Actions, HSize, CmdLine, Options0) -> 1943: % io:format("port_expect(~p, ~p, ~p, ~p)", 1944: % [Actions, HSize, CmdLine, Options0]), 1945: PortTest = port_test(Config), 1946: Cmd = lists:concat([PortTest, " -h", HSize, " ", CmdLine]), 1947: PortType = 1948: case HSize of 1949: 0 -> stream; 1950: _ -> {packet, HSize} 1951: end, 1952: Options = [PortType|Options0], 1953: io:format("open_port({spawn, ~p}, ~p)", [Cmd, Options]), 1954: Port = open_port({spawn, Cmd}, Options), 1955: port_expect(Port, Actions, Options), 1956: Port. 1957: 1958: port_expect(Port, [{Send, Expects}|Rest], Options) when is_list(Expects) -> 1959: port_send(Port, Send), 1960: IsBinaryPort = lists:member(binary, Options), 1961: Receiver = 1962: case {lists:member(stream, Options), line_option(Options)} of 1963: {false, _} -> fun receive_all/2; 1964: {true,false} -> fun stream_receive_all/2; 1965: {_, true} -> fun receive_all/2 1966: end, 1967: Receiver(Port, maybe_to_binary(Expects, IsBinaryPort)), 1968: port_expect(Port, Rest, Options); 1969: port_expect(_, [], _) -> 1970: ok. 1971: 1972: %%% Check for either line or {line,N} in option list 1973: line_option([{line,_}|_]) -> 1974: true; 1975: line_option([line|_]) -> 1976: true; 1977: line_option([_|T]) -> 1978: line_option(T); 1979: line_option([]) -> 1980: false. 1981: 1982: any_list_to_binary({Atom, List}) -> 1983: {Atom, list_to_binary(List)}; 1984: any_list_to_binary(List) -> 1985: list_to_binary(List). 1986: 1987: maybe_to_binary(Expects, true) -> 1988: lists:map(fun any_list_to_binary/1, Expects); 1989: maybe_to_binary(Expects, false) -> 1990: Expects. 1991: 1992: port_send(_Port, false) -> ok; 1993: port_send(Port, Send) when is_list(Send) -> 1994: % io:format("port_send(~p, ~p)", [Port, Send]), 1995: Port ! {self(), {command, Send}}. 1996: 1997: receive_all(Port, [Expect|Rest]) -> 1998: % io:format("receive_all(~p, [~p|Rest])", [Port, Expect]), 1999: receive 2000: {Port, {data, Expect}} -> 2001: io:format("Received ~s", [format(Expect)]), 2002: ok; 2003: {Port, {data, Other}} -> 2004: io:format("Received ~s; expected ~s", 2005: [format(Other), format(Expect)]), 2006: test_server:fail(bad_message); 2007: Other -> 2008: %% (We're not yet prepared for receiving both 'eol' and 2009: %% 'exit_status'; remember that they may appear in any order.) 2010: case {Expect, Rest, Other} of 2011: {eof, [], {Port, eof}} -> 2012: io:format("Received soft EOF.",[]), 2013: ok; 2014: {{exit_status, S}, [], {Port, {exit_status, S}}} -> 2015: io:format("Received exit status ~p.",[S]), 2016: ok; 2017: _ -> 2018: %%% io:format("Unexpected message: ~s", [format(Other)]), 2019: io:format("Unexpected message: ~w", [Other]), 2020: test_server:fail(unexpected_message) 2021: end 2022: end, 2023: receive_all(Port, Rest); 2024: receive_all(_Port, []) -> 2025: ok. 2026: 2027: stream_receive_all(Port, [Expect]) -> 2028: stream_receive_all1(Port, Expect). 2029: 2030: stream_receive_all1(_Port, Empty) when is_binary(Empty), size(Empty) == 0 -> 2031: ok; 2032: stream_receive_all1(_Port, []) -> 2033: ok; 2034: stream_receive_all1(Port, Expect) -> 2035: receive 2036: {Port, {data, Data}} -> 2037: Remaining = compare(Data, Expect), 2038: stream_receive_all1(Port, Remaining); 2039: Other -> 2040: test_server:fail({bad_message, Other}) 2041: end. 2042: 2043: compare(B1, B2) when is_binary(B1), is_binary(B2), byte_size(B1) =< byte_size(B2) -> 2044: case split_binary(B2, size(B1)) of 2045: {B1,Remaining} -> 2046: Remaining; 2047: _Other -> 2048: test_server:fail(nomatch) 2049: end; 2050: compare(B1, B2) when is_binary(B1), is_binary(B2) -> 2051: test_server:fail(too_much_data); 2052: compare([X|Rest1], [X|Rest2]) -> 2053: compare(Rest1, Rest2); 2054: compare([_|_], [_|_]) -> 2055: test_server:fail(nomatch); 2056: compare([], Remaining) -> 2057: Remaining; 2058: compare(_Data, []) -> 2059: test_server:fail(too_much_data). 2060: 2061: maybe_to_list(Bin) when is_binary(Bin) -> 2062: binary_to_list(Bin); 2063: maybe_to_list(List) -> 2064: List. 2065: 2066: format({Eol,List}) -> 2067: io_lib:format("tuple<~w,~s>",[Eol, maybe_to_list(List)]); 2068: format(List) when is_list(List) -> 2069: case list_at_least(50, List) of 2070: true -> 2071: io_lib:format("\"~-50s...\"", [List]); 2072: false -> 2073: io_lib:format("~p", [List]) 2074: end; 2075: format(Bin) when is_binary(Bin), size(Bin) >= 50 -> 2076: io_lib:format("binary<~-50s...>", [binary_to_list(Bin, 1, 50)]); 2077: format(Bin) when is_binary(Bin) -> 2078: io_lib:format("binary<~s>", [binary_to_list(Bin)]). 2079: 2080: 2081: list_at_least(Number, [_|Rest]) when Number > 0 -> 2082: list_at_least(Number-1, Rest); 2083: list_at_least(Number, []) when Number > 0 -> 2084: false; 2085: list_at_least(0, _List) -> true. 2086: 2087: 2088: %%% Utility functions. 2089: 2090: random_packet(Size) -> 2091: random_packet(Size, ""). 2092: 2093: random_packet(Size, Prefix) -> 2094: build_packet(Size-length(Prefix), lists:reverse(Prefix), random_char()). 2095: 2096: build_packet(0, Result, _NextChar) -> 2097: lists:reverse(Result); 2098: build_packet(Left, Result, NextChar0) -> 2099: NextChar = 2100: if 2101: NextChar0 >= 126 -> 2102: 33; 2103: true -> 2104: NextChar0+1 2105: end, 2106: build_packet(Left-1, [NextChar0|Result], NextChar). 2107: 2108: sizes() -> 2109: [10, 13, 64, 127, 128, 255, 256, 1023, 1024, 2110: 32767, 32768, 65535, 65536]. 2111: 2112: sizes(Header_Size) -> 2113: sizes(Header_Size, sizes(), []). 2114: 2115: sizes(1, [Packet_Size|Rest], Result) when Packet_Size < 256 -> 2116: sizes(1, Rest, [Packet_Size|Result]); 2117: sizes(2, [Packet_Size|Rest], Result) when Packet_Size < 65536 -> 2118: sizes(2, Rest, [Packet_Size|Result]); 2119: sizes(4, [Packet_Size|Rest], Result) -> 2120: sizes(4, Rest, [Packet_Size|Result]); 2121: sizes(_, _, Result) -> 2122: Result. 2123: 2124: random_char() -> 2125: random_char("abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789"). 2126: 2127: random_char(Chars) -> 2128: lists:nth(uniform(length(Chars)), Chars). 2129: 2130: uniform(N) -> 2131: case get(random_seed) of 2132: undefined -> 2133: {X, Y, Z} = Seed = time(), 2134: io:format("Random seed = ~p\n",[Seed]), 2135: random:seed(X, Y, Z); 2136: _ -> 2137: ok 2138: end, 2139: random:uniform(N). 2140: 2141: fun_spawn(Fun) -> 2142: fun_spawn(Fun, []). 2143: 2144: fun_spawn(Fun, Args) -> 2145: spawn_link(erlang, apply, [Fun, Args]). 2146: 2147: port_test(Config) when is_list(Config) -> 2148: filename:join(?config(data_dir, Config), "port_test"). 2149: 2150: 2151: ports(doc) -> "Test that erlang:ports/0 returns a consistent snapshot of ports"; 2152: ports(suite) -> []; 2153: ports(Config) when is_list(Config) -> 2154: Path = ?config(data_dir, Config), 2155: ok = load_driver(Path, "exit_drv"), 2156: 2157: receive after 1000 -> ok end, % Wait for other ports to stabilize 2158: 2159: OtherPorts = erlang:ports(), 2160: io:format("Other ports: ~p\n",[OtherPorts]), 2161: MaxPorts = 1024 - length(OtherPorts), 2162: 2163: TrafficPid = spawn_link(fun() -> ports_traffic(MaxPorts) end), 2164: 2165: ports_snapshots(100, TrafficPid, OtherPorts), 2166: TrafficPid ! {self(),die}, 2167: receive {TrafficPid, dead} -> ok end, 2168: ok. 2169: 2170: ports_snapshots(0, _, _) -> 2171: ok; 2172: ports_snapshots(Iter, TrafficPid, OtherPorts) -> 2173: 2174: TrafficPid ! start, 2175: receive after 1 -> ok end, 2176: 2177: Snapshot = erlang:ports(), 2178: 2179: TrafficPid ! {self(), stop}, 2180: receive {TrafficPid, EventList, TrafficPorts} -> ok end, 2181: 2182: %%io:format("Snapshot=~p\n", [Snapshot]), 2183: ports_verify(Snapshot, OtherPorts ++ TrafficPorts, EventList), 2184: 2185: ports_snapshots(Iter-1, TrafficPid, OtherPorts). 2186: 2187: 2188: ports_traffic(MaxPorts) -> 2189: ports_traffic_stopped(MaxPorts, {[],0}). 2190: 2191: ports_traffic_stopped(MaxPorts, {PortList, PortCnt}) -> 2192: receive 2193: start -> 2194: %%io:format("Traffic started in ~p\n",[self()]), 2195: ports_traffic_started(MaxPorts, {PortList, PortCnt}, []); 2196: {Pid,die} -> 2197: lists:foreach(fun(Port)-> erlang:port_close(Port) end, 2198: PortList), 2199: Pid ! {self(),dead} 2200: end. 2201: 2202: ports_traffic_started(MaxPorts, {PortList, PortCnt}, EventList) -> 2203: receive 2204: {Pid, stop} -> 2205: %%io:format("Traffic stopped in ~p\n",[self()]), 2206: Pid ! {self(), EventList, PortList}, 2207: ports_traffic_stopped(MaxPorts, {PortList, PortCnt}) 2208: 2209: after 0 -> 2210: ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) 2211: end. 2212: 2213: ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) -> 2214: N = uniform(MaxPorts), 2215: case N > PortCnt of 2216: true -> % Open port 2217: P = open_port({spawn, "exit_drv"}, []), 2218: %%io:format("Created port ~p\n",[P]), 2219: ports_traffic_started(MaxPorts, {[P|PortList], PortCnt+1}, 2220: [{open,P}|EventList]); 2221: 2222: false -> % Close port 2223: P = lists:nth(N, PortList), 2224: %%io:format("Close port ~p\n",[P]), 2225: true = erlang:port_close(P), 2226: ports_traffic_started(MaxPorts, {lists:delete(P,PortList), PortCnt-1}, 2227: [{close,P}|EventList]) 2228: end. 2229: 2230: ports_verify(Ports, PortsAfter, EventList) -> 2231: %%io:format("Candidate=~p\nEvents=~p\n", [PortsAfter, EventList]), 2232: case lists:sort(Ports) =:= lists:sort(PortsAfter) of 2233: true -> 2234: io:format("Snapshot of ~p ports verified ok.\n",[length(Ports)]), 2235: ok; 2236: false -> 2237: %% Note that we track the event list "backwards", undoing open/close: 2238: case EventList of 2239: [{open,P} | Tail] -> 2240: ports_verify(Ports, lists:delete(P,PortsAfter), Tail); 2241: 2242: [{close,P} | Tail] -> 2243: ports_verify(Ports, [P | PortsAfter], Tail); 2244: 2245: [] -> 2246: test_server:fail("Inconsistent snapshot from erlang:ports()") 2247: end 2248: end. 2249: 2250: load_driver(Dir, Driver) -> 2251: case erl_ddll:load_driver(Dir, Driver) of 2252: ok -> ok; 2253: {error, Error} = Res -> 2254: io:format("~s\n", [erl_ddll:format_error(Error)]), 2255: Res 2256: end. 2257: 2258: 2259: close_deaf_port(doc) -> ["Send data to port program that does not read it, then close port." 2260: "Primary targeting Windows to test threaded_handle_closer in sys.c"]; 2261: close_deaf_port(suite) -> []; 2262: close_deaf_port(Config) when is_list(Config) -> 2263: Dog = test_server:timetrap(test_server:seconds(100)), 2264: DataDir = ?config(data_dir, Config), 2265: DeadPort = os:find_executable("dead_port", DataDir), 2266: Port = open_port({spawn,DeadPort++" 60"},[]), 2267: erlang:port_command(Port,"Hello, can you hear me!?!?"), 2268: port_close(Port), 2269: 2270: Res = close_deaf_port_1(0, DeadPort), 2271: io:format("Waiting for OS procs to terminate...\n"), 2272: receive after 5*1000 -> ok end, 2273: test_server:timetrap_cancel(Dog), 2274: Res. 2275: 2276: close_deaf_port_1(1000, _) -> 2277: ok; 2278: close_deaf_port_1(N, Cmd) -> 2279: Timeout = integer_to_list(random:uniform(5*1000)), 2280: try open_port({spawn_executable,Cmd},[{args,[Timeout]}]) of 2281: Port -> 2282: erlang:port_command(Port,"Hello, can you hear me!?!?"), 2283: port_close(Port), 2284: close_deaf_port_1(N+1, Cmd) 2285: catch 2286: _:eagain -> 2287: {comment, "Could not spawn more than " ++ integer_to_list(N) ++ " OS processes."} 2288: end. 2289: 2290: wait_until(Fun) -> 2291: case catch Fun() of 2292: true -> 2293: ok; 2294: _ -> 2295: receive after 100 -> ok end, 2296: wait_until(Fun) 2297: end.