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