%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1999-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%
%%

-module(match_spec_SUITE).

-export([all/0, suite/0, not_run/1]).
-export([test_1/1, test_2/1, test_3/1, bad_match_spec_bin/1,
	 trace_control_word/1, silent/1, silent_no_ms/1, silent_test/1,
	 ms_trace2/1, ms_trace3/1, ms_trace_dead/1, boxed_and_small/1,
	 destructive_in_test_bif/1, guard_exceptions/1,
	 empty_list/1,
	 unary_plus/1, unary_minus/1, moving_labels/1]).
-export([fpe/1]).
-export([otp_9422/1]).
-export([faulty_seq_trace/1, do_faulty_seq_trace/0]).
-export([maps/1]).
-export([runner/2, loop_runner/3]).
-export([f1/1, f2/2, f3/2, fn/1, fn/2, fn/3]).
-export([do_boxed_and_small/0]).

% This test suite assumes that tracing in general works. What we test is
% the match spec functionality.

-include_lib("common_test/include/ct.hrl").

suite() ->
    [{ct_hooks,[ts_install_cth]},
     {timetrap, {seconds, 30}}].

all() -> 
    case test_server:is_native(match_spec_SUITE) of
	false ->
	    [test_1, test_2, test_3, bad_match_spec_bin,
	     trace_control_word, silent, silent_no_ms, silent_test, ms_trace2,
	     ms_trace3, ms_trace_dead, boxed_and_small, destructive_in_test_bif,
	     guard_exceptions, unary_plus, unary_minus, fpe,
	     moving_labels,
	     faulty_seq_trace,
	     empty_list,
             otp_9422,
             maps];
	true -> [not_run]
    end.

not_run(Config) when is_list(Config) ->
    {skipped, "Native Code"}.

test_1(Config) when is_list(Config) ->
    tr(fun() -> ?MODULE:f1(a) end,
       {?MODULE, f1, 1},
       [],
       [{call, {?MODULE, f1, [a]}}]),

    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$1','$1'],[{is_atom, '$1'}],[]}],
       [{call, {?MODULE, f2, [a, a]}}]),

    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$1','$1'],[{is_atom, '$1'}],[{message, false}]}],
       []),

    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$1','$1'],[{is_atom, '$1'}],[{message, 4711}]}],
       [{call, {?MODULE, f2, [a, a]}, 4711}]),

    Ref = make_ref(),
    tr(fun() -> ?MODULE:f2(Ref, Ref) end,
       {?MODULE, f2, 2},
       [{[Ref,'$1'],[{is_reference, '$1'}],[{message, 4711}]}],
       [{call, {?MODULE, f2, [Ref, Ref]}, 4711}]),
    tr(fun() -> ?MODULE:f2(Ref, Ref) end,
       {?MODULE, f2, 2},
       [{['$1',Ref],[{is_reference, '$1'}],[{message, 4711}]}],
       [{call, {?MODULE, f2, [Ref, Ref]}, 4711}]),

    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$0','$0'],[{is_atom, '$0'}],[{message, 4711}]}],
       [{call, {?MODULE, f2, [a, a]}, 4711}]),

    tr(fun() -> ?MODULE:f2(a, b) end,
       {?MODULE, f2, 2},
       [{['_','_'],[],[]}],
       [{call, {?MODULE, f2, [a, b]}}]),

    tr(fun() -> ?MODULE:f2(a, b) end,
       {?MODULE, f2, 2},
       [{['_','_'],[],[{message, '$_'}]}],
       [{call, {?MODULE, f2, [a, b]}, [a, b]}]),

    tr(fun() -> ?MODULE:f2(a, '$_') end,
       {?MODULE, f2, 2},
       [{['$1','$_'],[{is_atom, '$1'}],[]}],
       [{call, {?MODULE, f2, [a, '$_']}}]),

    tr(fun() -> ?MODULE:f1({a}) end,
       {?MODULE, f1, 1},
       [{['$1'],[{'==', '$1', {const, {a}}}],[]}],
       [{call, {?MODULE, f1, [{a}]}}]),

    tr(fun() -> ?MODULE:f1({a}) end,
       {?MODULE, f1, 1},
       [{['$1'],[{'==', '$1', {{a}}}],[]}],
       [{call, {?MODULE, f1, [{a}]}}]),

    %% Undocumented, currently.
    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$1','$1'],[{is_atom, '$1'}],[{message, 4711},
                                        {message, true}]}],
       [{call, {?MODULE, f2, [a, a]}}]),

    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$1','$1'],[{is_atom, '$1'}],[{message, 4711},
                                        {message, false}]}],
       []),

    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$1','$1'],[{is_atom, '$1'}],[kakalorum]}],
       [{call, {?MODULE, f2, [a, a]}}]),

    %% Verify that 'process_dump' can handle a matchstate on the stack.
    tr(fun() -> fbinmatch(<<0>>, 0) end,
       {?MODULE, f1, 1},
       [{['_'],[],[{message, {process_dump}}]}],
       [fun({trace, _, call, {?MODULE, f1, [0]}, _Bin}) -> true end]),

    % Error cases
    errchk([{['$1','$1'],[{is_atom, '$1'}],[{banka, kanin}]}]),
    ok.

