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.