您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

277 行
12 KiB

  1. -module(rebar_test_utils).
  2. -include_lib("common_test/include/ct.hrl").
  3. -include_lib("eunit/include/eunit.hrl").
  4. -export([init_rebar_state/1, init_rebar_state/2, run_and_check/4]).
  5. -export([expand_deps/2, flat_deps/1, top_level_deps/1]).
  6. -export([create_app/4, create_empty_app/4, create_config/2]).
  7. -export([create_random_name/1, create_random_vsn/0]).
  8. %%%%%%%%%%%%%%
  9. %%% Public %%%
  10. %%%%%%%%%%%%%%
  11. %% @doc {@see init_rebar_state/2}
  12. init_rebar_state(Config) -> init_rebar_state(Config, "apps_dir1_").
  13. %% @doc Takes a common test config and a name (string) and sets up
  14. %% a basic OTP app directory with a pre-configured rebar state to
  15. %% run tests with.
  16. init_rebar_state(Config, Name) ->
  17. application:load(rebar),
  18. DataDir = ?config(priv_dir, Config),
  19. AppsDir = filename:join([DataDir, create_random_name(Name)]),
  20. CheckoutsDir = filename:join([AppsDir, "_checkouts"]),
  21. ok = ec_file:mkdir_p(AppsDir),
  22. ok = ec_file:mkdir_p(CheckoutsDir),
  23. Verbosity = rebar3:log_level(),
  24. rebar_log:init(command_line, Verbosity),
  25. State = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])}]),
  26. [{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, State} | Config].
  27. %% @doc Takes common test config, a rebar config ([] if empty), a command to
  28. %% run ("install_deps", "compile", etc.), and a list of expected applications
  29. %% and/or dependencies to be present, and verifies whether they are all in
  30. %% place.
  31. %%
  32. %% The expectation list takes elements of the form:
  33. %% - `{app, Name :: string()}': checks that the app is properly built.
  34. %% - `{dep, Name :: string()}': checks that the dependency has been fetched.
  35. %% Ignores the build status of the dependency.
  36. %% - `{dep, Name :: string(), Vsn :: string()}': checks that the dependency
  37. %% has been fetched, and that a given version has been chosen. Useful to
  38. %% test for conflict resolution. Also ignores the build status of the
  39. %% dependency.
  40. %%
  41. %% This function assumes `init_rebar_state/1-2' has run before, in order to
  42. %% fetch the `apps' and `state' values from the CT config.
  43. run_and_check(Config, RebarConfig, Command, Expect) ->
  44. %% Assumes init_rebar_state has run first
  45. AppDir = ?config(apps, Config),
  46. State = ?config(state, Config),
  47. Res = rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command),
  48. case Expect of
  49. {error, Reason} ->
  50. ?assertEqual({error, Reason}, Res);
  51. {ok, Expected} ->
  52. {ok, _} = Res,
  53. check_results(AppDir, Expected);
  54. return ->
  55. Res
  56. end.
  57. %% @doc Creates a dummy application including:
  58. %% - src/<file>.erl
  59. %% - src/<file>.app.src
  60. %% And returns a `rebar_app_info' object.
  61. create_app(AppDir, Name, Vsn, Deps) ->
  62. write_src_file(AppDir, Name),
  63. write_test_file(AppDir, Name),
  64. write_app_src_file(AppDir, Name, Vsn, Deps),
  65. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  66. %% @doc Creates a dummy application including:
  67. %% - ebin/<file>.app
  68. %% And returns a `rebar_app_info' object.
  69. create_empty_app(AppDir, Name, Vsn, Deps) ->
  70. write_app_file(AppDir, Name, Vsn, Deps),
  71. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  72. %% @doc Creates a rebar.config file. The function accepts a list of terms,
  73. %% each of which will be dumped as a consult file. For example, the list
  74. %% `[a, b, c]' will return the consult file `a. b. c.'.
  75. create_config(AppDir, Contents) ->
  76. Conf = filename:join([AppDir, "rebar.config"]),
  77. ok = filelib:ensure_dir(Conf),
  78. Config = lists:flatten([io_lib:fwrite("~p.~n", [Term]) || Term <- Contents]),
  79. ok = ec_file:write(Conf, Config),
  80. Conf.
  81. %% @doc Util to create a random variation of a given name.
  82. create_random_name(Name) ->
  83. random:seed(erlang:now()),
  84. Name ++ erlang:integer_to_list(random:uniform(1000000)).
  85. %% @doc Util to create a random variation of a given version.
  86. create_random_vsn() ->
  87. random:seed(erlang:now()),
  88. lists:flatten([erlang:integer_to_list(random:uniform(100)),
  89. ".", erlang:integer_to_list(random:uniform(100)),
  90. ".", erlang:integer_to_list(random:uniform(100))]).
  91. expand_deps(_, []) -> [];
  92. expand_deps(git, [{Name, Deps} | Rest]) ->
  93. Dep = {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}},
  94. [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
  95. expand_deps(git, [{Name, Vsn, Deps} | Rest]) ->
  96. Dep = {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}},
  97. [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
  98. expand_deps(pkg, [{Name, Deps} | Rest]) ->
  99. Dep = {pkg, Name, "0.0.0"},
  100. [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)];
  101. expand_deps(pkg, [{Name, Vsn, Deps} | Rest]) ->
  102. Dep = {pkg, Name, Vsn},
  103. [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)].
  104. flat_deps([]) -> [];
  105. flat_deps([{{Name,_Vsn,Ref}, Deps} | Rest]) ->
  106. [{{Name,vsn_from_ref(Ref)}, top_level_deps(Deps)}]
  107. ++
  108. flat_deps(Deps)
  109. ++
  110. flat_deps(Rest).
  111. vsn_from_ref({git, _, {_, Vsn}}) -> Vsn;
  112. vsn_from_ref({git, _, Vsn}) -> Vsn.
  113. top_level_deps([]) -> [];
  114. top_level_deps([{{pkg, Name, Vsn}, _} | Deps]) ->
  115. [{list_to_atom(Name), Vsn} | top_level_deps(Deps)];
  116. top_level_deps([{{Name, Vsn, Ref}, _} | Deps]) ->
  117. [{list_to_atom(Name), Vsn, Ref} | top_level_deps(Deps)].
  118. %%%%%%%%%%%%%%%
  119. %%% Helpers %%%
  120. %%%%%%%%%%%%%%%
  121. check_results(AppDir, Expected) ->
  122. BuildDir = filename:join([AppDir, "_build", "lib"]),
  123. CheckoutsDir = filename:join([AppDir, "_checkouts"]),
  124. LockFile = filename:join([AppDir, "rebar.lock"]),
  125. Locks = lists:flatten(rebar_config:consult_file(LockFile)),
  126. Apps = rebar_app_discover:find_apps([AppDir]),
  127. InvalidApps = rebar_app_discover:find_apps([AppDir], invalid),
  128. ValidApps = rebar_app_discover:find_apps([AppDir], valid),
  129. AppsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Apps],
  130. InvalidAppsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- InvalidApps],
  131. ValidAppsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- ValidApps],
  132. Deps = rebar_app_discover:find_apps([BuildDir], all),
  133. DepsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Deps],
  134. Checkouts = rebar_app_discover:find_apps([CheckoutsDir], all),
  135. CheckoutsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Checkouts],
  136. lists:foreach(
  137. fun({app, Name}) ->
  138. ct:pal("Name: ~p", [Name]),
  139. case lists:keyfind(Name, 1, AppsNames) of
  140. false ->
  141. error({app_not_found, Name});
  142. {Name, _App} ->
  143. ok
  144. end
  145. ; ({app, Name, invalid}) ->
  146. ct:pal("Name: ~p", [Name]),
  147. case lists:keyfind(Name, 1, InvalidAppsNames) of
  148. false ->
  149. error({app_not_found, Name});
  150. {Name, _App} ->
  151. ok
  152. end
  153. ; ({app, Name, valid}) ->
  154. ct:pal("Name: ~p", [Name]),
  155. case lists:keyfind(Name, 1, ValidAppsNames) of
  156. false ->
  157. error({app_not_found, Name});
  158. {Name, _App} ->
  159. ok
  160. end
  161. ; ({checkout, Name}) ->
  162. ct:pal("Name: ~p", [Name]),
  163. ?assertNotEqual(false, lists:keyfind(Name, 1, CheckoutsNames))
  164. ; ({dep, Name}) ->
  165. ct:pal("Name: ~p", [Name]),
  166. ?assertNotEqual(false, lists:keyfind(Name, 1, DepsNames))
  167. ; ({dep, Name, Vsn}) ->
  168. ct:pal("Name: ~p, Vsn: ~p", [Name, Vsn]),
  169. case lists:keyfind(Name, 1, DepsNames) of
  170. false ->
  171. error({dep_not_found, Name});
  172. {Name, App} ->
  173. ?assertEqual(iolist_to_binary(Vsn),
  174. iolist_to_binary(rebar_app_info:original_vsn(App)))
  175. end
  176. ; ({lock, Name}) ->
  177. ct:pal("Name: ~p", [Name]),
  178. ?assertNotEqual(false, lists:keyfind(iolist_to_binary(Name), 1, Locks))
  179. ; ({lock, Name, Vsn}) ->
  180. ct:pal("Name: ~p, Vsn: ~p", [Name, Vsn]),
  181. case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
  182. false ->
  183. error({lock_not_found, Name});
  184. {_LockName, {_, _, {ref, LockVsn}}, _} ->
  185. ?assertEqual(iolist_to_binary(Vsn),
  186. iolist_to_binary(LockVsn))
  187. end
  188. ; ({release, Name, Vsn}) ->
  189. ct:pal("Release: ~p-~s", [Name, Vsn]),
  190. {ok, Cwd} = file:get_cwd(),
  191. try
  192. file:set_cwd(AppDir),
  193. ReleaseDir = filename:join([AppDir, "_build", "rel"]),
  194. RelxState = rlx_state:new("", [], []),
  195. RelxState1 = rlx_state:base_output_dir(RelxState, ReleaseDir),
  196. {ok, RelxState2} = rlx_prv_app_discover:do(RelxState1),
  197. {ok, RelxState3} = rlx_prv_rel_discover:do(RelxState2),
  198. %% throws not_found if it doesn't exist
  199. rlx_state:get_realized_release(RelxState3, Name, Vsn)
  200. catch
  201. _ ->
  202. ct:fail(release_not_found)
  203. after
  204. file:set_cwd(Cwd)
  205. end
  206. ; ({tar, Name, Vsn}) ->
  207. ct:pal("Tarball: ~s-~s", [Name, Vsn]),
  208. Tarball = filename:join([AppDir, "_build", "rel", Name, Name++"-"++Vsn++".tar.gz"]),
  209. ?assertNotEqual([], filelib:is_file(Tarball))
  210. end, Expected).
  211. write_src_file(Dir, Name) ->
  212. Erl = filename:join([Dir, "src", "not_a_real_src_" ++ Name ++ ".erl"]),
  213. ok = filelib:ensure_dir(Erl),
  214. ok = ec_file:write(Erl, erl_src_file("not_a_real_src_" ++ Name ++ ".erl")).
  215. write_test_file(Dir, Name) ->
  216. Erl = filename:join([Dir, "test", "not_a_real_src_" ++ Name ++ "_tests.erl"]),
  217. ok = filelib:ensure_dir(Erl),
  218. ok = ec_file:write(Erl, erl_test_file("not_a_real_src_" ++ Name ++ ".erl")).
  219. write_app_file(Dir, Name, Version, Deps) ->
  220. Filename = filename:join([Dir, "ebin", Name ++ ".app"]),
  221. ok = filelib:ensure_dir(Filename),
  222. ok = ec_file:write_term(Filename, get_app_metadata(ec_cnv:to_list(Name), Version, Deps)).
  223. write_app_src_file(Dir, Name, Version, Deps) ->
  224. Filename = filename:join([Dir, "src", Name ++ ".app.src"]),
  225. ok = filelib:ensure_dir(Filename),
  226. ok = ec_file:write_term(Filename, get_app_metadata(ec_cnv:to_list(Name), Version, Deps)).
  227. erl_src_file(Name) ->
  228. io_lib:format("-module(~s).\n"
  229. "-export([main/0]).\n"
  230. "main() -> ok.\n"
  231. "-ifdef(TEST).\n"
  232. "-include_lib(\"eunit/include/eunit.hrl\").\n"
  233. "some_test_() -> ?_assertEqual(ok, main()).\n"
  234. "-endif.\n", [filename:basename(Name, ".erl")]).
  235. erl_test_file(Name) ->
  236. BaseName = filename:basename(Name, ".erl"),
  237. io_lib:format("-module(~s_tests).\n"
  238. "-compile(export_all).\n"
  239. "-ifndef(some_define).\n"
  240. "-define(some_define, false).\n"
  241. "-endif.\n"
  242. "-ifdef(TEST).\n"
  243. "-include_lib(\"eunit/include/eunit.hrl\").\n"
  244. "some_test_() -> ?_assertEqual(ok, ~s:main()).\n"
  245. "define_test_() -> ?_assertEqual(true, ?some_define).\n"
  246. "-endif.\n", [BaseName, BaseName]).
  247. get_app_metadata(Name, Vsn, Deps) ->
  248. {application, erlang:list_to_atom(Name),
  249. [{description, ""},
  250. {vsn, Vsn},
  251. {modules, []},
  252. {included_applications, []},
  253. {registered, []},
  254. {applications, Deps}]}.