test_2(Config) when is_list(Config) ->
    tr(fun() -> ?MODULE:f2(a, a) end,
       {?MODULE, f2, 2},
       [{['$1','$1'],[{is_atom, '$1'}],[{return_trace}]}],
       [{call, {?MODULE, f2, [a, a]}},
        {return_from, {?MODULE, f2, 2}, {a, a}}]),
    ok.

%% Test the enable_trace/2 and caller/0 PAM instructions
test_3(Config) when is_list(Config) ->
    Fun1 = fun() -> 
		   register(fnoppelklopfer,self()),
		   ?MODULE:f2(a, b),
		   ?MODULE:f2(a, b) 
	   end,
    P1 = spawn(?MODULE, runner, [self(), Fun1]),
    Pat = [{['$1','$1'],[],[{message,
                             [{enable_trace, P1, call},{caller}]}]},
           {['_','_'],[],[{message,
                           [{disable_trace, fnoppelklopfer, call}]}]}],
    Fun2 = fun() -> ?MODULE:f3(a, a) end,
    P2 = spawn(?MODULE, runner, [self(), Fun2]),
    erlang:trace(P2, true, [call]),
    erlang:trace_pattern({?MODULE, f2, 2}, Pat),
    collect(P2, [{trace, P2, call, {?MODULE, f2, [a, a]}, [true,
							     {?MODULE,f3,2}]}]),
    collect(P1, [{trace, P1, call, {?MODULE, f2, [a, b]}, [true]}]),
    ok.

otp_9422(Config) when is_list(Config) ->
    Laps = 10000,
    Fun1 = fun() -> otp_9422_tracee() end,
    P1 = spawn_link(?MODULE, loop_runner, [self(), Fun1, Laps]),
    io:format("spawned ~p as tracee\n", [P1]),

    erlang:trace(P1, true, [call, silent]),

    Fun2 = fun() -> otp_9422_trace_changer() end,
    P2 = spawn_link(?MODULE, loop_runner, [self(), Fun2, Laps]),
    io:format("spawned ~p as trace_changer\n", [P2]),

    start_collect(P1),
    start_collect(P2),

    %%receive after 10*1000 -> ok end,

    stop_collect(P1),
    stop_collect(P2, abort),
    ok.
    
otp_9422_tracee() ->
    ?MODULE:f1(a),
    ?MODULE:f1(b),
    ?MODULE:f1(c).

otp_9422_trace_changer() ->
    Pat1 = [{[a], [], [{enable_trace, arity}]}],
    erlang:trace_pattern({?MODULE, f1, 1}, Pat1),
    Pat2 = [{[b], [], [{disable_trace, arity}]}],
    erlang:trace_pattern({?MODULE, f1, 1}, Pat2).

    
    


bad_match_spec_bin(Config) when is_list(Config) ->
    {'EXIT',{badarg,_}} = (catch ets:match_spec_run([1], <<>>)),
    B0 = <<1,2>>,
    {B,_} = split_binary(B0, 0),
    {'EXIT',{badarg,_}} = (catch ets:match_spec_run([1], B)),
    ok.



%% Test the erlang:system_info(trace_control_word) and
%% erlang:system_flag(trace_control_word, Value) BIFs,
%% as well as the get_tcw/0 and set_tcw/1 PAM instructions
trace_control_word(Config) when is_list(Config) ->
    32 = Bits = tcw_bits(),
    High = 1 bsl (Bits - 1),
    erlang:system_flag(trace_control_word, 17),
    tr(fun() -> ?MODULE:f1(a) end,
       {?MODULE, f1, 1},
       [{'_',[{'=:=', {get_tcw}, 17}],[]}],
       [{call, {?MODULE, f1, [a]}}]),
    tr(fun() -> ?MODULE:f1(a) end,
       {?MODULE, f1, 1},
       [{'_',[{'=:=', {get_tcw}, 18}],[]}],
       []),
    erlang:system_flag(trace_control_word, High),
    tr(fun() -> ?MODULE:f1(a) end,
       {?MODULE, f1, 1},
       [{'_',[{'=:=', {get_tcw}, High}],[]}],
       [{call, {?MODULE, f1, [a]}}]),
    erlang:system_flag(trace_control_word, 0),
    tr(fun() -> 
               ?MODULE:f1(a), 
               ?MODULE:f1(start), 
               ?MODULE:f1(b), 
               ?MODULE:f1(c), 
               ?MODULE:f1(high), 
               ?MODULE:f1(d),
               ?MODULE:f1(stop), 
               ?MODULE:f1(e) 
       end,
       {?MODULE, f1, 1},
       [{[start],
         [],
         [{message, {set_tcw, 17}}]},
        {[stop],
         [],
         [{message, {set_tcw, 0}}]},
        {[high],
         [],
         [{message, {set_tcw, High}}]},
        {['_'],
         [{'>', {get_tcw}, 0}],
         [{set_tcw, {'+', 1, {get_tcw}}}, {message, {get_tcw}}] }],
       [{call, {?MODULE, f1, [start]}, 0},
        {call, {?MODULE, f1, [b]}, 18},
        {call, {?MODULE, f1, [c]}, 19},
        {call, {?MODULE, f1, [high]}, 19},
        {call, {?MODULE, f1, [d]}, High + 1},
        {call, {?MODULE, f1, [stop]}, High + 1}]),
    0 = erlang:system_info(trace_control_word),
    ok.

