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.

343 lines
12 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}].
  7. groups() ->
  8. [{all, [], [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, all}]},
  14. {pkg, [], [{group, all}]}].
  15. init_per_suite(Config) ->
  16. application:start(meck),
  17. Config.
  18. end_per_suite(_Config) ->
  19. application:stop(meck).
  20. init_per_group(git, Config) ->
  21. [{deps_type, git} | Config];
  22. init_per_group(pkg, Config) ->
  23. [{deps_type, pkg} | Config];
  24. init_per_group(_, Config) ->
  25. Config.
  26. end_per_group(_, Config) ->
  27. Config.
  28. init_per_testcase(Case, Config) ->
  29. {Deps, Warnings, Expect} = deps(Case),
  30. Expected = case Expect of
  31. {ok, List} -> {ok, format_expected_deps(List)};
  32. Other -> Other
  33. end,
  34. DepsType = ?config(deps_type, Config),
  35. mock_warnings(),
  36. [{expect, Expected},
  37. {warnings, Warnings}
  38. | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))].
  39. end_per_testcase(_, Config) ->
  40. meck:unload(),
  41. Config.
  42. format_expected_deps(Deps) ->
  43. lists:append([case Dep of
  44. {N,V} -> [{dep, N, V}, {lock, N, V}];
  45. N -> [{dep, N}, {lock, N}]
  46. end || Dep <- Deps]).
  47. %% format:
  48. %% {Spec,
  49. %% [Warning],
  50. %% {ok, Result} | {error, Reason}}
  51. %%
  52. %% Spec is a list of levelled dependencies of two possible forms:
  53. %% - {"Name", Spec}
  54. %% - {"Name", "Vsn", Spec}
  55. %%
  56. %% Warnings are going to match on mocked ?WARN(...)
  57. %% calls to be evaluated. An empty list means we do not care about
  58. %% warnings, not that no warnings will be printed. This means
  59. %% the list of warning isn't interpreted to be exhaustive, and more
  60. %% warnings may be generated than are listed.
  61. deps(flat) ->
  62. {[{"B", []},
  63. {"C", []}],
  64. [],
  65. {ok, ["B", "C"]}};
  66. deps(pick_highest_left) ->
  67. {[{"B", [{"C", "2", []}]},
  68. {"C", "1", []}],
  69. [{"C","2"}],
  70. {ok, ["B", {"C", "1"}]}};
  71. deps(pick_highest_right) ->
  72. {[{"B", "1", []},
  73. {"C", [{"B", "2", []}]}],
  74. [{"B","2"}],
  75. {ok, [{"B","1"}, "C"]}};
  76. deps(pick_smallest1) ->
  77. {[{"B", [{"D", "1", []}]},
  78. {"C", [{"D", "2", []}]}],
  79. [{"D","2"}],
  80. %% we pick D1 because B < C
  81. {ok, ["B","C",{"D","1"}]}};
  82. deps(pick_smallest2) ->
  83. {[{"C", [{"D", "2", []}]},
  84. {"B", [{"D", "1", []}]}],
  85. [{"D","2"}],
  86. %% we pick D1 because B < C
  87. {ok, ["B","C",{"D","1"}]}};
  88. deps(circular1) ->
  89. {[{"B", [{"A", []}]}, % A is the top-level app
  90. {"C", []}],
  91. [],
  92. {error, {rebar_prv_install_deps, {cycles, [[<<"A">>,<<"B">>]]}}}};
  93. deps(circular2) ->
  94. {[{"B", [{"C", [{"B", []}]}]},
  95. {"C", []}],
  96. [],
  97. {error, {rebar_prv_install_deps, {cycles, [[<<"B">>,<<"C">>]]}}}};
  98. deps(circular_skip) ->
  99. %% Never spot the circular dep due to being to low in the deps tree
  100. %% in source deps
  101. {[{"B", [{"C", "2", [{"B", []}]}]},
  102. {"C", "1", [{"D",[]}]}],
  103. [{"C","2"}],
  104. {ok, ["B", {"C","1"}, "D"]}};
  105. deps(fail_conflict) ->
  106. {[{"B", [{"C", "2", []}]},
  107. {"C", "1", []}],
  108. [{"C","2"}],
  109. rebar_abort};
  110. deps(default_profile) ->
  111. {[{"B", []},
  112. {"C", []}],
  113. [],
  114. {ok, ["B", "C"]}};
  115. deps(nondefault_profile) ->
  116. {[{"B", []},
  117. {"C", []}],
  118. [],
  119. {ok, ["B", "C"]}};
  120. deps(nondefault_pick_highest) ->
  121. %% This is all handled in setup_project
  122. {[],[],{ok,[]}}.
  123. setup_project(fail_conflict, Config0, Deps) ->
  124. DepsType = ?config(deps_type, Config0),
  125. Config = rebar_test_utils:init_rebar_state(
  126. Config0,
  127. "fail_conflict_"++atom_to_list(DepsType)++"_"
  128. ),
  129. AppDir = ?config(apps, Config),
  130. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  131. TopDeps = rebar_test_utils:top_level_deps(Deps),
  132. RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps},
  133. {deps_error_on_conflict, true}]),
  134. case DepsType of
  135. git ->
  136. mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]);
  137. pkg ->
  138. mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}])
  139. end,
  140. [{rebarconfig, RebarConf} | Config];
  141. setup_project(nondefault_profile, Config0, Deps) ->
  142. DepsType = ?config(deps_type, Config0),
  143. Config = rebar_test_utils:init_rebar_state(
  144. Config0,
  145. "nondefault_profile_"++atom_to_list(DepsType)++"_"
  146. ),
  147. AppDir = ?config(apps, Config),
  148. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  149. TopDeps = rebar_test_utils:top_level_deps(Deps),
  150. RebarConf = rebar_test_utils:create_config(AppDir, [{profiles, [
  151. {nondef, [{deps, TopDeps}]}
  152. ]}]),
  153. case DepsType of
  154. git ->
  155. mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]);
  156. pkg ->
  157. mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}])
  158. end,
  159. [{rebarconfig, RebarConf} | Config];
  160. setup_project(nondefault_pick_highest, Config0, _) ->
  161. DepsType = ?config(deps_type, Config0),
  162. Config = rebar_test_utils:init_rebar_state(
  163. Config0,
  164. "nondefault_pick_highest_"++atom_to_list(DepsType)++"_"
  165. ),
  166. AppDir = ?config(apps, Config),
  167. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  168. DefaultDeps = rebar_test_utils:expand_deps(DepsType, [{"B", [{"C", "1", []}]}]),
  169. ProfileDeps = rebar_test_utils:expand_deps(DepsType, [{"C", "2", []}]),
  170. DefaultTop = rebar_test_utils:top_level_deps(DefaultDeps),
  171. ProfileTop = rebar_test_utils:top_level_deps(ProfileDeps),
  172. RebarConf = rebar_test_utils:create_config(
  173. AppDir,
  174. [{deps, DefaultTop},
  175. {profiles, [{nondef, [{deps, ProfileTop}]}]}]
  176. ),
  177. case DepsType of
  178. git ->
  179. mock_git_resource:mock(
  180. [{deps, rebar_test_utils:flat_deps(DefaultDeps ++ ProfileDeps)}]
  181. );
  182. pkg ->
  183. mock_pkg_resource:mock(
  184. [{pkgdeps, rebar_test_utils:flat_pkgdeps(DefaultDeps ++ ProfileDeps)}]
  185. )
  186. end,
  187. [{rebarconfig, RebarConf} | Config];
  188. setup_project(Case, Config0, Deps) ->
  189. DepsType = ?config(deps_type, Config0),
  190. Config = rebar_test_utils:init_rebar_state(
  191. Config0,
  192. atom_to_list(Case)++"_installdeps_"++atom_to_list(DepsType)++"_"
  193. ),
  194. AppDir = ?config(apps, Config),
  195. rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
  196. TopDeps = rebar_test_utils:top_level_deps(Deps),
  197. RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
  198. case DepsType of
  199. git ->
  200. mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}]);
  201. pkg ->
  202. mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}])
  203. end,
  204. [{rebarconfig, RebarConf} | Config].
  205. mock_warnings() ->
  206. %% just let it do its thing, we check warnings through
  207. %% the call log.
  208. meck:new(rebar_log, [no_link, passthrough]).
  209. %%% TESTS %%%
  210. flat(Config) -> run(Config).
  211. pick_highest_left(Config) -> run(Config).
  212. pick_highest_right(Config) -> run(Config).
  213. pick_smallest1(Config) -> run(Config).
  214. pick_smallest2(Config) -> run(Config).
  215. circular1(Config) -> run(Config).
  216. circular2(Config) -> run(Config).
  217. circular_skip(Config) -> run(Config).
  218. fail_conflict(Config) ->
  219. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  220. rebar_test_utils:run_and_check(
  221. Config, RebarConfig, ["lock"], ?config(expect, Config)
  222. ),
  223. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)).
  224. default_profile(Config) ->
  225. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  226. AppDir = ?config(apps, Config),
  227. {ok, Apps} = Expect = ?config(expect, Config),
  228. rebar_test_utils:run_and_check(
  229. Config, RebarConfig, ["as", "profile", "lock"], Expect
  230. ),
  231. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)),
  232. BuildDir = filename:join([AppDir, "_build"]),
  233. [?assertMatch({ok, #file_info{type=directory}},
  234. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  235. || {dep, App} <- Apps],
  236. [?assertMatch({ok, #file_info{type=directory}}, % somehow symlinks return dirs
  237. file:read_file_info(filename:join([BuildDir, "profile", "lib", App])))
  238. || {dep, App} <- Apps],
  239. %% A second run to another profile also links default to the right spot
  240. rebar_test_utils:run_and_check(
  241. Config, RebarConfig, ["as", "other", "lock"], Expect
  242. ),
  243. [?assertMatch({ok, #file_info{type=directory}}, % somehow symlinks return dirs
  244. file:read_file_info(filename:join([BuildDir, "other", "lib", App])))
  245. || {dep, App} <- Apps].
  246. nondefault_profile(Config) ->
  247. %% The dependencies here are saved directly to the
  248. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  249. AppDir = ?config(apps, Config),
  250. {ok, AppLocks} = ?config(expect, Config),
  251. try
  252. rebar_test_utils:run_and_check(
  253. Config, RebarConfig, ["as", "nondef", "lock"], {ok, AppLocks}
  254. ),
  255. error(generated_locks)
  256. catch
  257. error:generated_locks -> error(generated_locks);
  258. _:_ -> ok
  259. end,
  260. Apps = [App || App = {dep, _} <- AppLocks],
  261. Expect = {ok, Apps},
  262. rebar_test_utils:run_and_check(
  263. Config, RebarConfig, ["as", "nondef", "lock"], Expect
  264. ),
  265. check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)),
  266. BuildDir = filename:join([AppDir, "_build"]),
  267. [?assertMatch({error, enoent},
  268. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  269. || {dep, App} <- Apps],
  270. [?assertMatch({ok, #file_info{type=directory}},
  271. file:read_file_info(filename:join([BuildDir, "nondef", "lib", App])))
  272. || {dep, App} <- Apps],
  273. %% A second run to another profile doesn't link dependencies
  274. rebar_test_utils:run_and_check(
  275. Config, RebarConfig, ["as", "other", "lock"], Expect
  276. ),
  277. [?assertMatch({error, enoent},
  278. file:read_file_info(filename:join([BuildDir, "default", "lib", App])))
  279. || {dep, App} <- Apps].
  280. nondefault_pick_highest(Config) ->
  281. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  282. %AppDir = ?config(apps, Config),
  283. rebar_test_utils:run_and_check(
  284. Config, RebarConfig, ["as", "nondef", "lock"],
  285. {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "2"}], "nondef"}
  286. ),
  287. rebar_test_utils:run_and_check(
  288. Config, RebarConfig, ["lock"],
  289. {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "1"}, {lock, "C", "1"}], "default"}
  290. ),
  291. rebar_test_utils:run_and_check(
  292. Config, RebarConfig, ["as", "nondef", "lock"],
  293. {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "2"}], "nondef"}
  294. ).
  295. run(Config) ->
  296. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  297. rebar_test_utils:run_and_check(
  298. Config, RebarConfig, ["lock"], ?config(expect, Config)
  299. ),
  300. check_warnings(warning_calls(), ?config(warnings, Config), ?config(deps_type, Config)).
  301. warning_calls() ->
  302. History = meck:history(rebar_log),
  303. [{Str, Args} || {_, {rebar_log, log, [warn, Str, Args]}, _} <- History].
  304. error_calls() ->
  305. History = meck:history(rebar_log),
  306. [{Str, Args} || {_, {rebar_log, log, [error, Str, Args]}, _} <- History].
  307. check_warnings(_, [], _) ->
  308. ok;
  309. check_warnings(Warns, [{Name, Vsn} | Rest], Type) ->
  310. ct:pal("Checking for warning ~p in ~p", [{Name,Vsn},Warns]),
  311. ?assert(in_warnings(Type, Warns, Name, Vsn)),
  312. check_warnings(Warns, Rest, Type).
  313. in_warnings(git, Warns, NameRaw, VsnRaw) ->
  314. Name = iolist_to_binary(NameRaw),
  315. 1 =< length([1 || {_, [AppName, {git, _, {_, Vsn}}]} <- Warns,
  316. AppName =:= Name, Vsn =:= VsnRaw]);
  317. in_warnings(pkg, Warns, NameRaw, VsnRaw) ->
  318. Name = iolist_to_binary(NameRaw),
  319. Vsn = iolist_to_binary(VsnRaw),
  320. 1 =< length([1 || {_, [AppName, AppVsn]} <- Warns,
  321. AppName =:= Name, AppVsn =:= Vsn]).