Просмотр исходного кода

Merge pull request #718 from tsloughter/ferd-add-mixed-deps-tests

Ferd add mixed deps tests
pull/723/head
Fred Hebert 9 лет назад
Родитель
Сommit
d147aaa023
14 измененных файлов: 444 добавлений и 177 удалений
  1. +63
    -11
      src/rebar_app_discover.erl
  2. +84
    -52
      src/rebar_digraph.erl
  3. +4
    -4
      src/rebar_prv_common_test.erl
  4. +9
    -4
      src/rebar_prv_deps_tree.erl
  5. +10
    -7
      src/rebar_prv_install_deps.erl
  6. +10
    -7
      src/rebar_prv_upgrade.erl
  7. +4
    -3
      test/mock_git_resource.erl
  8. +2
    -1
      test/rebar_compile_SUITE.erl
  9. +7
    -24
      test/rebar_deps_SUITE.erl
  10. +169
    -34
      test/rebar_install_deps_SUITE.erl
  11. +4
    -2
      test/rebar_plugins_SUITE.erl
  12. +10
    -5
      test/rebar_profiles_SUITE.erl
  13. +60
    -19
      test/rebar_test_utils.erl
  14. +8
    -4
      test/rebar_upgrade_SUITE.erl

+ 63
- 11
src/rebar_app_discover.erl Просмотреть файл

@ -16,7 +16,25 @@ do(State, LibDirs) ->
Apps = find_apps(Dirs, all),
ProjectDeps = rebar_state:deps_names(State),
DepsDir = rebar_dir:deps_dir(State),
CurrentProfiles = rebar_state:current_profiles(State),
%% There may be a top level src which is an app and there may not
%% Find it here if there is, otherwise define the deps parent as root
TopLevelApp = define_root_app(Apps, State),
%% Handle top level deps
State1 = lists:foldl(fun(Profile, StateAcc) ->
ProfileDeps = rebar_state:get(StateAcc, {deps, Profile}, []),
ProfileDeps2 = rebar_utils:tup_dedup(rebar_utils:tup_sort(ProfileDeps)),
ParsedDeps = parse_profile_deps(Profile
,TopLevelApp
,ProfileDeps2
,StateAcc
,StateAcc),
rebar_state:set(StateAcc, {parsed_deps, Profile}, ParsedDeps)
end, State, lists:reverse(CurrentProfiles)),
%% Handle sub project apps deps
%% Sort apps so we get the same merged deps config everytime
SortedApps = rebar_utils:sort_deps(Apps),
lists:foldl(fun(AppInfo, StateAcc) ->
@ -33,7 +51,19 @@ do(State, LibDirs) ->
?INFO("Ignoring ~s", [Name]),
StateAcc
end
end, State, SortedApps).
end, State1, SortedApps).
define_root_app(Apps, State) ->
RootDir = rebar_dir:root_dir(State),
case ec_lists:find(fun(X) ->
ec_file:real_dir_path(rebar_app_info:dir(X)) =:=
ec_file:real_dir_path(RootDir)
end, Apps) of
{ok, App} ->
rebar_app_info:name(App);
error ->
root
end.
format_error({module_list, File}) ->
io_lib:format("Error reading module list from ~p~n", [File]);
@ -51,24 +81,46 @@ merge_deps(AppInfo, State) ->
rebar_state:apply_profiles(
rebar_state:new(reset_hooks(rebar_state:opts(State, Default)), C,
rebar_app_info:dir(AppInfo)), CurrentProfiles), Name),
AppState1 = rebar_state:overrides(AppState, rebar_state:get(AppState, overrides, [])),
rebar_utils:check_min_otp_version(rebar_state:get(AppState, minimum_otp_vsn, undefined)),
rebar_utils:check_blacklisted_otp_versions(rebar_state:get(AppState, blacklisted_otp_vsns, [])),
rebar_utils:check_min_otp_version(rebar_state:get(AppState1, minimum_otp_vsn, undefined)),
rebar_utils:check_blacklisted_otp_versions(rebar_state:get(AppState1, blacklisted_otp_vsns, [])),
AppState1 = rebar_state:set(AppState, artifacts, []),
AppInfo1 = rebar_app_info:state(AppInfo, AppState1),
AppState2 = rebar_state:set(AppState1, artifacts, []),
AppInfo1 = rebar_app_info:state(AppInfo, AppState2),
State1 = lists:foldl(fun(Profile, StateAcc) ->
AppProfDeps = rebar_state:get(AppState, {deps, Profile}, []),
TopLevelProfDeps = rebar_state:get(StateAcc, {deps, Profile}, []),
ProfDeps2 = rebar_utils:tup_dedup(rebar_utils:tup_umerge(
rebar_utils:tup_sort(TopLevelProfDeps)
,rebar_utils:tup_sort(AppProfDeps))),
rebar_state:set(StateAcc, {deps, Profile}, ProfDeps2)
handle_profile(Profile, Name, AppState1, StateAcc)
end, State, lists:reverse(CurrentProfiles)),
{AppInfo1, State1}.
handle_profile(Profile, Name, AppState, State) ->
{TopSrc, TopPkg} = rebar_state:get(State, {parsed_deps, Profile}, {[], []}),
TopLevelProfileDeps = rebar_state:get(State, {deps, Profile}, []),
AppProfileDeps = rebar_state:get(AppState, {deps, Profile}, []),
AppProfileDeps2 = rebar_utils:tup_dedup(rebar_utils:tup_sort(AppProfileDeps)),
ProfileDeps2 = rebar_utils:tup_dedup(rebar_utils:tup_umerge(
rebar_utils:tup_sort(TopLevelProfileDeps)
,rebar_utils:tup_sort(AppProfileDeps2))),
State1 = rebar_state:set(State, {deps, Profile}, ProfileDeps2),
%% Only deps not also specified in the top level config need
%% to be included in the parsed deps
NewDeps = ProfileDeps2 -- TopLevelProfileDeps,
{ParsedSrc, ParsedPkg} = parse_profile_deps(Profile, Name, NewDeps, AppState, State1),
rebar_state:set(State1, {parsed_deps, Profile}, {TopSrc++ParsedSrc, TopPkg++ParsedPkg}).
parse_profile_deps(Profile, Name, Deps, AppState, State) ->
DepsDir = rebar_prv_install_deps:profile_dep_dir(State, Profile),
Locks = rebar_state:get(State, {locks, Profile}, []),
rebar_prv_install_deps:parse_deps(Name
,DepsDir
,Deps
,AppState
,Locks
,1).
project_app_config(AppInfo, State) ->
C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
Dir = rebar_app_info:dir(AppInfo),