tcw_bits() ->
    tcw_bits(erlang:system_flag(trace_control_word, 0), 0, 0).

tcw_bits(Save, Prev, Bits) ->
    Curr = 1 bsl Bits,
    case catch erlang:system_flag(trace_control_word, Curr) of
        {'EXIT' , {badarg, _}} ->
            Prev = erlang:system_flag(trace_control_word, Save),
            Bits;
        Prev ->
            Curr = erlang:system_info(trace_control_word),
            tcw_bits(Save, Curr, Bits+1)
    end.


%% Test the erlang:trace(_, _, [silent]) flag
%% as well as the silent/0 PAM instruction
silent(Config) when is_list(Config) ->
    %% Global call trace
    tr(fun() -> 
               ?MODULE:f1(a),     % No trace - not active
               ?MODULE:f1(miss),  % No trace - no activation
               ?MODULE:f1(b),     % No trace - still not active
               ?MODULE:f1(start), % Trace    - activation
               ?MODULE:f1(c),     % Trace    - active
               f1(d),             % No trace - local call
               ?MODULE:f1(miss),  % Trace    - no inactivation
               ?MODULE:f1(e),     % Trace    - still active
               ?MODULE:f1(stop),  % No trace - inactivation
               ?MODULE:f1(f)      % No trace - not active
       end,
       {?MODULE, f1, 1},
       [call, silent],
       [{[start],
         [],
         [{silent, false}, {message, start}]},
        {[stop],
         [],
         [{silent, true}, {message, stop}]},
        {[miss],
         [],
         [{silent, neither_true_nor_false}, {message, miss}]},
        {['$1'],
         [],
         [{message, '$1'}] }],
       [global],
       [{call, {?MODULE, f1, [start]}, start},
        {call, {?MODULE, f1, [c]}, c},
        {call, {?MODULE, f1, [miss]}, miss},
        {call, {?MODULE, f1, [e]}, e} ]),
    %% Local call trace
    tr(fun() -> 
               ?MODULE:f1(a),     % No trace - not active
               f1(b),             % No trace - not active
               ?MODULE:f1(start), % Trace    - activation
               ?MODULE:f1(c),     % Trace    - active
               f1(d),             % Trace    - active
               f1(stop),          % No trace - inactivation
               ?MODULE:f1(e),     % No trace - not active
               f1(f)              % No trace - not active
       end,
       {?MODULE, f1, 1},
       [call, silent],
       [{[start],
         [],
         [{silent, false}, {message, start}]},
        {[stop],
         [],
         [{silent, true}, {message, stop}]},
        {['$1'],
         [],
         [{message, '$1'}] }],
       [local],
       [{call, {?MODULE, f1, [start]}, start},
        {call, {?MODULE, f1, [c]}, c},
        {call, {?MODULE, f1, [d]}, d} ]),
    ok.

