blob: b845a45d23efe16e6b32fd528e7f8489eb9f523d [file] [log] [blame]
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 et
%%
%% Copyright 2013-2018 Triq authors
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @author Richard Carlsson <carlsson.richard@gnail.com>
%% @author Kresten Krab Thorup <krab@trifork.com>
%% @copyright 2006-2014 Richard Carlsson
%% @private
%% @see triq
%% @doc Parse transform for automatic exporting of prop_ functions.
-module(triq_autoexport).
-define(DEFAULT_PROP_PREFIX, "prop_").
-export([parse_transform/2]).
-define(CHECK,check).
parse_transform(Forms, Options) ->
%% io:format("FORMS ~n~p~n", [Forms]),
PropPrefix = proplists:get_value(triq_prop_prefix, Options,
?DEFAULT_PROP_PREFIX),
F = fun (Form, Set) ->
t_form(Form, Set, PropPrefix)
end,
PropExports = sets:to_list(lists:foldl(F, sets:new(), Forms)),
EUnit = maybe_gen_eunit(PropPrefix, Forms),
EUnitExports = lists:map(fun ({function, _, Name, 0, _}) -> {Name, 0} end, EUnit),
Forms1 = t_rewrite(Forms, PropExports ++ EUnitExports),
add_eunit(Forms1, EUnit).
t_form({function, _L, Name, 0, _Cs}, S, PropPrefix) ->
N = atom_to_list(Name),
case lists:prefix(PropPrefix, N) of
true ->
sets:add_element({Name, 0}, S);
false ->
S
end;
t_form(_, S, _) ->
S.
gen_eunit(Forms, PropPrefix, Opts) ->
EUnitGen = fun(Form, Set) ->
t_eunit_form(Form, Set, PropPrefix, Opts)
end,
sets:to_list(lists:foldl(EUnitGen, sets:new(), Forms)).
lists_last([]) -> [];
lists_last(L) -> lists:last(L).
maybe_gen_eunit(PropPrefix, Forms) ->
TriqAttrs = lists:foldl(
fun({attribute,_,triq,Val}, Acc) ->
[Val|Acc];
(_, Acc) ->
Acc
end,
[], Forms),
Attrs = lists_last(proplists:lookup_all(eunit, TriqAttrs)),
case Attrs of
{eunit, true} ->
gen_eunit(Forms, PropPrefix, []);
{eunit, Opts} ->
gen_eunit(Forms, PropPrefix, Opts);
_ ->
[]
end.
check_args(Name, Line, Opts) ->
case lists_last(proplists:lookup_all(runs, Opts)) of
[] ->
F = [{call,Line,{atom,Line,list_to_atom(Name)},[]}],
{F, "triq : check ( "++Name++" ( ) )"};
{runs, Runs} ->
F = [{call,Line,{atom,Line,list_to_atom(Name)},[]},
{integer,Line,Runs}],
{F, "triq : check ( "++Name++" ( ), "++integer_to_list(Runs)++" )"}
end.
assertion(Name, Line, Opts) ->
"prop_" ++ PropName = Name,
TestName = list_to_atom(PropName ++ "_test_"),
{CheckArgs, CheckCallStr} = check_args(Name, Line, Opts),
{function,Line,TestName,0,
[{clause,Line,[],[],
[{tuple,Line,
[{atom,Line,timeout},
{integer,Line,3600},
{tuple,Line,
[{integer,Line,Line},
{'fun',Line,
{clauses,
[{clause,Line,[],[],
[{block,Line,
[{call,Line,
{'fun',Line,
{clauses,
[{clause,Line,[],[],
[{'case',Line,
{call,Line,
{remote,Line,{atom,Line,triq},{atom,Line,check}},
CheckArgs},
[{clause,Line,[{atom,Line,true}],[],[{atom,Line,ok}]},
{clause,Line,
[{var,Line,'__V'}],
[],
[{call,Line,
{remote,Line,{atom,Line,erlang},{atom,Line,error}},
[{tuple,Line,
[{atom,Line,assertion_failed},
{cons,Line,
{tuple,Line,
[{atom,Line,module},{atom,Line,mutations}]},
{cons,Line,
{tuple,Line,
[{atom,Line,line},{integer,Line,Line}]},
{cons,Line,
{tuple,Line,
[{atom,Line,expression},
{string,Line,CheckCallStr}]},
{cons,Line,
{tuple,Line,
[{atom,Line,expected},{atom,Line,true}]},
{cons,Line,
{tuple,Line,
[{atom,Line,value},
{'case',Line,
{var,Line,'__V'},
[{clause,Line,
[{atom,Line,false}],
[],
[{var,Line,'__V'}]},
{clause,Line,
[{var,Line,'_'}],
[],
[{tuple,Line,
[{atom,Line,not_a_boolean},
{var,Line,'__V'}]}]}]}]},
{nil,Line}}}}}}]}]}]}]}]}]}},
[]}]}]}]}}]}]}]}]}.
add_eunit([Form|[]], Eunit) ->
[Form|Eunit];
add_eunit([Form|Rest], Eunit) ->
[Form|add_eunit(Rest,Eunit)];
add_eunit([],_Eunit ) ->
[].
t_eunit_form({function, L, Name, 0, _Cs}, S, PropPrefix, Opts) ->
N = atom_to_list(Name) ,
case lists:prefix(PropPrefix, N) of
true ->
Assertion = assertion(N, L, Opts),
sets:add_element(Assertion, S);
false ->
S
end;
t_eunit_form(_, S, _, _) ->
S.
t_rewrite([{attribute,_,module,{Name,_Ps}}=M | Fs], Exports) ->
module_decl(Name, M, Fs, Exports);
t_rewrite([{attribute,_,module,Name}=M | Fs], Exports) ->
module_decl(Name, M, Fs, Exports);
t_rewrite([F | Fs], Exports) ->
[F | t_rewrite(Fs, Exports)];
t_rewrite([], _Exports) ->
[]. % fail-safe, in case there is no module declaration
rewrite([{function,_,?CHECK,0,_}=F | Fs], As, Module, _GenQC) ->
rewrite(Fs, [F | As], Module, false);
rewrite([F | Fs], As, Module, GenQC) ->
rewrite(Fs, [F | As], Module, GenQC);
rewrite([], As, Module, GenQC) ->
{if GenQC ->
[{function,0,?CHECK,0,
[{clause,0,[],[],
[{call,0,{remote,0,{atom,0,triq},{atom,0,check}},
[{atom,0,Module}]}]}]}
| As];
true ->
As
end,
GenQC}.
module_decl(Name, M, Fs, Exports) ->
Module = Name,
{Fs1, GenQC} = rewrite(Fs, [], Module, true),
Es = if GenQC -> [{?CHECK,0} | Exports];
true -> Exports
end,
[M, {attribute,0,export,Es} | lists:reverse(Fs1)].