Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

300 строки
7.9 KiB

  1. %%% @doc
  2. %%% Unit tests for epp-related compiler utils.
  3. %%% Make it easier to validate internal behaviour of compiler data and
  4. %%% handling of module parsing without having to actually set up
  5. %%% entire projects.
  6. %%% @end
  7. -module(rebar_compiler_epp_SUITE).
  8. -include_lib("common_test/include/ct.hrl").
  9. -include_lib("eunit/include/eunit.hrl").
  10. -compile([export_all, nowarn_export_all]).
  11. all() ->
  12. [{group, module}].
  13. groups() ->
  14. [{module, [], [
  15. analyze, analyze_old_behaviour, analyze_old_behavior,
  16. analyze_empty, analyze_bad_mod,
  17. resolve_module
  18. ]}
  19. ].
  20. init_per_group(module, Config) ->
  21. to_file(Config, {"direct.hrl", "-direct(val). "}),
  22. Config;
  23. init_per_group(_, Config) ->
  24. Config.
  25. end_per_group(_, Config) ->
  26. Config.
  27. init_per_testcase(_, Config) ->
  28. Config.
  29. end_per_testcase(_, Config) ->
  30. Config.
  31. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  32. %%% module analysis group %%%
  33. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  34. analyze() ->
  35. [{docs, "Analyzing a module returns all the "
  36. "parseable dependencies for it in a map."}].
  37. analyze(Config) ->
  38. ?assert(check_analyze(
  39. #{include => [
  40. "eunit-[0-9.]+/include/eunit.hrl$",
  41. "stdlib-[0-9.]+/include/assert.hrl$",
  42. "/direct.hrl$"
  43. ],
  44. %% missing includes
  45. missing_include_file => [
  46. "^false.hrl$"
  47. ],
  48. missing_include_lib => [
  49. "^some_app/include/lib.hrl$"
  50. ],
  51. parse_transform => [
  52. erl_id_trans,
  53. eunit_autoexport, % added by include file!
  54. missing_parse_trans1,
  55. missing_parse_trans2
  56. ],
  57. behaviour => [gen_server, gen_statem],
  58. is_behaviour => true
  59. },
  60. rebar_compiler_epp:deps(
  61. to_file(Config, fake_mod()),
  62. [{includes, []}, {macros, []}]
  63. )
  64. )),
  65. ok.
  66. analyze_old_behaviour() ->
  67. [{docs, "Analyzing old-style behaviour annotation"}].
  68. analyze_old_behaviour(Config) ->
  69. ?assert(check_analyze(
  70. #{include => [],
  71. missing_include_file => [],
  72. missing_include_lib => [],
  73. parse_transform => [],
  74. behaviour => [],
  75. is_behaviour => true
  76. },
  77. rebar_compiler_epp:deps(
  78. to_file(Config, old_behaviour_mod()),
  79. [{includes, []}, {macros, []}]
  80. )
  81. )),
  82. ok.
  83. analyze_old_behavior() ->
  84. [{docs, "Analyzing old-style behavior annotation"}].
  85. analyze_old_behavior(Config) ->
  86. ?assert(check_analyze(
  87. #{include => [],
  88. missing_include_file => [],
  89. missing_include_lib => [],
  90. parse_transform => [],
  91. behaviour => [],
  92. is_behaviour => true
  93. },
  94. rebar_compiler_epp:deps(
  95. to_file(Config, old_behavior_mod()),
  96. [{includes, []}, {macros, []}]
  97. )
  98. )),
  99. ok.
  100. analyze_empty() ->
  101. [{docs, "Making sure empty files are properly handled as valid but null "
  102. "and let some other compiler phase handle this. We follow "
  103. "what EPP handles."}].
  104. analyze_empty(Config) ->
  105. ?assert(check_analyze(
  106. #{include => [],
  107. missing_include_file => [],
  108. missing_include_lib => [],
  109. parse_transform => [],
  110. behaviour => [],
  111. is_behaviour => false
  112. },
  113. rebar_compiler_epp:deps(
  114. to_file(Config, empty_mod()),
  115. [{includes, []}, {macros, []}]
  116. )
  117. )),
  118. ok.
  119. analyze_bad_mod() ->
  120. [{docs, "Errors for bad modules that don't compile are skipped "
  121. "by EPP and so we defer that to a later phase of the "
  122. "compilation process"}].
  123. analyze_bad_mod(Config) ->
  124. ?assert(check_analyze(
  125. #{include => [],
  126. missing_include_file => [],
  127. missing_include_lib => [],
  128. parse_transform => [],
  129. behaviour => [],
  130. is_behaviour => false
  131. },
  132. rebar_compiler_epp:deps(
  133. to_file(Config, bad_mod()),
  134. [{includes, []}, {macros, []}]
  135. )
  136. )),
  137. ok.
  138. resolve_module() ->
  139. [{doc, "given a module name and a bunch of paths, find "
  140. "the first path that matches the module"}].
  141. resolve_module(Config) ->
  142. Path1 = to_file(Config, fake_mod()),
  143. Path2 = to_file(Config, old_behaviour_mod()),
  144. Path3 = to_file(Config, empty_mod()),
  145. ?assertEqual(
  146. {ok, Path2},
  147. rebar_compiler_epp:resolve_module(
  148. old_behaviour,
  149. [Path1, Path2, Path3]
  150. )
  151. ),
  152. ok.
  153. %%%%%%%%%%%%%%%
  154. %%% HELPERS %%%
  155. %%%%%%%%%%%%%%%
  156. %% check each field of `Map' and validate them against `CheckMap'.
  157. %% This allows to check each value in the map has a matching assertion.
  158. %% Then check each field of `CheckMap' against `Map' to find if
  159. %% any missing value exists.
  160. check_analyze(CheckMap, Map) ->
  161. ct:pal("check_analyze:~n~p~n~p", [CheckMap, Map]),
  162. maps:fold(fun(K,V,Acc) -> check(CheckMap, K, V) and Acc end,
  163. true, Map)
  164. andalso
  165. maps:fold(
  166. fun(K,_,Acc) ->
  167. check(CheckMap, K, maps:get(K, Map, make_ref())) and Acc
  168. end,
  169. true,
  170. Map
  171. ).
  172. check(Map, K, V) ->
  173. case maps:is_key(K, Map) of
  174. false -> false;
  175. true ->
  176. #{K := Val} = Map,
  177. compare_val(Val, V)
  178. end.
  179. %% two identical values always works
  180. compare_val(V, V) ->
  181. true;
  182. %% compare lists of strings; each string must be checked individually
  183. %% because they are assumed to be regexes.
  184. compare_val(V1, V2) when is_list(hd(V1)) ->
  185. match_regexes(V1, V2);
  186. compare_val(V1, _V2) when not is_integer(hd(V1)) ->
  187. %% failing list of some sort, but not a string
  188. false;
  189. %% strings as regexes
  190. compare_val(V1, V2) when is_list(V1) ->
  191. match_regex(V1, [V2]) =/= nomatch;
  192. %% anything else is not literally the same and is bad
  193. compare_val(_, _) ->
  194. false.
  195. match_regexes([], List) ->
  196. List == []; % no extra patterns, that would be weird
  197. match_regexes([H|T], List) ->
  198. case match_regex(H, List) of
  199. nomatch ->
  200. false;
  201. {ok, Entry} ->
  202. match_regexes(T, List -- [Entry])
  203. end.
  204. match_regex(_Pattern, []) ->
  205. nomatch;
  206. match_regex(Pattern, [H|T]) ->
  207. case re:run(H, Pattern) of
  208. nomatch -> match_regex(Pattern, T);
  209. _ -> {ok, H}
  210. end.
  211. %% custom zip function that causes value failures (by using make_ref()
  212. %% that will never match in compare_val/2) rather than crashing because
  213. %% of lists of different lengths.
  214. zip([], []) -> [];
  215. zip([], [H|T]) -> [{make_ref(),H} | zip([], T)];
  216. zip([H|T], []) -> [{H,make_ref()} | zip(T, [])];
  217. zip([X|Xs], [Y|Ys]) -> [{X,Y} | zip(Xs, Ys)].
  218. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  219. %%% Module specifications %%%
  220. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  221. %% turn a module string to a file that will live in CT's scratch dir
  222. to_file(Config, {Name,Contents}) ->
  223. Path = filename:join([?config(priv_dir, Config), Name]),
  224. file:write_file(Path, Contents, [sync]),
  225. Path.
  226. %% base module with all the interesting includes and attributes
  227. %% we want to track
  228. fake_mod() ->
  229. {"somemod.erl", "
  230. -module(somemod).
  231. -export([f/1]).
  232. -include(\"direct.hrl\").
  233. -include(\"direct.hrl\").
  234. -include_lib(\"some_app/include/lib.hrl\").
  235. -include_lib(\"eunit/include/eunit.hrl\").
  236. -compile({parse_transform, {erl_id_trans, []}}).
  237. -compile({parse_transform, missing_parse_trans1}).
  238. -compile([{parse_transform, {missing_parse_trans2, []}}]).
  239. -behaviour(gen_server).
  240. -behavior(gen_statem).
  241. -callback f() -> ok.
  242. -ifdef(OPT).
  243. -include(\"true.hrl\").
  244. -else.
  245. -include(\"false.hrl\").
  246. -endif.
  247. f(X) -> X.
  248. "}.
  249. %% variations for attributes that can't be checked in the
  250. %% same base module
  251. old_behaviour_mod() ->
  252. {"old_behaviour.erl", "
  253. -module(old_behaviour).
  254. -export([f/1, behaviour_info/1]).
  255. f(X) -> X.
  256. behaviour_info(callbacks) -> [{f,1}].
  257. "}.
  258. old_behavior_mod() ->
  259. {"old_behaviour.erl", "
  260. -module(old_behaviour).
  261. -export([f/1, behaviour_info/1]).
  262. f(X) -> X.
  263. behavior_info(callbacks) -> [{f,1}].
  264. "}.
  265. empty_mod() ->
  266. {"empty.erl", ""}.
  267. bad_mod() ->
  268. {"badmod.erl", "
  269. -module(bad_mod). % wrong name!
  270. f(x) -> X+1. % bad vars
  271. f((x)cv) -> bad syntax.
  272. "}.