%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% %%---------------------------------------------------------------------- %% Purpose: Test library module for Megaco/H.248 encode/decode %%---------------------------------------------------------------------- -module(megaco_codec_test_lib). %% ---- -include_lib("megaco/include/megaco.hrl"). -include_lib("megaco/include/megaco_message_v1.hrl"). -include("megaco_test_lib.hrl"). %% ---- -export([ skip/1, display_text_messages/2, display_text_messages/3, generate_text_messages/4, test_msgs/6, plain_decode_encode/5, plain_encode_decode/5, trans_first_encode_decode/5, actions_first_encode_decode/5, action_first_encode_decode/5, encode_message/4, decode_message/5, decode_message/6, expect_instruction/3, expect_encode/3, expect_encode_only/3, expect_encode_decode/4, expect_encode_decode_only/4, expect_decode/3, expect_decode_only/3, expect_decode_encode/4, expect_decode_encode_only/4, expect_exec/2 ]). -record(expect_instruction, { %% Short description of what this instruction does description, % string() %% The actual instruction command, % function(Data) -> term() %% Verification function of the instruction verify % function(Res, Data) -> {ok, NewData} | {error, Reason} } ). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% display_text_messages(V, Msgs) -> display_text_messages(V, [], Msgs). display_text_messages(_, _, []) -> ok; display_text_messages(V, EC, [{Name, Msg, _ED, _Conf}|Msgs]) -> (catch display_text_message(Name, EC, Msg, V)), display_text_messages(V, EC, Msgs). display_text_message(Name, EC, Msg, V) when is_tuple(Msg) -> io:format("~n(Erlang) message ~p:~n~p~n", [Name, Msg]), case (catch megaco_pretty_text_encoder:encode_message(EC,V,Msg)) of {'EXIT', _R} -> io:format("~nPretty encoded: failed (exit)~n", []); {error, {{deprecated, PWhat}, _}} -> io:format("~nPretty encoded: deprecated~n~p~n", [PWhat]), throw(continue); {error, PReason} -> io:format("~nPretty encoded: failed (error)~n~p~n", [PReason]), throw(continue); {ok, Pretty} -> io:format("~nPretty encoded:~n~s~n", [binary_to_list(Pretty)]) end, case (catch megaco_compact_text_encoder:encode_message(EC,V,Msg)) of {'EXIT', _} -> io:format("~nCompact encoded: failed~n", []); {error, {{deprecated, CWhat}, _}} -> io:format("~nPretty encoded: deprecated~n~p~n", [CWhat]); {ok, Compact} -> io:format("~nCompact encoded:~n~s~n", [binary_to_list(Compact)]) end; display_text_message(_, _, _, _) -> skipping. generate_text_messages(DirName, V, EC, Msgs) when is_atom(DirName) -> generate_text_messages(atom_to_list(DirName), V, EC, Msgs); generate_text_messages(DirName, V, EC, Msgs) when is_list(DirName) -> DirPath = filename:join(["/tmp", DirName]), case file:make_dir(DirPath) of ok -> generate_text_messages2(DirPath, V, EC, Msgs); {error, eexist} -> generate_text_messages2(DirPath, V, EC, Msgs); {error, Reason} -> io:format("Failed creating directory ~s: ~p~n", [DirPath, Reason]), ok end. generate_text_messages2(_, _, _, []) -> ok; generate_text_messages2(Dir, V, EC, [{Name, Msg, _ED, _Conf}|Msgs]) -> (catch generate_text_message(Dir, Name, EC, Msg, V)), generate_text_messages2(Dir, V, EC, Msgs). generate_text_message(Dir, Name, EC, Msg, V) -> io:format("~p: ", [Name]), case (catch megaco_pretty_text_encoder:encode_message(EC,V,Msg)) of {'EXIT', EReason} -> io:format("failed encoding [exit]: ~n~p~n", [EReason]), throw(continue); {error, {{deprecated, PWhat}, _}} -> io:format("failed encoding [deprecated]: ~n~p~n", [PWhat]), throw(continue); {error, PReason} -> io:format("failed encoding [error]: ~n~p~n", [PReason]), throw(continue); {ok, Pretty} -> io:format("encoded", []), FName = filename:flatten([Name, ".txt"]), Filename = filename:join([Dir, FName]), case (catch file:open(Filename, [write])) of {ok, Fd} -> io:format(Fd, "~s", [binary_to_list(Pretty)]), io:format(" - written to disk~n", []), (catch file:close(Fd)), ok; {error, OReason} -> io:format(" - failed writing to disk: " "~n~p~n~s~n", [OReason, binary_to_list(Pretty)]), throw(continue) end end. test_msgs(Codec, DynamicDecode, Ver, EC, Check, Msgs) when is_function(Check) andalso is_list(Msgs) -> io:format("~n", []), test_msgs(Codec, DynamicDecode, Ver, EC, Check, Msgs, []). test_msgs(_Codec, _DD, _Ver, _EC, _Check, [], []) -> ok; test_msgs(_Codec, _DD, _Ver, _EC, _Check, [], Errs) -> ?ERROR(lists:reverse(Errs)); test_msgs(Codec, DD, Ver, EC, Check, [{Name, {error, Error}, _ED, _Conf}|Msgs], Acc) -> io:format("error~n", []), test_msgs(Codec, DD, Ver, EC, Check, Msgs, [{Name, Error}|Acc]); test_msgs(Codec, DD, Ver, EC, Check, [{Name, Msg, ED, Conf}|Msgs], Acc) -> Dbg = test_msgs_debug(Conf), put(dbg, Dbg), io:format("~-16w ", [Name]), case (catch encode_decode(ED, Check, Codec, DD, Ver, EC, Msg)) of ok -> io:format("ok~n", []), erase(dbg), test_msgs(Codec, DD, Ver, EC, Check, Msgs, Acc); Error -> io:format("error~n", []), erase(dbg), test_msgs(Codec, DD, Ver, EC, Check, Msgs, [{Name, Error}|Acc]) end. test_msgs_debug(Conf) -> case lists:keysearch(dbg, 1, Conf) of {value, {dbg, true}} -> true; _ -> false end. encode_decode(Func, Check, Codec, DynamicDecode, Ver, EC, Msg1) when is_function(Func) -> d("encode_decode -> entry with" "~n Func: ~p" "~n Check: ~p" "~n Codec: ~p" "~n DynamicDecode: ~p" "~n Ver: ~p" "~n EC: ~p", [Func, Check, Codec, DynamicDecode, Ver, EC]), case (catch Func(Codec, DynamicDecode, Ver, EC, Msg1)) of {ok, Msg1} -> d("encode_decode -> expected result"), ok; {ok, Msg2} -> d("encode_decode -> unexpected result - check"), case (catch Check(Msg1, Msg2)) of ok -> d("encode_decode -> check - ok"), ok; {error, Reason} -> d("encode_decode -> check - error: " "~n Reason: ~p", [Reason]), {error, {Reason, Msg1, Msg2}}; Else -> d("encode_decode -> check - failed: " "~n Else: ~p", [Else]), {error, {invalid_check_result, Else}} end; Else -> d("encode_decode -> failed: " "~n Else: ~p", [Else]), Else end. %% *** plain_encode_decode *** plain_encode_decode(Codec, DynamicDecode, Ver, EC, M1) -> d("plain_encode_decode -> entry with" "~n Codec: ~p" "~n DynamicDecode: ~p" "~n Ver: ~p" "~n EC: ~p", [Codec, DynamicDecode, Ver, EC]), case (catch encode_message(Codec, Ver, EC, M1)) of {ok, Bin} -> d("plain_encode_decode -> encode - ok"), decode_message(Codec, DynamicDecode, Ver, EC, Bin, true); Error -> d("plain_encode_decode -> encode - failed: " "~n Error: ~p", [Error]), Error end. %% *** plain_decode_encode *** plain_decode_encode(Codec, DynamicDecode, Ver, EC, M) when is_list(M) -> Bin = list_to_binary(M), plain_decode_encode(Codec, DynamicDecode, Ver, EC, Bin); plain_decode_encode(Codec, DynamicDecode, Ver, EC, B) when is_binary(B) -> case (catch decode_message(Codec, DynamicDecode, Ver, EC, B, true)) of {ok, M} -> encode_message(Codec, Ver, EC, M); Error -> Error end. %% *** trans_first_encode_decode *** trans_first_encode_decode(Codec, DynamicDecode, Ver, EC, M1) -> d("trans_first_encode_decode -> entry"), case (catch trans_first_encode_message(Codec, Ver, EC, M1)) of {ok, Bin} -> decode_message(Codec, DynamicDecode, Ver, EC, Bin, true); Error -> Error end. trans_first_encode_message(Codec, Ver, EC, M1) -> d("trans_first_encode_message -> entry"), Mess1 = M1#'MegacoMessage'.mess, {transactions, Trans1} = Mess1#'Message'.messageBody, Trans2 = encode_transactions(Codec, Ver, EC, Trans1), Mess2 = Mess1#'Message'{messageBody = {transactions, Trans2}}, M2 = M1#'MegacoMessage'{mess = Mess2}, encode_message(Codec, Ver, EC, M2). encode_transactions(Codec, Ver, EC, Trans) when is_list(Trans) -> d("encode_transactions -> entry"), [encode_transaction(Codec, Ver, EC, T) || T <- Trans]. encode_transaction(Codec, Ver, EC, T) -> d("encode_transaction -> entry"), case (catch Codec:encode_transaction(EC, Ver, T)) of {ok, EncodecTransactions} -> EncodecTransactions; Error -> throw({error, {transaction_encode_failed, Error, T}}) end. %% *** actions_first_encode_decode *** actions_first_encode_decode(Codec, DynamicDecode, Ver, EC, M1) -> d("actions_first_encode_decode -> entry"), case (catch actions_first_encode_message(Codec, Ver, EC, M1)) of {ok, Bin} -> decode_message(Codec, DynamicDecode, Ver, EC, Bin, true); Error -> Error end. actions_first_encode_message(Codec, Ver, EC, M1) -> d("actions_first_encode_message -> entry"), Mess1 = M1#'MegacoMessage'.mess, {transactions, Trans1} = Mess1#'Message'.messageBody, Trans2 = encode_actions(Codec, Ver, EC, Trans1), Mess2 = Mess1#'Message'{messageBody = {transactions, Trans2}}, M2 = M1#'MegacoMessage'{mess = Mess2}, encode_message(Codec, Ver, EC, M2). encode_actions(Codec, Ver, EC, Trans) when is_list(Trans) -> d("encode_actions -> entry"), [encode_actions1(Codec, Ver, EC, T) || T <- Trans]. encode_actions1(Codec, Ver, EC, {transactionRequest, TR1}) -> d("encode_actions1 -> entry"), #'TransactionRequest'{actions = ARs} = TR1, case (catch encode_action_requests(Codec, Ver, EC, ARs)) of {ok, EncodedARs} -> TR2 = TR1#'TransactionRequest'{actions = EncodedARs}, {transactionRequest, TR2}; Error -> throw({error, {actions_encode_failed, Error, TR1}}) end. encode_action_requests(Codec, Ver, EC, ARs) -> d("encode_action_requests -> entry"), Codec:encode_action_requests(EC, Ver, ARs). %% *** action_first_encode_decode *** action_first_encode_decode(Codec, DynamicDecode, Ver, EC, M1) -> d("action_first_encode_decode -> entry"), case (catch action_first_encode_message(Codec, Ver, EC, M1)) of {ok, Bin} -> decode_message(Codec, DynamicDecode, Ver, EC, Bin, true); Error -> Error end. action_first_encode_message(Codec, Ver, EC, M1) -> d("action_first_encode_message -> entry"), Mess1 = M1#'MegacoMessage'.mess, {transactions, Trans1} = Mess1#'Message'.messageBody, Trans2 = encode_action(Codec, Ver, EC, Trans1), Mess2 = Mess1#'Message'{messageBody = {transactions, Trans2}}, M2 = M1#'MegacoMessage'{mess = Mess2}, encode_message(Codec, Ver, EC, M2). encode_action(Codec, Ver, EC, Trans) when is_list(Trans) -> d("encode_action -> entry"), [encode_action1(Codec, Ver, EC, T) || T <- Trans]. encode_action1(Codec, Ver, EC, {transactionRequest, TR1}) -> d("encode_action1 -> entry"), #'TransactionRequest'{actions = ARs1} = TR1, ARs2 = [encode_action_request(Codec, Ver, EC, AR) || AR <- ARs1], TR2 = TR1#'TransactionRequest'{actions = ARs2}, {transactionRequest, TR2}. encode_action_request(Codec, Ver, EC, AR) -> d("encode_action_request -> entry"), case (catch Codec:encode_action_request(EC, Ver, AR)) of {ok, Bin} -> Bin; Error -> throw({error, {encode_action_request_failed, Error, AR}}) end. encode_message(Codec, Ver, EC, M) -> d("encode_message -> entry with" "~n Codec: ~p" "~n Ver: ~p" "~n EC: ~p" "~n M: ~p", [Codec, Ver, EC, M]), %% case (catch Codec:encode_message(EC, Ver, M)) of %% {ok, Bin} -> %% d("encode_message -> encode - ok: " %% "~n~s", [binary_to_list(Bin)]), %% {ok, Bin}; %% Error -> %% d("encode_message -> encode - failed"), %% throw({error, {message_encode_failed, Error, M}}) %% end. case (catch timer:tc(Codec, encode_message, [EC, Ver, M])) of {Time, {ok, Bin}} -> d("encode_message -> encode - ok after ~p: " "~n~s", [Time, binary_to_list(Bin)]), {ok, Bin}; {_Time, Error} -> d("encode_message -> encode - failed"), throw({error, {message_encode_failed, Error, M}}) end. decode_message(Codec, Dynamic, Ver, EC, M) -> decode_message(Codec, Dynamic, Ver, EC, M, false). decode_message(Codec, true, _Ver, EC, M, _Timed) -> d("decode_message -> entry - when using dynamic"), Codec:decode_message(EC, dynamic, M); decode_message(Codec, _, Ver, EC, M, false) -> d("decode_message -> entry with" "~n Codec: ~p" "~n Ver: ~p" "~n EC: ~p", [Codec, Ver, EC]), Codec:decode_message(EC, Ver, M); decode_message(Codec, _, Ver, EC, M, true) -> d("decode_message -> entry with" "~n Codec: ~p" "~n Ver: ~p" "~n EC: ~p", [Codec, Ver, EC]), {Time, Result} = timer:tc(Codec, decode_message, [EC, Ver, M]), io:format("~-8w", [Time]), Result. %% ======================================================================= %% ------------------------------------------------------------------ %% Create an instruction record %% ------------------------------------------------------------------ expect_instruction(Desc, Cmd, Verify) when is_list(Desc) andalso is_function(Cmd) andalso is_function(Verify) -> #expect_instruction{description = Desc, command = Cmd, verify = Verify}. %% ------------------------------------------------------------------ %% Function: expect_encode %% Parameters: Msg -> MegacoMessage %% Encode -> function/1 %% Check -> function/1 %% Description: This function simply encodes, with the Encode fun, %% and expects this to fail. The failure reason is %% checked with the Check fun. %% ------------------------------------------------------------------ expect_encode(InitialData, Encode, Check) when is_function(Encode) andalso is_function(Check) -> Instructions = [ %% Initial encode expect_instruction( "Encode (initial) message", fun(Msg) when is_record(Msg, 'MegacoMessage') -> (catch Encode(Msg)); (Bad) -> {error, {invalid_data, Bad}} end, fun({error, Reason}, _) -> io:format("check error reason ", []), case (catch Check(Reason)) of ok -> {ok, done}; Error -> Error end; ({ok, Bin}, Msg) when is_binary(Bin) -> M = binary_to_list(Bin), {error, {unexpected_encode_success, {M, Msg}}}; (Crap, _) -> {error, {unexpected_encode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_encode_only %% Parameters: InitialData -> list() | binary() %% Encode -> function/1 %% Check -> function/1 %% Description: This function simply encodes, with the Encode fun, %% and expects it to succeed, which is checked by %% calling the Check fun with the resulting message. %% ------------------------------------------------------------------ expect_encode_only(InitialData, Encode, Check) when is_function(Encode) andalso is_function(Check) -> Instructions = [ %% Initial encode expect_instruction( "Encode (initial) message", fun(Msg) when is_record(Msg, 'MegacoMessage') -> (catch Encode(Msg)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Bin}, _Msg) when is_binary(Bin) -> case (catch Check(Bin)) of ok -> {ok, done}; Error -> Error end; (Crap, _) -> {error, {unexpected_encode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_encode_decode %% Parameters: InitialData -> MegacoMessage %% Encode -> function/1 %% Decode -> function/1 %% Check -> function/2 %% Description: This function simply encodes, with the Encode fun, and %% then decodes, with the Decode fun, the megaco message. %% The resulting message should be identical, but if it %% is not, the messages are checked, with the Check fun. %% ------------------------------------------------------------------ expect_encode_decode(InitialData, Encode, Decode, Check) when is_function(Encode) andalso is_function(Decode) andalso is_function(Check) -> Instructions = [ %% Initial encode expect_instruction( "Encode (initial) message", fun(M) when is_record(M, 'MegacoMessage') -> (catch Encode(M)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Bin}, M) when is_binary(Bin) -> {ok, {Bin, M}}; ({error, Reason}, _) -> {error, {unexpected_encode_failure, Reason}}; (Crap, _) -> {error, {unexpected_encode_result, Crap}} end), %% Decode the (encoded) message expect_instruction( "Decode message", fun({Bin, _}) when is_binary(Bin) -> (catch Decode(Bin)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Msg1}, {_Bin, Msg1}) when is_record(Msg1, 'MegacoMessage') -> io:format("messages identical - done ", []), {ok, done}; ({ok, Msg2}, {_Bin, Msg1}) -> io:format("messages not identical - check - ", []), case (catch Check(Msg1, Msg2)) of ok -> io:format("equal ", []), {ok, done}; Error -> io:format("not equal ", []), io:format("~nError: ~p~n", [Error]), Error end; (Crap, _) -> {error, {unexpected_decode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_encode_decode_only %% Parameters: InitialData -> MegacoMessage %% Encode -> function/1 %% Decode -> function/1 %% Check -> function/2 %% Description: This function simply encodes, with the Encode fun, %% and then decodes, with the Decode fun, the megaco %% message and expects it to succeed. The resulting %% message is checked by calling the Check fun with the %% resulting message. %% ------------------------------------------------------------------ expect_encode_decode_only(InitialData, Encode, Decode, Check) when is_function(Encode) andalso is_function(Decode) andalso is_function(Check) -> Instructions = [ %% Initial encode expect_instruction( "Encode (initial) message", fun(M) when is_record(M, 'MegacoMessage') -> (catch Encode(M)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Bin}, M) when is_binary(Bin) -> {ok, {Bin, M}}; ({error, Reason}, _) -> {error, {unexpected_encode_failure, Reason}}; (Crap, _) -> {error, {unexpected_encode_result, Crap}} end), %% Decode the (encoded) message expect_instruction( "Decode message", fun({Bin, _}) when is_binary(Bin) -> (catch Decode(Bin)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Msg}, _B) when is_record(Msg, 'MegacoMessage') -> io:format("decoded - now check ", []), case (catch Check(Msg)) of ok -> {ok, done}; Error -> Error end; ({error, R}, _) -> {Line, Mod, Reason} = case lists:keysearch(reason, 1, R) of {value, {reason, {L, M, Raw}}} when is_list(Raw) -> {L, M, lists:flatten(Raw)}; {value, {reason, {L, M, Raw}}} -> {L, M, Raw}; _ -> {-1, undefined, R} end, Tokens = case lists:keysearch(token, 1, R) of {value, {token, T}} -> T; _ -> undefined end, {error, {unexpected_decode_failure, {Mod, Line, Reason, Tokens}}}; (Crap, _) -> {error, {unexpected_decode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_decode %% Parameters: InitialData -> list() | binary() %% Decode -> function/1 %% Check -> function/1 %% Description: This function simply decodes, with the Decode fun, %% and expects this to fail. The failure reason is %% checked with the Check fun. %% ------------------------------------------------------------------ expect_decode(InitialData, Decode, Check) when is_list(InitialData) -> expect_decode(list_to_binary(InitialData), Decode, Check); expect_decode(InitialData, Decode, Check) when is_function(Decode) andalso is_function(Check) -> Instructions = [ %% Initial decode expect_instruction( "Decode (initial) message", fun(Bin) when is_binary(Bin) -> (catch Decode(Bin)); (Bad) -> {error, {invalid_data, Bad}} end, fun({error, Reason}, _) -> io:format("check error reason - ", []), case (catch Check(Reason)) of ok -> {ok, done}; Error -> Error end; ({ok, Msg}, Bin) -> io:format("unexpected decode success - ", []), M = binary_to_list(Bin), {error, {unexpected_decode_success, {Msg, M}}}; (Crap, _) -> {error, {unexpected_decode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_decode_only %% Parameters: InitialData -> list() | binary() %% Decode -> function/1 %% Check -> function/2 %% Description: This function simply decodes, with the Decode fun, %% and expects it to succeed, which is checked by %% calling the Check fun with the resulting message. %% ------------------------------------------------------------------ expect_decode_only(InitialData, Decode, Check) when is_list(InitialData) -> expect_decode_only(list_to_binary(InitialData), Decode, Check); expect_decode_only(InitialData, Decode, Check) when is_function(Decode) andalso is_function(Check) -> Instructions = [ %% Initial decode expect_instruction( "Decode (initial) message", fun(B) when is_binary(B) -> (catch Decode(B)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Msg}, _B) when is_record(Msg, 'MegacoMessage') -> case (catch Check(Msg)) of ok -> {ok, done}; Error -> Error end; ({error, R}, _) -> {Line, Mod, Reason} = case lists:keysearch(reason, 1, R) of {value, {reason, {L, M, Raw}}} when is_list(Raw) -> {L, M, lists:flatten(Raw)}; {value, {reason, {L, M, Raw}}} -> {L, M, Raw}; _ -> {-1, undefined, R} end, Tokens = case lists:keysearch(token, 1, R) of {value, {token, T}} -> T; _ -> undefined end, {error, {unexpected_decode_failure, {Mod, Line, Reason, Tokens}}}; (Crap, _) -> {error, {unexpected_decode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_decode_encode %% Parameters: InitialData -> list() | binary() %% Decode -> function/1 %% Encode -> function/1 %% Check -> function/2 %% Description: This function simply decodes, with the Decode fun, %% and then encodes, with the Encode fun, the megaco %% message. The resulting binary message should be %% identical, but if it is not, the messages are %% decoded again and then if necessary checked, with %% the Check fun. %% ------------------------------------------------------------------ expect_decode_encode(InitialData, Decode, Encode, Check) when is_list(InitialData) -> expect_decode_encode(list_to_binary(InitialData), Decode, Encode, Check); expect_decode_encode(InitialData, Decode, Encode, Check) when is_function(Decode) andalso is_function(Encode) andalso is_function(Check) -> Instructions = [ %% Initial decode expect_instruction( "Decode (initial) message", fun(B) when is_binary(B) -> (catch Decode(B)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Msg}, B) when is_record(Msg, 'MegacoMessage') -> {ok, {Msg, B}}; ({error, R}, _) -> {Line, Mod, Reason} = case lists:keysearch(reason, 1, R) of {value, {reason, {L, M, Raw}}} when is_list(Raw) -> {L, M, lists:flatten(Raw)}; {value, {reason, {L, M, Raw}}} -> {L, M, Raw}; _ -> {-1, undefined, R} end, Tokens = case lists:keysearch(token, 1, R) of {value, {token, T}} -> T; _ -> undefined end, {error, {unexpected_decode_failure, {Mod, Line, Reason, Tokens}}}; (Crap, _) -> {error, {unexpected_decode_result, Crap}} end), %% Encode the (decoded) message expect_instruction( "Encode message", fun({Msg, _Bin}) when is_record(Msg, 'MegacoMessage') -> (catch Encode(Msg)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, B}, {_, B}) -> io:format("binaries equal - done ", []), {ok, done}; ({ok, B}, {Msg, _}) -> {ok, {Msg, B}}; ({error, Reason}, _) -> {error, {unexpected_encode_failure, Reason}}; (Crap, _) -> {error, {unexpected_encode_result, Crap}} end), %% Fallback instruction in case encode produced %% a binary not equal to the initial expect_instruction( "Decode message (if binaries not equal)", fun(done) -> done; ({_Msg, B}) when is_binary(B) -> (catch Decode(B)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Msg}, {Msg, _Bin}) when is_record(Msg, 'MegacoMessage') -> io:format("messages identical - done ", []), {ok, done}; (done, _) -> io:format("done ", []), {ok, done}; ({ok, Msg2}, {Msg1, _}) -> io:format("messages not identical - check - ", []), case (catch Check(Msg1, Msg2)) of ok -> io:format("equal ", []), {ok, done}; Error -> io:format("not equal ", []), Error end; ({error, Reason}, _) -> {error, {unexpected_decode_failure, Reason}}; (Crap, _) -> {error, {unexpected_decode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_decode_encode_only %% Parameters: InitialData -> list() | binary() %% Decode -> function/1 %% Encode -> function/1 %% Check -> function/2 %% Description: This function simply decodes, with the Decode fun, %% and then encodes, with the Encode fun, the megaco %% message. The resulting binary message is then checked %% with the Check fun. %% ------------------------------------------------------------------ expect_decode_encode_only(InitialData, Decode, Encode, Check) when is_list(InitialData) -> expect_decode_encode_only(list_to_binary(InitialData), Decode, Encode, Check); expect_decode_encode_only(InitialData, Decode, Encode, Check) when is_function(Decode) andalso is_function(Encode) andalso is_function(Check) -> Instructions = [ %% Initial decode expect_instruction( "Decode (initial) message", fun(B) when is_binary(B) -> (catch Decode(B)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, Msg}, B) when is_record(Msg, 'MegacoMessage') -> {ok, {Msg, B}}; ({error, R}, _) -> {Line, Mod, Reason} = case lists:keysearch(reason, 1, R) of {value, {reason, {L, M, Raw}}} when is_list(Raw) -> {L, M, lists:flatten(Raw)}; {value, {reason, {L, M, Raw}}} -> {L, M, Raw}; _ -> {-1, undefined, R} end, Tokens = case lists:keysearch(token, 1, R) of {value, {token, T}} -> T; _ -> undefined end, {error, {unexpected_decode_failure, {Mod, Line, Reason, Tokens}}}; (Crap, _) -> {error, {unexpected_decode_result, Crap}} end), %% Encode the (decoded) message expect_instruction( "Encode message", fun({Msg, _Bin}) when is_record(Msg, 'MegacoMessage') -> (catch Encode(Msg)); (Bad) -> {error, {invalid_data, Bad}} end, fun({ok, B2}, {_, B1}) -> io:format("encode ok - check bins - ", []), case (catch Check(B1, B2)) of ok -> {ok, done}; Crap -> {error, {unexpected_encode_check_result, Crap}} end; ({error, Reason}, _) -> {error, {unexpected_encode_failure, Reason}}; (Crap, _) -> {error, {unexpected_encode_result, Crap}} end) ], expect_exec(Instructions, InitialData). %% ------------------------------------------------------------------ %% Function: expect_exec %% Parameters: Instructions -> [instruction()] %% InitialData -> term() %% Description: This function is the engine in the codec test %% cases. It executes each instruction in turn. %% ------------------------------------------------------------------ expect_exec(Instructions, InitialData) -> expect_exec(Instructions, InitialData, 1). expect_exec([], _, _) -> io:format("~n", []), ok; expect_exec([#expect_instruction{description = Desc, command = Cmd, verify = Verify}|T], Data, Num) -> io:format("~n Exec command ~w: ~s => ", [Num, Desc]), case Verify((catch Cmd(Data)), Data) of {ok, NewData} -> io:format("ok", []), expect_exec(T, NewData, Num+1); {error, Reason} -> io:format("error", []), {error, {Num, Desc, Reason}} end. %% ======================================================================= skip({What, Why}) when is_atom(What) andalso is_list(Why) -> Reason = lists:flatten(io_lib:format("~p: ~s", [What, Why])), exit({skipped, Reason}); skip({What, Why}) -> Reason = lists:flatten(io_lib:format("~p: ~p", [What, Why])), exit({skipped, Reason}); skip(Reason) when is_list(Reason) -> exit({skipped, Reason}); skip(Reason1) -> Reason2 = lists:flatten(io_lib:format("~p", [Reason1])), exit({skipped, Reason2}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% ------------------------------------------------------------------ %% Internal functions %% ------------------------------------------------------------------ d(F) -> d(F, []). d(F, A) -> d(get(dbg), F, A). d(true, F, A) -> io:format("DBG:~w:" ++ F ++ "~n", [?MODULE|A]); d(_, _, _) -> ok.