summaryrefslogtreecommitdiff
path: root/test/mock_pkg_resource.erl
diff options
context:
space:
mode:
Diffstat (limited to 'test/mock_pkg_resource.erl')
-rw-r--r--test/mock_pkg_resource.erl143
1 files changed, 143 insertions, 0 deletions
diff --git a/test/mock_pkg_resource.erl b/test/mock_pkg_resource.erl
new file mode 100644
index 0000000..502e184
--- /dev/null
+++ b/test/mock_pkg_resource.erl
@@ -0,0 +1,143 @@
+%%% Mock a package resource and create an app magically for each URL submitted.
+-module(mock_pkg_resource).
+-export([mock/0, mock/1, unmock/0]).
+-define(MOD, rebar_pkg_resource).
+
+%%%%%%%%%%%%%%%%%
+%%% Interface %%%
+%%%%%%%%%%%%%%%%%
+
+%% @doc same as `mock([])'.
+mock() -> mock([]).
+
+%% @doc Mocks a fake version of the git resource fetcher that creates
+%% empty applications magically, rather than trying to download them.
+%% Specific config options are explained in each of the private functions.
+-spec mock(Opts) -> ok when
+ Opts :: [Option],
+ Option :: {update, [App]}
+ | {default_vsn, Vsn}
+ | {override_vsn, [{App, Vsn}]}
+ | {not_in_index, [{App, Vsn}]}
+ | {pkgdeps, [{{App,Vsn}, [Dep]}]},
+ App :: string(),
+ Dep :: {App, string(), {pkg, App, Vsn, Url::string()}},
+ Vsn :: string().
+mock(Opts) ->
+ meck:new(?MOD, [no_link]),
+ mock_lock(Opts),
+ mock_update(Opts),
+ mock_vsn(Opts),
+ mock_download(Opts),
+ mock_pkg_index(Opts),
+ ok.
+
+unmock() ->
+ meck:unload(?MOD),
+ meck:unload(rebar_packages).
+
+%%%%%%%%%%%%%%%
+%%% Private %%%
+%%%%%%%%%%%%%%%
+
+%% @doc creates values for a lock file.
+mock_lock(_) ->
+ meck:expect(?MOD, lock, fun(_AppDir, Source) -> Source end).
+
+%% @doc The config passed to the `mock/2' function can specify which apps
+%% should be updated on a per-name basis: `{update, ["App1", "App3"]}'.
+mock_update(Opts) ->
+ ToUpdate = proplists:get_value(update, Opts, []),
+ meck:expect(
+ ?MOD, needs_update,
+ fun(_Dir, {pkg, App, _Vsn, _Url}) ->
+ lists:member(App, ToUpdate)
+ end).
+
+%% @doc Replicated an unsupported call.
+mock_vsn(_Opts) ->
+ meck:expect(
+ ?MOD, make_vsn,
+ fun(_Dir) ->
+ {error, "Replacing version of type pkg not supported."}
+ end).
+
+%% @doc For each app to download, create a dummy app on disk instead.
+%% The configuration for this one (passed in from `mock/1') includes:
+%%
+%% - Specify a version with `{pkg, _, Vsn, _}'
+%% - Dependencies for each application must be passed of the form:
+%% `{pkgdeps, [{"app1", [{app2, ".*", {pkg, ...}}]}]}' -- basically
+%% the `pkgdeps' option takes a key/value list of terms to output directly
+%% into a `rebar.config' file to describe dependencies.
+mock_download(Opts) ->
+ Deps = proplists:get_value(pkgdeps, Opts, []),
+ meck:expect(
+ ?MOD, download,
+ fun (Dir, {pkg, AppBin, Vsn, _Url}) ->
+ App = binary_to_list(AppBin),
+ filelib:ensure_dir(Dir),
+ AppDeps = proplists:get_value({App,Vsn}, Deps, []),
+ {ok, AppInfo} = rebar_test_utils:create_app(
+ Dir, App, Vsn,
+ [element(1,D) || D <- AppDeps]
+ ),
+ rebar_test_utils:create_config(Dir, [{deps, AppDeps}]),
+ Tarball = filename:join([Dir, "package.tar.gz"]),
+ Files = all_files(rebar_app_info:dir(AppInfo)),
+ ok = erl_tar:create(Tarball,
+ archive_names(Dir, App, Vsn, Files),
+ [compressed]),
+ [file:delete(F) || F <- Files],
+ {tarball, Tarball}
+ end).
+
+%% @doc On top of the pkg resource mocking, we need to mock the package
+%% index.
+%%
+%% A special option, `{not_in_index, [App]}' lets the index leave out
+%% specific applications otherwise listed.
+mock_pkg_index(Opts) ->
+ Deps = proplists:get_value(pkgdeps, Opts, []),
+ Skip = proplists:get_value(not_in_index, Opts, []),
+ %% Dict: {App, Vsn}: [{<<"link">>, <<>>}, {<<"deps">>, []}]
+ %% Digraph: all apps and deps in the index
+ Dict = find_parts(Deps, Skip),
+ GraphParts = to_graph_parts(Dict),
+ Digraph = rebar_digraph:restore_graph(GraphParts),
+ meck:new(rebar_packages, [passthrough, no_link]),
+ meck:expect(rebar_packages, get_packages,
+ fun(_State) -> {Dict, Digraph} end).
+
+
+%%%%%%%%%%%%%%%
+%%% Helpers %%%
+%%%%%%%%%%%%%%%
+all_files(Dir) ->
+ filelib:wildcard(filename:join([Dir, "**"])).
+
+archive_names(Dir, App, Vsn, Files) ->
+ ArchName = App++"-"++binary_to_list(Vsn),
+ [{ArchName ++ (F -- Dir), F} || F <- Files].
+
+find_parts(Apps, Skip) -> find_parts(Apps, Skip, dict:new()).
+
+find_parts([], _, Acc) -> Acc;
+find_parts([{AppName, Deps}|Rest], Skip, Acc) ->
+ case lists:member(AppName, Skip) orelse dict:is_key(AppName,Acc) of
+ true -> find_parts(Rest, Skip, Acc);
+ false ->
+ AccNew = dict:store(AppName,
+ [{<<"deps">>,Deps}, {<<"link">>,<<"undef">>}],
+ Acc),
+ find_parts(Rest, Skip, AccNew)
+ end.
+
+to_graph_parts(Dict) ->
+ LastUpdated = now(),
+ dict:fold(fun(K,V,{Ks,Vs}) ->
+ {_,Deps} = lists:keyfind(<<"deps">>, 1, V),
+ {[{K,LastUpdated}|Ks],
+ [{K,{list_to_binary(atom_to_list(DK)), list_to_binary(DV)}}
+ || {DK,DV} <- Deps] ++ Vs}
+ end, {[],[]}, Dict).