aboutsummaryrefslogtreecommitdiffstats
path: root/lib/snmp/test/snmp_appup_test.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/snmp/test/snmp_appup_test.erl')
-rw-r--r--lib/snmp/test/snmp_appup_test.erl560
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.
+