summaryrefslogtreecommitdiff
path: root/deps/meck/src/meck_cover.erl
blob: da8888c8e43705502632bc631708f3d193f82adc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
%%==============================================================================
%% 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 `<name>_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.