blob: fccdfcc4a6e30b947ae648aa736c7bfd594d5048 [file] [log] [blame]
%%=============================================================================
%% 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.
%%=============================================================================
%% @private
%% @doc Module containing functions needed by meck to integrate with cover.
-module(meck_cover).
%% Interface exports
-export([compile_beam/2]).
-export([rename_module/2]).
-export([dump_coverdata/1]).
%%=============================================================================
%% Interface exports
%%=============================================================================
%% @doc Enabled cover on `<name>_meck_original'.
compile_beam(OriginalMod, Bin) ->
CompileBeams = alter_cover(),
[{ok, _}] = CompileBeams([{OriginalMod, Bin}]).
%% @doc Given a cover file `File' exported by `cover:export' overwrite
%% the module name with `Name'.
rename_module(File, Name) ->
NewTerms = change_cover_mod_name(read_cover_file(File), Name),
write_terms(File, NewTerms),
ok.
%% @doc Dump cover data for `Mod' into a .coverdata file in the current
%% directory. Return the absolute file name.
dump_coverdata(Mod) ->
{ok, CWD} = file:get_cwd(),
File = filename:join(CWD, atom_to_list(Mod) ++ ".coverdata"),
ok = cover:export(File, Mod),
File.
%%=============================================================================
%% Internal functions
%%=============================================================================
%% @private
%%
%% @doc Alter the cover BEAM module to export some of it's private
%% functions. This is done for two reasons:
%%
%% 1. Meck needs to alter the export analysis data on disk and
%% therefore needs to understand this format. This is why `get_term'
%% and `write' are exposed.
%%
%% 2. In order to avoid creating temporary files meck needs direct
%% access to `compile_beam/2' which allows passing a binary.
%% In OTP 18.0 the internal API of cover changed a bit and
%% compile_beam/2 was replaced by compile_beams/1.
alter_cover() ->
CoverExports = cover:module_info(exports),
case {lists:member({compile_beams,1}, CoverExports),
lists:member({compile_beam,2}, CoverExports)} of
{true, _} ->
fun cover:compile_beams/1;
{_, true} ->
fun compile_beam_wrapper/1;
{false, false} ->
Beam = meck_code:beam_file(cover),
AbsCode = meck_code:abstract_code(Beam),
{Exports, CompileBeams} =
case lists:member({analyse,0}, CoverExports) of
true ->
%% new API from OTP 18.0 on
{[{compile_beams, 1}, {get_term, 1}, {write, 2}],
fun cover:compile_beams/1};
false ->
{[{compile_beam, 2}, {get_term, 1}, {write, 2}],
fun compile_beam_wrapper/1}
end,
AbsCode2 = meck_code:add_exports(Exports, AbsCode),
_Bin = meck_code:compile_and_load_forms(AbsCode2),
CompileBeams
end.
%% wrap cover's pre-18.0 internal API to simulate the new API
compile_beam_wrapper(ModFiles) ->
[cover:compile_beam(Mod, Bin)||{Mod, Bin} <- ModFiles].
change_cover_mod_name(CoverTerms, Name) ->
{_, Terms} = lists:foldl(fun change_name_in_term/2, {Name,[]}, CoverTerms),
Terms.
change_name_in_term({file, Mod, File}, {Name, Terms}) ->
Term2 = {file, Name, replace_string(File, Mod, Name)},
{Name, [Term2|Terms]};
change_name_in_term({Bump={bump,_,_,_,_,_},_}=Term, {Name, Terms}) ->
Bump2 = setelement(2, Bump, Name),
Term2 = setelement(1, Term, Bump2),
{Name, [Term2|Terms]};
change_name_in_term({_Mod,Clauses}, {Name, Terms}) ->
Clauses2 = lists:foldl(fun change_name_in_clause/2, {Name, []}, Clauses),
Term2 = {Name, Clauses2},
{Name, [Term2|Terms]}.
change_name_in_clause(Clause, {Name, NewClauses}) ->
{Name, [setelement(1, Clause, Name)|NewClauses]}.
replace_string(File, Old, New) ->
Old2 = atom_to_list(Old),
New2 = atom_to_list(New),
re:replace(File, Old2, New2, [{return, list}]).
read_cover_file(File) ->
{ok, Fd} = file:open(File, [read, binary, raw]),
Terms = get_terms(Fd, []),
ok = file:close(Fd),
Terms.
get_terms(Fd, Terms) ->
case cover:get_term(Fd) of
eof -> Terms;
Term -> get_terms(Fd, [Term|Terms])
end.
write_terms(File, Terms) ->
{ok, Fd} = file:open(File, [write, binary, raw]),
lists:foreach(write_term(Fd), Terms),
ok.
write_term(Fd) ->
fun(Term) -> cover:write(Term, Fd) end.