You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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