Browse Source

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
pull/126/head
Fred Hebert 10 years ago
parent
commit
b94afdfa2d
7 changed files with 238 additions and 20 deletions
  1. +1
    -1
      src/rebar_prv_install_deps.erl
  2. +1
    -1
      src/rebar_prv_lock.erl
  3. +34
    -14
      src/rebar_prv_upgrade.erl
  4. +6
    -2
      src/rebar_state.erl
  5. +0
    -1
      test/rebar_deps_SUITE.erl
  6. +11
    -1
      test/rebar_test_utils.erl
  7. +185
    -0
      test/rebar_upgrade_SUITE.erl

+ 1
- 1
src/rebar_prv_install_deps.erl View File

@ -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 ->

+ 1
- 1
src/rebar_prv_lock.erl View File

@ -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().

+ 34
- 14
src/rebar_prv_upgrade.erl View File

@ -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().

+ 6
- 2
src/rebar_state.erl View File

@ -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]}.

+ 0
- 1
test/rebar_deps_SUITE.erl View File

@ -1,4 +1,3 @@
%%% TODO: check that warnings are appearing
-module(rebar_deps_SUITE).
-compile(export_all).
-include_lib("common_test/include/ct.hrl").

+ 11
- 1
test/rebar_test_utils.erl View File

@ -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:

+ 185
- 0
test/rebar_upgrade_SUITE.erl View File

@ -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].

Loading…
Cancel
Save