%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %%% 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/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2, a_test/1, outputv_echo/1, timer_measure/1, timer_cancel/1, timer_change/1, timer_delay/1, queue_echo/1, outputv_errors/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_base_ver/1, driver_system_info_prev_ver/1, driver_system_info_current_ver/1, driver_monitor/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, otp_9302/1, thr_free_drv/1, async_blast/1, thr_msg_blast/1, consume_timeslice/1, z_test/1]). -export([bin_prefix/2]). -include_lib("common_test/include/ct.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) -> 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}), 0 = element(1, erts_debug:get_internal_state(check_io_debug)), [{testcase, Case}|Config]. end_per_testcase(Case, Config) -> erlang:display({end_per_testcase, Case}), 0 = element(1, erts_debug:get_internal_state(check_io_debug)), ok. suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {minutes, 1}}]. all() -> %% Keep a_test first and z_test last... [a_test, outputv_errors, outputv_echo, queue_echo, {group, timer}, driver_unloaded, io_ready_exit, use_fallback_pollset, bad_fd_in_pollset, driver_event, fd_change, steal_control, otp_6602, driver_system_info_base_ver, driver_system_info_prev_ver, driver_system_info_current_ver, driver_monitor, {group, 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, otp_9302, thr_free_drv, async_blast, thr_msg_blast, consume_timeslice, z_test]. groups() -> [{timer, [], [timer_measure, timer_cancel, timer_delay, timer_change]}, {ioq_exit, [], [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]}]. init_per_suite(Config) -> Config. end_per_suite(_Config) -> catch erts_debug:set_internal_state(available_internal_state, false). init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %% Test sending bad types to port with an outputv-capable driver. outputv_errors(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, outputv_drv), outputv_bad_types(fun(T) -> outputv_errors_1(T), outputv_errors_1([1|T]), L = [1,2,3], outputv_errors_1([L,T]), outputv_errors_1([L|T]) end), outputv_errors_1(42), %% Test iolists that do not fit in the address space. %% Unfortunately, it would be too slow to test in a 64-bit emulator. case erlang:system_info(wordsize) of 4 -> outputv_huge_iolists(); _ -> ok end. outputv_bad_types(Test) -> Types = [-1,256,atom,42.0,{a,b,c},make_ref(),fun() -> 42 end, [1|2],<<1:1>>,<<1:9>>,<<1:15>>], _ = [Test(Type) || Type <- Types], ok. outputv_huge_iolists() -> FourGigs = 1 bsl 32, Sizes = [FourGigs+N || N <- lists:seq(0, 64)] ++ [1 bsl N || N <- lists:seq(33, 37)], Base = <<0:(1 bsl 20)/unit:8>>, [begin L = build_iolist(Sz, Base), outputv_errors_1(L) end || Sz <- Sizes], ok. outputv_errors_1(Term) -> Port = open_port({spawn_driver,outputv_drv}, []), {'EXIT',{badarg,_}} = (catch port_command(Port, Term)), port_close(Port). build_iolist(N, Base) when N < 16 -> case rand:uniform(3) of 1 -> <<Bin:N/binary,_/binary>> = Base, Bin; _ -> lists:seq(1, N) end; build_iolist(N, Base) when N =< byte_size(Base) -> case rand:uniform(3) of 1 -> <<Bin:N/binary,_/binary>> = Base, Bin; 2 -> <<Bin:N/binary,_/binary>> = Base, [Bin]; 3 -> case N rem 2 of 0 -> L = build_iolist(N div 2, Base), [L,L]; 1 -> L = build_iolist(N div 2, Base), [L,L,45] end end; build_iolist(N0, Base) -> Small = rand:uniform(15), Seq = lists:seq(1, Small), N = N0 - Small, case N rem 2 of 0 -> L = build_iolist(N div 2, Base), [L,L|Seq]; 1 -> L = build_iolist(N div 2, Base), [47,L,L|Seq] end. %% Test echoing data with a driver that supports outputv. outputv_echo(Config) when is_list(Config) -> ct:timetrap({minutes, 10}), Name = 'outputv_drv', P = start_driver(Config, Name, true), ov_test(P, {bin,0}), ov_test(P, {bin,1}), ov_test(P, {bin,2}), ov_test(P, {bin,3}), ov_test(P, {bin,4}), ov_test(P, {bin,5}), ov_test(P, {bin,6}), ov_test(P, {bin,7}), ov_test(P, {bin,8}), ov_test(P, {bin,15}), ov_test(P, {bin,16}), ov_test(P, {bin,17}), ov_test(P, {list,0}), ov_test(P, {list,1}), ov_test(P, {list,2}), ov_test(P, [int,int,{list,0},int]), ov_test(P, [int,int,{list,1},int]), ov_test(P, [int,int,{list,2}]), ov_test(P, [{list,3},int,int,{list,2}]), ov_test(P, {list,33}), ov_test(P, [{bin,0}]), ov_test(P, [{bin,1}]), ov_test(P, [{bin,2}]), ov_test(P, [{bin,3}]), ov_test(P, [{bin,4}]), ov_test(P, [{bin,5}]), ov_test(P, [{bin,6},int]), ov_test(P, [int,{bin,3}]), ov_test(P, [int|{bin,4}]), ov_test(P, [{bin,17},int,{bin,13}|{bin,3}]), ov_test(P, [int,{bin,17},int,{bin,?heap_binary_size+1}|{bin,3}]), stop_driver(P, Name), 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}} -> ct:fail("~p returned WRONG data ~p", [Port,OtherData]); Wrong -> ct: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} -> ct:fail(got_bad_data) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Driver timer test suites %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Check that timers time out in good time. timer_measure(Config) when is_list(Config) -> Name = 'timer_drv', Port = start_driver(Config, Name, false), try_timeouts(Port, 8997), stop_driver(Port, Name), ok. try_timeouts(_, 0) -> ok; try_timeouts(Port, Timeout) -> TimeBefore = erlang:monotonic_time(), erlang:port_command(Port, <<?START_TIMER,Timeout:32>>), receive {Port,{data,[?TIMER]}} -> Elapsed = erl_millisecs() - erl_millisecs(TimeBefore), io:format("Elapsed: ~p Timeout: ~p\n", [Elapsed, Timeout]), if Elapsed < Timeout -> ct:fail(too_short); Elapsed > Timeout + ?delay -> ct:fail(too_long); true -> try_timeouts(Port, Timeout div 2) end after Timeout + ?delay -> ct:fail("driver failed to timeout") end. %% Try cancelling timers set in a driver. timer_cancel(Config) when is_list(Config) -> Name = 'timer_drv', Port = start_driver(Config, Name, false), try_cancel(Port, 10000), stop_driver(Port, Name), ok. try_cancel(Port, Timeout) -> T_before = erl_millisecs(), Port ! {self(),{command,<<?START_TIMER,(Timeout + ?delay):32>>}}, receive {Port, {data, [?TIMER]}} -> ct:fail("driver timed out before cancelling it") after Timeout -> Port ! {self(), {command, [?CANCEL_TIMER]}}, receive {Port, {data, [?TIMER]}} -> ct:fail("driver timed out after cancelling it"); {Port, {data, [?CANCELLED]}} -> 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) -> ct:fail("too long real time"); Timeout == 0 -> ok; true -> try_cancel(Port, Timeout div 2) end after ?delay -> ct: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) -> Name = 'timer_drv', Port = start_driver(Config, Name, false), TimeBefore = erlang:monotonic_time(), Timeout0 = 350, 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]}} -> Elapsed = erl_millisecs() - erl_millisecs(TimeBefore), io:format("Elapsed time: ~p Timeout: ~p\n", [Elapsed,Timeout]), if Elapsed < Timeout -> ct:fail(too_short); Elapsed > Timeout + ?delay -> ct:fail(too_long); true -> ok end end, stop_driver(Port, Name), 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) -> Name = 'timer_drv', Port = start_driver(Config, Name, false), try_change_timer(Port, 10000), stop_driver(Port, Name), ok. try_change_timer(_Port, 0) -> ok; try_change_timer(Port, Timeout) -> Timeout_3 = Timeout*3, TimeBefore = erlang:monotonic_time(), erlang:port_command(Port, <<?START_TIMER,Timeout_3:32>>), erlang:port_command(Port, <<?START_TIMER,Timeout:32>>), receive {Port,{data,[?TIMER]}} -> Elapsed = erl_millisecs() - erl_millisecs(TimeBefore), io:format("Elapsed: ~p Timeout: ~p\n", [Elapsed,Timeout]), if Elapsed < Timeout -> ct:fail(too_short); Elapsed > Timeout + ?delay -> ct:fail(too_long); true -> try_timeouts(Port, Timeout div 2) end after Timeout + ?delay -> ct:fail("driver failed to timeout") end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Queue test suites %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 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 test_server:is_native(?MODULE) of true -> exit(crashes_native_code); false -> queue_echo_1(Config) end. queue_echo_1(Config) -> ct:timetrap({minutes, 10}), Name = 'queue_drv', P = start_driver(Config, Name, true), 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}]}]), stop_driver(P, Name), 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 -> ct:fail("Qb_before: ~p\n" "Qb_before+Size: ~p\n" "Qb_in_driver: ~p", [Qb_before,Sum,Qb_in_driver]) 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 -> ct:fail("Len_to_get: ~p\nBytes in queue: ~p", [Len_to_get,BytesInQueue]) 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 -> ct:fail("Bytes to dequeue: ~p\nDequeued: ~p\nQueued in port: ~P", [BytesToDequeue, Dequeued, QueuedInPort0,12]) 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 -> ct: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(Config) when is_list(Config) -> process_flag(trap_exit, true), Drv = timer_drv, User = self(), Loaded = make_ref(), Die = make_ref(), Loader = spawn(fun () -> erl_ddll:start(), ok = load_driver(proplists:get_value(data_dir, Config), Drv), User ! Loaded, receive Die -> exit(bye) end end), receive Loaded -> ok end, Port = open_port({spawn, Drv}, []), Loader ! Die, receive {'EXIT', Port, Reason} -> driver_unloaded = Reason %% Reason used to be -1 end. io_ready_exit(Config) when is_list(Config) -> OTE = process_flag(trap_exit, true), Test = self(), 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), receive dgawd_handler_started -> ok end, Drv = io_ready_exit_drv, erl_ddll:start(), ok = load_driver(proplists:get_value(data_dir, Config), Drv), Port = open_port({spawn, Drv}, []), case erlang:port_control(Port, 0, "") of "ok" -> receive {'EXIT', Port, Reason} -> case Reason of ready_output_driver_failure -> io:format("Exited in output_ready()~n"), ok; ready_input_driver_failure -> io:format("Exited in input_ready()~n"), ok; Error -> ct:fail(Error) end end, receive after 2000 -> ok end, false = dgawd_handler:got_dgawd_report(), Dgawd ! stop_dgawd_handler, receive dgawd_handler_stopped -> ok end, process_flag(trap_exit, OTE), ok; "nyiftos" -> process_flag(trap_exit, OTE), {skipped, "Not yet implemented for this OS"}; Error -> process_flag(trap_exit, OTE), ct: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(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 -> ok; Error -> ct:fail({failed_to_use_fallback, Error}) end end, {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 -> {FlbkFun, Hndl, ok}; _ -> {fun () -> ok end, Hndl, {comment, "This implementation does not use " "a fallback pollset"}} end; Skip -> {fun () -> ok end, Skip, ok} end, case chkio_test_fini(chkio_test(Handel, ?CHKIO_USE_FALLBACK_POLLSET, fun () -> sleep(1000), BckupTest() end)) of {skipped, _} = Res -> Res; _ -> OkRes end. bad_fd_in_pollset(Config) when is_list(Config) -> chkio_test_fini(chkio_test(chkio_test_init(Config), ?CHKIO_BAD_FD_IN_POLLSET, fun () -> sleep(1000) end)). driver_event(Config) when is_list(Config) -> chkio_test_fini(chkio_test(chkio_test_init(Config), ?CHKIO_DRIVER_EVENT, fun () -> sleep(1000) end)). fd_change(Config) when is_list(Config) -> chkio_test_fini(chkio_test(chkio_test_init(Config), ?CHKIO_FD_CHANGE, fun () -> sleep(1000) end)). steal_control(Config) when is_list(Config) -> chkio_test_fini(case chkio_test_init(Config) of {erts_poll_info, _} = Hndl -> steal_control_test(Hndl); Skip -> Skip end). steal_control_test(Hndl = {erts_poll_info, Before}) -> Port = open_chkio_port(), case erlang:port_control(Port, ?CHKIO_STEAL_AUX, "") of [$f,$d,$s,$:| _] = FdList -> chk_chkio_port(Port), sleep(500), chk_chkio_port(Port), Res = chkio_test(Hndl, ?CHKIO_STEAL, FdList, fun () -> chk_chkio_port(Port), sleep(500), chk_chkio_port(Port) end), case erlang:port_control(Port, ?CHKIO_STOP, "") of "ok" -> chk_chkio_port(Port), ok; StopErr -> chk_chkio_port(Port), ct:fail({stop_error, StopErr}) end, close_chkio_port(Port), Res; [$s,$k,$i,$p,$:,$\ |Skip] -> chk_chkio_port(Port), close_chkio_port(Port), {chkio_test_result, {skipped, Skip}, Before}; StartErr -> chk_chkio_port(Port), ct:fail({start_error, StartErr}) end. chkio_test_init(Config) when is_list(Config) -> ChkIo = get_stable_check_io_info(), case catch lists:keysearch(name, 1, ChkIo) of {value, {name, erts_poll}} -> io:format("Before test: ~p~n", [ChkIo]), Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, 'chkio_drv'), process_flag(trap_exit, true), {erts_poll_info, ChkIo}; _ -> {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}) -> ok = erl_ddll:unload_driver('chkio_drv'), ok = erl_ddll:stop(), After = get_stable_check_io_info(), io:format("After test: ~p~n", [After]), verify_chkio_state(Before, After), 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} -> ct:fail({abnormal_port_exit, Port, Reason}); {Port, Message} -> ct:fail({strange_message_from_port, Message}) end. chk_chkio_port(Port) -> receive {'EXIT', Port, Reason} when Reason /= normal -> ct:fail({port_exited, Port, Reason}) after 0 -> ok end. chkio_test({skipped, _} = Res, _Test, _Fun) -> 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) -> Res; chkio_test({erts_poll_info, Before}, Test, TestArgs, Fun) when is_integer(Test), is_list(TestArgs) -> Port = open_chkio_port(), case erlang:port_control(Port, Test, TestArgs) of "ok" -> chk_chkio_port(Port), Fun(), During = erlang:system_info(check_io), erlang:display(During), 0 = element(1, erts_debug:get_internal_state(check_io_debug)), io:format("During test: ~p~n", [During]), chk_chkio_port(Port), case erlang:port_control(Port, ?CHKIO_STOP, "") of Res when is_list(Res) -> chk_chkio_port(Port), io:format("~s", [Res]), close_chkio_port(Port), Res, case Res of [$c,$o,$m,$m,$e,$n,$t,$:,$\ |Cmnt] -> {chkio_test_result, {comment, Cmnt}, Before}; _ -> {chkio_test_result, Res, Before} end; StopErr -> chk_chkio_port(Port), ct:fail({stop_error, StopErr}) end; [$s,$k,$i,$p,$:,$\ |Skip] -> chk_chkio_port(Port), close_chkio_port(Port), {chkio_test_result, {skipped, Skip}, Before}; StartErr -> chk_chkio_port(Port), ct:fail({start_error, StartErr}) end. verify_chkio_state(Before, After) -> TotSetSize = lists:keysearch(total_poll_set_size, 1, Before), TotSetSize = lists:keysearch(total_poll_set_size, 1, After), case lists:keysearch(fallback, 1, Before) of {value,{fallback,false}} -> ok; _ -> BckupSetSize = lists:keysearch(fallback_poll_set_size, 1, Before), BckupSetSize = lists:keysearch(fallback_poll_set_size, 1, After) end, ok. get_stable_check_io_info() -> ChkIo = erlang:system_info(check_io), PendUpdNo = case lists:keysearch(pending_updates, 1, ChkIo) of {value, {pending_updates, PendNo}} -> PendNo; false -> 0 end, {value, {active_fds, ActFds}} = lists:keysearch(active_fds, 1, ChkIo), case {PendUpdNo, ActFds} of {0, 0} -> ChkIo; _ -> receive after 10 -> ok end, get_stable_check_io_info() end. %% 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(Config) when is_list(Config) -> {ok, Node} = start_node(Config), Done = make_ref(), Parent = self(), Tester = spawn_link(Node, fun () -> %% Inet driver use port locking... {ok, S} = gen_udp:open(0), {ok, Fd} = inet:getfd(S), %% Steal fd (lock checker used to %% trigger here). {ok, _S2} = gen_udp:open(0,[{fd,Fd}]), Parent ! Done end), receive Done -> ok end, unlink(Tester), stop_node(Node), 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_NAMES3, (?EXPECTED_SYSTEM_INFO_NAMES2 ++ ["emu_nif_vsn"])). -define(EXPECTED_SYSTEM_INFO_NAMES4, (?EXPECTED_SYSTEM_INFO_NAMES3 ++ ["dirty_sched"])). -define(EXPECTED_SYSTEM_INFO_NAMES, ?EXPECTED_SYSTEM_INFO_NAMES4). 'driver_system_info_base_ver'(Config) when is_list(Config) -> driver_system_info_test(Config, sys_info_base_drv). 'driver_system_info_prev_ver'(Config) when is_list(Config) -> driver_system_info_test(Config, sys_info_prev_drv). driver_system_info_current_ver(Config) when is_list(Config) -> driver_system_info_test(Config, sys_info_curr_drv). driver_system_info_test(Config, Name) -> Port = start_driver(Config, Name, false), case erlang:port_control(Port, 0, []) of [$o,$k,$:,_ | Result] -> check_driver_system_info_result(Result); [$e,$r,$r,$o,$r,$:,_ | Error] -> ct:fail(Error); Unexpected -> ct:fail({unexpected_result, Unexpected}) end, stop_driver(Port, Name), ok. check_driver_system_info_result(Result) -> io:format("All names: ~p~n", [?EXPECTED_SYSTEM_INFO_NAMES]), io:format("Result: ~p~n", [Result]), {[], Ns, DDVSN} = chk_sis(lists:map(fun (Str) -> string:tokens(Str, "=") end, string:tokens(Result, " ")), ?EXPECTED_SYSTEM_INFO_NAMES), case {DDVSN, drv_vsn_str2tup(erlang:system_info(driver_version))} of {DDVSN, DDVSN} -> [] = Ns; %% {{1, 0}, _} -> %% ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES %% -- ?EXPECTED_SYSTEM_INFO_NAMES1), %% ExpNs = lists:sort(Ns); %% {{1, 1}, _} -> %% ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES %% -- ?EXPECTED_SYSTEM_INFO_NAMES2), %% ExpNs = lists:sort(Ns); {{3, 0}, _} -> ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES -- ?EXPECTED_SYSTEM_INFO_NAMES3), ExpNs = lists:sort(Ns) end. chk_sis(SIs, Ns) -> chk_sis(SIs, Ns, unknown). chk_sis(SIs, [], DDVSN) -> {SIs, [], DDVSN}; chk_sis([], Ns, DDVSN) -> {[], Ns, DDVSN}; chk_sis([[N, _] = SI| SIs], Ns, DDVSN) -> true = lists:member(N, Ns), case check_si_res(SI) of {driver_version, NewDDVSN} -> chk_sis(SIs, lists:delete(N, Ns), NewDDVSN); _ -> 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]) -> DDVSN = drv_vsn_str2tup(Value), {Major, DMinor} = DDVSN, {Major, EMinor} = drv_vsn_str2tup(erlang:system_info(driver_version)), true = DMinor =< EMinor, {driver_version, DDVSN}; check_si_res(["emu_drv_vsn", Value]) -> Value = erlang:system_info(driver_version); check_si_res(["erts_vsn", Value]) -> Value = erlang:system_info(version); check_si_res(["otp_vsn", Value]) -> Value = erlang:system_info(otp_release); check_si_res(["thread", "true"]) -> true = erlang:system_info(threads); check_si_res(["thread", "false"]) -> false = erlang:system_info(threads); check_si_res(["smp", "true"]) -> true = erlang:system_info(smp_support); check_si_res(["smp", "false"]) -> 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]) -> Value = integer_to_list(erlang:system_info(thread_pool_size)); check_si_res(["sched_thrs", Value]) -> Value = integer_to_list(erlang:system_info(schedulers)); %% Data added in 3rd version of driver_system_info() (driver version 1.5) check_si_res(["emu_nif_vsn", Value]) -> Value = erlang:system_info(nif_version); %% Data added in 4th version of driver_system_info() (driver version 3.1) check_si_res(["dirty_sched", _Value]) -> true; check_si_res(Unexpected) -> ct: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). %% Test monitoring of processes from drivers driver_monitor(Config) when is_list(Config) -> Name = monitor_drv, Port = start_driver(Config, Name, false), "ok" = port_control(Port,?MON_OP_I_AM_IPID,[]), "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), {monitors, []} = erlang:port_info(Port,monitors), "ok:"++Id1 = port_control(Port,?MON_OP_MONITOR_ME_LATER,[]), {monitored_by, []} = process_info(self(),monitored_by), "ok" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id1), {monitored_by, [Port]} = process_info(self(),monitored_by), "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), {monitored_by, []} = process_info(self(),monitored_by), "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), Me = self(), {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), ok = receive "ok" -> ok after 1000 -> timeout end, 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, 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, ok = receive {'DOWN', Ref1, process, Pid1, _} -> ok after 1000 -> timeout end, ok = receive {monitor_fired,Port,Pid1} -> ok after 1000 -> timeout end, "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), {monitors,[]} = erlang:port_info(Port,monitors), {monitored_by, []} = process_info(self(),monitored_by), "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), {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), Pid2 ! go, {ok,Id2} = receive "ok:"++II -> {ok,II} after 1000 -> timeout end, ok = receive {monitored_by, [Me]} -> ok after 1000 -> timeout end, ok = receive {monitors, [{process,Me}]} -> ok after 1000 -> timeout end, ok = receive {'DOWN', Ref2, process, Pid2, _} -> ok after 1000 -> timeout end, "noproc" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id2), {monitors,[{process,Me}]} = erlang:port_info(Port,monitors), "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), "not_monitored" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), {monitors,[]} = erlang:port_info(Port,monitors), {monitored_by, []} = process_info(self(),monitored_by), "ok" = port_control(Port,?MON_OP_MONITOR_ME,[]), {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), Pid3 ! go, {ok,Id3} = receive "ok:"++III -> {ok,III} after 1000 -> timeout end, ok = receive {monitored_by, [Me]} -> ok after 1000 -> timeout end, ok = receive {monitors, [{process,Me}]} -> ok after 1000 -> timeout end, "ok" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id3), LLL1 = lists:sort([{process,Me},{process,Pid3}]), {monitors,LLL2} = erlang:port_info(Port,monitors), LLL1 = lists:sort(LLL2), "ok" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), {monitors,[{process,Pid3}]} = erlang:port_info(Port,monitors), Pid3 ! die, ok = receive {'DOWN', Ref3, process, Pid3, _} -> ok after 1000 -> timeout end, "not_found" = port_control(Port,?MON_OP_DO_DELAYED_MONITOR,Id2), {monitors,[]} = erlang:port_info(Port,monitors), "not_monitored" = port_control(Port,?MON_OP_DEMONITOR_ME,[]), {monitors,[]} = erlang:port_info(Port,monitors), {monitored_by, []} = process_info(self(),monitored_by), stop_driver(Port, Name), ok. -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) -> Drv = ioq_exit_drv, try begin case load_driver(proplists:get_value(data_dir, Config), Drv) of ok -> ok; {error, permanent} -> ok; LoadError -> ct:fail({load_error, LoadError}) end, case open_port({spawn, Drv}, []) of Port when is_port(Port) -> try port_control(Port, TestNo, "") of "ok" -> ok; "nyiftos" -> throw({skipped, "Not yet implemented for " "this OS"}); [$s,$k,$i,$p,$:,$ | Comment] -> throw({skipped, Comment}); [$e,$r,$r,$o,$r,$:,$ | Error] -> ct:fail(Error) after Port ! {self(), close}, receive {Port, closed} -> ok end, false = lists:member(Port, erlang:ports()), ok end; Error -> ct:fail({open_port_failed, Error}) end end catch throw:Term -> Term after erl_ddll:unload_driver(Drv) end. ioq_exit_ready_input(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_READY_INPUT). ioq_exit_ready_output(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_READY_OUTPUT). ioq_exit_timeout(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_TIMEOUT). ioq_exit_ready_async(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_READY_ASYNC). ioq_exit_event(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_EVENT). ioq_exit_ready_input_async(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_READY_INPUT_ASYNC). ioq_exit_ready_output_async(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_READY_OUTPUT_ASYNC). ioq_exit_timeout_async(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_TIMEOUT_ASYNC). ioq_exit_event_async(Config) when is_list(Config) -> ioq_exit_test(Config, ?IOQ_EXIT_EVENT_ASYNC). vsn_mismatch_test(Config, LoadResult) -> Path = proplists:get_value(data_dir, Config), DrvName = proplists:get_value(testcase, Config), LoadResult = load_driver(Path, DrvName), case LoadResult of ok -> Port = open_port({spawn, DrvName}, []), true = is_port(Port), true = port_close(Port), ok = erl_ddll:unload_driver(DrvName); _ -> ok end. zero_extended_marker_garb_drv(Config) when is_list(Config) -> vsn_mismatch_test(Config, {error, driver_incorrect_version}). invalid_extended_marker_drv(Config) when is_list(Config) -> vsn_mismatch_test(Config, {error, driver_incorrect_version}). larger_major_vsn_drv(Config) when is_list(Config) -> vsn_mismatch_test(Config, {error, driver_incorrect_version}). larger_minor_vsn_drv(Config) when is_list(Config) -> vsn_mismatch_test(Config, {error, driver_incorrect_version}). smaller_major_vsn_drv(Config) when is_list(Config) -> vsn_mismatch_test(Config, {error, driver_incorrect_version}). 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(Config) when is_list(Config) -> OTE = process_flag(trap_exit, true), Drv = peek_non_existing_queue_drv, try begin case load_driver(proplists:get_value(data_dir, Config), Drv) of ok -> ok; {error, permanent} -> ok; LoadError -> ct: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" -> ok; [$s,$k,$i,$p,$p,$e,$d,$:,$ | SkipReason] -> throw({skipped, SkipReason}); [$e,$r,$r,$o,$r,$:,$ | Error1] -> ct:fail(Error1) after exit(Port1, kill), receive {'EXIT', Port1, _} -> ok end end; Error1 -> ct: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" -> ok; [$e,$r,$r,$o,$r,$:,$ | Error2] -> ct:fail(Error2) after receive {Port2, test_successful} -> ok end, Port2 ! {self(), close}, receive {Port2, closed} -> ok end end; Error2 -> ct:fail({open_port2_failed, Error2}) end end catch throw:Term -> Term after process_flag(trap_exit, OTE), erl_ddll:unload_driver(Drv) end. otp_6879(Config) when is_list(Config) -> Drv = 'otp_6879_drv', Parent = self(), ok = load_driver(proplists:get_value(data_dir, Config), Drv), 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)), lists:foreach(fun (P) -> receive {P, ok} -> ok; {P, Error} -> ct:fail({P, Error}) end end, Procs), %% Also try it when input exceeds default buffer (256 bytes) Data = lists:seq(1, 1000), case open_port({spawn, Drv}, []) of Port when is_port(Port) -> ok = otp_6879_call(Port, Data, 10), erlang:port_close(Port); _ -> ct:fail(open_port_failed) end, erl_ddll:unload_driver(Drv), 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(Config) when is_list(Config) -> run_caller_test(Config, false), run_caller_test(Config, true). run_caller_test(Config, Outputv) -> Drv = 'caller_drv', Cmd = case Outputv of true -> os:putenv("CALLER_DRV_USE_OUTPUTV", "true"), outputv; false -> os:putenv("CALLER_DRV_USE_OUTPUTV", "false"), output end, ok = load_driver(proplists:get_value(data_dir, Config), Drv), Port = open_port({spawn, Drv}, []), true = is_port(Port), chk_caller(Port, start, self()), chk_caller(Port, Cmd, spawn_link( fun () -> port_command(Port, "") end)), Port ! {self(), {command, ""}}, chk_caller(Port, Cmd, self()), chk_caller(Port, control, spawn_link( fun () -> port_control(Port, 0, "") end)), chk_caller(Port, call, spawn_link( fun () -> erlang:port_call(Port, 0, "") end)), true = port_close(Port), erl_ddll:unload_driver(Drv), ok. chk_caller(Port, Callback, ExpectedCaller) -> receive {caller, Port, Callback, Caller} -> ExpectedCaller = Caller end. %% Check that many simultaneously signalled events work (win32) many_events(Config) when is_list(Config) -> Name = 'many_events_drv', Port = start_driver(Config, Name, false), Number = "1000", Port ! {self(), {command, Number}}, receive {Port, {data,Number}} -> receive %% Just to make sure the emulator does not crash %% after this case is run (if faulty) after 2000 -> ok end after 1000 -> exit(the_driver_does_not_respond) end, stop_driver(Port, Name), ok. missing_callbacks(Config) when is_list(Config) -> Name = 'missing_callback_drv', Port = start_driver(Config, Name, false), Port ! {self(), {command, "tjenix"}}, true = erlang:port_command(Port, "halloj"), {'EXIT', {badarg, _}} = (catch erlang:port_control(Port, 4711, "mors")), {'EXIT', {badarg, _}} = (catch erlang:port_call(Port, 17, "hej")), %% Give the (non-existing) ready_output(), ready_input(), event(), %% and timeout() some time to be called. receive after 1000 -> ok end, stop_driver(Port, Name), ok. %% Test concurrent calls to driver_select. 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) -> DrvName = 'chkio_drv', Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, DrvName), Master = self(), ProcFun = fun()-> io:format("Worker ~p starting\n",[self()]), Port = open_port({spawn, DrvName}, []), smp_select_loop(Port, 100000), sleep(1000), % wait for driver to handle pending events true = erlang:port_close(Port), Master ! {ok,self()}, io:format("Worker ~p finished\n",[self()]) end, 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), ok = erl_ddll:unload_driver(DrvName), ok = erl_ddll:stop(), ok. smp_select_loop(_, 0) -> ok; smp_select_loop(Port, N) -> "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. %% Test driver_select() with new ERL_DRV_USE flag. 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) -> DrvName = 'chkio_drv', Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, DrvName), Port = open_port({spawn, DrvName}, []), "ok" = erlang:port_control(Port, ?CHKIO_DRV_USE, []), {Port,{data,"TheEnd"}} = receive Msg -> Msg after 10000 -> timeout end, true = erlang:port_close(Port), ok = erl_ddll:unload_driver(DrvName), 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, _} -> {skipped, "No mseg_alloc"}; {false, _, _} -> {skipped, "No threads"}; {_, _, false} -> {skipped, "driver_alloc() not using the alloc_util framework"}; {_, _, SBCT} when is_integer(SBCT), SBCT > 10*1024*1024 -> {skipped, "driver_alloc() using too large single block threshold"}; {_, _, 0} -> {skipped, "driver_alloc() using too low single block threshold"}; {true, _MsegAllocInfo, SBCT} -> DrvName = 'thr_alloc_drv', Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, DrvName), Port = open_port({spawn, DrvName}, []), CCI = 1000, io:format("CCI = ~p~n", [CCI]), CCC = mseg_alloc_ccc(), io:format("CCC = ~p~n", [CCC]), thread_mseg_alloc_cache_clean_test(Port, 10, CCI, SBCT+100), true = erlang:port_close(Port), ok = erl_ddll:unload_driver(DrvName), ok = erl_ddll:stop(), ok end. mseg_alloc_cci(MsegAllocInfo) -> {value,{options, OL}} = lists:keysearch(options, 1, MsegAllocInfo), {value,{cci,CCI}} = lists:keysearch(cci,1,OL), CCI. mseg_alloc_ccc() -> mseg_alloc_ccc(mseg_inst_info(0)). mseg_alloc_ccc(MsegAllocInfo) -> {value,{memkind, MKL}} = lists:keysearch(memkind,1,MsegAllocInfo), {value,{calls, CL}} = lists:keysearch(calls, 1, MKL), {value,{mseg_check_cache, GigaCCC, CCC}} = lists:keysearch(mseg_check_cache, 1, CL), GigaCCC*1000000000 + CCC. mseg_alloc_cached_segments() -> mseg_alloc_cached_segments(mseg_inst_info(0)). mseg_alloc_cached_segments(MsegAllocInfo) -> MemName = "all memory", [{memkind,DrvMem}] = lists:filter(fun(E) -> case E of {memkind, [{name, MemName} | _]} -> true; _ -> false end end, MsegAllocInfo), {value,{status, SL}} = lists:keysearch(status, 1, DrvMem), {value,{cached_segments, CS}} = lists:keysearch(cached_segments, 1, SL), CS. mseg_inst_info(I) -> {value, {instance, I, Value}} = lists:keysearch(I, 2, erlang:system_info({allocator,mseg_alloc})), Value. 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) -> ok; thread_mseg_alloc_cache_clean_test(Port, N, CCI, Size) -> wait_until(fun () -> 0 == mseg_alloc_cached_segments() end), receive after CCI+500 -> ok end, OCCC = mseg_alloc_ccc(), "ok" = erlang:port_control(Port, 0, integer_to_list(Size)), receive after CCI+500 -> ok end, CCC = mseg_alloc_ccc(), io:format("CCC = ~p~n", [CCC]), true = CCC > OCCC, thread_mseg_alloc_cache_clean_test(Port, N-1, CCI, Size). otp_9302(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, otp_9302_drv), Port = open_port({spawn, otp_9302_drv}, []), true = is_port(Port), port_command(Port, ""), {msg, block} = get_port_msg(Port, infinity), {msg, job} = get_port_msg(Port, infinity), C = case erlang:system_info(thread_pool_size) of 0 -> {msg, cancel} = get_port_msg(Port, infinity), {msg, job} = get_port_msg(Port, infinity), false; _ -> case get_port_msg(Port, infinity) of {msg, cancel} -> %% Cancel always fail in Rel >= 15 {msg, job} = get_port_msg(Port, infinity), false; {msg, job} -> ok, true end end, {msg, end_of_jobs} = get_port_msg(Port, infinity), no_msg = get_port_msg(Port, 2000), port_close(Port), case C of true -> {comment, "Async job cancelled"}; false -> {comment, "Async job not cancelled"} end. thr_free_drv(Config) when is_list(Config) -> case erlang:system_info(threads) of false -> {skipped, "No thread support"}; true -> thr_free_drv_do(Config) end. thr_free_drv_do(Config) -> Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, thr_free_drv), MemBefore = driver_alloc_size(), % io:format("SID=~p", [erlang:system_info(scheduler_id)]), Port = open_port({spawn, thr_free_drv}, []), MemPeek = driver_alloc_size(), true = is_port(Port), ok = thr_free_drv_control(Port, 0), port_close(Port), MemAfter = driver_alloc_size(), io:format("MemPeek=~p~n", [MemPeek]), io:format("MemBefore=~p, MemAfter=~p~n", [MemBefore, MemAfter]), MemBefore = MemAfter, case MemPeek of undefined -> ok; _ -> true = MemPeek > MemBefore end, ok. thr_free_drv_control(Port, N) -> case erlang:port_control(Port, 0, "") of "done" -> ok; "more" -> erlang:yield(), % io:format("N=~p, SID=~p", [N, erlang:system_info(scheduler_id)]), thr_free_drv_control(Port, N+1) end. async_blast(Config) when is_list(Config) -> Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, async_blast_drv), SchedOnln = erlang:system_info(schedulers_online), MemBefore = driver_alloc_size(), Start = os:timestamp(), Blast = fun () -> Port = open_port({spawn, async_blast_drv}, []), true = is_port(Port), port_command(Port, ""), receive {Port, done} -> ok end, port_close(Port) end, Ps = lists:map(fun (N) -> spawn_opt(Blast, [{scheduler, (N rem SchedOnln)+ 1}, monitor]) end, lists:seq(1, 100)), MemMid = driver_alloc_size(), lists:foreach(fun ({Pid, Mon}) -> receive {'DOWN',Mon,process,Pid,_} -> ok end end, Ps), End = os:timestamp(), MemAfter = driver_alloc_size(), io:format("MemBefore=~p, MemMid=~p, MemAfter=~p~n", [MemBefore, MemMid, MemAfter]), AsyncBlastTime = timer:now_diff(End,Start)/1000000, io:format("AsyncBlastTime=~p~n", [AsyncBlastTime]), MemBefore = MemAfter, erlang:display({async_blast_time, AsyncBlastTime}), ok. thr_msg_blast_receiver(_Port, N, N) -> ok; thr_msg_blast_receiver(Port, N, Max) -> receive {Port, hi} -> thr_msg_blast_receiver(Port, N+1, Max) end. thr_msg_blast_receiver_proc(Port, Max, Parent, Done) -> case port_control(Port, 0, "") of "receiver" -> spawn(fun () -> thr_msg_blast_receiver_proc(Port, Max+1, Parent, Done) end), thr_msg_blast_receiver(Port, 0, Max); "done" -> Parent ! Done end. thr_msg_blast(Config) when is_list(Config) -> case erlang:system_info(smp_support) of false -> {skipped, "Non-SMP emulator; nothing to test..."}; true -> Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, thr_msg_blast_drv), MemBefore = driver_alloc_size(), Start = os:timestamp(), Port = open_port({spawn, thr_msg_blast_drv}, []), true = is_port(Port), Done = make_ref(), Me = self(), spawn(fun () -> thr_msg_blast_receiver_proc(Port, 1, Me, Done) end), receive Done -> ok end, ok = thr_msg_blast_receiver(Port, 0, 32*10000), port_close(Port), End = os:timestamp(), receive Garbage -> ct:fail({received_garbage, Port, Garbage}) after 2000 -> ok end, MemAfter = driver_alloc_size(), io:format("MemBefore=~p, MemAfter=~p~n", [MemBefore, MemAfter]), ThrMsgBlastTime = timer:now_diff(End,Start)/1000000, io:format("ThrMsgBlastTime=~p~n", [ThrMsgBlastTime]), MemBefore = MemAfter, Res = {thr_msg_blast_time, ThrMsgBlastTime}, erlang:display(Res), Res end. -define(IN_RANGE(LoW_, VaLuE_, HiGh_), case in_range(LoW_, VaLuE_, HiGh_) of true -> ok; false -> case erlang:system_info(lock_checking) of true -> io:format("~p:~p: Ignore bad sched count due to " "lock checking~n", [?MODULE,?LINE]); false -> ct:fail({unexpected_sched_counts, VaLuE_}) end end). consume_timeslice(Config) when is_list(Config) -> %% %% Verify that erl_drv_consume_timeslice() works. %% %% The first four cases expect that the command signal is %% delivered immediately, i.e., isn't scheduled. Since there %% are no conflicts these signals should normally be delivered %% immediately. However some builds and configurations may %% schedule these ops anyway, in these cases we do not verify %% scheduling counts. %% %% When signal is delivered immediately we must take into account %% that process and port are "virtualy" scheduled out and in %% in the trace generated. %% %% Port ! {_, {command, _}, and port_command() differs. The send %% instruction needs to check if the caller is out of reductions %% at the end of the instruction, since no erlang function call %% is involved. Otherwise, a sequence of send instructions would %% not be scheduled out even when out of reductions. port_commond() %% doesn't do that since it will always (since R16A) be called via %% the erlang wrappers in the erlang module. %% %% The last two cases tests scheduled operations. We create %% a conflict by executing at the same time on different %% schedulers. When only one scheduler we enable parallelism on %% the port instead. %% Path = proplists:get_value(data_dir, Config), erl_ddll:start(), ok = load_driver(Path, consume_timeslice_drv), Port = open_port({spawn, consume_timeslice_drv}, [{parallelism, false}]), Parent = self(), Go = make_ref(), "enabled" = port_control(Port, $E, ""), Proc1 = spawn_link(fun () -> receive Go -> ok end, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}} end), receive after 100 -> ok end, count_pp_sched_start(), Proc1 ! Go, wait_command_msgs(Port, 10), [{Port, Sprt1}, {Proc1, Sproc1}] = count_pp_sched_stop([Port, Proc1]), ?IN_RANGE(10, Sprt1, 10), ?IN_RANGE(5, Sproc1-10, 7), "disabled" = port_control(Port, $D, ""), Proc2 = spawn_link(fun () -> receive Go -> ok end, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}}, Port ! {Parent, {command, ""}} end), receive after 100 -> ok end, count_pp_sched_start(), Proc2 ! Go, wait_command_msgs(Port, 10), [{Port, Sprt2}, {Proc2, Sproc2}] = count_pp_sched_stop([Port, Proc2]), ?IN_RANGE(10, Sprt2, 10), ?IN_RANGE(1, Sproc2-10, 2), "enabled" = port_control(Port, $E, ""), Proc3 = spawn_link(fun () -> receive Go -> ok end, port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, "") end), count_pp_sched_start(), Proc3 ! Go, wait_command_msgs(Port, 10), [{Port, Sprt3}, {Proc3, Sproc3}] = count_pp_sched_stop([Port, Proc3]), ?IN_RANGE(10, Sprt3, 10), ?IN_RANGE(5, Sproc3-10, 7), "disabled" = port_control(Port, $D, ""), Proc4 = spawn_link(fun () -> receive Go -> ok end, port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, ""), port_command(Port, "") end), count_pp_sched_start(), Proc4 ! Go, wait_command_msgs(Port, 10), [{Port, Sprt4}, {Proc4, Sproc4}] = count_pp_sched_stop([Port, Proc4]), ?IN_RANGE(10, Sprt4, 10), ?IN_RANGE(1, Sproc4-10, 2), SOnl = erlang:system_info(schedulers_online), %% If only one scheduler use port with parallelism set to true, %% in order to trigger scheduling of command signals Port2 = case SOnl of 1 -> Port ! {self(), close}, receive {Port, closed} -> ok end, open_port({spawn, consume_timeslice_drv}, [{parallelism, true}]); _ -> process_flag(scheduler, 1), 1 = erlang:system_info(scheduler_id), Port end, count_pp_sched_start(), "enabled" = port_control(Port2, $E, ""), W5 = case SOnl of 1 -> false; _ -> W1= spawn_opt(fun () -> 2 = erlang:system_info(scheduler_id), "sleeped" = port_control(Port2, $S, "") end, [link,{scheduler,2}]), receive after 100 -> ok end, W1 end, Proc5 = spawn_opt(fun () -> receive Go -> ok end, 1 = erlang:system_info(scheduler_id), Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}} end, [link,{scheduler,1}]), receive after 100 -> ok end, Proc5 ! Go, wait_procs_exit([W5, Proc5]), wait_command_msgs(Port2, 10), [{Port2, Sprt5}, {Proc5, Sproc5}] = count_pp_sched_stop([Port2, Proc5]), ?IN_RANGE(2, Sproc5, 3), ?IN_RANGE(6, Sprt5, 20), count_pp_sched_start(), "disabled" = port_control(Port2, $D, ""), W6 = case SOnl of 1 -> false; _ -> W2= spawn_opt(fun () -> 2 = erlang:system_info(scheduler_id), "sleeped" = port_control(Port2, $S, "") end, [link,{scheduler,2}]), receive after 100 -> ok end, W2 end, Proc6 = spawn_opt(fun () -> receive Go -> ok end, 1 = erlang:system_info(scheduler_id), Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}}, Port2 ! {Parent, {command, ""}} end, [link,{scheduler,1}]), receive after 100 -> ok end, Proc6 ! Go, wait_procs_exit([W6, Proc6]), wait_command_msgs(Port2, 10), [{Port2, Sprt6}, {Proc6, Sproc6}] = count_pp_sched_stop([Port2, Proc6]), ?IN_RANGE(2, Sproc6, 3), ?IN_RANGE(2, Sprt6, 6), process_flag(scheduler, 0), Port2 ! {self(), close}, receive {Port2, closed} -> ok end, ok. wait_command_msgs(_, 0) -> ok; wait_command_msgs(Port, N) -> receive {Port, command} -> wait_command_msgs(Port, N-1) end. in_range(Low, Val, High) when is_integer(Low), is_integer(Val), is_integer(High), Low =< Val, Val =< High -> true; in_range(Low, Val, High) when is_integer(Low), is_integer(Val), is_integer(High) -> false. count_pp_sched_start() -> erlang:trace(all, true, [running_procs, running_ports, {tracer, self()}]), ok. count_pp_sched_stop(Ps) -> Td = erlang:trace_delivered(all), erlang:trace(all, false, [running_procs, running_ports, {tracer, self()}]), PNs = lists:map(fun (P) -> {P, 0} end, Ps), receive {trace_delivered, all, Td} -> ok end, Res = count_proc_sched(Ps, PNs), io:format("Scheduling counts: ~p~n", [Res]), erlang:display({scheduling_counts, Res}), Res. do_inc_pn(_P, []) -> throw(undefined); do_inc_pn(P, [{P,N}|PNs]) -> [{P,N+1}|PNs]; do_inc_pn(P, [PN|PNs]) -> [PN|do_inc_pn(P, PNs)]. inc_pn(P, PNs) -> try do_inc_pn(P, PNs) catch throw:undefined -> PNs end. count_proc_sched(Ps, PNs) -> receive TT when element(1, TT) == trace, element(3, TT) == in -> % erlang:display(TT), count_proc_sched(Ps, inc_pn(element(2, TT), PNs)); TT when element(1, TT) == trace, element(3, TT) == out -> count_proc_sched(Ps, PNs) after 0 -> PNs end. a_test(Config) when is_list(Config) -> check_io_debug(). z_test(Config) when is_list(Config) -> check_io_debug(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Utilities %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% check_io_debug() -> get_stable_check_io_info(), {NoErrorFds, NoUsedFds, NoDrvSelStructs, NoDrvEvStructs} = CheckIoDebug = erts_debug:get_internal_state(check_io_debug), HasGetHost = has_gethost(), ct:log("check_io_debug: ~p~n" "HasGetHost: ~p",[CheckIoDebug, HasGetHost]), 0 = NoErrorFds, if NoUsedFds == NoDrvSelStructs -> ok; HasGetHost andalso (NoUsedFds == (NoDrvSelStructs - 1)) -> %% If the inet_gethost port is alive, we may have %% one extra used fd that is not selected on ok end, 0 = NoDrvEvStructs, ok. has_gethost() -> has_gethost(erlang:ports()). has_gethost([P|T]) -> case erlang:port_info(P, name) of {name,"inet_gethost"++_} -> true; _ -> has_gethost(T) end; has_gethost([]) -> false. %flush_msgs() -> % receive % M -> % erlang:display(M), % flush_msgs() % after 0 -> % ok % end. wait_procs_exit([]) -> ok; wait_procs_exit([P|Ps]) when is_pid(P) -> Mon = erlang:monitor(process, P), receive {'DOWN', Mon, process, P, _} -> wait_procs_exit(Ps) end; wait_procs_exit([_|Ps]) -> wait_procs_exit(Ps). get_port_msg(Port, Timeout) -> receive {Port, What} -> {msg, What} after Timeout -> no_msg end. 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) -> rand:uniform(N). erl_millisecs() -> erl_millisecs(erlang:monotonic_time()). erl_millisecs(MonotonicTime) -> (1000*MonotonicTime)/erlang:convert_time_unit(1,seconds,native). %% Start/stop drivers. start_driver(Config, Name, Binary) -> Path = proplists:get_value(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) -> true = erlang:port_close(Port), receive {Port,Message} -> ct:fail({strange_message_from_port,Message}) after 0 -> ok end, %% Unload the driver. ok = erl_ddll:unload_driver(Name), 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) -> Pa = filename:dirname(code:which(?MODULE)), Name = list_to_atom(atom_to_list(?MODULE) ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config)) ++ "-" ++ integer_to_list(erlang:system_time(seconds)) ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))), test_server:start_node(Name, slave, [{args, "-pa "++Pa}]). stop_node(Node) -> test_server:stop_node(Node). wait_deallocations() -> try erts_debug:set_internal_state(wait, deallocations) catch error:undef -> erts_debug:set_internal_state(available_internal_state, true), wait_deallocations() end. driver_alloc_size() -> case erlang:system_info(smp_support) of true -> ok; false -> %% driver_alloc also used by elements in lock-free queues, %% give these some time to be deallocated... receive after 100 -> ok end end, wait_deallocations(), case erlang:system_info({allocator_sizes, driver_alloc}) of false -> undefined; MemInfo -> CS = lists:foldl( fun ({instance, _, L}, Acc) -> {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L), {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L), [MBCS,SBCS | Acc] end, [], MemInfo), lists:foldl( fun(L, Sz0) -> {value,{_,Sz,_,_}} = lists:keysearch(blocks_size, 1, L), Sz0+Sz end, 0, CS) end.