diff --git a/apps/emqx_machine/src/emqx_machine.erl b/apps/emqx_machine/src/emqx_machine.erl index 59587926a..229e8ca7e 100644 --- a/apps/emqx_machine/src/emqx_machine.erl +++ b/apps/emqx_machine/src/emqx_machine.erl @@ -27,6 +27,10 @@ -export([sorted_reboot_apps/0]). +-ifdef(TEST). +-export([sorted_reboot_apps/1]). +-endif. + -include_lib("emqx/include/logger.hrl"). %% @doc EMQ X boot entrypoint. @@ -150,13 +154,17 @@ app_deps(App) -> sorted_reboot_apps(Apps) -> G = digraph:new(), - lists:foreach(fun({App, Deps}) -> add_app(G, App, Deps) end, Apps), - case digraph_utils:topsort(G) of - Sorted when is_list(Sorted) -> - Sorted; - false -> - Loops = find_loops(G), - error({circular_application_dependency, Loops}) + try + lists:foreach(fun({App, Deps}) -> add_app(G, App, Deps) end, Apps), + case digraph_utils:topsort(G) of + Sorted when is_list(Sorted) -> + Sorted; + false -> + Loops = find_loops(G), + error({circular_application_dependency, Loops}) + end + after + digraph:delete(G) end. add_app(G, App, undefined) -> diff --git a/apps/emqx_machine/test/emqx_machine_tests.erl b/apps/emqx_machine/test/emqx_machine_tests.erl new file mode 100644 index 000000000..dded07570 --- /dev/null +++ b/apps/emqx_machine/test/emqx_machine_tests.erl @@ -0,0 +1,60 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_machine_tests). + +-include_lib("eunit/include/eunit.hrl"). + +sorted_reboot_apps_test_() -> + Apps1 = [{1, [2, 3, 4]}, + {2, [3, 4]} + ], + Apps2 = [{1, [2, 3, 4]}, + {2, [3, 4]}, + {5, [4, 3, 2, 1, 1]} + ], + [fun() -> check_order(Apps1) end, + fun() -> check_order(Apps2) end + ]. + +sorted_reboot_apps_cycle_test() -> + Apps = [{1,[2]},{2, [1,3]}], + ?assertError({circular_application_dependency, [[1, 2, 1], [2, 1, 2]]}, + check_order(Apps)). + + +check_order(Apps) -> + AllApps = lists:usort(lists:append([[A | Deps] || {A, Deps} <- Apps])), + Sorted = emqx_machine:sorted_reboot_apps(Apps), + case length(AllApps) =:= length(Sorted) of + true -> ok; + false -> error({AllApps, Sorted}) + end, + {_, SortedWithIndex} = + lists:foldr(fun(A, {I, Acc}) -> {I + 1, [{A, I} | Acc]} end, {1, []}, Sorted), + do_check_order(Apps, SortedWithIndex). + +do_check_order([], _) -> ok; +do_check_order([{A, Deps} | Rest], Sorted) -> + case lists:filter(fun(Dep) -> is_sorted_before(Dep, A, Sorted) end, Deps) of + [] -> do_check_order(Rest, Sorted); + Bad -> throw({A, Bad}) + end. + +is_sorted_before(A, B, Sorted) -> + {A, IndexA} = lists:keyfind(A, 1, Sorted), + {B, IndexB} = lists:keyfind(B, 1, Sorted), + IndexA < IndexB.