%% Test the erlang:trace(_, _, [silent]) flag without match specs
silent_no_ms(Config) when is_list(Config) ->
    %% Global call trace
    %%
    %% Trace f2/2 and erlang:integer_to_list/1 without match spec
    %% and use match spec on f1/1 to control silent flag.
    tr(
      fun () -> 
              ?MODULE:f1(a),
              ?MODULE:f2(b, c),
              _ = erlang:integer_to_list(id(1)),
              ?MODULE:f3(d, e),
              ?MODULE:f1(start),
              ?MODULE:f2(f, g),
              _ = erlang:integer_to_list(id(2)),
              ?MODULE:f3(h, i),
              ?MODULE:f1(stop),
              ?MODULE:f2(j, k),
              _ = erlang:integer_to_list(id(3)),
              ?MODULE:f3(l, m)
      end,
      fun (Tracee) ->
              1 = erlang:trace(Tracee, true, [call,silent,return_to]),
              1 = erlang:trace_pattern( {?MODULE,f2,2}, [], [global]),
              1 = erlang:trace_pattern( {erlang,integer_to_list,1}, [], [global]),
              1 = erlang:trace_pattern(
                    {?MODULE,f1,1},
                    [{[start],[],[{silent,false}]},
                     {[stop],[],[{silent,true}]}],
                    [global]),
              %%
              %% Expected: (no return_to for global call trace)
              %%
              [{trace,Tracee,call,{?MODULE,f1,[start]}},
               {trace,Tracee,call,{?MODULE,f2,[f,g]}},
               {trace,Tracee,call,{erlang,integer_to_list,[2]}},
               {trace,Tracee,call,{?MODULE,f2,[h,i]}}]
      end),
    %% Local call trace
    %%
    %% Trace f2/2 and erlang:integer_to_list/1 without match spec
    %% and use match spec on f1/1 to control silent flag.
    tr(
      fun () -> 
              ?MODULE:f1(a),
              ?MODULE:f2(b, c),
              _ = erlang:integer_to_list(id(1)),
              ?MODULE:f3(d, e),
              ?MODULE:f1(start),
              ?MODULE:f2(f, g),
              _ = erlang:integer_to_list(id(2)),
              ?MODULE:f3(h, i),
              ?MODULE:f1(stop),
              ?MODULE:f2(j, k),
              _ = erlang:integer_to_list(id(3)),
              ?MODULE:f3(l, m)
      end,
      fun (Tracee) ->
              1 = erlang:trace(Tracee, true, [call,silent,return_to]),
              1 = erlang:trace_pattern( {?MODULE,f2,2}, [], [local]),
              1 = erlang:trace_pattern( {erlang,integer_to_list,1}, [], [local]),
              1 = erlang:trace_pattern(
                    {?MODULE,f1,1},
                    [{[start],[],[{silent,false}]},
                     {[stop],[],[{silent,true}]}],
                    [local]),
              %%
              %% Expected:
              %%
              [{trace,Tracee,call,{?MODULE,f1,[start]}},
               {trace,Tracee,return_to,
                {?MODULE,'-silent_no_ms/1-fun-2-',0}},
               {trace,Tracee,call,{?MODULE,f2,[f,g]}},
               {trace,Tracee,return_to,
                {?MODULE,'-silent_no_ms/1-fun-2-',0}},
               {trace,Tracee,call,{erlang,integer_to_list,[2]}},
               {trace,Tracee,return_to,
                {?MODULE,'-silent_no_ms/1-fun-2-',0}},
               {trace,Tracee,call,{?MODULE,f2,[h,i]}},
               {trace,Tracee,return_to,{?MODULE,f3,2}}]
      end).

%% Test that match_spec_test does not activate silent
silent_test(_Config) ->
    {flags,[]} = erlang:trace_info(self(),flags),
    erlang:match_spec_test([],[{'_',[],[{silent,true}]}],trace),
    {flags,[]} = erlang:trace_info(self(),flags).


%% Test the match spec functions {trace/2}
ms_trace2(Config) when is_list(Config) ->
    Tracer = self(),
    %% Meta trace init
    %%
    %% Trace global f1/1, local f2/2, global erlang:integer_to_list/1
    %% without match spec. Use match spec functions
    %% {trace/2} to control trace through fn/2,3.
    tr(
      fun () -> 
              ?MODULE:f1(a),
              ?MODULE:f2(b, c),
              _ = erlang:integer_to_list(id(1)),
              ?MODULE:f3(d, e),
              fn([all], [call,return_to,{tracer,Tracer}]),
              ?MODULE:f1(f),
              f2(g, h),
              f1(i),
              _ = erlang:integer_to_list(id(2)),
              ?MODULE:f3(j, k),
              fn([call,return_to], []),
              ?MODULE:f1(l),
              ?MODULE:f2(m, n),
              _ = erlang:integer_to_list(id(3)),
              ?MODULE:f3(o, p)
      end,
      fun (Tracee) ->
              1 = erlang:trace(Tracee, false, [all]),
              1 = erlang:trace_pattern( {?MODULE,f1,1}, [], [global]),
              1 = erlang:trace_pattern( {?MODULE,f2,2}, [], [local]),
              1 = erlang:trace_pattern( {erlang,integer_to_list,1}, [], [global]),
              3 = erlang:trace_pattern(
                    {?MODULE,fn,'_'},
                    [{['$1','$2'],[],
                      [{trace,'$1','$2'},{message,ms_trace2}]}],
                    [meta]),
              %%
              %% Expected: (no return_to for global call trace)
              %%
              Origin = {match_spec_SUITE,'-ms_trace2/1-fun-0-',1},
              [{trace_ts,Tracee,call,
                {?MODULE,fn,
                 [[all],[call,return_to,{tracer,Tracer}]]},
                ms_trace2},
               {trace,Tracee,call,{?MODULE,f1,[f]}},
               {trace,Tracee,call,{?MODULE,f2,[g,h]}},
               {trace,Tracee,return_to,Origin},
               {trace,Tracee,call,{erlang,integer_to_list,[2]}},
               {trace,Tracee,call,{?MODULE,f2,[j,k]}},
               {trace,Tracee,return_to,{?MODULE,f3,2}},
               {trace_ts,Tracee,call,
                {?MODULE,fn,
                 [[call,return_to],[]]},
                ms_trace2}]
      end),
    %% Silence valgrind
    erlang:trace_pattern({?MODULE,fn,'_'},[],[]),
    ok.



