Add experimental tests= filter for eunit suites

This commit is contained in:
Roberto Ostinelli 2012-07-28 17:04:50 -07:00 committed by Tuncer Ayaz
parent 563607bc02
commit 69dc9ec933
3 changed files with 263 additions and 27 deletions

View file

@ -297,7 +297,9 @@ generate-upgrade previous_release=path Build an upgrade package
generate-appups previous_release=path Generate appup files
eunit [suites=foo] Run eunit [test/foo_tests.erl] tests
eunit [suites=foo] Run eunit tests [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]
ct [suites=] [case=] Run common_test suites
qc Test QuickCheck properties

View file

@ -43,7 +43,14 @@
%% 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
%% </ul>
%% Additionally, for projects that have separate folders for the core
%% implementation, and for the unit tests, then the following
@ -92,21 +99,7 @@ run_eunit(Config, CodePath, SrcErls) ->
%% 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)."
%% compilation info to be lost.
AllBeamFiles = rebar_utils:beams(?EUNIT_DIR),
{BeamFiles, TestBeamFiles} =
@ -115,16 +108,20 @@ run_eunit(Config, CodePath, SrcErls) ->
OtherBeamFiles = TestBeamFiles --
[filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
ModuleBeamFiles = BeamFiles ++ OtherBeamFiles,
Modules = [rebar_utils:beam_to_mod(?EUNIT_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),
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),
@ -164,17 +161,136 @@ setup_code_path() ->
true = code:add_pathz(rebar_utils:ebin_dir()),
CodePath.
filter_modules(Config, Modules) ->
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_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) ->
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 existing modules
build_tests(Config, FilteredModules)
end.
build_tests(Config, SuitesModules) ->
RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""),
Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")],
case Tests of
[] ->
SuitesModules;
Functions ->
case build_tests1(SuitesModules, Functions, []) of
[] ->
[];
RawTests ->
?CONSOLE(" Running test function(s):~n", []),
F = fun({M, F2}, Acc) ->
?CONSOLE(" ~p:~p/0~n", [M, F2]),
[eunit_test:function_wrapper(M, F2)|Acc]
end,
lists:foldl(F, [], RawTests)
end
end.
build_tests1([], _Functions, TestFunctions) ->
TestFunctions;
build_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 = build_tests2(Functions, {Module, ModuleExports},
{list_to_atom(TestModuleStr), TestModuleExports}),
build_tests1(TModules, Functions, lists:merge([TestFunctions, Tests])).
build_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.
perform_eunit(Config, Tests) ->
EunitOpts = get_eunit_opts(Config),
%% Move down into ?EUNIT_DIR while we run tests so any generated files
@ -182,7 +298,7 @@ perform_eunit(Config, FilteredModules) ->
Cwd = rebar_utils:get_cwd(),
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),

View file

@ -59,6 +59,104 @@ 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 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 _tests suites",
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}].
cover_test_() ->
{"Ensure Cover runs with tests in a test dir and no defined suite",
setup, fun() -> setup_cover_project(), rebar("-v eunit") end,
@ -158,6 +256,21 @@ 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(mysuite,
["-module(mysuite).\n",
"-export([all_test_/0]).\n",
@ -186,6 +299,11 @@ 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).
setup_cover_project() ->
setup_basic_project(),
ok = file:write_file("rebar.config", "{cover_enabled, true}.\n").