You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
6.7 KiB

  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-2014 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. -export([qc/2, triq/2, eqc/2, clean/2]).
  29. %% for internal use only
  30. -export([info/2]).
  31. -include("rebar.hrl").
  32. -define(QC_DIR, ".qc").
  33. %% ===================================================================
  34. %% Public API
  35. %% ===================================================================
  36. qc(Config, _AppFile) ->
  37. ?CONSOLE("NOTICE: Using experimental 'qc' command~n", []),
  38. run_qc(Config, qc_opts(Config)).
  39. triq(Config, _AppFile) ->
  40. ?CONSOLE("NOTICE: Using experimental 'triq' command~n", []),
  41. ok = load_qc_mod(triq),
  42. run_qc(Config, qc_opts(Config), triq).
  43. eqc(Config, _AppFile) ->
  44. ?CONSOLE("NOTICE: Using experimental 'eqc' command~n", []),
  45. ok = load_qc_mod(eqc),
  46. run_qc(Config, qc_opts(Config), eqc).
  47. clean(_Config, _File) ->
  48. rebar_file_utils:rm_rf(?QC_DIR).
  49. %% ===================================================================
  50. %% Internal functions
  51. %% ===================================================================
  52. info(help, qc) ->
  53. ?CONSOLE(
  54. "Test QuickCheck properties.~n"
  55. "~n"
  56. "Valid rebar.config options:~n"
  57. " {qc_opts, [{qc_mod, module()}, Options]}~n"
  58. " ~p~n"
  59. " ~p~n"
  60. " ~p~n"
  61. " ~p~n"
  62. " ~p~n"
  63. "Valid command line options:~n"
  64. " compile_only=true (Compile but do not test properties)",
  65. [
  66. {qc_compile_opts, []},
  67. {qc_first_files, []},
  68. {cover_enabled, false},
  69. {cover_print_enabled, false},
  70. {cover_export_enabled, false}
  71. ]);
  72. info(help, clean) ->
  73. Description = ?FMT("Delete QuickCheck test dir (~s)", [?QC_DIR]),
  74. ?CONSOLE("~s.~n", [Description]).
  75. -define(TRIQ_MOD, triq).
  76. -define(EQC_MOD, eqc).
  77. qc_opts(Config) ->
  78. rebar_config:get(Config, qc_opts, []).
  79. run_qc(Config, QCOpts) ->
  80. run_qc(Config, QCOpts, select_qc_mod(QCOpts)).
  81. run_qc(Config, RawQCOpts, QC) ->
  82. ?DEBUG("Selected QC module: ~p~n", [QC]),
  83. QCOpts = lists:filter(fun({qc_mod, _}) -> false;
  84. (_) -> true
  85. end, RawQCOpts),
  86. run(Config, QC, QCOpts).
  87. select_qc_mod(QCOpts) ->
  88. case proplists:get_value(qc_mod, QCOpts) of
  89. undefined ->
  90. detect_qc_mod();
  91. QC ->
  92. case code:ensure_loaded(QC) of
  93. {module, QC} ->
  94. QC;
  95. {error, nofile} ->
  96. ?ABORT("Configured QC library '~p' not available~n", [QC])
  97. end
  98. end.
  99. detect_qc_mod() ->
  100. case code:ensure_loaded(?TRIQ_MOD) of
  101. {module, ?TRIQ_MOD} ->
  102. ?TRIQ_MOD;
  103. {error, nofile} ->
  104. case code:ensure_loaded(?EQC_MOD) of
  105. {module, ?EQC_MOD} ->
  106. ?EQC_MOD;
  107. {error, nofile} ->
  108. ?ABORT("No QC library available~n", [])
  109. end
  110. end.
  111. load_qc_mod(Mod) ->
  112. case code:ensure_loaded(Mod) of
  113. {module, Mod} ->
  114. ok;
  115. {error, nofile} ->
  116. ?ABORT("Failed to load QC lib '~p'~n", [Mod])
  117. end.
  118. ensure_dirs() ->
  119. ok = filelib:ensure_dir(filename:join(qc_dir(), "dummy")),
  120. ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
  121. setup_codepath() ->
  122. CodePath = code:get_path(),
  123. true = code:add_patha(qc_dir()),
  124. true = code:add_pathz(rebar_utils:ebin_dir()),
  125. CodePath.
  126. qc_dir() ->
  127. filename:join(rebar_utils:get_cwd(), ?QC_DIR).
  128. run(Config, QC, QCOpts) ->
  129. ?DEBUG("qc_opts: ~p~n", [QCOpts]),
  130. ok = ensure_dirs(),
  131. CodePath = setup_codepath(),
  132. CompileOnly = rebar_config:get_global(Config, compile_only, false),
  133. %% Compile erlang code to ?QC_DIR, using a tweaked config
  134. %% with appropriate defines, and include all the test modules
  135. %% as well.
  136. {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "qc", ?QC_DIR),
  137. case CompileOnly of
  138. "true" ->
  139. true = code:set_path(CodePath),
  140. ?CONSOLE("Compiled modules for qc~n", []);
  141. false ->
  142. run1(QC, QCOpts, Config, CodePath, SrcErls)
  143. end.
  144. run1(QC, QCOpts, Config, CodePath, SrcErls) ->
  145. AllBeamFiles = rebar_utils:beams(?QC_DIR),
  146. AllModules = [rebar_utils:beam_to_mod(?QC_DIR, N)
  147. || N <- AllBeamFiles],
  148. PropMods = find_prop_mods(),
  149. FilteredModules = AllModules -- PropMods,
  150. SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
  151. {ok, CoverLog} = rebar_cover_utils:init(Config, AllBeamFiles, qc_dir()),
  152. TestModule = fun(M) -> qc_module(QC, QCOpts, M) end,
  153. QCResult = lists:flatmap(TestModule, PropMods),
  154. rebar_cover_utils:perform_cover(Config, FilteredModules, SrcModules,
  155. qc_dir()),
  156. rebar_cover_utils:close(CoverLog),
  157. ok = rebar_cover_utils:exit(),
  158. true = code:set_path(CodePath),
  159. case QCResult of
  160. [] ->
  161. ok;
  162. Errors ->
  163. ?ABORT("One or more QC properties didn't hold true:~n~p~n",
  164. [Errors])
  165. end.
  166. qc_module(QC=triq, _QCOpts, M) ->
  167. case QC:module(M) of
  168. true ->
  169. [];
  170. Failed ->
  171. [Failed]
  172. end;
  173. qc_module(QC=eqc, [], M) -> QC:module(M);
  174. qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M).
  175. find_prop_mods() ->
  176. Beams = rebar_utils:find_files(?QC_DIR, ".*\\.beam\$"),
  177. [M || M <- [rebar_utils:erl_to_mod(Beam) || Beam <- Beams], has_prop(M)].
  178. has_prop(Mod) ->
  179. lists:any(fun({F,_A}) -> lists:prefix("prop_", atom_to_list(F)) end,
  180. Mod:module_info(exports)).