diff options
Diffstat (limited to 'src/rebar_prv_eunit.erl')
-rw-r--r-- | src/rebar_prv_eunit.erl | 231 |
1 files changed, 124 insertions, 107 deletions
diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index d744204..18a8993 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -8,6 +8,8 @@ -export([init/1, do/1, format_error/1]). +%% exported solely for tests +-export([compile/1, prepare_tests/1, eunit_opts/1]). -include("rebar.hrl"). -include_lib("providers/include/providers.hrl"). @@ -37,11 +39,8 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - %% inject the `TEST` macro, `eunit_first_files` and `eunit_compile_opts` - %% into the applications to be compiled - NewState = inject_eunit_state(State), - - case rebar_prv_compile:do(NewState) of + case compile(State) of + %% successfully compiled apps {ok, S} -> do_tests(S); %% this should look like a compiler error, not an eunit error Error -> Error @@ -49,9 +48,10 @@ do(State) -> do_tests(State) -> ?INFO("Performing EUnit tests...", []), + rebar_utils:update_code(rebar_state:code_paths(State, all_deps)), - %% Run compile provider prehooks + %% Run eunit provider prehooks Providers = rebar_state:providers(State), Cwd = rebar_dir:get_cwd(), rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), @@ -101,8 +101,32 @@ format_error({error, Error}) -> %% Internal functions %% =================================================================== -%% currently only add the `extra_drc_dirs` on provider init -test_state(_State) -> [{extra_src_dirs, ["test"]}]. +test_state(State) -> + ErlOpts = rebar_state:get(State, erl_opts, []), + TestOpts = safe_define_test_macro(ErlOpts), + [{extra_src_dirs, ["test"]}, {erl_opts, TestOpts}]. + +safe_define_test_macro(Opts) -> + %% defining a compile macro twice results in an exception so + %% make sure 'TEST' is only defined once + case test_defined(Opts) of + true -> Opts; + false -> [{d, 'TEST'}] ++ Opts + end. + +compile(State) -> + %% inject `eunit_first_files` and `eunit_compile_opts` into the applications to be compiled + NewState = inject_eunit_state(State), + + case rebar_prv_compile:do(NewState) of + %% successfully compiled apps + {ok, S} -> + _ = maybe_cover_compile(S), + _ = maybe_compile_bare_testdir(S), + {ok, S}; + %% this should look like a compiler error, not an eunit error + Error -> Error + end. inject_eunit_state(State) -> Apps = project_apps(State), @@ -110,11 +134,10 @@ inject_eunit_state(State) -> rebar_state:project_apps(State, ModdedApps). inject(State, App) -> - %% append `eunit_compile_opts` to app defined `erl_opts` and define - %% the `TEST` macro if not already compiled + %% append `eunit_compile_opts` to app defined `erl_opts` ErlOpts = rebar_app_info:get(App, erl_opts, []), EUnitOpts = rebar_state:get(State, eunit_compile_opts, []), - NewOpts = safe_define_test_macro(EUnitOpts ++ ErlOpts), + NewOpts = EUnitOpts ++ ErlOpts, %% append `eunit_first_files` to app defined `erl_first_files` FirstFiles = rebar_app_info:get(App, erl_first_files, []), EUnitFirstFiles = rebar_state:get(State, eunit_first_files, []), @@ -124,29 +147,27 @@ inject(State, App) -> App, [{erl_opts, NewOpts}, {erl_first_files, NewFirstFiles}]). -safe_define_test_macro(Opts) -> - %% defining a compile macro twice results in an exception so - %% make sure 'TEST' is only defined once - case test_defined(Opts) of - true -> Opts; - false -> [{d, 'TEST'}] ++ Opts - end. - test_defined([{d, 'TEST'}|_]) -> true; test_defined([{d, 'TEST', true}|_]) -> true; test_defined([_|Rest]) -> test_defined(Rest); test_defined([]) -> false. prepare_tests(State) -> - {RawOpts, _} = rebar_state:command_parsed_args(State), - ok = maybe_cover_compile(State, RawOpts), - CmdTests = resolve_tests(RawOpts), + %% parse and translate command line tests + CmdTests = resolve_tests(State), CfgTests = rebar_state:get(State, eunit_tests, []), ProjectApps = project_apps(State), - Tests = select_tests(ProjectApps, CmdTests, CfgTests), + %% prioritize tests to run first trying any command line specified + %% tests falling back to tests specified in the config file finally + %% running a default set if no other tests are present + Tests = select_tests(State, ProjectApps, CmdTests, CfgTests), + %% check applications for existence in project, modules for existence + %% in applications, files and dirs for existence on disk and allow + %% any unrecognized tests through for eunit to handle validate_tests(State, ProjectApps, Tests). -resolve_tests(RawOpts) -> +resolve_tests(State) -> + {RawOpts, _} = rebar_state:command_parsed_args(State), Apps = resolve(app, application, RawOpts), Applications = resolve(application, RawOpts), Dirs = resolve(dir, RawOpts), @@ -166,8 +187,38 @@ resolve(Flag, EUnitKey, RawOpts) -> normalize(Key, Value) when Key == dir; Key == file -> {Key, Value}; normalize(Key, Value) -> {Key, list_to_atom(Value)}. -select_tests(ProjectApps, [], []) -> default_tests(ProjectApps); -select_tests(_ProjectApps, A, B) -> A ++ B. +project_apps(State) -> + filter_checkouts(rebar_state:project_apps(State)). + +filter_checkouts(Apps) -> filter_checkouts(Apps, []). + +filter_checkouts([], Acc) -> lists:reverse(Acc); +filter_checkouts([App|Rest], Acc) -> + case rebar_app_info:is_checkout(App) of + true -> filter_checkouts(Rest, Acc); + false -> filter_checkouts(Rest, [App|Acc]) + end. + +select_tests(State, ProjectApps, [], []) -> default_tests(State, ProjectApps); +select_tests(_State, _ProjectApps, [], Tests) -> Tests; +select_tests(_State, _ProjectApps, Tests, _) -> Tests. + +default_tests(State, Apps) -> + Tests = set_apps(Apps, []), + BareTest = filename:join([rebar_state:dir(State), "test"]), + F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end, + case filelib:is_dir(BareTest) andalso not lists:any(F, Apps) of + %% `test` dir at root of project is already scheduled to be + %% included or `test` does not exist + false -> lists:reverse(Tests); + %% need to add `test` dir at root to dirs to be included + true -> lists:reverse([{dir, filename:join([rebar_dir:base_dir(State), "test"])}|Tests]) + end. + +set_apps([], Acc) -> Acc; +set_apps([App|Rest], Acc) -> + AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))), + set_apps(Rest, [{application, AppName}|Acc]). validate_tests(State, ProjectApps, Tests) -> gather_tests(fun(Elem) -> validate(State, ProjectApps, Elem) end, Tests, []). @@ -175,34 +226,30 @@ validate_tests(State, ProjectApps, Tests) -> gather_tests(_F, [], Acc) -> {ok, lists:reverse(Acc)}; gather_tests(F, [Test|Rest], Acc) -> case F(Test) of - true -> gather_tests(F, Rest, [Test|Acc]); - false -> gather_tests(F, Rest, Acc); + ok -> gather_tests(F, Rest, [Test|Acc]); %% failure mode, switch to gathering errors - Error -> gather_errors(F, Rest, [Error]) + {error, Error} -> gather_errors(F, Rest, [Error]) end. gather_errors(_F, [], Acc) -> ?PRV_ERROR({eunit_test_errors, lists:reverse(Acc)}); gather_errors(F, [Test|Rest], Acc) -> case F(Test) of - true -> gather_errors(F, Rest, Acc); - false -> gather_errors(F, Rest, Acc); - Error -> gather_errors(F, Rest, [Error|Acc]) + ok -> gather_errors(F, Rest, Acc); + {error, Error} -> gather_errors(F, Rest, [Error|Acc]) end. validate(State, ProjectApps, {application, App}) -> validate_app(State, ProjectApps, App); validate(State, _ProjectApps, {dir, Dir}) -> - ok = maybe_compile_dir(State, Dir), validate_dir(State, Dir); validate(State, _ProjectApps, {file, File}) -> - ok = maybe_compile_file(State, File), validate_file(State, File); -validate(State, ProjectApps, {module, Module}) -> - validate_module(State, ProjectApps, Module); -validate(State, ProjectApps, {suite, Module}) -> - validate_module(State, ProjectApps, Module); -validate(State, ProjectApps, Module) when is_atom(Module) -> - validate_module(State, ProjectApps, Module); +validate(State, _ProjectApps, {module, Module}) -> + validate_module(State, Module); +validate(State, _ProjectApps, {suite, Module}) -> + validate_module(State, Module); +validate(State, _ProjectApps, Module) when is_atom(Module) -> + validate_module(State, Module); validate(State, ProjectApps, Path) when is_list(Path) -> case ec_file:is_dir(Path) of true -> validate(State, ProjectApps, {dir, Path}); @@ -211,80 +258,35 @@ validate(State, ProjectApps, Path) when is_list(Path) -> %% unrecognized tests should be included. if they're invalid eunit will error %% and rebar.config may contain arbitrarily complex tests that are effectively %% unvalidatable -validate(_State, _ProjectApps, _Test) -> true. +validate(_State, _ProjectApps, _Test) -> ok. -validate_app(State, [], AppName) -> - warn_or_error(State, lists:concat(["Application `", AppName, "' not found in project."])); +validate_app(_State, [], AppName) -> + {error, lists:concat(["Application `", AppName, "' not found in project."])}; validate_app(State, [App|Rest], AppName) -> case AppName == binary_to_atom(rebar_app_info:name(App), unicode) of - true -> true; + true -> ok; false -> validate_app(State, Rest, AppName) end. -validate_dir(State, Dir) -> +validate_dir(_State, Dir) -> case ec_file:is_dir(Dir) of - true -> true; - false -> warn_or_error(State, lists:concat(["Directory `", Dir, "' not found."])) + true -> ok; + false -> {error, lists:concat(["Directory `", Dir, "' not found."])} end. -validate_file(State, File) -> +validate_file(_State, File) -> case ec_file:exists(File) of - true -> true; - false -> warn_or_error(State, lists:concat(["File `", File, "' not found."])) - end. - -validate_module(State, Apps, Module) -> - AppModules = app_modules([binary_to_atom(rebar_app_info:name(A), unicode) || A <- Apps], []), - case lists:member(Module, AppModules) of - true -> true; - false -> warn_or_error(State, lists:concat(["Module `", Module, "' not found in applications."])) - end. - -warn_or_error(State, Msg) -> - {RawOpts, _} = rebar_state:command_parsed_args(State), - Error = proplists:get_value(error_on_warning, RawOpts, false), - case Error of - true -> Msg; - false -> ?WARN(Msg, []), false - end. - -project_apps(State) -> - filter_checkouts(rebar_state:project_apps(State)). - -filter_checkouts(Apps) -> filter_checkouts(Apps, []). - -filter_checkouts([], Acc) -> lists:reverse(Acc); -filter_checkouts([App|Rest], Acc) -> - case rebar_app_info:is_checkout(App) of - true -> filter_checkouts(Rest, Acc); - false -> filter_checkouts(Rest, [App|Acc]) + true -> ok; + false -> {error, lists:concat(["File `", File, "' not found."])} end. -app_modules([], Acc) -> Acc; -app_modules([App|Rest], Acc) -> - Unload = case application:load(App) of - ok -> true; - {error, {already_loaded, _}} -> false - end, - NewAcc = case application:get_key(App, modules) of - {ok, Modules} -> Modules ++ Acc; - undefined -> Acc - end, - case Unload of - true -> - application:unload(App), - app_modules(Rest, NewAcc); - false -> - app_modules(Rest, NewAcc) +validate_module(_State, Module) -> + Path = code:which(Module), + case beam_lib:chunks(Path, [exports]) of + {ok, _} -> ok; + {error, beam_lib, _} -> {error, lists:concat(["Module `", Module, "' not found in project."])} end. -default_tests(Apps) -> set_apps(Apps, []). - -set_apps([], Acc) -> lists:reverse(Acc); -set_apps([App|Rest], Acc) -> - AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))), - set_apps(Rest, [{application, AppName}|Acc]). - resolve_eunit_opts(State) -> {Opts, _} = rebar_state:command_parsed_args(State), EUnitOpts = rebar_state:get(State, eunit_opts, []), @@ -300,16 +302,33 @@ set_verbose(Opts) -> false -> [verbose] ++ Opts end. -maybe_compile_dir(_, _) -> ok. -maybe_compile_file(_, _) -> ok. - -maybe_cover_compile(State, Opts) -> - State1 = case proplists:get_value(cover, Opts, false) of +maybe_cover_compile(State) -> + State1 = case rebar_state:get(State, cover, false) of true -> rebar_state:set(State, cover_enabled, true); false -> State end, rebar_prv_cover:maybe_cover_compile(State1). +maybe_compile_bare_testdir(State) -> + case ec_file:is_dir(filename:join([rebar_state:dir(State), "test"])) of + true -> + ErlOpts = rebar_state:get(State, erl_opts, []), + EUnitOpts = rebar_state:get(State, eunit_compile_opts, []), + NewErlOpts = safe_define_test_macro(EUnitOpts ++ ErlOpts), + %% append `eunit_first_files` to app defined `erl_first_files` + FirstFiles = rebar_state:get(State, erl_first_files, []), + EUnitFirstFiles = rebar_state:get(State, eunit_first_files, []), + NewFirstFiles = EUnitFirstFiles ++ FirstFiles, + Opts = rebar_state:opts(State), + NewOpts = lists:foldl(fun({K, V}, Dict) -> rebar_opts:set(Dict, K, V) end, + Opts, + [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}, {src_dirs, ["test"]}]), + OutDir = filename:join([rebar_dir:base_dir(State), "test"]), + filelib:ensure_dir(filename:join([OutDir, "dummy.beam"])), + rebar_erlc_compiler:compile(NewOpts, rebar_state:dir(State), OutDir); + false -> ok + end. + handle_results(ok) -> ok; handle_results(error) -> {error, unknown_error}; @@ -321,7 +340,6 @@ eunit_opts(_State) -> {application, undefined, "application", string, help(app)}, {cover, $c, "cover", boolean, help(cover)}, {dir, undefined, "dir", string, help(dir)}, - {error_on_warning, $e, "error_on_warning", boolean, help(error)}, {file, undefined, "file", string, help(file)}, {module, undefined, "module", string, help(module)}, {suite, undefined, "suite", string, help(module)}, @@ -330,7 +348,6 @@ eunit_opts(_State) -> help(app) -> "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`."; help(cover) -> "Generate cover data. Defaults to false."; help(dir) -> "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`."; -help(error) -> "Error on invalid test specifications instead of warning."; help(file) -> "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`."; help(module) -> "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`."; help(verbose) -> "Verbose output. Defaults to false.". |