summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml3
-rw-r--r--README.md86
-rw-r--r--THANKS9
-rwxr-xr-xbootstrap18
-rw-r--r--dialyzer_reference3
-rw-r--r--ebin/rebar.app2
-rw-r--r--include/rebar.hrl2
-rw-r--r--inttest/ct1/rebar.config1
-rw-r--r--inttest/ct2/ct2_rt.erl4
-rw-r--r--inttest/t_custom_config/custom.config1
-rw-r--r--inttest/t_custom_config/t_custom_config_rt.erl9
-rw-r--r--inttest/tdeps1/a.erl1
-rw-r--r--inttest/tdeps1/a.rebar.config1
-rw-r--r--inttest/tdeps1/b.hrl2
-rw-r--r--inttest/tdeps1/b.rebar.config1
-rw-r--r--inttest/tdeps1/c.hrl1
-rw-r--r--inttest/tdeps1/tdeps1_rt.erl3
-rw-r--r--inttest/tdeps2/a.erl1
-rw-r--r--inttest/tdeps2/a.rebar.config1
-rw-r--r--inttest/tdeps2/b.hrl2
-rw-r--r--inttest/tdeps2/b.rebar.config1
-rw-r--r--inttest/tdeps2/c.hrl1
-rw-r--r--inttest/tdeps2/tdeps2_rt.erl3
-rw-r--r--priv/shell-completion/bash/rebar4
-rw-r--r--rebar.config4
-rw-r--r--rebar.config.sample39
-rw-r--r--src/mustache.erl38
-rw-r--r--src/rebar.erl23
-rw-r--r--src/rebar_app_utils.erl24
-rw-r--r--src/rebar_base_compiler.erl20
-rw-r--r--src/rebar_core.erl4
-rw-r--r--src/rebar_ct.erl31
-rw-r--r--src/rebar_deps.erl109
-rw-r--r--src/rebar_edoc.erl2
-rw-r--r--src/rebar_erlc_compiler.erl29
-rw-r--r--src/rebar_escripter.erl56
-rw-r--r--src/rebar_eunit.erl347
-rw-r--r--src/rebar_port_compiler.erl13
-rw-r--r--src/rebar_protobuffs_compiler.erl14
-rw-r--r--src/rebar_qc.erl32
-rw-r--r--src/rebar_reltool.erl30
-rw-r--r--src/rebar_require_vsn.erl29
-rw-r--r--src/rebar_utils.erl66
-rw-r--r--src/rebar_xref.erl18
-rw-r--r--test/rebar_eunit_tests.erl218
46 files changed, 929 insertions, 379 deletions
diff --git a/.gitignore b/.gitignore
index 14cd88e..ef672fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ rt.work
.test
dialyzer_warnings
rebar.cmd
+.eunit
+deps
diff --git a/.travis.yml b/.travis.yml
index fc9c771..f5e3d99 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,4 @@
language: erlang
-notifications:
- webhooks: http://basho-engbot.herokuapp.com/travis?key=a0e66154af272adba328195454cfdc10ff5f33f5
- email: eng@basho.com
otp_release:
- R15B01
- R15B
diff --git a/README.md b/README.md
index 68236e1..3be5091 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,28 @@
rebar
=====
-rebar is an Erlang build tool that makes it easy to compile and
-test Erlang applications, port drivers and releases.
+rebar is an Erlang build tool that makes it easy to compile and test Erlang
+applications, port drivers and releases.
-[![Build Status](https://secure.travis-ci.org/basho/rebar.png?branch=master)](http://travis-ci.org/basho/rebar)
+[![Build Status](https://secure.travis-ci.org/rebar/rebar.png?branch=master)](http://travis-ci.org/rebar/rebar)
-rebar is a self-contained Erlang script, so it's easy to distribute or even
-embed directly in a project. Where possible, rebar uses standard Erlang/OTP
-conventions for project structures, thus minimizing the amount of build
-configuration work. rebar also provides dependency management, enabling
-application writers to easily re-use common libraries from a variety of
+rebar is a self-contained Erlang script, so it's easy to distribute or even
+embed directly in a project. Where possible, rebar uses standard Erlang/OTP
+conventions for project structures, thus minimizing the amount of build
+configuration work. rebar also provides dependency management, enabling
+application writers to easily re-use common libraries from a variety of
locations (git, hg, etc).
Building
--------
-Information on building and installing [Erlang/OTP](http://www.erlang.org)
-can be found [here](https://github.com/erlang/otp/wiki/Installation)
-([more info](https://github.com/erlang/otp/blob/master/INSTALL.md)).
+Information on building and installing [Erlang/OTP](http://www.erlang.org) can
+be found [here](https://github.com/erlang/otp/wiki/Installation) ([more
+info](https://github.com/erlang/otp/blob/master/INSTALL.md)).
### Dependencies
-To build rebar you will need a working installation of Erlang R13B03 (or
-later).
+To build rebar you will need a working installation of Erlang R13B03 (or later).
Should you want to clone the rebar repository, you will also require git.
@@ -31,12 +30,12 @@ Should you want to clone the rebar repository, you will also require git.
You can download a pre-built binary version of rebar from:
-https://github.com/basho/rebar/wiki/rebar
+https://github.com/rebar/rebar/wiki/rebar
#### Building rebar
```sh
-$ git clone git://github.com/basho/rebar.git
+$ git clone git://github.com/rebar/rebar.git
$ cd rebar
$ ./bootstrap
Recompile: src/getopt
@@ -72,9 +71,10 @@ Do not mix spaces and tabs.
Do not introduce lines longer than 80 characters.
-[erlang-mode (emacs)](http://www.erlang.org/doc/man/erlang.el.html) indentation is preferred.
-vi-only users are encouraged to
-give [Vim emulation](http://emacswiki.org/emacs/Evil) ([more info](https://gitorious.org/evil/pages/Home)) a try.
+[erlang-mode (emacs)](http://www.erlang.org/doc/man/erlang.el.html) indentation
+is preferred. vi-only users are encouraged to give [Vim
+emulation](http://emacswiki.org/emacs/Evil) ([more
+info](https://gitorious.org/evil/pages/Home)) a try.
Writing Commit Messages
-----------------------
@@ -111,43 +111,39 @@ Longer description (wrap at 72 characters)
Run checks
----------
-Before you submit a patch, run ``make check`` to execute
-the test suite and check for
-[xref](http://www.erlang.org/doc/man/xref.html) and
-[Dialyzer](http://www.erlang.org/doc/man/dialyzer.html)
-warnings. You may have to run ``make clean`` first.
+Before you submit a patch, run ``make check`` to execute the test suite and
+check for [xref](http://www.erlang.org/doc/man/xref.html) and
+[Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) warnings. You may have
+to run ``make clean`` first.
[Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) warnings are compared
-against a set of safe-to-ignore warnings found in
-[dialyzer_reference](https://raw.github.com/basho/rebar/master/dialyzer_reference).
-[xref](http://www.erlang.org/doc/man/xref.html) is run with
-[custom queries](https://raw.github.com/basho/rebar/master/rebar.config)
-to suppress safe-to-ignore warnings.
-
-It is **strongly recommended** to check the code with
-[Tidier](http://tidier.softlab.ntua.gr:20000/tidier/getstarted).
-Select all transformation
-options and enable **automatic** transformation. If Tidier suggests a transformation,
-apply the changes **manually** to the source code. Do not use the code from
-the
-tarball (*out.tgz*) as it will have white-space changes applied by Erlang's pretty-printer.
+against a set of safe-to-ignore warnings found in
+[dialyzer_reference](https://raw.github.com/rebar/rebar/master/dialyzer_reference).
+[xref](http://www.erlang.org/doc/man/xref.html) is run with [custom
+queries](https://raw.github.com/rebar/rebar/master/rebar.config) to suppress
+safe-to-ignore warnings.
Community and Resources
-----------------------
-In case of problems that cannot be solved through documentation or examples, you may
-want to try to contact members of the community for help. The community is also where
-you want to go for questions about how to extend rebar, fill in bug reports, and so on.
+In case of problems that cannot be solved through documentation or examples, you
+may want to try to contact members of the community for help. The community is
+also where you want to go for questions about how to extend rebar, fill in bug
+reports, and so on.
-The main place to go for questions is the [rebar mailing list](http://lists.basho.com/pipermail/rebar_lists.basho.com/). If you need quick feedback,
-you can try the #rebar channel on [irc.freenode.net](http://freenode.net). Be sure to check the [wiki](https://github.com/basho/rebar/wiki) first,
-just to be sure you're not asking about things with well known answers.
+The main place to go for questions is the [rebar mailing
+list](http://lists.basho.com/pipermail/rebar_lists.basho.com/). If you need
+quick feedback, you can try the #rebar channel on
+[irc.freenode.net](http://freenode.net). Be sure to check the
+[wiki](https://github.com/rebar/rebar/wiki) first, just to be sure you're not
+asking about things with well known answers.
-For bug reports, roadmaps, and issues, visit the [github issues page](https://github.com/basho/rebar/issues).
+For bug reports, roadmaps, and issues, visit the [github issues
+page](https://github.com/rebar/rebar/issues).
General rebar community resources and links:
- [Rebar Mailing List](http://lists.basho.com/pipermail/rebar_lists.basho.com/)
- #rebar on [irc.freenode.net](http://freenode.net/)
-- [wiki](https://github.com/basho/rebar/wiki)
-- [issues](https://github.com/basho/rebar/issues)
+- [wiki](https://github.com/rebar/rebar/wiki)
+- [issues](https://github.com/rebar/rebar/issues)
diff --git a/THANKS b/THANKS
index 9a012b1..16b1f24 100644
--- a/THANKS
+++ b/THANKS
@@ -100,3 +100,12 @@ David Aaberg
DeadZen
Edwin Fine
Lev Walkin
+Roberto Ostinelli
+Joe DeVivo
+Markus Nasman
+Dmitriy Kargapolov
+Ryan Zezeski
+Daniel White
+Martin Schut
+Serge Aleynikov
+Magnus Henoch
diff --git a/bootstrap b/bootstrap
index fc6d1a8..70d8da1 100755
--- a/bootstrap
+++ b/bootstrap
@@ -7,15 +7,16 @@ main(Args) ->
Built = build_time(),
%% Get a string repr of first matching VCS changeset
- VcsInfo = vcs_info([{hg, ".hg", "hg identify -i"},
- {git, ".git", "git describe --always --tags"}]),
+ VcsInfo = vcs_info([{hg, ".hg", "hg identify -i", "hg status"},
+ {git, ".git", "git describe --always --tags",
+ "git status -s"}]),
%% Check for force=1 flag to force a rebuild
case lists:member("force=1", Args) of
true ->
rm("ebin/*.beam");
false ->
- rm("ebin/rebar_core.beam")
+ rm("ebin/rebar.beam")
end,
%% Add check for debug flag
@@ -93,10 +94,17 @@ build_time() ->
vcs_info([]) ->
"No VCS info available.";
-vcs_info([{Id, Dir, Cmd} | Rest]) ->
+vcs_info([{Id, Dir, VsnCmd, StatusCmd} | Rest]) ->
case filelib:is_dir(Dir) of
true ->
- lists:concat([Id, " ", string:strip(os:cmd(Cmd), both, $\n)]);
+ Vsn = string:strip(os:cmd(VsnCmd), both, $\n),
+ Status = case string:strip(os:cmd(StatusCmd), both, $\n) of
+ [] ->
+ "";
+ _ ->
+ "-dirty"
+ end,
+ lists:concat([Id, " ", Vsn, Status]);
false ->
vcs_info(Rest)
end.
diff --git a/dialyzer_reference b/dialyzer_reference
index be486b5..a50bb88 100644
--- a/dialyzer_reference
+++ b/dialyzer_reference
@@ -1,2 +1,3 @@
-rebar_utils.erl:163: Call to missing or unexported function escript:foldl/3
+rebar_eunit.erl:351: Call to missing or unexported function eunit_test:function_wrapper/2
+rebar_utils.erl:162: Call to missing or unexported function escript:foldl/3
diff --git a/ebin/rebar.app b/ebin/rebar.app
index 9e0bab5..f4444a3 100644
--- a/ebin/rebar.app
+++ b/ebin/rebar.app
@@ -3,7 +3,7 @@
{application, rebar,
[{description, "Rebar: Erlang Build Tool"},
- {vsn, "2.9.0"},
+ {vsn, "2.0.0"},
{modules, [ rebar,
rebar_abnfc_compiler,
rebar_app_utils,
diff --git a/include/rebar.hrl b/include/rebar.hrl
index b338fc2..58debfc 100644
--- a/include/rebar.hrl
+++ b/include/rebar.hrl
@@ -12,5 +12,3 @@
-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/inttest/ct1/rebar.config b/inttest/ct1/rebar.config
index 839fe4b..a4b5284 100644
--- a/inttest/ct1/rebar.config
+++ b/inttest/ct1/rebar.config
@@ -1,2 +1 @@
-
{ct_dir, "itest"}.
diff --git a/inttest/ct2/ct2_rt.erl b/inttest/ct2/ct2_rt.erl
index 2b14ff9..ecab0e4 100644
--- a/inttest/ct2/ct2_rt.erl
+++ b/inttest/ct2/ct2_rt.erl
@@ -6,11 +6,11 @@
files() ->
[{create, "ebin/foo.app", app(foo)},
{copy, "../../rebar", "rebar"},
- {copy, "foo.test.spec", "test/foo.test.spec"},
+ {copy, "foo.test.spec", "foo.test.spec"},
{copy, "foo_SUITE.erl", "test/foo_SUITE.erl"}].
run(_Dir) ->
- {ok, _} = retest:sh("./rebar compile ct -v"),
+ {ok, _} = retest:sh("./rebar compile ct -vvv"),
ok.
%%
diff --git a/inttest/t_custom_config/custom.config b/inttest/t_custom_config/custom.config
index 8c60b5e..711c27f 100644
--- a/inttest/t_custom_config/custom.config
+++ b/inttest/t_custom_config/custom.config
@@ -1,4 +1,3 @@
-
{deps, [
{boo, "."}
]}.
diff --git a/inttest/t_custom_config/t_custom_config_rt.erl b/inttest/t_custom_config/t_custom_config_rt.erl
index db1b29a..864ce5e 100644
--- a/inttest/t_custom_config/t_custom_config_rt.erl
+++ b/inttest/t_custom_config/t_custom_config_rt.erl
@@ -11,16 +11,17 @@ files() ->
run(Dir) ->
retest_log:log(debug, "Running in Dir: ~s~n", [Dir]),
- Ref = retest:sh("./rebar -C custom.config check-deps -vvv", [{async, true}]),
+ Ref = retest:sh("./rebar -C custom.config check-deps -vvv",
+ [{async, true}]),
{ok, Captured} =
retest:sh_expect(Ref,
"DEBUG: Consult config file .*/custom.config.*",
[{capture, all, list}]),
{ok, Missing} =
retest:sh_expect(Ref,
- "DEBUG: Missing deps : \\[\\{dep,bad_name,"
- "boo,\"\\.\",undefined\\}\\]",
- [{capture, all, list}]),
+ "DEBUG: Missing deps : \\[\\{dep,bad_name,"
+ "boo,\"\\.\",undefined,false\\}\\]",
+ [{capture, all, list}]),
retest_log:log(debug, "[CAPTURED]: ~s~n", [Captured]),
retest_log:log(debug, "[Missing]: ~s~n", [Missing]),
ok.
diff --git a/inttest/tdeps1/a.erl b/inttest/tdeps1/a.erl
index e64b16d..835522a 100644
--- a/inttest/tdeps1/a.erl
+++ b/inttest/tdeps1/a.erl
@@ -6,4 +6,3 @@
hello() ->
io:format("~s\n", [?HELLO]).
-
diff --git a/inttest/tdeps1/a.rebar.config b/inttest/tdeps1/a.rebar.config
index afeef7a..01609ee 100644
--- a/inttest/tdeps1/a.rebar.config
+++ b/inttest/tdeps1/a.rebar.config
@@ -1,2 +1 @@
{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}.
-
diff --git a/inttest/tdeps1/b.hrl b/inttest/tdeps1/b.hrl
index 897f348..efbeab1 100644
--- a/inttest/tdeps1/b.hrl
+++ b/inttest/tdeps1/b.hrl
@@ -1,3 +1 @@
-
-include_lib("c/include/c.hrl").
-
diff --git a/inttest/tdeps1/b.rebar.config b/inttest/tdeps1/b.rebar.config
index ced29cc..59c8987 100644
--- a/inttest/tdeps1/b.rebar.config
+++ b/inttest/tdeps1/b.rebar.config
@@ -1,2 +1 @@
{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}.
-
diff --git a/inttest/tdeps1/c.hrl b/inttest/tdeps1/c.hrl
index 84cf2d4..9f02fab 100644
--- a/inttest/tdeps1/c.hrl
+++ b/inttest/tdeps1/c.hrl
@@ -1,2 +1 @@
-define(HELLO, hello).
-
diff --git a/inttest/tdeps1/tdeps1_rt.erl b/inttest/tdeps1/tdeps1_rt.erl
index 9072e9c..43184c6 100644
--- a/inttest/tdeps1/tdeps1_rt.erl
+++ b/inttest/tdeps1/tdeps1_rt.erl
@@ -30,14 +30,11 @@ run(_Dir) ->
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/b"}]),
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/c"}]),
-
{ok, _} = retest_sh:run("./rebar get-deps compile", []),
true = filelib:is_regular("ebin/a.beam"),
ok.
-
-
%%
%% Generate the contents of a simple .app file
%%
diff --git a/inttest/tdeps2/a.erl b/inttest/tdeps2/a.erl
index 29538e0..294ae21 100644
--- a/inttest/tdeps2/a.erl
+++ b/inttest/tdeps2/a.erl
@@ -1,4 +1,3 @@
-module({{module}}).
-include_lib("b/include/b.hrl").
-
diff --git a/inttest/tdeps2/a.rebar.config b/inttest/tdeps2/a.rebar.config
index afeef7a..01609ee 100644
--- a/inttest/tdeps2/a.rebar.config
+++ b/inttest/tdeps2/a.rebar.config
@@ -1,2 +1 @@
{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}.
-
diff --git a/inttest/tdeps2/b.hrl b/inttest/tdeps2/b.hrl
index 897f348..efbeab1 100644
--- a/inttest/tdeps2/b.hrl
+++ b/inttest/tdeps2/b.hrl
@@ -1,3 +1 @@
-
-include_lib("c/include/c.hrl").
-
diff --git a/inttest/tdeps2/b.rebar.config b/inttest/tdeps2/b.rebar.config
index ced29cc..59c8987 100644
--- a/inttest/tdeps2/b.rebar.config
+++ b/inttest/tdeps2/b.rebar.config
@@ -1,2 +1 @@
{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}.
-
diff --git a/inttest/tdeps2/c.hrl b/inttest/tdeps2/c.hrl
index 84cf2d4..9f02fab 100644
--- a/inttest/tdeps2/c.hrl
+++ b/inttest/tdeps2/c.hrl
@@ -1,2 +1 @@
-define(HELLO, hello).
-
diff --git a/inttest/tdeps2/tdeps2_rt.erl b/inttest/tdeps2/tdeps2_rt.erl
index cdf10b5..bad546e 100644
--- a/inttest/tdeps2/tdeps2_rt.erl
+++ b/inttest/tdeps2/tdeps2_rt.erl
@@ -38,12 +38,9 @@ run(_Dir) ->
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/b"}]),
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/c"}]),
-
{ok, _} = retest_sh:run("./rebar -v get-deps compile", []),
ok.
-
-
%%
%% Generate the contents of a simple .app file
%%
diff --git a/priv/shell-completion/bash/rebar b/priv/shell-completion/bash/rebar
index 0801fd1..d76e2ba 100644
--- a/priv/shell-completion/bash/rebar
+++ b/priv/shell-completion/bash/rebar
@@ -10,8 +10,8 @@ _rebar()
lopts=" --help --commands --verbose --force --jobs= --version"
cmdsnvars="check-deps clean compile create create-app create-node ct \
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= \
+ help list-deps list-templates qc 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 b/rebar.config
index e9252fa..cbdaf52 100644
--- a/rebar.config
+++ b/rebar.config
@@ -2,7 +2,6 @@
%% ex: ts=4 sw=4 ft=erlang et
{app_bin, ["priv/rebar"]}.
-{escript_emu_args, "%%! -noshell -noinput\n"}.
%% escript_incl_extra is for internal rebar-private use only.
%% Do not use outside rebar. Config interface is not stable.
{escript_incl_extra, [{"priv/templates/*", "."}]}.
@@ -11,9 +10,10 @@
{xref_queries,
[{"(XC - UC) || (XU - X - B
- (\"escript\":\"foldl\"/\"3\")
+ - (\"eunit_test\":\"function_wrapper\"/\"2\")
- (\"abnfc\":\"file\"/\"2\")
- (\"erlydtl\":\"compile\"/\"3\")
- (\"lfe_comp\":\"file\"/\"2\")
- (\"neotoma\":\"file\"/\"2\")
- - (\"protobuffs_compile\":\"scan_file\"/\"1\"))",
+ - (\"protobuffs_compile\":\"scan_file\"/\"2\"))",
[]}]}.
diff --git a/rebar.config.sample b/rebar.config.sample
index 4243924..fac55af 100644
--- a/rebar.config.sample
+++ b/rebar.config.sample
@@ -22,13 +22,6 @@
{platform_define, "(linux|freebsd)", 'BACKLOG', 128},
{platform_define, "R13", 'old_inets'}]}.
-%% Additional compile options for test-compile. erl_opts is also used
-{test_compile_opts, []}.
-
-%% Same as erl_first_files, but used only when running 'test-compile', 'eunit',
-%% or 'qc'
-{test_first_files, []}.
-
%% MIB Options?
{mib_opts, []}.
@@ -72,6 +65,14 @@
%% Options for eunit:test()
{eunit_opts, []}.
+%% Additional compile options for eunit. erl_opts is also used
+{eunit_compile_opts, []}.
+
+%% Same as erl_first_files, but used only when running 'eunit'
+{eunit_first_files, []}.
+
+%% == Cover ==
+
%% Whether to enable coverage reporting. Default is `false'
{cover_enabled, false}.
@@ -100,6 +101,12 @@
%% If qc_mod is unspecified, rebar tries to detect Triq or EQC
{qc_opts, [{qc_mod, module()}, Options]}.
+%% Additional compile options for qc. erl_opts is also used
+{qc_compile_opts, []}.
+
+%% Same as erl_first_files, but used only when running 'qc'
+{qc_first_files, []}.
+
%% == Cleanup ==
%% Which files to cleanup
@@ -126,11 +133,25 @@
%% What dependencies we have, dependencies can be of 3 forms, an application
%% name as an atom, eg. mochiweb, a name and a version (from the .app file), or
%% an application name, a version and the SCM details on how to fetch it (SCM
-%% type, location and revision). Rebar currently supports git, hg, bzr and svn.
+%% type, location and revision).
+%% Rebar currently supports git, hg, bzr, svn, and rsync.
{deps, [application_name,
{application_name, "1.0.*"},
{application_name, "1.0.*",
- {git, "git://github.com/basho/rebar.git", {branch, "master"}}}]}.
+ {git, "git://github.com/basho/rebar.git", {branch, "master"}}},
+
+%% Dependencies can be marked as 'raw'. Rebar does not require such dependencies
+%% to have a standard Erlang/OTP layout which assumes the presence of either
+%% "src/dependency_name.app.src" or "ebin/dependency_name.app" files.
+%%
+%% 'raw' dependencies can still contain 'rebar.config' and even can have the
+%% proper OTP directory layout, but they won't be compiled.
+%%
+%% Only a subset of rebar commands will be executed on the 'raw' subdirectories:
+%% get-deps, update-deps, check-deps, list-deps and delete-deps.
+ {application_name, "",
+ {git, "git://github.com/basho/rebar.git", {branch, "master"}},
+ [raw]}]}.
%% == Subdirectories ==
diff --git a/src/mustache.erl b/src/mustache.erl
index ac501a0..f6963cd 100644
--- a/src/mustache.erl
+++ b/src/mustache.erl
@@ -31,10 +31,6 @@
section_re = undefined,
tag_re = undefined}).
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
compile(Body) when is_list(Body) ->
State = #mstate{},
CompiledTemplate = pre_compile(Body, State),
@@ -113,9 +109,9 @@ compile_section(Name, Content, State) ->
Result = compiler(Content, State),
"fun() -> " ++
"case mustache:get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++
- "true -> " ++
+ "\"true\" -> " ++
Result ++ "; " ++
- "false -> " ++
+ "\"false\" -> " ++
"[]; " ++
"List when is_list(List) -> " ++
"[fun(Ctx) -> " ++ Result ++ " end(dict:merge(CFun, SubCtx, Ctx)) || SubCtx <- List]; " ++
@@ -154,9 +150,21 @@ compile_tag("{", Content, State) ->
compile_tag("!", _Content, _State) ->
"[]".
+template_dir(Mod) ->
+ DefaultDirPath = filename:dirname(code:which(Mod)),
+ case application:get_env(mustache, templates_dir) of
+ {ok, DirPath} when is_list(DirPath) ->
+ case filelib:ensure_dir(DirPath) of
+ ok -> DirPath;
+ _ -> DefaultDirPath
+ end;
+ _ ->
+ DefaultDirPath
+ end.
template_path(Mod) ->
- ModPath = code:which(Mod),
- re:replace(ModPath, "\.beam$", ".mustache", [{return, list}]).
+ DirPath = template_dir(Mod),
+ Basename = atom_to_list(Mod),
+ filename:join(DirPath, Basename ++ ".mustache").
get(Key, Ctx) when is_list(Key) ->
{ok, Mod} = dict:find('__mod__', Ctx),
@@ -218,17 +226,3 @@ escape([X | Rest], Acc) ->
start([T]) ->
Out = render(list_to_atom(T)),
io:format(Out ++ "~n", []).
-
--ifdef(TEST).
-
-simple_test() ->
- Ctx = dict:from_list([{name, "world"}]),
- Result = render("Hello {{name}}!", Ctx),
- ?assertEqual("Hello world!", Result).
-
-integer_values_too_test() ->
- Ctx = dict:from_list([{name, "Chris"}, {value, 10000}]),
- Result = render("Hello {{name}}~nYou have just won ${{value}}!", Ctx),
- ?assertEqual("Hello Chris~nYou have just won $10000!", Result).
-
--endif.
diff --git a/src/rebar.erl b/src/rebar.erl
index 7680d5a..cd0bed5 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -87,11 +87,10 @@ run(RawArgs) ->
true ->
io:format("Profiling!\n"),
try
- fprof:apply(fun([C, A]) -> run_aux(C, A) end,
- [BaseConfig1, Cmds])
+ fprof:apply(fun run_aux/2, [BaseConfig1, Cmds])
after
- fprof:profile(),
- fprof:analyse([{dest, "fprof.analysis"}])
+ ok = fprof:profile(),
+ ok = fprof:analyse([{dest, "fprof.analysis"}])
end;
false ->
run_aux(BaseConfig1, Cmds)
@@ -297,8 +296,17 @@ 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 [suites=foo] Run eunit tests in foo.erl and
+ test/foo_tests.erl
+ [suites=foo] [tests=bar] Run specific eunit tests [first test name
+ starting with 'bar' in foo.erl and
+ test/foo_tests.erl]
+ [tests=bar] For every existing suite, run the first
+ test whose name starts with bar and, if
+ no such test exists, run the test whose
+ name starts with bar in the suite's
+ _tests module
+
ct [suites=] [case=] Run common_test suites
qc Test QuickCheck properties
@@ -366,8 +374,7 @@ command_names() ->
["check-deps", "clean", "compile", "create", "create-app", "create-node",
"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"].
+ "qc", "update-deps", "overlay", "shell", "version", "xref"].
unabbreviate_command_names([]) ->
[];
diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl
index c0df97f..8158eb6 100644
--- a/src/rebar_app_utils.erl
+++ b/src/rebar_app_utils.erl
@@ -138,7 +138,7 @@ load_app_file(Config, Filename) ->
AppFile = {app_file, Filename},
case rebar_config:get_xconf(Config, {appfile, AppFile}, undefined) of
undefined ->
- case file:consult(Filename) of
+ case consult_app_file(Filename) of
{ok, [{application, AppName, AppData}]} ->
Config1 = rebar_config:set_xconf(Config,
{appfile, AppFile},
@@ -153,6 +153,28 @@ load_app_file(Config, Filename) ->
{ok, Config, AppName, AppData}
end.
+%% In the case of *.app.src we want to give the user the ability to
+%% dynamically script the application resource file (think dynamic version
+%% string, etc.), in a way similar to what can be done with the rebar
+%% config. However, in the case of *.app, rebar should not manipulate
+%% that file. This enforces that dichotomy between app and app.src.
+consult_app_file(Filename) ->
+ case lists:suffix(".app.src", Filename) of
+ false ->
+ file:consult(Filename);
+ true ->
+ %% TODO: EXPERIMENTAL For now let's warn the user if a
+ %% script is going to be run.
+ case filelib:is_regular([Filename, ".script"]) of
+ true ->
+ ?CONSOLE("NOTICE: Using experimental *.app.src.script "
+ "functionality on ~s ~n", [Filename]);
+ _ ->
+ ok
+ end,
+ rebar_config:consult_file(Filename)
+ end.
+
get_value(Key, AppInfo, AppFile) ->
case proplists:get_value(Key, AppInfo) of
undefined ->
diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl
index e83a7aa..63e408b 100644
--- a/src/rebar_base_compiler.erl
+++ b/src/rebar_base_compiler.erl
@@ -235,15 +235,17 @@ maybe_report(_) ->
report(Messages) ->
lists:foreach(fun(Msg) -> io:format("~s", [Msg]) end, Messages).
-format_errors(Config, Source, Extra, Errors) ->
- AbsSource = case rebar_utils:processing_base_dir(Config) of
- true ->
- Source;
- false ->
- filename:absname(Source)
- end,
- [[format_error(AbsSource, Extra, Desc) || Desc <- Descs]
- || {_, Descs} <- Errors].
+format_errors(Config, _MainSource, Extra, Errors) ->
+ [begin
+ AbsSource = case rebar_utils:processing_base_dir(Config) of
+ true ->
+ Source;
+ false ->
+ filename:absname(Source)
+ end,
+ [format_error(AbsSource, Extra, Desc) || Desc <- Descs]
+ end
+ || {Source, Descs} <- Errors].
format_error(AbsSource, Extra, {{Line, Column}, Mod, Desc}) ->
ErrorDesc = Mod:format_error(Desc),
diff --git a/src/rebar_core.erl b/src/rebar_core.erl
index a150ef2..9e3f9f0 100644
--- a/src/rebar_core.erl
+++ b/src/rebar_core.erl
@@ -372,9 +372,11 @@ restore_code_path(no_change) ->
restore_code_path({old, Path}) ->
%% Verify that all of the paths still exist -- some dynamically
%% added paths can get blown away during clean.
- true = code:set_path([F || F <- Path, filelib:is_file(F)]),
+ true = code:set_path([F || F <- Path, erl_prim_loader_is_file(F)]),
ok.
+erl_prim_loader_is_file(File) ->
+ erl_prim_loader:read_file_info(File) =/= error.
expand_lib_dirs([], _Root, Acc) ->
Acc;
diff --git a/src/rebar_ct.erl b/src/rebar_ct.erl
index 50efeb1..e33c6c9 100644
--- a/src/rebar_ct.erl
+++ b/src/rebar_ct.erl
@@ -59,30 +59,35 @@ run_test_if_present(TestDir, LogDir, Config, File) ->
?WARN("~s directory not present - skipping\n", [TestDir]),
ok;
true ->
- case filelib:wildcard(TestDir ++ "/*_SUITE.erl") of
+ case filelib:wildcard(TestDir ++ "/*_SUITE.{beam,erl}") of
[] ->
?WARN("~s directory present, but no common_test"
- ++ " SUITES - skipping\n", [TestDir]),
+ ++ " SUITES - skipping\n", [TestDir]),
ok;
_ ->
- run_test(TestDir, LogDir, Config, File)
+ try
+ run_test(TestDir, LogDir, Config, File)
+ catch
+ throw:skip ->
+ ok
+ end
end
end.
run_test(TestDir, LogDir, Config, _File) ->
{Cmd, RawLog} = make_cmd(TestDir, LogDir, Config),
+ ?DEBUG("ct_run cmd:~n~p~n", [Cmd]),
clear_log(LogDir, RawLog),
- case rebar_config:is_verbose(Config) of
- false ->
- Output = " >> " ++ RawLog ++ " 2>&1";
- true ->
- Output = " 2>&1 | tee -a " ++ RawLog
- end,
+ Output = case rebar_config:is_verbose(Config) of
+ false ->
+ " >> " ++ RawLog ++ " 2>&1";
+ true ->
+ " 2>&1 | tee -a " ++ RawLog
+ end,
rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}]),
check_log(Config, RawLog).
-
clear_log(LogDir, RawLog) ->
case filelib:ensure_dir(filename:join(LogDir, "index.html")) of
ok ->
@@ -270,8 +275,10 @@ find_suite_path(Suite, TestDir) ->
Path = filename:join(TestDir, Suite ++ "_SUITE.erl"),
case filelib:is_regular(Path) of
false ->
- ?ERROR("Suite ~s not found\n", [Suite]),
- ?FAIL;
+ ?WARN("Suite ~s not found\n", [Suite]),
+ %% Note - this throw is caught in run_test_if_present/3;
+ %% this solution was easier than refactoring the entire module.
+ throw(skip);
true ->
Path
end.
diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl
index c79e4c1..074e929 100644
--- a/src/rebar_deps.erl
+++ b/src/rebar_deps.erl
@@ -42,7 +42,8 @@
-record(dep, { dir,
app,
vsn_regex,
- source }).
+ source,
+ is_raw }). %% is_raw = true means non-Erlang/OTP dependency
%% ===================================================================
%% Public API
@@ -90,8 +91,12 @@ preprocess(Config, _) ->
Config3
end,
+ %% Filtering out 'raw' dependencies so that no commands other than
+ %% deps-related can be executed on their directories.
+ NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw],
+
%% Return all the available dep directories for process
- {ok, NewConfig, dep_dirs(AvailableDeps)}.
+ {ok, NewConfig, dep_dirs(NonRawAvailableDeps)}.
postprocess(Config, _) ->
case rebar_config:get_xconf(Config, ?MODULE, undefined) of
@@ -102,8 +107,9 @@ postprocess(Config, _) ->
{ok, NewConfig, Dirs}
end.
-compile(Config, AppFile) ->
- 'check-deps'(Config, AppFile).
+compile(Config, _) ->
+ {Config1, _AvailDeps} = do_check_deps(Config),
+ {ok, Config1}.
%% set REBAR_DEPS_DIR and ERL_LIBS environment variables
setup_env(Config) ->
@@ -123,13 +129,14 @@ setup_env(Config) ->
end,
[{"REBAR_DEPS_DIR", DepsDir}, ERL_LIBS].
-'check-deps'(Config, _) ->
+%% common function used by 'check-deps' and 'compile'
+do_check_deps(Config) ->
%% Get the list of immediate (i.e. non-transitive) deps that are missing
Deps = rebar_config:get_local(Config, deps, []),
case find_deps(Config, find, Deps) of
- {Config1, {_, []}} ->
+ {Config1, {AvailDeps, []}} ->
%% No missing deps
- {ok, Config1};
+ {Config1, AvailDeps};
{_Config1, {_, MissingDeps}} ->
lists:foreach(fun (#dep{app=App, vsn_regex=Vsn, source=Src}) ->
?CONSOLE("Dependency not available: "
@@ -138,6 +145,10 @@ setup_env(Config) ->
?FAIL
end.
+'check-deps'(Config, _) ->
+ {Config1, AvailDeps} = do_check_deps(Config),
+ {ok, save_dep_dirs(Config1, AvailDeps)}.
+
'get-deps'(Config, _) ->
%% Determine what deps are available and missing
Deps = rebar_config:get_local(Config, deps, []),
@@ -183,7 +194,7 @@ setup_env(Config) ->
case find_deps(Config, find, Deps) of
{Config1, {AvailDeps, []}} ->
lists:foreach(fun(Dep) -> print_source(Dep) end, AvailDeps),
- {ok, Config1};
+ {ok, save_dep_dirs(Config1, AvailDeps)};
{_, MissingDeps} ->
?ABORT("Missing dependencies: ~p\n", [MissingDeps])
end.
@@ -196,8 +207,9 @@ setup_env(Config) ->
%% need all deps in same dir and should be the one set by the root rebar.config
%% Sets a default if root config has no deps_dir set
set_shared_deps_dir(Config, []) ->
- rebar_config:set_xconf(Config, deps_dir,
- rebar_config:get_local(Config, deps_dir, "deps"));
+ GlobalDepsDir = rebar_config:get_global(Config, deps_dir, "deps"),
+ DepsDir = rebar_config:get_local(Config, deps_dir, GlobalDepsDir),
+ rebar_config:set_xconf(Config, deps_dir, DepsDir);
set_shared_deps_dir(Config, _DepsDir) ->
Config.
@@ -232,7 +244,7 @@ update_deps_code_path(Config, []) ->
update_deps_code_path(Config, [Dep | Rest]) ->
Config2 =
case is_app_available(Config, Dep#dep.app,
- Dep#dep.vsn_regex, Dep#dep.dir) of
+ Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of
{Config1, {true, _}} ->
Dir = filename:join(Dep#dep.dir, "ebin"),
ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
@@ -258,9 +270,14 @@ find_deps(Config, Mode, [App | Rest], Acc) when is_atom(App) ->
find_deps(Config, Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) ->
find_deps(Config, Mode, [{App, VsnRegex, undefined} | Rest], Acc);
find_deps(Config, Mode, [{App, VsnRegex, Source} | Rest], Acc) ->
+ find_deps(Config, Mode, [{App, VsnRegex, Source, []} | Rest], Acc);
+find_deps(Config, Mode, [{App, VsnRegex, Source, Opts} | Rest], Acc) when is_list(Opts) ->
Dep = #dep { app = App,
vsn_regex = VsnRegex,
- source = Source },
+ source = Source,
+ %% dependency is considered raw (i.e. non-Erlang/OTP) when
+ %% 'raw' option is present
+ is_raw = proplists:get_value(raw, Opts, false) },
{Config1, {Availability, FoundDir}} = find_dep(Config, Dep),
find_deps(Config1, Mode, Rest,
acc_deps(Mode, Availability, Dep, FoundDir, Acc));
@@ -295,7 +312,8 @@ find_dep_in_dir(Config, _Dep, {false, Dir}) ->
find_dep_in_dir(Config, Dep, {true, Dir}) ->
App = Dep#dep.app,
VsnRegex = Dep#dep.vsn_regex,
- case is_app_available(Config, App, VsnRegex, Dir) of
+ IsRaw = Dep#dep.is_raw,
+ case is_app_available(Config, App, VsnRegex, Dir, IsRaw) of
{Config1, {true, _AppFile}} -> {Config1, {avail, Dir}};
{Config1, {false, _}} -> {Config1, {missing, Dir}}
end.
@@ -320,7 +338,11 @@ require_source_engine(Source) ->
true = source_engine_avail(Source),
ok.
-is_app_available(Config, App, VsnRegex, Path) ->
+%% IsRaw = false means regular Erlang/OTP dependency
+%%
+%% IsRaw = true means non-Erlang/OTP dependency, e.g. the one that does not
+%% have a proper .app file
+is_app_available(Config, App, VsnRegex, Path, _IsRaw = false) ->
?DEBUG("is_app_available, looking for App ~p with Path ~p~n", [App, Path]),
case rebar_app_utils:is_app_dir(Path) of
{true, AppFile} ->
@@ -351,6 +373,19 @@ is_app_available(Config, App, VsnRegex, Path) ->
?WARN("Expected ~s to be an app dir (containing ebin/*.app), "
"but no .app found.\n", [Path]),
{Config, {false, {missing_app_file, Path}}}
+ end;
+is_app_available(Config, App, _VsnRegex, Path, _IsRaw = true) ->
+ ?DEBUG("is_app_available, looking for Raw Depencency ~p with Path ~p~n", [App, Path]),
+ case filelib:is_dir(Path) of
+ true ->
+ %% TODO: look for version string in <Path>/VERSION file? Not clear
+ %% how to detect git/svn/hg/{cmd, ...} settings that can be passed
+ %% to rebar_utils:vcs_vsn/2 to obtain version dynamically
+ {Config, {true, Path}};
+ false ->
+ ?WARN("Expected ~s to be a raw dependency directory, "
+ "but no directory found.\n", [Path]),
+ {Config, {false, {missing_raw_dependency_directory, Path}}}
end.
use_source(Config, Dep) ->
@@ -364,7 +399,7 @@ use_source(Config, Dep, Count) ->
true ->
%% Already downloaded -- verify the versioning matches the regex
case is_app_available(Config, Dep#dep.app,
- Dep#dep.vsn_regex, Dep#dep.dir) of
+ Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of
{Config1, {true, _}} ->
Dir = filename:join(Dep#dep.dir, "ebin"),
ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
@@ -422,7 +457,19 @@ download_source(AppDir, {svn, Url, Rev}) ->
[{cd, filename:dirname(AppDir)}]);
download_source(AppDir, {rsync, Url}) ->
ok = filelib:ensure_dir(AppDir),
- rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []).
+ rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []);
+download_source(AppDir, {fossil, Url}) ->
+ download_source(AppDir, {fossil, Url, ""});
+download_source(AppDir, {fossil, Url, latest}) ->
+ download_source(AppDir, {fossil, Url, ""});
+download_source(AppDir, {fossil, Url, Version}) ->
+ Repository = filename:join(AppDir, filename:basename(AppDir) ++ ".fossil"),
+ ok = filelib:ensure_dir(Repository),
+ ok = file:set_cwd(AppDir),
+ rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]),
+ [{cd, AppDir}]),
+ rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]),
+ []).
update_source(Config, Dep) ->
%% It's possible when updating a source, that a given dep does not have a
@@ -465,7 +512,16 @@ update_source1(AppDir, {hg, _Url, Rev}) ->
update_source1(AppDir, {bzr, _Url, Rev}) ->
rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]);
update_source1(AppDir, {rsync, Url}) ->
- rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]).
+ rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]);
+update_source1(AppDir, {fossil, Url}) ->
+ update_source1(AppDir, {fossil, Url, ""});
+update_source1(AppDir, {fossil, Url, latest}) ->
+ update_source1(AppDir, {fossil, Url, ""});
+update_source1(AppDir, {fossil, _Url, Version}) ->
+ ok = file:set_cwd(AppDir),
+ rebar_utils:sh("fossil pull", [{cd, AppDir}]),
+ rebar_utils:sh(?FMT("fossil update ~s", [Version]), []).
+
%% ===================================================================
%% Source helper functions
@@ -476,7 +532,8 @@ source_engine_avail(Source) ->
source_engine_avail(Name, Source).
source_engine_avail(Name, Source)
- when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync ->
+ when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync;
+ Name == fossil ->
case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of
true ->
true;
@@ -497,11 +554,12 @@ vcs_client_vsn(Path, VsnArg, VsnRegex) ->
false
end.
-required_vcs_client_vsn(hg) -> {1, 1};
-required_vcs_client_vsn(git) -> {1, 5};
-required_vcs_client_vsn(bzr) -> {2, 0};
-required_vcs_client_vsn(svn) -> {1, 6};
-required_vcs_client_vsn(rsync) -> {2, 0}.
+required_vcs_client_vsn(hg) -> {1, 1};
+required_vcs_client_vsn(git) -> {1, 5};
+required_vcs_client_vsn(bzr) -> {2, 0};
+required_vcs_client_vsn(svn) -> {1, 6};
+required_vcs_client_vsn(rsync) -> {2, 0};
+required_vcs_client_vsn(fossil) -> {1, 0}.
vcs_client_vsn(hg) ->
vcs_client_vsn(rebar_utils:find_executable("hg"), " --version",
@@ -517,7 +575,10 @@ vcs_client_vsn(svn) ->
"svn, version (\\d+).(\\d+)");
vcs_client_vsn(rsync) ->
vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version",
- "rsync version (\\d+).(\\d+)").
+ "rsync version (\\d+).(\\d+)");
+vcs_client_vsn(fossil) ->
+ vcs_client_vsn(rebar_utils:find_executable("fossil"), " version",
+ "version (\\d+).(\\d+)").
has_vcs_dir(git, Dir) ->
filelib:is_dir(filename:join(Dir, ".git"));
diff --git a/src/rebar_edoc.erl b/src/rebar_edoc.erl
index e7dd3e6..cf0239c 100644
--- a/src/rebar_edoc.erl
+++ b/src/rebar_edoc.erl
@@ -108,7 +108,7 @@ newer_file_exists(Paths, OldFile) ->
%% Needs regen if any dependent file is changed since the last
%% edoc run. Dependent files are the erlang source files,
%% and the overview file, if it exists.
--spec needs_regen(edoc:proplist()) -> boolean().
+-spec needs_regen(proplists:proplist()) -> boolean().
needs_regen(EDocOpts) ->
DocDir = proplists:get_value(dir, EDocOpts, "doc"),
EDocInfoName = filename:join(DocDir, "edoc-info"),
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 7123395..d704d2d 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -30,7 +30,7 @@
clean/2]).
%% for internal use by only eunit and qc
--export([test_compile/1]).
+-export([test_compile/3]).
-include("rebar.hrl").
@@ -114,7 +114,7 @@ clean(_Config, _AppFile) ->
%% .erl Compilation API (externally used by only eunit and qc)
%% ===================================================================
-test_compile(Config) ->
+test_compile(Config, Cmd, OutDir) ->
%% Obtain all the test modules for inclusion in the compile stage.
%% Notice: this could also be achieved with the following
%% rebar.config option: {test_compile_opts, [{src_dirs, ["test"]}]}
@@ -133,16 +133,16 @@ test_compile(Config) ->
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
+ %% files already present in OutDir. 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
+ %% need to be sure that the files already present in OutDir 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
+ %% Get the full path to a file that was previously copied in OutDir
ToCleanUp = fun(F, Acc) ->
F2 = filename:basename(F),
- F3 = filename:join([?TEST_DIR, F2]),
+ F3 = filename:join([OutDir, F2]),
case filelib:is_regular(F3) of
true -> [F3|Acc];
false -> Acc
@@ -152,12 +152,12 @@ test_compile(Config) ->
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),
+ ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, OutDir),
- %% Compile erlang code to ?TEST_DIR, using a tweaked config
+ %% Compile erlang code to OutDir, 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 = doterl_compile(test_compile_config(Config, Cmd), OutDir, TestErls),
{ok, SrcErls}.
@@ -165,19 +165,21 @@ test_compile(Config) ->
%% Internal functions
%% ===================================================================
-test_compile_config(Config) ->
+test_compile_config(Config, Cmd) ->
{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, test_compile_opts, []),
+ OptsAtom = list_to_atom(Cmd ++ "_compile_opts"),
+ EunitOpts = rebar_config:get_list(Config3, OptsAtom, []),
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, test_first_files, []),
+ FirstFilesAtom = list_to_atom(Cmd ++ "_first_files"),
+ FirstErls = rebar_config:get_list(Config4, FirstFilesAtom, []),
rebar_config:set(Config4, erl_first_files, FirstErls).
triq_opts(Config) ->
@@ -255,9 +257,10 @@ doterl_compile(Config, OutDir, MoreSources) ->
ok = filelib:ensure_dir(filename:join("ebin", "dummy.beam")),
CurrPath = code:get_path(),
true = code:add_path(filename:absname("ebin")),
+ OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir),
rebar_base_compiler:run(Config, NewFirstErls, OtherErls,
fun(S, C) ->
- internal_erl_compile(C, S, OutDir, ErlOpts)
+ internal_erl_compile(C, S, OutDir1, ErlOpts)
end),
true = code:set_path(CurrPath),
ok.
diff --git a/src/rebar_escripter.erl b/src/rebar_escripter.erl
index e8aa5dc..706cf7c 100644
--- a/src/rebar_escripter.erl
+++ b/src/rebar_escripter.erl
@@ -40,6 +40,7 @@ escriptize(Config0, AppFile) ->
%% Extract the application name from the archive -- this is the default
%% name of the generated script
{Config, AppName} = rebar_app_utils:app_name(Config0, AppFile),
+ AppNameStr = atom_to_list(AppName),
%% Get the output filename for the escript -- this may include dirs
Filename = rebar_config:get_local(Config, escript_name, AppName),
@@ -57,7 +58,10 @@ escriptize(Config0, AppFile) ->
%% Construct the archive of everything in ebin/ dir -- put it on the
%% top-level of the zip file so that code loading works properly.
- Files = load_files("*", "ebin") ++ InclBeams ++ InclExtra,
+ EbinPrefix = filename:join(AppNameStr, "ebin"),
+ EbinFiles = usort(load_files(EbinPrefix, "*", "ebin")),
+ ExtraFiles = usort(InclBeams ++ InclExtra),
+ Files = EbinFiles ++ ExtraFiles,
case zip:create("mem", Files, [memory]) of
{ok, {"mem", ZipBin}} ->
@@ -66,7 +70,10 @@ escriptize(Config0, AppFile) ->
Shebang = rebar_config:get(Config, escript_shebang,
"#!/usr/bin/env escript\n"),
Comment = rebar_config:get(Config, escript_comment, "%%\n"),
- EmuArgs = rebar_config:get(Config, escript_emu_args, "%%!\n"),
+ DefaultEmuArgs = ?FMT("%%! -pa ~s/~s/ebin\n",
+ [AppNameStr, AppNameStr]),
+ EmuArgs = rebar_config:get(Config, escript_emu_args,
+ DefaultEmuArgs),
Script = iolist_to_binary([Shebang, Comment, EmuArgs, ZipBin]),
case file:write_file(Filename, Script) of
ok ->
@@ -109,9 +116,8 @@ get_app_beams([App | Rest], Acc) ->
?ABORT("Failed to get ebin/ directory for "
"~p escript_incl_apps.", [App]);
Path ->
- Acc2 = [{filename:join([App, ebin, F]),
- file_contents(filename:join(Path, F))} ||
- F <- filelib:wildcard("*", Path)],
+ Prefix = filename:join(atom_to_list(App), "ebin"),
+ Acc2 = load_files(Prefix, "*", Path),
get_app_beams(Rest, Acc2 ++ Acc)
end.
@@ -122,11 +128,43 @@ get_extra(Config) ->
end, [], Extra).
load_files(Wildcard, Dir) ->
- [read_file(Filename, Dir) || Filename <- filelib:wildcard(Wildcard, Dir)].
-
-read_file(Filename, Dir) ->
- {Filename, file_contents(filename:join(Dir, Filename))}.
+ load_files("", Wildcard, Dir).
+
+load_files(Prefix, Wildcard, Dir) ->
+ [read_file(Prefix, Filename, Dir)
+ || Filename <- filelib:wildcard(Wildcard, Dir)].
+
+read_file(Prefix, Filename, Dir) ->
+ Filename1 = case Prefix of
+ "" ->
+ Filename;
+ _ ->
+ filename:join([Prefix, Filename])
+ end,
+ [dir_entries(filename:dirname(Filename1)),
+ {Filename1, file_contents(filename:join(Dir, Filename))}].
file_contents(Filename) ->
{ok, Bin} = file:read_file(Filename),
Bin.
+
+%% Given a filename, return zip archive dir entries for each sub-dir.
+%% Required to work around issues fixed in OTP-10071.
+dir_entries(File) ->
+ Dirs = dirs(File),
+ [{Dir ++ "/", <<>>} || Dir <- Dirs].
+
+%% Given "foo/bar/baz", return ["foo", "foo/bar", "foo/bar/baz"].
+dirs(Dir) ->
+ dirs1(filename:split(Dir), "", []).
+
+dirs1([], _, Acc) ->
+ lists:reverse(Acc);
+dirs1([H|T], "", []) ->
+ dirs1(T, H, [H]);
+dirs1([H|T], Last, Acc) ->
+ Dir = filename:join(Last, H),
+ dirs1(T, Dir, [Dir|Acc]).
+
+usort(List) ->
+ lists:ukeysort(1, lists:flatten(List)).
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
index b1bcdaf..b82da0f 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 ?TEST_DIR directory</li>
+%% <li>clean - remove ?EUNIT_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:
@@ -43,7 +43,20 @@
%% The following Global options are supported:
%% <ul>
%% <li>verbose=1 - show extra output from the eunit test</li>
-%% <li>suites="foo,bar" - runs test/foo_tests.erl and test/bar_tests.erl</li>
+%% <li>
+%% suites="foo,bar" - runs tests in foo.erl, test/foo_tests.erl and
+%% tests in bar.erl, test/bar_tests.erl
+%% </li>
+%% <li>
+%% suites="foo,bar" tests="baz"- runs first test with name starting
+%% with 'baz' in foo.erl, test/foo_tests.erl and tests in bar.erl,
+%% test/bar_tests.erl
+%% </li>
+%% <li>
+%% tests="baz"- For every existing suite, run the first test whose
+%% name starts with bar and, if no such test exists, run the test
+%% whose name starts with bar in the suite's _tests module
+%% </li>
%% </ul>
%% Additionally, for projects that have separate folders for the core
%% implementation, and for the unit tests, then the following
@@ -54,11 +67,12 @@
-module(rebar_eunit).
-export([eunit/2,
- clean/2,
- 'test-compile'/2]).
+ clean/2]).
-include("rebar.hrl").
+-define(EUNIT_DIR, ".eunit").
+
%% ===================================================================
%% Public API
%% ===================================================================
@@ -67,46 +81,55 @@ eunit(Config, _AppFile) ->
ok = ensure_dirs(),
%% Save code path
CodePath = setup_code_path(),
+ CompileOnly = rebar_utils:get_experimental_global(Config, compile_only,
+ false),
+ {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "eunit",
+ ?EUNIT_DIR),
+ case CompileOnly of
+ "true" ->
+ true = code:set_path(CodePath),
+ ?CONSOLE("Compiled modules for eunit~n", []);
+ false ->
+ run_eunit(Config, CodePath, SrcErls)
+ end.
- {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config),
+clean(_Config, _File) ->
+ rebar_file_utils:rm_rf(?EUNIT_DIR).
- %% Build a list of all the .beams in ?TEST_DIR -- use this for
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+run_eunit(Config, CodePath, SrcErls) ->
+ %% Build a list of all the .beams in ?EUNIT_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
- %% compilation info to be lost. Filter out "*_tests" modules so
- %% 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.
- %%
- %% From 'Primitives' in the EUnit User's Guide
- %% http://www.erlang.org/doc/apps/eunit/chapter.html
- %% "In addition, EUnit will also look for another module whose
- %% name is ModuleName plus the suffix _tests, and if it exists,
- %% all the tests from that module will also be added. (If
- %% ModuleName already contains the suffix _tests, this is not
- %% done.) E.g., the specification {module, mymodule} will run all
- %% tests in the modules mymodule and mymodule_tests. Typically,
- %% the _tests module should only contain test cases that use the
- %% public interface of the main module (and no other code)."
-
- AllBeamFiles = rebar_utils:beams(?TEST_DIR),
+ %% compilation info to be lost.
+
+ AllBeamFiles = rebar_utils:beams(?EUNIT_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(?TEST_DIR, N) || N <- ModuleBeamFiles],
+
+ %% Get modules to be run in eunit
+ AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles],
+ {SuitesProvided, FilteredModules} = filter_suites(Config, AllModules),
+
+ %% Get matching tests
+ Tests = get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules),
+
SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
- FilteredModules = filter_modules(Config, Modules),
{ok, CoverLog} = cover_init(Config, ModuleBeamFiles),
StatusBefore = status_before_eunit(),
- EunitResult = perform_eunit(Config, FilteredModules),
- perform_cover(Config, FilteredModules, SrcModules),
+ EunitResult = perform_eunit(Config, Tests),
+ perform_cover(Config, FilteredModules, SrcModules),
cover_close(CoverLog),
case proplists:get_value(reset_after_eunit, get_eunit_opts(Config),
@@ -117,6 +140,10 @@ eunit(Config, _AppFile) ->
ok
end,
+ %% Stop cover to clean the cover_server state. This is important if we want
+ %% eunit+cover to not slow down when analyzing many Erlang modules.
+ ok = cover:stop(),
+
case EunitResult of
ok ->
ok;
@@ -128,57 +155,214 @@ eunit(Config, _AppFile) ->
true = code:set_path(CodePath),
ok.
-clean(_Config, _File) ->
- rebar_file_utils:rm_rf(?TEST_DIR).
-
-'test-compile'(Config, _File) ->
- ?CONSOLE("NOTICE: Using experimental 'test-compile' command~n", []),
- ok = ensure_dirs(),
- %% Save code path
- CodePath = setup_code_path(),
- {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config),
- %% Restore code path
- true = code:set_path(CodePath),
- ok.
-
-%% ===================================================================
-%% Internal functions
-%% ===================================================================
-
ensure_dirs() ->
- %% Make sure ?TEST_DIR/ and ebin/ directory exists (append dummy module)
- ok = filelib:ensure_dir(filename:join(rebar_utils:test_dir(), "dummy")),
+ %% 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(rebar_utils:ebin_dir(), "dummy")).
+eunit_dir() ->
+ filename:join(rebar_utils:get_cwd(), ?EUNIT_DIR).
+
setup_code_path() ->
%% Setup code path prior to compilation so that parse_transforms
%% and the like work properly. Also, be sure to add ebin_dir()
%% 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(rebar_utils:test_dir()),
+ true = code:add_patha(eunit_dir()),
true = code:add_pathz(rebar_utils:ebin_dir()),
CodePath.
-filter_modules(Config, Modules) ->
+%%
+%% == filter suites ==
+%%
+
+filter_suites(Config, Modules) ->
RawSuites = rebar_config:get_global(Config, suites, ""),
+ SuitesProvided = RawSuites =/= "",
Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")],
- filter_modules1(Modules, Suites).
+ {SuitesProvided, filter_suites1(Modules, Suites)}.
-filter_modules1(Modules, []) ->
+filter_suites1(Modules, []) ->
Modules;
-filter_modules1(Modules, Suites) ->
+filter_suites1(Modules, Suites) ->
[M || M <- Modules, lists:member(M, Suites)].
-perform_eunit(Config, FilteredModules) ->
+%%
+%% == get matching tests ==
+%%
+get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) ->
+ Modules = case SuitesProvided of
+ false ->
+ %% No specific suites have been provided, use
+ %% ModuleBeamFiles which filters out "*_tests" modules
+ %% so eunit won't doubly run them and cover only
+ %% calculates coverage on production code. However,
+ %% keep "*_tests" modules that are not automatically
+ %% included by eunit.
+ %%
+ %% From 'Primitives' in the EUnit User's Guide
+ %% http://www.erlang.org/doc/apps/eunit/chapter.html
+ %% "In addition, EUnit will also look for another
+ %% module whose name is ModuleName plus the suffix
+ %% _tests, and if it exists, all the tests from that
+ %% module will also be added. (If ModuleName already
+ %% contains the suffix _tests, this is not done.) E.g.,
+ %% the specification {module, mymodule} will run all
+ %% tests in the modules mymodule and mymodule_tests.
+ %% Typically, the _tests module should only contain
+ %% test cases that use the public interface of the main
+ %% module (and no other code)."
+ [rebar_utils:beam_to_mod(?EUNIT_DIR, N) ||
+ N <- ModuleBeamFiles];
+ true ->
+ %% Specific suites have been provided, return the
+ %% filtered modules
+ FilteredModules
+ end,
+ get_matching_tests(Config, Modules).
+
+get_matching_tests(Config, Modules) ->
+ RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""),
+ Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")],
+ case Tests of
+ [] ->
+ Modules;
+ Functions ->
+ case get_matching_tests1(Modules, Functions, []) of
+ [] ->
+ [];
+ RawTests ->
+ make_test_primitives(RawTests)
+ end
+ end.
+
+get_matching_tests1([], _Functions, TestFunctions) ->
+ TestFunctions;
+
+get_matching_tests1([Module|TModules], Functions, TestFunctions) ->
+ %% Get module exports
+ ModuleStr = atom_to_list(Module),
+ ModuleExports = get_beam_test_exports(ModuleStr),
+ %% Get module _tests exports
+ TestModuleStr = string:concat(ModuleStr, "_tests"),
+ TestModuleExports = get_beam_test_exports(TestModuleStr),
+ %% Build tests {M, F} list
+ Tests = get_matching_tests2(Functions, {Module, ModuleExports},
+ {list_to_atom(TestModuleStr),
+ TestModuleExports}),
+ get_matching_tests1(TModules, Functions,
+ lists:merge([TestFunctions, Tests])).
+
+get_matching_tests2(Functions, {Mod, ModExports}, {TestMod, TestModExports}) ->
+ %% Look for matching functions into ModExports
+ ModExportsStr = [atom_to_list(E1) || E1 <- ModExports],
+ TestModExportsStr = [atom_to_list(E2) || E2 <- TestModExports],
+ get_matching_exports(Functions, {Mod, ModExportsStr},
+ {TestMod, TestModExportsStr}, []).
+
+get_matching_exports([], _, _, Matched) ->
+ Matched;
+get_matching_exports([Function|TFunctions], {Mod, ModExportsStr},
+ {TestMod, TestModExportsStr}, Matched) ->
+
+ FunctionStr = atom_to_list(Function),
+ %% Get matching Function in module, otherwise look in _tests module
+ NewMatch = case get_matching_export(FunctionStr, ModExportsStr) of
+ [] ->
+ {TestMod, get_matching_export(FunctionStr,
+ TestModExportsStr)};
+ MatchingExport ->
+ {Mod, MatchingExport}
+ end,
+ case NewMatch of
+ {_, []} ->
+ get_matching_exports(TFunctions, {Mod, ModExportsStr},
+ {TestMod, TestModExportsStr}, Matched);
+ _ ->
+ get_matching_exports(TFunctions, {Mod, ModExportsStr},
+ {TestMod, TestModExportsStr},
+ [NewMatch|Matched])
+ end.
+
+get_matching_export(_FunctionStr, []) ->
+ [];
+get_matching_export(FunctionStr, [ExportStr|TExportsStr]) ->
+ case string:str(ExportStr, FunctionStr) of
+ 1 ->
+ list_to_atom(ExportStr);
+ _ ->
+ get_matching_export(FunctionStr, TExportsStr)
+ end.
+
+get_beam_test_exports(ModuleStr) ->
+ FilePath = filename:join(eunit_dir(),
+ string:concat(ModuleStr, ".beam")),
+ case filelib:is_regular(FilePath) of
+ true ->
+ {beam_file, _, Exports0, _, _, _} = beam_disasm:file(FilePath),
+ Exports1 = [FunName || {FunName, FunArity, _} <- Exports0,
+ FunArity =:= 0],
+ F = fun(FName) ->
+ FNameStr = atom_to_list(FName),
+ re:run(FNameStr, "_test(_)?") =/= nomatch
+ end,
+ lists:filter(F, Exports1);
+ _ ->
+ []
+ end.
+
+make_test_primitives(RawTests) ->
+ %% Use {test,M,F} and {generator,M,F} if at least R15B02. Otherwise,
+ %% use eunit_test:function_wrapper/2 fallback.
+ %% eunit_test:function_wrapper/2 was renamed to eunit_test:mf_wrapper/2
+ %% in R15B02; use that as >= R15B02 check.
+ %% TODO: remove fallback and use only {test,M,F} and {generator,M,F}
+ %% primitives once at least R15B02 is required.
+ {module, eunit_test} = code:ensure_loaded(eunit_test),
+ MakePrimitive = case erlang:function_exported(eunit_test, mf_wrapper, 2) of
+ true -> fun eunit_primitive/3;
+ false -> fun pre15b02_eunit_primitive/3
+ end,
+
+ ?CONSOLE(" Running test function(s):~n", []),
+ F = fun({M, F2}, Acc) ->
+ ?CONSOLE(" ~p:~p/0~n", [M, F2]),
+ FNameStr = atom_to_list(F2),
+ NewFunction =
+ case re:run(FNameStr, "_test_") of
+ nomatch ->
+ %% Normal test
+ MakePrimitive(test, M, F2);
+ _ ->
+ %% Generator
+ MakePrimitive(generator, M, F2)
+ end,
+ [NewFunction|Acc]
+ end,
+ lists:foldl(F, [], RawTests).
+
+eunit_primitive(Type, M, F) ->
+ {Type, M, F}.
+
+pre15b02_eunit_primitive(test, M, F) ->
+ eunit_test:function_wrapper(M, F);
+pre15b02_eunit_primitive(generator, M, F) ->
+ {generator, eunit_test:function_wrapper(M, F)}.
+
+%%
+%% == run tests ==
+%%
+
+perform_eunit(Config, Tests) ->
EunitOpts = get_eunit_opts(Config),
- %% Move down into ?TEST_DIR while we run tests so any generated files
+ %% Move down into ?EUNIT_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(?TEST_DIR),
+ ok = file:set_cwd(?EUNIT_DIR),
- EunitResult = (catch eunit:test(FilteredModules, EunitOpts)),
+ EunitResult = (catch eunit:test(Tests, EunitOpts)),
%% Return to original working dir
ok = file:set_cwd(Cwd),
@@ -196,6 +380,10 @@ get_eunit_opts(Config) ->
BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []).
+%%
+%% == code coverage ==
+%%
+
perform_cover(Config, BeamFiles, SrcModules) ->
perform_cover(rebar_config:get(Config, cover_enabled, false),
Config, BeamFiles, SrcModules).
@@ -209,7 +397,9 @@ cover_analyze(_Config, [], _SrcModules) ->
ok;
cover_analyze(Config, FilteredModules, SrcModules) ->
%% Generate coverage info for all the cover-compiled modules
- Coverage = lists:flatten([cover_analyze_mod(M) || M <- FilteredModules]),
+ Coverage = lists:flatten([cover_analyze_mod(M)
+ || M <- FilteredModules,
+ cover:is_compiled(M) =/= false]),
%% Write index of coverage info
cover_write_index(lists:sort(Coverage), SrcModules),
@@ -220,7 +410,7 @@ cover_analyze(Config, FilteredModules, SrcModules) ->
[html])
end, Coverage),
- Index = filename:join([rebar_utils:get_cwd(), ?TEST_DIR, "index.html"]),
+ Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]),
?CONSOLE("Cover analysis: ~s\n", [Index]),
%% Export coverage data, if configured
@@ -247,28 +437,25 @@ cover_close(F) ->
cover_init(false, _BeamFiles) ->
{ok, not_enabled};
cover_init(true, BeamFiles) ->
- %% Attempt to start the cover server, then set it's group leader to
- %% ?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
- {ok, _P} = OkStart ->
- OkStart;
- {error,{already_started, P}} ->
- {ok, P};
- {error, _Reason} = ErrorStart ->
- ErrorStart
+ %% Attempt to start the cover server, then set its group leader to
+ %% .eunit/cover.log, so all cover log messages will go there instead of
+ %% to stdout. If the cover server is already started, we'll kill that
+ %% server and start a new one in order not to inherit a polluted
+ %% cover_server state.
+ {ok, CoverPid} = case whereis(cover_server) of
+ undefined ->
+ cover:start();
+ _ ->
+ cover:stop(),
+ cover:start()
end,
{ok, F} = OkOpen = file:open(
- filename:join([?TEST_DIR, "cover.log"]),
+ filename:join([?EUNIT_DIR, "cover.log"]),
[write]),
group_leader(F, CoverPid),
- %% Make sure any previous runs of cover don't unduly influence
- cover:reset(),
-
?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]),
Compiled = [{Beam, cover:compile_beam(Beam)} || Beam <- BeamFiles],
@@ -336,7 +523,7 @@ align_notcovered_count(Module, Covered, NotCovered, true) ->
{Module, Covered, NotCovered - 1}.
cover_write_index(Coverage, SrcModules) ->
- {ok, F} = file:open(filename:join([?TEST_DIR, "index.html"]), [write]),
+ {ok, F} = file:open(filename:join([?EUNIT_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),
@@ -348,7 +535,7 @@ cover_write_index(Coverage, SrcModules) ->
cover_write_index_section(_F, _SectionName, []) ->
ok;
cover_write_index_section(F, SectionName, Coverage) ->
- %% Calculate total coverage %
+ %% Calculate total coverage
{Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
{CAcc + C, NAcc + N}
end, {0, 0}, Coverage),
@@ -394,10 +581,10 @@ cover_print_coverage(Coverage) ->
?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]).
cover_file(Module) ->
- filename:join([?TEST_DIR, atom_to_list(Module) ++ ".COVER.html"]).
+ filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]).
cover_export_coverdata() ->
- ExportFile = filename:join(rebar_utils:test_dir(), "eunit.coverdata"),
+ ExportFile = filename:join(eunit_dir(), "eunit.coverdata"),
case cover:export(ExportFile) of
ok ->
?CONSOLE("Coverdata export: ~s~n", [ExportFile]);
@@ -410,14 +597,18 @@ percentage(0, 0) ->
percentage(Cov, NotCov) ->
integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%".
-get_app_names() ->
- [AppName || {AppName, _, _} <- application:loaded_applications()].
+%%
+%% == reset_after_eunit ==
+%%
status_before_eunit() ->
Apps = get_app_names(),
AppEnvs = [{App, application:get_all_env(App)} || App <- Apps],
{erlang:processes(), erlang:is_alive(), AppEnvs, ets:tab2list(ac_tab)}.
+get_app_names() ->
+ [AppName || {AppName, _, _} <- application:loaded_applications()].
+
reset_after_eunit({OldProcesses, WasAlive, OldAppEnvs, _OldACs}) ->
IsAlive = erlang:is_alive(),
if not WasAlive andalso IsAlive ->
diff --git a/src/rebar_port_compiler.erl b/src/rebar_port_compiler.erl
index 644d8c2..91b2cac 100644
--- a/src/rebar_port_compiler.erl
+++ b/src/rebar_port_compiler.erl
@@ -519,15 +519,10 @@ default_env() ->
{"darwin9.*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"},
{"darwin9.*-64$", "LDFLAGS", "-arch x86_64 $LDFLAGS"},
- %% OS X Snow Leopard flags for 32-bit
- {"darwin10.*-32", "CFLAGS", "-m32 $CFLAGS"},
- {"darwin10.*-32", "CXXFLAGS", "-m32 $CXXFLAGS"},
- {"darwin10.*-32", "LDFLAGS", "-arch i386 $LDFLAGS"},
-
- %% OS X Lion flags for 32-bit
- {"darwin11.*-32", "CFLAGS", "-m32 $CFLAGS"},
- {"darwin11.*-32", "CXXFLAGS", "-m32 $CXXFLAGS"},
- {"darwin11.*-32", "LDFLAGS", "-arch i386 $LDFLAGS"},
+ %% OS X Snow Leopard, Lion, and Mountain Lion flags for 32-bit
+ {"darwin1[0-2].*-32", "CFLAGS", "-m32 $CFLAGS"},
+ {"darwin1[0-2].*-32", "CXXFLAGS", "-m32 $CXXFLAGS"},
+ {"darwin1[0-2].*-32", "LDFLAGS", "-arch i386 $LDFLAGS"},
%% Windows specific flags
%% add MS Visual C++ support to rebar on Windows
diff --git a/src/rebar_protobuffs_compiler.erl b/src/rebar_protobuffs_compiler.erl
index 249588c..7ef58d6 100644
--- a/src/rebar_protobuffs_compiler.erl
+++ b/src/rebar_protobuffs_compiler.erl
@@ -35,7 +35,7 @@
%% Public API
%% ===================================================================
-compile(_Config, _AppFile) ->
+compile(Config, _AppFile) ->
case rebar_utils:find_files("src", ".*\\.proto$") of
[] ->
ok;
@@ -49,7 +49,7 @@ compile(_Config, _AppFile) ->
Proto <- FoundFiles],
%% Compile each proto file
- compile_each(Targets);
+ compile_each(Config, Targets);
false ->
?ERROR("Protobuffs library not present in code path!\n",
[]),
@@ -95,13 +95,15 @@ needs_compile(Proto, Beam) ->
ActualBeam = filename:join(["ebin", filename:basename(Beam)]),
filelib:last_modified(ActualBeam) < filelib:last_modified(Proto).
-compile_each([]) ->
+compile_each(_, []) ->
ok;
-compile_each([{Proto, Beam, Hrl} | Rest]) ->
+compile_each(Config, [{Proto, Beam, Hrl} | Rest]) ->
case needs_compile(Proto, Beam) of
true ->
?CONSOLE("Compiling ~s\n", [Proto]),
- case protobuffs_compile:scan_file(Proto) of
+ ErlOpts = rebar_utils:erl_opts(Config),
+ case protobuffs_compile:scan_file(Proto,
+ [{compile_flags,ErlOpts}]) of
ok ->
%% Compilation worked, but we need to move the
%% beam and .hrl file into the ebin/ and include/
@@ -120,7 +122,7 @@ compile_each([{Proto, Beam, Hrl} | Rest]) ->
false ->
ok
end,
- compile_each(Rest).
+ compile_each(Config, Rest).
delete_each([]) ->
ok;
diff --git a/src/rebar_qc.erl b/src/rebar_qc.erl
index 051400f..5784f7d 100644
--- a/src/rebar_qc.erl
+++ b/src/rebar_qc.erl
@@ -30,6 +30,8 @@
-include("rebar.hrl").
+-define(QC_DIR, ".qc").
+
%% ===================================================================
%% Public API
%% ===================================================================
@@ -102,23 +104,41 @@ load_qc_mod(Mod) ->
?ABORT("Failed to load QC lib '~p'~n", [Mod])
end.
+ensure_dirs() ->
+ ok = filelib:ensure_dir(filename:join(qc_dir(), "dummy")),
+ ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
+
setup_codepath() ->
CodePath = code:get_path(),
- true = code:add_patha(rebar_utils:test_dir()),
- true = code:add_patha(rebar_utils:ebin_dir()),
+ true = code:add_patha(qc_dir()),
+ true = code:add_pathz(rebar_utils:ebin_dir()),
CodePath.
+qc_dir() ->
+ filename:join(rebar_utils:get_cwd(), ?QC_DIR).
+
run(Config, QC, QCOpts) ->
?DEBUG("qc_opts: ~p~n", [QCOpts]),
- ok = filelib:ensure_dir(?TEST_DIR ++ "/foo"),
+ ok = ensure_dirs(),
CodePath = setup_codepath(),
- %% Compile erlang code to ?TEST_DIR, using a tweaked config
+ CompileOnly = rebar_utils:get_experimental_global(Config, compile_only,
+ false),
+ %% Compile erlang code to ?QC_DIR, using a tweaked config
%% with appropriate defines, and include all the test modules
%% as well.
- {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config),
+ {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config, "qc", ?QC_DIR),
+
+ case CompileOnly of
+ "true" ->
+ true = code:set_path(CodePath),
+ ?CONSOLE("Compiled modules for qc~n", []);
+ false ->
+ run1(QC, QCOpts, CodePath)
+ end.
+run1(QC, QCOpts, CodePath) ->
TestModule = fun(M) -> qc_module(QC, QCOpts, M) end,
case lists:flatmap(TestModule, find_prop_mods()) of
[] ->
@@ -139,7 +159,7 @@ qc_module(QC=triq, _QCOpts, M) ->
qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M).
find_prop_mods() ->
- Beams = rebar_utils:find_files(?TEST_DIR, ".*\\.beam\$"),
+ Beams = rebar_utils:find_files(?QC_DIR, ".*\\.beam\$"),
[M || M <- [rebar_utils:erl_to_mod(Beam) || Beam <- Beams], has_prop(M)].
has_prop(Mod) ->
diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl
index 51fcb0e..3c9b728 100644
--- a/src/rebar_reltool.erl
+++ b/src/rebar_reltool.erl
@@ -67,7 +67,7 @@ generate(Config0, ReltoolFile) ->
overlay(Config, ReltoolFile) ->
%% Load the reltool configuration from the file
{Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
- {Config1, process_overlay(Config, ReltoolConfig)}.
+ {process_overlay(Config, ReltoolConfig), Config1}.
clean(Config, ReltoolFile) ->
{Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
@@ -109,7 +109,8 @@ process_overlay(Config, ReltoolConfig) ->
OverlayVars0 =
dict:from_list([{erts_vsn, "erts-" ++ erlang:system_info(version)},
{rel_vsn, BootRelVsn},
- {target_dir, TargetDir}]),
+ {target_dir, TargetDir},
+ {hostname, net_adm:localhost()}]),
%% Load up any variables specified by overlay_vars
OverlayVars1 = overlay_vars(Config, OverlayVars0, ReltoolConfig),
@@ -149,15 +150,13 @@ overlay_vars(Config, Vars0, ReltoolConfig) ->
load_vars_file(undefined) ->
dict:new();
load_vars_file(File) ->
- case file:consult(File) of
+ case rebar_config:consult_file(File) of
{ok, Terms} ->
dict:from_list(Terms);
{error, Reason} ->
?ABORT("Unable to load overlay_vars from ~p: ~p\n", [File, Reason])
end.
-
-
validate_rel_apps(ReltoolServer, {sys, ReltoolConfig}) ->
case lists:keyfind(rel, 1, ReltoolConfig) of
false ->
@@ -190,7 +189,6 @@ app_exists(App, Server) when is_atom(App) ->
app_exists(AppTuple, Server) when is_tuple(AppTuple) ->
app_exists(element(1, AppTuple), Server).
-
run_reltool(Server, Config, ReltoolConfig) ->
case reltool:get_target_spec(Server) of
{ok, Spec} ->
@@ -224,7 +222,6 @@ run_reltool(Server, Config, ReltoolConfig) ->
?ABORT("Unable to generate spec: ~s\n", [Reason])
end.
-
mk_target_dir(Config, TargetDir) ->
case filelib:ensure_dir(filename:join(TargetDir, "dummy")) of
ok ->
@@ -246,7 +243,6 @@ mk_target_dir(Config, TargetDir) ->
?FAIL
end.
-
dump_spec(Config, Spec) ->
case rebar_config:get_global(Config, dump_spec, "0") of
"1" ->
@@ -349,10 +345,22 @@ apply_file_info(InFile, OutFile) ->
create_RELEASES(TargetDir, RelName, RelVsn) ->
ReleasesDir = filename:join(TargetDir, "releases"),
+ RelFile = filename:join([ReleasesDir, RelVsn, RelName ++ ".rel"]),
+ Apps = rebar_rel_utils:get_rel_apps(RelFile),
+ TargetLib = filename:join(TargetDir,"lib"),
+
+ AppDirs =
+ [ {App, Vsn, TargetLib}
+ || {App, Vsn} <- Apps,
+ filelib:is_dir(
+ filename:join(TargetLib,
+ lists:concat([App, "-", Vsn]))) ],
+
case release_handler:create_RELEASES(
- ".", ReleasesDir,
- filename:join([ReleasesDir, RelVsn, RelName ++ ".rel"]),
- filename:join(TargetDir, "lib")) of
+ code:root_dir(),
+ ReleasesDir,
+ RelFile,
+ AppDirs) of
ok ->
ok;
{error, Reason} ->
diff --git a/src/rebar_require_vsn.erl b/src/rebar_require_vsn.erl
index 327f75c..83cb79d 100644
--- a/src/rebar_require_vsn.erl
+++ b/src/rebar_require_vsn.erl
@@ -67,4 +67,33 @@ check_versions(Config) ->
nomatch ->
?ABORT("OTP release ~s does not match required regex ~s\n",
[erlang:system_info(otp_release), OtpRegex])
+ end,
+
+ case rebar_config:get(Config, require_min_otp_vsn, undefined) of
+ undefined -> ?DEBUG("Min OTP version unconfigured~n", []);
+ MinOtpVsn ->
+ {MinMaj, MinMin} = version_tuple(MinOtpVsn, "configured"),
+ {OtpMaj, OtpMin} = version_tuple(erlang:system_info(otp_release),
+ "OTP Release"),
+ case {OtpMaj, OtpMin} >= {MinMaj, MinMin} of
+ true ->
+ ?DEBUG("~s satisfies the requirement for vsn ~s~n",
+ [erlang:system_info(otp_release),
+ MinOtpVsn]);
+ false ->
+ ?ABORT("OTP release ~s or later is required, you have: ~s~n",
+ [MinOtpVsn,
+ erlang:system_info(otp_release)])
+ end
+ end.
+
+version_tuple(OtpRelease, Type) ->
+ case re:run(OtpRelease, "R(\\d+)B?-?(\\d+)?", [{capture, all, list}]) of
+ {match, [_Full, Maj, Min]} ->
+ {list_to_integer(Maj), list_to_integer(Min)};
+ {match, [_Full, Maj]} ->
+ {list_to_integer(Maj), 0};
+ nomatch ->
+ ?ABORT("Cannot parse ~s version string: ~s~n",
+ [Type, OtpRelease])
end.
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index 66cd7e2..bb58460 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -45,12 +45,12 @@
vcs_vsn/3,
deprecated/3, deprecated/4,
get_deprecated_global/4, get_deprecated_global/5,
+ get_experimental_global/3, get_experimental_local/3,
get_deprecated_list/4, get_deprecated_list/5,
get_deprecated_local/4, get_deprecated_local/5,
delayed_halt/1,
erl_opts/1,
src_dirs/1,
- test_dir/0,
ebin_dir/0,
processing_base_dir/1, processing_base_dir/2]).
@@ -75,8 +75,7 @@ is_arch(ArchRegex) ->
get_arch() ->
Words = wordsize(),
erlang:system_info(otp_release) ++ "-"
- ++ erlang:system_info(system_architecture) ++ "-" ++ Words
- ++ "-" ++ os_family().
+ ++ erlang:system_info(system_architecture) ++ "-" ++ Words.
wordsize() ->
try erlang:system_info({wordsize, external}) of
@@ -176,7 +175,7 @@ prop_check(false, Msg, Args) -> ?ABORT(Msg, Args).
%% Convert all the entries in the code path to absolute paths.
expand_code_path() ->
- CodePath = lists:foldl(fun (Path, Acc) ->
+ CodePath = lists:foldl(fun(Path, Acc) ->
[filename:absname(Path) | Acc]
end, [], code:get_path()),
code:set_path(lists:reverse(CodePath)).
@@ -217,18 +216,14 @@ get_deprecated_global(Config, OldOpt, NewOpt, When) ->
get_deprecated_global(Config, OldOpt, NewOpt, undefined, When).
get_deprecated_global(Config, OldOpt, NewOpt, Default, When) ->
- case rebar_config:get_global(Config, NewOpt, Default) of
- Default ->
- case rebar_config:get_global(Config, OldOpt, Default) of
- Default ->
- Default;
- Old ->
- deprecated(OldOpt, NewOpt, When),
- Old
- end;
- New ->
- New
- end.
+ get_deprecated_3(fun rebar_config:get_global/3,
+ Config, OldOpt, NewOpt, Default, When).
+
+get_experimental_global(Config, Opt, Default) ->
+ get_experimental_3(fun rebar_config:get_global/3, Config, Opt, Default).
+
+get_experimental_local(Config, Opt, Default) ->
+ get_experimental_3(fun rebar_config:get_local/3, Config, Opt, Default).
get_deprecated_list(Config, OldOpt, NewOpt, When) ->
get_deprecated_list(Config, OldOpt, NewOpt, undefined, When).
@@ -308,9 +303,6 @@ src_dirs([]) ->
src_dirs(SrcDirs) ->
SrcDirs.
-test_dir() ->
- filename:join(get_cwd(), ?TEST_DIR).
-
ebin_dir() ->
filename:join(get_cwd(), "ebin").
@@ -325,10 +317,6 @@ processing_base_dir(Config, Dir) ->
%% Internal functions
%% ====================================================================
-os_family() ->
- {OsFamily, _} = os:type(),
- atom_to_list(OsFamily).
-
get_deprecated_3(Get, Config, OldOpt, NewOpt, Default, When) ->
case Get(Config, NewOpt, Default) of
Default ->
@@ -343,6 +331,16 @@ get_deprecated_3(Get, Config, OldOpt, NewOpt, Default, When) ->
New
end.
+get_experimental_3(Get, Config, Opt, Default) ->
+ Val = Get(Config, Opt, Default),
+ case Val of
+ Default ->
+ Default;
+ Val ->
+ ?CONSOLE("NOTICE: Using experimental option '~p'~n", [Opt]),
+ Val
+ end.
+
%% We do the shell variable substitution ourselves on Windows and hope that the
%% command doesn't use any other shell magic.
patch_on_windows(Cmd, Env) ->
@@ -353,7 +351,8 @@ patch_on_windows(Cmd, Env) ->
expand_env_variable(Acc, Key, Value)
end, Cmd, Env),
%% Remove left-over vars
- re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "", [global, {return, list}]);
+ re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
+ [global, {return, list}]);
_ ->
Cmd
end.
@@ -472,20 +471,11 @@ vcs_vsn_1(Vcs, Dir) ->
end
end.
-vcs_vsn_cmd(git) ->
- %% git describe the last commit that touched CWD
- %% required for correct versioning of apps in subdirs, such as apps/app1
- case os:type() of
- {win32,nt} ->
- "FOR /F \"usebackq tokens=* delims=\" %i in "
- "(`git log -n 1 \"--pretty=format:%h\" .`) do "
- "@git describe --always --tags %i";
- _ ->
- "git describe --always --tags `git log -n 1 --pretty=format:%h .`"
- end;
-vcs_vsn_cmd(hg) -> "hg identify -i";
-vcs_vsn_cmd(bzr) -> "bzr revno";
-vcs_vsn_cmd(svn) -> "svnversion";
+vcs_vsn_cmd(git) -> "git describe --always --tags";
+vcs_vsn_cmd(hg) -> "hg identify -i";
+vcs_vsn_cmd(bzr) -> "bzr revno";
+vcs_vsn_cmd(svn) -> "svnversion";
+vcs_vsn_cmd(fossil) -> "fossil info";
vcs_vsn_cmd({cmd, _Cmd}=Custom) -> Custom;
vcs_vsn_cmd(Version) -> {unknown, Version}.
diff --git a/src/rebar_xref.erl b/src/rebar_xref.erl
index 4b045a0..84b59f6 100644
--- a/src/rebar_xref.erl
+++ b/src/rebar_xref.erl
@@ -146,10 +146,10 @@ filter_away_ignored(UnusedExports) ->
%% any functions marked to ignore. We then use this list to mask any
%% functions marked as unused exports by xref
F = fun(Mod) ->
- Attrs = kf(attributes, Mod:module_info()),
- Ignore = kf(ignore_xref, Attrs),
- Callbacks =
- [B:behaviour_info(callbacks) || B <- kf(behaviour, Attrs)],
+ Attrs = Mod:module_info(attributes),
+ Ignore = keyall(ignore_xref, Attrs),
+ Callbacks = [B:behaviour_info(callbacks)
+ || B <- keyall(behaviour, Attrs)],
[{Mod, F, A} || {F, A} <- Ignore ++ lists:flatten(Callbacks)]
end,
AttrIgnore =
@@ -157,14 +157,8 @@ filter_away_ignored(UnusedExports) ->
lists:map(F, lists:usort([M || {M, _, _} <- UnusedExports]))),
[X || X <- UnusedExports, not lists:member(X, AttrIgnore)].
-
-kf(Key, List) ->
- case lists:keyfind(Key, 1, List) of
- {Key, Value} ->
- Value;
- false ->
- []
- end.
+keyall(Key, List) ->
+ lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List).
display_mfas([], _Message) ->
ok;
diff --git a/test/rebar_eunit_tests.erl b/test/rebar_eunit_tests.erl
index 828e1a4..7b2eec5 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 '.test'
+%% command, the current working directory will be '.eunit'
-define(REBAR_SCRIPT, "../rebar").
-define(TMP_DIR, "tmp_eunit/").
@@ -59,18 +59,157 @@ eunit_test_() ->
?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
end}.
+eunit_with_suites_and_tests_test_() ->
+ [{"Ensure EUnit runs selected suites",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests in 'test' directory are found and run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2_tests:") =/= 0)},
+
+ {"Selected suite tests in 'src' directory are found and run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2:") =/= 0)},
+
+ {"Unselected suite tests in 'test' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod_tests:") =:= 0)},
+
+ {"Unselected suite tests in 'src' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod:") =:= 0)},
+
+ {"Selected suite tests are only run once",
+ ?_assert(string:str(RebarOut, "All 4 tests passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs selected _tests suites",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2_tests")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests in 'test' directory are found and run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2_tests:") =/= 0)},
+
+ {"Selected suite tests in 'src' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2:") =:= 0)},
+
+ {"Unselected suite tests in 'test' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod_tests:") =:= 0)},
+
+ {"Unselected suite tests in 'src' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod:") =:= 0)},
+
+ {"Selected suite tests are only run once",
+ ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs a specific test defined in a selected suite",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2 tests=myprivate2")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests are found and run",
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2:myprivate2_test/0") =/= 0)},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "Test passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs a specific generator test defined in a selected suite",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod3 tests=mygenerator")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite's generator test is found and run",
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod3:mygenerator_test_/0") =/= 0)},
+
+ {"Selected suite's generator test raises an error",
+ ?_assert(string:str(RebarOut,
+ "assertEqual_failed") =/= 0)},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "Failed: 1.") =/= 0)}]
+ end},
+ {"Ensure EUnit runs specific tests defined in selected suites",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod,myapp_mymod2"
+ " tests=myprivate,myfunc2")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests are found and run",
+ [?_assert(string:str(RebarOut,
+ "myapp_mymod:myprivate_test/0") =/= 0),
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2:myprivate2_test/0") =/= 0),
+ ?_assert(
+ string:str(RebarOut,
+ "myapp_mymod2_tests:myfunc2_test/0") =/= 0)]},
+
+ {"Selected suite tests are run once",
+ ?_assert(string:str(RebarOut, "All 3 tests passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs specific test in a _tests suite",
+ setup,
+ fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2_tests tests=common_name_test")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Only selected suite tests are found and run",
+ [?_assert(string:str(RebarOut,
+ "myapp_mymod2:common_name_test/0") =:= 0),
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2_tests:common_name_test/0")
+ =/= 0)]},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "Test passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs a specific test without a specified suite",
+ setup,
+ fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit tests=myprivate")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Only selected suite tests are found and run",
+ [?_assert(string:str(RebarOut,
+ "myapp_mymod:myprivate_test/0") =/= 0),
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2:myprivate2_test/0")
+ =/= 0)]},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
+ end}].
+
cover_test_() ->
{"Ensure Cover runs with tests in a test dir and no defined suite",
setup, fun() -> setup_cover_project(), rebar("-v eunit") end,
fun teardown/1,
- [{"All cover reports are generated",
- assert_files_in("the temporary eunit directory",
- expected_cover_generated_files())},
+ fun(RebarOut) ->
+ [{"Error messages are not present",
+ ?_assert(string:str(RebarOut, "Cover analyze failed for") =:= 0)},
+
+ {"All cover reports are generated",
+ assert_files_in("the temporary eunit directory",
+ expected_cover_generated_files())},
- {"Only production modules get coverage reports",
- assert_files_not_in("the temporary eunit directory",
- [".test/myapp_mymod_tests.COVER.html"])}]}.
+ {"Only production modules get coverage reports",
+ assert_files_not_in("the temporary eunit directory",
+ [".eunit/myapp_mymod_tests.COVER.html"])}]
+ end}.
cover_with_suite_test_() ->
{"Ensure Cover runs with Tests in a test dir and a test suite",
@@ -81,23 +220,28 @@ cover_with_suite_test_() ->
end,
fun teardown/1,
- [{"Cover reports are generated for module",
- assert_files_in("the temporary eunit directory",
- [".test/index.html",
- ".test/mysuite.COVER.html"])},
-
- {"Only production modules get coverage reports",
- assert_files_not_in("the temporary eunit directory",
- [".test/myapp_app.COVER.html",
- ".test/myapp_mymod.COVER.html",
- ".test/myapp_sup.COVER.html",
- ".test/myapp_mymod_tests.COVER.html"])}]}.
+ fun(RebarOut) ->
+ [{"Error messages are not present",
+ ?_assert(string:str(RebarOut, "Cover analyze failed for") =:= 0)},
+
+ {"Cover reports are generated for module",
+ assert_files_in("the temporary eunit directory",
+ [".eunit/index.html",
+ ".eunit/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"])}]
+ end}.
expected_cover_generated_files() ->
- [".test/index.html",
- ".test/myapp_app.COVER.html",
- ".test/myapp_mymod.COVER.html",
- ".test/myapp_sup.COVER.html"].
+ [".eunit/index.html",
+ ".eunit/myapp_app.COVER.html",
+ ".eunit/myapp_mymod.COVER.html",
+ ".eunit/myapp_sup.COVER.html"].
cover_coverage_test_() ->
{"Coverage is accurately calculated",
@@ -158,6 +302,28 @@ basic_setup_test_() ->
"-include_lib(\"eunit/include/eunit.hrl\").\n",
"myfunc_test() -> ?assertMatch(ok, myapp_mymod:myfunc()).\n"]).
+-define(myapp_mymod2,
+ ["-module(myapp_mymod2).\n",
+ "-export([myfunc2/0]).\n",
+ "-include_lib(\"eunit/include/eunit.hrl\").\n",
+ "myfunc2() -> ok.\n",
+ "myprivate2_test() -> ?assert(true).\n",
+ "common_name_test() -> ?assert(true).\n"]).
+
+-define(myapp_mymod2_tests,
+ ["-module(myapp_mymod2_tests).\n",
+ "-compile([export_all]).\n",
+ "-include_lib(\"eunit/include/eunit.hrl\").\n",
+ "myfunc2_test() -> ?assertMatch(ok, myapp_mymod2:myfunc2()).\n",
+ "common_name_test() -> ?assert(true).\n"]).
+
+-define(myapp_mymod3,
+ ["-module(myapp_mymod3).\n",
+ "-export([myfunc3/0]).\n",
+ "-include_lib(\"eunit/include/eunit.hrl\").\n",
+ "myfunc3() -> ok.\n",
+ "mygenerator_test_() -> [?_assertEqual(true, false)].\n"]).
+
-define(mysuite,
["-module(mysuite).\n",
"-export([all_test_/0]).\n",
@@ -186,6 +352,12 @@ setup_basic_project() ->
ok = file:write_file("test/myapp_mymod_tests.erl", ?myapp_mymod_tests),
ok = file:write_file("src/myapp_mymod.erl", ?myapp_mymod).
+setup_project_with_multiple_modules() ->
+ setup_basic_project(),
+ ok = file:write_file("test/myapp_mymod2_tests.erl", ?myapp_mymod2_tests),
+ ok = file:write_file("src/myapp_mymod2.erl", ?myapp_mymod2),
+ ok = file:write_file("src/myapp_mymod3.erl", ?myapp_mymod3).
+
setup_cover_project() ->
setup_basic_project(),
ok = file:write_file("rebar.config", "{cover_enabled, true}.\n").
@@ -246,7 +418,7 @@ assert_files_not_in(_, []) -> [].
assert_full_coverage(Mod) ->
fun() ->
- {ok, F} = file:read_file(".test/index.html"),
+ {ok, F} = file:read_file(".eunit/index.html"),
Result = [X || X <- string:tokens(binary_to_list(F), "\n"),
string:str(X, Mod) =/= 0,
string:str(X, "100%") =/= 0],