%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2003-2010. 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 Megaco application
%%----------------------------------------------------------------------
-module(snmp_appup_test).
-export([
all/1, init_per_suite/1, fin_suite/1,
init_per_testcase/2, fin_per_testcase/2,
appup_file/1
]).
-include("test_server.hrl").
-include("snmp_test_lib.hrl").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all(suite) ->
Cases =
[
appup_file
],
{conf, init_per_suite, Cases, fin_suite}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_per_suite(suite) -> [];
init_per_suite(doc) -> [];
init_per_suite(Config) when is_list(Config) ->
PrivDir = ?config(priv_dir, Config),
TopDir = filename:join(PrivDir, appup),
case file:make_dir(TopDir) of
ok ->
ok;
Error ->
fail({failed_creating_subsuite_top_dir, Error})
end,
AppFile = file_name(?APPLICATION, ".app"),
AppupFile = file_name(?APPLICATION, ".appup"),
[{app_file, AppFile},
{appup_file, AppupFile},
{appup_topdir, TopDir} | Config].
file_name(App, Ext) ->
Env = init:get_arguments(),
LibDir =
case lists:keysearch(clearcase, 1, Env) of
false ->
code:lib_dir(App);
_ ->
".."
end,
filename:join([LibDir, "ebin", atom_to_list(App) ++ Ext]).
fin_suite(suite) -> [];
fin_suite(doc) -> [];
fin_suite(Config) when is_list(Config) ->
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test server callbacks
init_per_testcase(_Case, Config) when is_list(Config) ->
Config.
fin_per_testcase(_Case, Config) when is_list(Config) ->
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
appup_file(suite) ->
[];
appup_file(doc) ->
"Perform a simple check of the appup file";
appup_file(Config) when is_list(Config) ->
AppupFile = key1search(appup_file, Config),
AppFile = key1search(app_file, Config),
Modules = modules(AppFile),
check_appup(AppupFile, Modules).
modules(File) ->
case file:consult(File) of
{ok, [{application,snmp,Info}]} ->
case lists:keysearch(modules,1,Info) of
{value, {modules, Modules}} ->
Modules;
false ->
fail({bad_appinfo, Info})
end;
Error ->
fail({bad_appfile, Error, File})
end.
check_appup(AppupFile, Modules) ->
case file:consult(AppupFile) of
{ok, [{V, UpFrom, DownTo}]} ->
check_appup(V, UpFrom, DownTo, Modules);
Else ->
fail({bad_appupfile, Else})
end.
check_appup(V, UpFrom, DownTo, Modules) ->
check_version(V),
check_depends(up, UpFrom, Modules),
check_depends(down, DownTo, Modules),
check_module_subset(up, UpFrom),
check_module_subset(down, DownTo),
ok.
check_depends(_, [], _) ->
ok;
check_depends(UpDown, [Dep|Deps], Modules) ->
check_depend(UpDown, Dep, Modules),
check_depends(UpDown, Deps, Modules).
check_depend(up = UpDown, {add_application, ?APPLICATION} = Instr, Modules) ->
d("check_instructions(~w) -> entry with"
"~n Instruction: ~p"
"~n Modules: ~p", [UpDown, Instr, Modules]),
ok;
check_depend(down = UpDown, {remove_application, ?APPLICATION} = Instr,
Modules) ->
d("check_instructions(~w) -> entry with"
"~n Instruction: ~p"
"~n Modules: ~p", [UpDown, Instr, Modules]),
ok;
check_depend(UpDown, {V, Instructions}, Modules) ->
d("check_instructions(~w) -> entry with"
"~n V: ~p"
"~n Modules: ~p", [UpDown, V, Modules]),
check_version(V),
case check_instructions(UpDown,
Instructions, Instructions, [], [], Modules) of
{_Good, []} ->
ok;
{_, Bad} ->
fail({bad_instructions, Bad, UpDown})
end.
check_instructions(_, [], _, Good, Bad, _) ->
{lists:reverse(Good), lists:reverse(Bad)};
check_instructions(UpDown, [Instr|Instrs], AllInstr, Good, Bad, Modules) ->
d("check_instructions(~w) -> entry with"
"~n Instr: ~p", [UpDown,Instr]),
case (catch check_instruction(UpDown, Instr, AllInstr, Modules)) of
ok ->
check_instructions(UpDown, Instrs, AllInstr,
[Instr|Good], Bad, Modules);
{error, Reason} ->
check_instructions(UpDown, Instrs, AllInstr, Good,
[{Instr, Reason}|Bad], Modules)
end;
check_instructions(UpDown, Instructions, _, _, _, _) ->
fail({bad_instructions, {UpDown, Instructions}}).
check_instruction(_, {restart_application, ?APPLICATION}, _, _Modules) ->
d("check_instruction -> entry when restart_application instruction"),
ok;
%% A new module is added
check_instruction(up, {add_module, Module}, _, Modules)
when is_atom(Module) ->
d("check_instruction -> entry when up-add_module instruction with"
"~n Module: ~p", [Module]),
check_module(Module, Modules);
%% An old module is re-added
check_instruction(down, {add_module, Module}, _, Modules)
when is_atom(Module) ->
d("check_instruction -> entry when down-add_module instruction with"
"~n Module: ~p", [Module]),
case (catch check_module(Module, Modules)) of
{error, {unknown_module, Module, Modules}} ->
ok;
ok ->
error({existing_readded_module, Module})
end;
check_instruction(up, {delete_module, Module}, _, Modules)
when is_atom(Module) ->
d("check_instruction -> entry when up-delete_module instruction with"
"~n Module: ~p", [Module]),
case (catch check_module(Module, Modules)) of
{error, {unknown_module, Module, Modules}} ->
ok;
ok ->
error({module_cannot_be_deleted, Module})
end;
%% An new module is deleted
check_instruction(down, {delete_module, Module}, _, Modules)
when is_atom(Module) ->
d("check_instruction -> entry when down-delete_module instruction with"
"~n Module: ~p", [Module]),
check_module(Module, Modules);
%% Removing a module on upgrade:
%% - the module has been removed from the app-file.
%% - check that no module depends on this (removed) module
check_instruction(up, {remove, {Module, Pre, Post}}, _, Modules)
when is_atom(Module) and is_atom(Pre) and is_atom(Post) ->
d("check_instruction -> entry when up-remove instruction with"
"~n Module: ~p"
"~n Pre: ~p"
"~n Post: ~p", [Module, Pre, Post]),
case (catch check_module(Module, Modules)) of
{error, {unknown_module, Module, Modules}} ->
check_purge(Pre),
check_purge(Post);
ok ->
error({existing_removed_module, Module})
end;
%% Removing a module on downgrade: the module exist
%% in the app-file.
check_instruction(down, {remove, {Module, Pre, Post}}, AllInstr, Modules)
when is_atom(Module) and is_atom(Pre) and is_atom(Post) ->
d("check_instruction -> entry when down-remove instruction with"
"~n Module: ~p"
"~n Pre: ~p"
"~n Post: ~p", [Module, Pre, Post]),
case (catch check_module(Module, Modules)) of
ok ->
check_purge(Pre),
check_purge(Post),
check_no_remove_depends(Module, AllInstr);
{error, {unknown_module, Module, Modules}} ->
error({nonexisting_removed_module, Module})
end;
check_instruction(_, {load_module, Module, Pre, Post, Depend},
AllInstr, Modules)
when is_atom(Module) and
is_atom(Pre) and
is_atom(Post) and
is_list(Depend) ->
d("check_instruction -> entry when load_module instruction with"
"~n Module: ~p"
"~n Pre: ~p"
"~n Post: ~p"
"~n Depend: ~p", [Module, Pre, Post, Depend]),
check_module(Module, Modules),
check_module_depend(Module, Depend, Modules),
check_module_depend(Module, Depend, updated_modules(AllInstr, [])),
check_purge(Pre),
check_purge(Post);
check_instruction(_, {update, Module, Change, Pre, Post, Depend},
AllInstr, Modules)
when is_atom(Module) and
is_atom(Pre) and
is_atom(Post) and
is_list(Depend) ->
d("check_instruction -> entry when update instruction with"
"~n Module: ~p"
"~n Change: ~p"
"~n Pre: ~p"
"~n Post: ~p"
"~n Depend: ~p", [Module, Change, Pre, Post, Depend]),
check_module(Module, Modules),
check_module_depend(Module, Depend, Modules),
check_module_depend(Module, Depend, updated_modules(AllInstr, [])),
check_change(Change),
check_purge(Pre),
check_purge(Post);
check_instruction(_, {update, Module, supervisor}, _, Modules)
when is_atom(Module) ->
d("check_instruction -> entry when supervisor update instruction with"
"~n Module: ~p", [Module]),
check_module(Module, Modules);
check_instruction(_, {apply, {Module, Function, Args}}, _, _Modules)
when is_atom(Module) and is_atom(Function) and is_list(Args) ->
d("check_instruction -> entry when apply instruction with"
"~n Module: ~p"
"~n Function: ~p"
"~n Args: ~p", [Module, Function, Args]),
check_apply(Module, Function, Args);
check_instruction(_, Instr, _AllInstr, _Modules) ->
error({error, {unknown_instruction, Instr}}).
%% If Module X depends on Module Y, then module Y must have an update
%% instruction of some sort (otherwise the depend is faulty).
updated_modules([], Modules) ->
d("updated_modules -> entry when done with"
"~n Modules: ~p", [Modules]),
Modules;
updated_modules([Instr|Instrs], Modules) ->
d("updated_modules -> entry with"
"~n Instr: ~p"
"~n Modules: ~p", [Instr,Modules]),
case instruction_module(Instr) of
{module, Module} ->
d("updated_modules -> Module: ~p", [Module]),
updated_modules(Instrs, [Module|Modules]);
no_module ->
updated_modules(Instrs, Modules)
end.
instruction_module({add_module, Module}) ->
{module, Module};
instruction_module({delete_module, Module}) ->
{module, Module};
instruction_module({remove, {Module, _, _}}) ->
{module, Module};
instruction_module({load_module, Module, _, _, _}) ->
{module, Module};
instruction_module({update, Module, _, _, _, _}) ->
{module, Module};
instruction_module({update, Module, _}) ->
{module, Module};
instruction_module({apply, {_, _, _}}) ->
no_module;
instruction_module(Instr) ->
d("instruction_module -> entry when unknown instruction with"
"~n Instr: ~p", [Instr]),
error({error, {unknown_instruction, Instr}}).
%% Check that the modules handled in an instruction set for version X
%% is a subset of the instruction set for version X-1.
check_module_subset(Direction, Instructions) ->
d("check_module_subset(~w) -> entry when"
"~n Instructions: ~p", [Direction,Instructions]),
do_check_module_subset(modules_of(Instructions)).
do_check_module_subset([]) ->
ok;
do_check_module_subset([_]) ->
ok;
do_check_module_subset([{_V1, Mods1}|T]) ->
d("do_check_module_subset -> entry with"
"~n V1: ~s"
"~n Mods1: ~p", [_V1, Mods1]),
{V2, Mods2} = hd(T),
d("do_check_module_subset -> "
"~n V2: ~s"
"~n Mods2: ~p", [V2, Mods2]),
%% Check that the modules in V1 is a subset of V2
case do_check_module_subset2(Mods1, Mods2) of
ok ->
do_check_module_subset(T);
{error, Modules} ->
fail({subset_missing_instructions, V2, Modules})
end.
do_check_module_subset2(_Mods1, [{restart_application, ?APPLICATION}]) ->
ok;
do_check_module_subset2(Mods1, Mods2) ->
do_check_module_subset2(Mods1, Mods2, []).
do_check_module_subset2([], _, []) ->
ok;
do_check_module_subset2([], _, Acc) ->
{error, lists:reverse(Acc)};
do_check_module_subset2([Mod|Mods], Mods2, Acc) ->
case lists:member(Mod, Mods2) of
true ->
do_check_module_subset2(Mods, Mods2, Acc);
false ->
do_check_module_subset2(Mods, Mods2, [Mod|Acc])
end.
modules_of(Instructions) ->
modules_of(Instructions, []).
modules_of([], Acc) ->
lists:reverse(Acc);
modules_of([{_V,[{restart_application, ?APPLICATION}]}|T], Acc) ->
modules_of(T, Acc);
modules_of([{V,Instructions}|T], Acc) ->
Mods = modules_of2(Instructions, []),
modules_of(T, [{V, Mods}|Acc]).
modules_of2([], Acc) ->
lists:reverse(Acc);
modules_of2([Instr|Instructions], Acc) ->
d("module_of -> entry with"
"~n Instr: ~p", [Instr]),
case module_of(Instr) of
{value, Mod} ->
d("module_of -> Mod: ~p", [Mod]),
modules_of2(Instructions, [Mod|Acc]);
false ->
modules_of2(Instructions, Acc)
end.
module_of({add_module, Module}) ->
{value, Module};
module_of({delete_module, Module}) ->
{value, Module};
module_of({remove, {Module, _Pre, _Post}}) ->
{value, Module};
module_of({load_module, Module, _Pre, _Post, _Depend}) ->
{value, Module};
module_of({update, Module, _Change, _Pre, _Post, _Depend}) ->
{value, Module};
module_of({update, Module, supervisor}) ->
{value, Module};
module_of(_) ->
false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
check_version(V) when is_list(V) ->
ok;
check_version(V) ->
error({bad_version, V}).
check_module(M, Modules) when is_atom(M) ->
case lists:member(M, Modules) of
true ->
ok;
false ->
error({unknown_module, M, Modules})
end;
check_module(M, _) ->
error({bad_module, M}).
check_module_depend(M, [], _) when is_atom(M) ->
ok;
check_module_depend(M, Deps, Modules) when is_atom(M) and is_list(Deps) ->
case [Dep || Dep <- Deps, lists:member(Dep, Modules) == false] of
[] ->
ok;
Unknown ->
error({unknown_depend_modules, Unknown})
end;
check_module_depend(_M, D, _Modules) ->
error({bad_depend, D}).
check_no_remove_depends(_Module, []) ->
ok;
check_no_remove_depends(Module, [Instr|Instrs]) ->
check_no_remove_depend(Module, Instr),
check_no_remove_depends(Module, Instrs).
check_no_remove_depend(Module, {load_module, Mod, _Pre, _Post, Depend}) ->
case lists:member(Module, Depend) of
true ->
error({removed_module_in_depend, load_module, Mod, Module});
false ->
ok
end;
check_no_remove_depend(Module, {update, Mod, _Change, _Pre, _Post, Depend}) ->
case lists:member(Module, Depend) of
true ->
error({removed_module_in_depend, update, Mod, Module});
false ->
ok
end;
check_no_remove_depend(_, _) ->
ok.
check_change(soft) ->
ok;
check_change({advanced, _Something}) ->
ok;
check_change(Change) ->
error({bad_change, Change}).
check_purge(soft_purge) ->
ok;
check_purge(brutal_purge) ->
ok;
check_purge(Purge) ->
error({bad_purge, Purge}).
check_apply(Module, Function, Args) ->
case (catch Module:module_info()) of
Info when is_list(Info) ->
check_exported(Function, Args, Info);
{'EXIT', {undef, _}} ->
error({not_existing_module, Module})
end.
check_exported(Function, Args, Info) ->
case lists:keysearch(exports, 1, Info) of
{value, {exports, FuncList}} ->
Arity = length(Args),
Arities = [A || {F, A} <- FuncList, F == Function],
case lists:member(Arity, Arities) of
true ->
ok;
false ->
fail({not_exported_function, Function, Arity})
end;
_ ->
error({bad_export, Info})
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
error(Reason) ->
throw({error, Reason}).
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.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
d(F) ->
d(F, []).
d(F, A) ->
d(true, F, A).
d(true, F, A) ->
io:format(F ++ "~n", A);
d(_, _, _) ->
ok.