aboutsummaryrefslogblamecommitdiffstats
path: root/lib/diameter/test/diameter_compiler_SUITE.erl
blob: df4dde6240746f41211cef49676c5d7ef1820085 (plain) (tree)
1
2
3
4


                   
                                                        



















                                                                         






                           

                                


                                 


                                             


                                      


                                  
                            





                                                                              
                         
                



               

                     


                   





                                   








                                                                       





























































                                                                          


                                  






                                                               


                                           














                                          























































































































                                             








                                                                   


                                                                    








                                              
                     














                                                                   

                                                                  


                                               
                                      








                                                                              
                                

        
            
             


               












                                                                              

           
                                                  
 

                                            





                                               

                                                    

                             
                    

                                                                  




                         
                                                                              
            


                                                                  


                                            


                                                

                          
                          
                                                    
                              
                 

                                                  




                                           
                                                                              



                                                     





                                                              
                                                       


                                                                   
                              
                                        













                                                            

                                                           


                      
 
                                                                              








































                                                                              

                                                           
                       
                                         
 

                                                                  
                       
                                         


                                                                          

                                                        

                                                                
                                         































                                                                 




                                                        
                                                                              
 







                                     




                                          
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-2014. 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%
%%

%%
%% Tests of the dictionary file compiler.
%%

-module(diameter_compiler_SUITE).

-export([suite/0,
         all/0,
         init_per_suite/1,
         end_per_suite/1]).

%% testcases
-export([format/1,    format/2,
         replace/1,   replace/2,
         generate/1,  generate/4,
         flatten1/1,  flatten1/3,
         flatten2/1]).

-export([dict/0]).  %% fake dictionary module

%% dictionary callbacks for flatten2/1
-export(['A1'/3, 'Unsigned32'/3]).

-define(base, "base_rfc3588.dia").
-define(util, diameter_util).
-define(S, atom_to_list).
-define(L, integer_to_list).

%% ===========================================================================

