diff options
author | Fred Hebert <mononcqc@ferd.ca> | 2015-01-20 01:39:43 +0000 |
---|---|---|
committer | Fred Hebert <mononcqc@ferd.ca> | 2015-02-11 21:57:58 +0000 |
commit | b94afdfa2d44b0cb1489234c7746cd72f85a167d (patch) | |
tree | c76a7c7bd3bd25ee369f843de83b1200aaeeb90c | |
parent | 2fd6f43c020555435c758ffe84ffe56a632387d8 (diff) |
WIP
Test that all the correct locks are set for an upgrade run.
Now to actually re-run the install deps and prove it works
-rw-r--r-- | src/rebar_prv_install_deps.erl | 2 | ||||
-rw-r--r-- | src/rebar_prv_lock.erl | 2 | ||||
-rw-r--r-- | src/rebar_prv_upgrade.erl | 48 | ||||
-rw-r--r-- | src/rebar_state.erl | 8 | ||||
-rw-r--r-- | test/rebar_deps_SUITE.erl | 1 | ||||
-rw-r--r-- | test/rebar_test_utils.erl | 12 | ||||
-rw-r--r-- | test/rebar_upgrade_SUITE.erl | 185 |
7 files changed, 238 insertions, 20 deletions
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index 025d32a..49aa65c 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -287,7 +287,7 @@ update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen, handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> Name = rebar_app_info:name(AppInfo), - {_, _, _, DepLevel} = lists:keyfind(Name, 1, Locks), + {_, _, DepLevel} = lists:keyfind(Name, 1, Locks), case UpdateLevel < DepLevel orelse Name =:= UpdateName of true -> diff --git a/src/rebar_prv_lock.erl b/src/rebar_prv_lock.erl index 2f26a7f..8a69434 100644 --- a/src/rebar_prv_lock.erl +++ b/src/rebar_prv_lock.erl @@ -41,7 +41,7 @@ do(State) -> ,rebar_app_info:dep_level(Dep)} end, AllDeps), Dir = rebar_state:dir(State), - file:write_file(filename:join(Dir, "rebar.lock"), io_lib:format("~p.~n", [Locks])), + file:write_file(filename:join(Dir, ?LOCK_FILE), io_lib:format("~p.~n", [Locks])), {ok, State}. -spec format_error(any()) -> iolist(). diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index c0a56c3..4609f57 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -12,7 +12,10 @@ -include("rebar.hrl"). -define(PROVIDER, upgrade). --define(DEPS, []). +-define(DEPS, [lock]). +%% Also only upgrade top-level (0) deps. Transitive deps shouldn't be +%% upgradable -- if the user wants this, they should declare it at the +%% top level and then upgrade. %% =================================================================== %% Public API @@ -38,19 +41,36 @@ init(State) -> do(State) -> {Args, _} = rebar_state:command_parsed_args(State), Name = ec_cnv:to_binary(proplists:get_value(package, Args)), - Locks = rebar_state:get(State, locks, []), - case lists:keyfind(Name, 1, Locks) of - {_, _, _, Level} -> - Deps = rebar_state:get(State, deps), - case lists:keyfind(binary_to_atom(Name, utf8), 1, Deps) of - false -> - {error, io_lib:format("No such dependency ~s~n", [Name])}; - Dep -> - rebar_prv_install_deps:handle_deps(State, [Dep], {true, Name, Level}), - {ok, State} - end; - _ -> - {error, io_lib:format("No such dependency ~s~n", [Name])} + Locks = rebar_state:lock(State), + %% TODO: optimize by running the find + unlock in one sweep + case find_app(Name, Locks) of + {AppInfo, 0} -> + %% Unlock the app and all those with a lock level higher than + %% it has + NewLocks = unlock_higher_than(0, Locks -- [AppInfo]), + {ok, rebar_state:lock(State, NewLocks)}; + {_AppInfo, Level} when Level > 0 -> + {error, transitive_dependency}; + false -> + {error, unknown_dependency} + end. + +find_app(_Name, []) -> false; +find_app(Name, [App|Apps]) -> + case rebar_app_info:name(App) of + Name -> {App, rebar_app_info:dep_level(App)}; + _ -> find_app(Name, Apps) + end. + +%% Because we operate on a lock list, removing the app from the list +%% unlocks it. +unlock_higher_than(_, []) -> []; +unlock_higher_than(Level, [App | Apps]) -> + case rebar_app_info:dep_level(App) of + N when N > Level -> + unlock_higher_than(Level, Apps); + N when N =< Level -> + [App | unlock_higher_than(Level, Apps)] end. -spec format_error(any()) -> iolist(). diff --git a/src/rebar_state.erl b/src/rebar_state.erl index 29b7c3f..70aba51 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -89,8 +89,10 @@ new(ParentState, Config, Dir) -> Opts = ParentState#state_t.opts, LocalOpts = case rebar_config:consult_file(filename:join(Dir, ?LOCK_FILE)) of [D] -> - LockedDeps = [X || X <- D, element(3, X) =:= 0], - dict:from_list([{{locks, default}, LockedDeps}, {{deps, default}, D} | Config]); + %% We want the top level deps only from the lock file. + %% This ensures deterministic overrides for configs. + Deps = [X || X <- D, element(3, X) =:= 0], + dict:from_list([{{locks, default}, D}, {{deps, default}, Deps} | Config]); _ -> D = proplists:get_value(deps, Config, []), dict:from_list([{{deps, default}, D} | Config]) @@ -133,6 +135,8 @@ current_profiles(#state_t{current_profiles=Profiles}) -> lock(#state_t{lock=Lock}) -> Lock. +lock(State=#state_t{}, Apps) when is_list(Apps) -> + State#state_t{lock=Apps}; lock(State=#state_t{lock=Lock}, App) -> State#state_t{lock=[App | Lock]}. diff --git a/test/rebar_deps_SUITE.erl b/test/rebar_deps_SUITE.erl index d67efe4..8407b55 100644 --- a/test/rebar_deps_SUITE.erl +++ b/test/rebar_deps_SUITE.erl @@ -1,4 +1,3 @@ -%%% TODO: check that warnings are appearing -module(rebar_deps_SUITE). -compile(export_all). -include_lib("common_test/include/ct.hrl"). diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl index 6095d6d..af40995 100644 --- a/test/rebar_test_utils.erl +++ b/test/rebar_test_utils.erl @@ -53,7 +53,17 @@ run_and_check(Config, RebarConfig, Command, Expect) -> ?assertEqual({error, Reason}, Res); {ok, Expected} -> {ok, _} = Res, - check_results(AppDir, Expected) + check_results(AppDir, Expected); + {unlocked, Expected} -> + ct:pal("Expected: ~p", [Expected]), + {ok, ResState} = Res, + Locks = rebar_state:lock(ResState), + ct:pal("Locks: ~p", [Locks]), + ?assertEqual(lists:sort(Expected), + lists:sort([rebar_app_info:name(Lock) + || Lock <- Locks])); + return -> + Res end. %% @doc Creates a dummy application including: diff --git a/test/rebar_upgrade_SUITE.erl b/test/rebar_upgrade_SUITE.erl new file mode 100644 index 0000000..5915d0d --- /dev/null +++ b/test/rebar_upgrade_SUITE.erl @@ -0,0 +1,185 @@ +-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, pair, triplet, tree]}, + {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, Unlocks} = upgrades(Case), + Expanded = expand_deps(DepsType, Deps), + [{unlocks, normalize_unlocks(Unlocks)}, + {mock, fun() -> mock_deps(DepsType, Expanded, []) end} + | setup_project(Case, Config, Expanded)]. + +end_per_testcase(_, Config) -> + meck:unload(), + Config. + +setup_project(Case, Config0, Deps) -> + 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} | Config]. + + +upgrades(top) -> + {[{"A", [{"B", [{"D", "1", []}]}, + {"C", [{"D", "2", []}]}]} + ], + %% upgrade vs. locked after upgrade + [{"A", []}, + {"B", {error, transitive_dependency}}, + {"C", {error, transitive_dependency}}, + {"D", "1", {error, transitive_dependency}}, + {"D", "2", {error, unknown_dependency}}]}; +upgrades(pair) -> + {[{"A", [{"C", []}]}, + {"B", [{"D", []}]}], + [{"A", ["B"]}, + {"B", ["A"]}, + {"C", {error, transitive_dependency}}, + {"D", {error, transitive_dependency}}]}; +upgrades(triplet) -> + {[{"A", [{"D",[]}, + {"E",[]}]}, + {"B", [{"F",[]}, + {"G",[]}]}, + {"C", [{"H",[]}, + {"I",[]}]}], + [{"A", ["B","C"]}, + {"B", ["A","C"]}, + {"C", ["A","B"]}, + {"D", {error, transitive_dependency}}, + %% ... + {"I", {error, transitive_dependency}}]}; +upgrades(tree) -> + {[{"A", [{"D",[{"J",[]}]}, + {"E",[{"K",[]}]}]}, + {"B", [{"F",[]}, + {"G",[]}]}, + {"C", [{"H",[]}, + {"I",[]}]}], + [{"A", ["B","C"]}, + {"B", ["A","C"]}, + {"C", ["A","B"]}, + {"D", {error, transitive_dependency}}, + %% ... + {"K", {error, transitive_dependency}}]}. + +%% 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, _URL}, _} | Deps]) -> + [{list_to_atom(Name), Vsn} | top_level_deps(Deps)]. + +mock_deps(git, Deps, Upgrades) -> + mock_git_resource:mock([{deps, flat_deps(Deps)}, {upgrade, Upgrades}]); +mock_deps(pkg, Deps, Upgrades) -> + 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, _Url}, 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", "https://example.org/user/"++Name++".tar.gz"}, + [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)]; +expand_deps(pkg, [{Name, Vsn, Deps} | Rest]) -> + Dep = {pkg, Name, Vsn, "https://example.org/user/"++Name++".tar.gz"}, + [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)]. + +normalize_unlocks([]) -> []; +normalize_unlocks([{App, Locks} | Rest]) -> + [{iolist_to_binary(App), + normalize_unlocks_expect(Locks)} | normalize_unlocks(Rest)]; +normalize_unlocks([{App, Vsn, Locks} | Rest]) -> + [{iolist_to_binary(App), iolist_to_binary(Vsn), + normalize_unlocks_expect(Locks)} | normalize_unlocks(Rest)]. + +normalize_unlocks_expect({error, Reason}) -> + {error, Reason}; +normalize_unlocks_expect([]) -> + []; +normalize_unlocks_expect([{App,Vsn} | Rest]) -> + [{iolist_to_binary(App), iolist_to_binary(Vsn)} + | normalize_unlocks_expect(Rest)]; +normalize_unlocks_expect([App | Rest]) -> + [iolist_to_binary(App) | normalize_unlocks_expect(Rest)]. + +top(Config) -> run(Config). +pair(Config) -> run(Config). +triplet(Config) -> run(Config). +tree(Config) -> run(Config). + +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, []}), + Unlocks = ?config(unlocks, Config), + [begin + ct:pal("Unlocks: ~p -> ~p", [App, Unlocked]), + Expectation = case Unlocked of + Tuple when is_tuple(Tuple) -> Tuple; + _ -> {unlocked, Unlocked} + end, + rebar_test_utils:run_and_check( + Config, RebarConfig, ["upgrade", App], Expectation + ) + end || {App, Unlocked} <- Unlocks]. + |