No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

487 líneas
17 KiB

  1. -module(rebar_install_deps_SUITE).
  2. -compile(export_all).
  3. -include_lib("common_test/include/ct.hrl").
  4. -include_lib("eunit/include/eunit.hrl").
  5. -include_lib("kernel/include/file.hrl").
  6. all() -> [{group, git}, {group, pkg}, {group, mixed}].
  7. groups() ->
  8. [{unique, [], [flat, pick_highest_left, pick_highest_right,
  9. pick_smallest1, pick_smallest2,
  10. circular1, circular2, circular_skip,
  11. fail_conflict, default_profile, nondefault_profile,
  12. nondefault_pick_highest]},
  13. {git, [], [{group, unique}]},
  14. {pkg, [], [{group, unique}]},
  15. {mixed, [], [
  16. m_flat1, m_flat2, m_circular1, m_circular2, m_circular3,
  17. m_pick_source1, m_pick_source2, m_pick_source3,
  18. m_pick_source4, m_pick_source5, m_source_to_pkg,
  19. m_pkg_level1, m_pkg_level2, m_pkg_level3, m_pkg_level3_alpha_order
  20. ]}
  21. ].
  22. init_per_suite(Config) ->
  23. application:start(meck),
  24. Config.
  25. end_per_suite(_Config) ->
  26. application:stop(meck).
  27. init_per_group(git, Config) ->
  28. [{deps_type, git} | Config];
  29. init_per_group(pkg, Config) ->
  30. [{deps_type, pkg} | Config];
  31. init_per_group(mixed, Config) ->
  32. [{deps_type, mixed} | Config];
  33. init_per_group(_, Config) ->
  34. Config.
  35. end_per_group(_, Config) ->
  36. Config.
  37. init_per_testcase(Case, Config) when is_atom(Case) ->
  38. DepsType = ?config(deps_type, Config),
  39. init_per_testcase({DepsType, Case}, Config);
  40. init_per_testcase({mixed, Case}, Config) ->
  41. {Deps, Warnings, Expect} = mdeps(Case),
  42. Expected = case Expect of
  43. {ok, List} -> {ok, format_expected_mdeps(List)};
  44. Other -> Other
  45. end,
  46. mock_warnings(),
  47. [{expect, Expected},
  48. {warnings, format_expected_mixed_warnings(Warnings)}
  49. | setup_project(Case, Config, rebar_test_utils:expand_deps(mixed, Deps))];
  50. init_per_testcase({DepsType, Case}, Config) ->
  51. {Deps, Warnings, Expect} = deps(Case),
  52. Expected = case Expect of
  53. {ok, List} -> {ok, format_expected_deps(List)};
  54. Other -> Other
  55. end,
  56. mock_warnings(),
  57. [{expect, Expected},
  58. {warnings, Warnings}
  59. | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))].
  60. end_per_testcase(_, Config) ->
  61. meck:unload(),
  62. Config.
  63. format_expected_deps(Deps) ->
  64. lists:append([case Dep of
  65. {N,V} -> [{dep, N, V}, {lock, N, V}];
  66. N -> [{dep, N}, {lock, N}]
  67. end || Dep <- Deps]).
  68. format_expected_mdeps(Deps) ->
  69. %% for mixed deps, lowercase is a package, uppercase is source.
  70. %% We can't check which was used from the dep, but the lock contains
  71. %% the type and we can use that information.
  72. lists:append([
  73. case Dep of
  74. {N,V} when hd(N) >= $a, hd(N) =< $z ->
  75. UN = string:to_upper(N),
  76. [{dep, UN, V}, {lock, pkg, UN, V}];
  77. {N,V} when hd(N) >= $A, hd(N) =< $Z ->
  78. [{dep, N, V}, {lock, src, N, V}];
  79. N when hd(N) >= $a, hd(N) =< $z ->
  80. UN = string:to_upper(N),
  81. [{dep, UN}, {lock, pkg, UN, "0.0.0"}];
  82. N when hd(N) >= $A, hd(N) =< $Z ->
  83. [{dep, N}, {lock, src, N, "0.0.0"}]
  84. end || Dep <- Deps]).
  85. format_expected_mixed_warnings(Warnings) ->
  86. [case W of
  87. {N, Vsn} when hd(N) >= $a, hd(N) =< $z -> {pkg, string:to_upper(N), Vsn};
  88. {N, Vsn} when hd(N) >= $A, hd(N) =< $Z -> {src, N, Vsn};
  89. N when hd(N) >= $a, hd(N) =< $z -> {pkg, string:to_upper(N), "0.0.0"};
  90. N when hd(N) >= $A, hd(N) =< $Z -> {src, N, "0.0.0"}
  91. end || W <- Warnings].
  92. %% format:
  93. %% {Spec,
  94. %% [Warning],
  95. %% {ok, Result} | {error, Reason}}
  96. %%
  97. %% Spec is a list of levelled dependencies of two possible forms:
  98. %% - {"Name", Spec}
  99. %% - {"Name", "Vsn", Spec}
  100. %%
  101. %% Warnings are going to match on mocked ?WARN(...)
  102. %% calls to be evaluated. An empty list means we do not care about
  103. %% warnings, not that no warnings will be printed. This means
  104. %% the list of warning isn't interpreted to be exhaustive, and more
  105. %% warnings may be generated than are listed.
  106. deps(flat) ->
  107. {[{"B", []},
  108. {"C", []}],
  109. [],
  110. {ok, ["B", "C"]}};
  111. deps(pick_highest_left) ->
  112. {[{"B", [{"C", "2", []}]},
  113. {"C", "1", []}],
  114. [{"C","2"}],
  115. {ok, ["B", {"C", "1"}]}};
  116. deps(pick_highest_right) ->
  117. {[{"B", "1", []},
  118. {"C", [{"B", "2", []}]}],
  119. [{"B","2"}],
  120. {ok, [{"B","1"}, "C"]}};
  121. deps(pick_smallest1) ->
  122. {[{"B", [{"D", "1", []}]},
  123. {"C", [{"D", "2", []}]}],
  124. [{"D","2"}],
  125. %% we pick D1 because B < C
  126. {ok, ["B","C",{"D","1"}]}};
  127. deps(pick_smallest2) ->
  128. {[{"C", [{"D", "2", []}]},
  129. {"B", [{"D", "1", []}]}],
  130. [{"D","2"}],
  131. %% we pick D1 because B < C
  132. {ok, ["B","C",{"D","1"}]}};
  133. deps(circular1) ->
  134. {[{"B", [{"A", []}]}, % A is the top-level app
  135. {"C", []}],
  136. [],
  137. {error, {rebar_prv_install_deps, {cycles, [[<<"A">>,<<"B">>]]}}}};
  138. deps(circular2) ->
  139. {[{"B", [{"C", [{"B", []}]}]},
  140. {"C", []}],
  141. [],
  142. {error, {rebar_prv_install_deps, {cycles, [[<<"B">>,<<"C">>]]}}}};
  143. deps(circular_skip) ->
  144. %% Never spot the circular dep due to being to low in the deps tree
  145. %% in source deps
  146. {[{"B", [{"C", "2", [{"B", []}]}]},
  147. {"C", "1", [{"D",[]}]}],
  148. [{"C","2"}],
  149. {ok, ["B", {"C","1"}, "D"]}};
  150. deps(fail_conflict) ->
  151. {[{"B", [{"C", "2", []}]},
  152. {"C", "1", []}],
  153. [{"C","2"}],
  154. rebar_abort};
  155. deps(default_profile) ->
  156. {[{"B", []},
  157. {"C", []}],
  158. [],
  159. {ok, ["B", "C"]}};
  160. deps(nondefault_profile) ->
  161. {[{"B", []},
  162. {"C", []}],
  163. [],
  164. {ok, ["B", "C"]}};
  165. deps(nondefault_pick_highest) ->
  166. %% This is all handled in setup_project
  167. {[],[],{ok,[]}}.
  168. %% format:
  169. %% Same as `deps/1' except "A" is a source dep
  170. %% and "a" is a package dep.
  171. mdeps(m_flat1) ->
  172. {[{"c", []},
  173. {"B", []}],
  174. [],
  175. {ok, ["B","c"]}};
  176. mdeps(m_flat2) ->
  177. {[{"B", []},
  178. {"c", []}],
  179. [],
  180. {ok, ["B","c"]}};
  181. mdeps(m_circular1) ->
  182. {[{"b", [{"a",[]}]}], % "A" is the top app
  183. [],
  184. {error, {rebar_prv_install_deps, {cycles, [[<<"A">>,<<"B">>]]}}}};
  185. mdeps(m_circular2) ->
  186. {[{"B", [{"c", [{"b", []}]}]}],
  187. [],
  188. {error, {rebar_prv_install_deps, {cycles, [[<<"B">>,<<"C">>]]}}}};
  189. mdeps(m_circular3) ->
  190. %% Spot the circular dep due to being to low in the deps tree
  191. %% but as a source dep, taking precedence over packages
  192. {[{"B", [{"C", "2", [{"B", []}]}]},
  193. {"c", "1", [{"d",[]}]}],
  194. [],
  195. {error, {rebar_prv_install_deps, {cycles, [[<<"B">>,<<"C">>]]}}}};
  196. mdeps(m_pick_source1) ->
  197. {[{"B", [{"D", []}]},
  198. {"c", [{"d", []}]}],
  199. ["d"],
  200. {ok, ["B", "c", "D"]}};
  201. mdeps(m_pick_source2) ->
  202. {[{"b", [{"d", []}]},
  203. {"C", [{"D", []}]}],
  204. ["d"],
  205. {ok, ["b", "C", "D"]}};
  206. mdeps(m_pick_source3) ->
  207. %% The order of declaration is important.
  208. {[{"b", []},
  209. {"B", []}],
  210. [],
  211. {ok, ["b"]}};
  212. mdeps(m_pick_source4) ->
  213. {[{"B", []},
  214. {"b", []}],
  215. [],
  216. {ok, ["B"]}};
  217. mdeps(m_pick_source5) ->
  218. {[{"B", [{"d", []}]},
  219. {"C", [{"D", []}]}],
  220. ["d"],
  221. {ok, ["B", "C", "D"]}};
  222. mdeps(m_source_to_pkg) ->
  223. {[{"B", [{"c",[{"d", []}]}]}],
  224. [],
  225. {ok, ["B", "c", "d"]}};
  226. mdeps(m_pkg_level1) ->
  227. {[{"B", [{"D", [{"e", "2", []}]}]},
  228. {"C", [{"e", "1", []}]}],
  229. [{"e","2"}],
  230. {ok, ["B","C","D",{"e","1"}]}};
  231. mdeps(m_pkg_level2) ->
  232. {[{"B", [{"e", "1", []}]},
  233. {"C", [{"D", [{"e", "2", []}]}]}],
  234. [{"e","2"}],
  235. {ok, ["B","C","D",{"e","1"}]}};
  236. mdeps(m_pkg_level3_alpha_order) ->
  237. {[{"B", [{"d", [{"f", "1", []}]}]},
  238. {"C", [{"E", [{"f", "2", []}]}]}],
  239. [{"f","2"}],
  240. {ok, ["B","C","d","E",{"f","1"}]}};
  241. mdeps(m_pkg_level3) ->
  242. {[{"B", [{"d", [{"f", "1", []}]}]},
  243. {"C", [{"E", [{"G", [{"f", "2", []}]}]}]}],
  244. [{"f","2"}],
  245. {ok, ["B","C","d","E","G",{"f","1"}]}}.
  246. setup_project(fail_conflict, Config0, Deps) ->
  247. DepsType = ?config(deps_type, Config0),
  248. Config = rebar_test_utils:init_rebar_state(
  249. Config0,
  250. "fail_conflict_"++atom_to_list(DepsType)++"_"
  251. ),
  252. AppDir = ?config(apps, Config),
  253. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  254. TopDeps = rebar_test_utils:top_level_deps(Deps),
  255. RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps},
  256. {deps_error_on_conflict, true}]),
  257. {SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
  258. mock_git_resource:mock([{deps, SrcDeps}]),
  259. mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
  260. [{rebarconfig, RebarConf} | Config];
  261. setup_project(nondefault_profile, Config0, Deps) ->
  262. DepsType = ?config(deps_type, Config0),
  263. Config = rebar_test_utils:init_rebar_state(
  264. Config0,
  265. "nondefault_profile_"++atom_to_list(DepsType)++"_"
  266. ),
  267. AppDir = ?config(apps, Config),
  268. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  269. TopDeps = rebar_test_utils:top_level_deps(Deps),
  270. RebarConf = rebar_test_utils:create_config(AppDir, [{profiles, [
  271. {nondef, [{deps, TopDeps}]}
  272. ]}]),
  273. {SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
  274. mock_git_resource:mock([{deps, SrcDeps}]),
  275. mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
  276. [{rebarconfig, RebarConf} | Config];
  277. setup_project(nondefault_pick_highest, Config0, _) ->
  278. DepsType = ?config(deps_type, Config0),
  279. Config = rebar_test_utils:init_rebar_state(
  280. Config0,
  281. "nondefault_pick_highest_"++atom_to_list(DepsType)++"_"
  282. ),
  283. AppDir = ?config(apps, Config),
  284. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  285. DefaultDeps = rebar_test_utils:expand_deps(DepsType, [{"B", [{"C", "1", []}]}]),
  286. ProfileDeps = rebar_test_utils:expand_deps(DepsType, [{"C", "2", []}]),
  287. DefaultTop = rebar_test_utils:top_level_deps(DefaultDeps),
  288. ProfileTop = rebar_test_utils:top_level_deps(ProfileDeps),
  289. RebarConf = rebar_test_utils:create_config(
  290. AppDir,
  291. [{deps, DefaultTop},
  292. {profiles, [{nondef, [{deps, ProfileTop}]}]}]
  293. ),
  294. case DepsType of
  295. git ->
  296. {SrcDeps, _} = rebar_test_utils:flat_deps(DefaultDeps++ProfileDeps),
  297. mock_git_resource:mock([{deps, SrcDeps}]);
  298. pkg ->
  299. {_, PkgDeps} = rebar_test_utils:flat_deps(DefaultDeps++ProfileDeps),
  300. mock_pkg_resource:mock([{pkgdeps, PkgDeps}])
  301. end,
  302. [{rebarconfig, RebarConf} | Config];
  303. setup_project(Case, Config0, Deps) ->
  304. DepsType = ?config(deps_type, Config0),
  305. Config = rebar_test_utils:init_rebar_state(
  306. Config0,
  307. atom_to_list(Case)++"_installdeps_"++atom_to_list(DepsType)++"_"
  308. ),
  309. AppDir = ?config(apps, Config),
  310. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  311. TopDeps = rebar_test_utils:top_level_deps(Deps),
  312. RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
  313. {SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
  314. mock_git_resource:mock([{deps, SrcDeps}]),
  315. mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
  316. [{rebarconfig, RebarConf} | Config].
  317. mock_warnings() ->
  318. %% just let it do its thing, we check warnings through
  319. %% the call log.
  320. meck:new(rebar_log, [no_link, passthrough]).
  321. %%% TESTS %%%
  322. flat(Config) -> run(Config).
  323. pick_highest_left(Config) -> run(Config).
  324. pick_highest_right(Config) -> run(Config).
  325. pick_smallest1(Config) -> run(Config).
  326. pick_smallest2(Config) -> run(Config).
  327. circular1(Config) -> run(Config).
  328. circular2(Config) -> run(Config).
  329. circular_skip(Config) -> run(Config).
  330. fail_conflict(Config) ->
  331. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  332. rebar_test_utils:run_and_check(
  333. Config, RebarConfig, ["lock"], ?config(expect, Config)
  334. ),
  335. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)).
  336. default_profile(Config) ->
  337. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  338. AppDir = ?config(apps, Config),
  339. {ok, Apps} = Expect = ?config(expect, Config),
  340. rebar_test_utils:run_and_check(
  341. Config, RebarConfig, ["lock"], Expect
  342. ),
  343. rebar_test_utils:run_and_check(
  344. Config, RebarConfig, ["as", "profile", "lock"], Expect
  345. ),
  346. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)),
  347. BuildDir = filename:join([AppDir, "_build"]),
  348. [?assertMatch({ok, #file_info{type=directory}},
  349. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  350. || {dep, App} <- Apps],
  351. [?assertMatch({ok, #file_info{type=directory}}, % somehow symlinks return dirs
  352. file:read_file_info(filename:join([BuildDir, "profile", "lib", App])))
  353. || {dep, App} <- Apps],
  354. %% A second run to another profile also links default to the right spot
  355. rebar_test_utils:run_and_check(
  356. Config, RebarConfig, ["lock"], Expect
  357. ),
  358. rebar_test_utils:run_and_check(
  359. Config, RebarConfig, ["as", "other", "lock"], Expect
  360. ),
  361. [?assertMatch({ok, #file_info{type=directory}}, % somehow symlinks return dirs
  362. file:read_file_info(filename:join([BuildDir, "other", "lib", App])))
  363. || {dep, App} <- Apps].
  364. nondefault_profile(Config) ->
  365. %% The dependencies here are saved directly to the
  366. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  367. AppDir = ?config(apps, Config),
  368. {ok, AppLocks} = ?config(expect, Config),
  369. try
  370. rebar_test_utils:run_and_check(
  371. Config, RebarConfig, ["as", "nondef", "lock"], {ok, AppLocks}
  372. ),
  373. error(generated_locks)
  374. catch
  375. error:generated_locks -> error(generated_locks);
  376. _:_ -> ok
  377. end,
  378. Apps = [App || App = {dep, _} <- AppLocks],
  379. Expect = {ok, Apps},
  380. rebar_test_utils:run_and_check(
  381. Config, RebarConfig, ["as", "nondef", "lock"], Expect
  382. ),
  383. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)),
  384. BuildDir = filename:join([AppDir, "_build"]),
  385. [?assertMatch({error, enoent},
  386. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  387. || {dep, App} <- Apps],
  388. [?assertMatch({ok, #file_info{type=directory}},
  389. file:read_file_info(filename:join([BuildDir, "nondef", "lib", App])))
  390. || {dep, App} <- Apps],
  391. %% A second run to another profile doesn't link dependencies
  392. rebar_test_utils:run_and_check(
  393. Config, RebarConfig, ["as", "other", "lock"], Expect
  394. ),
  395. [?assertMatch({error, enoent},
  396. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  397. || {dep, App} <- Apps].
  398. nondefault_pick_highest(Config) ->
  399. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  400. rebar_test_utils:run_and_check(
  401. Config, RebarConfig, ["lock"],
  402. {ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "1"}], "default"}
  403. ),
  404. rebar_test_utils:run_and_check(
  405. Config, RebarConfig, ["as", "nondef", "lock"],
  406. {ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "2"}], "nondef"}
  407. ),
  408. rebar_test_utils:run_and_check(
  409. Config, RebarConfig, ["lock"],
  410. {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "1"}, {lock, "C", "1"}], "default"}
  411. ),
  412. rebar_test_utils:run_and_check(
  413. Config, RebarConfig, ["as", "nondef", "lock"],
  414. {ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "2"}], "nondef"}
  415. ).
  416. m_flat1(Config) -> run(Config).
  417. m_flat2(Config) -> run(Config).
  418. m_circular1(Config) -> run(Config).
  419. m_circular2(Config) -> run(Config).
  420. m_circular3(Config) -> run(Config).
  421. m_pick_source1(Config) -> run(Config).
  422. m_pick_source2(Config) -> run(Config).
  423. m_pick_source3(Config) -> run(Config).
  424. m_pick_source4(Config) -> run(Config).
  425. m_pick_source5(Config) -> run(Config).
  426. m_source_to_pkg(Config) -> run(Config).
  427. m_pkg_level1(Config) -> run(Config).
  428. m_pkg_level2(Config) -> run(Config).
  429. m_pkg_level3(Config) -> run(Config).
  430. m_pkg_level3_alpha_order(Config) -> run(Config).
  431. run(Config) ->
  432. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  433. rebar_test_utils:run_and_check(
  434. Config, RebarConfig, ["lock"], ?config(expect, Config)
  435. ),
  436. check_warnings(warning_calls(), ?config(warnings, Config), ?config(deps_type, Config)).
  437. warning_calls() ->
  438. History = meck:history(rebar_log),
  439. [{Str, Args} || {_, {rebar_log, log, [warn, Str, Args]}, _} <- History].
  440. error_calls() ->
  441. History = meck:history(rebar_log),
  442. [{Str, Args} || {_, {rebar_log, log, [error, Str, Args]}, _} <- History].
  443. check_warnings(_, [], _) ->
  444. ok;
  445. check_warnings(Warns, [{Type, Name, Vsn} | Rest], mixed) ->
  446. ct:pal("Checking for warning ~p in ~p", [{Name,Vsn},Warns]),
  447. ?assert(in_warnings(Type, Warns, Name, Vsn)),
  448. check_warnings(Warns, Rest, mixed);
  449. check_warnings(Warns, [{Name, Vsn} | Rest], Type) ->
  450. ct:pal("Checking for warning ~p in ~p", [{Name,Vsn},Warns]),
  451. ?assert(in_warnings(Type, Warns, Name, Vsn)),
  452. check_warnings(Warns, Rest, Type).
  453. in_warnings(git, Warns, NameRaw, VsnRaw) ->
  454. Name = iolist_to_binary(NameRaw),
  455. 1 =< length([1 || {_, [AppName, {git, _, {_, Vsn}}]} <- Warns,
  456. AppName =:= Name, Vsn =:= VsnRaw]);
  457. in_warnings(pkg, Warns, NameRaw, VsnRaw) ->
  458. Name = iolist_to_binary(NameRaw),
  459. Vsn = iolist_to_binary(VsnRaw),
  460. 1 =< length([1 || {_, [AppName, AppVsn]} <- Warns,
  461. AppName =:= Name, AppVsn =:= Vsn]).