%%
%% %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_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]).

-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_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].

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_base_ver'(doc) ->
    [];
'driver_system_info_base_ver'(suite) ->
    [];
'driver_system_info_base_ver'(Config) when is_list(Config) ->
    ?line driver_system_info_test(Config, sys_info_base_drv).

'driver_system_info_prev_ver'(doc) ->
    [];
'driver_system_info_prev_ver'(suite) ->
    [];
'driver_system_info_prev_ver'(Config) when is_list(Config) ->
    ?line driver_system_info_test(Config, sys_info_prev_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 exceeds 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 = 1000,
	    ?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(mseg_inst_info(0)).

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(mseg_inst_info(0)).

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.

mseg_inst_info(I) ->
    {value, {instance, I, Value}}
	= lists:keysearch(I,
			  2,
			  erlang:system_info({allocator,mseg_alloc})),
    Value.

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

otp_9302(Config) when is_list(Config) ->
    ?line Path = ?config(data_dir, Config),
    ?line erl_ddll:start(),
    ?line ok = load_driver(Path, otp_9302_drv),
    ?line Port = open_port({spawn, otp_9302_drv}, []),
    ?line true = is_port(Port),
    ?line port_command(Port, ""),
    ?line {msg, block} = get_port_msg(Port, infinity),
    ?line {msg, job} = get_port_msg(Port, infinity),
    ?line C = case erlang:system_info(thread_pool_size) of
		  0 ->
		      ?line {msg, cancel} = get_port_msg(Port, infinity),
		      ?line {msg, job} = get_port_msg(Port, infinity),
		      ?line false;
		  _ ->
		      case get_port_msg(Port, infinity) of
			  {msg, cancel} -> %% Cancel always fail in Rel >= 15
			      ?line {msg, job} = get_port_msg(Port, infinity),
			      ?line false;
			  {msg, job} ->
			      ?line ok,
			      ?line true
		      end
	      end,
    ?line {msg, end_of_jobs} = get_port_msg(Port, infinity),
    ?line no_msg = get_port_msg(Port, 2000),
    ?line port_close(Port),
    ?line case C of
	      true ->
		  ?line {comment, "Async job cancelled"};
	      false ->
		  ?line {comment, "Async job not cancelled"}
	  end.

thr_free_drv(Config) when is_list(Config) ->
    ?line Path = ?config(data_dir, Config),
    ?line erl_ddll:start(),
    ?line ok = load_driver(Path, thr_free_drv),
    ?line MemBefore = driver_alloc_size(),
%    io:format("SID=~p", [erlang:system_info(scheduler_id)]),
    ?line Port = open_port({spawn, thr_free_drv}, []),
    ?line MemPeek = driver_alloc_size(),
    ?line true = is_port(Port),
    ?line ok = thr_free_drv_control(Port, 0),
    ?line port_close(Port),
    ?line MemAfter = driver_alloc_size(),
    ?line io:format("MemPeek=~p~n", [MemPeek]),
    ?line io:format("MemBefore=~p, MemAfter=~p~n", [MemBefore, MemAfter]),
    ?line MemBefore = MemAfter,
    ?line case MemPeek of
	      undefined -> ok;
	      _ ->
		  ?line true = MemPeek > MemBefore
	  end,
    ?line 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) ->
    ?line Path = ?config(data_dir, Config),
    ?line erl_ddll:start(),
    ?line ok = load_driver(Path, async_blast_drv),
    ?line SchedOnln = erlang:system_info(schedulers_online),
    ?line MemBefore = driver_alloc_size(),
    ?line Start = os:timestamp(),
    ?line 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,
    ?line Ps = lists:map(fun (N) ->
				 spawn_opt(Blast,
					   [{scheduler,
					     (N rem SchedOnln)+ 1},
					    monitor])
			 end,
			 lists:seq(1, 100)),
    ?line MemMid = driver_alloc_size(),
    ?line lists:foreach(fun ({Pid, Mon}) ->
				receive
				    {'DOWN',Mon,process,Pid,_} -> ok
				end
			end, Ps),
    ?line End = os:timestamp(),
    ?line MemAfter = driver_alloc_size(),
    ?line io:format("MemBefore=~p, MemMid=~p, MemAfter=~p~n",
		    [MemBefore, MemMid, MemAfter]),
    ?line AsyncBlastTime = timer:now_diff(End,Start)/1000000,
    ?line io:format("AsyncBlastTime=~p~n", [AsyncBlastTime]),
    ?line MemBefore = MemAfter,
    ?line erlang:display({async_blast_time, AsyncBlastTime}),
    ?line ok.



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 		Utilities
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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

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,{_,SBMBCS}} = lists:keysearch(sbmbcs, 1, L),
			   {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L),
			   {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L),
			   [SBMBCS,MBCS,SBCS | Acc]
		   end,
		   [],
		   MemInfo),
	    lists:foldl(
	      fun(L, Sz0) ->
		      {value,{_,Sz,_,_}} = lists:keysearch(blocks_size, 1, L),
		      Sz0+Sz
	      end, 0, CS)
    end.