Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

453 linhas
16 KiB

  1. -module(rebar_compiler_dag_SUITE).
  2. -compile([export_all, nowarn_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() ->
  7. [{group, with_project}].
  8. groups() ->
  9. %% The tests in this group are dirty, the order is specific
  10. %% and required across runs for tests to work.
  11. [{with_project, [sequence], [
  12. find_structure, app_sort,
  13. propagate_include_app1a, propagate_include_app1b,
  14. propagate_include_app2, propagate_behaviour,
  15. propagate_app1_ptrans, propagate_app2_ptrans,
  16. propagate_app2_ptrans_hrl
  17. ]}
  18. ].
  19. init_per_suite(Config) ->
  20. rebar_compiler_erl:module_info(), % ensure it is loaded
  21. Config.
  22. end_per_suite(Config) ->
  23. Config.
  24. init_per_group(with_project, Config) ->
  25. NewConfig = rebar_test_utils:init_rebar_state(Config, "apps"),
  26. AppDir = ?config(apps, NewConfig),
  27. Name1 = rebar_test_utils:create_random_name("app1_"),
  28. Vsn1 = rebar_test_utils:create_random_vsn(),
  29. rebar_test_utils:create_app(filename:join([AppDir,"apps",Name1]), Name1, Vsn1, [kernel, stdlib]),
  30. Name2 = rebar_test_utils:create_random_name("app2_"),
  31. Vsn2 = rebar_test_utils:create_random_vsn(),
  32. rebar_test_utils:create_app(filename:join([AppDir,"apps",Name2]), Name2, Vsn2, [kernel, stdlib]),
  33. Name3 = rebar_test_utils:create_random_name("app3_"),
  34. Vsn3 = rebar_test_utils:create_random_vsn(),
  35. rebar_test_utils:create_app(filename:join([AppDir,"apps",Name3]), Name3, Vsn3, [kernel, stdlib]),
  36. apply_project(AppDir, [{app1, Name1}, {app2, Name2}, {app3, Name3}],
  37. project()),
  38. [{app_names, [Name1, Name2, Name3]},
  39. {vsns, [Vsn1, Vsn2, Vsn3]}
  40. | NewConfig];
  41. init_per_group(_, Config) ->
  42. Config.
  43. end_per_group(_, Config) ->
  44. Config.
  45. project() ->
  46. [{app1, [
  47. {"src/app1.erl",
  48. "-module(app1).\n"
  49. "-include(\"app1_a.hrl\").\n"
  50. "-include(\"app1_b.hrl\").\n"
  51. "-include_lib(\"{{app2}}/include/app2.hrl\").\n"
  52. "-compile({parse_transform, app1_trans}).\n"
  53. "-compile({parse_transform, {app3, []}}).\n"
  54. "-behaviour(app2).\n"
  55. "-export([cb/0]).\n"
  56. "cb() -> {?APP1A, ?APP1B, ?APP2}.\n"},
  57. {"src/app1_trans.erl",
  58. "-module(app1_trans).n"
  59. "-export([parse_transform/2]).\n"
  60. "parse_transform(Forms, _Opts) -> Forms.\n"},
  61. {"src/app1_a.hrl",
  62. "-define(APP1A, 1).\n"},
  63. {"include/app1_b.hrl",
  64. "-define(APP1B, 1).\n"}
  65. ]},
  66. {app2, [
  67. {"src/app2.erl",
  68. "-module(app2).\n"
  69. "-callback cb() -> term().\n"},
  70. {"include/app2.hrl",
  71. "-include(\"app2_resolve.hrl\").\n"
  72. "-define(APP2, 1).\n"},
  73. {"src/app2_resolve.hrl",
  74. "this file should be found but never is"},
  75. {"include/never_found.hrl",
  76. "%% just comments"}
  77. ]},
  78. {app3, [
  79. {"src/app3.erl",
  80. "-module(app3).\n"
  81. "-include_lib(\"{{app2}}/include/app2.hrl\").\n"
  82. "-include(\"app3_resolve.hrl\").\n"
  83. "-export([parse_transform/2]).\n"
  84. "parse_transform(Forms, _Opts) -> Forms.\n"},
  85. {"src/app3_resolve.hrl",
  86. "%% this file should be found"}
  87. ]}
  88. ].
  89. find_structure() ->
  90. [{doc, "ensure a proper digraph is built with all files"}].
  91. find_structure(Config) ->
  92. AppDir = ?config(apps, Config),
  93. AppNames = ?config(app_names, Config),
  94. %% assume an empty graph
  95. G = digraph:new([acyclic]),
  96. analyze_apps(G, AppNames, AppDir),
  97. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  98. Edges = [{V1,V2} || E <- digraph:edges(G),
  99. {_,V1,V2,_} <- [digraph:edge(G, E)]],
  100. %% All timestamps are the same since we just created the thing
  101. {_, Stamp} = hd(FileStamps),
  102. Matches = [
  103. {"/src/app1.erl", Stamp},
  104. {"/src/app1_trans.erl", Stamp},
  105. {"/src/app1_a.hrl", Stamp},
  106. {"/include/app1_b.hrl", Stamp},
  107. {"/src/app2.erl", Stamp},
  108. {"/include/app2.hrl", Stamp},
  109. {"/include/app2.hrl", Stamp},
  110. {"/src/app3.erl", Stamp},
  111. {"/src/app3_resolve.hrl", Stamp}
  112. ],
  113. matches(Matches, FileStamps),
  114. ?assertEqual(undefined, find_match(".*/never_found.hrl", FileStamps)),
  115. ?assertEqual(undefined, find_match(".*/app2_resolve.hrl", FileStamps)),
  116. ct:pal("Edges: ~p", [Edges]),
  117. edges([
  118. {"/src/app1.erl", "/src/app1_a.hrl"},
  119. {"/src/app1.erl", "/include/app1_b.hrl"},
  120. {"/src/app1.erl", "/src/app2.erl"},
  121. {"/src/app1.erl", "/include/app2.hrl"},
  122. {"/src/app1.erl", "/src/app1_trans.erl"},
  123. {"/src/app1.erl", "/src/app3.erl"},
  124. {"/src/app3.erl", "/include/app2.hrl"},
  125. {"/src/app3.erl", "/src/app3_resolve.hrl"}
  126. ], Edges, FileStamps),
  127. ok.
  128. app_sort() ->
  129. [{doc, "once the digraph is complete, we can sort apps by dependency order"}].
  130. app_sort(Config) ->
  131. AppDir = ?config(apps, Config),
  132. AppNames = ?config(app_names, Config),
  133. %% assume an empty graph
  134. G = digraph:new([acyclic]),
  135. analyze_apps(G, AppNames, AppDir),
  136. AppPaths = [
  137. {AppName, filename:join([AppDir, "apps", AppName])} || AppName <- AppNames
  138. ],
  139. ?assertEqual([lists:nth(2, AppNames),
  140. lists:nth(3, AppNames),
  141. lists:nth(1, AppNames)],
  142. rebar_compiler_dag:compile_order(G, AppPaths)),
  143. ok.
  144. propagate_include_app1a() ->
  145. [{doc, "changing the app1a header file propagates to its dependents"}].
  146. propagate_include_app1a(Config) ->
  147. AppDir = ?config(apps, Config),
  148. AppNames = ?config(app_names, Config),
  149. %% assume an empty graph
  150. G = digraph:new([acyclic]),
  151. next_second(),
  152. F = filename:join([AppDir, "apps", lists:nth(1, AppNames), "src/app1_a.hrl"]),
  153. bump_file(F),
  154. analyze_apps(G, AppNames, AppDir),
  155. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  156. %% All timestamps are the same since we just created the thing
  157. [Stamp1, Stamp2] = lists:usort([S || {_, S} <- FileStamps]),
  158. Matches = [
  159. {"/src/app1.erl", Stamp2},
  160. {"/src/app1_trans.erl", Stamp1},
  161. {"/src/app1_a.hrl", Stamp2},
  162. {"/include/app1_b.hrl", Stamp1},
  163. {"/src/app2.erl", Stamp1},
  164. {"/include/app2.hrl", Stamp1},
  165. {"/src/app3.erl", Stamp1},
  166. {"/src/app3_resolve.hrl", Stamp1}
  167. ],
  168. matches(Matches, FileStamps),
  169. ok.
  170. propagate_include_app1b() ->
  171. [{doc, "changing the app1b header file propagates to its dependents"}].
  172. propagate_include_app1b(Config) ->
  173. AppDir = ?config(apps, Config),
  174. AppNames = ?config(app_names, Config),
  175. %% assume an empty graph
  176. G = digraph:new([acyclic]),
  177. next_second(),
  178. F = filename:join([AppDir, "apps", lists:nth(1, AppNames), "include/app1_b.hrl"]),
  179. bump_file(F),
  180. analyze_apps(G, AppNames, AppDir),
  181. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  182. %% All timestamps are the same since we just created the thing
  183. [Stamp1, Stamp2, Stamp3] = lists:usort([S || {_, S} <- FileStamps]),
  184. Matches = [
  185. {"/src/app1.erl", Stamp3},
  186. {"/src/app1_trans.erl", Stamp1},
  187. {"/src/app1_a.hrl", Stamp2},
  188. {"/include/app1_b.hrl", Stamp3},
  189. {"/src/app2.erl", Stamp1},
  190. {"/include/app2.hrl", Stamp1},
  191. {"/src/app3.erl", Stamp1},
  192. {"/src/app3_resolve.hrl", Stamp1}
  193. ],
  194. matches(Matches, FileStamps),
  195. ok.
  196. propagate_include_app2() ->
  197. [{doc, "changing the app2 header file propagates to its dependents"}].
  198. propagate_include_app2(Config) ->
  199. AppDir = ?config(apps, Config),
  200. AppNames = ?config(app_names, Config),
  201. %% assume an empty graph
  202. G = digraph:new([acyclic]),
  203. next_second(),
  204. F = filename:join([AppDir, "apps", lists:nth(2, AppNames), "include/app2.hrl"]),
  205. bump_file(F),
  206. analyze_apps(G, AppNames, AppDir),
  207. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  208. %% All timestamps are the same since we just created the thing
  209. [S1, S2, S3, S4] = lists:usort([S || {_, S} <- FileStamps]),
  210. Matches = [
  211. {"/src/app1.erl", S4},
  212. {"/src/app1_trans.erl", S1},
  213. {"/src/app1_a.hrl", S2},
  214. {"/include/app1_b.hrl", S3},
  215. {"/src/app2.erl", S1},
  216. {"/include/app2.hrl", S4},
  217. {"/src/app3.erl", S4},
  218. {"/src/app3_resolve.hrl", S1}
  219. ],
  220. matches(Matches, FileStamps),
  221. ok.
  222. propagate_behaviour() ->
  223. [{doc, "changing the behaviour file propagates to its dependents"}].
  224. propagate_behaviour(Config) ->
  225. AppDir = ?config(apps, Config),
  226. AppNames = ?config(app_names, Config),
  227. %% assume an empty graph
  228. G = digraph:new([acyclic]),
  229. next_second(),
  230. F = filename:join([AppDir, "apps", lists:nth(2, AppNames), "src/app2.erl"]),
  231. bump_file(F),
  232. analyze_apps(G, AppNames, AppDir),
  233. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  234. %% All timestamps are the same since we just created the thing
  235. [S1, S2, S3, S4, S5] = lists:usort([S || {_, S} <- FileStamps]),
  236. Matches = [
  237. {"/src/app1.erl", S5},
  238. {"/src/app1_trans.erl", S1},
  239. {"/src/app1_a.hrl", S2},
  240. {"/include/app1_b.hrl", S3},
  241. {"/src/app2.erl", S5},
  242. {"/include/app2.hrl", S4},
  243. {"/src/app3.erl", S4},
  244. {"/src/app3_resolve.hrl", S1}
  245. ],
  246. matches(Matches, FileStamps),
  247. ok.
  248. propagate_app1_ptrans() ->
  249. [{doc, "changing an app-local parse transform propagates to its dependents"}].
  250. propagate_app1_ptrans(Config) ->
  251. AppDir = ?config(apps, Config),
  252. AppNames = ?config(app_names, Config),
  253. %% assume an empty graph
  254. G = digraph:new([acyclic]),
  255. next_second(),
  256. F = filename:join([AppDir, "apps", lists:nth(1, AppNames), "src/app1_trans.erl"]),
  257. bump_file(F),
  258. analyze_apps(G, AppNames, AppDir),
  259. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  260. %% All timestamps are the same since we just created the thing
  261. [S1, S2, S3, S4, S5, S6] = lists:usort([S || {_, S} <- FileStamps]),
  262. Matches = [
  263. {"/src/app1.erl", S6},
  264. {"/src/app1_trans.erl", S6},
  265. {"/src/app1_a.hrl", S2},
  266. {"/include/app1_b.hrl", S3},
  267. {"/src/app2.erl", S5},
  268. {"/include/app2.hrl", S4},
  269. {"/src/app3.erl", S4},
  270. {"/src/app3_resolve.hrl", S1}
  271. ],
  272. matches(Matches, FileStamps),
  273. ok.
  274. propagate_app2_ptrans() ->
  275. [{doc, "changing an app-foreign parse transform propagates to its dependents"}].
  276. propagate_app2_ptrans(Config) ->
  277. AppDir = ?config(apps, Config),
  278. AppNames = ?config(app_names, Config),
  279. %% assume an empty graph
  280. G = digraph:new([acyclic]),
  281. next_second(),
  282. F = filename:join([AppDir, "apps", lists:nth(3, AppNames), "src/app3.erl"]),
  283. bump_file(F),
  284. analyze_apps(G, AppNames, AppDir),
  285. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  286. %% All timestamps are the same since we just created the thing
  287. [S1, S2, S3, S4, S5, S6, S7] = lists:usort([S || {_, S} <- FileStamps]),
  288. Matches = [
  289. {"/src/app1.erl", S7},
  290. {"/src/app1_trans.erl", S6},
  291. {"/src/app1_a.hrl", S2},
  292. {"/include/app1_b.hrl", S3},
  293. {"/src/app2.erl", S5},
  294. {"/include/app2.hrl", S4},
  295. {"/src/app3.erl", S7},
  296. {"/src/app3_resolve.hrl", S1}
  297. ],
  298. matches(Matches, FileStamps),
  299. ok.
  300. propagate_app2_ptrans_hrl() ->
  301. %% the app-foreign ptrans' foreign hrl dep is tested by propagate_include_app2 as well
  302. [{doc, "changing an app-foreign parse transform's local hrl propagates to its dependents"}].
  303. propagate_app2_ptrans_hrl(Config) ->
  304. AppDir = ?config(apps, Config),
  305. AppNames = ?config(app_names, Config),
  306. %% assume an empty graph
  307. G = digraph:new([acyclic]),
  308. next_second(),
  309. F = filename:join([AppDir, "apps", lists:nth(3, AppNames), "src/app3_resolve.hrl"]),
  310. bump_file(F),
  311. analyze_apps(G, AppNames, AppDir),
  312. FileStamps = [digraph:vertex(G, V) || V <- digraph:vertices(G)],
  313. %% All timestamps are the same since we just created the thing
  314. %% S1 and S7 are gone from the propagation now
  315. [S2, S3, S4, S5, S6, S8] = lists:usort([S || {_, S} <- FileStamps]),
  316. Matches = [
  317. {"/src/app1.erl", S8},
  318. {"/src/app1_trans.erl", S6},
  319. {"/src/app1_a.hrl", S2},
  320. {"/include/app1_b.hrl", S3},
  321. {"/src/app2.erl", S5},
  322. {"/include/app2.hrl", S4},
  323. {"/src/app3.erl", S8},
  324. {"/src/app3_resolve.hrl", S8}
  325. ],
  326. matches(Matches, FileStamps),
  327. ok.
  328. %%%%%%%%%%%%%%%
  329. %%% HELPERS %%%
  330. %%%%%%%%%%%%%%%
  331. apply_project(_BaseDir, _Names, []) ->
  332. ok;
  333. apply_project(BaseDir, Names, [{_AppName, []}|Rest]) ->
  334. apply_project(BaseDir, Names, Rest);
  335. apply_project(BaseDir, Names, [{AppName, [File|Files]}|Rest]) ->
  336. apply_file(BaseDir, Names, AppName, File),
  337. apply_project(BaseDir, Names, [{AppName, Files}|Rest]).
  338. apply_file(BaseDir, Names, App, {FileName, Contents}) ->
  339. AppName = proplists:get_value(App, Names),
  340. FilePath = filename:join([BaseDir, "apps", AppName, FileName]),
  341. ok = filelib:ensure_dir(FilePath),
  342. file:write_file(FilePath, apply_template(Contents, Names)).
  343. apply_template("", _) -> "";
  344. apply_template("{{" ++ Text, Names) ->
  345. {Var, Rest} = parse_to_var(Text),
  346. App = list_to_atom(Var),
  347. proplists:get_value(App, Names) ++ apply_template(Rest, Names);
  348. apply_template([H|T], Names) ->
  349. [H|apply_template(T, Names)].
  350. parse_to_var(Str) -> parse_to_var(Str, []).
  351. parse_to_var("}}"++Rest, Acc) ->
  352. {lists:reverse(Acc), Rest};
  353. parse_to_var([H|T], Acc) ->
  354. parse_to_var(T, [H|Acc]).
  355. analyze_apps(G, AppNames, AppDir) ->
  356. populate_app(G, lists:nth(1, AppNames), AppNames, AppDir, ["app1.erl", "app1_trans.erl"]),
  357. populate_app(G, lists:nth(2, AppNames), AppNames, AppDir, ["app2.erl"]),
  358. populate_app(G, lists:nth(3, AppNames), AppNames, AppDir, ["app3.erl"]),
  359. rebar_compiler_dag:populate_deps(G, ".erl", [{".beam", "ebin/"}]),
  360. rebar_compiler_dag:propagate_stamps(G),
  361. %% manually clear the dirty bit for ease of validation
  362. digraph:del_vertex(G, '$r3_dirty_bit').
  363. populate_app(G, Name, AppNames, AppDir, Sources) ->
  364. InDirs = [filename:join([AppDir, "apps", AppName, "src"])
  365. || AppName <- AppNames]
  366. ++ [filename:join([AppDir, "apps", AppName, "include"])
  367. || AppName <- AppNames],
  368. AbsSources = [filename:join([AppDir, "apps", Name, "src", Src])
  369. || Src <- Sources],
  370. DepOpts = [{includes,
  371. [filename:join([AppDir, "apps", Name, "src"]),
  372. filename:join([AppDir, "apps", Name, "include"])
  373. ]},
  374. {include_libs, [filename:join([AppDir, "apps"])]}
  375. ],
  376. rebar_compiler_dag:populate_sources(
  377. G, rebar_compiler_erl,
  378. InDirs, AbsSources, DepOpts
  379. ).
  380. find_match(Regex, FileStamps) ->
  381. try
  382. [throw(F) || {F, _} <- FileStamps, re:run(F, Regex) =/= nomatch],
  383. undefined
  384. catch
  385. throw:F -> {ok, F}
  386. end.
  387. matches([], _) ->
  388. ok;
  389. matches([{R, Stamp} | T], FileStamps) ->
  390. case find_match(R, FileStamps) of
  391. {ok, F} ->
  392. ?assertEqual(Stamp, proplists:get_value(F, FileStamps)),
  393. matches(T, FileStamps);
  394. undefined ->
  395. ?assertEqual({R, Stamp}, FileStamps)
  396. end.
  397. edges([], _, _) ->
  398. ok;
  399. edges([{A,B}|T], Edges, Files) ->
  400. {ok, AbsA} = find_match(A, Files),
  401. {ok, AbsB} = find_match(B, Files),
  402. ?assert(lists:member({AbsA, AbsB}, Edges)),
  403. edges(T, Edges, Files).
  404. bump_file(F) ->
  405. {ok, Bin} = file:read_file(F),
  406. file:write_file(F, [Bin, "\n"]).
  407. next_second() ->
  408. %% Sleep until the next second. Rather than just doing a
  409. %% sleep(1000) call, sleep for the amount of time required
  410. %% to reach the next second as seen by the OS; this can save us
  411. %% a few hundred milliseconds per test by triggering shorter delays.
  412. {Mega, Sec, Micro} = os:timestamp(),
  413. Now = (Mega*1000000 + Sec)*1000 + round(Micro/1000),
  414. Ms = (trunc(Now / 1000)*1000 + 1000) - Now,
  415. %% add a 50ms for jitter since the exact amount sometimes causes failures
  416. timer:sleep(max(Ms+50, 1000)).