Add qualified name tests specification (see #118)

Augment 'tests' option of 'rebar eunit' command with ability to specify
tests to run using module-qualified names. This change also forced me
to change the way modules for coverage and for testing itself are
selected - module-qualified tests specifications are now taken into
consideration. Extend tests to cover new functionality. Update
dialyzer_reference accordingly.
This commit is contained in:
Sergey Savenko 2013-08-15 12:26:50 +04:00
parent f47af30e65
commit 93689703c1
3 changed files with 144 additions and 69 deletions

View file

@ -1,3 +1,3 @@
rebar_eunit.erl:434: Call to missing or unexported function eunit_test:function_wrapper/2 rebar_eunit.erl:469: Call to missing or unexported function eunit_test:function_wrapper/2
rebar_utils.erl:164: Call to missing or unexported function escript:foldl/3 rebar_utils.erl:164: Call to missing or unexported function escript:foldl/3

View file

@ -155,12 +155,10 @@ run_eunit(Config, CodePath, SrcErls) ->
[filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles], [filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
ModuleBeamFiles = randomize_suites(Config, BeamFiles ++ OtherBeamFiles), ModuleBeamFiles = randomize_suites(Config, BeamFiles ++ OtherBeamFiles),
%% Get modules to be run in eunit %% Get matching tests and modules
AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles], AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles],
{SuitesProvided, FilteredModules} = filter_suites(Config, AllModules), {Tests, CoverageModules} =
get_tests_and_modules(Config, ModuleBeamFiles, AllModules),
%% Get matching tests
Tests = get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules),
SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls], SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
@ -169,7 +167,7 @@ run_eunit(Config, CodePath, SrcErls) ->
StatusBefore = status_before_eunit(), StatusBefore = status_before_eunit(),
EunitResult = perform_eunit(Config, Tests), EunitResult = perform_eunit(Config, Tests),
perform_cover(Config, FilteredModules, SrcModules), perform_cover(Config, CoverageModules, SrcModules),
cover_close(CoverLog), cover_close(CoverLog),
case proplists:get_value(reset_after_eunit, get_eunit_opts(Config), case proplists:get_value(reset_after_eunit, get_eunit_opts(Config),
@ -214,18 +212,23 @@ setup_code_path() ->
CodePath. CodePath.
%% %%
%% == filter suites == %% == get matching tests ==
%% %%
get_tests_and_modules(Config, ModuleBeamFiles, AllModules) ->
SelectedSuites = get_selected_suites(Config, AllModules),
{Tests, QualifiedTests} = get_qualified_and_unqualified_tests(Config),
Modules = get_test_modules(SelectedSuites, Tests,
QualifiedTests, ModuleBeamFiles),
CoverageModules = get_coverage_modules(AllModules, Modules, QualifiedTests),
MatchedTests = get_matching_tests(Modules, Tests, QualifiedTests),
{MatchedTests, CoverageModules}.
filter_suites(Config, Modules) -> %%
%% == get suites specified via 'suites' option ==
%%
get_selected_suites(Config, Modules) ->
RawSuites = get_suites(Config), RawSuites = get_suites(Config),
SuitesProvided = RawSuites =/= "",
Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")], Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")],
{SuitesProvided, filter_suites1(Modules, Suites)}.
filter_suites1(Modules, []) ->
Modules;
filter_suites1(Modules, Suites) ->
[M || M <- Suites, lists:member(M, Modules)]. [M || M <- Suites, lists:member(M, Modules)].
get_suites(Config) -> get_suites(Config) ->
@ -236,6 +239,32 @@ get_suites(Config) ->
Suites Suites
end. end.
get_qualified_and_unqualified_tests(Config) ->
RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""),
FunctionNames = [FunctionName ||
FunctionName <- string:tokens(RawFunctions, ",")],
get_qualified_and_unqualified_tests1(FunctionNames, [], []).
get_qualified_and_unqualified_tests1([], Functions, QualifiedFunctions) ->
{Functions, QualifiedFunctions};
get_qualified_and_unqualified_tests1([TestName|TestNames], Functions,
QualifiedFunctions) ->
case string:tokens(TestName, ":") of
[TestName] ->
Function = list_to_atom(TestName),
get_qualified_and_unqualified_tests1(
TestNames, [Function|Functions], QualifiedFunctions);
[ModuleName, FunctionName] ->
M = list_to_atom(ModuleName),
F = list_to_atom(FunctionName),
get_qualified_and_unqualified_tests1(TestNames, Functions,
[{M, F}|QualifiedFunctions]);
_ ->
?ABORT("Unsupported test function specification: ~s~n", [TestName])
end.
%% Provide modules which are to be searched for tests.
%% Several scenarios are possible:
%% %%
%% == randomize suites == %% == randomize suites ==
%% %%
@ -265,60 +294,66 @@ randomize_suites1(Modules, Seed) ->
%% %%
%% == get matching tests == %% == get matching tests ==
%% 1) Specific tests have been provided and/or
%% no unqualified tests have been specified and
%% there were some qualified tests, then we can search for
%% functions in specified suites (or in empty set of suites).
%% %%
get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) -> %% 2) Neither specific suites nor qualified test names have been
Modules = case SuitesProvided of %% provided use ModuleBeamFiles which filters out "*_tests"
false -> %% modules so EUnit won't doubly run them and cover only
%% No specific suites have been provided, use %% calculates coverage on production code. However,
%% ModuleBeamFiles which filters out "*_tests" modules %% keep "*_tests" modules that are not automatically
%% so eunit won't doubly run them and cover only %% included by EUnit.
%% calculates coverage on production code. However, %%
%% keep "*_tests" modules that are not automatically %% From 'Primitives' in the EUnit User's Guide
%% included by eunit. %% http://www.erlang.org/doc/apps/eunit/chapter.html
%% %% "In addition, EUnit will also look for another
%% From 'Primitives' in the EUnit User's Guide %% module whose name is ModuleName plus the suffix
%% http://www.erlang.org/doc/apps/eunit/chapter.html %% _tests, and if it exists, all the tests from that
%% "In addition, EUnit will also look for another %% module will also be added. (If ModuleName already
%% module whose name is ModuleName plus the suffix %% contains the suffix _tests, this is not done.) E.g.,
%% _tests, and if it exists, all the tests from that %% the specification {module, mymodule} will run all
%% module will also be added. (If ModuleName already %% tests in the modules mymodule and mymodule_tests.
%% contains the suffix _tests, this is not done.) E.g., %% Typically, the _tests module should only contain
%% the specification {module, mymodule} will run all %% test cases that use the public interface of the main
%% tests in the modules mymodule and mymodule_tests. %% module (and no other code)."
%% Typically, the _tests module should only contain get_test_modules(SelectedSuites, Tests, QualifiedTests, ModuleBeamFiles) ->
%% test cases that use the public interface of the main SuitesProvided = SelectedSuites =/= [],
%% module (and no other code)." OnlyQualifiedTestsProvided = QualifiedTests =/= [] andalso Tests =:= [],
[rebar_utils:beam_to_mod(?EUNIT_DIR, N) || if
N <- ModuleBeamFiles]; SuitesProvided orelse OnlyQualifiedTestsProvided ->
SelectedSuites;
true -> true ->
%% Specific suites have been provided, return the [rebar_utils:beam_to_mod(?EUNIT_DIR, N) ||
%% filtered modules N <- ModuleBeamFiles]
FilteredModules
end,
get_matching_tests(Config, Modules).
get_tests(Config) ->
case rebar_config:get_global(Config, tests, "") of
"" ->
rebar_config:get_global(Config, test, "");
Suites ->
Suites
end. end.
get_matching_tests(Config, Modules) -> get_coverage_modules(AllModules, Modules, QualifiedTests) ->
RawFunctions = get_tests(Config), ModuleFilterMapper =
Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")], fun({M, _}) ->
case Tests of case lists:member(M, AllModules) of
[] -> true -> {true, M};
Modules; _-> false
Functions ->
case get_matching_tests1(Modules, Functions, []) of
[] ->
[];
RawTests ->
make_test_primitives(RawTests)
end end
end. end,
ModulesFromQualifiedTests = lists:zf(ModuleFilterMapper, QualifiedTests),
lists:usort(Modules ++ ModulesFromQualifiedTests).
get_matching_tests(Modules, [], []) ->
Modules;
get_matching_tests(Modules, [], QualifiedTests) ->
FilteredQualifiedTests = filter_qualified_tests(Modules, QualifiedTests),
lists:merge(Modules, make_test_primitives(FilteredQualifiedTests));
get_matching_tests(Modules, Tests, QualifiedTests) ->
AllTests = lists:merge(QualifiedTests,
get_matching_tests1(Modules, Tests, [])),
make_test_primitives(AllTests).
filter_qualified_tests(Modules, QualifiedTests) ->
TestsFilter = fun({Module, _Function}) ->
lists:all(fun(M) -> M =/= Module end, Modules) end,
lists:filter(TestsFilter, QualifiedTests).
get_matching_tests1([], _Functions, TestFunctions) -> get_matching_tests1([], _Functions, TestFunctions) ->
TestFunctions; TestFunctions;

