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 lines
20 KiB

пре 9 година
пре 9 година
пре 10 година
пре 10 година
пре 10 година
пре 9 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 9 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 9 година
  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. 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}.