%% RE/Replacement (in the sense of re:replace/4) pairs for morphing
%% base_rfc3588.dia. The key is 'ok' or the the expected error as
%% returned in the first element of the error tuple returned by
%% diameter_make:codec/2.
-define(REPLACE,
        [{ok,
          "",
          ""},
         {scan,
          "@id 0",
          "@id \\&"},
         {scan,
          "@name ",
          "&'"},
         {parse,
          "@id 0",
          "@id @id"},
         {avp_code_already_defined,
          "480",
          "485"},
         {uint32_out_of_range,
          "@id 0",
          "@id 4294967296"},
         {uint32_out_of_range,
          "@vendor 0",
          "@vendor 4294967296"},
         {uint32_out_of_range,
          [{"^ *Failed-AVP .*$", "&V"},
           {"@avp_types", "@avp_vendor_id 4294967296 Failed-AVP\n&"}]},
         {imported_avp_already_defined,
          "@avp_types",
          "@inherits diameter_gen_base_rfc3588 &"},
         {duplicate_import,
          [{"@avp_types", "@inherits diameter_gen_base_rfc3588 Class\n&"},
           {"@avp_types", "@inherits diameter_gen_base_rfc3588\n&"},
           {"^@avp_types[^@]*", ""},
           {"^@enum[^&]*", ""}]},
         {duplicate_section,
          "@prefix",
          "@name"},
         {already_declared,
          "@enum Termination-Cause",
          "& XXX 0\n &"},
         {already_declared,
          "@define Result-Code",
          "& XXX 1000 &"},
         {inherited_avp_already_defined,
          "@id",
          "@inherits nomod Origin-Host &"},
         {avp_already_defined,
          "@avp_types",
          "@inherits m XXX\nXXX\n&"},
         {avp_already_defined,
          "@avp_types",
          "@inherits mod1 XXX\n@inherits mod2 XXX\n&"},
         {key_already_defined,
          "DIAMETER_SUCCESS",
          "& 2001\n&"},
         {messages_without_id,
          "@id 0",
          ""},
         {avp_name_already_defined,
          "Class",
          "& 666 Time M\n&"},
         {avp_has_unknown_type,
          "Enumerated",
          "Enum"},
         {avp_has_invalid_flag,
          " -",
          " X"},
         {avp_has_duplicate_flag,
          " -",
          " MM"},
         {avp_has_vendor_id,
          "@avp_types",
          "@avp_vendor_id 667 Class\n&"},
         {avp_has_no_vendor,
          [{"^ *Class .*$", "&V"},
           {"@vendor .*", ""}]},
         {group_already_defined,
          "@grouped",
          "& Failed-AVP ::= < AVP Header: 279 > " "{AVP}\n&"},
         {grouped_avp_code_mismatch,
          "(Failed-AVP ::= [^0-9]*27)9",
          "&8"},
         {grouped_avp_has_wrong_type,
          "(Failed-AVP *279 *)Grouped",
          "\\1Time"},
         {grouped_avp_not_defined,
          "Failed-AVP *.*",
          ""},
         {grouped_avp_not_grouped,
          "Failed-AVP ::=.*\n.*}",
          ""},
         {grouped_vendor_id_without_flag,
          "(Failed-AVP .*)>",
          "\\1 668>"},
         {grouped_vendor_id_mismatch,
          [{"(Failed-AVP .*)>", "\\1 17>"},
           {"^ *Failed-AVP .*$", "&V"},
           {"@avp_types", "@avp_vendor_id 18 Failed-AVP\n&"}]},
         {ok,
          [{"(Failed-AVP .*)>", "\\1 17>"},
           {"^ *Failed-AVP .*$", "&V"}]},
         {message_name_already_defined,
          "CEA ::= .*:",
          "& 257 > {Result-Code}\n&"},
         {message_code_already_defined,
          "CEA( ::= .*)",
          "XXX\\1 {Result-Code}\n&"},
         {message_has_duplicate_flag,
          "(CER ::=.*)>",
          "\\1, REQ>"},
         {message_application_id_mismatch,
         "(CER ::=.*)>",
          "\\1 1>"},
         {invalid_avp_order,
          "CEA ::=",
          "{Result-Code} &"},
         {ok,
          "{ Product-Name",
          "* &"},
         {required_avp_has_zero_max_arity,
          "{ Product-Name",
          "*0 &"},
         {required_avp_has_zero_min_arity,
          "{ Product-Name",
          "0* &"},
         {required_avp_has_zero_min_arity,
          "{ Product-Name",
          "0*0 &"},
         {ok,
          "{ Product-Name",
          "*1 &"},
         {ok,
          "{ Product-Name",
          "1* &"},
         {ok,
          "{ Product-Name",
          "1*1 &"},
         {ok,
          "{ Product-Name",
          "2* &"},
         {ok,
          "{ Product-Name",
          "*2 &"},
         {ok,
          "{ Product-Name",
          "2*2 &"},
         {ok,
          "{ Product-Name",
          "2*3 &"},
         {qualifier_has_min_greater_than_max,
          "{ Product-Name",
          "3*2 &"},
         {ok,
          "\\[ Origin-State-Id",
          "* &"},
         {ok,
          "\\[ Origin-State-Id",
          "0* &"},
         {ok,
          "\\[ Origin-State-Id",
          "*0 &"},
         {ok,
          "\\[ Origin-State-Id",
          "0*0 &"},
         {ok,
          "\\[ Origin-State-Id",
          "0*1 &"},
         {ok,
          "\\[ Origin-State-Id",
          "0*2 &"},
         {ok,
          "\\[ Origin-State-Id",
          "*1 &"},
         {optional_avp_has_nonzero_min_arity,
          "\\[ Origin-State-Id",
          "1* &"},
         {optional_avp_has_nonzero_min_arity,
          "\\[ Origin-State-Id",
          "1*1 &"},
         {ok,
          "\\[ Origin-State-Id",
          "*2 &"},
         {optional_avp_has_nonzero_min_arity,
          "\\[ Origin-State-Id",
          "2* &"},
         {optional_avp_has_nonzero_min_arity,
          "\\[ Origin-State-Id",
          "2*2 &"},
         {optional_avp_has_nonzero_min_arity,
          "\\[ Origin-State-Id",
          "2*3 &"},
         {optional_avp_has_nonzero_min_arity,
          "\\[ Origin-State-Id",
          "3*2 &"},
         {ok,
          "^ *< Session-Id",
          "* &"},
         {ok,
          "^ *< Session-Id",
          "*0 &"},
         {ok,
          "^ *< Session-Id",
          "0* &"},
         {ok,
          "^ *< Session-Id",
          "0*0 &"},
         {ok,
          "^ *< Session-Id",
          "0*1 &"},
         {ok,
          "^ *< Session-Id",
          "0*2 &"},
         {ok,
          "^ *< Session-Id",
          "*1 &"},
         {ok,
          "^ *< Session-Id",
          "1* &"},
         {ok,
          "^ *< Session-Id",
          "1*1 &"},
         {ok,
          "^ *< Session-Id",
          "*2 &"},
         {ok,
          "^ *< Session-Id",
          "2* &"},
         {ok,
          "^ *< Session-Id",
          "2*2 &"},
         {ok,
          "^ *< Session-Id",
          "2*3 &"},
         {qualifier_has_min_greater_than_max,
          "^ *< Session-Id",
          "3*2 &"},
         {avp_already_referenced,
          "CER ::=.*",
          "& {Origin-Host}"},
         {message_missing,
          "CER ::=",
          "XXR ::= < Diameter-Header: 666, REQ > {Origin-Host} &"},
         {requested_avp_not_found,
          [{"@id", "@inherits diameter_gen_base_rfc3588 XXX &"},
           {"CEA ::=", "<XXX> &"}]},
         {requested_avp_not_found,
          [{"@id", "@inherits diameter_gen_base_rfc3588 'X X X' &"},
           {"CEA ::=", "<'X X X'> &"}]},
         {enumerated_avp_has_wrong_local_type,
          "Enumerated",
          "Time"},
         {enumerated_avp_not_defined,
         [{"{ Disconnect-Cause }", ""},
          {"^ *Disconnect-Cause .*", ""}]},
         {avp_not_defined,
          "CEA ::=",
          "<XXX> &"},
         {not_loaded,
          [{"@avp_types", "@inherits nomod XXX &"},
           {"CEA ::=", "<XXX> &"}]},
         {recompile,
          [{"@avp_types", "@inherits " ++ ?S(?MODULE) ++ " XXX &"},
           {"CEA ::=", "<XXX> &"}]},
         {no_dict,
          [{"@avp_types", "@inherits diameter XXX &"},
           {"CEA ::=", "<XXX> &"}]},
         {ok,
          "@avp_types",
          "@end & bad syntax"},
         {parse,
          "@avp_types",
          "& bad syntax"},
         {ok,
          [{"@avp_types", "& 3XXX 666 Time M 'X X X' 667 Time -"},
           {"^ *Class .*", "@avp_types"},
           {"^ *Failed-AVP ", "@avp_types &"},
           {"@grouped", "&&"},
           {"^ *Failed-AVP ::=", "@grouped &"},
           {"CEA ::=", "<'Class'> &"},
           {"@avp_types", "@inherits diameter_gen_base_rfc3588 Class\n&"},
           {"@avp_types", "@custom_types mymod "
                              "Product-Name Firmware-Revision\n"
                          "@codecs mymod "
                              "Origin-Host Origin-Realm\n&"}]}]).

