diff options
Diffstat (limited to 'src/rebar_eunit.erl')
-rw-r--r-- | src/rebar_eunit.erl | 168 |
1 files changed, 142 insertions, 26 deletions
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index 85eca3a..8e09452 100644 --- a/src/rebar_eunit.erl +++ b/src/rebar_eunit.erl @@ -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), |