+ 84
- 52
src/rebar_digraph.erl Просмотреть файл

@ -3,6 +3,7 @@
-export([compile_order/1
,restore_graph/1
,cull_deps/2
,cull_deps/3
,subgraph/2
,format_error/1]).
@ -72,8 +73,12 @@ restore_graph({Vs, Es}) ->
%% The first dep while traversing the graph is chosen and any conflicting
%% dep encountered later on is ignored.
cull_deps(Graph, Vertices) ->
{Solution, Levels} = build_initial_dicts(Vertices),
cull_deps(Graph, Vertices, Levels, Solution, []).
cull_deps(Graph, Vertices, sets:new()).
cull_deps(Graph, Vertices, Seen) ->
Vertices1 = lists:keysort(2, Vertices),
{Solution, Levels, Discarded} = {dict:new(), dict:new(), []},
cull_deps(Graph, Vertices1, Levels, Solution, Seen, Discarded).
format_error(no_solution) ->
io_lib:format("No solution for packages found.", []).
@ -82,79 +87,106 @@ format_error(no_solution) ->
%% Internal Functions
%%====================================================================
cull_deps(_Graph, [], Levels, Solution, Discarded) ->
cull_deps(_Graph, [], Levels, Solution, _, Discarded) ->
{_, Vertices} = lists:unzip(dict:to_list(Solution)),
LvlVertices = [{Profile, {Parent,App,Vsn,dict:fetch(App,Levels)}} || {Profile, {Parent,App,Vsn}} <- Vertices],
LvlVertices = [{Profile, {Parent, App, Vsn, dict:fetch(App, Levels)}}
|| {Profile, {Parent,App,Vsn}} <- Vertices],
{ok, LvlVertices, Discarded};
cull_deps(Graph, [{Profile, Level, Vs} | Vertices], Levels, Solution, Discarded) ->
cull_deps(Graph, [{Profile, Level, Vs} | Vertices], Levels, Solution, Seen, Discarded) ->
{NV, NS, LS, DS} =
lists:foldl(fun({_, Name, Vsn}, {Acc, SolutionAcc, LevelsAcc, DiscardedAcc}) ->
OutNeighbors = lists:keysort(1, digraph:out_neighbours(Graph, {Name,Vsn})),
handle_neighbors(Profile, Level, Name
,OutNeighbors, Acc, SolutionAcc
,LevelsAcc, DiscardedAcc)
end, {[], Solution, Levels, Discarded}, lists:keysort(2, Vs)),
cull_deps(Graph, Vertices++NV, LS, NS, DS).
lists:foldl(fun({Parent, Name, Vsn}, {Acc, SolutionAcc, LevelsAcc, DiscardedAcc}) ->
{SolutionAcc1, LevelsAcc1, DiscardedAcc1} =
maybe_add_to_solution(Profile, Level, Name, {Name, Vsn}, Parent
,SolutionAcc
,LevelsAcc, Seen, DiscardedAcc),
OutNeighbors = digraph:out_neighbours(Graph, {Name,Vsn}),
{NewVertices, DiscardedAcc2} = handle_neighbors(Profile, Level, Name
,OutNeighbors, Acc, SolutionAcc1
,Seen, DiscardedAcc1),
{NewVertices, SolutionAcc1, LevelsAcc1, DiscardedAcc2}
end, {[], Solution, Levels, Discarded}, Vs),
NewVertices = combine_profile_levels(Vertices, NV),
cull_deps(Graph, NewVertices, LS, NS, Seen, DS).
%% Combine lists of deps that have the same profile and level
combine_profile_levels(Vertices, NewVertices) ->
V = lists:foldl(fun({Profile, Level, Vs}, Acc) ->
case ec_lists:find(fun({P, L, _}) ->
P =:= Profile andalso L =:= Level
end, Acc) of
{ok, {_, _, OldVs}=Old} ->
lists:delete(Old, Acc)++[{Profile, Level, lists:keysort(1, OldVs++Vs)}];
error ->
Acc++[{Profile, Level, Vs}]
end
end, Vertices, NewVertices),
lists:keysort(2, V).
%% For each outgoing edge of a dep check if it should be added to the solution
%% and add it to the list of vertices to do the same for
handle_neighbors(Profile, Level, Parent, OutNeighbors, Vertices
,Solution, Levels, Discarded) ->
case lists:foldl(fun({Name, _}=N, {NewVertices, Solution1, Levels1, Discarded1}) ->
maybe_add_to_solution(Profile, Level, Name, N, Parent
,NewVertices, Solution1
,Levels1, Discarded1)
end, {[], Solution, Levels, Discarded}, OutNeighbors) of
{[], SolutionAcc2, LevelsAcc2, DiscardedAcc2} ->
{Vertices, SolutionAcc2, LevelsAcc2, DiscardedAcc2};
{NewVertices1, SolutionAcc2, LevelsAcc2, DiscardedAcc2} ->
{Vertices++[{Profile, Level+1, NewVertices1}]
,SolutionAcc2, LevelsAcc2, DiscardedAcc2}
,Solution, Seen, Discarded) ->
case lists:foldl(fun({Name, Vsn}=Value, {NewVertices, Discarded1}) ->
case dict:find(Name, Solution) of
{ok, {Profile, {Parent, Name, Vsn}}} -> % already seen
{NewVertices,
Discarded1};
{ok, _} -> % conflict resolution!
%% Warn on different version
{NewVertices,
[Value|Discarded1]};
error ->
%% We check Seen separately because we don't care
%% to warn if the exact same version of a package
%% was already part of the solution but we do
%% if it was simply seen in source deps
case sets:is_element(Name, Seen) of
true ->
{NewVertices,
[Value|Discarded1]};
false ->
{[{Parent, Name, Vsn} | NewVertices],
Discarded1}
end
end
end, {[], Discarded}, OutNeighbors) of
{[], DiscardedAcc2} ->
{Vertices, DiscardedAcc2};
{NewVertices1, DiscardedAcc2} ->
{Vertices++[{Profile, Level+1, NewVertices1}] ,DiscardedAcc2}
end.
maybe_add_to_solution(Profile, Level, Key, {Name, Vsn}=Value, Parent
,Vertices ,Solution, Levels, Discarded) ->
,Solution, Levels, Seen, Discarded) ->
case dict:find(Key, Solution) of
{ok, {Profile, {Parent, Name, Vsn}}} -> % already seen
{Vertices,
Solution,
{Solution,
Levels,
Discarded};
{ok, _} -> % conflict resolution!
{Vertices,
Solution,
%% Warn on different version
class="p">{Solution,
Levels,
[Value|Discarded]};
error ->
{[{Parent, Name, Vsn} | Vertices],
dict:store(Key, {Profile, {Parent, Name, Vsn}}, Solution),
dict:store(Key, Level+1, Levels),
Discarded}
%% We check Seen separately because we don't care to warn if the exact
%% same version of a package was already part of the solution but we do
%% if it was simply seen in source deps
case sets:is_element(Name, Seen) of
true ->
{Solution,
Levels,
[Value|Discarded]};
false ->
{dict:store(Key, {Profile, {Parent, Name, Vsn}}, Solution),
dict:store(Key, Level, Levels),
Discarded}
end
end.
subgraph(Graph, Vertices) ->
digraph_utils:subgraph(Graph, Vertices).
maybe_add_to_dict(Key, Value, Dict) ->
case dict:is_key(Key, Dict) of
true ->
Dict;
false ->
dict:store(Key, Value, Dict)
end.
%% Track the profile (so we know where to install it), name/vsn of each dep
%% and the level it is from (for the lock file)
build_initial_dicts(Vertices) ->
lists:foldl(fun({Profile, Level, Vs}, {Solution, Levels}) ->
lists:foldl(fun({Parent, Key, Vsn}, {SAcc, LAcc}) ->
{maybe_add_to_dict(Key, {Profile, {Parent,Key,Vsn}}, SAcc),
maybe_add_to_dict(Key, Level, LAcc)}
end, {Solution, Levels}, Vs)
end, {dict:new(), dict:new()}, Vertices).
-spec names_to_apps([atom()], [rebar_app_info:t()]) -> [rebar_app_info:t()].
names_to_apps(Names, Apps) ->
[element(2, App) || App <- [find_app_by_name(Name, Apps) || Name <- Names], App =/= error].

+ 4
- 4
src/rebar_prv_common_test.erl Просмотреть файл

@ -361,10 +361,10 @@ remove_links(Path) ->
end.
delete_dir_link(Path) ->
case os:type() of
{unix, _} -> file:delete(Path);
{win32, _} -> file:del_dir(Path)
end.
case os:type() of
{unix, _} -> file:delete(Path);
{win32, _} -> file:del_dir(Path)
end.
dir_entries(Path) ->
{ok, SubDirs} = file:list_dir(Path),

+ 9
- 4
src/rebar_prv_deps_tree.erl Просмотреть файл

@ -29,7 +29,7 @@ init(State) ->
do(State) ->
{Args, _} = rebar_state:command_parsed_args(State),
Verbose = proplists:get_value(verbose, Args, false),
print_deps_tree(rebar_state:all_deps(State), Verbose),
print_deps_tree(rebar_state:all_deps(State), Verbose, State),
{ok, State}.
-spec format_error(any()) -> iolist().
@ -38,7 +38,7 @@ format_error(Reason) ->
%% Internal functions
print_deps_tree(SrcDeps, Verbose) ->
print_deps_tree(SrcDeps, Verbose, State) ->
D = lists:foldl(fun(App, Dict) ->
Name = rebar_app_info:name(App),
Vsn = rebar_app_info:original_vsn(App),
@ -46,11 +46,14 @@ print_deps_tree(SrcDeps, Verbose) ->
Parent = rebar_app_info:parent(App),
dict:append_list(Parent, [{Name, Vsn, Source}], Dict)
end, dict:new(), SrcDeps),
ProjectAppNames = [{rebar_app_info:name(App)
,rebar_app_info:original_vsn(App)
,project} || App <- rebar_state:project_apps(State)],
case dict:find(root, D) of
{ok, Children} ->
print_children(-1, lists:keysort(1, Children), D, Verbose);
print_children(-1, lists:keysort(1, Children++ProjectAppNames), D, Verbose);
error ->
none
print_children(-n>1, lists:keysort(1, ProjectAppNames), D, Verbose)
end.
print_children(_, [], _, _) ->
@ -68,6 +71,8 @@ print_children(Indent, [{Name, Vsn, Source} | Rest], Dict, Verbose) ->
print_children(Indent, Rest, Dict, Verbose)
end.
type(project, _) ->
"project app";
type(Source, Verbose) when is_tuple(Source) ->
case {element(1, Source), Verbose} of
{pkg, _} ->

+ 10
- 7
src/rebar_prv_install_deps.erl Просмотреть файл

@ -37,6 +37,8 @@
-export([handle_deps_as_profile/4,
parse_deps/5,
parse_deps/6,
profile_dep_dir/2,
find_cycles/1,
cull_compile/2]).
@ -155,10 +157,8 @@ deps_per_profile(Profiles, Upgrade, State) ->
handle_profile_pkg_level(PkgDeps1, AllApps, Seen, Upgrade, Locks, State1).
parse_profile_deps(State, Profile, Level) ->
DepsDir = profile_dep_dir(State, Profile),
Locks = rebar_state:get(State, {locks, Profile}, []),
Deps = rebar_state:get(State, {deps, Profile}, []),
{SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, State, Locks, Level),
{SrcDeps, PkgDeps} = rebar_state:get(State, {parsed_deps, Profile}, {[], []}),
{{Profile, SrcDeps, Locks, Level}, {Profile, Level, PkgDeps}}.
%% Level-order traversal of all dependencies, across profiles.
@ -190,9 +190,12 @@ handle_profile_pkg_level(PkgDeps, AllApps, Seen, Upgrade, Locks, State) ->
State1 = rebar_state:packages(rebar_state:registry(State, Registry)
,{Packages, Graph}),
S = case rebar_digraph:cull_deps(Graph, lists:keysort(2, PkgDeps)) of
{ok, [], _} ->
S = case rebar_digraph:cull_deps(Graph, lists:keysort(2, PkgDeps), Seen) of
{ok, [], []} ->
throw({rebar_digraph, no_solution});
{ok, [], Discarded} ->
[warn_skip_pkg(Pkg, State) || Pkg <- Discarded, not(pkg_locked(Pkg, Locks))],
[];
{ok, Solution, []} ->
Solution;
{ok, Solution, Discarded} ->
@ -376,9 +379,9 @@ handle_dep(State, Profile, DepsDir, AppInfo, Locks, Level) ->
Profiles = rebar_state:current_profiles(State),
Name = rebar_app_info:name(AppInfo),
C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
%% Deps may be under a sub project app, find it and use its state if so
S = rebar_app_info:state(AppInfo),
C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
S1 = rebar_state:new(S, C, rebar_app_info:dir(AppInfo)),
S2 = rebar_state:apply_overrides(S1, Name),

+ 10
- 7
src/rebar_prv_upgrade.erl Просмотреть файл

@ -53,18 +53,21 @@ do(State) ->
{Locks0, _Unlocks0} ->
Deps0 = top_level_deps(Deps, Locks),
State1 = rebar_state:set(State, {deps, default}, Deps0),
State2 = rebar_state:set(State1, {locks, default}, Locks0),
State3 = rebar_state:set(State2, upgrade, true),
UpdatedLocks = [L || L <- rebar_state:lock(State3),
DepsDir = rebar_prv_install_deps:profile_dep_dir(State, default),
D = rebar_prv_install_deps:parse_deps(root, DepsDir, Deps0, State1, Locks0, 0),
State2 = rebar_state:set(State1, {parsed_deps, default}, D),
State3 = rebar_state:set(State2, {locks, default}, Locks0),
State4 = rebar_state:set(State3, upgrade, true),
UpdatedLocks = [L || L <- rebar_state:lock(State4),
lists:keymember(rebar_app_info:name(L), 1, Locks0)],
Res = rebar_prv_install_deps:do(rebar_state:lock(State3, UpdatedLocks)),
Res = rebar_prv_install_deps:do(rebar_state:lock(State4, UpdatedLocks)),
case Res of
{ok, State4} ->
{ok, State5} ->
rebar_utils:info_useless(
[element(1,Lock) || Lock <- Locks],
[rebar_app_info:name(App) || App <- rebar_state:lock(State4)]
[rebar_app_info:name(App) || App <- rebar_state:lock(State5)]
),
rebar_prv_lock:do(State4);
rebar_prv_lock:do(State5);
_ ->
Res
end

+ 4
- 3
test/mock_git_resource.erl Просмотреть файл

@ -20,7 +20,8 @@ mock() -> mock([]).
| {override_vsn, [{App, Vsn}]}
| {deps, [{App, [Dep]}]},
App :: string(),
Dep :: {App, string(), {git, string()} | {git, string(), term()}},
Dep :: {App, string(), {git, string()} | {git, string(), term()}}
| {pkg, App, term()},
Vsn :: string().
mock(Opts) ->
meck:new(?MOD, [no_link]),
@ -46,8 +47,8 @@ mock_lock(_) ->
case Git of
{git, Url, {tag, Ref}} -> {git, Url, {ref, Ref}};
{git, Url, {ref, Ref}} -> {git, Url, {ref, Ref}};
{git, Url} -> {git, Url, {ref, "fake-ref"}};
{git, Url, _} -> {git, Url, {ref, "fake-ref"}}
{git, Url} -> {git, Url, {ref, "0.0.0"}};
{git, Url, _} -> {git, Url, {ref, "0.0.0"}}
end
end).

+ 2
- 1
test/rebar_compile_SUITE.erl Просмотреть файл

@ -544,7 +544,8 @@ only_default_transitive_deps(Config) ->
GitDeps = rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}]),
PkgName = rebar_test_utils:create_random_name("pkg1_"),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(GitDeps)},
{SrcDeps, _} = rebar_test_utils:flat_deps(GitDeps),
mock_git_resource:mock([{deps, SrcDeps},
{config, [{profiles, [{test, [{deps, [list_to_atom(PkgName)]}]}]}]}]),
mock_pkg_resource:mock([{pkgdeps, [{{iolist_to_binary(PkgName), <<"0.1.0">>}, []}]}]),