View file

@ -191,6 +191,46 @@ eunit_with_suites_and_tests_test_() ->
{"Selected suite tests is run once", {"Selected suite tests is run once",
?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}] ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
end},
{"Ensure EUnit runs a specific test by qualified function name",
setup,
fun() ->
setup_project_with_multiple_modules(),
rebar("-v eunit tests=myapp_mymod:myprivate_test")
end,
fun teardown/1,
fun(RebarOut) ->
[{"Selected test is run",
[?_assert(string:str(RebarOut,
"myapp_mymod:myprivate_test/0")
=/= 0)]},
{"Only selected test is run",
[?_assert(string:str(RebarOut,
"Test passed.") =/= 0)]}]
end},
{"Ensure EUnit runs a specific test by qualified function "
++ "name and tests from other module",
setup,
fun() ->
setup_project_with_multiple_modules(),
rebar("-v eunit suites=myapp_mymod3 "
++ "tests=myapp_mymod:myprivate_test")
end,
fun teardown/1,
fun(RebarOut) ->
[{"Selected test is run",
[?_assert(string:str(RebarOut,
"myapp_mymod:myprivate_test/0") =/= 0)]},
{"Tests from module are run",
[?_assert(string:str(RebarOut,
"myapp_mymod3:") =/= 0)]},
{"Only selected tests are run",
[?_assert(string:str(RebarOut,
"Failed: 1. Skipped: 0. Passed: 1")
=/= 0)]}]
end}]. end}].
cover_test_() -> cover_test_() ->