Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

146 рядки
5.0 KiB

  1. %%% Mock a package resource and create an app magically for each URL submitted.
  2. -module(mock_pkg_resource).
  3. -export([mock/0, mock/1, unmock/0]).
  4. -define(MOD, rebar_pkg_resource).
  5. %%%%%%%%%%%%%%%%%
  6. %%% Interface %%%
  7. %%%%%%%%%%%%%%%%%
  8. %% @doc same as `mock([])'.
  9. mock() -> mock([]).
  10. %% @doc Mocks a fake version of the git resource fetcher that creates
  11. %% empty applications magically, rather than trying to download them.
  12. %% Specific config options are explained in each of the private functions.
  13. -spec mock(Opts) -> ok when
  14. Opts :: [Option],
  15. Option :: {update, [App]}
  16. | {default_vsn, Vsn}
  17. | {override_vsn, [{App, Vsn}]}
  18. | {not_in_index, [{App, Vsn}]}
  19. | {pkgdeps, [{{App,Vsn}, [Dep]}]},
  20. App :: string(),
  21. Dep :: {App, string(), {pkg, App, Vsn}},
  22. Vsn :: string().
  23. mock(Opts) ->
  24. meck:new(?MOD, [no_link]),
  25. mock_lock(Opts),
  26. mock_update(Opts),
  27. mock_vsn(Opts),
  28. mock_download(Opts),
  29. mock_pkg_index(Opts),
  30. ok.
  31. unmock() ->
  32. meck:unload(?MOD),
  33. meck:unload(rebar_packages).
  34. %%%%%%%%%%%%%%%
  35. %%% Private %%%
  36. %%%%%%%%%%%%%%%
  37. %% @doc creates values for a lock file.
  38. mock_lock(_) ->
  39. meck:expect(?MOD, lock, fun(_AppDir, Source) -> Source end).
  40. %% @doc The config passed to the `mock/2' function can specify which apps
  41. %% should be updated on a per-name basis: `{update, ["App1", "App3"]}'.
  42. mock_update(Opts) ->
  43. ToUpdate = proplists:get_value(update, Opts, []),
  44. meck:expect(
  45. ?MOD, needs_update,
  46. fun(_Dir, {pkg, App, _Vsn}) ->
  47. lists:member(App, ToUpdate)
  48. end).
  49. %% @doc Replicated an unsupported call.
  50. mock_vsn(_Opts) ->
  51. meck:expect(
  52. ?MOD, make_vsn,
  53. fun(_Dir) ->
  54. {error, "Replacing version of type pkg not supported."}
  55. end).
  56. %% @doc For each app to download, create a dummy app on disk instead.
  57. %% The configuration for this one (passed in from `mock/1') includes:
  58. %%
  59. %% - Specify a version with `{pkg, _, Vsn}'
  60. %% - Dependencies for each application must be passed of the form:
  61. %% `{pkgdeps, [{"app1", [{app2, ".*", {pkg, ...}}]}]}' -- basically
  62. %% the `pkgdeps' option takes a key/value list of terms to output directly
  63. %% into a `rebar.config' file to describe dependencies.
  64. mock_download(Opts) ->
  65. Deps = proplists:get_value(pkgdeps, Opts, []),
  66. meck:expect(
  67. ?MOD, download,
  68. fun (Dir, {pkg, AppBin, Vsn}, _) ->
  69. App = binary_to_list(AppBin),
  70. filelib:ensure_dir(Dir),
  71. AppDeps = proplists:get_value({App,Vsn}, Deps, []),
  72. {ok, AppInfo} = rebar_test_utils:create_empty_app(
  73. Dir, App, Vsn,
  74. [element(1,D) || D <- AppDeps]
  75. ),
  76. rebar_test_utils:create_config(Dir, [{deps, AppDeps}]),
  77. Tarball = filename:join([Dir, App++"-"++binary_to_list(Vsn)++".tar"]),
  78. Contents = filename:join([Dir, "contents.tar.gz"]),
  79. Files = all_files(rebar_app_info:dir(AppInfo)),
  80. ok = erl_tar:create(Contents,
  81. archive_names(Dir, App, Vsn, Files),
  82. [compressed]),
  83. ok = erl_tar:create(Tarball,
  84. [{"contents.tar.gz", Contents}],
  85. []),
  86. [file:delete(F) || F <- Files],
  87. {tarball, Tarball}
  88. end).
  89. %% @doc On top of the pkg resource mocking, we need to mock the package
  90. %% index.
  91. %%
  92. %% A special option, `{not_in_index, [App]}' lets the index leave out
  93. %% specific applications otherwise listed.
  94. mock_pkg_index(Opts) ->
  95. Deps = proplists:get_value(pkgdeps, Opts, []),
  96. Skip = proplists:get_value(not_in_index, Opts, []),
  97. %% Dict: {App, Vsn}: [{<<"link">>, <<>>}, {<<"deps">>, []}]
  98. %% Digraph: all apps and deps in the index
  99. Dict = find_parts(Deps, Skip),
  100. GraphParts = to_graph_parts(Dict),
  101. Digraph = rebar_digraph:restore_graph(GraphParts),
  102. meck:new(rebar_packages, [passthrough, no_link]),
  103. meck:expect(rebar_packages, get_packages,
  104. fun(_State) -> {Dict, Digraph} end).
  105. %%%%%%%%%%%%%%%
  106. %%% Helpers %%%
  107. %%%%%%%%%%%%%%%
  108. all_files(Dir) ->
  109. filelib:wildcard(filename:join([Dir, "**"])).
  110. archive_names(Dir, App, Vsn, Files) ->
  111. [{(F -- Dir) -- "/", F} || F <- Files].
  112. find_parts(Apps, Skip) -> find_parts(Apps, Skip, dict:new()).
  113. find_parts([], _, Acc) -> Acc;
  114. find_parts([{AppName, Deps}|Rest], Skip, Acc) ->
  115. case lists:member(AppName, Skip) orelse dict:is_key(AppName,Acc) of
  116. true -> find_parts(Rest, Skip, Acc);
  117. false ->
  118. AccNew = dict:store(AppName,
  119. [{<<"deps">>,Deps}, {<<"link">>,<<"undef">>}],
  120. Acc),
  121. find_parts(Rest, Skip, AccNew)
  122. end.
  123. to_graph_parts(Dict) ->
  124. LastUpdated = now(),
  125. dict:fold(fun(K,V,{Ks,Vs}) ->
  126. {_,Deps} = lists:keyfind(<<"deps">>, 1, V),
  127. {[{K,LastUpdated}|Ks],
  128. [{K,{list_to_binary(atom_to_list(DK)), list_to_binary(DV)}}
  129. || {DK,DV} <- Deps] ++ Vs}
  130. end, {[],[]}, Dict).