選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

378 行
16 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, flat_pkgdeps/1, top_level_deps/1]).
  6. -export([create_app/4, create_eunit_app/4, create_empty_app/4, create_config/2]).
  7. -export([create_random_name/1, create_random_vsn/0, write_src_file/2]).
  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. ,{root_dir, AppsDir}]),
  27. [{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, State} | Config].
  28. %% @doc Takes common test config, a rebar config ([] if empty), a command to
  29. %% run ("install_deps", "compile", etc.), and a list of expected applications
  30. %% and/or dependencies to be present, and verifies whether they are all in
  31. %% place.
  32. %%
  33. %% The expectation list takes elements of the form:
  34. %% - `{app, Name :: string()}': checks that the app is properly built.
  35. %% - `{dep, Name :: string()}': checks that the dependency has been fetched.
  36. %% Ignores the build status of the dependency.
  37. %% - `{dep, Name :: string(), Vsn :: string()}': checks that the dependency
  38. %% has been fetched, and that a given version has been chosen. Useful to
  39. %% test for conflict resolution. Also ignores the build status of the
  40. %% dependency.
  41. %%
  42. %% This function assumes `init_rebar_state/1-2' has run before, in order to
  43. %% fetch the `apps' and `state' values from the CT config.
  44. run_and_check(Config, RebarConfig, Command, Expect) ->
  45. %% Assumes init_rebar_state has run first
  46. AppDir = ?config(apps, Config),
  47. State = ?config(state, Config),
  48. try
  49. Res = rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command),
  50. case Expect of
  51. {error, Reason} ->
  52. ?assertEqual({error, Reason}, Res);
  53. {ok, Expected} ->
  54. {ok, _} = Res,
  55. check_results(AppDir, Expected),
  56. Res;
  57. return ->
  58. Res
  59. end
  60. catch
  61. rebar_abort when Expect =:= rebar_abort -> rebar_abort
  62. end.
  63. %% @doc Creates a dummy application including:
  64. %% - src/<file>.erl
  65. %% - src/<file>.app.src
  66. %% And returns a `rebar_app_info' object.
  67. create_app(AppDir, Name, Vsn, Deps) ->
  68. write_src_file(AppDir, Name ++ ".erl"),
  69. write_src_file(AppDir, "not_a_real_src_" ++ Name ++ ".erl"),
  70. write_app_src_file(AppDir, Name, Vsn, Deps),
  71. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  72. %% @doc Creates a dummy application including:
  73. %% - src/<file>.erl
  74. %% - src/<file>.app.src
  75. %% - test/<file>_tests.erl
  76. %% And returns a `rebar_app_info' object.
  77. create_eunit_app(AppDir, Name, Vsn, Deps) ->
  78. write_eunitized_src_file(AppDir, Name),
  79. write_eunit_suite_file(AppDir, Name),
  80. write_app_src_file(AppDir, Name, Vsn, Deps),
  81. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  82. %% @doc Creates a dummy application including:
  83. %% - ebin/<file>.app
  84. %% And returns a `rebar_app_info' object.
  85. create_empty_app(AppDir, Name, Vsn, Deps) ->
  86. write_app_file(AppDir, Name, Vsn, Deps),
  87. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  88. %% @doc Creates a rebar.config file. The function accepts a list of terms,
  89. %% each of which will be dumped as a consult file. For example, the list
  90. %% `[a, b, c]' will return the consult file `a. b. c.'.
  91. create_config(AppDir, Contents) ->
  92. Conf = filename:join([AppDir, "rebar.config"]),
  93. ok = filelib:ensure_dir(Conf),
  94. Config = lists:flatten([io_lib:fwrite("~p.~n", [Term]) || Term <- Contents]),
  95. ok = ec_file:write(Conf, Config),
  96. Conf.
  97. %% @doc Util to create a random variation of a given name.
  98. create_random_name(Name) ->
  99. <<A:32, B:32, C:32>> = crypto:rand_bytes(12),
  100. random:seed({A,B,C}),
  101. Name ++ erlang:integer_to_list(random:uniform(1000000)).
  102. %% @doc Util to create a random variation of a given version.
  103. create_random_vsn() ->
  104. random:seed(erlang:now()),
  105. lists:flatten([erlang:integer_to_list(random:uniform(100)),
  106. ".", erlang:integer_to_list(random:uniform(100)),
  107. ".", erlang:integer_to_list(random:uniform(100))]).
  108. expand_deps(_, []) -> [];
  109. expand_deps(git, [{Name, Deps} | Rest]) ->
  110. Dep = {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}},
  111. [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
  112. expand_deps(git, [{Name, Vsn, Deps} | Rest]) ->
  113. Dep = {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}},
  114. [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
  115. expand_deps(pkg, [{Name, Deps} | Rest]) ->
  116. Dep = {pkg, Name, "0.0.0"},
  117. [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)];
  118. expand_deps(pkg, [{Name, Vsn, Deps} | Rest]) ->
  119. Dep = {pkg, Name, Vsn},
  120. [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)].
  121. flat_deps([]) -> [];
  122. flat_deps([{{Name,_Vsn,Ref}, Deps} | Rest]) ->
  123. [{{Name,vsn_from_ref(Ref)}, top_level_deps(Deps)}]
  124. ++
  125. flat_deps(Deps)
  126. ++
  127. flat_deps(Rest).
  128. flat_pkgdeps([]) -> [];
  129. flat_pkgdeps([{{pkg, Name, Vsn}, Deps} | Rest]) ->
  130. [{{iolist_to_binary(Name),iolist_to_binary(Vsn)}, top_level_deps(Deps)}]
  131. ++
  132. flat_pkgdeps(Deps)
  133. ++
  134. flat_pkgdeps(Rest).
  135. vsn_from_ref({git, _, {_, Vsn}}) -> Vsn;
  136. vsn_from_ref({git, _, Vsn}) -> Vsn.
  137. top_level_deps([]) -> [];
  138. top_level_deps([{{pkg, Name, Vsn}, _} | Deps]) ->
  139. [{list_to_atom(Name), Vsn} | top_level_deps(Deps)];
  140. top_level_deps([{{Name, Vsn, Ref}, _} | Deps]) ->
  141. [{list_to_atom(Name), Vsn, Ref} | top_level_deps(Deps)].
  142. %%%%%%%%%%%%%%%
  143. %%% Helpers %%%
  144. %%%%%%%%%%%%%%%
  145. check_results(AppDir, Expected) ->
  146. BuildDirs = filelib:wildcard(filename:join([AppDir, "_build", "*", "lib"])),
  147. PluginDirs = filelib:wildcard(filename:join([AppDir, "_build", "*", "plugins"])),
  148. GlobalPluginDirs = filelib:wildcard(filename:join([AppDir, "global", "plugins"])),
  149. CheckoutsDir = filename:join([AppDir, "_checkouts"]),
  150. LockFile = filename:join([AppDir, "rebar.lock"]),
  151. Locks = lists:flatten(rebar_config:consult_lock_file(LockFile)),
  152. InvalidApps = rebar_app_discover:find_apps(BuildDirs, invalid),
  153. ValidApps = rebar_app_discover:find_apps(BuildDirs, valid),
  154. InvalidDepsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- InvalidApps],
  155. ValidDepsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- ValidApps],
  156. Deps = rebar_app_discover:find_apps(BuildDirs, all),
  157. DepsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Deps],
  158. Checkouts = rebar_app_discover:find_apps([CheckoutsDir], all),
  159. CheckoutsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Checkouts],
  160. Plugins = rebar_app_discover:find_apps(PluginDirs, all),
  161. PluginsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Plugins],
  162. GlobalPlugins = rebar_app_discover:find_apps(GlobalPluginDirs, all),
  163. GlobalPluginsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- GlobalPlugins],
  164. lists:foreach(
  165. fun({app, Name}) ->
  166. ct:pal("App Name: ~p", [Name]),
  167. case lists:keyfind(Name, 1, DepsNames) of
  168. false ->
  169. error({app_not_found, Name});
  170. {Name, _App} ->
  171. ok
  172. end
  173. ; ({app, Name, invalid}) ->
  174. ct:pal("Invalid Name: ~p", [Name]),
  175. case lists:keyfind(Name, 1, InvalidDepsNames) of
  176. false ->
  177. error({app_not_found, Name});
  178. {Name, _App} ->
  179. ok
  180. end
  181. ; ({app, Name, valid}) ->
  182. ct:pal("Valid Name: ~p", [Name]),
  183. case lists:keyfind(Name, 1, ValidDepsNames) of
  184. false ->
  185. error({app_not_found, Name});
  186. {Name, _App} ->
  187. ok
  188. end
  189. ; ({checkout, Name}) ->
  190. ct:pal("Checkout Name: ~p", [Name]),
  191. ?assertNotEqual(false, lists:keyfind(Name, 1, CheckoutsNames))
  192. ; ({dep, Name}) ->
  193. ct:pal("Dep Name: ~p", [Name]),
  194. ?assertNotEqual(false, lists:keyfind(Name, 1, DepsNames))
  195. ; ({dep, Name, Vsn}) ->
  196. ct:pal("Dep Name: ~p, Vsn: ~p", [Name, Vsn]),
  197. ct:pal("DepNames: ~p~n", [DepsNames]),
  198. case lists:keyfind(Name, 1, DepsNames) of
  199. false ->
  200. error({dep_not_found, Name});
  201. {Name, App} ->
  202. ?assertEqual(iolist_to_binary(Vsn),
  203. iolist_to_binary(rebar_app_info:original_vsn(App)))
  204. end
  205. ; ({plugin, Name}) ->
  206. ct:pal("Plugin Name: ~p", [Name]),
  207. ?assertNotEqual(false, lists:keyfind(Name, 1, PluginsNames))
  208. ; ({plugin, Name, Vsn}) ->
  209. ct:pal("Plugin Name: ~p, Vsn: ~p", [Name, Vsn]),
  210. case lists:keyfind(Name, 1, PluginsNames) of
  211. false ->
  212. error({plugin_not_found, Name});
  213. {Name, App} ->
  214. ?assertEqual(iolist_to_binary(Vsn),
  215. iolist_to_binary(rebar_app_info:original_vsn(App)))
  216. end
  217. ; ({global_plugin, Name}) ->
  218. ct:pal("Global Plugin Name: ~p", [Name]),
  219. ?assertNotEqual(false, lists:keyfind(Name, 1, GlobalPluginsNames))
  220. ; ({global_plugin, Name, Vsn}) ->
  221. ct:pal("Global Plugin Name: ~p, Vsn: ~p", [Name, Vsn]),
  222. case lists:keyfind(Name, 1, GlobalPluginsNames) of
  223. false ->
  224. error({global_plugin_not_found, Name});
  225. {Name, App} ->
  226. ?assertEqual(iolist_to_binary(Vsn),
  227. iolist_to_binary(rebar_app_info:original_vsn(App)))
  228. end
  229. ; ({lock, Name}) ->
  230. ct:pal("Lock Name: ~p", [Name]),
  231. ?assertNotEqual(false, lists:keyfind(iolist_to_binary(Name), 1, Locks))
  232. ; ({lock, Name, Vsn}) ->
  233. ct:pal("Lock Name: ~p, Vsn: ~p", [Name, Vsn]),
  234. case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
  235. false ->
  236. error({lock_not_found, Name});
  237. {_LockName, {pkg, _, LockVsn}, _} ->
  238. ?assertEqual(iolist_to_binary(Vsn),
  239. iolist_to_binary(LockVsn));
  240. {_LockName, {_, _, {ref, LockVsn}}, _} ->
  241. ?assertEqual(iolist_to_binary(Vsn),
  242. iolist_to_binary(LockVsn))
  243. end
  244. ; ({release, Name, Vsn, ExpectedDevMode}) ->
  245. ct:pal("Release: ~p-~s", [Name, Vsn]),
  246. {ok, Cwd} = file:get_cwd(),
  247. try
  248. file:set_cwd(AppDir),
  249. [ReleaseDir] = filelib:wildcard(filename:join([AppDir, "_build", "*", "rel"])),
  250. RelxState = rlx_state:new("", [], []),
  251. RelxState1 = rlx_state:base_output_dir(RelxState, ReleaseDir),
  252. {ok, RelxState2} = rlx_prv_app_discover:do(RelxState1),
  253. {ok, RelxState3} = rlx_prv_rel_discover:do(RelxState2),
  254. LibDir = filename:join([ReleaseDir, Name, "lib"]),
  255. {ok, RelLibs} = file:list_dir(LibDir),
  256. ct:pal("RelLibs: ~p~n", [RelLibs]),
  257. IsSymLinkFun =
  258. fun(X) ->
  259. ec_file:is_symlink(filename:join(LibDir, X))
  260. end,
  261. IsDirFun =
  262. fun(X) ->
  263. filelib:is_dir(filename:join([LibDir, X]))
  264. end,
  265. DevMode =
  266. case os:type() of
  267. {unix, _} ->
  268. lists:all(IsSymLinkFun, RelLibs);
  269. {win32, _} ->
  270. Bool = lists:all(IsDirFun, RelLibs),
  271. case ExpectedDevMode of
  272. true ->
  273. Bool;
  274. false ->
  275. not Bool
  276. end
  277. end,
  278. ?assertEqual(ExpectedDevMode, DevMode),
  279. %% throws not_found if it doesn't exist
  280. rlx_state:get_realized_release(RelxState3, Name, Vsn)
  281. catch
  282. _ ->
  283. ct:fail(release_not_found)
  284. after
  285. file:set_cwd(Cwd)
  286. end
  287. ; ({tar, Name, Vsn}) ->
  288. ct:pal("Tarball: ~s-~s", [Name, Vsn]),
  289. Tarball = filename:join([AppDir, "_build", "rel", Name, Name++"-"++Vsn++".tar.gz"]),
  290. ?assertNotEqual([], filelib:is_file(Tarball))
  291. ; ({file, Filename}) ->
  292. ct:pal("Filename: ~s", [Filename]),
  293. ?assert(filelib:is_file(Filename))
  294. end, Expected).
  295. write_src_file(Dir, Name) ->
  296. Erl = filename:join([Dir, "src", Name]),
  297. ok = filelib:ensure_dir(Erl),
  298. ok = ec_file:write(Erl, erl_src_file(Name)).
  299. write_eunitized_src_file(Dir, Name) ->
  300. Erl = filename:join([Dir, "src", "not_a_real_src_" ++ Name ++ ".erl"]),
  301. ok = filelib:ensure_dir(Erl),
  302. ok = ec_file:write(Erl, erl_eunitized_src_file("not_a_real_src_" ++ Name ++ ".erl")).
  303. write_eunit_suite_file(Dir, Name) ->
  304. Erl = filename:join([Dir, "test", "not_a_real_src_" ++ Name ++ "_tests.erl"]),
  305. ok = filelib:ensure_dir(Erl),
  306. ok = ec_file:write(Erl, erl_eunit_suite_file("not_a_real_src_" ++ Name ++ ".erl")).
  307. write_app_file(Dir, Name, Version, Deps) ->
  308. Filename = filename:join([Dir, "ebin", Name ++ ".app"]),
  309. ok = filelib:ensure_dir(Filename),
  310. ok = ec_file:write_term(Filename, get_app_metadata(ec_cnv:to_list(Name), Version, Deps)).
  311. write_app_src_file(Dir, Name, Version, Deps) ->
  312. Filename = filename:join([Dir, "src", Name ++ ".app.src"]),
  313. ok = filelib:ensure_dir(Filename),
  314. ok = ec_file:write_term(Filename, get_app_metadata(ec_cnv:to_list(Name), Version, Deps)).
  315. erl_src_file(Name) ->
  316. io_lib:format("-module(~s).\n"
  317. "-export([main/0]).\n"
  318. "main() -> ok.\n", [filename:basename(Name, ".erl")]).
  319. erl_eunitized_src_file(Name) ->
  320. io_lib:format("-module(~s).\n"
  321. "-export([main/0]).\n"
  322. "main() -> ok.\n"
  323. "-ifdef(TEST).\n"
  324. "-include_lib(\"eunit/include/eunit.hrl\").\n"
  325. "some_test_() -> ?_assertEqual(ok, main()).\n"
  326. "-endif.\n", [filename:basename(Name, ".erl")]).
  327. erl_eunit_suite_file(Name) ->
  328. BaseName = filename:basename(Name, ".erl"),
  329. io_lib:format("-module(~s_tests).\n"
  330. "-compile(export_all).\n"
  331. "-ifndef(some_define).\n"
  332. "-define(some_define, false).\n"
  333. "-endif.\n"
  334. "-ifdef(TEST).\n"
  335. "-include_lib(\"eunit/include/eunit.hrl\").\n"
  336. "some_test_() -> ?_assertEqual(ok, ~s:main()).\n"
  337. "define_test_() -> ?_assertEqual(true, ?some_define).\n"
  338. "-endif.\n", [BaseName, BaseName]).
  339. get_app_metadata(Name, Vsn, Deps) ->
  340. {application, erlang:list_to_atom(Name),
  341. [{description, ""},
  342. {vsn, Vsn},
  343. {modules, []},
  344. {included_applications, []},
  345. {registered, []},
  346. {applications, Deps}]}.