%% -*- 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 %% @author Kresten Krab Thorup %% @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)].