%% Test the match spec functions {trace/3}
ms_trace3(Config) when is_list(Config) ->
    TraceeName = 'match_spec_SUITE:ms_trace3',
    Tracer = self(),
    %% Meta trace init
    %%
    %% Trace global f1/1, local f2/2, global erlang:integer_to_list/1
    %% without match spec. Use match spec functions
    %% {trace/2} to control trace through fn/2,3.
    Tag = make_ref(),
    Controller =
    spawn_link(
      fun () ->
              receive 
                  {Tracee,Tag,start} ->
                      fn(TraceeName, [all], 
                         [call,return_to,send,'receive',
                          {tracer,Tracer}]),
                      Tracee ! {self(),Tag,started},
                      receive {Tracee,Tag,stop_1} -> ok end,
                      fn(Tracee, [call,return_to], []),
                      Tracee ! {self(),Tag,stopped_1},
                      receive {Tracee,Tag,stop_2} -> ok end,
                      fn(Tracee, [all], []),
                      Tracee ! {self(),Tag,stopped_2}
              end
      end),
    tr(
      fun () -> %% Action
              register(TraceeName, self()),
              ?MODULE:f1(a),
              ?MODULE:f2(b, c),
              _ = erlang:integer_to_list(id(1)),
              ?MODULE:f3(d, e),
              Controller ! {self(),Tag,start},
              receive {Controller,Tag,started} -> ok end,
              ?MODULE:f1(f),
              f2(g, h),
              f1(i),
              _ = erlang:integer_to_list(id(2)),
              ?MODULE:f3(j, k),
              Controller ! {self(),Tag,stop_1},
              receive {Controller,Tag,stopped_1} -> ok end,
              ?MODULE:f1(l),
              ?MODULE:f2(m, n),
              _ = erlang:integer_to_list(id(3)),
              ?MODULE:f3(o, p),
              Controller ! {self(),Tag,stop_2},
              receive {Controller,Tag,stopped_2} -> ok end,
              ?MODULE:f1(q),
              ?MODULE:f2(r, s),
              _ = erlang:integer_to_list(id(4)),
              ?MODULE:f3(t, u)
      end,

      fun (Tracee) -> %% Startup
              1 = erlang:trace(Tracee, false, [all]),
              1 = erlang:trace_pattern( {?MODULE,f1,1}, [], [global]),
              1 = erlang:trace_pattern( {?MODULE,f2,2}, [], [local]),
              1 = erlang:trace_pattern( {erlang,integer_to_list,1}, [], [global]),
              3 = erlang:trace_pattern(
                    {?MODULE,fn,'_'},
                    [{['$1','$2','$3'],[],
                      [{trace,'$1','$2','$3'},{message,Tag}]}],
                    [meta]),
              %%
              %% Expected: (no return_to for global call trace)
              %%
              Origin = {match_spec_SUITE,'-ms_trace3/1-fun-1-',2},
              [{trace_ts,Controller,call,
                {?MODULE,fn,[TraceeName,[all],
                             [call,return_to,send,'receive',
                              {tracer,Tracer}]]},
                Tag},
               {trace,Tracee,'receive',{Controller,Tag,started}},
               {trace,Tracee,call,{?MODULE,f1,[f]}},
               {trace,Tracee,call,{?MODULE,f2,[g,h]}},
               {trace,Tracee,return_to,Origin},
               {trace,Tracee,call,{erlang,integer_to_list,[2]}},
               {trace,Tracee,call,{?MODULE,f2,[j,k]}},
               {trace,Tracee,return_to,{?MODULE,f3,2}},
               {trace,Tracee,send,{Tracee,Tag,stop_1},Controller},
               {trace_ts,Controller,call,
                {?MODULE,fn,[Tracee,[call,return_to],[]]},
                Tag},
               {trace_ts,Controller,call,
                {?MODULE,fn,[Tracee,[all],[]]},
                Tag}]
      end),
    ok.

%% Test that a dead tracer is removed using ms
ms_trace_dead(_Config) ->
    Self = self(),
    TFun = fun F() -> receive M -> Self ! M, F() end end,
    {Tracer, MRef} = spawn_monitor(TFun),
    MetaTracer = spawn_link(TFun),
    erlang:trace_pattern({?MODULE, f1, '_'},
                         [{'_',[],[{message, false},
                                   {trace,[],
                                    [call,{const,{tracer,Tracer}}]}]}],
                         [{meta, MetaTracer}]),
    erlang:trace_pattern({?MODULE, f2, '_'}, []),
    ?MODULE:f2(1,2),
    ?MODULE:f1(1),
    {tracer,Tracer} = erlang:trace_info(self(), tracer),
    {flags,[call]} = erlang:trace_info(self(), flags),
    ?MODULE:f2(2,3),
    receive {trace, Self, call, {?MODULE, f2, _}} -> ok end,
    exit(Tracer, stop),
    receive {'DOWN',MRef,_,_,_} -> ok end,
    ?MODULE:f1(2),
    {tracer,[]} = erlang:trace_info(self(), tracer),
    ?MODULE:f2(3,4),
    TRef = erlang:trace_delivered(all),
    receive {trace_delivered, _, TRef} -> ok end,
    receive M -> ct:fail({unexpected, M}) after 10 -> ok end.