%% ===========================================================================

suite() ->
    [{timetrap, {minutes, 10}}].

all() ->
    [format,
     replace,
     generate,
     flatten1,
     flatten2].

%% Error handling testcases will make an erroneous dictionary out of
%% the base dictionary and check that the expected error results.
%% ?REPLACE encodes the modifications and expected error.
init_per_suite(Config) ->
    Path = filename:join([code:lib_dir(diameter, src), "dict", ?base]),
    {ok, Bin} = file:read_file(Path),
    [{base, Bin} | Config].

end_per_suite(_Config) ->
    ok.

%% ===========================================================================
%% format/1
%%
%% Ensure that parse o format is the identity map.

format(Config) ->
    Bin = proplists:get_value(base, Config),
    [] = ?util:run([{?MODULE, [format, M, Bin]}
                    || E <- ?REPLACE,
                       {ok, M} <- [norm(E)]]).

format(Mods, Bin) ->
    B = modify(Bin, Mods),
    {ok, Dict} = parse(B, []),
    {ok, D} = parse(diameter_make:format(Dict), []),
    {Dict, Dict} = {Dict, D}.

parse(File, Opts) ->
    case diameter_make:codec(File, [parse, hrl, return | Opts]) of
        {ok, [Dict, _]} ->
            {ok, Dict};
        {error, _} = E ->
            E
    end.

