diff --git a/.gitignore b/.gitignore
index 8852dc9..14cd88e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,6 @@ rebar
.*.swp
rt.work
.hgignore
-.eunit
+.test
dialyzer_warnings
-xref_warnings
rebar.cmd
-rebar.ps1
diff --git a/Makefile b/Makefile
index 68f0f52..3f7b8ee 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ all:
./bootstrap
clean:
- @rm -rf rebar ebin/*.beam inttest/rt.work rt.work .eunit
+ @rm -rf rebar ebin/*.beam inttest/rt.work rt.work .test
debug:
@./bootstrap debug
diff --git a/dialyzer_reference b/dialyzer_reference
index 22d1e81..61f6fce 100644
--- a/dialyzer_reference
+++ b/dialyzer_reference
@@ -1,2 +1,2 @@
-rebar_utils.erl:161: Call to missing or unexported function escript:foldl/3
+rebar_utils.erl:162: Call to missing or unexported function escript:foldl/3
diff --git a/ebin/rebar.app b/ebin/rebar.app
index b5e1be3..f4444a3 100644
--- a/ebin/rebar.app
+++ b/ebin/rebar.app
@@ -27,6 +27,7 @@
rebar_otp_app,
rebar_port_compiler,
rebar_protobuffs_compiler,
+ rebar_qc,
rebar_rel_utils,
rebar_reltool,
rebar_require_vsn,
@@ -73,6 +74,7 @@
rebar_otp_app,
rebar_ct,
rebar_eunit,
+ rebar_qc,
rebar_escripter,
rebar_edoc,
rebar_shell,
diff --git a/include/rebar.hrl b/include/rebar.hrl
index 7568898..e84bb69 100644
--- a/include/rebar.hrl
+++ b/include/rebar.hrl
@@ -10,3 +10,5 @@
-define(ERROR(Str, Args), rebar_log:log(error, Str, Args)).
-define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))).
+
+-define(TEST_DIR, ".test").
diff --git a/priv/shell-completion/bash/rebar b/priv/shell-completion/bash/rebar
index 968287c..0801fd1 100644
--- a/priv/shell-completion/bash/rebar
+++ b/priv/shell-completion/bash/rebar
@@ -9,8 +9,8 @@ _rebar()
sopts="-h -c -v -V -f -j"
lopts=" --help --commands --verbose --force --jobs= --version"
cmdsnvars="check-deps clean compile create create-app create-node ct \
- doc delete-deps escriptize eunit eunit-compile get-deps generate \
- generate-upgrade help list-deps list-templates update-deps version \
+ doc delete-deps escriptize eunit get-deps generate generate-upgrade \
+ help list-deps list-templates qc test-compile update-deps version \
xref overlay apps= case= force=1 jobs= suites= verbose=1 appid= \
previous_release= nodeid= root_dir= skip_deps=true skip_apps= \
template= template_dir="
diff --git a/rebar.config.sample b/rebar.config.sample
index 0e846f9..9082808 100644
--- a/rebar.config.sample
+++ b/rebar.config.sample
@@ -88,6 +88,11 @@
%% Option to use short names (i.e., -sname test) when starting ct
{ct_use_short_names, true}.
+%% == QuickCheck ==
+
+%% If qc_mod is unspecified, rebar tries to detect Triq or EQC
+{qc_opts, [{qc_mod, module()}, Options]}.
+
%% == Cleanup ==
%% Which files to cleanup
diff --git a/src/rebar.erl b/src/rebar.erl
index 54e27b6..cd285df 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -297,10 +297,12 @@ generate-upgrade previous_release=path Build an upgrade package
generate-appups previous_release=path Generate appup files
+test-compile Compile sources for eunit/qc run
eunit [suites=foo] Run eunit [test/foo_tests.erl] tests
-eunit-compile Compile sources for EUnit run
ct [suites=] [case=] Run common_test suites
+qc Test QuichCheck properties
+
xref Run cross reference analysis
help Show the program options
@@ -362,9 +364,10 @@ filter_flags(Config, [Item | Rest], Commands) ->
command_names() ->
["check-deps", "clean", "compile", "create", "create-app", "create-node",
- "ct", "delete-deps", "doc", "eunit", "eunit-compile", "generate",
- "generate-appups", "generate-upgrade", "get-deps", "help", "list-deps",
- "list-templates", "update-deps", "overlay", "shell", "version", "xref"].
+ "ct", "delete-deps", "doc", "eunit", "generate", "generate-appups",
+ "generate-upgrade", "get-deps", "help", "list-deps", "list-templates",
+ "test-compile", "qc", "update-deps", "overlay", "shell", "version",
+ "xref"].
unabbreviate_command_names([]) ->
[];
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 6f7e3a3..b835053 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -29,9 +29,8 @@
-export([compile/2,
clean/2]).
-%% for internal use by only eunit
--export([doterl_compile/2,
- doterl_compile/3]).
+%% for internal use by only eunit and qc
+-export([test_compile/1]).
-include("rebar.hrl").
@@ -111,11 +110,113 @@ clean(_Config, _AppFile) ->
lists:foreach(fun(Dir) -> delete_dir(Dir, dirs(Dir)) end, dirs("ebin")),
ok.
+%% ===================================================================
+%% .erl Compilation API (externally used by only eunit and qc)
+%% ===================================================================
+
+test_compile(Config) ->
+ %% Obtain all the test modules for inclusion in the compile stage.
+ %% Notice: this could also be achieved with the following
+ %% rebar.config option: {eunit_compile_opts, [{src_dirs, ["test"]}]}
+ TestErls = rebar_utils:find_files("test", ".*\\.erl\$"),
+
+ %% Copy source files to eunit dir for cover in case they are not directly
+ %% in src but in a subdirectory of src. Cover only looks in cwd and ../src
+ %% for source files. Also copy files from src_dirs.
+ ErlOpts = rebar_utils:erl_opts(Config),
+
+ SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)),
+ SrcErls = lists:foldl(
+ fun(Dir, Acc) ->
+ Files = rebar_utils:find_files(Dir, ".*\\.erl\$"),
+ lists:append(Acc, Files)
+ end, [], SrcDirs),
+
+ %% If it is not the first time rebar eunit is executed, there will be source
+ %% files already present in ?TEST_DIR. Since some SCMs (like Perforce) set
+ %% the source files as being read only (unless they are checked out), we
+ %% need to be sure that the files already present in ?TEST_DIR are writable
+ %% before doing the copy. This is done here by removing any file that was
+ %% already present before calling rebar_file_utils:cp_r.
+
+ %% Get the full path to a file that was previously copied in ?TEST_DIR
+ ToCleanUp = fun(F, Acc) ->
+ F2 = filename:basename(F),
+ F3 = filename:join([?TEST_DIR, F2]),
+ case filelib:is_regular(F3) of
+ true -> [F3|Acc];
+ false -> Acc
+ end
+ end,
+
+ ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], TestErls)),
+ ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], SrcErls)),
+
+ ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, ?TEST_DIR),
+
+ %% Compile erlang code to ?TEST_DIR, using a tweaked config
+ %% with appropriate defines for eunit, and include all the test modules
+ %% as well.
+ ok = doterl_compile(test_compile_config(Config), ?TEST_DIR, TestErls),
+
+ {ok, SrcErls}.
%% ===================================================================
-%% .erl Compilation API (externally used by only eunit)
+%% Internal functions
%% ===================================================================
+test_compile_config(Config) ->
+ {Config1, TriqOpts} = triq_opts(Config),
+ {Config2, PropErOpts} = proper_opts(Config1),
+ {Config3, EqcOpts} = eqc_opts(Config2),
+
+ ErlOpts = rebar_config:get_list(Config3, erl_opts, []),
+ EunitOpts = rebar_config:get_list(Config3, eunit_compile_opts, []),
+ Opts0 = [{d, 'TEST'}] ++
+ ErlOpts ++ EunitOpts ++ TriqOpts ++ PropErOpts ++ EqcOpts,
+ Opts = [O || O <- Opts0, O =/= no_debug_info],
+ Config4 = rebar_config:set(Config3, erl_opts, Opts),
+
+ FirstErls = rebar_config:get_list(Config4, eunit_first_files, []),
+ rebar_config:set(Config4, erl_first_files, FirstErls).
+
+triq_opts(Config) ->
+ {NewConfig, IsAvail} = is_lib_avail(Config, is_triq_avail, triq,
+ "triq.hrl", "Triq"),
+ Opts = define_if('TRIQ', IsAvail),
+ {NewConfig, Opts}.
+
+proper_opts(Config) ->
+ {NewConfig, IsAvail} = is_lib_avail(Config, is_proper_avail, proper,
+ "proper.hrl", "PropEr"),
+ Opts = define_if('PROPER', IsAvail),
+ {NewConfig, Opts}.
+
+eqc_opts(Config) ->
+ {NewConfig, IsAvail} = is_lib_avail(Config, is_eqc_avail, eqc,
+ "eqc.hrl", "QuickCheck"),
+ Opts = define_if('EQC', IsAvail),
+ {NewConfig, Opts}.
+
+define_if(Def, true) -> [{d, Def}];
+define_if(_Def, false) -> [].
+
+is_lib_avail(Config, DictKey, Mod, Hrl, Name) ->
+ case rebar_config:get_xconf(Config, DictKey, undefined) of
+ undefined ->
+ IsAvail = case code:lib_dir(Mod, include) of
+ {error, bad_name} ->
+ false;
+ Dir ->
+ filelib:is_regular(filename:join(Dir, Hrl))
+ end,
+ NewConfig = rebar_config:set_xconf(Config, DictKey, IsAvail),
+ ?DEBUG("~s availability: ~p\n", [Name, IsAvail]),
+ {NewConfig, IsAvail};
+ IsAvail ->
+ {Config, IsAvail}
+ end.
+
-spec doterl_compile(Config::rebar_config:config(),
OutDir::file:filename()) -> 'ok'.
doterl_compile(Config, OutDir) ->
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
index b1e061b..96f1e5f 100644
--- a/src/rebar_eunit.erl
+++ b/src/rebar_eunit.erl
@@ -28,7 +28,7 @@
%% @doc rebar_eunit supports the following commands:
%%
%% - eunit - runs eunit tests
-%% - clean - remove .eunit directory
+%% - clean - remove ?TEST_DIR directory
%% - reset_after_eunit::boolean() - default = true.
%% If true, try to "reset" VM state to approximate state prior to
%% running the EUnit tests:
@@ -55,12 +55,10 @@
-export([eunit/2,
clean/2,
- 'eunit-compile'/2]).
+ 'test-compile'/2]).
-include("rebar.hrl").
--define(EUNIT_DIR, ".eunit").
-
%% ===================================================================
%% Public API
%% ===================================================================
@@ -70,9 +68,9 @@ eunit(Config, _AppFile) ->
%% Save code path
CodePath = setup_code_path(),
- {ok, SrcErls} = eunit_compile(Config),
+ {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config),
- %% Build a list of all the .beams in ?EUNIT_DIR -- use this for
+ %% Build a list of all the .beams in ?TEST_DIR -- use this for
%% cover and eunit testing. Normally you can just tell cover
%% and/or eunit to scan the directory for you, but eunit does a
%% code:purge in conjunction with that scan and causes any cover
@@ -80,14 +78,14 @@ eunit(Config, _AppFile) ->
%% eunit won't doubly run them and so cover only calculates
%% coverage on production code. However, keep "*_tests" modules
%% that are not automatically included by eunit.
- AllBeamFiles = rebar_utils:beams(?EUNIT_DIR),
+ AllBeamFiles = rebar_utils:beams(?TEST_DIR),
{BeamFiles, TestBeamFiles} =
lists:partition(fun(N) -> string:str(N, "_tests.beam") =:= 0 end,
AllBeamFiles),
OtherBeamFiles = TestBeamFiles --
[filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
ModuleBeamFiles = BeamFiles ++ OtherBeamFiles,
- Modules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- ModuleBeamFiles],
+ Modules = [rebar_utils:beam_to_mod(?TEST_DIR, N) || N <- ModuleBeamFiles],
SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
FilteredModules = filter_modules(Config, Modules),
@@ -119,13 +117,13 @@ eunit(Config, _AppFile) ->
ok.
clean(_Config, _File) ->
- rebar_file_utils:rm_rf(?EUNIT_DIR).
+ rebar_file_utils:rm_rf(?TEST_DIR).
-'eunit-compile'(Config, _File) ->
+'test-compile'(Config, _File) ->
ok = ensure_dirs(),
%% Save code path
CodePath = setup_code_path(),
- {ok, _SrcErls} = eunit_compile(Config),
+ {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config),
%% Restore code path
true = code:set_path(CodePath),
ok.
@@ -134,57 +132,10 @@ clean(_Config, _File) ->
%% Internal functions
%% ===================================================================
-eunit_compile(Config) ->
- %% Obtain all the test modules for inclusion in the compile stage.
- %% Notice: this could also be achieved with the following
- %% rebar.config option: {eunit_compile_opts, [{src_dirs, ["test"]}]}
- TestErls = rebar_utils:find_files("test", ".*\\.erl\$"),
-
- %% Copy source files to eunit dir for cover in case they are not directly
- %% in src but in a subdirectory of src. Cover only looks in cwd and ../src
- %% for source files. Also copy files from src_dirs.
- ErlOpts = rebar_utils:erl_opts(Config),
-
- SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)),
- SrcErls = lists:foldl(
- fun(Dir, Acc) ->
- Files = rebar_utils:find_files(Dir, ".*\\.erl\$"),
- lists:append(Acc, Files)
- end, [], SrcDirs),
-
- %% If it is not the first time rebar eunit is executed, there will be source
- %% files already present in ?EUNIT_DIR. Since some SCMs (like Perforce) set
- %% the source files as being read only (unless they are checked out), we
- %% need to be sure that the files already present in ?EUNIT_DIR are writable
- %% before doing the copy. This is done here by removing any file that was
- %% already present before calling rebar_file_utils:cp_r.
-
- %% Get the full path to a file that was previously copied in ?EUNIT_DIR
- ToCleanUp = fun(F, Acc) ->
- F2 = filename:basename(F),
- F3 = filename:join([?EUNIT_DIR, F2]),
- case filelib:is_regular(F3) of
- true -> [F3|Acc];
- false -> Acc
- end
- end,
-
- ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], TestErls)),
- ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], SrcErls)),
-
- ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, ?EUNIT_DIR),
-
- %% Compile erlang code to ?EUNIT_DIR, using a tweaked config
- %% with appropriate defines for eunit, and include all the test modules
- %% as well.
- ok = rebar_erlc_compiler:doterl_compile(eunit_config(Config),
- ?EUNIT_DIR, TestErls),
- {ok, SrcErls}.
-
ensure_dirs() ->
- %% Make sure ?EUNIT_DIR/ and ebin/ directory exists (append dummy module)
- ok = filelib:ensure_dir(filename:join(eunit_dir(), "dummy")),
- ok = filelib:ensure_dir(filename:join(ebin_dir(), "dummy")).
+ %% Make sure ?TEST_DIR/ and ebin/ directory exists (append dummy module)
+ ok = filelib:ensure_dir(filename:join(rebar_utils:test_dir(), "dummy")),
+ ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
setup_code_path() ->
%% Setup code path prior to compilation so that parse_transforms
@@ -192,16 +143,10 @@ setup_code_path() ->
%% to the END of the code path so that we don't have to jump
%% through hoops to access the .app file
CodePath = code:get_path(),
- true = code:add_patha(eunit_dir()),
- true = code:add_pathz(ebin_dir()),
+ true = code:add_patha(rebar_utils:test_dir()),
+ true = code:add_pathz(rebar_utils:ebin_dir()),
CodePath.
-eunit_dir() ->
- filename:join(rebar_utils:get_cwd(), ?EUNIT_DIR).
-
-ebin_dir() ->
- filename:join(rebar_utils:get_cwd(), "ebin").
-
filter_modules(Config, Modules) ->
RawSuites = rebar_utils:get_deprecated_global(Config, suite, suites,
[], "soon"),
@@ -216,10 +161,10 @@ filter_modules1(Modules, Suites) ->
perform_eunit(Config, FilteredModules) ->
EunitOpts = get_eunit_opts(Config),
- %% Move down into ?EUNIT_DIR while we run tests so any generated files
+ %% Move down into ?TEST_DIR while we run tests so any generated files
%% are created there (versus in the source dir)
Cwd = rebar_utils:get_cwd(),
- ok = file:set_cwd(?EUNIT_DIR),
+ ok = file:set_cwd(?TEST_DIR),
EunitResult = (catch eunit:test(FilteredModules, EunitOpts)),
@@ -239,51 +184,6 @@ get_eunit_opts(Config) ->
BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []).
-eunit_config(Config) ->
- {Config1, EqcOpts} = eqc_opts(Config),
- {Config2, PropErOpts} = proper_opts(Config1),
-
- ErlOpts = rebar_config:get_list(Config2, erl_opts, []),
- EunitOpts = rebar_config:get_list(Config2, eunit_compile_opts, []),
- Opts0 = [{d, 'TEST'}] ++
- ErlOpts ++ EunitOpts ++ EqcOpts ++ PropErOpts,
- Opts = [O || O <- Opts0, O =/= no_debug_info],
- Config3 = rebar_config:set(Config2, erl_opts, Opts),
-
- FirstErls = rebar_config:get_list(Config3, eunit_first_files, []),
- rebar_config:set(Config3, erl_first_files, FirstErls).
-
-eqc_opts(Config) ->
- {NewConfig, IsAvail} = is_lib_avail(Config, is_eqc_avail, eqc,
- "eqc.hrl", "QuickCheck"),
- Opts = define_if('EQC', IsAvail),
- {NewConfig, Opts}.
-
-proper_opts(Config) ->
- {NewConfig, IsAvail} = is_lib_avail(Config, is_proper_avail, proper,
- "proper.hrl", "PropEr"),
- Opts = define_if('PROPER', IsAvail),
- {NewConfig, Opts}.
-
-define_if(Def, true) -> [{d, Def}];
-define_if(_Def, false) -> [].
-
-is_lib_avail(Config, DictKey, Mod, Hrl, Name) ->
- case rebar_config:get_xconf(Config, DictKey, undefined) of
- undefined ->
- IsAvail = case code:lib_dir(Mod, include) of
- {error, bad_name} ->
- false;
- Dir ->
- filelib:is_regular(filename:join(Dir, Hrl))
- end,
- NewConfig = rebar_config:set_xconf(Config, DictKey, IsAvail),
- ?DEBUG("~s availability: ~p\n", [Name, IsAvail]),
- {NewConfig, IsAvail};
- IsAvail ->
- {Config, IsAvail}
- end.
-
perform_cover(Config, BeamFiles, SrcModules) ->
perform_cover(rebar_config:get(Config, cover_enabled, false),
Config, BeamFiles, SrcModules).
@@ -308,7 +208,7 @@ cover_analyze(Config, FilteredModules, SrcModules) ->
[html])
end, Coverage),
- Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]),
+ Index = filename:join([rebar_utils:get_cwd(), ?TEST_DIR, "index.html"]),
?CONSOLE("Cover analysis: ~s\n", [Index]),
%% Print coverage report, if configured
@@ -328,7 +228,7 @@ cover_init(false, _BeamFiles) ->
{ok, not_enabled};
cover_init(true, BeamFiles) ->
%% Attempt to start the cover server, then set it's group leader to
- %% .eunit/cover.log, so all cover log messages will go there instead of
+ %% ?TEST_DIR/cover.log, so all cover log messages will go there instead of
%% to stdout. If the cover server is already started we'll reuse that
%% pid.
{ok, CoverPid} = case cover:start() of
@@ -341,7 +241,7 @@ cover_init(true, BeamFiles) ->
end,
{ok, F} = OkOpen = file:open(
- filename:join([?EUNIT_DIR, "cover.log"]),
+ filename:join([?TEST_DIR, "cover.log"]),
[write]),
group_leader(F, CoverPid),
@@ -416,7 +316,7 @@ align_notcovered_count(Module, Covered, NotCovered, true) ->
{Module, Covered, NotCovered - 1}.
cover_write_index(Coverage, SrcModules) ->
- {ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]),
+ {ok, F} = file:open(filename:join([?TEST_DIR, "index.html"]), [write]),
ok = file:write(F, "Coverage Summary\n"),
IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end,
{SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage),
@@ -474,7 +374,7 @@ cover_print_coverage(Coverage) ->
?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]).
cover_file(Module) ->
- filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]).
+ filename:join([?TEST_DIR, atom_to_list(Module) ++ ".COVER.html"]).
percentage(0, 0) ->
"not executed";
diff --git a/src/rebar_qc.erl b/src/rebar_qc.erl
new file mode 100644
index 0000000..20c12ca
--- /dev/null
+++ b/src/rebar_qc.erl
@@ -0,0 +1,140 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2011-2012 Tuncer Ayaz
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_qc).
+
+-export([qc/2, triq/2, eqc/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+qc(Config, _AppFile) ->
+ ?CONSOLE("NOTICE: Using experimental 'qc' command~n", []),
+ run_qc(Config, qc_opts(Config)).
+
+triq(Config, _AppFile) ->
+ ?CONSOLE("NOTICE: Using experimental 'triq' command~n", []),
+ ok = load_qc_mod(triq),
+ run_qc(Config, qc_opts(Config), triq).
+
+eqc(Config, _AppFile) ->
+ ?CONSOLE("NOTICE: Using experimental 'eqc' command~n", []),
+ ok = load_qc_mod(eqc),
+ run_qc(Config, qc_opts(Config), eqc).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+-define(TRIQ_MOD, triq).
+-define(EQC_MOD, eqc).
+
+qc_opts(Config) ->
+ rebar_config:get(Config, qc_opts, []).
+
+run_qc(Config, QCOpts) ->
+ run_qc(Config, QCOpts, select_qc_mod(QCOpts)).
+
+run_qc(Config, RawQCOpts, QC) ->
+ ?DEBUG("Selected QC module: ~p~n", [QC]),
+ QCOpts = lists:filter(fun({qc_mod, _}) -> false;
+ (_) -> true
+ end, RawQCOpts),
+ run(Config, QC, QCOpts).
+
+select_qc_mod(QCOpts) ->
+ case proplists:get_value(qc_mod, QCOpts) of
+ undefined ->
+ detect_qc_mod();
+ QC ->
+ case code:ensure_loaded(QC) of
+ {module, QC} ->
+ QC;
+ {error, nofile} ->
+ ?ABORT("Configured QC library '~p' not available~n", [QC])
+ end
+ end.
+
+detect_qc_mod() ->
+ case code:ensure_loaded(?TRIQ_MOD) of
+ {module, ?TRIQ_MOD} ->
+ ?TRIQ_MOD;
+ {error, nofile} ->
+ case code:ensure_loaded(?EQC_MOD) of
+ {module, ?EQC_MOD} ->
+ ?EQC_MOD;
+ {error, nofile} ->
+ ?ABORT("No QC library available~n", [])
+ end
+ end.
+
+load_qc_mod(Mod) ->
+ case code:ensure_loaded(Mod) of
+ {module, Mod} ->
+ ok;
+ {error, nofile} ->
+ ?ABORT("Failed to load QC lib '~p'~n", [Mod])
+ end.
+
+setup_codepath() ->
+ CodePath = code:get_path(),
+ true = code:add_patha(rebar_utils:test_dir()),
+ true = code:add_patha(rebar_utils:ebin_dir()),
+ CodePath.
+
+run(Config, QC, QCOpts) ->
+ ?DEBUG("qc_opts: ~p~n", [QCOpts]),
+
+ ok = filelib:ensure_dir(?TEST_DIR ++ "/foo"),
+ CodePath = setup_codepath(),
+
+ %% Compile erlang code to ?TEST_DIR, using a tweaked config
+ %% with appropriate defines, and include all the test modules
+ %% as well.
+ {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config),
+
+ case lists:flatten([qc_module(QC, QCOpts, M) || M <- find_prop_mods()]) of
+ [] ->
+ true = code:set_path(CodePath),
+ ok;
+ Errors ->
+ ?ABORT("One or more QC properties didn't hold true:~n~p~n",
+ [Errors])
+ end.
+
+qc_module(QC=triq, _QCOpts, M) -> QC:module(M);
+qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M).
+
+find_prop_mods() ->
+ Beams = rebar_utils:find_files(?TEST_DIR, ".*\\.beam\$"),
+ [M || M <- [rebar_utils:erl_to_mod(Beam) || Beam <- Beams], has_prop(M)].
+
+has_prop(Mod) ->
+ lists:any(fun({F,_A}) -> lists:prefix("prop_", atom_to_list(F)) end,
+ Mod:module_info(exports)).
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index d608ee1..0e94d08 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -49,8 +49,9 @@
get_deprecated_local/4, get_deprecated_local/5,
delayed_halt/1,
erl_opts/1,
- src_dirs/1
- ]).
+ src_dirs/1,
+ test_dir/0,
+ ebin_dir/0]).
-include("rebar.hrl").
@@ -306,6 +307,12 @@ src_dirs([]) ->
src_dirs(SrcDirs) ->
SrcDirs.
+test_dir() ->
+ filename:join(rebar_utils:get_cwd(), ?TEST_DIR).
+
+ebin_dir() ->
+ filename:join(rebar_utils:get_cwd(), "ebin").
+
%% ====================================================================
%% Internal functions
%% ====================================================================
diff --git a/test/rebar_eunit_tests.erl b/test/rebar_eunit_tests.erl
index 72fb3ea..828e1a4 100644
--- a/test/rebar_eunit_tests.erl
+++ b/test/rebar_eunit_tests.erl
@@ -35,7 +35,7 @@
-include_lib("eunit/include/eunit.hrl").
%% Assuming this test is run inside the rebar 'eunit'
-%% command, the current working directory will be '.eunit'
+%% command, the current working directory will be '.test'
-define(REBAR_SCRIPT, "../rebar").
-define(TMP_DIR, "tmp_eunit/").
@@ -70,7 +70,7 @@ cover_test_() ->
{"Only production modules get coverage reports",
assert_files_not_in("the temporary eunit directory",
- [".eunit/myapp_mymod_tests.COVER.html"])}]}.
+ [".test/myapp_mymod_tests.COVER.html"])}]}.
cover_with_suite_test_() ->
{"Ensure Cover runs with Tests in a test dir and a test suite",
@@ -83,21 +83,21 @@ cover_with_suite_test_() ->
[{"Cover reports are generated for module",
assert_files_in("the temporary eunit directory",
- [".eunit/index.html",
- ".eunit/mysuite.COVER.html"])},
+ [".test/index.html",
+ ".test/mysuite.COVER.html"])},
{"Only production modules get coverage reports",
assert_files_not_in("the temporary eunit directory",
- [".eunit/myapp_app.COVER.html",
- ".eunit/myapp_mymod.COVER.html",
- ".eunit/myapp_sup.COVER.html",
- ".eunit/myapp_mymod_tests.COVER.html"])}]}.
+ [".test/myapp_app.COVER.html",
+ ".test/myapp_mymod.COVER.html",
+ ".test/myapp_sup.COVER.html",
+ ".test/myapp_mymod_tests.COVER.html"])}]}.
expected_cover_generated_files() ->
- [".eunit/index.html",
- ".eunit/myapp_app.COVER.html",
- ".eunit/myapp_mymod.COVER.html",
- ".eunit/myapp_sup.COVER.html"].
+ [".test/index.html",
+ ".test/myapp_app.COVER.html",
+ ".test/myapp_mymod.COVER.html",
+ ".test/myapp_sup.COVER.html"].
cover_coverage_test_() ->
{"Coverage is accurately calculated",
@@ -246,7 +246,7 @@ assert_files_not_in(_, []) -> [].
assert_full_coverage(Mod) ->
fun() ->
- {ok, F} = file:read_file(".eunit/index.html"),
+ {ok, F} = file:read_file(".test/index.html"),
Result = [X || X <- string:tokens(binary_to_list(F), "\n"),
string:str(X, Mod) =/= 0,
string:str(X, "100%") =/= 0],