%% Test that destructive operations in test bif does not really happen
destructive_in_test_bif(Config) when is_list(Config) ->
    {ok,OldToken,_,_} = erlang:match_spec_test
				([],
				 [{'_',[],[{message,{get_seq_token}}]}],trace),
    {ok,_,_,_} = erlang:match_spec_test
			 ([],
			  [{'_',[],[{message,{set_seq_token, label, 1}}]}],
			  trace),
    {ok,OldToken,_,_} = erlang:match_spec_test
				([],
				 [{'_',[],[{message,{get_seq_token}}]}],trace),
    {ok, OldTCW,_,_} = erlang:match_spec_test
			       ([],[{'_',[],[{message,{get_tcw}}]}],trace),
    {ok,OldTCW,_,_} = erlang:match_spec_test
			      ([],
			       [{'_',[],[{message,{set_tcw, OldTCW+1}}]}],
			       trace),
    {ok, OldTCW,_,_} = erlang:match_spec_test
			       ([],[{'_',[],[{message,{get_tcw}}]}],trace),
    ok.

%% Test that the comparision between boxed and small does not crash emulator
boxed_and_small(Config) when is_list(Config) ->
    {ok, Node} = start_node(match_spec_suite_other),
    ok = rpc:call(Node,?MODULE,do_boxed_and_small,[]),
    stop_node(Node),
    ok.

do_boxed_and_small() ->
    {ok, false, _, _} = erlang:match_spec_test({0,3},[{{1.47,'_'},[],['$_']}],table),
    {ok, false, _, _} = erlang:match_spec_test({0,3},[{{12345678901234567890,'_'},[],['$_']}],table),
    {ok, false, _, _} = erlang:match_spec_test({0,3},[{{<<1,2,3,4>>,'_'},[],['$_']}],table),
    {ok, false, _, _} = erlang:match_spec_test({0,3},[{{make_ref(),'_'},[],['$_']}],table),
    ok.

%% Test that faulty seq_trace_call does not crash emulator
faulty_seq_trace(Config) when is_list(Config) ->
    {ok, Node} = start_node(match_spec_suite_other),
    ok = rpc:call(Node,?MODULE,do_faulty_seq_trace,[]),
    stop_node(Node),
    ok.

do_faulty_seq_trace() ->
    {ok,'EXIT',_,_} = erlang:match_spec_test([],[{'_',[],[{message,{set_seq_token,yxa,true}}]}],trace),
    ok.

errchk(Pat) ->
    case catch erlang:trace_pattern({?MODULE, f2, 2}, Pat) of
	{'EXIT', {badarg, _}} ->
	    ok;
	Other ->
	    ct:fail({noerror, Other})
    end.

%% Checks that unary minus works
unary_minus(Config) when is_list(Config) ->
    {ok,true,[],[]} = erlang:match_spec_test
			      (5,
			       [{'$1',
				 [{'<',{'-','$1'},-4}],
				 [true]}],
			       table),
    {ok,false,[],[]} = erlang:match_spec_test
			      (5,
			       [{'$1',
				 [{'<',{'-','$1'},-6}],
				 [true]}],
			       table),
    {ok,true,[],[]} = erlang:match_spec_test
			      (5,
			       [{'$1',
				 [{'=:=',{'-','$1',2},3}],
				 [true]}],
			       table),
    {ok,false,[],[]} = erlang:match_spec_test
			      (hej,
			       [{'$1',
				 [{'=/=',{'-','$1'},0}],
				 [true]}],
			       table),
    ok.

%% Checks that unary plus works
unary_plus(Config) when is_list(Config) ->
    {ok,true,[],[]} = erlang:match_spec_test
			      (5,
			       [{'$1',
				 [{'<',{'+','$1'},6}],
				 [true]}],
			       table),
    {ok,false,[],[]} = erlang:match_spec_test
			      (5,
			       [{'$1',
				 [{'<',{'+','$1'},4}],
				 [true]}],
			       table),
    {ok,true,[],[]} = erlang:match_spec_test
			      (5,
			       [{'$1',
				 [{'=:=',{'+','$1',2},7}],
				 [true]}],
			       table),
    {ok,false,[],[]} = erlang:match_spec_test
			      (hej,
			       [{'$1',
				 [{'=/=',{'+','$1'},0}],
				 [true]}],
			       table),
    ok.


    

%% Checks that exceptions in guards are handled correctly
guard_exceptions(Config) when is_list(Config) ->
    {ok,false,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'or',{is_integer,'$1'},{'or','$1','$1'}}],
				  [true]}],
				table),
    {ok,true,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'orelse',{is_integer,'$1'},
				    {'or','$1','$1'}}],
				  [true]}],
				table),
    {ok,false,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'orelse',{'or','$1',true},
				    {is_integer,'$1'}}],
				  [true]}],
				table),
    {ok,false,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'or',{is_integer,'$1'},
				    {'orelse','$1',true}}],
				  [true]}],
				table),
    {ok,true,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'or',{is_integer,'$1'},
				    {'orelse',true,'$1'}}],
				  [true]}],
				table),
    {ok,true,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'or',{is_integer,'$1'},
				    {'andalso',false,'$1'}}],
				  [true]}],
				table),
    {ok,false,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'or',{is_integer,'$1'},
				    {'andalso','$1',false}}],
				  [true]}],
				table),

    {ok,false,[],[]} = erlang:match_spec_test
			       (5,
				[{'$1',
				  [{'or',{is_integer,'$1'},
				    {'andalso','$1',false}}],
				  [true]}],
				table),

    ok.