%% ===========================================================================
%% replace/1
%%
%% Ensure the expected success/error when parsing a morphed common
%% dictionary.

replace(Config) ->
    Bin = proplists:get_value(base, Config),
    [] = ?util:run([{?MODULE, [replace, N, Bin]}
                    || E <- ?REPLACE,
                       N <- [norm(E)]]).

replace({E, Mods}, Bin) ->
    B = modify(Bin, Mods),
    case {E, parse(B, [{include, here()}]), Mods} of
        {ok, {ok, Dict}, _} ->
            Dict;
        {_, {error, {E,_} = T}, _} when E /= ok ->
            diameter_make:format_error(T)
    end.

re({RE, Repl}, Bin) ->
    re:replace(Bin, RE, Repl, [multiline]).

%% ===========================================================================
%% generate/1
%%
%% Ensure success when generating code and compiling.

generate(Config) ->
    Bin = proplists:get_value(base, Config),
    Rs  = lists:zip(?REPLACE, lists:seq(1, length(?REPLACE))),
    [] = ?util:run([{?MODULE, [generate, M, Bin, N, T]}
                    || {E,N} <- Rs,
                       {ok, M} <- [norm(E)],
                       T <- [erl, hrl, parse, forms]]).

generate(Mods, Bin, N, Mode) ->
    B = modify(Bin, Mods ++ [{"@name .*", "@name dict" ++ ?L(N)}]),
    {ok, Dict} = parse(B, []),
    File = "dict" ++ integer_to_list(N),
    {_, ok} = {Dict, diameter_make:codec(Dict,
                                         [{name, File},
                                          {prefix, "base"},
                                          Mode])},
    generate(Mode, File, Dict).

generate(erl, File, _) ->
    {ok, _} = compile:file(File ++ ".erl", [return_errors]);

generate(forms, File, _) ->
    {ok, [_]} = file:consult(File ++ ".F");

generate(parse, File, Dict) ->
    {ok, [Dict]} = file:consult(File ++ ".D"),  %% assert
    {ok, [F]} = diameter_make:codec(Dict, [forms, return]),
    {ok, _, _, _} = compile:forms(F, [return]);

generate(hrl, _, _) ->
    ok.

%% ===========================================================================
%% flatten1/1

flatten1(_Config) ->
    [Vsn | BaseD] = diameter_gen_base_rfc6733:dict(),
    {ok, I} = parse("@inherits diameter_gen_base_rfc6733\n", []),
    [Vsn | FlatD] = diameter_make:flatten(I),
    [] = ?util:run([{?MODULE, [flatten1, K, BaseD, FlatD]}
                    || K <- [avp_types, grouped, enum]]).

flatten1(Key, BaseD, FlatD) ->
    Vs = orddict:fetch(Key, BaseD),
    Vs = orddict:fetch(Key, FlatD).

%% ===========================================================================
%% flatten2/1

