%%============================================================================== %% 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. %%============================================================================== %% @doc Module containing functions needed by meck to integrate with cover. -module(meck_cover). %% Interface exports -export([compile_beam/2]). -export([rename_module/2]). %%============================================================================== %% Interface exports %%============================================================================== %% @doc Enabled cover on `_meck_original'. compile_beam(OriginalMod, Bin) -> alter_cover(), {ok, _} = cover:compile_beam(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. %%============================================================================== %% 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. alter_cover() -> case lists:member({compile_beam,2}, cover:module_info(exports)) of true -> ok; false -> Beam = meck_mod:beam_file(cover), AbsCode = meck_mod:abstract_code(Beam), Exports = [{compile_beam, 2}, {get_term, 1}, {write, 2}], AbsCode2 = meck_mod:add_exports(Exports, AbsCode), _Bin = meck_mod:compile_and_load_forms(AbsCode2), ok end. 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.