|
|
@ -0,0 +1,300 @@ |
|
|
|
%%% @doc |
|
|
|
%%% Unit tests for epp-related compiler utils. |
|
|
|
%%% Make it easier to validate internal behaviour of compiler data and |
|
|
|
%%% handling of module parsing without having to actually set up |
|
|
|
%%% entire projects. |
|
|
|
%%% @end |
|
|
|
-module(rebar_compiler_epp_SUITE). |
|
|
|
-include_lib("common_test/include/ct.hrl"). |
|
|
|
-include_lib("eunit/include/eunit.hrl"). |
|
|
|
-compile([export_all, nowarn_export_all]). |
|
|
|
|
|
|
|
all() -> |
|
|
|
[{group, module}]. |
|
|
|
|
|
|
|
groups() -> |
|
|
|
[{module, [], [ |
|
|
|
analyze, analyze_old_behaviour, analyze_old_behavior, |
|
|
|
analyze_empty, analyze_bad_mod, |
|
|
|
resolve_module |
|
|
|
]} |
|
|
|
]. |
|
|
|
|
|
|
|
init_per_group(module, Config) -> |
|
|
|
to_file(Config, {"direct.hrl", "-direct(val). "}), |
|
|
|
Config; |
|
|
|
init_per_group(_, Config) -> |
|
|
|
Config. |
|
|
|
|
|
|
|
end_per_group(_, Config) -> |
|
|
|
Config. |
|
|
|
|
|
|
|
init_per_testcase(_, Config) -> |
|
|
|
Config. |
|
|
|
|
|
|
|
end_per_testcase(_, Config) -> |
|
|
|
Config. |
|
|
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
|
%%% module analysis group %%% |
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
|
|
|
|
|
analyze() -> |
|
|
|
[{docs, "Analyzing a module returns all the " |
|
|
|
"parseable dependencies for it in a map."}]. |
|
|
|
analyze(Config) -> |
|
|
|
?assert(check_analyze( |
|
|
|
#{include => [ |
|
|
|
"eunit-[0-9.]+/include/eunit.hrl$", |
|
|
|
"stdlib-[0-9.]+/include/assert.hrl$", |
|
|
|
"/direct.hrl$" |
|
|
|
], |
|
|
|
%% missing includes |
|
|
|
missing_include_file => [ |
|
|
|
"^false.hrl$" |
|
|
|
], |
|
|
|
missing_include_lib => [ |
|
|
|
"^some_app/include/lib.hrl$" |
|
|
|
], |
|
|
|
parse_transform => [ |
|
|
|
erl_id_trans, |
|
|
|
eunit_autoexport, % added by include file! |
|
|
|
missing_parse_trans1, |
|
|
|
missing_parse_trans2 |
|
|
|
], |
|
|
|
behaviour => [gen_server, gen_statem], |
|
|
|
is_behaviour => true |
|
|
|
}, |
|
|
|
rebar_compiler_epp:deps( |
|
|
|
to_file(Config, fake_mod()), |
|
|
|
[{includes, []}, {macros, []}] |
|
|
|
) |
|
|
|
)), |
|
|
|
ok. |
|
|
|
|
|
|
|
analyze_old_behaviour() -> |
|
|
|
[{docs, "Analyzing old-style behaviour annotation"}]. |
|
|
|
analyze_old_behaviour(Config) -> |
|
|
|
?assert(check_analyze( |
|
|
|
#{include => [], |
|
|
|
missing_include_file => [], |
|
|
|
missing_include_lib => [], |
|
|
|
parse_transform => [], |
|
|
|
behaviour => [], |
|
|
|
is_behaviour => true |
|
|
|
}, |
|
|
|
rebar_compiler_epp:deps( |
|
|
|
to_file(Config, old_behaviour_mod()), |
|
|
|
[{includes, []}, {macros, []}] |
|
|
|
) |
|
|
|
)), |
|
|
|
ok. |
|
|
|
|
|
|
|
analyze_old_behavior() -> |
|
|
|
[{docs, "Analyzing old-style behavior annotation"}]. |
|
|
|
analyze_old_behavior(Config) -> |
|
|
|
?assert(check_analyze( |
|
|
|
#{include => [], |
|
|
|
missing_include_file => [], |
|
|
|
missing_include_lib => [], |
|
|
|
parse_transform => [], |
|
|
|
behaviour => [], |
|
|
|
is_behaviour => true |
|
|
|
}, |
|
|
|
rebar_compiler_epp:deps( |
|
|
|
to_file(Config, old_behavior_mod()), |
|
|
|
[{includes, []}, {macros, []}] |
|
|
|
) |
|
|
|
)), |
|
|
|
ok. |
|
|
|
|
|
|
|
analyze_empty() -> |
|
|
|
[{docs, "Making sure empty files are properly handled as valid but null " |
|
|
|
"and let some other compiler phase handle this. We follow " |
|
|
|
"what EPP handles."}]. |
|
|
|
analyze_empty(Config) -> |
|
|
|
?assert(check_analyze( |
|
|
|
#{include => [], |
|
|
|
missing_include_file => [], |
|
|
|
missing_include_lib => [], |
|
|
|
parse_transform => [], |
|
|
|
behaviour => [], |
|
|
|
is_behaviour => false |
|
|
|
}, |
|
|
|
rebar_compiler_epp:deps( |
|
|
|
to_file(Config, empty_mod()), |
|
|
|
[{includes, []}, {macros, []}] |
|
|
|
) |
|
|
|
)), |
|
|
|
ok. |
|
|
|
|
|
|
|
analyze_bad_mod() -> |
|
|
|
[{docs, "Errors for bad modules that don't compile are skipped " |
|
|
|
"by EPP and so we defer that to a later phase of the " |
|
|
|
"compilation process"}]. |
|
|
|
analyze_bad_mod(Config) -> |
|
|
|
?assert(check_analyze( |
|
|
|
#{include => [], |
|
|
|
missing_include_file => [], |
|
|
|
missing_include_lib => [], |
|
|
|
parse_transform => [], |
|
|
|
behaviour => [], |
|
|
|
is_behaviour => false |
|
|
|
}, |
|
|
|
rebar_compiler_epp:deps( |
|
|
|
to_file(Config, bad_mod()), |
|
|
|
[{includes, []}, {macros, []}] |
|
|
|
) |
|
|
|
)), |
|
|
|
ok. |
|
|
|
|
|
|
|
resolve_module() -> |
|
|
|
[{doc, "given a module name and a bunch of paths, find " |
|
|
|
"the first path that matches the module"}]. |
|
|
|
resolve_module(Config) -> |
|
|
|
Path1 = to_file(Config, fake_mod()), |
|
|
|
Path2 = to_file(Config, old_behaviour_mod()), |
|
|
|
Path3 = to_file(Config, empty_mod()), |
|
|
|
?assertEqual( |
|
|
|
{ok, Path2}, |
|
|
|
rebar_compiler_epp:resolve_module( |
|
|
|
old_behaviour, |
|
|
|
[Path1, Path2, Path3] |
|
|
|
) |
|
|
|
), |
|
|
|
ok. |
|
|
|
|
|
|
|
%%%%%%%%%%%%%%% |
|
|
|
%%% HELPERS %%% |
|
|
|
%%%%%%%%%%%%%%% |
|
|
|
|
|
|
|
%% check each field of `Map' and validate them against `CheckMap'. |
|
|
|
%% This allows to check each value in the map has a matching assertion. |
|
|
|
%% Then check each field of `CheckMap' against `Map' to find if |
|
|
|
%% any missing value exists. |
|
|
|
check_analyze(CheckMap, Map) -> |
|
|
|
ct:pal("check_analyze:~n~p~n~p", [CheckMap, Map]), |
|
|
|
maps:fold(fun(K,V,Acc) -> check(CheckMap, K, V) and Acc end, |
|
|
|
true, Map) |
|
|
|
andalso |
|
|
|
maps:fold( |
|
|
|
fun(K,_,Acc) -> |
|
|
|
check(CheckMap, K, maps:get(K, Map, make_ref())) and Acc |
|
|
|
end, |
|
|
|
true, |
|
|
|
Map |
|
|
|
). |
|
|
|
|
|
|
|
check(Map, K, V) -> |
|
|
|
case maps:is_key(K, Map) of |
|
|
|
false -> false; |
|
|
|
true -> |
|
|
|
#{K := Val} = Map, |
|
|
|
compare_val(Val, V) |
|
|
|
end. |
|
|
|
|
|
|
|
%% two identical values always works |
|
|
|
compare_val(V, V) -> |
|
|
|
true; |
|
|
|
%% compare lists of strings; each string must be checked individually |
|
|
|
%% because they are assumed to be regexes. |
|
|
|
compare_val(V1, V2) when is_list(hd(V1)) -> |
|
|
|
match_regexes(V1, V2); |
|
|
|
compare_val(V1, _V2) when not is_integer(hd(V1)) -> |
|
|
|
%% failing list of some sort, but not a string |
|
|
|
false; |
|
|
|
%% strings as regexes |
|
|
|
compare_val(V1, V2) when is_list(V1) -> |
|
|
|
match_regex(V1, [V2]) =/= nomatch; |
|
|
|
%% anything else is not literally the same and is bad |
|
|
|
compare_val(_, _) -> |
|
|
|
false. |
|
|
|
|
|
|
|
match_regexes([], List) -> |
|
|
|
List == []; % no extra patterns, that would be weird |
|
|
|
match_regexes([H|T], List) -> |
|
|
|
case match_regex(H, List) of |
|
|
|
nomatch -> |
|
|
|
false; |
|
|
|
{ok, Entry} -> |
|
|
|
match_regexes(T, List -- [Entry]) |
|
|
|
end. |
|
|
|
|
|
|
|
match_regex(_Pattern, []) -> |
|
|
|
nomatch; |
|
|
|
match_regex(Pattern, [H|T]) -> |
|
|
|
case re:run(H, Pattern) of |
|
|
|
nomatch -> match_regex(Pattern, T); |
|
|
|
_ -> {ok, H} |
|
|
|
end. |
|
|
|
|
|
|
|
%% custom zip function that causes value failures (by using make_ref() |
|
|
|
%% that will never match in compare_val/2) rather than crashing because |
|
|
|
%% of lists of different lengths. |
|
|
|
zip([], []) -> []; |
|
|
|
zip([], [H|T]) -> [{make_ref(),H} | zip([], T)]; |
|
|
|
zip([H|T], []) -> [{H,make_ref()} | zip(T, [])]; |
|
|
|
zip([X|Xs], [Y|Ys]) -> [{X,Y} | zip(Xs, Ys)]. |
|
|
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
|
%%% Module specifications %%% |
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
|
|
|
|
|
%% turn a module string to a file that will live in CT's scratch dir |
|
|
|
to_file(Config, {Name,Contents}) -> |
|
|
|
Path = filename:join([?config(priv_dir, Config), Name]), |
|
|
|
file:write_file(Path, Contents, [sync]), |
|
|
|
Path. |
|
|
|
|
|
|
|
%% base module with all the interesting includes and attributes |
|
|
|
%% we want to track |
|
|
|
fake_mod() -> |
|
|
|
{"somemod.erl", " |
|
|
|
-module(somemod). |
|
|
|
-export([f/1]). |
|
|
|
-include(\"direct.hrl\"). |
|
|
|
-include(\"direct.hrl\"). |
|
|
|
-include_lib(\"some_app/include/lib.hrl\"). |
|
|
|
-include_lib(\"eunit/include/eunit.hrl\"). |
|
|
|
-compile({parse_transform, {erl_id_trans, []}}). |
|
|
|
-compile({parse_transform, missing_parse_trans1}). |
|
|
|
-compile([{parse_transform, {missing_parse_trans2, []}}]). |
|
|
|
-behaviour(gen_server). |
|
|
|
-behavior(gen_statem). |
|
|
|
-callback f() -> ok. |
|
|
|
-ifdef(OPT). |
|
|
|
-include(\"true.hrl\"). |
|
|
|
-else. |
|
|
|
-include(\"false.hrl\"). |
|
|
|
-endif. |
|
|
|
f(X) -> X. |
|
|
|
"}. |
|
|
|
|
|
|
|
%% variations for attributes that can't be checked in the |
|
|
|
%% same base module |
|
|
|
old_behaviour_mod() -> |
|
|
|
{"old_behaviour.erl", " |
|
|
|
-module(old_behaviour). |
|
|
|
-export([f/1, behaviour_info/1]). |
|
|
|
f(X) -> X. |
|
|
|
behaviour_info(callbacks) -> [{f,1}]. |
|
|
|
"}. |
|
|
|
|
|
|
|
old_behavior_mod() -> |
|
|
|
{"old_behaviour.erl", " |
|
|
|
-module(old_behaviour). |
|
|
|
-export([f/1, behaviour_info/1]). |
|
|
|
f(X) -> X. |
|
|
|
behavior_info(callbacks) -> [{f,1}]. |
|
|
|
"}. |
|
|
|
|
|
|
|
empty_mod() -> |
|
|
|
{"empty.erl", ""}. |
|
|
|
|
|
|
|
bad_mod() -> |
|
|
|
{"badmod.erl", " |
|
|
|
-module(bad_mod). % wrong name! |
|
|
|
f(x) -> X+1. % bad vars |
|
|
|
f((x)cv) -> bad syntax. |
|
|
|
"}. |
|
|
|
|