Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Add 'generator' option to eunit to run a single generator #250

wants to merge 2 commits into from

4 participants


Eunit allows to specify individual test generators to run, instead of all the tests or a specific suite (Ref). This is very useful when you are working on a single test and don't necessarily want to run all tests every time you are writing one.

You can for instance run just the single test myapp_mymod_tests:myfunc_test_/0 by running:

eunit:test({generator, myapp_mymod_tests, myfunc_test_}).

This patch allows to specify a single generator test like this:

rebar eunit generator=myapp_mymod_tests:myfunc_test_

Output of make check:

$ make check
Command 'debug' not understood or not applicable
Congratulations! You now have a self-contained script called "rebar" in
your current working directory. Place this script anywhere in your path
and you can use rebar to build OTP-compliant apps.
==> rebar (xref)
make: [dialyzer_warnings] Error 2 (ignored)

Three new tests added, partial output of rebar eunit -v:

Ensure EUnit runs with tests in a 'test' dir and a defined generator
  rebar_eunit_tests:68: eunit_with_generator_test_ (Specific module is found and run)...ok
  rebar_eunit_tests:71: eunit_with_generator_test_ (Specific test is found and run)...ok
  rebar_eunit_tests:74: eunit_with_generator_test_ (Single test is run)...ok

+1 for selecting generators/tests with "tests" parameter.


@richcarl thoughts?


I think that if you write suite=a,b,... test=x,y,..., then rebar should for each m in a,b,... check module m (and m_tests) for test functions (...test() and ..._test()), and for each t in x,y,..., pick the shortest named test function (if any) such that t is a prefix of the name, and run that as a test if it is named ...test() or as a test generator if it is named ..._test(). Alternatively, don't allow t to be an arbitrary prefix, but only an exact match of the whole name before test() or _test().


fair enough.

however, according to the documentation I don't see an option to run a specific test, just a generator.

@richcarl can you please point me in the right direction?


Oops, you're right, I never implemented {test, M, F}. I think I should do that. (Unofficially, {M,F} works right now but should be removed, since it's ambiguous.) Meanwhile, you can use eunit_test:mf_wrapper(M,F) to turn a module and function name into a 0-arity test fun. This gives better error reporting than erlang:make_fun(M,F,0) in case the module or function is missing.


@richcarl the two functions you are referring to eunit_test:mf_wrapper(M,F) and erlang:make_fun(M,F,0) seem to be undocumented (or at least, I wasn't able to find any docs for them).

I'm a little doubtful on how to proceed.

Wouldn't it be better to implement only the generators selection for now, and leave the implementation of the tests once these are properly supported in eunit? Or is there a way to implement those in a clean and maintainable way (i.e. without undocumented functions)? @tuncer?


Don't be such a wuss, Roberto! ;-)

Anyhow, you can trust that eunit_test:mf_wrapper(M,F) won't go away anytime soon, even though it's not documented. And erlang:make_fun(M,F,0) isn't documented either, but in practice it's currently the only way of dynamically making a symbolic fun object from atoms+arity, and it's used by several projects; it's not likely to be removed or renamed.


No problem in going this way, if that's what it takes. ^^_

But before I dig into this I'd still want to hear from @tuncer, as I'd rather avoid losing dev time on this if this route isn't viable in the first place.


Works for me as @richcarl made it clear and promised that the undocumented functions won't be changed or removed. @richcarl would it make sense to document the two functions?


I don't think eunit:mf_wrapper/2 should be documented. (In my dev repo at github, I have added {test,M,F} which has the same effect, but that might not be included in the official OTP distro soon enough for you. But you can change your implementation to use this later on when it's generally available.) The erlang:make_fun/3 function shouldn't need to be documented either - actually, if you're only targeting R15 and later, you can simply use the syntax "fun M:F/A" where M, F, A, are variables (only constant atoms and integers are allowed in R14 and earlier). From the R15A relase notes: "OTP-9643 Variables are now now allowed in 'fun M:F/A'".


I've opened a new pull request #282 implementing what has been discussed above.

@ostinelli ostinelli closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 53 additions and 8 deletions.
  1. +1 −1  src/rebar.erl
  2. +23 −7 src/rebar_eunit.erl
  3. +29 −0 test/rebar_eunit_tests.erl