%% Checks floating point exceptions in match-specs
fpe(Config) when is_list(Config) ->
    MS = [{{'$1'},[],[{'/','$1',0}]}],
    case catch (['EXIT','EXIT'] = 
		ets:match_spec_run([{1},{2}],ets:match_spec_compile(MS))) of 
	{'EXIT',_} -> ct:fail({error, "Floating point exceptions faulty"});
	_ -> ok 
    end.

%% Test maps in match-specs
maps(Config) when is_list(Config) ->
    {ok,#{},[],[]} = erlang:match_spec_test(#{}, [{'_',[],['$_']}], table),
    {ok,#{},[],[]} = erlang:match_spec_test(#{}, [{#{},[],['$_']}], table),
    {ok,false,[],[]} =
        erlang:match_spec_test(#{}, [{not_a_map,[],['$_']}], table),
    {ok,bar,[],[]} =
        erlang:match_spec_test(#{foo => bar},
                               [{#{foo => '$1'},[],['$1']}],
                               table),
    {ok,false,[],[]} =
        erlang:match_spec_test(#{foo => bar},
                               [{#{foo => qux},[],[qux]}],
                               table),
    {ok,false,[],[]} =
        erlang:match_spec_test(#{}, [{#{foo => '_'},[],[foo]}], table),
    {error,_} =
        erlang:match_spec_test(#{}, [{#{'$1' => '_'},[],[foo]}], table),
    {ok,bar,[],[]} =
        erlang:match_spec_test({#{foo => bar}},
                               [{{#{foo => '$1'}},[],['$1']}],
                               table),
    {ok,#{foo := 3},[],[]} =
        erlang:match_spec_test({}, [{{},[],[#{foo => {'+',1,2}}]}], table),

    {ok,"camembert",[],[]} =
        erlang:match_spec_test(#{b => "camembert",c => "cabécou"},
                               [{#{b => '$1',c => "cabécou"},[],['$1']}], table),

    {ok,#{a :="camembert",b := "hi"},[],[]} =
        erlang:match_spec_test(#{<<"b">> =>"camembert","c"=>"cabécou", "wat"=>"hi", b=><<"other">>},
                               [{#{<<"b">> => '$1',"wat" => '$2'},[],[#{a=>'$1',b=>'$2'}]}],
                               table),
    %% large maps

    Ls0 = [{I,<<I:32>>}||I <- lists:seq(1,415)],
    M0  = maps:from_list(Ls0),
    M1  = #{a=>1,b=>2,c=>3,d=>4},

    R1  = M0#{263 := #{ a=> 3 }},
    Ms1 = [{M1#{c:='$1'},[],[M0#{263 := #{a => '$1'}}]}],

    {ok,R1,[],[]} = erlang:match_spec_test(M1,Ms1,table),

    Ms2 = [{M0#{63:='$1', 19:='$2'},[],[M0#{19:='$1', 63:='$2'}]}],
    R2  = M0#{63 := maps:get(19,M0), 19 := maps:get(63,M0) },
    {ok,R2,[],[]} = erlang:match_spec_test(M0,Ms2,table),

    ok = maps_check_loop(M1),
    ok = maps_check_loop(M0),
    M2 = maps:from_list([{integer_to_list(K),V} || {K,V} <- Ls0]),
    ok = maps_check_loop(M2),
    ok.

maps_check_loop(M) ->
    Ks = maps:keys(M),
    maps_check_loop(M,M,M,M,Ks,lists:reverse(Ks),1).

maps_check_loop(Orig,M0,MsM0,Rm0,[K|Ks],[Rk|Rks],Ix) ->
    MsK  = list_to_atom([$$]++integer_to_list(Ix)),
    MsM1 = MsM0#{K := MsK},
    Rm1  = Rm0#{Rk := MsK},
    M1   = M0#{Rk  := maps:get(K,MsM0)},
    Ms   = [{MsM1,[],[Rm1]}],
    {ok,M1,[],[]} = erlang:match_spec_test(Orig,Ms,table),
    maps_check_loop(Orig,M1,MsM1,Rm1,Ks,Rks,Ix+1);
maps_check_loop(_,_,_,_,[],[],_) -> ok.


empty_list(Config) when is_list(Config) ->
    Val=[{'$1',[], [{message,'$1'},{message,{caller}},{return_trace}]}],
     %% Did crash debug VM in faulty assert:
    erlang:match_spec_test([],Val,trace).

moving_labels(Config) when is_list(Config) ->
    %% Force an andalso/orelse construction to be moved by placing it
    %% in a tuple followed by a constant term. Labels should still
    %% point at their correct target.
    %% 
    Ms = [{{'$1','$2'},[],[{{ok,{'andalso','$1','$2'},[1,2,3]}}]}],
    {ok,{ok,false,[1,2,3]},[],[]} =
	erlang:match_spec_test({true,false}, Ms, table),

    Ms2 = [{{'$1','$2'},[],[{{ok,{'orelse','$1','$2'},[1,2,3]}}]}],
    {ok,{ok,true,[1,2,3]},[],[]} =
	erlang:match_spec_test({true,false}, Ms2, table),

    ok.
    
tr(Fun, MFA, Pat, Expected) ->
    tr(Fun, MFA, [call], Pat, [global], Expected).

tr(Fun, MFA, TraceFlags, Pat, PatFlags, Expected0) ->
    tr(Fun,
       fun(P) ->
               erlang:trace(P, true, TraceFlags),
               erlang:trace_pattern(MFA, Pat, PatFlags),
               lists:map(
                 fun(X) when is_function(X,1) -> X;
                    (X) -> list_to_tuple([trace, P | tuple_to_list(X)])
                 end,
                 Expected0)
       end).

tr(RunFun, ControlFun) ->
    P = spawn_link(?MODULE, runner, [self(), RunFun]),
    collect(P, ControlFun(P)).

collect(P, TMs) ->
    start_collect(P),
    collect(TMs),
    stop_collect(P).

collect([]) ->
    receive
	M ->
	    io:format("Got unexpected: ~p~n", [M]),
	    flush({got_unexpected,M})
    after 17 ->
	    ok
    end;
collect([TM | TMs]) ->
    io:format(        "Expecting:      ~p~n", [TM]),
    receive
        %% We only look at trace messages with the same tracee
        %% as the message we are looking for. This because
        %% the order of trace messages is only guaranteed from
        %% within a single process.
	M0 when element(2, M0) =:= element(2, TM); is_function(TM, 1) ->
	    M = case element(1, M0) of
		    trace_ts ->
			list_to_tuple(lists:reverse(
					tl(lists:reverse(tuple_to_list(M0)))));
		    _ -> M0
		end,
	    case is_function(TM,1) of
		true ->
		    case (catch TM(M)) of
			true ->
			    io:format("Got:            ~p~n", [M]),
			    collect(TMs);
			_ ->
			    io:format("Got unexpected: ~p~n", [M]),
			    flush({got_unexpected,M})
		    end;

		false ->
		    case M of
			TM ->
			    io:format("Got:            ~p~n", [M]),
			    collect(TMs);
			_ ->
			    io:format("Got unexpected: ~p~n", [M]),
			    flush({got_unexpected,M})
		    end
	    end
    after 15000 ->
            flush(timeout)
    end.

flush(Reason) ->
    receive
        M ->
            io:format("In queue:       ~p~n", [M]),
            flush(Reason)
    after 17 ->
              ct:fail(Reason)
    end.

start_collect(P) ->
    P ! {go, self()}.

stop_collect(P) ->
    stop_collect(P, done).
stop_collect(P, Order) ->
    P ! {Order, self()},
    receive
        {gone, P} ->
            ok
    end.


runner(Collector, Fun) ->
    receive
        {go, Collector} ->
            go
    end,
    Fun(),
    receive
        {done, Collector} ->
            Collector ! {gone, self()}
    end.

loop_runner(Collector, Fun, Laps) ->
    receive
        {go, Collector} ->
            go
    end,
    loop_runner_cont(Collector, Fun, 0, Laps).

loop_runner_cont(Collector, _Fun, Laps, Laps) ->
    receive
        {done, Collector} -> ok;
        {abort, Collector} -> ok
    end,
    io:format("loop_runner ~p exit after ~p laps\n", [self(), Laps]),
    Collector ! {gone, self()};

loop_runner_cont(Collector, Fun, N, Laps) ->
    Fun(),
    receive
        {abort, Collector} ->
            io:format("loop_runner ~p aborted after ~p of ~p laps\n", [self(), N+1, Laps]),
            Collector ! {gone, self()}
    after 0 ->
              loop_runner_cont(Collector, Fun, N+1, Laps)
    end.


f1(X) ->
    {X}.

f2(X, Y) ->
    {X, Y}.

f3(X,Y) ->
    ?MODULE:f2(X,Y),
    ok.

fn(X) ->
    [X].
fn(X, Y) ->
    [X, Y].
fn(X, Y, Z) ->
    [X, Y, Z].

fbinmatch(<<Int, Rest/binary>>, Acc) ->
    fbinmatch(Rest, [?MODULE:f1(Int) | Acc]);
fbinmatch(<<>>, Acc) -> Acc.

id(X) ->
    X.

start_node(Name) ->
    Pa = filename:dirname(code:which(?MODULE)),
    Cookie = atom_to_list(erlang:get_cookie()),
    test_server:start_node(Name, slave, 
                           [{args, "-setcookie " ++ Cookie ++" -pa " ++ Pa}]).

stop_node(Node) ->
    test_server:stop_node(Node).