diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | dialyzer_reference | 2 | ||||
-rw-r--r-- | ebin/rebar.app | 2 | ||||
-rw-r--r-- | include/rebar.hrl | 2 | ||||
-rw-r--r-- | priv/shell-completion/bash/rebar | 4 | ||||
-rw-r--r-- | rebar.config.sample | 5 | ||||
-rw-r--r-- | src/rebar.erl | 11 | ||||
-rw-r--r-- | src/rebar_erlc_compiler.erl | 109 | ||||
-rw-r--r-- | src/rebar_eunit.erl | 142 | ||||
-rw-r--r-- | src/rebar_qc.erl | 140 | ||||
-rw-r--r-- | src/rebar_utils.erl | 11 | ||||
-rw-r--r-- | test/rebar_eunit_tests.erl | 26 |
13 files changed, 309 insertions, 151 deletions
@@ -5,8 +5,6 @@ rebar .*.swp rt.work .hgignore -.eunit +.test dialyzer_warnings -xref_warnings rebar.cmd -rebar.ps1 @@ -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: %% <ul> %% <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. %% 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, "<html><head><title>Coverage Summary</title></head>\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], |