summaryrefslogtreecommitdiff
path: root/test/rebar_upgrade_SUITE.erl
diff options
context:
space:
mode:
authorFred Hebert <mononcqc@ferd.ca>2019-03-15 13:45:57 -0400
committerFred Hebert <mononcqc@ferd.ca>2019-03-15 13:45:57 -0400
commita0a3334bff128fe0dd7fe891e0000d2a3dc77592 (patch)
treecf678ad55278c5ebb3acee0726360f05e97e7fb5 /test/rebar_upgrade_SUITE.erl
parent9fa37cf8785765dcb8685c53ee450e18dc250f66 (diff)
Fix handling of transient locks during upgrade
We currently have a problem in the handling of locks in the `upgrade <app>` command when a transitive dependency switches supervision trees. If we start with: A / \ B1 C | D | E And upgrade to: A / \ B2 C | | E D by calling `rebar3 upgrade b`, we would expect the ugprade command to do so directly. However we are currently left with the following lock file: A / \ B2 C | D | E This is _not_ critical since on the next run, the lock file is fixed automatically through an install-deps sequence. However, this is a jarring state in which to leave a project. The reason is that we proceed the following way to handle an upgrade: 1. pick the name of the chosen app, and unlock it if it's at the level 0 2. grab a list of all its current children (and their own) and unlock them 3. do the same for all the other unlocked dependencies 4. pass this new lock internal format to the get-deps provider in upgrade mode 5. take the result and save it to the lock file. What's interesting is that right now this yields invalid locks at step 3, because we have removed the lock on B1, but not on E (which we don't yet know is going to be a new child of the next B version until we fetch it) In step 4, we fetch dependencies, and in there we compare the pre-upgrade locks and fetch structure with those expected in the current lock state, and rebuild everything. The fix in this commit adds a check there where if the app is in the current lock set at a level deeper than the current one, the lock is forced to be refreshed as if it were not there. This lock would get refreshed on a blank run anyway since the current lock set would start empty (rather than pre-populated by `upgrade`), and the level-order traversal ensures that the locks remain safe in all cases. Fixes #2030
Diffstat (limited to 'test/rebar_upgrade_SUITE.erl')
-rw-r--r--test/rebar_upgrade_SUITE.erl49
1 files changed, 47 insertions, 2 deletions
diff --git a/test/rebar_upgrade_SUITE.erl b/test/rebar_upgrade_SUITE.erl
index c55456c..54ff18c 100644
--- a/test/rebar_upgrade_SUITE.erl
+++ b/test/rebar_upgrade_SUITE.erl
@@ -12,7 +12,7 @@ groups() ->
tree_a, tree_b, tree_c, tree_c2, tree_cj, tree_ac, tree_all,
delete_d, promote, stable_lock, fwd_lock,
compile_upgrade_parity, umbrella_config,
- profiles, profiles_exclusion]},
+ profiles, profiles_exclusion, tree_migration]},
{git, [], [{group, all}]},
{pkg, [], [{group, all}]}].
@@ -512,7 +512,15 @@ upgrades(profiles_exclusion) ->
["A","B","C","E","F","H"],
{"A", [{"A","1.0.0"}, "D", {"E","3.0.0"},
{"B","2.0.0"}, {"F","2.0.0"}, "G",
- {"C","1.0.0"}, {"H","4.0.0"}, "I"]}}.
+ {"C","1.0.0"}, {"H","4.0.0"}, "I"]}};
+upgrades(tree_migration) ->
+ {[{"B", "1.0.0", []},
+ {"C", "1.0.0", [{"D","1.0.0",[{"E", "1.0.0", []}]}]}],
+ [{"B", "2.0.0", [{"E","1.0.0",[]}]},
+ {"C", "1.0.0", [{"D","1.0.0",[]}]}],
+ ["B"],
+ {"B", [{"A","1.0.0"}, "D", {"E","1.0.0"},
+ {"B","2.0.0"}]}}.
%% TODO: add a test that verifies that unlocking files and then
%% running the upgrade code is enough to properly upgrade things.
@@ -702,6 +710,43 @@ profiles(Config) ->
profiles_exclusion(Config) -> profiles(Config).
+tree_migration(Config) ->
+ apply(?config(mock, Config), []),
+ ConfigPath = ?config(rebarconfig, Config),
+ {ok, RebarConfig} = file:consult(ConfigPath),
+ %% Install dependencies before re-mocking for an upgrade
+ rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}),
+ {App, _Unlocks} = ?config(expected, Config),
+
+ meck:new(rebar_prv_upgrade, [passthrough]),
+ meck:expect(rebar_prv_upgrade, do, fun(S) ->
+ apply(?config(mock_update, Config), []),
+ meck:passthrough([S])
+ end),
+ NewRebarConf = rebar_test_utils:create_config(filename:dirname(ConfigPath),
+ [{deps, ?config(next_top_deps, Config)}]),
+ {ok, NewRebarConfig} = file:consult(NewRebarConf),
+ {ok, NewState} = rebar_test_utils:run_and_check(
+ Config, NewRebarConfig, ["upgrade", App], return
+ ),
+ meck:unload(rebar_prv_upgrade),
+ %% Check that the internal state properly has E with a lock-level
+ %% of 1.
+ Locks = rebar_state:lock(NewState),
+ [Locked] = [X || X <- Locks, rebar_app_info:name(X) =:= <<"E">>],
+ ?assertEqual(1, rebar_app_info:dep_level(Locked)),
+ %% Check that the lockfile on disk agrees
+ AppDir = ?config(apps, Config),
+ Lockfile = filename:join([AppDir, "rebar.lock"]),
+ case file:consult(Lockfile) of
+ {ok, [{_Vsn, Prop}|_]} -> % packages
+ ?assertMatch({<<"E">>, _, 1}, lists:keyfind(<<"E">>, 1, Prop));
+ {ok, [Prop]} -> % git source
+ ?assertMatch({<<"E">>, _, 1}, lists:keyfind(<<"E">>, 1, Prop))
+ end,
+ ok.
+
+
run(Config) ->
apply(?config(mock, Config), []),
ConfigPath = ?config(rebarconfig, Config),