mirror of
https://github.com/correl/rebar.git
synced 2024-12-24 03:00:16 +00:00
Add experimental tests= filter for eunit suites
This commit is contained in:
parent
563607bc02
commit
69dc9ec933
3 changed files with 263 additions and 27 deletions
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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").
|
||||
|
|
Loading…
Reference in a new issue