%%
%% %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.