-module(rebar_upgrade_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -compile(export_all). all() -> [{group, git}].%, {group, pkg}]. groups() -> [{all, [], [top_a, top_b, top_c, top_d1, top_d2, top_e, pair_a, pair_b, pair_ab, pair_c, pair_all, triplet_a, triplet_b, triplet_c, tree_a, tree_b, tree_c, tree_c2, tree_ac, tree_all, delete_d]}, {git, [], [{group, all}]}, {pkg, [], [{group, all}]}]. init_per_suite(Config) -> application:start(meck), Config. end_per_suite(_Config) -> application:stop(meck). init_per_group(git, Config) -> [{deps_type, git} | Config]; init_per_group(pkg, Config) -> [{deps_type, pkg} | Config]; init_per_group(_, Config) -> Config. end_per_group(_, Config) -> Config. init_per_testcase(Case, Config) -> DepsType = ?config(deps_type, Config), {Deps, UpDeps, ToUp, Expectations} = upgrades(Case), Expanded = expand_deps(DepsType, Deps), UpExpanded = expand_deps(DepsType, UpDeps), [{expected, normalize_unlocks(Expectations)}, {mock, fun() -> mock_deps(DepsType, Expanded, []) end}, {mock_update, fun() -> mock_deps(DepsType, UpExpanded, ToUp) end} | setup_project(Case, Config, Expanded, UpExpanded)]. end_per_testcase(_, Config) -> meck:unload(), Config. setup_project(Case, Config0, Deps, UpDeps) -> DepsType = ?config(deps_type, Config0), Config = rebar_test_utils:init_rebar_state( Config0, atom_to_list(Case)++"_"++atom_to_list(DepsType)++"_" ), AppDir = ?config(apps, Config), rebar_test_utils:create_app(AppDir, "Root", "0.0.0", [kernel, stdlib]), TopDeps = top_level_deps(Deps), RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]), [{rebarconfig, RebarConf}, {next_top_deps, top_level_deps(UpDeps)} | Config]. upgrades(top_a) -> %% Original tree {[{"A", "1", [{"B", [{"D", "1", []}]}, {"C", [{"D", "2", []}]}]} ], %% Updated tree [{"A", "1", [{"B", [{"D", "3", []}]}, {"C", [{"D", "2", []}]}]} ], %% Modified apps, gobally ["A","B","D"], %% upgrade vs. new tree {"A", [{"A","1"}, "B", "C", {"D","3"}]}}; upgrades(top_b) -> %% Original tree {[{"A", "1", [{"B", [{"D", "1", []}]}, {"C", [{"D", "2", []}]}]} ], %% Updated tree [{"A", "1", [{"B", [{"D", "3", []}]}, {"C", [{"D", "2", []}]}]} ], %% Modified apps, gobally ["A","B","D"], %% upgrade vs. new tree {"B", {error, {rebar_prv_upgrade, {transitive_dependency, <<"B">>}}}}}; upgrades(top_c) -> %% Original tree {[{"A", "1", [{"B", [{"D", "1", []}]}, {"C", [{"D", "2", []}]}]} ], %% Updated tree [{"A", "1", [{"B", [{"D", "3", []}]}, {"C", [{"D", "2", []}]}]} ], %% Modified apps, gobally ["A","B","D"], %% upgrade vs. new tree {"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}}; upgrades(top_d1) -> %% Original tree {[{"A", "1", [{"B", [{"D", "1", []}]}, {"C", [{"D", "2", []}]}]} ], %% Updated tree [{"A", "1", [{"B", [{"D", "3", []}]}, {"C", [{"D", "2", []}]}]} ], %% Modified apps, gobally ["A","B","D"], %% upgrade vs. new tree {"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}}; upgrades(top_d2) -> %% Original tree {[{"A", "1", [{"B", [{"D", "1", []}]}, {"C", [{"D", "2", []}]}]} ], %% Updated tree [{"A", "1", [{"B", [{"D", "3", []}]}, {"C", [{"D", "2", []}]}]} ], %% Modified apps, gobally ["A","B","D"], %% upgrade vs. new tree {"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}}; upgrades(top_e) -> %% Original tree {[{"A", "1", [{"B", [{"D", "1", []}]}, {"C", [{"D", "2", []}]}]} ], %% Updated tree [{"A", "1", [{"B", [{"D", "3", []}]}, {"C", [{"D", "2", []}]}]} ], %% Modified apps, gobally ["A","B","D"], %% upgrade vs. new tree {"E", {error, {rebar_prv_upgrade, {unknown_dependency, <<"E">>}}}}}; upgrades(pair_a) -> {[{"A", "1", [{"C", "1", []}]}, {"B", "1", [{"D", "1", []}]} ], [{"A", "2", [{"C", "2", []}]}, {"B", "2", [{"D", "2", []}]} ], ["A","B","C","D"], {"A", [{"A","2"},{"C","2"},{"B","1"},{"D","1"}]}}; upgrades(pair_b) -> {[{"A", "1", [{"C", "1", []}]}, {"B", "1", [{"D", "1", []}]} ], [{"A", "2", [{"C", "2", []}]}, {"B", "2", [{"D", "2", []}]} ], ["A","B","C","D"], {"B", [{"A","1"},{"C","1"},{"B","2"},{"D","2"}]}}; upgrades(pair_ab) -> {[{"A", "1", [{"C", "1", []}]}, {"B", "1", [{"D", "1", []}]} ], [{"A", "2", [{"C", "2", []}]}, {"B", "2", [{"D", "2", []}]} ], ["A","B","C","D"], {"A,B", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}}; upgrades(pair_c) -> {[{"A", "1", [{"C", "1", []}]}, {"B", "1", [{"D", "1", []}]} ], [{"A", "2", [{"C", "2", []}]}, {"B", "2", [{"D", "2", []}]} ], ["A","B","C","D"], {"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}}; upgrades(pair_all) -> {[{"A", "1", [{"C", "1", []}]}, {"B", "1", [{"D", "1", []}]} ], [{"A", "2", [{"C", "2", []}]}, {"B", "2", [{"D", "2", []}]} ], ["A","B","C","D"], {"", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}}; upgrades(triplet_a) -> {[{"A", "1", [{"D",[]}, {"E","3",[]}]}, {"B", "1", [{"F","1",[]}, {"G",[]}]}, {"C", "0", [{"H","3",[]}, {"I",[]}]}], [{"A", "1", [{"D",[]}, {"E","2",[]}]}, {"B", "1", [{"F","1",[]}, {"G",[]}]}, {"C", "1", [{"H","4",[]}, {"I",[]}]}], ["A","C","E","H"], {"A", [{"A","1"}, "D", {"E","2"}, {"B","1"}, {"F","1"}, "G", {"C","0"}, {"H","3"}, "I"]}}; upgrades(triplet_b) -> {[{"A", "1", [{"D",[]}, {"E","3",[]}]}, {"B", "1", [{"F","1",[]}, {"G",[]}]}, {"C", "0", [{"H","3",[]}, {"I",[]}]}], [{"A", "1", [{"D",[]}, {"E","2",[]}]}, {"B", "1", [{"F","1",[]}, {"G",[]}]}, {"C", "1", [{"H","4",[]}, {"I",[]}]}], ["A","C","E","H"], {"B", [{"A","1"}, "D", {"E","3"}, {"B","1"}, {"F","1"}, "G", {"C","0"}, {"H","3"}, "I"]}}; upgrades(triplet_c) -> {[{"A", "1", [{"D",[]}, {"E","3",[]}]}, {"B", "1", [{"F","1",[]}, {"G",[]}]}, {"C", "0", [{"H","3",[]}, {"I",[]}]}], [{"A", "1", [{"D",[]}, {"E","2",[]}]}, {"B", "1", [{"F","1",[]}, {"G",[]}]}, {"C", "1", [{"H","4",[]}, {"I",[]}]}], ["A","C","E","H"], {"C", [{"A","1"}, "D", {"E","3"}, {"B","1"}, {"F","1"}, "G", {"C","1"}, {"H","4"}, "I"]}}; upgrades(tree_a) -> {[{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}, {"I","2",[]}]} ], [{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}]} ], ["C"], {"A", [{"A","1"}, "D", "J", "E", {"B","1"}, "F", "G", {"C","1"}, "H", {"I","2"}]}}; upgrades(tree_b) -> {[{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}, {"I","2",[]}]} ], [{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}]} ], ["C"], {"B", [{"A","1"}, "D", "J", "E", {"B","1"}, "F", "G", {"C","1"}, "H", {"I","2"}]}}; upgrades(tree_c) -> {[{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}, {"I","2",[]}]} ], [{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}]} ], ["C","I"], {"C", [{"A","1"}, "D", "J", "E", {"I","1"}, {"B","1"}, "F", "G", {"C","1"}, "H"]}}; upgrades(tree_c2) -> {[{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}, {"I","2",[]}]} ], [{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[{"K",[]}]}, {"I","2",[]}]} ], ["C", "H"], {"C", [{"A","1"}, "D", "J", "E", {"B","1"}, "F", "G", {"C","1"}, "H", {"I", "2"}, "K"]}}; upgrades(tree_ac) -> {[{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}, {"I","2",[]}]} ], [{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}]} ], ["C","I"], {"C, A", [{"A","1"}, "D", "J", "E", {"I","1"}, {"B","1"}, "F", "G", {"C","1"}, "H"]}}; upgrades(tree_all) -> {[{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}, {"I","2",[]}]} ], [{"A", "1", [{"D",[{"J",[]}]}, {"E",[{"I","1",[]}]}]}, {"B", "1", [{"F",[]}, {"G",[]}]}, {"C", "1", [{"H",[]}]} ], ["C","I"], {"", [{"A","1"}, "D", "J", "E", {"I","1"}, {"B","1"}, "F", "G", {"C","1"}, "H"]}}; upgrades(delete_d) -> {[{"A", "1", [{"B", [{"D", "1", []}]}, {"C", [{"D", "2", []}]}]} ], [{"A", "2", [{"B", []}, {"C", []}]} ], ["A","B", "C"], %% upgrade vs. new tree {"", [{"A","2"}, "B", "C"]}}. %% TODO: add a test that verifies that unlocking files and then %% running the upgrade code is enough to properly upgrade things. top_level_deps([]) -> []; top_level_deps([{{Name, Vsn, Ref}, _} | Deps]) -> [{list_to_atom(Name), Vsn, Ref} | top_level_deps(Deps)]; top_level_deps([{{pkg, Name, Vsn}, _} | Deps]) -> [{list_to_atom(Name), Vsn} | top_level_deps(Deps)]. mock_deps(git, Deps, Upgrades) -> catch mock_git_resource:unmock(), mock_git_resource:mock([{deps, flat_deps(Deps)}, {upgrade, Upgrades}]); mock_deps(pkg, Deps, Upgrades) -> catch mock_pkg_resource:unmock(), mock_pkg_resource:mock([{pkgdeps, flat_pkgdeps(Deps)}, {upgrade, Upgrades}]). flat_deps([]) -> []; flat_deps([{{Name,_Vsn,Ref}, Deps} | Rest]) -> [{{Name,vsn_from_ref(Ref)}, top_level_deps(Deps)}] ++ flat_deps(Deps) ++ flat_deps(Rest). vsn_from_ref({git, _, {_, Vsn}}) -> Vsn; vsn_from_ref({git, _, Vsn}) -> Vsn. flat_pkgdeps([]) -> []; flat_pkgdeps([{{pkg, Name, Vsn}, Deps} | Rest]) -> [{{iolist_to_binary(Name),iolist_to_binary(Vsn)}, top_level_deps(Deps)}] ++ flat_pkgdeps(Deps) ++ flat_pkgdeps(Rest). expand_deps(_, []) -> []; expand_deps(git, [{Name, Deps} | Rest]) -> Dep = {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}}, [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)]; expand_deps(git, [{Name, Vsn, Deps} | Rest]) -> Dep = {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}}, [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)]; expand_deps(pkg, [{Name, Deps} | Rest]) -> Dep = {pkg, Name, "0.0.0"}, [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)]; expand_deps(pkg, [{Name, Vsn, Deps} | Rest]) -> Dep = {pkg, Name, Vsn}, [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)]. normalize_unlocks({App, Locks}) -> {iolist_to_binary(App), normalize_unlocks_expect(Locks)}; normalize_unlocks({App, Vsn, Locks}) -> {iolist_to_binary(App), iolist_to_binary(Vsn), normalize_unlocks_expect(Locks)}. normalize_unlocks_expect({error, Reason}) -> {error, Reason}; normalize_unlocks_expect([]) -> []; normalize_unlocks_expect([{App,Vsn} | Rest]) -> [{dep, App, Vsn}, {lock, App, Vsn} | normalize_unlocks_expect(Rest)]; normalize_unlocks_expect([App | Rest]) -> [{dep, App}, {lock, App} | normalize_unlocks_expect(Rest)]. top_a(Config) -> run(Config). top_b(Config) -> run(Config). top_c(Config) -> run(Config). top_d1(Config) -> run(Config). top_d2(Config) -> run(Config). top_e(Config) -> run(Config). pair_a(Config) -> run(Config). pair_b(Config) -> run(Config). pair_ab(Config) -> run(Config). pair_c(Config) -> run(Config). pair_all(Config) -> run(Config). triplet_a(Config) -> run(Config). triplet_b(Config) -> run(Config). triplet_c(Config) -> run(Config). tree_a(Config) -> run(Config). tree_b(Config) -> run(Config). tree_c(Config) -> run(Config). tree_c2(Config) -> run(Config). tree_ac(Config) -> run(Config). tree_all(Config) -> run(Config). delete_d(Config) -> meck:new(rebar_log, [no_link, passthrough]), run(Config), Infos = [{Str, Args} || {_, {rebar_log, log, [info, Str, Args]}, _} <- meck:history(rebar_log)], meck:unload(rebar_log), ?assertNotEqual([], [1 || {"App ~ts is no longer needed and can be deleted.", [<<"D">>]} <- Infos]). run(Config) -> apply(?config(mock, Config), []), {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), %% Install dependencies before re-mocking for an upgrade rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}), {App, Unlocks} = ?config(expected, Config), ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]), Expectation = case Unlocks of {error, Term} -> {error, Term}; _ -> {ok, Unlocks} end, apply(?config(mock_update, Config), []), NewRebarConf = rebar_test_utils:create_config(?config(apps, Config), [{deps, ?config(next_top_deps, Config)}]), {ok, NewRebarConfig} = file:consult(NewRebarConf), rebar_test_utils:run_and_check( Config, NewRebarConfig, ["upgrade", App], Expectation ).