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.

443 rivejä
20 KiB

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