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

418 рядки
18 KiB

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