diff options
Diffstat (limited to 'lib/snmp/test/snmp_appup_test.erl')
-rw-r--r-- | lib/snmp/test/snmp_appup_test.erl | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/lib/snmp/test/snmp_appup_test.erl b/lib/snmp/test/snmp_appup_test.erl new file mode 100644 index 0000000000..18509526cf --- /dev/null +++ b/lib/snmp/test/snmp_appup_test.erl @@ -0,0 +1,560 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. 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_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_suite, Cases, fin_suite}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_suite(suite) -> []; +init_suite(doc) -> []; +init_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. + |