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 lines
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 години
преди 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. 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}]}.