+ 7
- 24
test/rebar_deps_SUITE.erl Просмотреть файл

@ -171,30 +171,11 @@ setup_project(Case, Config0, Deps) ->
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
TopDeps = rebar_test_utils:top_level_deps(Deps),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
case DepsType of
git ->
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]);
pkg ->
mock_pkg_resource:mock([{pkgdeps, flat_pkgdeps(Deps)}])
end,
{SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
[{rebarconfig, RebarConf} | Config].
flat_pkgdeps([]) -> [];
flat_pkgdeps([{{pkg, Name, Vsn}, Deps} | Rest]) ->
[{{iolist_to_binary(Name),iolist_to_binary(Vsn)}, rebar_test_utils:top_level_deps(Deps)}]
++
flat_pkgdeps(Deps)
++
flat_pkgdeps(Rest).
app_vsn([]) -> [];
app_vsn([{Source, Deps} | Rest]) ->
{Name, Vsn} = case Source of
{pkg, N, V} -> {N,V};
{N,V,_Ref} -> {N,V}
end,
[{Name, Vsn}] ++ app_vsn(Deps) ++ app_vsn(Rest).
mock_warnings() ->
%% just let it do its thing, we check warnings through
%% the call log.
@ -217,7 +198,8 @@ sub_app_deps(Config) ->
Deps = rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}
,{"b", "1.0.0", []}
,{"b", "2.0.0", []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]),
{SrcDeps, _} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
Name = rebar_test_utils:create_random_name("sub_app1_"),
Vsn = rebar_test_utils:create_random_vsn(),
@ -241,7 +223,8 @@ newly_added_dep(Config) ->
Deps = rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}
,{"b", "1.0.0", [{"c", "1.0.0", []}]}
,{"c", "2.0.0", []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]),
{SrcDeps, _} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
Name = rebar_test_utils:create_random_name("app_"),
Vsn = rebar_test_utils:create_random_vsn(),

