%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% %%---------------------------------------------------------------------- %% Purpose: Verify the application specifics of the Diameter application %%---------------------------------------------------------------------- -module(diameter_app_test). -export([ init_per_testcase/2, fin_per_testcase/2, all/0, groups/0, init_per_suite/1, end_per_suite/1, suite_init/1, suite_fin/1, init_per_group/2, end_per_group/2, fields/1, modules/1, exportall/1, app_depend/1, undef_funcs/1 ]). -export([t/0, t/1]). -include("diameter_test_lib.hrl"). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% t() -> diameter_test_server:t(?MODULE). t(Case) -> diameter_test_server:t({?MODULE, Case}). %% Test server callbacks init_per_testcase(undef_funcs = Case, Config) -> NewConfig = [{tc_timeout, ?MINUTES(10)} | Config], diameter_test_server:init_per_testcase(Case, NewConfig); init_per_testcase(Case, Config) -> diameter_test_server:init_per_testcase(Case, Config). fin_per_testcase(Case, Config) -> diameter_test_server:fin_per_testcase(Case, Config). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> [ fields, modules, exportall, app_depend, undef_funcs ]. groups() -> []. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% suite_init(X) -> init_per_suite(X). init_per_suite(suite) -> []; init_per_suite(doc) -> []; init_per_suite(Config) when is_list(Config) -> io:format("~w:init_per_suite -> entry with" "~n Config: ~p" "~n", [?MODULE, Config]), case is_app(diameter) of {ok, AppFile} -> io:format("AppFile: ~n~p~n", [AppFile]), %% diameter:print_version_info(), [{app_file, AppFile}|Config]; {error, Reason} -> ?FAIL(Reason) end. is_app(App) -> LibDir = code:lib_dir(App), File = filename:join([LibDir, "ebin", atom_to_list(App) ++ ".app"]), case file:consult(File) of {ok, [{application, App, AppFile}]} -> {ok, AppFile}; Error -> {error, {invalid_format, Error}} end. suite_fin(X) -> end_per_suite(X). end_per_suite(suite) -> []; end_per_suite(doc) -> []; end_per_suite(Config) when is_list(Config) -> Config. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% fields(suite) -> []; fields(doc) -> []; fields(Config) when is_list(Config) -> AppFile = ?KEY1SEARCH(app_file, Config), Fields = [vsn, description, modules, registered, applications], case check_fields(Fields, AppFile, []) of [] -> ok; Missing -> ?FAIL({missing_fields, Missing}) end. check_fields([], _AppFile, Missing) -> Missing; check_fields([Field|Fields], AppFile, Missing) -> check_fields(Fields, AppFile, check_field(Field, AppFile, Missing)). check_field(Name, AppFile, Missing) -> io:format("checking field: ~p~n", [Name]), case lists:keymember(Name, 1, AppFile) of true -> Missing; false -> [Name|Missing] end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% modules(suite) -> []; modules(doc) -> []; modules(Config) when is_list(Config) -> AppFile = ?KEY1SEARCH(app_file, Config), Mods = ?KEY1SEARCH(modules, AppFile), EbinList = get_ebin_mods(diameter), case missing_modules(Mods, EbinList, []) of [] -> ok; Missing -> throw({error, {missing_modules, Missing}}) end, Allowed = [diameter_codegen, diameter_make, diameter_spec_scan, diameter_spec_util], case extra_modules(Mods, EbinList, Allowed, []) of [] -> ok; Extra -> throw({error, {extra_modules, Extra}}) end, {ok, Mods}. get_ebin_mods(App) -> LibDir = code:lib_dir(App), EbinDir = filename:join([LibDir,"ebin"]), {ok, Files0} = file:list_dir(EbinDir), Files1 = [lists:reverse(File) || File <- Files0], [list_to_atom(lists:reverse(Name)) || [$m,$a,$e,$b,$.|Name] <- Files1]. missing_modules([], _Ebins, Missing) -> Missing; missing_modules([Mod|Mods], Ebins, Missing) -> case lists:member(Mod, Ebins) of true -> missing_modules(Mods, Ebins, Missing); false -> io:format("missing module: ~p~n", [Mod]), missing_modules(Mods, Ebins, [Mod|Missing]) end. extra_modules(_Mods, [], Allowed, Extra) -> Extra--Allowed; extra_modules(Mods, [Mod|Ebins], Allowed, Extra) -> case lists:member(Mod, Mods) of true -> extra_modules(Mods, Ebins, Allowed, Extra); false -> io:format("supefluous module: ~p~n", [Mod]), extra_modules(Mods, Ebins, Allowed, [Mod|Extra]) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% exportall(suite) -> []; exportall(doc) -> []; exportall(Config) when is_list(Config) -> AppFile = ?KEY1SEARCH(app_file, Config), Mods = ?KEY1SEARCH(modules, AppFile), check_export_all(Mods). check_export_all([]) -> ok; check_export_all([Mod|Mods]) -> case (catch apply(Mod, module_info, [compile])) of {'EXIT', {undef, _}} -> check_export_all(Mods); O -> case lists:keysearch(options, 1, O) of false -> check_export_all(Mods); {value, {options, List}} -> case lists:member(export_all, List) of true -> throw({error, {export_all, Mod}}); false -> check_export_all(Mods) end end end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% app_depend(suite) -> []; app_depend(doc) -> []; app_depend(Config) when is_list(Config) -> AppFile = ?KEY1SEARCH(app_file, Config), Apps = ?KEY1SEARCH(applications, AppFile), check_apps(Apps). check_apps([]) -> ok; check_apps([App|Apps]) -> case is_app(App) of {ok, _} -> check_apps(Apps); Error -> throw({error, {missing_app, {App, Error}}}) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% undef_funcs(suite) -> []; undef_funcs(doc) -> []; undef_funcs(Config) when is_list(Config) -> ?SKIP(diameter_not_known_by_xref), App = diameter, AppFile = ?KEY1SEARCH(app_file, Config), Mods = ?KEY1SEARCH(modules, AppFile), Root = code:root_dir(), LibDir = code:lib_dir(App), EbinDir = filename:join([LibDir,"ebin"]), XRefTestName = undef_funcs_make_name(App, xref_test_name), try begin XRef = xref_start(XRefTestName), xref_set_defaults(XRef, [{verbose,false},{warnings,false}]), XRefName = undef_funcs_make_name(App, xref_name), XRefName = xref_add_release(XRef, Root, XRefName), xref_replace_application(XRef, App, EbinDir), Undefs = xref_analyze(XRef), xref_stop(XRef), analyze_undefined_function_calls(Undefs, Mods, []) end catch throw:{error, Reason} -> ?FAIL(Reason) end. xref_start(XRefTestName) -> case (catch xref:start(XRefTestName)) of {ok, XRef} -> XRef; {error, Reason} -> throw({error, {failed_starting_xref, Reason}}); Error -> throw({error, {failed_starting_xref, Error}}) end. xref_set_defaults(XRef, Defs) -> case (catch xref:set_default(XRef, Defs)) of ok -> ok; Error -> throw({error, {failed_setting_defaults, Defs, Error}}) end. xref_add_release(XRef, Root, Name) -> case (catch xref:add_release(XRef, Root, {name, Name})) of {ok, XRefName} -> XRefName; {error, Reason} -> throw({error, {failed_adding_release, Reason}}); Error -> throw({error, {failed_adding_release, Error}}) end. xref_replace_application(XRef, App, EbinDir) -> case (catch xref:replace_application(XRef, App, EbinDir)) of {ok, App} -> ok; {error, XRefMod, Reason} -> throw({error, {failed_replacing_app, XRefMod, Reason}}); Error -> throw({error, {failed_replacing_app, Error}}) end. xref_analyze(XRef) -> case (catch xref:analyze(XRef, undefined_function_calls)) of {ok, Undefs} -> Undefs; {error, Reason} -> throw({error, {failed_detecting_func_calls, Reason}}); Error -> throw({error, {failed_detecting_func_calls, Error}}) end. xref_stop(XRef) -> xref:stop(XRef). analyze_undefined_function_calls([], _, []) -> ok; analyze_undefined_function_calls([], _, AppUndefs) -> exit({suite_failed, {undefined_function_calls, AppUndefs}}); analyze_undefined_function_calls([{{Mod, _F, _A}, _C} = AppUndef|Undefs], AppModules, AppUndefs) -> %% Check that this module is our's case lists:member(Mod,AppModules) of true -> {Calling,Called} = AppUndef, {Mod1,Func1,Ar1} = Calling, {Mod2,Func2,Ar2} = Called, io:format("undefined function call: " "~n ~w:~w/~w calls ~w:~w/~w~n", [Mod1,Func1,Ar1,Mod2,Func2,Ar2]), analyze_undefined_function_calls(Undefs, AppModules, [AppUndef|AppUndefs]); false -> io:format("dropping ~p~n", [Mod]), analyze_undefined_function_calls(Undefs, AppModules, AppUndefs) end. %% This function is used simply to avoid cut-and-paste errors later... undef_funcs_make_name(App, PostFix) -> list_to_atom(atom_to_list(App) ++ "_" ++ atom_to_list(PostFix)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% fail(Reason) -> %% exit({suite_failed, Reason}). %% ?KEY1SEARCH(Key, L) -> %% case lists:keysearch(Key, 1, L) of %% undefined -> %% fail({not_found, Key, L}); %% {value, {Key, Value}} -> %% Value %% end.