diff options
46 files changed, 929 insertions, 379 deletions
@@ -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 @@ -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) @@ -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 @@ -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], |