%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2017. 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% %% %% %% 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'/4, 'Unsigned32'/4]). -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"}, {ok, "@vendor 0", "@vendor 10415"}, {ok, [{"@vendor 0", "@vendor 10415"}, {"Proxy-Info .*M$", "&V"}, {"Proxy-Info ::= [^>]*", "& 10415 "}]}, {grouped_vendor_id_without_flag, [{"@vendor 0", "@vendor 10415"}, {"Proxy-Info ::= [^>]*", "& 10415 "}]}, {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 ::=", " &"}]}, {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 ::=", " &"}, {ok, "@avp_types", "@codecs tmod Session-Id &"}, {ok, "@avp_types", "@custom_types tmod Session-Id &"}, {avp_not_defined, "@avp_types", "@codecs tmod OctetString &"}, {avp_not_defined, "@avp_types", "@custom_types tmod OctetString &"}, {avp_already_defined, "@avp_types", "@codecs tmod Session-Id @custom_types tmod Session-Id &"}, {not_loaded, [{"@avp_types", "@inherits nomod XXX &"}, {"CEA ::=", " &"}]}, {recompile, [{"@avp_types", "@inherits " ++ ?S(?MODULE) ++ " XXX &"}, {"CEA ::=", " &"}]}, {no_dict, [{"@avp_types", "@inherits diameter XXX &"}, {"CEA ::=", " &"}]}, {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, #{module => M})], RC /= {T, Ref}]. 'A1'(T, 'Unsigned32', Ref, _Opts) -> {T, Ref}. 'Unsigned32'(T, 'A3', Ref, _Opts) -> {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()].