1%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- 2%% ex: ts=4 sw=4 et 3%% ------------------------------------------------------------------- 4%% 5%% rebar: Erlang Build Tools 6%% 7%% Copyright (c) 2011-2016 Tuncer Ayaz 8%% 9%% Permission is hereby granted, free of charge, to any person obtaining a copy 10%% of this software and associated documentation files (the "Software"), to deal 11%% in the Software without restriction, including without limitation the rights 12%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13%% copies of the Software, and to permit persons to whom the Software is 14%% furnished to do so, subject to the following conditions: 15%% 16%% The above copyright notice and this permission notice shall be included in 17%% all copies or substantial portions of the Software. 18%% 19%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25%% THE SOFTWARE. 26%% ------------------------------------------------------------------- 27-module(rebar_qc). 28 29-export([qc/2, 30 triq/2, 31 eqc/2, 32 clean/2]). 33 34%% for internal use only 35-export([info/2]). 36 37-include("rebar.hrl"). 38 39-define(QC_DIR, ".qc"). 40 41%% =================================================================== 42%% Public API 43%% =================================================================== 44 45qc(Config, _AppFile) -> 46 run_qc(Config, qc_opts(Config)). 47 48triq(Config, _AppFile) -> 49 ok = load_qc_mod(triq), 50 run_qc(Config, qc_opts(Config), triq). 51 52eqc(Config, _AppFile) -> 53 ok = load_qc_mod(eqc), 54 run_qc(Config, qc_opts(Config), eqc). 55 56clean(_Config, _File) -> 57 rebar_file_utils:rm_rf(?QC_DIR). 58 59%% =================================================================== 60%% Internal functions 61%% =================================================================== 62 63info(help, qc) -> 64 ?CONSOLE( 65 "Test QuickCheck properties.~n" 66 "~n" 67 "Valid rebar.config options:~n" 68 " {qc_opts, [{qc_mod, module()}, Options]}~n" 69 " ~p~n" 70 " ~p~n" 71 " ~p~n" 72 " ~p~n" 73 " ~p~n" 74 "Valid command line options:~n" 75 " compile_only=true (Compile but do not test properties)", 76 [ 77 {qc_compile_opts, []}, 78 {qc_first_files, []}, 79 {cover_enabled, false}, 80 {cover_print_enabled, false}, 81 {cover_export_enabled, false} 82 ]); 83info(help, clean) -> 84 Description = ?FMT("Delete QuickCheck test dir (~s)", [?QC_DIR]), 85 ?CONSOLE("~s.~n", [Description]). 86 87-define(TRIQ_MOD, triq). 88-define(EQC_MOD, eqc). 89 90qc_opts(Config) -> 91 rebar_config:get(Config, qc_opts, []). 92 93run_qc(Config, QCOpts) -> 94 run_qc(Config, QCOpts, select_qc_mod(QCOpts)). 95 96run_qc(Config, RawQCOpts, QC) -> 97 ?DEBUG("Selected QC module: ~p~n", [QC]), 98 QCOpts = lists:filter(fun({qc_mod, _}) -> false; 99 (_) -> true 100 end, RawQCOpts), 101 run(Config, QC, QCOpts). 102 103select_qc_mod(QCOpts) -> 104 case proplists:get_value(qc_mod, QCOpts) of 105 undefined -> 106 detect_qc_mod(); 107 QC -> 108 case code:ensure_loaded(QC) of 109 {module, QC} -> 110 QC; 111 {error, nofile} -> 112 ?ABORT("Configured QC library '~p' not available~n", [QC]) 113 end 114 end. 115 116detect_qc_mod() -> 117 case code:ensure_loaded(?TRIQ_MOD) of 118 {module, ?TRIQ_MOD} -> 119 ?TRIQ_MOD; 120 {error, nofile} -> 121 case code:ensure_loaded(?EQC_MOD) of 122 {module, ?EQC_MOD} -> 123 ?EQC_MOD; 124 {error, nofile} -> 125 ?ABORT("No QC library available~n", []) 126 end 127 end. 128 129load_qc_mod(Mod) -> 130 case code:ensure_loaded(Mod) of 131 {module, Mod} -> 132 ok; 133 {error, nofile} -> 134 ?ABORT("Failed to load QC lib '~p'~n", [Mod]) 135 end. 136 137ensure_dirs() -> 138 ok = filelib:ensure_dir(filename:join(qc_dir(), "dummy")), 139 ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")). 140 141setup_codepath() -> 142 CodePath = code:get_path(), 143 true = code:add_patha(qc_dir()), 144 true = code:add_pathz(rebar_utils:ebin_dir()), 145 CodePath. 146 147qc_dir() -> 148 filename:join(rebar_utils:get_cwd(), ?QC_DIR). 149 150run(Config, QC, QCOpts) -> 151 ?DEBUG("qc_opts: ~p~n", [QCOpts]), 152 153 ok = ensure_dirs(), 154 CodePath = setup_codepath(), 155 156 CompileOnly = rebar_config:get_global(Config, compile_only, false), 157 %% Compile erlang code to ?QC_DIR, using a tweaked config 158 %% with appropriate defines, and include all the test modules 159 %% as well. 160 {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "qc", ?QC_DIR), 161 162 case CompileOnly of 163 "true" -> 164 true = rebar_utils:cleanup_code_path(CodePath), 165 ?CONSOLE("Compiled modules for qc~n", []); 166 false -> 167 run1(QC, QCOpts, Config, CodePath, SrcErls) 168 end. 169 170run1(QC, QCOpts, Config, CodePath, SrcErls) -> 171 172 AllBeamFiles = rebar_utils:beams(?QC_DIR), 173 AllModules = [rebar_utils:beam_to_mod(?QC_DIR, N) 174 || N <- AllBeamFiles], 175 PropMods = find_prop_mods(), 176 FilteredModules = AllModules -- PropMods, 177 178 SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls], 179 180 {ok, CoverLog} = rebar_cover_utils:init(Config, AllBeamFiles, qc_dir()), 181 182 TestModule = fun(M) -> qc_module(QC, QCOpts, M) end, 183 QCResult = lists:flatmap(TestModule, PropMods), 184 185 rebar_cover_utils:perform_cover(Config, FilteredModules, SrcModules, 186 qc_dir()), 187 rebar_cover_utils:close(CoverLog), 188 ok = rebar_cover_utils:exit(), 189 190 true = rebar_utils:cleanup_code_path(CodePath), 191 192 case QCResult of 193 [] -> 194 ok; 195 Errors -> 196 ?ABORT("One or more QC properties didn't hold true:~n~p~n", 197 [Errors]) 198 end. 199 200qc_module(QC=triq, _QCOpts, M) -> 201 case QC:module(M) of 202 true -> 203 []; 204 Failed -> 205 [Failed] 206 end; 207qc_module(QC=eqc, [], M) -> QC:module(M); 208qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M). 209 210find_prop_mods() -> 211 Beams = rebar_utils:find_files_by_ext(?QC_DIR, ".beam"), 212 [M || M <- [rebar_utils:erl_to_mod(Beam) || Beam <- Beams], has_prop(M)]. 213 214has_prop(Mod) -> 215 lists:any(fun({F,_A}) -> lists:prefix("prop_", atom_to_list(F)) end, 216 Mod:module_info(exports)). 217