%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2011. 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/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,
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_ver1.0'/1,
'driver_system_info_ver1.1'/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]).
-export([bin_prefix/2]).
-include_lib("test_server/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].
end_per_testcase(Case, Config) ->
Dog = ?config(watchdog, Config),
erlang:display({end_per_testcase, Case}),
?line 0 = erts_debug:get_internal_state(check_io_debug),
?t:timetrap_cancel(Dog).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[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_ver1.0',
'driver_system_info_ver1.1',
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].
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.
outputv_errors(doc) -> "Test sending bad types to port with an outputv-capable driver.";
outputv_errors(Config) when is_list(Config) ->
?line Path = ?config(data_dir, Config),
?line erl_ddll:start(),
?line ok = load_driver(Path, outputv_drv),
outputv_bad_types(fun(T) ->
?line outputv_errors_1(T),
?line outputv_errors_1([1|T]),
?line L = [1,2,3],
?line outputv_errors_1([L,T]),
?line 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,
?line Sizes = [FourGigs+N || N <- lists:seq(0, 64)] ++
[1 bsl N || N <- lists:seq(33, 37)],
?line Base = <<0:(1 bsl 20)/unit:8>>,
[begin
?line L = build_iolist(Sz, Base),
?line 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 random: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 random: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 = random: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.
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_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.
-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(1000), % 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,{memkind, MKL}} = lists:keysearch(memkind,1,MsegAllocInfo),
?line {value,{calls, CL}} = lists:keysearch(calls, 1, MKL),
?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) ->
MemName = case is_halfword_vm() of
true -> "high memory";
false -> "all memory"
end,
?line [{memkind,DrvMem}]
= lists:filter(fun(E) -> case E of
{memkind, [{name, MemName} | _]} -> true;
_ -> false
end end, MsegAllocInfo),
?line {value,{status, SL}}
= lists:keysearch(status, 1, DrvMem),
?line {value,{cached_segments, CS}}
= lists:keysearch(cached_segments, 1, SL),
?line CS.
is_halfword_vm() ->
case {erlang:system_info({wordsize, internal}),
erlang:system_info({wordsize, external})} of
{4, 8} -> true;
{WS, WS} -> false
end.
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).