25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

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