Add 'qc' cmd and rename eunit-compile to test-compile

This commit is contained in:
Tuncer Ayaz 2011-03-26 17:56:54 +01:00
parent 902dcdf3ff
commit e75a97ad33
13 changed files with 309 additions and 151 deletions

4
.gitignore vendored
View file

@ -5,8 +5,6 @@ rebar
.*.swp .*.swp
rt.work rt.work
.hgignore .hgignore
.eunit .test
dialyzer_warnings dialyzer_warnings
xref_warnings
rebar.cmd rebar.cmd
rebar.ps1

View file

@ -7,7 +7,7 @@ all:
./bootstrap ./bootstrap
clean: clean:
@rm -rf rebar ebin/*.beam inttest/rt.work rt.work .eunit @rm -rf rebar ebin/*.beam inttest/rt.work rt.work .test
debug: debug:
@./bootstrap debug @./bootstrap debug

View file

@ -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

View file

@ -27,6 +27,7 @@
rebar_otp_app, rebar_otp_app,
rebar_port_compiler, rebar_port_compiler,
rebar_protobuffs_compiler, rebar_protobuffs_compiler,
rebar_qc,
rebar_rel_utils, rebar_rel_utils,
rebar_reltool, rebar_reltool,
rebar_require_vsn, rebar_require_vsn,
@ -73,6 +74,7 @@
rebar_otp_app, rebar_otp_app,
rebar_ct, rebar_ct,
rebar_eunit, rebar_eunit,
rebar_qc,
rebar_escripter, rebar_escripter,
rebar_edoc, rebar_edoc,
rebar_shell, rebar_shell,

View file

@ -10,3 +10,5 @@
-define(ERROR(Str, Args), rebar_log:log(error, Str, Args)). -define(ERROR(Str, Args), rebar_log:log(error, Str, Args)).
-define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))).
-define(TEST_DIR, ".test").

View file

@ -9,8 +9,8 @@ _rebar()
sopts="-h -c -v -V -f -j" sopts="-h -c -v -V -f -j"
lopts=" --help --commands --verbose --force --jobs= --version" lopts=" --help --commands --verbose --force --jobs= --version"
cmdsnvars="check-deps clean compile create create-app create-node ct \ cmdsnvars="check-deps clean compile create create-app create-node ct \
doc delete-deps escriptize eunit eunit-compile get-deps generate \ doc delete-deps escriptize eunit get-deps generate generate-upgrade \
generate-upgrade help list-deps list-templates update-deps version \ help list-deps list-templates qc test-compile update-deps version \
xref overlay apps= case= force=1 jobs= suites= verbose=1 appid= \ xref overlay apps= case= force=1 jobs= suites= verbose=1 appid= \
previous_release= nodeid= root_dir= skip_deps=true skip_apps= \ previous_release= nodeid= root_dir= skip_deps=true skip_apps= \
template= template_dir=" template= template_dir="

View file

@ -88,6 +88,11 @@
%% Option to use short names (i.e., -sname test) when starting ct %% Option to use short names (i.e., -sname test) when starting ct
{ct_use_short_names, true}. {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 == %% == Cleanup ==
%% Which files to cleanup %% Which files to cleanup

View file

@ -297,10 +297,12 @@ generate-upgrade previous_release=path Build an upgrade package
generate-appups previous_release=path Generate appup files 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 [suites=foo] Run eunit [test/foo_tests.erl] tests
eunit-compile Compile sources for EUnit run
ct [suites=] [case=] Run common_test suites ct [suites=] [case=] Run common_test suites
qc Test QuichCheck properties
xref Run cross reference analysis xref Run cross reference analysis
help Show the program options help Show the program options
@ -362,9 +364,10 @@ filter_flags(Config, [Item | Rest], Commands) ->
command_names() -> command_names() ->
["check-deps", "clean", "compile", "create", "create-app", "create-node", ["check-deps", "clean", "compile", "create", "create-app", "create-node",
"ct", "delete-deps", "doc", "eunit", "eunit-compile", "generate", "ct", "delete-deps", "doc", "eunit", "generate", "generate-appups",
"generate-appups", "generate-upgrade", "get-deps", "help", "list-deps", "generate-upgrade", "get-deps", "help", "list-deps", "list-templates",
"list-templates", "update-deps", "overlay", "shell", "version", "xref"]. "test-compile", "qc", "update-deps", "overlay", "shell", "version",
"xref"].
unabbreviate_command_names([]) -> unabbreviate_command_names([]) ->
[]; [];

View file

@ -29,9 +29,8 @@
-export([compile/2, -export([compile/2,
clean/2]). clean/2]).
%% for internal use by only eunit %% for internal use by only eunit and qc
-export([doterl_compile/2, -export([test_compile/1]).
doterl_compile/3]).
-include("rebar.hrl"). -include("rebar.hrl").
@ -111,11 +110,113 @@ clean(_Config, _AppFile) ->
lists:foreach(fun(Dir) -> delete_dir(Dir, dirs(Dir)) end, dirs("ebin")), lists:foreach(fun(Dir) -> delete_dir(Dir, dirs(Dir)) end, dirs("ebin")),
ok. 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(), -spec doterl_compile(Config::rebar_config:config(),
OutDir::file:filename()) -> 'ok'. OutDir::file:filename()) -> 'ok'.
doterl_compile(Config, OutDir) -> doterl_compile(Config, OutDir) ->

View file

@ -28,7 +28,7 @@
%% @doc rebar_eunit supports the following commands: %% @doc rebar_eunit supports the following commands:
%% <ul> %% <ul>
%% <li>eunit - runs eunit tests</li> %% <li>eunit - runs eunit tests</li>
%% <li>clean - remove .eunit directory</li> %% <li>clean - remove ?TEST_DIR directory</li>
%% <li>reset_after_eunit::boolean() - default = true. %% <li>reset_after_eunit::boolean() - default = true.
%% If true, try to "reset" VM state to approximate state prior to %% If true, try to "reset" VM state to approximate state prior to
%% running the EUnit tests: %% running the EUnit tests:
@ -55,12 +55,10 @@
-export([eunit/2, -export([eunit/2,
clean/2, clean/2,
'eunit-compile'/2]). 'test-compile'/2]).
-include("rebar.hrl"). -include("rebar.hrl").
-define(EUNIT_DIR, ".eunit").
%% =================================================================== %% ===================================================================
%% Public API %% Public API
%% =================================================================== %% ===================================================================
@ -70,9 +68,9 @@ eunit(Config, _AppFile) ->
%% Save code path %% Save code path
CodePath = setup_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 %% cover and eunit testing. Normally you can just tell cover
%% and/or eunit to scan the directory for you, but eunit does a %% and/or eunit to scan the directory for you, but eunit does a
%% code:purge in conjunction with that scan and causes any cover %% 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 %% eunit won't doubly run them and so cover only calculates
%% coverage on production code. However, keep "*_tests" modules %% coverage on production code. However, keep "*_tests" modules
%% that are not automatically included by eunit. %% that are not automatically included by eunit.
AllBeamFiles = rebar_utils:beams(?EUNIT_DIR), AllBeamFiles = rebar_utils:beams(?TEST_DIR),
{BeamFiles, TestBeamFiles} = {BeamFiles, TestBeamFiles} =
lists:partition(fun(N) -> string:str(N, "_tests.beam") =:= 0 end, lists:partition(fun(N) -> string:str(N, "_tests.beam") =:= 0 end,
AllBeamFiles), AllBeamFiles),
OtherBeamFiles = TestBeamFiles -- OtherBeamFiles = TestBeamFiles --
[filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles], [filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
ModuleBeamFiles = BeamFiles ++ OtherBeamFiles, 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], SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
FilteredModules = filter_modules(Config, Modules), FilteredModules = filter_modules(Config, Modules),
@ -119,13 +117,13 @@ eunit(Config, _AppFile) ->
ok. ok.
clean(_Config, _File) -> 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(), ok = ensure_dirs(),
%% Save code path %% Save code path
CodePath = setup_code_path(), CodePath = setup_code_path(),
{ok, _SrcErls} = eunit_compile(Config), {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config),
%% Restore code path %% Restore code path
true = code:set_path(CodePath), true = code:set_path(CodePath),
ok. ok.
@ -134,57 +132,10 @@ clean(_Config, _File) ->
%% Internal functions %% 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() -> ensure_dirs() ->
%% Make sure ?EUNIT_DIR/ and ebin/ directory exists (append dummy module) %% Make sure ?TEST_DIR/ and ebin/ directory exists (append dummy module)
ok = filelib:ensure_dir(filename:join(eunit_dir(), "dummy")), ok = filelib:ensure_dir(filename:join(rebar_utils:test_dir(), "dummy")),
ok = filelib:ensure_dir(filename:join(ebin_dir(), "dummy")). ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
setup_code_path() -> setup_code_path() ->
%% Setup code path prior to compilation so that parse_transforms %% 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 %% to the END of the code path so that we don't have to jump
%% through hoops to access the .app file %% through hoops to access the .app file
CodePath = code:get_path(), CodePath = code:get_path(),
true = code:add_patha(eunit_dir()), true = code:add_patha(rebar_utils:test_dir()),
true = code:add_pathz(ebin_dir()), true = code:add_pathz(rebar_utils:ebin_dir()),
CodePath. CodePath.
eunit_dir() ->
filename:join(rebar_utils:get_cwd(), ?EUNIT_DIR).
ebin_dir() ->
filename:join(rebar_utils:get_cwd(), "ebin").
filter_modules(Config, Modules) -> filter_modules(Config, Modules) ->
RawSuites = rebar_utils:get_deprecated_global(Config, suite, suites, RawSuites = rebar_utils:get_deprecated_global(Config, suite, suites,
[], "soon"), [], "soon"),
@ -216,10 +161,10 @@ filter_modules1(Modules, Suites) ->
perform_eunit(Config, FilteredModules) -> perform_eunit(Config, FilteredModules) ->
EunitOpts = get_eunit_opts(Config), 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) %% are created there (versus in the source dir)
Cwd = rebar_utils:get_cwd(), Cwd = rebar_utils:get_cwd(),
ok = file:set_cwd(?EUNIT_DIR), ok = file:set_cwd(?TEST_DIR),
EunitResult = (catch eunit:test(FilteredModules, EunitOpts)), EunitResult = (catch eunit:test(FilteredModules, EunitOpts)),
@ -239,51 +184,6 @@ get_eunit_opts(Config) ->
BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []). 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(Config, BeamFiles, SrcModules) ->
perform_cover(rebar_config:get(Config, cover_enabled, false), perform_cover(rebar_config:get(Config, cover_enabled, false),
Config, BeamFiles, SrcModules). Config, BeamFiles, SrcModules).
@ -308,7 +208,7 @@ cover_analyze(Config, FilteredModules, SrcModules) ->
[html]) [html])
end, Coverage), 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]), ?CONSOLE("Cover analysis: ~s\n", [Index]),
%% Print coverage report, if configured %% Print coverage report, if configured
@ -328,7 +228,7 @@ cover_init(false, _BeamFiles) ->
{ok, not_enabled}; {ok, not_enabled};
cover_init(true, BeamFiles) -> cover_init(true, BeamFiles) ->
%% Attempt to start the cover server, then set it's group leader to %% 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 %% to stdout. If the cover server is already started we'll reuse that
%% pid. %% pid.
{ok, CoverPid} = case cover:start() of {ok, CoverPid} = case cover:start() of
@ -341,7 +241,7 @@ cover_init(true, BeamFiles) ->
end, end,
{ok, F} = OkOpen = file:open( {ok, F} = OkOpen = file:open(
filename:join([?EUNIT_DIR, "cover.log"]), filename:join([?TEST_DIR, "cover.log"]),
[write]), [write]),
group_leader(F, CoverPid), group_leader(F, CoverPid),
@ -416,7 +316,7 @@ align_notcovered_count(Module, Covered, NotCovered, true) ->
{Module, Covered, NotCovered - 1}. {Module, Covered, NotCovered - 1}.
cover_write_index(Coverage, SrcModules) -> 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, "<html><head><title>Coverage Summary</title></head>\n"), ok = file:write(F, "<html><head><title>Coverage Summary</title></head>\n"),
IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end, IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end,
{SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage), {SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage),
@ -474,7 +374,7 @@ cover_print_coverage(Coverage) ->
?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]). ?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]).
cover_file(Module) -> 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) -> percentage(0, 0) ->
"not executed"; "not executed";

140
src/rebar_qc.erl Normal file
View file

@ -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)).

View file

@ -49,8 +49,9 @@
get_deprecated_local/4, get_deprecated_local/5, get_deprecated_local/4, get_deprecated_local/5,
delayed_halt/1, delayed_halt/1,
erl_opts/1, erl_opts/1,
src_dirs/1 src_dirs/1,
]). test_dir/0,
ebin_dir/0]).
-include("rebar.hrl"). -include("rebar.hrl").
@ -306,6 +307,12 @@ src_dirs([]) ->
src_dirs(SrcDirs) -> src_dirs(SrcDirs) ->
SrcDirs. SrcDirs.
test_dir() ->
filename:join(rebar_utils:get_cwd(), ?TEST_DIR).
ebin_dir() ->
filename:join(rebar_utils:get_cwd(), "ebin").
%% ==================================================================== %% ====================================================================
%% Internal functions %% Internal functions
%% ==================================================================== %% ====================================================================

View file

@ -35,7 +35,7 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
%% Assuming this test is run inside the rebar 'eunit' %% 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(REBAR_SCRIPT, "../rebar").
-define(TMP_DIR, "tmp_eunit/"). -define(TMP_DIR, "tmp_eunit/").
@ -70,7 +70,7 @@ cover_test_() ->
{"Only production modules get coverage reports", {"Only production modules get coverage reports",
assert_files_not_in("the temporary eunit directory", assert_files_not_in("the temporary eunit directory",
[".eunit/myapp_mymod_tests.COVER.html"])}]}. [".test/myapp_mymod_tests.COVER.html"])}]}.
cover_with_suite_test_() -> cover_with_suite_test_() ->
{"Ensure Cover runs with Tests in a test dir and a test suite", {"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", [{"Cover reports are generated for module",
assert_files_in("the temporary eunit directory", assert_files_in("the temporary eunit directory",
[".eunit/index.html", [".test/index.html",
".eunit/mysuite.COVER.html"])}, ".test/mysuite.COVER.html"])},
{"Only production modules get coverage reports", {"Only production modules get coverage reports",
assert_files_not_in("the temporary eunit directory", assert_files_not_in("the temporary eunit directory",
[".eunit/myapp_app.COVER.html", [".test/myapp_app.COVER.html",
".eunit/myapp_mymod.COVER.html", ".test/myapp_mymod.COVER.html",
".eunit/myapp_sup.COVER.html", ".test/myapp_sup.COVER.html",
".eunit/myapp_mymod_tests.COVER.html"])}]}. ".test/myapp_mymod_tests.COVER.html"])}]}.
expected_cover_generated_files() -> expected_cover_generated_files() ->
[".eunit/index.html", [".test/index.html",
".eunit/myapp_app.COVER.html", ".test/myapp_app.COVER.html",
".eunit/myapp_mymod.COVER.html", ".test/myapp_mymod.COVER.html",
".eunit/myapp_sup.COVER.html"]. ".test/myapp_sup.COVER.html"].
cover_coverage_test_() -> cover_coverage_test_() ->
{"Coverage is accurately calculated", {"Coverage is accurately calculated",
@ -246,7 +246,7 @@ assert_files_not_in(_, []) -> [].
assert_full_coverage(Mod) -> assert_full_coverage(Mod) ->
fun() -> 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"), Result = [X || X <- string:tokens(binary_to_list(F), "\n"),
string:str(X, Mod) =/= 0, string:str(X, Mod) =/= 0,
string:str(X, "100%") =/= 0], string:str(X, "100%") =/= 0],