Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

193 rader
7.4 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. -include("rebar.hrl").
  6. %%%%%%%%%%%%%%%%%
  7. %%% Interface %%%
  8. %%%%%%%%%%%%%%%%%
  9. %% @doc same as `mock([])'.
  10. mock() -> mock([]).
  11. %% @doc Mocks a fake version of the git resource fetcher that creates
  12. %% empty applications magically, rather than trying to download them.
  13. %% Specific config options are explained in each of the private functions.
  14. -spec mock(Opts) -> ok when
  15. Opts :: [Option],
  16. Option :: {upgrade, [App]}
  17. | {cache_dir, string()}
  18. | {default_vsn, Vsn}
  19. | {override_vsn, [{App, Vsn}]}
  20. | {not_in_index, [{App, Vsn}]}
  21. | {pkgdeps, [{{App,Vsn}, [Dep]}]},
  22. App :: string(),
  23. Dep :: {App, string(), {pkg, App, Vsn, Hash}},
  24. Vsn :: string(),
  25. Hash :: string() | undefined.
  26. mock(Opts) ->
  27. meck:new(?MOD, [no_link, passthrough]),
  28. mock_lock(Opts),
  29. mock_update(Opts),
  30. mock_vsn(Opts),
  31. mock_download(Opts),
  32. mock_pkg_index(Opts),
  33. ok.
  34. unmock() ->
  35. meck:unload(?MOD),
  36. meck:unload(rebar_packages).
  37. %%%%%%%%%%%%%%%
  38. %%% Private %%%
  39. %%%%%%%%%%%%%%%
  40. %% @doc creates values for a lock file.
  41. mock_lock(_) ->
  42. meck:expect(?MOD, lock, fun(AppInfo, _) ->
  43. {pkg, Name, Vsn, Hash, _RepoConfig} = rebar_app_info:source(AppInfo),
  44. {pkg, Name, Vsn, Hash}
  45. end).
  46. %% @doc The config passed to the `mock/2' function can specify which apps
  47. %% should be updated on a per-name basis: `{update, ["App1", "App3"]}'.
  48. mock_update(Opts) ->
  49. ToUpdate = proplists:get_value(upgrade, Opts, []),
  50. meck:expect(
  51. ?MOD, needs_update,
  52. fun(AppInfo, _) ->
  53. {pkg, App, _Vsn, _Hash, _} = rebar_app_info:source(AppInfo),
  54. lists:member(binary_to_list(App), ToUpdate)
  55. end).
  56. %% @doc Replicated an unsupported call.
  57. mock_vsn(_Opts) ->
  58. meck:expect(
  59. ?MOD, make_vsn,
  60. fun(_AppInfo, _) ->
  61. {error, "Replacing version of type pkg not supported."}
  62. end).
  63. %% @doc For each app to download, create a dummy app on disk instead.
  64. %% The configuration for this one (passed in from `mock/1') includes:
  65. %%
  66. %% - Specify a version with `{pkg, _, Vsn, _}'
  67. %% - Dependencies for each application must be passed of the form:
  68. %% `{pkgdeps, [{"app1", [{app2, ".*", {pkg, ...}}]}]}' -- basically
  69. %% the `pkgdeps' option takes a key/value list of terms to output directly
  70. %% into a `rebar.config' file to describe dependencies.
  71. mock_download(Opts) ->
  72. Deps = proplists:get_value(pkgdeps, Opts, []),
  73. Config = proplists:get_value(config, Opts, []),
  74. meck:expect(
  75. ?MOD, download,
  76. fun (Dir, AppInfo, _, _) ->
  77. {pkg, AppBin, Vsn, _, _} = rebar_app_info:source(AppInfo),
  78. App = rebar_utils:to_list(AppBin),
  79. filelib:ensure_dir(Dir),
  80. AppDeps = proplists:get_value({App,Vsn}, Deps, []),
  81. {ok, AppInfo1} = rebar_test_utils:create_app(
  82. Dir, App, rebar_utils:to_list(Vsn),
  83. [kernel, stdlib] ++ [element(1,D) || D <- AppDeps]
  84. ),
  85. rebar_test_utils:create_config(Dir, [{deps, AppDeps}]++Config),
  86. TarApp = App++"-"++rebar_utils:to_list(Vsn)++".tar",
  87. Tarball = filename:join([Dir, TarApp]),
  88. Contents = filename:join([Dir, "contents.tar.gz"]),
  89. Files = all_files(rebar_app_info:dir(AppInfo1)),
  90. ok = erl_tar:create(Contents,
  91. archive_names(Dir, App, Vsn, Files),
  92. [compressed]),
  93. ok = erl_tar:create(Tarball,
  94. [{"contents.tar.gz", Contents}],
  95. []),
  96. Cache = proplists:get_value(cache_dir, Opts, filename:join(Dir,"cache")),
  97. Cached = filename:join([Cache, TarApp]),
  98. filelib:ensure_dir(Cached),
  99. rebar_file_utils:mv(Tarball, Cached),
  100. ok
  101. end).
  102. %% @doc On top of the pkg resource mocking, we need to mock the package
  103. %% index.
  104. %%
  105. %% A special option, `{not_in_index, [App]}' lets the index leave out
  106. %% specific applications otherwise listed.
  107. mock_pkg_index(Opts) ->
  108. Deps = proplists:get_value(pkgdeps, Opts, []),
  109. Repos = proplists:get_value(repos, Opts, [<<"hexpm">>]),
  110. Skip = proplists:get_value(not_in_index, Opts, []),
  111. %% Dict: {App, Vsn}: [{<<"link">>, <<>>}, {<<"deps">>, []}]
  112. %% Index: all apps and deps in the index
  113. Dict = find_parts(Deps, Skip),
  114. to_index(Deps, Dict, Repos),
  115. meck:new(rebar_packages, [passthrough, no_link]),
  116. meck:expect(rebar_packages, update_package,
  117. fun(_, _, _State) -> ok end),
  118. meck:expect(rebar_packages, verify_table,
  119. fun(_State) -> true end).
  120. %%%%%%%%%%%%%%%
  121. %%% Helpers %%%
  122. %%%%%%%%%%%%%%%
  123. all_files(Dir) ->
  124. filelib:wildcard(filename:join([Dir, "**"])).
  125. archive_names(Dir, _App, _Vsn, Files) ->
  126. [{(F -- Dir) -- "/", F} || F <- Files].
  127. find_parts(Apps, Skip) -> find_parts(Apps, Skip, dict:new()).
  128. find_parts([], _, Acc) -> Acc;
  129. find_parts([{AppName, Deps}|Rest], Skip, Acc) ->
  130. case lists:member(AppName, Skip) orelse dict:is_key(AppName,Acc) of
  131. true -> find_parts(Rest, Skip, Acc);
  132. false ->
  133. AccNew = dict:store(AppName,
  134. Deps,
  135. Acc),
  136. find_parts(Rest, Skip, AccNew)
  137. end.
  138. parse_deps(Deps) ->
  139. [{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}} || D=#{package := Name,
  140. requirement := Constraint} <- Deps].
  141. to_index(AllDeps, Dict, Repos) ->
  142. catch ets:delete(?PACKAGE_TABLE),
  143. rebar_packages:new_package_table(),
  144. dict:fold(
  145. fun({N, V}, Deps, _) ->
  146. DepsList = [#{package => DKB,
  147. app => DKB,
  148. requirement => DVB,
  149. source => {pkg, DKB, DVB, undefined}}
  150. || {DK, DV} <- Deps,
  151. DKB <- [ec_cnv:to_binary(DK)],
  152. DVB <- [ec_cnv:to_binary(DV)]],
  153. Repo = rebar_test_utils:random_element(Repos),
  154. ets:insert(?PACKAGE_TABLE, #package{key={N, V, Repo},
  155. dependencies=parse_deps(DepsList),
  156. retired=false,
  157. checksum = <<"checksum">>})
  158. end, ok, Dict),
  159. lists:foreach(fun({{Name, Vsn}, _}) ->
  160. case lists:any(fun(R) ->
  161. ets:member(?PACKAGE_TABLE, {ec_cnv:to_binary(Name), Vsn, R})
  162. end, Repos) of
  163. false ->
  164. Repo = rebar_test_utils:random_element(Repos),
  165. ets:insert(?PACKAGE_TABLE, #package{key={ec_cnv:to_binary(Name), Vsn, Repo},
  166. dependencies=[],
  167. retired=false,
  168. checksum = <<"checksum">>});
  169. true ->
  170. ok
  171. end
  172. end, AllDeps).