+ 169
- 34
test/rebar_install_deps_SUITE.erl Просмотреть файл

@ -4,16 +4,23 @@
-include_lib("eunit/include/eunit.hrl").
-include_lib("kernel/include/file.hrl").
all() -> [{group, git}, {group, pkg}].
all() -> [{group, git}, {group, pkg}, {group, mixed}].
groups() ->
[{all, [], [flat, pick_highest_left, pick_highest_right,
pick_smallest1, pick_smallest2,
circular1, circular2, circular_skip,
fail_conflict, default_profile, nondefault_profile,
nondefault_pick_highest]},
{git, [], [{group, all}]},
{pkg, [], [{group, all}]}].
[{unique, [], [flat, pick_highest_left, pick_highest_right,
pick_smallest1, pick_smallest2,
circular1, circular2, circular_skip,
fail_conflict, default_profile, nondefault_profile,
nondefault_pick_highest]},
{git, [], [{group, unique}]},
{pkg, [], [{group, unique}]},
{mixed, [], [
m_flat1, m_flat2, m_circular1, m_circular2, m_circular3,
m_pick_source1, m_pick_source2, m_pick_source3,
m_pick_source4, m_pick_source5, m_source_to_pkg,
m_pkg_level1, m_pkg_level2, m_pkg_level3, m_pkg_level3_alpha_order
]}
].
init_per_suite(Config) ->
application:start(meck),
@ -26,19 +33,33 @@ init_per_group(git, Config) ->
[{deps_type, git} | Config];
init_per_group(pkg, Config) ->
[{deps_type, pkg} | Config];
init_per_group(mixed, Config) ->
[{deps_type, mixed} | Config];
init_per_group(_, Config) ->
Config.
end_per_group(_, Config) ->
Config.
init_per_testcase(Case, Config) ->
init_per_testcase(Case, Config) when is_atom(Case) ->
DepsType = ?config(deps_type, Config),
init_per_testcase({DepsType, Case}, Config);
init_per_testcase({mixed, Case}, Config) ->
{Deps, Warnings, Expect} = mdeps(Case),
Expected = case Expect of
{ok, List} -> {ok, format_expected_mdeps(List)};
Other -> Other
end,
mock_warnings(),
[{expect, Expected},
{warnings, format_expected_mixed_warnings(Warnings)}
| setup_project(Case, Config, rebar_test_utils:expand_deps(mixed, Deps))];
init_per_testcase({DepsType, Case}, Config) ->
{Deps, Warnings, Expect} = deps(Case),
Expected = case Expect of
{ok, List} -> {ok, format_expected_deps(List)};
Other -> Other
end,
DepsType = ?config(deps_type, Config),
mock_warnings(),
[{expect, Expected},
{warnings, Warnings}
@ -54,6 +75,32 @@ format_expected_deps(Deps) ->
N -> [{dep, N}, {lock, N}]
end || Dep <- Deps]).
format_expected_mdeps(Deps) ->
%% for mixed deps, lowercase is a package, uppercase is source.
%% We can't check which was used from the dep, but the lock contains
%% the type and we can use that information.
lists:append([
case Dep of
{N,V} when hd(N) >= $a, hd(N) =< $z ->
UN = string:to_upper(N),
[{dep, UN, V}, {lock, pkg, UN, V}];
{N,V} when hd(N) >= $A, hd(N) =< $Z ->
[{dep, N, V}, {lock, src, N, V}];
N when hd(N) >= $a, hd(N) =< $z ->
UN = string:to_upper(N),
[{dep, UN}, {lock, pkg, UN, "0.0.0"}];
N when hd(N) >= $A, hd(N) =< $Z ->
[{dep, N}, {lock, src, N, "0.0.0"}]
end || Dep <- Deps]).
format_expected_mixed_warnings(Warnings) ->
[case W of
{N, Vsn} when hd(N) >= $a, hd(N) =< $z -> {pkg, string:to_upper(N), Vsn};
{N, Vsn} when hd(N) >= $A, hd(N) =< $Z -> {src, N, Vsn};
N when hd(N) >= $a, hd(N) =< $z -> {pkg, string:to_upper(N), "0.0.0"};
N when hd(N) >= $A, hd(N) =< $Z -> {src, N, "0.0.0"}
end || W <- Warnings].
%% format:
%% {Spec,
%% [Warning],
@ -131,6 +178,85 @@ deps(nondefault_pick_highest) ->
%% This is all handled in setup_project
{[],[],{ok,[]}}.
%% format:
%% Same as `deps/1' except "A" is a source dep
%% and "a" is a package dep.
mdeps(m_flat1) ->
{[{"c", []},
{"B", []}],
[],
{ok, ["B","c"]}};
mdeps(m_flat2) ->
{[{"B", []},
{"c", []}],
[],
{ok, ["B","c"]}};
mdeps(m_circular1) ->
{[{"b", [{"a",[]}]}], % "A" is the top app
[],
{error, {rebar_prv_install_deps, {cycles, [[<<"A">>,<<"B">>]]}}}};
mdeps(m_circular2) ->
{[{"B", [{"c", [{"b", []}]}]}],
[],
{error, {rebar_prv_install_deps, {cycles, [[<<"B">>,<<"C">>]]}}}};
mdeps(m_circular3) ->
%% Spot the circular dep due to being to low in the deps tree
%% but as a source dep, taking precedence over packages
{[{"B", [{"C", "2", [{"B", []}]}]},
{"c", "1", [{"d",[]}]}],
[],
{error, {rebar_prv_install_deps, {cycles, [[<<"B">>,<<"C">>]]}}}};
mdeps(m_pick_source1) ->
{[{"B", [{"D", []}]},
{"c", [{"d", []}]}],
["d"],
{ok, ["B", "c", "D"]}};
mdeps(m_pick_source2) ->
{[{"b", [{"d", []}]},
{"C", [{"D", []}]}],
["d"],
{ok, ["b", "C", "D"]}};
mdeps(m_pick_source3) ->
%% The order of declaration is important.
{[{"b", []},
{"B", []}],
[],
{ok, ["b"]}};
mdeps(m_pick_source4) ->
{[{"B", []},
{"b", []}],
[],
{ok, ["B"]}};
mdeps(m_pick_source5) ->
{[{"B", [{"d", []}]},
{"C", [{"D", []}]}],
["d"],
{ok, ["B", "C", "D"]}};
mdeps(m_source_to_pkg) ->
{[{"B", [{"c",[{"d", []}]}]}],
[],
{ok, ["B", "c", "d"]}};
mdeps(m_pkg_level1) ->
{[{"B", [{"D", [{"e", "2", []}]}]},
{"C", [{"e", "1", []}]}],
[{"e","2"}],
{ok, ["B","C","D",{"e","1"}]}};
mdeps(m_pkg_level2) ->
{[{"B", [{"e", "1", []}]},
{"C", [{"D", [{"e", "2", []}]}]}],
[{"e","2"}],
{ok, ["B","C","D",{"e","1"}]}};
mdeps(m_pkg_level3_alpha_order) ->
{[{"B", [{"d", [{"f", "1", []}]}]},
{"C", [{"E", [{"f", "2", []}]}]}],
[{"f","2"}],
{ok, ["B","C","d","E",{"f","1"}]}};
mdeps(m_pkg_level3) ->
{[{"B", [{"d", [{"f", "1", []}]}]},
{"C", [{"E", [{"G", [{"f", "2", []}]}]}]}],
[{"f","2"}],
{ok, ["B","C","d","E","G",{"f","1"}]}}.
setup_project(fail_conflict, Config0, Deps) ->
DepsType = ?config(deps_type, Config0),
Config = rebar_test_utils:init_rebar_state(
@ -142,12 +268,9 @@ setup_project(fail_conflict, Config0, Deps) ->
TopDeps = rebar_test_utils:top_level_deps(Deps),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps},
{deps_error_on_conflict, true}]),
case DepsType of
git ->
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]);
pkg ->
mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}])
end,
{SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
[{rebarconfig, RebarConf} | Config];
setup_project(nondefault_profile, Config0, Deps) ->
DepsType = ?config(deps_type, Config0),
@ -161,12 +284,9 @@ setup_project(nondefault_profile, Config0, Deps) ->
RebarConf = rebar_test_utils:create_config(AppDir, [{profiles, [
{nondef, [{deps, TopDeps}]}
]}]),
case DepsType of
git ->
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]);
pkg ->
mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}])
end,
{SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
[{rebarconfig, RebarConf} | Config];
setup_project(nondefault_pick_highest, Config0, _) ->
DepsType = ?config(deps_type, Config0),
@ -187,13 +307,11 @@ setup_project(nondefault_pick_highest, Config0, _) ->
),
case DepsType of
git ->
mock_git_resource:mock(
[{deps, rebar_test_utils:flat_deps(DefaultDeps ++ ProfileDeps)}]
);
{SrcDeps, _} = rebar_test_utils:flat_deps(DefaultDeps++ProfileDeps),
mock_git_resource:mock([{deps, SrcDeps}]);
pkg ->
mock_pkg_resource:mock(
[{pkgdeps, rebar_test_utils:flat_pkgdeps(DefaultDeps ++ ProfileDeps)}]
)
{_, PkgDeps} = rebar_test_utils:flat_deps(DefaultDeps++ProfileDeps),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}])
end,
[{rebarconfig, RebarConf} | Config];
setup_project(Case, Config0, Deps) ->
@ -206,12 +324,9 @@ setup_project(Case, Config0, Deps) ->
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
TopDeps = rebar_test_utils:top_level_deps(Deps),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
case DepsType of
git ->
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]);
pkg ->
mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}])
end,
{SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
[{rebarconfig, RebarConf} | Config].
mock_warnings() ->
@ -319,6 +434,22 @@ nondefault_pick_highest(Config) ->
{ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "2"}], "nondef"}
).
m_flat1(Config) -> run(Config).
m_flat2(Config) -> run(Config).
m_circular1(Config) -> run(Config).
m_circular2(Config) -> run(Config).
m_circular3(Config) -> run(Config).
m_pick_source1(Config) -> run(Config).
m_pick_source2(Config) -> run(Config).
m_pick_source3(Config) -> run(Config).
m_pick_source4(Config) -> run(Config).
m_pick_source5(Config) -> run(Config).
m_source_to_pkg(Config) -> run(Config).
m_pkg_level1(Config) -> run(Config).
m_pkg_level2(Config) -> run(Config).
m_pkg_level3(Config) -> run(Config).
m_pkg_level3_alpha_order(Config) -> run(Config).
run(Config) ->
{ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
rebar_test_utils:run_and_check(
@ -336,6 +467,10 @@ error_calls() ->
check_warnings(_, [], _) ->
ok;
check_warnings(Warns, [{Type, Name, Vsn} | Rest], mixed) ->
ct:pal("Checking for warning ~p in ~p", [{Name,Vsn},Warns]),
?assert(in_warnings(Type, Warns, Name, Vsn)),
check_warnings(Warns, Rest, mixed);
check_warnings(Warns, [{Name, Vsn} | Rest], Type) ->
ct:pal("Checking for warning ~p in ~p", [{Name,Vsn},Warns]),
?assert(in_warnings(Type, Warns, Name, Vsn)),

+ 4
- 2
test/rebar_plugins_SUITE.erl Просмотреть файл

@ -45,7 +45,8 @@ compile_plugins(Config) ->
PluginName = rebar_test_utils:create_random_name("plugin1_"),
Plugins = rebar_test_utils:expand_deps(git, [{PluginName, Vsn, []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Plugins)}]),
{SrcDeps, _} = rebar_test_utils:flat_deps(Plugins),
mock_git_resource:mock([{deps, SrcDeps}]),
mock_pkg_resource:mock([{pkgdeps, [{{list_to_binary(DepName), list_to_binary(Vsn)}, []}]},
{config, [{plugins, [
@ -137,7 +138,8 @@ complex_plugins(Config) ->
Deps = rebar_test_utils:expand_deps(git, [{PluginName, Vsn2, [{DepName2, Vsn,
[{DepName3, Vsn, []}]}]}
,{DepName, Vsn, []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]),
{SrcDeps, _} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
RConfFile =
rebar_test_utils:create_config(AppDir,

+ 10
- 5
test/rebar_profiles_SUITE.erl Просмотреть файл

@ -57,7 +57,8 @@ profile_new_key(Config) ->
AllDeps = rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}
,{"b", "1.0.0", []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(AllDeps)}]),
{SrcDeps, []} = rebar_test_utils:flat_deps(AllDeps),
mock_git_resource:mock([{deps, SrcDeps}]),
Name = rebar_test_utils:create_random_name("profile_new_key_"),
Vsn = rebar_test_utils:create_random_vsn(),
@ -82,7 +83,8 @@ profile_merge_keys(Config) ->
AllDeps = rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}
,{"b", "1.0.0", []}
,{"b", "2.0.0", []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(AllDeps)}]),
{SrcDeps, []} = rebar_test_utils:flat_deps(AllDeps),
mock_git_resource:mock([{deps, SrcDeps}]),
Name = rebar_test_utils:create_random_name("profile_new_key_"),
Vsn = rebar_test_utils:create_random_vsn(),
@ -111,7 +113,8 @@ explicit_profile_deduplicate_deps(Config) ->
,{"a", "2.0.0", []}
,{"b", "1.0.0", []}
,{"b", "2.0.0", []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(AllDeps)}]),
{SrcDeps, []} = rebar_test_utils:flat_deps(AllDeps),
mock_git_resource:mock([{deps, SrcDeps}]),
Name = rebar_test_utils:create_random_name("explicit_profile_deduplicate_deps_"),
Vsn = rebar_test_utils:create_random_vsn(),
@ -141,7 +144,8 @@ implicit_profile_deduplicate_deps(Config) ->
,{"a", "2.0.0", []}
,{"b", "1.0.0", []}
,{"b", "2.0.0", []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(AllDeps)}]),
{SrcDeps, []} = rebar_test_utils:flat_deps(AllDeps),
mock_git_resource:mock([{deps, SrcDeps}]),
Name = rebar_test_utils:create_random_name("implicit_profile_deduplicate_deps_"),
Vsn = rebar_test_utils:create_random_vsn(),
@ -169,7 +173,8 @@ all_deps_code_paths(Config) ->
AllDeps = rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}
,{"b", "2.0.0", []}]),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(AllDeps)}]),
{SrcDeps, []} = rebar_test_utils:flat_deps(AllDeps),
mock_git_resource:mock([{deps, SrcDeps}]),
Name = rebar_test_utils:create_random_name("all_deps_code_paths"),
Vsn = rebar_test_utils:create_random_vsn(),

+ 60
- 19
test/rebar_test_utils.erl Просмотреть файл

@ -2,7 +2,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-export([init_rebar_state/1, init_rebar_state/2, run_and_check/4]).
-export([expand_deps/2, flat_deps/1, flat_pkgdeps/1, top_level_deps/1]).
-export([expand_deps/2, flat_deps/1, top_level_deps/1]).
-export([create_app/4, create_eunit_app/4, create_empty_app/4, create_config/2]).
-export([create_random_name/1, create_random_vsn/0, write_src_file/2]).
@ -137,24 +137,43 @@ expand_deps(pkg, [{Name, Deps} | Rest]) ->
[{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)].
flat_deps([]) -> [];
flat_deps([{{Name,_Vsn,Ref}, Deps} | Rest]) ->
[{{Name,vsn_from_ref(Ref)}, top_level_deps(Deps)}]
++
flat_deps(Deps)
++
flat_deps(Rest).
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).
[{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)];
expand_deps(mixed, [{Name, Deps} | Rest]) ->
Dep = if hd(Name) >= $a, hd(Name) =< $z ->
{pkg, string:to_upper(Name), "0.0.0"}
; hd(Name) >= $A, hd(Name) =< $Z ->
{Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}}
end,
[{Dep, expand_deps(mixed, Deps)} | expand_deps(mixed, Rest)];
expand_deps(mixed, [{Name, Vsn, Deps} | Rest]) ->
Dep = if hd(Name) >= $a, hd(Name) =< $z ->
{pkg, string:to_upper(Name), Vsn}
; hd(Name) >= $A, hd(Name) =< $Z ->
{Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}}
end,
[{Dep, expand_deps(mixed, Deps)} | expand_deps(mixed, Rest)].
%% Source deps can depend on both source and package dependencies;
%% package deps can only depend on package deps.
%% For things to work we have to go down the dep tree and find all
%% lineages of pkg deps and return them, whereas the source deps
%% can be left as is.
flat_deps(Deps) -> flat_deps(Deps, [], []).
flat_deps([], Src, Pkg) -> {Src, Pkg};
flat_deps([{{pkg, Name, Vsn}, PkgDeps} | Rest], Src, Pkg) ->
Current = {{iolist_to_binary(Name), iolist_to_binary(Vsn)},
top_level_deps(PkgDeps)},
{[], FlatPkgDeps} = flat_deps(PkgDeps),
flat_deps(Rest,
Src,
Pkg ++ [Current | FlatPkgDeps]);
flat_deps([{{Name,_Vsn,Ref}, Deps} | Rest], Src, Pkg) ->
Current = {{Name,vsn_from_ref(Ref)}, top_level_deps(Deps)},
{FlatDeps, FlatPkgDeps} = flat_deps(Deps),
flat_deps(Rest,
Src ++ [Current | FlatDeps],
Pkg ++ FlatPkgDeps).
vsn_from_ref({git, _, {_, Vsn}}) -> Vsn;
vsn_from_ref({git, _, Vsn}) -> Vsn.
@ -278,6 +297,28 @@ check_results(AppDir, Expected, ProfileRun) ->
?assertEqual(iolist_to_binary(Vsn),
iolist_to_binary(LockVsn))
end
; ({lock, pkg, Name, Vsn}) ->
ct:pal("Pkg Lock Name: ~p, Vsn: ~p", [Name, Vsn]),
case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
false ->
error({lock_not_found, Name});
{_LockName, {pkg, _, LockVsn}, _} ->
?assertEqual(iolist_to_binary(Vsn),
iolist_to_binary(LockVsn));
{_LockName, {_, _, {ref, LockVsn}}, _} ->
error({source_lock, {Name, LockVsn}})
end
; ({lock, src, Name, Vsn}) ->
ct:pal("Src Lock Name: ~p, Vsn: ~p", [Name, Vsn]),
case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
false ->
error({lock_not_found, Name});
{_LockName, {pkg, _, LockVsn}, _} ->
error({pkg_lock, {Name, LockVsn}});
{_LockName, {_, _, {ref, LockVsn}}, _} ->
?assertEqual(iolist_to_binary(Vsn),
iolist_to_binary(LockVsn))
end
; ({release, Name, Vsn, ExpectedDevMode}) ->
ct:pal("Release: ~p-~s", [Name, Vsn]),
{ok, Cwd} = file:get_cwd(),

+ 8
- 4
test/rebar_upgrade_SUITE.erl Просмотреть файл

@ -425,17 +425,21 @@ upgrades(compile_upgrade_parity) ->
mock_deps(git, Deps, Upgrades) ->
catch mock_git_resource:unmock(),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}, {upgrade, Upgrades}]);
{SrcDeps, _} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}, {upgrade, Upgrades}]);
mock_deps(pkg, Deps, Upgrades) ->
catch mock_pkg_resource:unmock(),
mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}, {upgrade, Upgrades}]).
{_, PkgDeps} = rebar_test_utils:flat_deps(Deps),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}, {upgrade, Upgrades}]).
mock_deps(git, OldDeps, Deps, Upgrades) ->
catch mock_git_resource:unmock(),
mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps++OldDeps)}, {upgrade, Upgrades}]);
{SrcDeps, _} = rebar_test_utils:flat_deps(Deps++OldDeps),
mock_git_resource:mock([{deps, SrcDeps}, {upgrade, Upgrades}]);
mock_deps(pkg, OldDeps, Deps, Upgrades) ->
catch mock_pkg_resource:unmock(),
mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps++OldDeps)}, {upgrade, Upgrades}]).
{_, PkgDeps} = rebar_test_utils:flat_deps(Deps++OldDeps),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}, {upgrade, Upgrades}]).
normalize_unlocks({App, Locks}) ->
{iolist_to_binary(App),

Загрузка…
Отмена
Сохранить