2  src/rebar.erl
@@ -279,7 +279,7 @@ generate-upgrade previous_release=path Build an upgrade package
generate-appups previous_release=path Generate appup files
-eunit [suite=foo] Run eunit [test/foo_tests.erl] tests
+eunit [suite=foo] [generator=mymod:my_test_] Run eunit [test/foo_tests.erl] [mymod:my_test_/0] tests
ct [suites=] [case=] Run common_test suites in ./test
xref Run cross reference analysis
30 src/rebar_eunit.erl
@@ -43,7 +43,10 @@
%% The following Global options are supported:
%% <ul>
%% <li>verbose=1 - show extra output from the eunit test</li>
-%% <li>suite="foo"" - runs test/foo_tests.erl</li>
+%% <li>suite="foo" - runs test/foo_tests.erl</li>
+%% <li>generator="myapp_mymod_tests:myfunc_test_" -
+%% runs single generator test myapp_mymod_tests:myfunc_test_/0
+%% </li>
%% </ul>
%% Additionally, for projects that have separate folders for the core
%% implementation, and for the unit tests, then the following
@@ -174,9 +177,18 @@ ebin_dir() ->
filename:join(rebar_utils:get_cwd(), "ebin").
perform_eunit(Config, Modules) ->
- %% suite defined, so only specify the module that relates to the
- %% suite (if any). Suite can be a comma seperated list of modules to run.
- Suite = rebar_config:get_global(suite, undefined),
+ %% suite or generator defined, so only specify the module that relates to the
+ %% suite/generator (if any). Suite/Generator can be a comma seperated list of
+ %% modules to run.
+ TestInfo = case rebar_config:get_global(suite, undefined) of
+ undefined ->
+ case rebar_config:get_global(generator, undefined) of
+ undefined -> undefined;
+ Generators -> {generators, Generators}
+ end;
+ Suites ->
+ {suites, Suites}
+ end,
EunitOpts = get_eunit_opts(Config),
%% Move down into ?EUNIT_DIR while we run tests so any generated files
@@ -184,7 +196,7 @@ perform_eunit(Config, Modules) ->
Cwd = rebar_utils:get_cwd(),
ok = file:set_cwd(?EUNIT_DIR),
- EunitResult = perform_eunit(EunitOpts, Modules, Suite),
+ EunitResult = perform_eunit(EunitOpts, Modules, TestInfo),
%% Return to original working dir
ok = file:set_cwd(Cwd),
@@ -193,9 +205,13 @@ perform_eunit(Config, Modules) ->
perform_eunit(EunitOpts, Modules, undefined) ->
(catch eunit:test(Modules, EunitOpts));
-perform_eunit(EunitOpts, _Modules, Suites) ->
+perform_eunit(EunitOpts, _Modules, {suites, Suites}) ->
(catch eunit:test([list_to_atom(Suite) ||
- Suite <- string:tokens(Suites, ",")], EunitOpts)).
+ Suite <- string:tokens(Suites, ",")], EunitOpts));
+perform_eunit(EunitOpts, _Modules, {generators, Generators}) ->
+ (catch eunit:test([{generator, list_to_atom(GeneratorMod), list_to_atom(GeneratorFun)} ||
+ [GeneratorMod, GeneratorFun] <- [string:tokens(Generator, ":") ||
+ Generator <- string:tokens(Generators, ",")]], EunitOpts)).
get_eunit_opts(Config) ->
%% Enable verbose in eunit if so requested..
29 test/rebar_eunit_tests.erl
@@ -59,6 +59,21 @@ eunit_test_() ->
?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
+eunit_with_generator_test_() ->
+ {"Ensure EUnit runs with tests in a 'test' dir and a defined generator",
+ setup, fun() -> setup_basic_generator_project(), rebar("-v eunit generator=myapp_mymod_tests:myfunc_test_") end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Specific module is found and run",
+ ?_assert(string:str(RebarOut, "myapp_mymod_tests:") =/= 0)},
+ {"Specific test is found and run",
+ ?_assert(string:str(RebarOut, "myfunc_test_") =/= 0)},
+ {"Single test is run",
+ ?_assert(string:str(RebarOut, "Test 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,
@@ -155,6 +170,12 @@ basic_setup_test_() ->
"myfunc_test() -> ?assertMatch(ok, myapp_mymod:myfunc()).\n"]).
+ ["-module(myapp_mymod_tests).\n",
+ "-compile([export_all]).\n",
+ "-include_lib(\"eunit/include/eunit.hrl\").\n",
+ "myfunc_test_() -> [?_assertMatch(ok, myapp_mymod:myfunc())].\n"]).
@@ -183,6 +204,14 @@ 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_basic_generator_project() ->
+ setup_environment(),
+ rebar("create-app appid=myapp"),
+ ok = file:make_dir("ebin"),
+ ok = file:make_dir("test"),
+ ok = file:write_file("test/myapp_mymod_tests.erl", ?myapp_mymod_generator_tests),
+ ok = file:write_file("src/myapp_mymod.erl", ?myapp_mymod).
setup_cover_project() ->
ok = file:write_file("rebar.config", "{cover_enabled, true}.\n").
Something went wrong with that request. Please try again.