diff options
Diffstat (limited to 'erts/emulator/test/driver_SUITE.erl')
-rw-r--r-- | erts/emulator/test/driver_SUITE.erl | 1993 |
1 files changed, 1993 insertions, 0 deletions
diff --git a/erts/emulator/test/driver_SUITE.erl b/erts/emulator/test/driver_SUITE.erl new file mode 100644 index 0000000000..39b2ed395f --- /dev/null +++ b/erts/emulator/test/driver_SUITE.erl @@ -0,0 +1,1993 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% + +%%% Purpose : Test interaction Erlang/Drivers (new features as of R3A) + +%%% Checks that new features (as of R3) of the Erlang/Driver +%%% implementation works as expected. +%%% +%%% Things that should be tested: +%%% - outputv +%%% - timeouts +%%% - queueing + +-module(driver_SUITE). +-export([all/1, + init_per_testcase/2, + fin_per_testcase/2, + end_per_suite/1, + outputv_echo/1, + timer/1, + timer_measure/1, + timer_cancel/1, + timer_change/1, + timer_delay/1, + queue_echo/1, + fun_to_port/1, + driver_unloaded/1, + io_ready_exit/1, + use_fallback_pollset/1, + bad_fd_in_pollset/1, + driver_event/1, + fd_change/1, + steal_control/1, + otp_6602/1, + 'driver_system_info_ver1.0'/1, + 'driver_system_info_ver1.1'/1, + driver_system_info_current_ver/1, + driver_monitor/1, + ioq_exit/1, + ioq_exit_ready_input/1, + ioq_exit_ready_output/1, + ioq_exit_timeout/1, + ioq_exit_ready_async/1, + ioq_exit_event/1, + ioq_exit_ready_input_async/1, + ioq_exit_ready_output_async/1, + ioq_exit_timeout_async/1, + ioq_exit_event_async/1, + zero_extended_marker_garb_drv/1, + invalid_extended_marker_drv/1, + larger_major_vsn_drv/1, + larger_minor_vsn_drv/1, + smaller_major_vsn_drv/1, + smaller_minor_vsn_drv/1, + peek_non_existing_queue/1, + otp_6879/1, + caller/1, + many_events/1, + missing_callbacks/1, + smp_select/1, + driver_select_use/1, + thread_mseg_alloc_cache_clean/1]). + +-export([bin_prefix/2]). + +-include("test_server.hrl"). + + +% First byte in communication with the timer driver +-define(START_TIMER, 0). +-define(CANCEL_TIMER, 1). +-define(DELAY_START_TIMER, 2). +-define(TIMER, 3). +-define(CANCELLED, 4). + +% First byte in communication with queue driver +-define(PUSHQ, 0). +-define(ENQ, 1). +-define(PUSHQ_BIN, 2). +-define(ENQ_BIN, 3). +-define(PUSHQV, 4). +-define(ENQV, 5). + +-define(DEQ, 6). +-define(BYTES_QUEUED, 7). +-define(READ_HEAD, 8). + +-define(RANDOM, random). + +% Max data size that is queued in one instance +-define(MAX_DATA_SIZE, 16384). + +% This is the allowed delay when testing the driver timer functionality +-define(delay, 100). + +-define(heap_binary_size, 64). + +init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> + Dog=?t:timetrap(?t:minutes(2)), + case catch erts_debug:get_internal_state(available_internal_state) of + true -> ok; + _ -> erts_debug:set_internal_state(available_internal_state, true) + end, + erlang:display({init_per_testcase, Case}), + ?line 0 = erts_debug:get_internal_state(check_io_debug), + [{watchdog, Dog},{testcase, Case}|Config]. + +fin_per_testcase(Case, Config) -> + Dog = ?config(watchdog, Config), + erlang:display({fin_per_testcase, Case}), + ?line 0 = erts_debug:get_internal_state(check_io_debug), + ?t:timetrap_cancel(Dog). + +end_per_suite(_Config) -> + catch erts_debug:set_internal_state(available_internal_state, false). + +all(suite) -> + [ + fun_to_port, + outputv_echo, + queue_echo, + timer, + driver_unloaded, + io_ready_exit, + use_fallback_pollset, + bad_fd_in_pollset, + driver_event, + fd_change, + steal_control, + otp_6602, + 'driver_system_info_ver1.0', + 'driver_system_info_ver1.1', + driver_system_info_current_ver, + driver_monitor, + ioq_exit, + zero_extended_marker_garb_drv, + invalid_extended_marker_drv, + larger_major_vsn_drv, + larger_minor_vsn_drv, + smaller_major_vsn_drv, + smaller_minor_vsn_drv, + peek_non_existing_queue, + otp_6879, + caller, + many_events, + missing_callbacks, + smp_select, + driver_select_use, + thread_mseg_alloc_cache_clean + ]. + +fun_to_port(doc) -> "Test sending a fun to port with an outputv-capable driver."; +fun_to_port(Config) when is_list(Config) -> + ?line Path = ?config(data_dir, Config), + ?line erl_ddll:start(), + ?line ok = load_driver(Path, outputv_drv), + + ?line fun_to_port_1(fun() -> 33 end), + ?line fun_to_port_1([fun() -> 42 end]), + ?line fun_to_port_1([1|fun() -> 42 end]), + L = build_io_list(65536), + ?line fun_to_port_1([L,fun() -> 42 end]), + ?line fun_to_port_1([L|fun() -> 42 end]), + ok. + +fun_to_port_1(Term) -> + Port = open_port({spawn,outputv_drv}, []), + {'EXIT',{badarg,_}} = (catch port_command(Port, Term)), + port_close(Port). + +build_io_list(0) -> []; +build_io_list(1) -> [7]; +build_io_list(N) -> + L = build_io_list(N div 2), + case N rem 2 of + 0 -> [L|L]; + 1 -> [7,L|L] + end. + + + +outputv_echo(doc) -> ["Test echoing data with a driver that supports outputv."]; +outputv_echo(Config) when is_list(Config) -> + ?line Dog = test_server:timetrap(test_server:minutes(10)), + Name = 'outputv_drv', + P = start_driver(Config, Name, true), + + ?line ov_test(P, {bin,0}), + ?line ov_test(P, {bin,1}), + ?line ov_test(P, {bin,2}), + ?line ov_test(P, {bin,3}), + ?line ov_test(P, {bin,4}), + ?line ov_test(P, {bin,5}), + ?line ov_test(P, {bin,6}), + ?line ov_test(P, {bin,7}), + ?line ov_test(P, {bin,8}), + ?line ov_test(P, {bin,15}), + ?line ov_test(P, {bin,16}), + ?line ov_test(P, {bin,17}), + + ?line ov_test(P, {list,0}), + ?line ov_test(P, {list,1}), + ?line ov_test(P, {list,2}), + ?line ov_test(P, [int,int,{list,0},int]), + ?line ov_test(P, [int,int,{list,1},int]), + ?line ov_test(P, [int,int,{list,2}]), + ?line ov_test(P, [{list,3},int,int,{list,2}]), + ?line ov_test(P, {list,33}), + + ?line ov_test(P, [{bin,0}]), + ?line ov_test(P, [{bin,1}]), + ?line ov_test(P, [{bin,2}]), + ?line ov_test(P, [{bin,3}]), + ?line ov_test(P, [{bin,4}]), + ?line ov_test(P, [{bin,5}]), + ?line ov_test(P, [{bin,6},int]), + ?line ov_test(P, [int,{bin,3}]), + ?line ov_test(P, [int|{bin,4}]), + ?line ov_test(P, [{bin,17},int,{bin,13}|{bin,3}]), + + ?line ov_test(P, [int,{bin,17},int,{bin,?heap_binary_size+1}|{bin,3}]), + + stop_driver(P, Name), + ?line test_server:timetrap_cancel(Dog), + ok. + +ov_test(Port, Template) -> + Self = self(), + spawn_opt(erlang, apply, [fun () -> ov_test(Self, Port, Template) end,[]], + [link,{fullsweep_after,0}]), + receive + done -> ok + end. + +ov_test(Parent, Port, Template) -> + true = port_connect(Port, self()), + + HeapData = build_data(Template), + io:format("Mostly heap binaries"), + ov_send_and_test(Port, HeapData, HeapData), + + %% Try sub binaries. + io:format("Mostly sub binaries of heap binaries"), + SubHeapData = make_sub_binaries(HeapData), + ov_send_and_test(Port, SubHeapData, HeapData), + + %% Try refc binaries. + io:format("Refc binaries"), + RefcData = make_refc_binaries(HeapData), + ov_send_and_test(Port, RefcData, RefcData), + + %% Try sub binaries of heap binaries. + io:format("Sub binaries of refc binaries"), + SubRefcData = make_sub_binaries(RefcData), + ov_send_and_test(Port, SubRefcData, RefcData), + io:format("", []), + + %% Garbage collect and make sure that there are no binaries left. + %% R7 note: + %% - dead variables on the stack are killed after last use, + %% - erlang:garbage_collect/0 collects garbage immediately. + %% (there used to be dummy functions here) + erlang:garbage_collect(), + {binary,[]} = process_info(self(), binary), + + %% Reassign Port back to parent and tell him we are done. + true = port_connect(Port, Parent), + Parent ! done. + +ov_send_and_test(Port, Data, ExpectedResult) -> + io:format("~p ! ~P", [Port,Data,12]), + Port ! {self(),{command,Data}}, + receive + {Port,{data,ReturnData}} -> + io:format("~p returned ~P", [Port,ReturnData,12]), + compare(ReturnData, ExpectedResult); + {Port,{data,OtherData}} -> + io:format("~p returned WRONG data ~p", [Port,OtherData]), + ?line test_server:fail(); + Wrong -> + ?line test_server:fail({unexpected_port_or_data,Wrong}) + end. + +compare(Got, Expected) -> + case {list_to_binary([Got]),list_to_binary([Expected])} of + {B,B} -> ok; + {_Gb,_Eb} -> + ?t:fail(got_bad_data) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Driver timer test suites +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +timer(suite) -> [timer_measure,timer_cancel,timer_delay,timer_change]. + +timer_measure(doc) -> ["Check that timers time out in good time."]; +timer_measure(Config) when is_list(Config) -> + ?line Dog = test_server:timetrap(test_server:minutes(1)), + Name = 'timer_drv', + ?line Port = start_driver(Config, Name, false), + + ?line try_timeouts(Port, 8997), + + ?line stop_driver(Port, Name), + ?line test_server:timetrap_cancel(Dog), + ok. + +try_timeouts(_, 0) -> ok; +try_timeouts(Port, Timeout) -> + ?line TimeBefore = now(), + ?line erlang:port_command(Port, <<?START_TIMER,Timeout:32>>), + receive + {Port,{data,[?TIMER]}} -> + ?line Elapsed = erl_millisecs() - erl_millisecs(TimeBefore), + io:format("Elapsed: ~p Timeout: ~p\n", [Elapsed,Timeout]), + if + Elapsed < Timeout -> + ?line ?t:fail(too_short); + Elapsed > Timeout + ?delay -> + ?line ?t:fail(too_long); + true -> + try_timeouts(Port, Timeout div 2) + end + after Timeout + ?delay -> + ?line test_server:fail("driver failed to timeout") + end. + +timer_cancel(doc) -> ["Try cancelling timers set in a driver."]; +timer_cancel(Config) when is_list(Config) -> + ?line Dog = test_server:timetrap(test_server:minutes(1)), + Name = 'timer_drv', + ?line Port = start_driver(Config, Name, false), + + ?line try_cancel(Port, 10000), + + ?line stop_driver(Port, Name), + ?line test_server:timetrap_cancel(Dog), + ok. + +try_cancel(Port, Timeout) -> + ?line T_before = erl_millisecs(), + Port ! {self(),{command,<<?START_TIMER,(Timeout + ?delay):32>>}}, + receive + {Port, {data, [?TIMER]}} -> + ?line test_server:fail("driver timed out before cancelling it") + after Timeout -> + Port ! {self(), {command, [?CANCEL_TIMER]}}, + receive + {Port, {data, [?TIMER]}} -> + ?line test_server:fail("driver timed out after cancelling it"); + {Port, {data, [?CANCELLED]}} -> + ?line Time_milli_secs = erl_millisecs() - T_before, + + io:format("Time_milli_secs: ~p Timeout: ~p\n", + [Time_milli_secs, Timeout]), + if + Time_milli_secs > (Timeout + ?delay) -> + ?line test_server:fail("too long real time"); + Timeout == 0 -> ok; + true -> try_cancel(Port, Timeout div 2) + end + after ?delay -> + test_server:fail("No message from driver") + end + end. + +%% Test that timers don't time out too early if we do a sleep +%% before setting a timer. + +timer_delay(Config) when is_list(Config) -> + ?line Dog = test_server:timetrap(test_server:minutes(1)), + Name = 'timer_drv', + ?line Port = start_driver(Config, Name, false), + + ?line TimeBefore = now(), + Timeout0 = 350, + ?line erlang:port_command(Port, <<?DELAY_START_TIMER,Timeout0:32>>), + Timeout = Timeout0 + + case os:type() of + {win32,_} -> 0; %Driver doesn't sleep on Windows. + _ -> 1000 + end, + receive + {Port,{data,[?TIMER]}} -> + ?line Elapsed = erl_millisecs() - erl_millisecs(TimeBefore), + io:format("Elapsed time: ~p Timeout: ~p\n", + [Elapsed,Timeout]), + if + Elapsed < Timeout -> + ?line ?t:fail(too_short); + Elapsed > Timeout + ?delay -> + ?line ?t:fail(too_long); + true -> + ok + end + end, + + ?line stop_driver(Port, Name), + ?line test_server:timetrap_cancel(Dog), + ok. + +%% Test that driver_set_timer with new timout really changes +%% the timer (ticket OTP-5942), it didn't work before + +timer_change(Config) when is_list(Config) -> + ?line Dog = test_server:timetrap(test_server:minutes(1)), + Name = 'timer_drv', + ?line Port = start_driver(Config, Name, false), + + ?line try_change_timer(Port, 10000), + + ?line stop_driver(Port, Name), + ?line test_server:timetrap_cancel(Dog), + ok. + +try_change_timer(_Port, 0) -> ok; +try_change_timer(Port, Timeout) -> + ?line Timeout_3 = Timeout*3, + ?line TimeBefore = now(), + ?line erlang:port_command(Port, <<?START_TIMER,Timeout_3:32>>), + ?line erlang:port_command(Port, <<?START_TIMER,Timeout:32>>), + receive + {Port,{data,[?TIMER]}} -> + ?line Elapsed = erl_millisecs() - erl_millisecs(TimeBefore), + io:format("Elapsed: ~p Timeout: ~p\n", [Elapsed,Timeout]), + if + Elapsed < Timeout -> + ?line ?t:fail(too_short); + Elapsed > Timeout + ?delay -> + ?line ?t:fail(too_long); + true -> + try_timeouts(Port, Timeout div 2) + end + after Timeout + ?delay -> + ?line test_server:fail("driver failed to timeout") + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Queue test suites +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +queue_echo(doc) -> + ["1) Queue up data in a driver that uses the full driver_queue API to do this." + "2) Get the data back, a random amount at a time."]; +queue_echo(Config) when is_list(Config) -> + case ?t:is_native(?MODULE) of + true -> exit(crashes_native_code); + false -> queue_echo_1(Config) + end. + +queue_echo_1(Config) -> + ?line Dog = test_server:timetrap(test_server:minutes(10)), + Name = 'queue_drv', + ?line P = start_driver(Config, Name, true), + + ?line q_echo(P, [{?ENQ, {list,1}}, + {?ENQ, {list,0}}, + {?ENQ, {bin,0}}, + {?ENQ, {bin,1}}, + {?ENQ, {bin,2}}, + {?ENQ, {bin,3}}, + {?ENQ, {bin,4}}, + {?ENQ, {bin,5}}, + {?ENQ, {bin,600}}, + {?PUSHQ, {list,0}}, + {?PUSHQ, {list,1}}, + {?PUSHQ, {bin,0}}, + {?PUSHQ, {bin,1}}, + {?PUSHQ, {bin,888}}, + {?ENQ_BIN, {bin,0}}, + {?ENQ_BIN, {bin,1}}, + {?ENQ_BIN, {bin,2}}, + {?ENQ_BIN, {bin,3}}, + {?ENQ_BIN, {bin,4}}, + {?ENQ_BIN, {bin,777}}, + {?PUSHQ_BIN, {bin,0}}, + {?PUSHQ_BIN, {bin,1}}, + {?PUSHQ_BIN, {bin,334}}, + {?ENQV, [{bin,0},{list,1},{bin,1},{bin,555}]}, + {?ENQV, [{bin,0},{list,1},{bin,1}]}, + {?PUSHQV, [{bin,0},{list,1},{bin,1},{bin,319}]}]), + + ?line stop_driver(P, Name), + ?line test_server:timetrap_cancel(Dog), + ok. + +q_echo(Port, SpecList) -> + io:format("Heap binaries"), + HeapData = [{M,build_data(T)} || {M,T} <- SpecList], + {HeapDataReturn,HeapDataLen} = feed_driver(Port, HeapData), + dequeue(Port, HeapDataReturn, HeapDataLen, 1), + + %% Try sub binaries. + io:format("Sub binaries of heap binaries"), + SubHeapData = make_sub_binaries(HeapData), + %% The following line will generate a warning. + {HeapDataReturn,HeapDataLen} = feed_driver(Port, SubHeapData), + dequeue(Port, HeapDataReturn, HeapDataLen, 1), + + %% Try refc binaries. + io:format("Refc binaries"), + RefcData = make_refc_binaries(HeapData), + {RefcDataReturn,RefcDataLen} = feed_driver(Port, RefcData), + dequeue(Port, RefcDataReturn, RefcDataLen, 1), + + %% Try sub binaries of refc binaries. + io:format("Sub binaries of refc binaries"), + SubRefcData = make_sub_binaries(RefcData), + {RefcDataReturn,RefcDataLen} = feed_driver(Port, SubRefcData), + dequeue(Port, RefcDataReturn, RefcDataLen, 1), + + %% Try a writable binary. + io:format("Writable binaries"), + WritableBinData = make_writable_binaries(HeapData), + {WritableDataReturn,WritableDatalen} = feed_driver(Port, WritableBinData), + _ = append_to_writable_binaries(WritableBinData), + dequeue(Port, WritableDataReturn, WritableDatalen, 1), + + %% Try dequeing more than one byte at the time. + io:format("Heap binaries -- dequeueing more than one byte at the time"), + feed_and_dequeue(Port, HeapData, 2), + feed_and_dequeue(Port, HeapData, 3), + feed_and_dequeue(Port, HeapData, 4), + + io:format("\n"). + +feed_and_dequeue(Port, Data, DeqSize) -> + {DataReturn,DataLen} = feed_driver(Port, Data), + dequeue(Port, DataReturn, DataLen, DeqSize), + ok. + +%% Send all data according to the specification to the driver side (where it +%% is queued up for later return to this process). + +feed_driver(Port, Description) -> + feed_driver(Port, Description, <<>>, 0). + +feed_driver(Port, [], ExpectedInPort, Qb) -> + io:format("Expected in port: ~P", [ExpectedInPort,12]), + io:format("In port: ~P", [read_head(Port, Qb),12]), + {ExpectedInPort,Qb}; +feed_driver(Port, [{Method0,Data}|T], Expected_return, Qb_before) -> + Method = case Method0 of + ?RANDOM -> uniform(6)-1; + Other -> Other + end, + Size = size(list_to_binary([Data])), + + %% *********************************************************************** + %% NOTE! Never never never change this to io:format/2, as that will imply + %% message sending, and sending as message will spoil the test of + %% writable binaries. + + %% erlang:display({sending,method_name(Method),Data}), + %% *********************************************************************** + + queue_op(Port, Method, Data), + + Qb_in_driver = bytes_queued(Port), + case Qb_before + Size of + Qb_in_driver -> ok; + Sum -> + io:format("Qb_before: ~p\n" + "Qb_before+Size: ~p\n" + "Qb_in_driver: ~p", + [Qb_before,Sum,Qb_in_driver]), + ?t:fail() + end, + X_return = case Method of + ?ENQ -> list_to_binary([Expected_return,Data]); + ?PUSHQ -> list_to_binary([Data,Expected_return]); + ?PUSHQ_BIN -> list_to_binary([Data,Expected_return]); + ?ENQ_BIN -> list_to_binary([Expected_return,Data]); + ?PUSHQV -> list_to_binary([Data,Expected_return]); + ?ENQV -> list_to_binary([Expected_return,Data]) + end, + feed_driver(Port, T, X_return, Qb_before + Size). + +%% method_name(0) -> pushq; +%% method_name(1) -> enq; +%% method_name(2) -> pushq_bin; +%% method_name(3) -> enq_bin; +%% method_name(4) -> pushqv; +%% method_name(5) -> enqv. + +dequeue(Port, DataList, LenToGet, DeqSize) -> + io:format("Dequeuing ~p bytes, ~p byte(s) at once...", [LenToGet,DeqSize]), + compare_return(Port, DataList, LenToGet, DeqSize). + +compare_return(Port, _Data_list, 0, _Back_len) -> + 0 = bytes_queued(Port); +compare_return(Port, QueuedInPort0, Len_to_get, DeqSize) -> + case bytes_queued(Port) of + Len_to_get -> ok; + BytesInQueue -> + io:format("Len_to_get: ~p", [Len_to_get]), + io:format("Bytes in queue: ~p", [BytesInQueue]), + ?line test_server:fail() + end, + BytesToDequeue = if (DeqSize > Len_to_get) -> Len_to_get; + true -> DeqSize + end, + Dequeued = read_head(Port, BytesToDequeue), + case bin_prefix(Dequeued, QueuedInPort0) of + true -> + deq(Port, BytesToDequeue), + <<_:BytesToDequeue/binary,QueuedInPort/binary>> = QueuedInPort0, + compare_return(Port, QueuedInPort, Len_to_get - BytesToDequeue, DeqSize); + false -> + io:format("Bytes to dequeue: ~p", [BytesToDequeue]), + io:format("Dequeued: ~p", [Dequeued]), + io:format("Queued in port: ~P", [QueuedInPort0,12]), + ?t:fail() + end. + +%% bin_prefix(PrefixBinary, Binary) +%% Is PrefixBinary a prefix of Binary? + +bin_prefix(<<C:8,PreTail/binary>>, <<C:8,Tail/binary>>) -> + bin_prefix(PreTail, Tail); +bin_prefix(<<>>, _Bin) -> true; +bin_prefix(_, _) -> false. + +queue_op(Port, Method, Data) -> + [] = erlang:port_control(Port, Method, []), + Port ! {self(),{command,Data}}, + ok. + +bytes_queued(Port) -> + case erlang:port_control(Port, ?BYTES_QUEUED, []) of + <<I:32>> -> I; + Bad -> ?t:fail({bad_result,Bad}) + end. + +deq(Port, Size) -> + [] = erlang:port_control(Port, ?DEQ, <<Size:32>>). + +read_head(Port, Size) -> + erlang:port_control(Port, ?READ_HEAD, <<Size:32>>). + + +driver_unloaded(doc) -> + []; +driver_unloaded(suite) -> + []; +driver_unloaded(Config) when is_list(Config) -> + ?line process_flag(trap_exit, true), + ?line Drv = timer_drv, + ?line User = self(), + ?line Loaded = make_ref(), + ?line Die = make_ref(), + ?line Loader = spawn(fun () -> + erl_ddll:start(), + ok = load_driver(?config(data_dir, + Config), + Drv), + User ! Loaded, + receive Die -> exit(bye) end + end), + ?line receive Loaded -> ok end, + ?line Port = open_port({spawn, Drv}, []), + ?line Loader ! Die, + ?line receive + {'EXIT', Port, Reason} -> + ?line driver_unloaded = Reason + %% Reason used to be -1 + end. + + +io_ready_exit(doc) -> []; +io_ready_exit(suite) -> []; +io_ready_exit(Config) when is_list(Config) -> + ?line OTE = process_flag(trap_exit, true), + ?line Test = self(), + ?line Dgawd = spawn(fun () -> + ok = dgawd_handler:install(), + Mon = erlang:monitor(process, Test), + Test ! dgawd_handler_started, + receive + {'DOWN', Mon, _, _, _} -> ok; + stop_dgawd_handler -> ok + end, + dgawd_handler:restore(), + Test ! dgawd_handler_stopped + end), + ?line receive dgawd_handler_started -> ok end, + ?line Drv = io_ready_exit_drv, + ?line erl_ddll:start(), + ?line ok = load_driver(?config(data_dir, Config), Drv), + ?line Port = open_port({spawn, Drv}, []), + ?line case erlang:port_control(Port, 0, "") of + "ok" -> + receive + {'EXIT', Port, Reason} -> + ?line case Reason of + ready_output_driver_failure -> + ?t:format("Exited in output_ready()~n"), + ?line ok; + ready_input_driver_failure -> + ?t:format("Exited in input_ready()~n"), + ?line ok; + Error -> ?line ?t:fail(Error) + end + end, + receive after 2000 -> ok end, + ?line false = dgawd_handler:got_dgawd_report(), + ?line Dgawd ! stop_dgawd_handler, + ?line receive dgawd_handler_stopped -> ok end, + ?line process_flag(trap_exit, OTE), + ?line ok; + "nyiftos" -> + ?line process_flag(trap_exit, OTE), + ?line {skipped, "Not yet implemented for this OS"}; + Error -> + ?line process_flag(trap_exit, OTE), + ?line ?t:fail({unexpected_control_result, Error}) + end. + + +-define(CHKIO_STOP, 0). +-define(CHKIO_USE_FALLBACK_POLLSET, 1). +-define(CHKIO_BAD_FD_IN_POLLSET, 2). +-define(CHKIO_DRIVER_EVENT, 3). +-define(CHKIO_FD_CHANGE, 4). +-define(CHKIO_STEAL, 5). +-define(CHKIO_STEAL_AUX, 6). +-define(CHKIO_SMP_SELECT, 7). +-define(CHKIO_DRV_USE, 8). + +use_fallback_pollset(doc) -> []; +use_fallback_pollset(suite) -> []; +use_fallback_pollset(Config) when is_list(Config) -> + FlbkFun = fun () -> + ChkIoDuring = erlang:system_info(check_io), + case lists:keysearch(fallback_poll_set_size, + 1, + ChkIoDuring) of + {value, + {fallback_poll_set_size, N}} when N > 0 -> + ?line ok; + Error -> + ?line ?t:fail({failed_to_use_fallback, Error}) + end + end, + ?line {BckupTest, Handel, OkRes} + = case chkio_test_init(Config) of + {erts_poll_info, ChkIo} = Hndl -> + case lists:keysearch(fallback, 1, ChkIo) of + {value, {fallback, B}} when B =/= false -> + ?line {FlbkFun, Hndl, ok}; + _ -> + ?line {fun () -> ok end, + Hndl, + {comment, + "This implementation does not use " + "a fallback pollset"}} + end; + Skip -> + {fun () -> ok end, Skip, ok} + end, + ?line case chkio_test_fini(chkio_test(Handel, + ?CHKIO_USE_FALLBACK_POLLSET, + fun () -> + ?line sleep(1000), + ?line BckupTest() + end)) of + {skipped, _} = Res -> ?line Res; + _ -> ?line OkRes + end. + +bad_fd_in_pollset(doc) -> []; +bad_fd_in_pollset(suite) -> []; +bad_fd_in_pollset(Config) when is_list(Config) -> + ?line chkio_test_fini(chkio_test(chkio_test_init(Config), + ?CHKIO_BAD_FD_IN_POLLSET, + fun () -> ?line sleep(1000) end)). + +driver_event(doc) -> []; +driver_event(suite) -> []; +driver_event(Config) when is_list(Config) -> + ?line chkio_test_fini(chkio_test(chkio_test_init(Config), + ?CHKIO_DRIVER_EVENT, + fun () -> ?line sleep(1000) end)). + +fd_change(doc) -> []; +fd_change(suite) -> []; +fd_change(Config) when is_list(Config) -> + ?line chkio_test_fini(chkio_test(chkio_test_init(Config), + ?CHKIO_FD_CHANGE, + fun () -> ?line sleep(1000) end)). + +steal_control(doc) -> []; +steal_control(suite) -> []; +steal_control(Config) when is_list(Config) -> + ?line chkio_test_fini(case chkio_test_init(Config) of + {erts_poll_info, _} = Hndl -> + ?line steal_control_test(Hndl); + Skip -> + ?line Skip + end). + +steal_control_test(Hndl = {erts_poll_info, Before}) -> + ?line Port = open_chkio_port(), + ?line case erlang:port_control(Port, ?CHKIO_STEAL_AUX, "") of + [$f,$d,$s,$:| _] = FdList -> + ?line chk_chkio_port(Port), + sleep(500), + ?line chk_chkio_port(Port), + ?line Res = chkio_test(Hndl, + ?CHKIO_STEAL, + FdList, + fun () -> + ?line chk_chkio_port(Port), + ?line sleep(500), + ?line chk_chkio_port(Port) + end), + ?line case erlang:port_control(Port, ?CHKIO_STOP, "") of + "ok" -> + ?line chk_chkio_port(Port), + ?line ok; + StopErr -> + ?line chk_chkio_port(Port), + ?line ?t:fail({stop_error, StopErr}) + end, + ?line close_chkio_port(Port), + ?line Res; + [$s,$k,$i,$p,$:,$\ |Skip] -> + ?line chk_chkio_port(Port), + ?line close_chkio_port(Port), + {chkio_test_result, + {skipped, Skip}, + Before}; + StartErr -> + ?line chk_chkio_port(Port), + ?line ?t:fail({start_error, StartErr}) + end. + +chkio_test_init(Config) when is_list(Config) -> + ?line wait_until_no_pending_updates(), + ?line ChkIo = erlang:system_info(check_io), + ?line case catch lists:keysearch(name, 1, ChkIo) of + {value, {name, erts_poll}} -> + ?line ?t:format("Before test: ~p~n", [ChkIo]), + ?line Path = ?config(data_dir, Config), + ?line erl_ddll:start(), + ?line ok = load_driver(Path, 'chkio_drv'), + ?line process_flag(trap_exit, true), + ?line {erts_poll_info, ChkIo}; + _ -> + ?line {skipped, "Test written to test erts_poll() which isn't used"} + end. + + +chkio_test_fini({skipped, _} = Res) -> + Res; +chkio_test_fini({chkio_test_result, Res, Before}) -> + ?line ok = erl_ddll:unload_driver('chkio_drv'), + ?line ok = erl_ddll:stop(), + ?line wait_until_no_pending_updates(), + ?line After = erlang:system_info(check_io), + ?line ?t:format("After test: ~p~n", [After]), + ?line verify_chkio_state(Before, After), + ?line Res. + +open_chkio_port() -> + open_port({spawn, 'chkio_drv'}, []). + +close_chkio_port(Port) when is_port(Port) -> + true = erlang:port_close(Port), + receive + {'EXIT', Port, normal} -> + ok; + {'EXIT', Port, Reason} -> + ?t:fail({abnormal_port_exit, Port, Reason}); + {Port, Message} -> + ?t:fail({strange_message_from_port, Message}) + end. + +chk_chkio_port(Port) -> + receive + {'EXIT', Port, Reason} when Reason /= normal -> + ?t:fail({port_exited, Port, Reason}) + after 0 -> + ok + end. + + +chkio_test({skipped, _} = Res, _Test, _Fun) -> + ?line Res; +chkio_test({erts_poll_info, _Before} = EPI, Test, Fun) when is_integer(Test) -> + chkio_test(EPI, Test, "", Fun). + +chkio_test({skipped, _} = Res, _Test, _TestArgs, _Fun) -> + ?line Res; +chkio_test({erts_poll_info, Before}, + Test, + TestArgs, + Fun) when is_integer(Test), + is_list(TestArgs) -> + ?line Port = open_chkio_port(), + ?line case erlang:port_control(Port, Test, TestArgs) of + "ok" -> + ?line chk_chkio_port(Port), + ?line Fun(), + ?line During = erlang:system_info(check_io), + ?line erlang:display(During), + ?line 0 = erts_debug:get_internal_state(check_io_debug), + ?line ?t:format("During test: ~p~n", [During]), + ?line chk_chkio_port(Port), + ?line case erlang:port_control(Port, ?CHKIO_STOP, "") of + Res when is_list(Res) -> + ?line chk_chkio_port(Port), + ?line ?t:format("~s", [Res]), + ?line close_chkio_port(Port), + ?line Res, + ?line case Res of + [$c,$o,$m,$m,$e,$n,$t,$:,$\ |Cmnt] -> + ?line {chkio_test_result, + {comment, Cmnt}, + Before}; + _ -> + ?line {chkio_test_result, + Res, + Before} + end; + StopErr -> + ?line chk_chkio_port(Port), + ?line ?t:fail({stop_error, StopErr}) + end; + [$s,$k,$i,$p,$:,$\ |Skip] -> + ?line chk_chkio_port(Port), + ?line close_chkio_port(Port), + {chkio_test_result, + {skipped, Skip}, + Before}; + StartErr -> + ?line chk_chkio_port(Port), + ?line ?t:fail({start_error, StartErr}) + end. + +verify_chkio_state(Before, After) -> + ?line TotSetSize = lists:keysearch(total_poll_set_size, 1, Before), + ?line TotSetSize = lists:keysearch(total_poll_set_size, 1, After), + ?line case lists:keysearch(fallback, 1, Before) of + {value,{fallback,false}} -> + ?line ok; + _ -> + ?line BckupSetSize = lists:keysearch(fallback_poll_set_size, + 1, + Before), + ?line BckupSetSize = lists:keysearch(fallback_poll_set_size, + 1, + After) + end, + ?line ok. + + + +wait_until_no_pending_updates() -> + case lists:keysearch(pending_updates, 1, erlang:system_info(check_io)) of + {value, {pending_updates, 0}} -> + ok; + false -> + ok; + _ -> + receive after 10 -> ok end, + wait_until_no_pending_updates() + end. + +otp_6602(doc) -> ["Missed port lock when stealing control of fd from a " + "driver that didn't use the same lock. The lock checker " + "used to trigger on this and dump core."]; +otp_6602(suite) -> + []; +otp_6602(Config) when is_list(Config) -> + ?line {ok, Node} = start_node(Config), + ?line Done = make_ref(), + ?line Parent = self(), + ?line Tester = spawn_link(Node, + fun () -> + %% Inet driver use port locking... + {ok, S} = gen_udp:open(0), + {ok, Fd} = inet:getfd(S), + {ok, Port} = inet:port(S), + %% Steal fd (lock checker used to + %% trigger here). + {ok, _S2} = gen_udp:open(Port,[{fd,Fd}]), + Parent ! Done + end), + ?line receive Done -> ok end, + ?line unlink(Tester), + ?line stop_node(Node), + ?line ok. + +-define(EXPECTED_SYSTEM_INFO_NAMES1, + ["drv_drv_vsn", + "emu_drv_vsn", + "erts_vsn", + "otp_vsn", + "thread", + "smp"]). +-define(EXPECTED_SYSTEM_INFO_NAMES2, + (?EXPECTED_SYSTEM_INFO_NAMES1 ++ + ["async_thrs", + "sched_thrs"])). + +-define(EXPECTED_SYSTEM_INFO_NAMES, ?EXPECTED_SYSTEM_INFO_NAMES2). + +'driver_system_info_ver1.0'(doc) -> + []; +'driver_system_info_ver1.0'(suite) -> + []; +'driver_system_info_ver1.0'(Config) when is_list(Config) -> + ?line driver_system_info_test(Config, sys_info_1_0_drv). + +'driver_system_info_ver1.1'(doc) -> + []; +'driver_system_info_ver1.1'(suite) -> + []; +'driver_system_info_ver1.1'(Config) when is_list(Config) -> + ?line driver_system_info_test(Config, sys_info_1_1_drv). + +driver_system_info_current_ver(doc) -> + []; +driver_system_info_current_ver(suite) -> + []; +driver_system_info_current_ver(Config) when is_list(Config) -> + ?line driver_system_info_test(Config, sys_info_curr_drv). + +driver_system_info_test(Config, Name) -> + ?line Port = start_driver(Config, Name, false), + ?line case erlang:port_control(Port, 0, []) of + [$o,$k,$:,_ | Result] -> + ?line check_driver_system_info_result(Result); + [$e,$r,$r,$o,$r,$:,_ | Error] -> + ?line ?t:fail(Error); + Unexpected -> + ?line ?t:fail({unexpected_result, Unexpected}) + end, + ?line stop_driver(Port, Name), + ?line ok. + +check_driver_system_info_result(Result) -> + ?line ?t:format("All names: ~p~n", [?EXPECTED_SYSTEM_INFO_NAMES]), + ?line ?t:format("Result: ~p~n", [Result]), + ?line {[], Ns, DDVSN} = chk_sis(lists:map(fun (Str) -> + string:tokens(Str, "=") + end, + string:tokens(Result, " ")), + ?EXPECTED_SYSTEM_INFO_NAMES), + ?line case {DDVSN, + drv_vsn_str2tup(erlang:system_info(driver_version))} of + {DDVSN, DDVSN} -> + ?line [] = Ns; + {{1, 0}, _} -> + ?line ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES + -- ?EXPECTED_SYSTEM_INFO_NAMES1), + ?line ExpNs = lists:sort(Ns); + {{1, 1}, _} -> + ?line ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES + -- ?EXPECTED_SYSTEM_INFO_NAMES2), + ?line ExpNs = lists:sort(Ns) + end. + +chk_sis(SIs, Ns) -> + chk_sis(SIs, Ns, unknown). + +chk_sis(SIs, [], DDVSN) -> + ?line {SIs, [], DDVSN}; +chk_sis([], Ns, DDVSN) -> + ?line {[], Ns, DDVSN}; +chk_sis([[N, _] = SI| SIs], Ns, DDVSN) -> + ?line true = lists:member(N, Ns), + ?line case check_si_res(SI) of + {driver_version, NewDDVSN} -> + ?line chk_sis(SIs, lists:delete(N, Ns), NewDDVSN); + _ -> + ?line chk_sis(SIs, lists:delete(N, Ns), DDVSN) + end. + +%% Data in first version of driver_system_info() (driver version 1.0) +check_si_res(["drv_drv_vsn", Value]) -> + ?line DDVSN = drv_vsn_str2tup(Value), + ?line {Major, DMinor} = DDVSN, + ?line {Major, EMinor} = drv_vsn_str2tup(erlang:system_info(driver_version)), + ?line true = DMinor =< EMinor, + ?line {driver_version, DDVSN}; +check_si_res(["emu_drv_vsn", Value]) -> + ?line Value = erlang:system_info(driver_version); +check_si_res(["erts_vsn", Value]) -> + ?line Value = erlang:system_info(version); +check_si_res(["otp_vsn", Value]) -> + ?line Value = erlang:system_info(otp_release); +check_si_res(["thread", "true"]) -> + ?line true = erlang:system_info(threads); +check_si_res(["thread", "false"]) -> + ?line false = erlang:system_info(threads); +check_si_res(["smp", "true"]) -> + ?line true = erlang:system_info(smp_support); +check_si_res(["smp", "false"]) -> + ?line false = erlang:system_info(smp_support); + +%% Data added in second version of driver_system_info() (driver version 1.1) +check_si_res(["async_thrs", Value]) -> + ?line Value = integer_to_list(erlang:system_info(thread_pool_size)); +check_si_res(["sched_thrs", Value]) -> + ?line Value = integer_to_list(erlang:system_info(schedulers)); + +check_si_res(Unexpected) -> + ?line ?t:fail({unexpected_result, Unexpected}). + +-define(MON_OP_I_AM_IPID,1). +-define(MON_OP_MONITOR_ME,2). +-define(MON_OP_DEMONITOR_ME,3). +-define(MON_OP_MONITOR_ME_LATER,4). +-define(MON_OP_DO_DELAYED_MONITOR,5). + +driver_monitor(suite) -> + []; +driver_monitor(doc) -> + ["Test monitoring of processes from drivers"]; +driver_monitor(Config) when is_list(Config) -> + ?line Name = monitor_drv, + ?line Port = start_driver(Config, Name, false), + ?line "ok" = port_control(Port,?MON_OP_I_AM_IPID,[]), + ?line "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), + ?line "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), + ?line {monitors, []} = erlang:port_info(Port,monitors), + + ?line "ok:"++Id1 = port_control(Port,?MON_OP_MONITOR_ME_LATER,[]), + ?line {monitored_by, []} = process_info(self(),monitored_by), + ?line "ok" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id1), + ?line {monitored_by, [Port]} = process_info(self(),monitored_by), + ?line "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), + ?line {monitored_by, []} = process_info(self(),monitored_by), + + ?line "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), + ?line Me = self(), + ?line {Pid1,Ref1} = + spawn_monitor(fun() -> + Me ! port_control(Port,?MON_OP_MONITOR_ME,[]), + Me ! process_info(self(),monitored_by), + Me ! erlang:port_info(Port,monitors) + end), + ?line ok = receive + "ok" -> + ok + after 1000 -> + timeout + end, + ?line ok = receive + {monitored_by, L} -> + L2 = lists:sort(L), + L3 = lists:sort([Me,Port]), + case L2 of + L3 -> + ok; + _ -> + mismatch + end + after 1000 -> + timeout + end, + ?line ok = receive + {monitors, LL} -> + LL2 = lists:sort(LL), + LL3 = lists:sort([{process,Me},{process,Pid1}]), + case LL2 of + LL3 -> + ok; + _ -> + mismatch + end + after 1000 -> + timeout + end, + ?line ok = receive + {'DOWN', Ref1, process, Pid1, _} -> + ok + after 1000 -> + timeout + end, + ?line ok = receive + {monitor_fired,Port,Pid1} -> + ok + after 1000 -> + timeout + end, + ?line "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), + ?line {monitors,[]} = erlang:port_info(Port,monitors), + ?line {monitored_by, []} = process_info(self(),monitored_by), + + ?line "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), + ?line {Pid2,Ref2} = + spawn_monitor(fun() -> + receive go -> ok end, + Me ! port_control(Port,?MON_OP_MONITOR_ME_LATER,[]), + Me ! process_info(self(),monitored_by), + Me ! erlang:port_info(Port,monitors) + end), + ?line Pid2 ! go, + ?line {ok,Id2} = receive + "ok:"++II -> + {ok,II} + after 1000 -> + timeout + end, + ?line ok = receive + {monitored_by, [Me]} -> + ok + after 1000 -> + timeout + end, + ?line ok = receive + {monitors, [{process,Me}]} -> + ok + after 1000 -> + timeout + end, + ?line ok = receive + {'DOWN', Ref2, process, Pid2, _} -> + ok + after 1000 -> + timeout + end, + ?line "noproc" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id2), + ?line {monitors,[{process,Me}]} = erlang:port_info(Port,monitors), + ?line "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), + ?line "not_monitored" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), + ?line {monitors,[]} = erlang:port_info(Port,monitors), + ?line {monitored_by, []} = process_info(self(),monitored_by), + + + ?line "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), + ?line {Pid3,Ref3} = + spawn_monitor(fun() -> + receive go -> ok end, + Me ! port_control(Port,?MON_OP_MONITOR_ME_LATER,[]), + Me ! process_info(self(),monitored_by), + Me ! erlang:port_info(Port,monitors) , + receive die -> ok end + end), + ?line Pid3 ! go, + ?line {ok,Id3} = receive + "ok:"++III -> + {ok,III} + after 1000 -> + timeout + end, + ?line ok = receive + {monitored_by, [Me]} -> + ok + after 1000 -> + timeout + end, + ?line ok = receive + {monitors, [{process,Me}]} -> + ok + after 1000 -> + timeout + end, + ?line "ok" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id3), + ?line LLL1 = lists:sort([{process,Me},{process,Pid3}]), + ?line {monitors,LLL2} = erlang:port_info(Port,monitors), + ?line LLL1 = lists:sort(LLL2), + ?line "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), + ?line {monitors,[{process,Pid3}]} = erlang:port_info(Port,monitors), + ?line Pid3 ! die, + ?line ok = receive + {'DOWN', Ref3, process, Pid3, _} -> + ok + after 1000 -> + timeout + end, + ?line "not_found" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id2), + ?line {monitors,[]} = erlang:port_info(Port,monitors), + ?line "not_monitored" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), + ?line {monitors,[]} = erlang:port_info(Port,monitors), + ?line {monitored_by, []} = process_info(self(),monitored_by), + + ?line stop_driver(Port, Name), + ?line ok. + +ioq_exit(doc) -> []; +ioq_exit(suite) -> + [ioq_exit_ready_input, + ioq_exit_ready_output, + ioq_exit_timeout, + ioq_exit_ready_async, + ioq_exit_event, + ioq_exit_ready_input_async, + ioq_exit_ready_output_async, + ioq_exit_timeout_async, + ioq_exit_event_async]. + +-define(IOQ_EXIT_READY_INPUT, 1). +-define(IOQ_EXIT_READY_OUTPUT, 2). +-define(IOQ_EXIT_TIMEOUT, 3). +-define(IOQ_EXIT_READY_ASYNC, 4). +-define(IOQ_EXIT_EVENT, 5). +-define(IOQ_EXIT_READY_INPUT_ASYNC, 6). +-define(IOQ_EXIT_READY_OUTPUT_ASYNC, 7). +-define(IOQ_EXIT_TIMEOUT_ASYNC, 8). +-define(IOQ_EXIT_EVENT_ASYNC, 9). + +ioq_exit_test(Config, TestNo) -> + ?line Drv = ioq_exit_drv, + ?line try + begin + ?line case load_driver(?config(data_dir, Config), + Drv) of + ok -> ?line ok; + {error, permanent} -> ?line ok; + LoadError -> ?line ?t:fail({load_error, LoadError}) + end, + case open_port({spawn, Drv}, []) of + Port when is_port(Port) -> + try port_control(Port, TestNo, "") of + "ok" -> + ?line ok; + "nyiftos" -> + ?line throw({skipped, + "Not yet implemented for " + "this OS"}); + [$s,$k,$i,$p,$:,$ | Comment] -> + ?line throw({skipped, Comment}); + [$e,$r,$r,$o,$r,$:,$ | Error] -> + ?line ?t:fail(Error) + after + Port ! {self(), close}, + receive {Port, closed} -> ok end, + false = lists:member(Port, erlang:ports()), + ok + end; + Error -> + ?line ?t:fail({open_port_failed, Error}) + end + end + catch + throw:Term -> ?line Term + after + erl_ddll:unload_driver(Drv) + end. + +ioq_exit_ready_input(doc) -> []; +ioq_exit_ready_input(suite) -> []; +ioq_exit_ready_input(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_READY_INPUT). + +ioq_exit_ready_output(doc) -> []; +ioq_exit_ready_output(suite) -> []; +ioq_exit_ready_output(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_READY_OUTPUT). + +ioq_exit_timeout(doc) -> []; +ioq_exit_timeout(suite) -> []; +ioq_exit_timeout(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_TIMEOUT). + +ioq_exit_ready_async(doc) -> []; +ioq_exit_ready_async(suite) -> []; +ioq_exit_ready_async(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_READY_ASYNC). + +ioq_exit_event(doc) -> []; +ioq_exit_event(suite) -> []; +ioq_exit_event(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_EVENT). + +ioq_exit_ready_input_async(doc) -> []; +ioq_exit_ready_input_async(suite) -> []; +ioq_exit_ready_input_async(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_READY_INPUT_ASYNC). + +ioq_exit_ready_output_async(doc) -> []; +ioq_exit_ready_output_async(suite) -> []; +ioq_exit_ready_output_async(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_READY_OUTPUT_ASYNC). + +ioq_exit_timeout_async(doc) -> []; +ioq_exit_timeout_async(suite) -> []; +ioq_exit_timeout_async(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_TIMEOUT_ASYNC). + +ioq_exit_event_async(doc) -> []; +ioq_exit_event_async(suite) -> []; +ioq_exit_event_async(Config) when is_list(Config) -> + ioq_exit_test(Config, ?IOQ_EXIT_EVENT_ASYNC). + + +vsn_mismatch_test(Config, LoadResult) -> + ?line Path = ?config(data_dir, Config), + ?line DrvName = ?config(testcase, Config), + ?line LoadResult = load_driver(Path, DrvName), + ?line case LoadResult of + ok -> + ?line Port = open_port({spawn, DrvName}, []), + ?line true = is_port(Port), + ?line true = port_close(Port), + ?line ok = erl_ddll:unload_driver(DrvName); + _ -> + ?line ok + end. + +zero_extended_marker_garb_drv(doc) -> []; +zero_extended_marker_garb_drv(suite) -> []; +zero_extended_marker_garb_drv(Config) when is_list(Config) -> + vsn_mismatch_test(Config, {error, driver_incorrect_version}). + +invalid_extended_marker_drv(doc) -> []; +invalid_extended_marker_drv(suite) -> []; +invalid_extended_marker_drv(Config) when is_list(Config) -> + vsn_mismatch_test(Config, {error, driver_incorrect_version}). + +larger_major_vsn_drv(doc) -> []; +larger_major_vsn_drv(suite) -> []; +larger_major_vsn_drv(Config) when is_list(Config) -> + vsn_mismatch_test(Config, {error, driver_incorrect_version}). + +larger_minor_vsn_drv(doc) -> []; +larger_minor_vsn_drv(suite) -> []; +larger_minor_vsn_drv(Config) when is_list(Config) -> + vsn_mismatch_test(Config, {error, driver_incorrect_version}). + +smaller_major_vsn_drv(doc) -> []; +smaller_major_vsn_drv(suite) -> []; +smaller_major_vsn_drv(Config) when is_list(Config) -> + vsn_mismatch_test(Config, {error, driver_incorrect_version}). + +smaller_minor_vsn_drv(doc) -> []; +smaller_minor_vsn_drv(suite) -> []; +smaller_minor_vsn_drv(Config) when is_list(Config) -> + DrvVsnStr = erlang:system_info(driver_version), + case drv_vsn_str2tup(DrvVsnStr) of + {_, 0} -> + {skipped, + "Cannot perform test when minor driver version is 0. " + "Current driver version is " ++ DrvVsnStr ++ "."}; + _ -> + vsn_mismatch_test(Config, ok) + end. + +-define(PEEK_NONXQ_TEST, 0). +-define(PEEK_NONXQ_WAIT, 1). + +peek_non_existing_queue(doc) -> []; +peek_non_existing_queue(suite) -> []; +peek_non_existing_queue(Config) when is_list(Config) -> + ?line OTE = process_flag(trap_exit, true), + ?line Drv = peek_non_existing_queue_drv, + ?line try + begin + ?line case load_driver(?config(data_dir, Config), + Drv) of + ok -> ?line ok; + {error, permanent} -> ?line ok; + LoadError -> ?line ?t:fail({load_error, LoadError}) + end, + case open_port({spawn, Drv}, []) of + Port1 when is_port(Port1) -> + try port_control(Port1, ?PEEK_NONXQ_TEST, "") of + "ok" -> + ?line ok; + [$s,$k,$i,$p,$p,$e,$d,$:,$ | SkipReason] -> + ?line throw({skipped, SkipReason}); + [$e,$r,$r,$o,$r,$:,$ | Error1] -> + ?line ?t:fail(Error1) + after + exit(Port1, kill), + receive {'EXIT', Port1, _} -> ok end + end; + Error1 -> + ?line ?t:fail({open_port1_failed, Error1}) + end, + case open_port({spawn, Drv}, []) of + Port2 when is_port(Port2) -> + try port_control(Port2, ?PEEK_NONXQ_WAIT, "") of + "ok" -> + ?line ok; + [$e,$r,$r,$o,$r,$:,$ | Error2] -> + ?line ?t:fail(Error2) + after + receive {Port2, test_successful} -> ok end, + Port2 ! {self(), close}, + receive {Port2, closed} -> ok end + end; + Error2 -> + ?line ?t:fail({open_port2_failed, Error2}) + end + end + catch + throw:Term -> ?line Term + after + process_flag(trap_exit, OTE), + erl_ddll:unload_driver(Drv) + end. + +otp_6879(doc) -> + []; +otp_6879(suite) -> + []; +otp_6879(Config) when is_list(Config) -> + ?line Drv = 'otp_6879_drv', + ?line Parent = self(), + ?line ok = load_driver(?config(data_dir, Config), Drv), + ?line Procs = lists:map( + fun (No) -> + spawn_link( + fun () -> + case open_port({spawn, Drv}, []) of + Port when is_port(Port) -> + Res = otp_6879_call(Port, No, 10000), + erlang:port_close(Port), + Parent ! {self(), Res}; + _ -> + Parent ! {self(), + open_port_failed} + end + end) + end, + lists:seq(1,10)), + ?line lists:foreach(fun (P) -> + ?line receive + {P, ok} -> + ?line ok; + {P, Error} -> + ?line ?t:fail({P, Error}) + end + end, + Procs), + %% Also try it when input exeeds default buffer (256 bytes) + ?line Data = lists:seq(1, 1000), + ?line case open_port({spawn, Drv}, []) of + Port when is_port(Port) -> + ?line ok = otp_6879_call(Port, Data, 10), + ?line erlang:port_close(Port); + _ -> + ?line ?t:fail(open_port_failed) + end, + ?line erl_ddll:unload_driver(Drv), + ?line ok. + +otp_6879_call(_Port, _Data, 0) -> + ok; +otp_6879_call(Port, Data, N) -> + case catch erlang:port_call(Port, 0, Data) of + Data -> otp_6879_call(Port, Data, N-1); + BadData -> {mismatch, Data, BadData} + end. + +caller(doc) -> + []; +caller(suite) -> + []; +caller(Config) when is_list(Config) -> + ?line run_caller_test(Config, false), + ?line run_caller_test(Config, true). + +run_caller_test(Config, Outputv) -> + ?line Drv = 'caller_drv', + ?line Cmd = case Outputv of + true -> + ?line os:putenv("CALLER_DRV_USE_OUTPUTV", + "true"), + outputv; + false -> + ?line os:putenv("CALLER_DRV_USE_OUTPUTV", + "false"), + output + end, + ?line ok = load_driver(?config(data_dir, Config), Drv), + ?line Port = open_port({spawn, Drv}, []), + ?line true = is_port(Port), + ?line chk_caller(Port, start, self()), + ?line chk_caller(Port, + Cmd, + spawn_link( + fun () -> + port_command(Port, "") + end)), + ?line Port ! {self(), {command, ""}}, + ?line chk_caller(Port, Cmd, self()), + ?line chk_caller(Port, + control, + spawn_link( + fun () -> + port_control(Port, 0, "") + end)), + ?line chk_caller(Port, + call, + spawn_link( + fun () -> + erlang:port_call(Port, 0, "") + end)), + ?line true = port_close(Port), + ?line erl_ddll:unload_driver(Drv), + ?line ok. + +chk_caller(Port, Callback, ExpectedCaller) -> + receive + {caller, Port, Callback, Caller} -> + ExpectedCaller = Caller + end. + +many_events(suite) -> + []; +many_events(doc) -> + ["Check that many simultaneously signalled events work (win32)"]; +many_events(Config) when is_list(Config) -> + ?line Name = 'many_events_drv', + ?line Port = start_driver(Config, Name, false), + Number = "1000", + Port ! {self(), {command, Number}}, + receive + {Port, {data,Number}} -> + ?line receive %% Just to make sure the emulator does not crash + %% after this case is run (if faulty) + after 2000 -> + ok + end + after 1000 -> + ?line exit(the_driver_does_not_respond) + end, + ?line stop_driver(Port, Name), + ?line ok. + + +missing_callbacks(doc) -> + []; +missing_callbacks(suite) -> + []; +missing_callbacks(Config) when is_list(Config) -> + ?line Name = 'missing_callback_drv', + ?line Port = start_driver(Config, Name, false), + + ?line Port ! {self(), {command, "tjenix"}}, + ?line true = erlang:port_command(Port, "halloj"), + ?line {'EXIT', {badarg, _}} = (catch erlang:port_control(Port, 4711, "mors")), + ?line {'EXIT', {badarg, _}} = (catch erlang:port_call(Port, 17, "hej")), + + ?line %% Give the (non-existing) ready_output(), ready_input(), event(), + ?line %% and timeout() some time to be called. + ?line receive after 1000 -> ok end, + + ?line stop_driver(Port, Name), + ?line ok. + +smp_select(doc) -> + ["Test concurrent calls to driver_select."]; +smp_select(suite) -> + []; +smp_select(Config) when is_list(Config) -> + case os:type() of + {win32,_} -> {skipped, "Test not implemented for this OS"}; + _ -> smp_select0(Config) + end. + +smp_select0(Config) -> + ?line DrvName = 'chkio_drv', + Path = ?config(data_dir, Config), + erl_ddll:start(), + ?line ok = load_driver(Path, DrvName), + Master = self(), + ProcFun = fun()-> io:format("Worker ~p starting\n",[self()]), + ?line Port = open_port({spawn, DrvName}, []), + smp_select_loop(Port, 100000), + sleep(500), % wait for driver to handle pending events + ?line true = erlang:port_close(Port), + Master ! {ok,self()}, + io:format("Worker ~p finished\n",[self()]) + end, + ?line Pids = lists:map(fun(_) -> spawn_link(ProcFun) end, + lists:seq(1,4)), + TimeoutMsg = make_ref(), + {ok,TRef} = timer:send_after(5*1000, TimeoutMsg), % Limit test duration on slow machines + smp_select_wait(Pids, TimeoutMsg), + timer:cancel(TRef), + ?line ok = erl_ddll:unload_driver(DrvName), + ?line ok = erl_ddll:stop(), + ok. + +smp_select_loop(_, 0) -> + ok; +smp_select_loop(Port, N) -> + ?line "ok" = erlang:port_control(Port, ?CHKIO_SMP_SELECT, []), + receive + stop -> + io:format("Worker ~p stopped with ~p laps left\n",[self(), N]), + ok + after 0 -> + smp_select_loop(Port, N-1) + end. + +smp_select_wait([], _) -> + ok; +smp_select_wait(Pids, TimeoutMsg) -> + receive + {ok,Pid} when is_pid(Pid) -> + smp_select_wait(lists:delete(Pid,Pids), TimeoutMsg); + TimeoutMsg -> + lists:foreach(fun(Pid)-> Pid ! stop end, + Pids), + smp_select_wait(Pids, TimeoutMsg) + end. + + +driver_select_use(doc) -> + ["Test driver_select() with new ERL_DRV_USE flag."]; +driver_select_use(suite) -> + []; +driver_select_use(Config) when is_list(Config) -> + case os:type() of + {win32,_} -> {skipped, "Test not implemented for this OS"}; + _ -> driver_select_use0(Config) + end. + +driver_select_use0(Config) -> + ?line DrvName = 'chkio_drv', + Path = ?config(data_dir, Config), + erl_ddll:start(), + ?line ok = load_driver(Path, DrvName), + ?line Port = open_port({spawn, DrvName}, []), + ?line "ok" = erlang:port_control(Port, ?CHKIO_DRV_USE, []), + ?line {Port,{data,"TheEnd"}} = receive Msg -> Msg + after 10000 -> timeout end, + ?line true = erlang:port_close(Port), + ?line ok = erl_ddll:unload_driver(DrvName), + ?line ok = erl_ddll:stop(), + ok. + +thread_mseg_alloc_cache_clean(Config) when is_list(Config) -> + case {erlang:system_info(threads), + erlang:system_info({allocator,mseg_alloc}), + driver_alloc_sbct()} of + {_, false, _} -> + ?line {skipped, "No mseg_alloc"}; + {false, _, _} -> + ?line {skipped, "No threads"}; + {_, _, false} -> + ?line {skipped, "driver_alloc() not using the alloc_util framework"}; + {_, _, SBCT} when is_integer(SBCT), SBCT > 10*1024*1024 -> + ?line {skipped, "driver_alloc() using too large single block threshold"}; + {_, _, 0} -> + ?line {skipped, "driver_alloc() using too low single block threshold"}; + {true, MsegAllocInfo, SBCT} -> + ?line DrvName = 'thr_alloc_drv', + ?line Path = ?config(data_dir, Config), + ?line erl_ddll:start(), + ?line ok = load_driver(Path, DrvName), + ?line Port = open_port({spawn, DrvName}, []), + ?line CCI = mseg_alloc_cci(MsegAllocInfo), + ?line ?t:format("CCI = ~p~n", [CCI]), + ?line CCC = mseg_alloc_ccc(), + ?line ?t:format("CCC = ~p~n", [CCC]), + ?line thread_mseg_alloc_cache_clean_test(Port, + 10, + CCI, + SBCT+100), + ?line true = erlang:port_close(Port), + ?line ok = erl_ddll:unload_driver(DrvName), + ?line ok = erl_ddll:stop(), + ?line ok + end. + +mseg_alloc_cci(MsegAllocInfo) -> + ?line {value,{options, OL}} + = lists:keysearch(options, 1, MsegAllocInfo), + ?line {value,{cci,CCI}} = lists:keysearch(cci,1,OL), + ?line CCI. + +mseg_alloc_ccc() -> + mseg_alloc_ccc(erlang:system_info({allocator,mseg_alloc})). + +mseg_alloc_ccc(MsegAllocInfo) -> + ?line {value,{calls, CL}} + = lists:keysearch(calls, 1, MsegAllocInfo), + ?line {value,{mseg_check_cache, GigaCCC, CCC}} + = lists:keysearch(mseg_check_cache, 1, CL), + ?line GigaCCC*1000000000 + CCC. + +mseg_alloc_cached_segments() -> + mseg_alloc_cached_segments(erlang:system_info({allocator,mseg_alloc})). + +mseg_alloc_cached_segments(MsegAllocInfo) -> + ?line {value,{status, SL}} + = lists:keysearch(status, 1, MsegAllocInfo), + ?line {value,{cached_segments, CS}} + = lists:keysearch(cached_segments, 1, SL), + ?line CS. + +driver_alloc_sbct() -> + {_, _, _, As} = erlang:system_info(allocator), + case lists:keysearch(driver_alloc, 1, As) of + {value,{driver_alloc,DAOPTs}} -> + case lists:keysearch(sbct, 1, DAOPTs) of + {value,{sbct,SBCT}} -> + SBCT; + _ -> + false + end; + _ -> + false + end. + +thread_mseg_alloc_cache_clean_test(_Port, 0, _CCI, _Size) -> + ?line ok; +thread_mseg_alloc_cache_clean_test(Port, N, CCI, Size) -> + ?line wait_until(fun () -> 0 == mseg_alloc_cached_segments() end), + ?line receive after CCI+500 -> ok end, + ?line OCCC = mseg_alloc_ccc(), + ?line "ok" = erlang:port_control(Port, 0, integer_to_list(Size)), + ?line receive after CCI+500 -> ok end, + ?line CCC = mseg_alloc_ccc(), + ?line ?t:format("CCC = ~p~n", [CCC]), + ?line true = CCC > OCCC, + ?line thread_mseg_alloc_cache_clean_test(Port, N-1, CCI, Size). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Utilities +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +wait_until(Fun) -> + case Fun() of + true -> ok; + false -> + receive after 100 -> ok end, + wait_until(Fun) + end. + +drv_vsn_str2tup(Str) -> + [Major, Minor] = string:tokens(Str, "."), + {list_to_integer(Major), list_to_integer(Minor)}. + +%% Build port data from a template. + +build_data({bin,Size}) -> build_binary(Size); +build_data({list,Size}) -> build_list(Size); +build_data(int) -> random_char(); +build_data([]) -> []; +build_data([H|T]) -> [build_data(H)|build_data(T)]. + +%% Transform all binaries in a term. + +transform_bins(_Transform, []) -> []; +transform_bins(Transform, [H|T]) -> + [transform_bins(Transform, H)|transform_bins(Transform, T)]; +transform_bins(Transform, Tuple) when is_tuple(Tuple) -> + list_to_tuple([transform_bins(Transform, E) || E <- tuple_to_list(Tuple)]); +transform_bins(Transform, Bin) when is_binary(Bin) -> + Transform(Bin); +transform_bins(_Transform, Other) -> Other. + + +%% Convert all binaries in a term to sub binaries. + +make_sub_binaries(Term) -> + MakeSub = fun(Bin0) -> + Bin1 = <<243:8,0:3,Bin0/binary,31:5,19:8>>, + Sz = size(Bin0), + <<243:8,0:3,Bin:Sz/binary,31:5,19:8>> = id(Bin1), + Bin + end, + transform_bins(MakeSub, Term). + +id(I) -> I. + +%% Convert all binaries in a term to refc binaries. + +make_refc_binaries(Term) -> + F = fun(B0) -> list_to_binary([build_binary(?heap_binary_size+1),B0]) end, + transform_bins(F, Term). + +build_binary(Elements) -> + list_to_binary(build_list(Elements)). + +build_list(Elements) -> build_list(Elements, []). + +build_list(0, Acc) -> Acc; +build_list(Elements, Acc) -> build_list(Elements-1, [random_char()|Acc]). + + +%% Convert all binaries in a list to writable binaries. + +make_writable_binaries(Term) -> + transform_bins(fun(Bin) -> <<Bin/binary,1,2,3>> end, Term). + +append_to_writable_binaries(Term) -> + transform_bins(fun(Bin) -> <<Bin/binary,0:(64*1024*8)>> end, Term). + +random_char() -> + uniform(256) - 1. + +uniform(N) -> + case get(random_seed) of + undefined -> + {X, Y, Z} = time(), + random:seed(X, Y, Z); + _ -> + ok + end, + random:uniform(N). + +%% return millisecs from statistics source +erl_millisecs() -> + {Ms, S, Us} = erlang:now(), + Ms * 1000000000 + S * 1000 + Us / 1000. + +erl_millisecs({Ms,S,Us}) -> + Ms * 1000000000 + S * 1000 + Us / 1000. + +%% Start/stop drivers. +start_driver(Config, Name, Binary) -> + Path = ?config(data_dir, Config), + erl_ddll:start(), + + %% Load the driver + ok = load_driver(Path, Name), + + %% open port. + case Binary of + true -> + open_port({spawn, Name}, [binary]); + false -> + open_port({spawn, Name}, []) + end. + +stop_driver(Port, Name) -> + ?line true = erlang:port_close(Port), + receive + {Port,Message} -> + ?t:fail({strange_message_from_port,Message}) + after 0 -> + ok + end, + + %% Unload the driver. + ok = erl_ddll:unload_driver(Name), + ?line ok = erl_ddll:stop(). + +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. + +sleep() -> + receive after infinity -> ok end. + +sleep(infinity) -> + sleep(); +sleep(Ms) when is_integer(Ms), Ms >= 0 -> + receive after Ms -> ok end. + + +start_node(Config) when is_list(Config) -> + ?line Pa = filename:dirname(code:which(?MODULE)), + ?line {A, B, C} = now(), + ?line Name = list_to_atom(atom_to_list(?MODULE) + ++ "-" + ++ atom_to_list(?config(testcase, Config)) + ++ "-" + ++ integer_to_list(A) + ++ "-" + ++ integer_to_list(B) + ++ "-" + ++ integer_to_list(C)), + ?line ?t:start_node(Name, slave, [{args, "-pa "++Pa}]). + +stop_node(Node) -> + ?t:stop_node(Node). |