您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

479 行
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,
  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 -> {git, 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 -> {git, 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_pick_source1) ->
  190. {[{"B", [{"D", []}]},
  191. {"c", [{"d", []}]}],
  192. ["d"],
  193. {ok, ["B", "c", "D"]}};
  194. mdeps(m_pick_source2) ->
  195. %% The order of declaration is important.
  196. {[{"b", []},
  197. {"B", []}],
  198. [],
  199. {ok, ["b"]}};
  200. mdeps(m_pick_source3) ->
  201. {[{"B", []},
  202. {"b", []}],
  203. [],
  204. {ok, ["B"]}};
  205. mdeps(m_pick_source4) ->
  206. {[{"b", [{"d", "1", []}]},
  207. {"C", [{"D", "1", []}]}],
  208. [{"D", "1"}],
  209. {ok, ["b", "C", {"d", "1"}]}};
  210. mdeps(m_pick_source5) ->
  211. {[{"B", [{"d", "1", []}]},
  212. {"C", [{"D", "1", []}]}],
  213. [{"D", "1"}],
  214. {ok, ["B", "C", {"d", "1"}]}};
  215. mdeps(m_source_to_pkg) ->
  216. {[{"B", [{"c",[{"d", []}]}]}],
  217. [],
  218. {ok, ["B", "c", "d"]}};
  219. mdeps(m_pkg_level1) ->
  220. {[{"B", [{"D", [{"e", "2", []}]}]},
  221. {"C", [{"e", "1", []}]}],
  222. [{"e","2"}],
  223. {ok, ["B","C","D",{"e","1"}]}};
  224. mdeps(m_pkg_level2) ->
  225. {[{"B", [{"e", "1", []}]},
  226. {"C", [{"D", [{"e", "2", []}]}]}],
  227. [{"e","2"}],
  228. {ok, ["B","C","D",{"e","1"}]}};
  229. mdeps(m_pkg_level3_alpha_order) ->
  230. {[{"B", [{"d", [{"f", "1", []}]}]},
  231. {"C", [{"E", [{"f", "2", []}]}]}],
  232. [{"f","2"}],
  233. {ok, ["B","C","d","E",{"f","1"}]}};
  234. mdeps(m_pkg_level3) ->
  235. {[{"B", [{"d", [{"f", "1", []}]}]},
  236. {"C", [{"E", [{"G", [{"f", "2", []}]}]}]}],
  237. [{"f","2"}],
  238. {ok, ["B","C","d","E","G",{"f","1"}]}}.
  239. setup_project(fail_conflict, Config0, Deps) ->
  240. DepsType = ?config(deps_type, Config0),
  241. Config = rebar_test_utils:init_rebar_state(
  242. Config0,
  243. "fail_conflict_"++atom_to_list(DepsType)++"_"
  244. ),
  245. AppDir = ?config(apps, Config),
  246. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  247. TopDeps = rebar_test_utils:top_level_deps(Deps),
  248. RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps},
  249. {deps_error_on_conflict, true}]),
  250. {SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
  251. mock_git_resource:mock([{deps, SrcDeps}]),
  252. mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
  253. [{rebarconfig, RebarConf} | Config];
  254. setup_project(nondefault_profile, Config0, Deps) ->
  255. DepsType = ?config(deps_type, Config0),
  256. Config = rebar_test_utils:init_rebar_state(
  257. Config0,
  258. "nondefault_profile_"++atom_to_list(DepsType)++"_"
  259. ),
  260. AppDir = ?config(apps, Config),
  261. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  262. TopDeps = rebar_test_utils:top_level_deps(Deps),
  263. RebarConf = rebar_test_utils:create_config(AppDir, [{profiles, [
  264. {nondef, [{deps, TopDeps}]}
  265. ]}]),
  266. {SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
  267. mock_git_resource:mock([{deps, SrcDeps}]),
  268. mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
  269. [{rebarconfig, RebarConf} | Config];
  270. setup_project(nondefault_pick_highest, Config0, _) ->
  271. DepsType = ?config(deps_type, Config0),
  272. Config = rebar_test_utils:init_rebar_state(
  273. Config0,
  274. "nondefault_pick_highest_"++atom_to_list(DepsType)++"_"
  275. ),
  276. AppDir = ?config(apps, Config),
  277. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  278. DefaultDeps = rebar_test_utils:expand_deps(DepsType, [{"B", [{"C", "1", []}]}]),
  279. ProfileDeps = rebar_test_utils:expand_deps(DepsType, [{"C", "2", []}]),
  280. DefaultTop = rebar_test_utils:top_level_deps(DefaultDeps),
  281. ProfileTop = rebar_test_utils:top_level_deps(ProfileDeps),
  282. RebarConf = rebar_test_utils:create_config(
  283. AppDir,
  284. [{deps, DefaultTop},
  285. {profiles, [{nondef, [{deps, ProfileTop}]}]}]
  286. ),
  287. case DepsType of
  288. git ->
  289. {SrcDeps, _} = rebar_test_utils:flat_deps(DefaultDeps++ProfileDeps),
  290. mock_git_resource:mock([{deps, SrcDeps}]);
  291. pkg ->
  292. {_, PkgDeps} = rebar_test_utils:flat_deps(DefaultDeps++ProfileDeps),
  293. mock_pkg_resource:mock([{pkgdeps, PkgDeps}])
  294. end,
  295. [{rebarconfig, RebarConf} | Config];
  296. setup_project(Case, Config0, Deps) ->
  297. DepsType = ?config(deps_type, Config0),
  298. Config = rebar_test_utils:init_rebar_state(
  299. Config0,
  300. atom_to_list(Case)++"_installdeps_"++atom_to_list(DepsType)++"_"
  301. ),
  302. AppDir = ?config(apps, Config),
  303. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  304. TopDeps = rebar_test_utils:top_level_deps(Deps),
  305. RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
  306. {SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
  307. mock_git_resource:mock([{deps, SrcDeps}]),
  308. mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
  309. [{rebarconfig, RebarConf} | Config].
  310. mock_warnings() ->
  311. %% just let it do its thing, we check warnings through
  312. %% the call log.
  313. meck:new(rebar_log, [no_link, passthrough]).
  314. %%% TESTS %%%
  315. flat(Config) -> run(Config).
  316. pick_highest_left(Config) -> run(Config).
  317. pick_highest_right(Config) -> run(Config).
  318. pick_smallest1(Config) -> run(Config).
  319. pick_smallest2(Config) -> run(Config).
  320. circular1(Config) -> run(Config).
  321. circular2(Config) -> run(Config).
  322. circular_skip(Config) -> run(Config).
  323. fail_conflict(Config) ->
  324. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  325. rebar_test_utils:run_and_check(
  326. Config, RebarConfig, ["lock"], ?config(expect, Config)
  327. ),
  328. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)).
  329. default_profile(Config) ->
  330. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  331. AppDir = ?config(apps, Config),
  332. {ok, Apps} = Expect = ?config(expect, Config),
  333. rebar_test_utils:run_and_check(
  334. Config, RebarConfig, ["lock"], Expect
  335. ),
  336. rebar_test_utils:run_and_check(
  337. Config, RebarConfig, ["as", "profile", "lock"], Expect
  338. ),
  339. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)),
  340. BuildDir = filename:join([AppDir, "_build"]),
  341. [?assertMatch({ok, #file_info{type=directory}},
  342. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  343. || {dep, App} <- Apps],
  344. [?assertMatch({ok, #file_info{type=directory}}, % somehow symlinks return dirs
  345. file:read_file_info(filename:join([BuildDir, "profile", "lib", App])))
  346. || {dep, App} <- Apps],
  347. %% A second run to another profile also links default to the right spot
  348. rebar_test_utils:run_and_check(
  349. Config, RebarConfig, ["lock"], Expect
  350. ),
  351. rebar_test_utils:run_and_check(
  352. Config, RebarConfig, ["as", "other", "lock"], Expect
  353. ),
  354. [?assertMatch({ok, #file_info{type=directory}}, % somehow symlinks return dirs
  355. file:read_file_info(filename:join([BuildDir, "other", "lib", App])))
  356. || {dep, App} <- Apps].
  357. nondefault_profile(Config) ->
  358. %% The dependencies here are saved directly to the
  359. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  360. AppDir = ?config(apps, Config),
  361. {ok, AppLocks} = ?config(expect, Config),
  362. try
  363. rebar_test_utils:run_and_check(
  364. Config, RebarConfig, ["as", "nondef", "lock"], {ok, AppLocks}
  365. ),
  366. error(generated_locks)
  367. catch
  368. error:generated_locks -> error(generated_locks);
  369. _:_ -> ok
  370. end,
  371. Apps = [App || App = {dep, _} <- AppLocks],
  372. Expect = {ok, Apps},
  373. rebar_test_utils:run_and_check(
  374. Config, RebarConfig, ["as", "nondef", "lock"], Expect
  375. ),
  376. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)),
  377. BuildDir = filename:join([AppDir, "_build"]),
  378. [?assertMatch({error, enoent},
  379. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  380. || {dep, App} <- Apps],
  381. [?assertMatch({ok, #file_info{type=directory}},
  382. file:read_file_info(filename:join([BuildDir, "nondef", "lib", App])))
  383. || {dep, App} <- Apps],
  384. %% A second run to another profile doesn't link dependencies
  385. rebar_test_utils:run_and_check(
  386. Config, RebarConfig, ["as", "other", "lock"], Expect
  387. ),
  388. [?assertMatch({error, enoent},
  389. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  390. || {dep, App} <- Apps].
  391. nondefault_pick_highest(Config) ->
  392. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  393. rebar_test_utils:run_and_check(
  394. Config, RebarConfig, ["lock"],
  395. {ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "1"}], "default"}
  396. ),
  397. rebar_test_utils:run_and_check(
  398. Config, RebarConfig, ["as", "nondef", "lock"],
  399. {ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "2"}], "nondef"}
  400. ),
  401. rebar_test_utils:run_and_check(
  402. Config, RebarConfig, ["lock"],
  403. {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "1"}, {lock, "C", "1"}], "default"}
  404. ),
  405. rebar_test_utils:run_and_check(
  406. Config, RebarConfig, ["as", "nondef", "lock"],
  407. {ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "2"}], "nondef"}
  408. ).
  409. m_flat1(Config) -> run(Config).
  410. m_flat2(Config) -> run(Config).
  411. m_circular1(Config) -> run(Config).
  412. m_circular2(Config) -> run(Config).
  413. m_pick_source1(Config) -> run(Config).
  414. m_pick_source2(Config) -> run(Config).
  415. m_pick_source3(Config) -> run(Config).
  416. m_pick_source4(Config) -> run(Config).
  417. m_pick_source5(Config) -> run(Config).
  418. m_source_to_pkg(Config) -> run(Config).
  419. m_pkg_level1(Config) -> run(Config).
  420. m_pkg_level2(Config) -> run(Config).
  421. m_pkg_level3(Config) -> run(Config).
  422. m_pkg_level3_alpha_order(Config) -> run(Config).
  423. run(Config) ->
  424. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  425. rebar_test_utils:run_and_check(
  426. Config, RebarConfig, ["lock"], ?config(expect, Config)
  427. ),
  428. check_warnings(warning_calls(), ?config(warnings, Config), ?config(deps_type, Config)).
  429. warning_calls() ->
  430. History = meck:history(rebar_log),
  431. [{Str, Args} || {_, {rebar_log, log, [warn, Str, Args]}, _} <- History].
  432. error_calls() ->
  433. History = meck:history(rebar_log),
  434. [{Str, Args} || {_, {rebar_log, log, [error, Str, Args]}, _} <- History].
  435. check_warnings(_, [], _) ->
  436. ok;
  437. check_warnings(Warns, [{Type, Name, Vsn} | Rest], mixed) ->
  438. ct:pal("Checking for warning ~p in ~p", [{Name,Vsn},Warns]),
  439. ?assert(in_warnings(Type, Warns, Name, Vsn)),
  440. check_warnings(Warns, Rest, mixed);
  441. check_warnings(Warns, [{Name, Vsn} | Rest], Type) ->
  442. ct:pal("Checking for warning ~p in ~p", [{Name,Vsn},Warns]),
  443. ?assert(in_warnings(Type, Warns, Name, Vsn)),
  444. check_warnings(Warns, Rest, Type).
  445. in_warnings(git, Warns, NameRaw, VsnRaw) ->
  446. Name = iolist_to_binary(NameRaw),
  447. 1 =< length([1 || {_, [AppName, {git, _, {_, Vsn}}]} <- Warns,
  448. AppName =:= Name, Vsn =:= VsnRaw]);
  449. in_warnings(pkg, Warns, NameRaw, VsnRaw) ->
  450. Name = iolist_to_binary(NameRaw),
  451. Vsn = iolist_to_binary(VsnRaw),
  452. 1 =< length([1 || {_, [AppName, {pkg, _, AppVsn, _}]} <- Warns,
  453. AppName =:= Name, AppVsn =:= Vsn]).