1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2010-2011. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: %%
   21: %%----------------------------------------------------------------------
   22: %% Purpose: Verify the application specifics of the Reltool application
   23: %%----------------------------------------------------------------------
   24: -module(reltool_app_SUITE).
   25: 
   26: -compile(export_all).
   27: 
   28: -include("reltool_test_lib.hrl").
   29: 
   30: 
   31: t()     -> reltool_test_lib:t(?MODULE).
   32: t(Case) -> reltool_test_lib:t({?MODULE, Case}).
   33: 
   34: %% Test server callbacks
   35: init_per_suite(Config) ->
   36:     Config2 = reltool_test_lib:init_per_suite(Config),
   37:     case is_app(reltool) of
   38: 	{ok, AppFile} ->
   39: 	    %% io:format("AppFile: ~n~p~n", [AppFile]),
   40: 	    [{app_file, AppFile} | Config2];
   41: 	{error, Reason} ->
   42: 	    fail(Reason)
   43:     end.
   44: 
   45: end_per_suite(Config) ->
   46:     reltool_test_lib:end_per_suite(Config).
   47: 
   48: init_per_testcase(undef_funcs=Case, Config) ->
   49:     case test_server:is_debug() of
   50: 	true ->
   51: 	    {skip,"Debug-compiled emulator -- far too slow"};
   52: 	false ->
   53: 	    Config2 = [{tc_timeout, timer:minutes(10)} | Config],
   54: 	    reltool_test_lib:init_per_testcase(Case, Config2)
   55: 	end;
   56: init_per_testcase(Case, Config) ->
   57:     reltool_test_lib:init_per_testcase(Case, Config).
   58: 
   59: end_per_testcase(Func,Config) ->
   60:     reltool_test_lib:end_per_testcase(Func,Config).
   61: 
   62: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   63: 
   64: suite() -> [{ct_hooks,[ts_install_cth]}].
   65: 
   66: all() -> 
   67:     [fields, modules, export_all, app_depend, undef_funcs].
   68: 
   69: groups() -> 
   70:     [].
   71: 
   72: init_per_group(_GroupName, Config) ->
   73:     Config.
   74: 
   75: end_per_group(_GroupName, Config) ->
   76:     Config.
   77: 
   78: 
   79: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   80: 
   81: is_app(App) ->
   82:     LibDir = code:lib_dir(App),
   83:     File = filename:join([LibDir, "ebin", atom_to_list(App) ++ ".app"]),
   84:     case file:consult(File) of
   85: 	{ok, [{application, App, AppFile}]} ->
   86: 	    {ok, AppFile};
   87: 	{error, {LineNo, Mod, Code}} ->
   88: 	    IoList = lists:concat([File, ":", LineNo, ": ",
   89: 				   Mod:format_error(Code)]),
   90: 	    {error, list_to_atom(lists:flatten(IoList))}
   91:     end.
   92: 
   93: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   94: 
   95: fields(suite) ->
   96:     [];
   97: fields(doc) ->
   98:     [];
   99: fields(Config) when is_list(Config) ->
  100:     AppFile = key1search(app_file, Config),
  101:     Fields = [vsn, description, modules, registered, applications],
  102:     case check_fields(Fields, AppFile, []) of
  103: 	[] ->
  104: 	    ok;
  105: 	Missing ->
  106: 	    fail({missing_fields, Missing})
  107:     end.
  108: 
  109: check_fields([], _AppFile, Missing) ->
  110:     Missing;
  111: check_fields([Field|Fields], AppFile, Missing) ->
  112:     check_fields(Fields, AppFile, check_field(Field, AppFile, Missing)).
  113: 
  114: check_field(Name, AppFile, Missing) ->
  115:     io:format("checking field: ~p~n", [Name]),
  116:     case lists:keymember(Name, 1, AppFile) of
  117: 	true ->
  118: 	    Missing;
  119: 	false ->
  120: 	    [Name|Missing]
  121:     end.
  122: 
  123: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  124: 
  125: modules(suite) ->
  126:     [];
  127: modules(doc) ->
  128:     [];
  129: modules(Config) when is_list(Config) ->
  130:     AppFile  = key1search(app_file, Config),
  131:     Mods     = key1search(modules, AppFile),
  132:     EbinList = get_ebin_mods(reltool),
  133:     case missing_modules(Mods, EbinList, []) of
  134: 	[] ->
  135: 	    ok;
  136: 	Missing ->
  137: 	    throw({error, {missing_modules, Missing}})
  138:     end,
  139:     case extra_modules(Mods, EbinList, []) of
  140: 	[] ->
  141: 	    ok;
  142: 	Extra ->
  143: 	    throw({error, {extra_modules, Extra}})
  144:     end,
  145:     {ok, Mods}.
  146: 
  147: get_ebin_mods(App) ->
  148:     LibDir  = code:lib_dir(App),
  149:     EbinDir = filename:join([LibDir,"ebin"]),
  150:     {ok, Files0} = file:list_dir(EbinDir),
  151:     Files1 = [lists:reverse(File) || File <- Files0],
  152:     [list_to_atom(lists:reverse(Name)) || [$m,$a,$e,$b,$.|Name] <- Files1].
  153: 
  154: missing_modules([], _Ebins, Missing) ->
  155:     Missing;
  156: missing_modules([Mod|Mods], Ebins, Missing) ->
  157:     case lists:member(Mod, Ebins) of
  158: 	true ->
  159: 	    missing_modules(Mods, Ebins, Missing);
  160: 	false ->
  161: 	    io:format("missing module: ~p~n", [Mod]),
  162: 	    missing_modules(Mods, Ebins, [Mod|Missing])
  163:     end.
  164: 
  165: 
  166: extra_modules(_Mods, [], Extra) ->
  167:     Extra;
  168: extra_modules(Mods, [Mod|Ebins], Extra) ->
  169:     case lists:member(Mod, Mods) of
  170: 	true ->
  171: 	    extra_modules(Mods, Ebins, Extra);
  172: 	false ->
  173: 	    io:format("supefluous module: ~p~n", [Mod]),
  174: 	    extra_modules(Mods, Ebins, [Mod|Extra])
  175:     end.
  176: 
  177: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  178: 
  179: export_all(suite) ->
  180:     [];
  181: export_all(doc) ->
  182:     [];
  183: export_all(Config) when is_list(Config) ->
  184:     AppFile = key1search(app_file, Config),
  185:     Mods    = key1search(modules, AppFile),
  186:     check_export_all(Mods).
  187: 
  188: 
  189: check_export_all([]) ->
  190:     ok;
  191: check_export_all([Mod|Mods]) ->
  192:     case (catch apply(Mod, module_info, [compile])) of
  193: 	{'EXIT', {undef, _}} ->
  194: 	    check_export_all(Mods);
  195: 	O ->
  196:             case lists:keysearch(options, 1, O) of
  197:                 false ->
  198:                     check_export_all(Mods);
  199:                 {value, {options, List}} ->
  200:                     case lists:member(export_all, List) of
  201:                         true ->
  202: 			    throw({error, {export_all, Mod}});
  203: 			false ->
  204: 			    check_export_all(Mods)
  205:                     end
  206:             end
  207:     end.
  208: 
  209: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  210: 
  211: app_depend(suite) ->
  212:     [];
  213: app_depend(doc) ->
  214:     [];
  215: app_depend(Config) when is_list(Config) ->
  216:     AppFile = key1search(app_file, Config),
  217:     Apps    = key1search(applications, AppFile),
  218:     check_apps(Apps).
  219: 
  220: check_apps([]) ->
  221:     ok;
  222: check_apps([App|Apps]) ->
  223:     case is_app(App) of
  224: 	{ok, _} ->
  225: 	    check_apps(Apps);
  226: 	Error ->
  227: 	    throw({error, {missing_app, {App, Error}}})
  228:     end.
  229: 
  230: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  231: 
  232: undef_funcs(suite) ->
  233:     [];
  234: undef_funcs(doc) ->
  235:     [];
  236: undef_funcs(Config) when is_list(Config) ->
  237:     App            = reltool,
  238:     AppFile        = key1search(app_file, Config),
  239:     Mods           = key1search(modules, AppFile),
  240:     Root           = code:root_dir(),
  241:     LibDir         = code:lib_dir(App),
  242:     EbinDir        = filename:join([LibDir,"ebin"]),
  243:     XRefTestName   = undef_funcs_make_name(App, xref_test_name),
  244:     {ok, XRef}     = xref:start(XRefTestName),
  245:     ok             = xref:set_default(XRef,
  246:                                       [{verbose,false},{warnings,false}]),
  247:     XRefName       = undef_funcs_make_name(App, xref_name),
  248:     {ok, XRefName} = xref:add_release(XRef, Root, {name,XRefName}),
  249:     {ok, App}      = xref:replace_application(XRef, App, EbinDir),
  250:     {ok, Undefs}   = xref:analyze(XRef, undefined_function_calls),
  251:     xref:stop(XRef),
  252:     analyze_undefined_function_calls(Undefs, Mods, []).
  253: 
  254: analyze_undefined_function_calls([], _, []) ->
  255:     ok;
  256: analyze_undefined_function_calls([], _, AppUndefs) ->
  257:     exit({suite_failed, {undefined_function_calls, AppUndefs}});
  258: analyze_undefined_function_calls([{{Mod, _F, _A}, _C} = AppUndef|Undefs],
  259:                                  AppModules, AppUndefs) ->
  260:     %% Check that this module is our's
  261:     case lists:member(Mod,AppModules) of
  262:         true ->
  263:             {Calling,Called} = AppUndef,
  264:             {Mod1,Func1,Ar1} = Calling,
  265:             {Mod2,Func2,Ar2} = Called,
  266:             io:format("undefined function call: "
  267:                       "~n   ~w:~w/~w calls ~w:~w/~w~n",
  268:                       [Mod1,Func1,Ar1,Mod2,Func2,Ar2]),
  269:             analyze_undefined_function_calls(Undefs, AppModules,
  270:                                              [AppUndef|AppUndefs]);
  271:         false ->
  272:             io:format("dropping ~p~n", [Mod]),
  273:             analyze_undefined_function_calls(Undefs, AppModules, AppUndefs)
  274:     end.
  275: 
  276: %% This function is used simply to avoid cut-and-paste errors later...
  277: undef_funcs_make_name(App, PostFix) ->
  278:     list_to_atom(atom_to_list(App) ++ "_" ++ atom_to_list(PostFix)).
  279: 
  280: 
  281: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  282: 
  283: fail(Reason) ->
  284:     exit({suite_failed, Reason}).
  285: 
  286: key1search(Key, L) ->
  287:     case lists:keysearch(Key, 1, L) of
  288: 	false ->
  289: 	    fail({not_found, Key, L});
  290: 	{value, {Key, Value}} ->
  291: 	    Value
  292:     end.