flatten2(_Config) ->
    Dict1 =
        "@name diameter_test1\n"
        "@prefix diameter_test1\n"
        "@vendor 666 test\n"
        "@avp_vendor_id 111 A1 A3\n"
        "@avp_vendor_id 222 A4 A6\n"
        "@custom_types " ++ ?S(?MODULE) ++ " A1 A4\n"
        "@codecs " ++ ?S(?MODULE) ++ " A3 A6\n"
        "@avp_types\n"
        "A1 1001 Unsigned32 V\n"
        "A2 1002 Unsigned32 V\n"
        "A3 1003 Unsigned32 V\n"
        "A4 1004 Unsigned32 V\n"
        "A5 1005 Unsigned32 V\n"
        "A6 1006 Unsigned32 V\n"
        "@end ignored\n",
    Dict2 =
        "@name diameter_test2\n"
        "@prefix diameter_test2\n"
        "@vendor 777 test\n"
        "@inherits diameter_test1 A1 A2 A3\n"
        "@inherits diameter_gen_base_rfc6733\n"
        "@avp_vendor_id 333 A1\n",

    {ok, [E1, F1]}
        = diameter_make:codec(Dict1, [erl, forms, return]),
    ct:pal("~s", [E1]),
    diameter_test1 = M1 = load_forms(F1),

    {ok, [D2, E2, F2]}
        = diameter_make:codec(Dict2, [parse, erl, forms, return]),
    ct:pal("~s", [E2]),
    diameter_test2 = M2 = load_forms(F2),

    Flat = lists:flatten(diameter_make:format(diameter_make:flatten(D2))),
    ct:pal("~s", [Flat]),
    {ok, [E3, F3]}
        = diameter_make:codec(Flat, [erl, forms, return,
                                     {name, "diameter_test3"}]),
    ct:pal("~s", [E3]),
    diameter_test3 = M3 = load_forms(F3),

    [{1001, 111, M1, 'A1'},  %% @avp_vendor_id
     {1002, 666, M1, 'A2'},  %% @vendor
     {1003, 111, M1, 'A3'},  %% @avp_vendor_id
     {1004, 222, M1, 'A4'},  %% @avp_vendor_id
     {1005, 666, M1, 'A5'},  %% @vendor
     {1006, 222, M1, 'A6'},  %% @avp_vendor_id
     {1001, 333, M2, 'A1'},  %% M2 @avp_vendor_id
     {1002, 666, M2, 'A2'},  %% M1 @vendor
     {1003, 666, M2, 'A3'},  %% M1 @vendor
     {1001, 333, M3, 'A1'},  %% (as for M2)
     {1002, 666, M3, 'A2'},  %%   "
     {1003, 666, M3, 'A3'}]  %%   "
        = [{Code, Vid, Mod, Name}
           || Mod <- [M1, M2, M3],
              Code <- lists:seq(1001, 1006),
              Vid <- [666, 111, 222, 777, 333],
              {Name, 'Unsigned32'} <- [Mod:avp_name(Code, Vid)]],

    [] = [{A,T,M,RC} || A <- ['A1', 'A3'],
                        T <- [encode, decode],
                        M <- [M2, M3],
                        Ref <- [make_ref()],
                        RC <- [M:avp(T, Ref, A)],
                        RC /= {T, Ref}].

'A1'(T, 'Unsigned32', Ref) ->
    {T, Ref}.

'Unsigned32'(T, 'A3', Ref) ->
    {T, Ref}.

load_forms(Forms) ->
    {ok, Mod, Bin, _} = compile:forms(Forms, [return]),
    {module, Mod} = code:load_binary(Mod, ?S(Mod), Bin),
    Mod.

%% ===========================================================================

modify(Bin, Mods) ->
    lists:foldl(fun re/2, Bin, Mods).

norm({E, RE, Repl}) ->
    {E, [{RE, Repl}]};
norm({_,_} = T) ->
    T.

here() ->
    filename:dirname(code:which(?MODULE)).

dict() ->
    [0 | orddict:new()].