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: %%