From 17e26f72653706e8c555082a2c9ae9e73351fff8 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Fri, 30 Oct 2015 01:01:12 -0700 Subject: delay validation of eunit tests until just before running --- src/rebar_prv_eunit.erl | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index b748b06..357b92f 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -9,7 +9,7 @@ do/1, format_error/1]). %% exported solely for tests --export([compile/2, prepare_tests/1, eunit_opts/1]). +-export([compile/2, prepare_tests/1, eunit_opts/1, validate_tests/2]). -include("rebar.hrl"). -include_lib("providers/include/providers.hrl"). @@ -57,7 +57,7 @@ do(State, Tests) -> Cwd = rebar_dir:get_cwd(), rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), - case Tests of + case validate_tests(State, Tests) of {ok, T} -> case run_tests(State, T) of {ok, State1} -> @@ -204,11 +204,7 @@ prepare_tests(State) -> %% 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). + select_tests(State, ProjectApps, CmdTests, CfgTests). resolve_tests(State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), @@ -231,9 +227,9 @@ resolve(Flag, EUnitKey, RawOpts) -> normalize(Key, Value) when Key == dir; Key == file -> {Key, Value}; normalize(Key, Value) -> {Key, list_to_atom(Value)}. -select_tests(State, ProjectApps, [], []) -> default_tests(State, ProjectApps); -select_tests(_State, _ProjectApps, [], Tests) -> Tests; -select_tests(_State, _ProjectApps, Tests, _) -> Tests. +select_tests(State, ProjectApps, [], []) -> {ok, default_tests(State, ProjectApps)}; +select_tests(_State, _ProjectApps, [], Tests) -> {ok, Tests}; +select_tests(_State, _ProjectApps, Tests, _) -> {ok, Tests}. default_tests(State, Apps) -> Tests = set_apps(Apps, []), @@ -252,8 +248,9 @@ 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, []). +validate_tests(State, {ok, Tests}) -> + gather_tests(fun(Elem) -> validate(State, Elem) end, Tests, []); +validate_tests(_State, Error) -> Error. gather_tests(_F, [], Acc) -> {ok, lists:reverse(Acc)}; gather_tests(F, [Test|Rest], Acc) -> @@ -270,27 +267,31 @@ gather_errors(F, [Test|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}) -> +validate(State, {application, App}) -> + validate_app(State, App); +validate(State, {dir, Dir}) -> validate_dir(State, Dir); -validate(State, _ProjectApps, {file, File}) -> +validate(State, {file, File}) -> validate_file(State, File); -validate(State, _ProjectApps, {module, Module}) -> +validate(State, {module, Module}) -> validate_module(State, Module); -validate(State, _ProjectApps, {suite, Module}) -> +validate(State, {suite, Module}) -> validate_module(State, Module); -validate(State, _ProjectApps, Module) when is_atom(Module) -> +validate(State, Module) when is_atom(Module) -> validate_module(State, Module); -validate(State, ProjectApps, Path) when is_list(Path) -> +validate(State, Path) when is_list(Path) -> case ec_file:is_dir(Path) of - true -> validate(State, ProjectApps, {dir, Path}); - false -> validate(State, ProjectApps, {file, Path}) + true -> validate(State, {dir, Path}); + false -> validate(State, {file, Path}) end; %% 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) -> ok. +validate(_State, _Test) -> ok. + +validate_app(State, AppName) -> + ProjectApps = rebar_state:project_apps(State), + validate_app(State, ProjectApps, AppName). validate_app(_State, [], AppName) -> {error, lists:concat(["Application `", AppName, "' not found in project."])}; -- cgit v1.1 From d48f02dbc4f45b7e4d13e4ae847c44aad67dd873 Mon Sep 17 00:00:00 2001 From: James Fish Date: Sat, 31 Oct 2015 16:43:04 +0000 Subject: Rebuild PLT when beams no longer exist --- src/rebar_prv_dialyzer.erl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 487e9d1..2e5728a 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -173,7 +173,7 @@ do_update_proj_plt(State, Plt, Output) -> case read_plt(State, Plt) of {ok, OldFiles} -> check_plt(State, Plt, Output, OldFiles, Files); - {error, no_such_file} -> + error -> build_proj_plt(State, Plt, Output, Files) end. @@ -252,14 +252,25 @@ read_plt(_State, Plt) -> case dialyzer:plt_info(Plt) of {ok, Info} -> Files = proplists:get_value(files, Info, []), - {ok, Files}; - {error, no_such_file} = Error -> - Error; + read_plt_files(Plt, Files); + {error, no_such_file} -> + error; {error, read_error} -> Error = io_lib:format("Could not read the PLT file ~p", [Plt]), throw({dialyzer_error, Error}) end. +%% If any file no longer exists dialyzer will fail when updating the PLT. +read_plt_files(Plt, Files) -> + case [File || File <- Files, not filelib:is_file(File)] of + [] -> + {ok, Files}; + Missing -> + ?INFO("Could not find ~p files in ~p...", [length(Missing), Plt]), + ?DEBUG("Could not find files: ~p", [Missing]), + error + end. + check_plt(State, Plt, Output, OldList, FilesList) -> Old = sets:from_list(OldList), Files = sets:from_list(FilesList), @@ -337,7 +348,7 @@ update_base_plt(State, BasePlt, Output, BaseFiles) -> case read_plt(State, BasePlt) of {ok, OldBaseFiles} -> check_plt(State, BasePlt, Output, OldBaseFiles, BaseFiles); - {error, no_such_file} -> + error -> _ = filelib:ensure_dir(BasePlt), build_plt(State, BasePlt, Output, BaseFiles) end. -- cgit v1.1 From 67bf54d04d332bbff961ab4e8ab730858bcdce7a Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Sun, 1 Nov 2015 13:41:46 -0800 Subject: add an option to soft purge rather than purge old code at the cost of some SASL warnings this prevents rebar3 from terminating processes when reloading their code before running tests --- src/rebar_prv_common_test.erl | 2 +- src/rebar_prv_eunit.erl | 2 +- src/rebar_utils.erl | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 1f4c02d..32d4433 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -38,7 +38,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Running Common Test suites...", []), - rebar_utils:update_code(rebar_state:code_paths(State, all_deps)), + rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]), %% Run ct provider prehooks Providers = rebar_state:providers(State), diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 0cdc20b..d5612e8 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -49,7 +49,7 @@ do(State) -> do(State, Tests) -> ?INFO("Performing EUnit tests...", []), - rebar_utils:update_code(rebar_state:code_paths(State, all_deps)), + rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]), %% Run eunit provider prehooks Providers = rebar_state:providers(State), diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index d00a46f..ea60e42 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -47,6 +47,7 @@ deprecated/4, indent/1, update_code/1, + update_code/2, remove_from_code_path/1, cleanup_code_path/1, args_to_tasks/1, @@ -644,7 +645,9 @@ indent(Amount) when erlang:is_integer(Amount) -> %% Replace code paths with new paths for existing apps and %% purge code of the old modules from those apps. -update_code(Paths) -> +update_code(Paths) -> update_code(Paths, []). + +update_code(Paths, Opts) -> lists:foreach(fun(Path) -> Name = filename:basename(Path, "/ebin"), App = list_to_atom(Name), @@ -654,19 +657,18 @@ update_code(Paths) -> code:add_patha(Path), ok; {ok, Modules} -> - %% stick rebar ebin dir before purging to prevent - %% inadvertent termination - RebarBin = code:lib_dir(rebar, ebin), - ok = code:stick_dir(RebarBin), %% replace_path causes problems when running %% tests in projects like erlware_commons that rebar3 %% also includes %code:replace_path(App, Path), code:del_path(App), code:add_patha(Path), - [begin code:purge(M), code:delete(M) end || M <- Modules], - %% unstick rebar dir - ok = code:unstick_dir(RebarBin) + case lists:member(soft_purge, Opts) of + true -> + [begin code:soft_purge(M), code:delete(M) end || M <- Modules]; + false -> + [begin code:purge(M), code:delete(M) end || M <- Modules] + end end end, Paths). -- cgit v1.1 From d8be03ab6a75b2f585487118ae38085c2f758d3c Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Sun, 1 Nov 2015 14:10:00 -0800 Subject: fixes for cover under new compiler/eunit/ct * only try to cover compile directories that actually exist * recover from failures where source files don't contain required attributes for cover compilation and print warning --- src/rebar_prv_cover.erl | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl index 0b9b9bb..15a067e 100644 --- a/src/rebar_prv_cover.erl +++ b/src/rebar_prv_cover.erl @@ -279,21 +279,26 @@ cover_compile(State, apps) -> Apps = filter_checkouts(rebar_state:project_apps(State)), AppDirs = app_dirs(Apps), ExtraDirs = extra_src_dirs(State, Apps), - cover_compile(State, AppDirs ++ ExtraDirs); + cover_compile(State, lists:filter(fun(D) -> ec_file:is_dir(D) end, AppDirs ++ ExtraDirs)); cover_compile(State, Dirs) -> %% start the cover server if necessary {ok, CoverPid} = start_cover(), %% redirect cover output true = redirect_cover_output(State, CoverPid), - CompileResult = compile(Dirs, []), - %% print any warnings about modules that failed to cover compile - lists:foreach(fun print_cover_warnings/1, lists:flatten(CompileResult)). - -compile([], Acc) -> lists:reverse(Acc); -compile([Dir|Rest], Acc) -> - ?INFO("covering ~p", [Dir]), - Result = cover:compile_beam_directory(Dir), - compile(Rest, [Result|Acc]). + lists:foreach(fun(Dir) -> + ?DEBUG("cover compiling ~p", [Dir]), + case catch(cover:compile_beam_directory(Dir)) of + {error, eacces} -> + ?WARN("Directory ~p not readable, modules will not be included in coverage", [Dir]); + {error, enoent} -> + ?WARN("Directory ~p not found", [Dir]); + {'EXIT', {Reason, _}} -> + ?WARN("Cover compilation for directory ~p failed: ~p", [Dir, Reason]); + Results -> + %% print any warnings about modules that failed to cover compile + lists:foreach(fun print_cover_warnings/1, lists:flatten(Results)) + end + end, Dirs). app_dirs(Apps) -> lists:foldl(fun app_ebin_dirs/2, [], Apps). @@ -302,7 +307,7 @@ app_ebin_dirs(App, Acc) -> AppDir = rebar_app_info:ebin_dir(App), ExtraDirs = rebar_dir:extra_src_dirs(rebar_app_info:opts(App), []), OutDir = rebar_app_info:out_dir(App), - [filename:join([OutDir, D]) || D <- [AppDir|ExtraDirs]] ++ Acc. + [AppDir] ++ [filename:join([OutDir, D]) || D <- ExtraDirs] ++ Acc. extra_src_dirs(State, Apps) -> BaseDir = rebar_state:dir(State), @@ -339,9 +344,8 @@ redirect_cover_output(State, CoverPid) -> group_leader(F, CoverPid). print_cover_warnings({ok, _}) -> ok; -print_cover_warnings({error, File}) -> - ?WARN("Cover compilation of ~p failed, module is not included in cover data.", - [File]). +print_cover_warnings({error, Error}) -> + ?WARN("Cover compilation failed: ~p", [Error]). write_coverdata(State, Task) -> DataDir = cover_dir(State), -- cgit v1.1 From e5aa04f27800ee83a5d5e76fd95c9f31e6aff090 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Sun, 1 Nov 2015 19:00:09 -0600 Subject: fix #903: use app discover of entire plugins dir when listing --- src/rebar_prv_plugins.erl | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_plugins.erl b/src/rebar_prv_plugins.erl index 20bc1ea..7e6b88e 100644 --- a/src/rebar_prv_plugins.erl +++ b/src/rebar_prv_plugins.erl @@ -34,34 +34,37 @@ do(State) -> GlobalConfigFile = rebar_dir:global_config(), GlobalConfig = rebar_state:new(rebar_config:consult_file(GlobalConfigFile)), GlobalPlugins = rebar_state:get(GlobalConfig, plugins, []), - GlobalPluginsDir = filename:join(rebar_dir:global_cache_dir(rebar_state:opts(State)), "plugins"), - display_plugins("Global plugins", GlobalPluginsDir, GlobalPlugins), + GlobalPluginsDir = filename:join([rebar_dir:global_cache_dir(rebar_state:opts(State)), "plugins", "*"]), + GlobalApps = rebar_app_discover:find_apps([GlobalPluginsDir], all), + display_plugins("Global plugins", GlobalApps, GlobalPlugins), Plugins = rebar_state:get(State, plugins, []), - PluginsDir =rebar_dir:plugins_dir(State), - display_plugins("Local plugins", PluginsDir, Plugins), + PluginsDir = filename:join(rebar_dir:plugins_dir(State), "*"), + CheckoutsDir = filename:join(rebar_dir:checkouts_dir(State), "*"), + Apps = rebar_app_discover:find_apps([CheckoutsDir, PluginsDir], all), + display_plugins("Local plugins", Apps, Plugins), {ok, State}. -spec format_error(any()) -> iolist(). format_error(Reason) -> io_lib:format("~p", [Reason]). -display_plugins(_Header, _Dir, []) -> +display_plugins(_Header, _Apps, []) -> ok; -display_plugins(Header, Dir, Plugins) -> +display_plugins(Header, Apps, Plugins) -> ?CONSOLE("--- ~s ---", [Header]), - display_plugins(Dir, Plugins), + display_plugins(Apps, Plugins), ?CONSOLE("", []). -display_plugins(Dir, Plugins) -> +display_plugins(Apps, Plugins) -> lists:foreach(fun(Plugin) -> - Name = if is_atom(Plugin) -> Plugin; - is_tuple(Plugin) -> element(1, Plugin) + Name = if is_atom(Plugin) -> ec_cnv:to_binary(Plugin); + is_tuple(Plugin) -> ec_cnv:to_binary(element(1, Plugin)) end, - case rebar_app_info:discover(filename:join(Dir, Name)) of + case rebar_app_utils:find(Name, Apps) of {ok, _App} -> ?CONSOLE("~s", [Name]); - not_found -> + error -> ?DEBUG("Unable to find plugin ~s", [Name]) end end, Plugins). -- cgit v1.1 From 19a3a96893ec8978233e2a815a61e26bfa86c4e0 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Thu, 15 Oct 2015 19:58:42 -0700 Subject: refactor `rebar_prv_common_test` * remove partial support for ct `test_spec` until it can be done properly and warn if `test_spec` is present in test opts * use new compiler functionality to reduce complexity of provider * reduce command line options available to those that can be supported properly --- src/rebar_prv_common_test.erl | 684 +++++++++++++++++++----------------------- 1 file changed, 311 insertions(+), 373 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 32d4433..910a1ca 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -2,19 +2,21 @@ %% ex: ts=4 sw=4 et -module(rebar_prv_common_test). + -behaviour(provider). -export([init/1, do/1, format_error/1]). %% exported for test purposes, consider private --export([setup_ct/1]). +-export([compile/2, prepare_tests/1, translate_paths/2]). -include("rebar.hrl"). -include_lib("providers/include/providers.hrl"). -define(PROVIDER, ct). --define(DEPS, [compile]). +%% we need to modify app_info state before compile +-define(DEPS, [lock]). %% =================================================================== %% Public API @@ -31,12 +33,19 @@ init(State) -> {desc, "Run Common Tests."}, {opts, ct_opts(State)}, {profiles, [test]}]), - State1 = rebar_state:add_provider(State, Provider), - State2 = rebar_state:add_to_profile(State1, test, test_state(State1)), - {ok, State2}. + {ok, rebar_state:add_provider(State, Provider)}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> + Tests = prepare_tests(State), + case compile(State, Tests) of + %% successfully compiled apps + {ok, S} -> do(S, Tests); + %% this should look like a compiler error, not a ct error + Error -> Error + end. + +do(State, Tests) -> ?INFO("Running Common Test suites...", []), rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]), @@ -45,61 +54,308 @@ do(State) -> Cwd = rebar_dir:get_cwd(), rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), - try run_test(State) of - {ok, State1} = Result -> - %% Run ct provider posthooks - rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1), - rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), - Result; - ?PRV_ERROR(_) = Error -> + case Tests of + {ok, T} -> + case run_tests(State, T) of + ok -> + %% Run ct provider posthooks + rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State), + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), + {ok, State}; + Error -> + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), + Error + end; + Error -> rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), Error - catch - throw:{error, Reason} -> - rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), - ?PRV_ERROR(Reason) + end. + +run_tests(State, Opts) -> + T = translate_paths(State, Opts), + Opts1 = setup_logdir(State, T), + %% strip test spec if present + Opts2 = strip_test_spec(Opts1), + ?DEBUG("ct_opts ~p", [Opts2]), + {RawOpts, _} = rebar_state:command_parsed_args(State), + ok = maybe_write_coverdata(State), + case proplists:get_value(verbose, RawOpts, false) of + true -> run_test_verbose(Opts2); + false -> run_test_quiet(Opts2) end. -spec format_error(any()) -> iolist(). -format_error({multiple_dirs_and_suites, Opts}) -> - io_lib:format("Multiple dirs declared alongside suite in opts: ~p", [Opts]); -format_error({bad_dir_or_suite, Opts}) -> - io_lib:format("Bad value for dir or suite in opts: ~p", [Opts]); +format_error({error, Reason}) -> + io_lib:format("Error running tests:~n ~p", [Reason]); +format_error({error_running_tests, Reason}) -> + format_error({error, Reason}); format_error({failures_running_tests, {Failed, AutoSkipped}}) -> io_lib:format("Failures occured running tests: ~b", [Failed+AutoSkipped]); -format_error({error_running_tests, Reason}) -> - io_lib:format("Error running tests: ~p", [Reason]); -format_error(suite_at_project_root) -> - io_lib:format("Test suites can't be located in project root", []); -format_error({error, Reason}) -> - io_lib:format("Unknown error: ~p", [Reason]). +format_error({multiple_errors, Errors}) -> + io_lib:format(lists:concat(["Error running tests:"] ++ + lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []). %% =================================================================== %% Internal functions %% =================================================================== -run_test(State) -> - case setup_ct(State) of - {error, {no_tests_specified, Opts}} -> - ?WARN("No tests specified in opts: ~p", [Opts]), - {ok, State}; - Opts -> - Opts1 = setup_logdir(State, Opts), - ?DEBUG("common test opts: ~p", [Opts1]), - run_test(State, Opts1) - end. +prepare_tests(State) -> + %% command line test options + CmdOpts = opts(State), + %% rebar.config test options + CfgOpts = rebar_state:get(State, ct_tests, []), + ProjectApps = rebar_state:project_apps(State), + %% 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 + select_tests(State, ProjectApps, CmdOpts, CfgOpts). -run_test(State, Opts) -> +opts(State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), - ok = rebar_prv_cover:maybe_cover_compile(State, apps), - Result = case proplists:get_value(verbose, RawOpts, false) of - true -> run_test_verbose(Opts); - false -> run_test_quiet(Opts) + %% filter out opts common_test doesn't know about and convert + %% to ct acceptable forms + transform_opts(RawOpts, []). + +transform_opts([], Acc) -> lists:reverse(Acc); +transform_opts([{dir, Dirs}|Rest], Acc) -> + transform_opts(Rest, [{dir, split_string(Dirs)}|Acc]); +transform_opts([{suite, Suites}|Rest], Acc) -> + transform_opts(Rest, [{suite, split_string(Suites)}|Acc]); +transform_opts([{group, Groups}|Rest], Acc) -> + transform_opts(Rest, [{group, split_string(Groups)}|Acc]); +transform_opts([{testcase, Cases}|Rest], Acc) -> + transform_opts(Rest, [{testcase, split_string(Cases)}|Acc]); +transform_opts([{config, Configs}|Rest], Acc) -> + transform_opts(Rest, [{config, split_string(Configs)}|Acc]); +transform_opts([{include, Includes}|Rest], Acc) -> + transform_opts(Rest, [{include, split_string(Includes)}|Acc]); +transform_opts([{force_stop, "true"}|Rest], Acc) -> + transform_opts(Rest, [{force_stop, true}|Acc]); +transform_opts([{force_stop, "false"}|Rest], Acc) -> + transform_opts(Rest, [{force_stop, false}|Acc]); +transform_opts([{force_stop, "skip_rest"}|Rest], Acc) -> + transform_opts(Rest, [{force_stop, skip_rest}|Acc]); +%% drop cover from opts, ct doesn't care about it +transform_opts([{cover, _}|Rest], Acc) -> + transform_opts(Rest, Acc); +%% drop verbose from opts, ct doesn't care about it +transform_opts([{verbose, _}|Rest], Acc) -> + transform_opts(Rest, Acc); +%% getopt should handle anything else +transform_opts([Opt|Rest], Acc) -> + transform_opts(Rest, [Opt|Acc]). + +split_string(String) -> + string:tokens(String, [$,]). + +select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> + Merged = lists:ukeymerge(1, + lists:ukeysort(1, CmdOpts), + lists:ukeysort(1, CfgOpts)), + %% make sure `dir` and/or `suite` from command line go in as + %% a pair overriding both `dir` and `suite` from config if + %% they exist + Opts = case {proplists:get_value(suite, CmdOpts), proplists:get_value(dir, CmdOpts)} of + {undefined, undefined} -> Merged; + {_Suite, undefined} -> lists:keydelete(dir, 1, Merged); + {undefined, _Dir} -> lists:keydelete(suite, 1, Merged); + {_Suite, _Dir} -> Merged end, - ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER), - case Result of - ok -> {ok, State}; - Error -> Error + discover_tests(State, ProjectApps, Opts). + +discover_tests(State, ProjectApps, Opts) -> + case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of + %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test` + %% as suites + {undefined, undefined} -> {ok, [default_tests(State, ProjectApps)|Opts]}; + {_, _} -> {ok, Opts} + end. + +default_tests(State, ProjectApps) -> + BareTest = filename:join([rebar_state:dir(State), "test"]), + F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end, + AppTests = application_dirs(ProjectApps, []), + case filelib:is_dir(BareTest) andalso not lists:any(F, ProjectApps) of + %% `test` dir at root of project is already scheduled to be + %% included or `test` does not exist + false -> {dir, AppTests}; + %% need to add `test` dir at root to dirs to be included + true -> {dir, AppTests ++ [BareTest]} + end. + +application_dirs([], []) -> []; +application_dirs([], Acc) -> lists:reverse(Acc); +application_dirs([App|Rest], Acc) -> + TestDir = filename:join([rebar_app_info:dir(App), "test"]), + case filelib:is_dir(TestDir) of + true -> application_dirs(Rest, [TestDir|Acc]); + false -> application_dirs(Rest, Acc) + end. + +compile(State, {ok, Tests}) -> + %% inject `ct_first_files` and `ct_compile_opts` into the applications + %% to be compiled + case inject_ct_state(State, Tests) of + {ok, NewState} -> do_compile(NewState); + Error -> Error + end; +%% maybe compile even in the face of errors? +compile(_State, Error) -> Error. + +do_compile(State) -> + case rebar_prv_compile:do(State) of + %% successfully compiled apps + {ok, S} -> + ok = maybe_cover_compile(S), + {ok, S}; + %% this should look like a compiler error, not an eunit error + Error -> Error + end. + +inject_ct_state(State, Tests) -> + Apps = rebar_state:project_apps(State), + ModdedApps = lists:map(fun(App) -> + NewOpts = inject(rebar_app_info:opts(App), State), + rebar_app_info:opts(App, NewOpts) + end, Apps), + NewOpts = inject(rebar_state:opts(State), State), + NewState = rebar_state:opts(State, NewOpts), + test_dirs(NewState, ModdedApps, Tests). + +inject(Opts, State) -> + %% append `ct_compile_opts` to app defined `erl_opts` + ErlOpts = rebar_opts:get(Opts, erl_opts, []), + CTOpts = rebar_state:get(State, ct_compile_opts, []), + NewErlOpts = CTOpts ++ ErlOpts, + %% append `ct_first_files` to app defined `erl_first_files` + FirstFiles = rebar_opts:get(Opts, erl_first_files, []), + CTFirstFiles = rebar_state:get(State, ct_first_files, []), + NewFirstFiles = CTFirstFiles ++ FirstFiles, + %% insert the new keys into the opts + lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end, + Opts, + [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]). + +test_dirs(State, Apps, Opts) -> + case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of + {Suites, undefined} -> set_compile_dirs(State, Apps, {suite, Suites}); + {undefined, Dirs} -> set_compile_dirs(State, Apps, {dir, Dirs}); + {Suites, [Dir]} -> set_compile_dirs(State, Apps, join(Suites, Dir)); + {_Suites, _Dirs} -> {error, "Only a single directory may be specified when specifying suites"} + end. + +join(Suites, Dir) -> + {suite, lists:map(fun(S) -> filename:join([Dir, S]) end, Suites)}. + +set_compile_dirs(State, Apps, {dir, Dir}) when is_integer(hd(Dir)) -> + %% single directory + %% insert `Dir` into an app if relative, or the base state if not + %% app relative but relative to the root or not at all if outside + %% project scope + {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir), + {ok, rebar_state:project_apps(NewState, NewApps)}; +set_compile_dirs(State, Apps, {dir, Dirs}) -> + %% multiple directories + F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end, + {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs), + {ok, rebar_state:project_apps(NewState, NewApps)}; +set_compile_dirs(State, Apps, {suite, Suites}) -> + %% suites with dir component + Dirs = find_suite_dirs(Suites), + F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end, + {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs), + {ok, rebar_state:project_apps(NewState, NewApps)}. + +find_suite_dirs(Suites) -> + AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites), + %% eliminate duplicates + lists:usort(AllDirs). + +maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) -> + case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of + {ok, Path} -> + Opts = inject_test_dir(rebar_app_info:opts(App), Path), + {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest}; + {error, badparent} -> + maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir) + end; +maybe_inject_test_dir(State, AppAcc, [], Dir) -> + case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of + {ok, []} -> + ?WARN("Can't have suites in root of project dir, dropping from tests", []), + {State, AppAcc}; + {ok, Path} -> + Opts = inject_test_dir(rebar_state:opts(State), Path), + {rebar_state:opts(State, Opts), AppAcc}; + {error, badparent} -> + {State, AppAcc} + end. + +inject_test_dir(Opts, Dir) -> + %% append specified test targets to app defined `extra_src_dirs` + ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []), + rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]). + +translate_paths(State, Opts) -> + case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of + {_Suites, undefined} -> translate_suites(State, Opts, []); + {undefined, _Dirs} -> translate_dirs(State, Opts, []); + %% both dirs and suites are defined, only translate dir paths + _ -> translate_dirs(State, Opts, []) + end. + +translate_dirs(_State, [], Acc) -> lists:reverse(Acc); +translate_dirs(State, [{dir, Dir}|Rest], Acc) when is_integer(hd(Dir)) -> + %% single dir + Apps = rebar_state:project_apps(State), + translate_dirs(State, Rest, [{dir, translate(State, Apps, Dir)}|Acc]); +translate_dirs(State, [{dir, Dirs}|Rest], Acc) -> + %% multiple dirs + Apps = rebar_state:project_apps(State), + NewDirs = {dir, lists:map(fun(Dir) -> translate(State, Apps, Dir) end, Dirs)}, + translate_dirs(State, Rest, [NewDirs|Acc]); +translate_dirs(State, [Test|Rest], Acc) -> + translate_dirs(State, Rest, [Test|Acc]). + +translate_suites(_State, [], Acc) -> lists:reverse(Acc); +translate_suites(State, [{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) -> + %% single suite + Apps = rebar_state:project_apps(State), + translate_suites(State, Rest, [{suite, translate(State, Apps, Suite)}|Acc]); +translate_suites(State, [{suite, Suites}|Rest], Acc) -> + %% multiple suites + Apps = rebar_state:project_apps(State), + NewSuites = {suite, lists:map(fun(Suite) -> translate(State, Apps, Suite) end, Suites)}, + translate_suites(State, Rest, [NewSuites|Acc]); +translate_suites(State, [Test|Rest], Acc) -> + translate_suites(State, Rest, [Test|Acc]). + +translate(State, [App|Rest], Path) -> + case rebar_file_utils:path_from_ancestor(Path, rebar_app_info:dir(App)) of + {ok, P} -> filename:join([rebar_app_info:out_dir(App), P]); + {error, badparent} -> translate(State, Rest, Path) + end; +translate(State, [], Path) -> + case rebar_file_utils:path_from_ancestor(Path, rebar_state:dir(State)) of + {ok, P} -> filename:join([rebar_dir:base_dir(State), "extras", P]); + %% not relative, leave as is + {error, badparent} -> Path + end. + +setup_logdir(State, Opts) -> + Logdir = case proplists:get_value(logdir, Opts) of + undefined -> filename:join([rebar_dir:base_dir(State), "logs"]); + Dir -> Dir + end, + filelib:ensure_dir(filename:join([Logdir, "dummy.beam"])), + [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)]. + +strip_test_spec(Opts) -> + case proplists:get_value(test_spec, Opts) of + undefined -> Opts; + _ -> + ?WARN("Test specs not supported", []), + lists:keydelete(test_spec, 1, Opts) end. run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)). @@ -171,272 +427,38 @@ format_skipped({0, 0}) -> format_skipped({User, Auto}) -> io_lib:format("Skipped ~p (~p, ~p) tests. ", [User+Auto, User, Auto]). -test_state(State) -> - TestOpts = case rebar_state:get(State, ct_compile_opts, []) of - [] -> []; - Opts -> [{erl_opts, Opts}] - end, - [first_files(State)|TestOpts]. - -first_files(State) -> - CTFirst = rebar_state:get(State, ct_first_files, []), - {erl_first_files, CTFirst}. - -setup_ct(State) -> - Opts = resolve_ct_opts(State), - Opts1 = discover_tests(State, Opts), - copy_and_compile_tests(State, Opts1). - -resolve_ct_opts(State) -> - {RawOpts, _} = rebar_state:command_parsed_args(State), - CmdOpts = transform_opts(RawOpts), - CfgOpts = rebar_state:get(State, ct_opts, []), - Merged = lists:ukeymerge(1, - lists:ukeysort(1, CmdOpts), - lists:ukeysort(1, CfgOpts)), - %% make sure `dir` and/or `suite` from command line go in as - %% a pair overriding both `dir` and `suite` from config if - %% they exist - case {proplists:get_value(suite, CmdOpts), proplists:get_value(dir, CmdOpts)} of - {undefined, undefined} -> Merged; - {_Suite, undefined} -> lists:keydelete(dir, 1, Merged); - {undefined, _Dir} -> lists:keydelete(suite, 1, Merged); - {_Suite, _Dir} -> Merged - end. - -discover_tests(State, Opts) -> - case proplists:get_value(spec, Opts) of - undefined -> discover_dirs_and_suites(State, Opts); - TestSpec -> discover_testspec(TestSpec, Opts) - end. - -discover_dirs_and_suites(State, Opts) -> - case {proplists:get_value(dir, Opts), proplists:get_value(suite, Opts)} of - %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test` - %% as suites - {undefined, undefined} -> test_dirs(State, Opts); - %% no dirs defined - {undefined, _} -> Opts; - %% no suites defined - {_, undefined} -> Opts; - %% a single dir defined, this is ok - {Dirs, Suites} when is_integer(hd(Dirs)), is_list(Suites) -> Opts; - %% still a single dir defined, adjust to make acceptable to ct - {[Dir], Suites} when is_integer(hd(Dir)), is_list(Suites) -> - [{dir, Dir}|lists:keydelete(dir, 1, Opts)]; - %% multiple dirs and suites, error now to simplify later steps - {_, _} -> throw({error, {multiple_dirs_and_suites, Opts}}) - end. - -discover_testspec(_TestSpec, Opts) -> - lists:keydelete(auto_compile, 1, Opts). - -copy_and_compile_tests(State, Opts) -> - %% possibly enable cover +maybe_cover_compile(State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), State1 = case proplists:get_value(cover, RawOpts, false) of true -> rebar_state:set(State, cover_enabled, true); false -> State end, - copy_and_compile_test_suites(State1, Opts). - -copy_and_compile_test_suites(State, Opts) -> - case proplists:get_value(suite, Opts) of - %% no suites, try dirs - undefined -> copy_and_compile_test_dirs(State, Opts); - Suites -> - Dir = proplists:get_value(dir, Opts, undefined), - AllSuites = join(Dir, Suites), - Dirs = find_suite_dirs(AllSuites), - lists:foreach(fun(S) -> - NewPath = copy(State, S), - compile_dir(State, NewPath) - end, Dirs), - NewSuites = lists:map(fun(S) -> retarget_path(State, S) end, AllSuites), - [{suite, NewSuites}|lists:keydelete(suite, 1, Opts)] - end. - -copy_and_compile_test_dirs(State, Opts) -> - case proplists:get_value(dir, Opts) of - undefined -> {error, {no_tests_specified, Opts}}; - %% dir is a single directory - Dir when is_list(Dir), is_integer(hd(Dir)) -> - NewPath = copy(State, Dir), - [{dir, compile_dir(State, NewPath)}|lists:keydelete(dir, 1, Opts)]; - %% dir is a list of directories - Dirs when is_list(Dirs) -> - NewDirs = lists:map(fun(Dir) -> - NewPath = copy(State, Dir), - compile_dir(State, NewPath) - end, Dirs), - [{dir, NewDirs}|lists:keydelete(dir, 1, Opts)] - end. - -join(undefined, Suites) -> Suites; -join(Dir, Suites) when is_list(Dir), is_integer(hd(Dir)) -> - lists:map(fun(S) -> filename:join([Dir, S]) end, Suites); -%% multiple dirs or a bad dir argument, try to continue anyways -join(_, Suites) -> Suites. - -find_suite_dirs(Suites) -> - AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites), - %% eliminate duplicates - lists:usort(AllDirs). - -copy(State, Dir) -> - From = reduce_path(Dir), - retarget_path(State, From). - -compile_dir(State, Dir) -> - NewState = replace_src_dirs(State, [filename:absname(Dir)]), - ok = rebar_erlc_compiler:compile(rebar_state:opts(NewState), rebar_dir:base_dir(State), Dir), - ok = maybe_cover_compile(State, Dir), - Dir. - -retarget_path(State, Path) -> - ProjectApps = rebar_state:project_apps(State), - retarget_path(State, Path, ProjectApps). - -%% not relative to any apps in project, check to see it's relative to -%% project root -retarget_path(State, Path, []) -> - case relative_path(reduce_path(Path), rebar_state:dir(State)) of - {ok, NewPath} -> filename:join([rebar_dir:base_dir(State), NewPath]); - %% not relative to project root, don't modify - {error, not_relative} -> Path - end; -%% relative to current app, retarget to the same dir relative to -%% the app's out_dir -retarget_path(State, Path, [App|Rest]) -> - case relative_path(reduce_path(Path), rebar_app_info:dir(App)) of - {ok, NewPath} -> filename:join([rebar_app_info:out_dir(App), NewPath]); - {error, not_relative} -> retarget_path(State, Path, Rest) - end. - -relative_path(Target, To) -> - relative_path1(filename:split(filename:absname(Target)), - filename:split(filename:absname(To))). - -relative_path1([Part|Target], [Part|To]) -> relative_path1(Target, To); -relative_path1([], []) -> {ok, ""}; -relative_path1(Target, []) -> {ok, filename:join(Target)}; -relative_path1(_, _) -> {error, not_relative}. - -reduce_path(Dir) -> reduce_path([], filename:split(filename:absname(Dir))). - -reduce_path([], []) -> filename:nativename("/"); -reduce_path(Acc, []) -> filename:join(lists:reverse(Acc)); -reduce_path(Acc, ["."|Rest]) -> reduce_path(Acc, Rest); -reduce_path([_|Acc], [".."|Rest]) -> reduce_path(Acc, Rest); -reduce_path([], [".."|Rest]) -> reduce_path([], Rest); -reduce_path(Acc, [Component|Rest]) -> reduce_path([Component|Acc], Rest). - -replace_src_dirs(State, Dirs) -> - %% replace any `src_dirs` with the test dirs - ErlOpts = rebar_state:get(State, erl_opts, []), - StrippedErlOpts = filter_src_dirs(ErlOpts), - State1 = rebar_state:set(State, erl_opts, StrippedErlOpts), - State2 = rebar_state:set(State1, src_dirs, []), - rebar_state:set(State2, extra_src_dirs, Dirs). + rebar_prv_cover:maybe_cover_compile(State1). -filter_src_dirs(ErlOpts) -> - lists:filter(fun({src_dirs, _}) -> false; ({extra_src_dirs, _}) -> false; (_) -> true end, ErlOpts). - -test_dirs(State, Opts) -> - BareTest = filename:join([rebar_state:dir(State), "test"]), - F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end, - TestApps = project_apps(State), - case filelib:is_dir(BareTest) andalso not lists:any(F, TestApps) of - %% `test` dir at root of project is already scheduled to be - %% included or `test` does not exist - false -> application_dirs(TestApps, Opts, []); - %% need to add `test` dir at root to dirs to be included - true -> application_dirs(TestApps, Opts, [BareTest]) - 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]) - end. - -application_dirs([], Opts, []) -> Opts; -application_dirs([], Opts, [Acc]) -> [{dir, Acc}|Opts]; -application_dirs([], Opts, Acc) -> [{dir, lists:reverse(Acc)}|Opts]; -application_dirs([App|Rest], Opts, Acc) -> - TestDir = filename:join([rebar_app_info:dir(App), "test"]), - case filelib:is_dir(TestDir) of - true -> application_dirs(Rest, Opts, [TestDir|Acc]); - false -> application_dirs(Rest, Opts, Acc) - end. - -setup_logdir(State, Opts) -> - Logdir = case proplists:get_value(logdir, Opts) of - undefined -> filename:join([rebar_dir:base_dir(State), "logs"]); - Dir -> Dir - end, - ensure_dir([Logdir]), - [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)]. - -ensure_dir([]) -> ok; -ensure_dir([Dir|Rest]) -> - case ec_file:is_dir(Dir) of - true -> - ok; - false -> - ec_file:mkdir_path(Dir) - end, - ensure_dir(Rest). - -maybe_cover_compile(State, Dir) -> - {Opts, _} = rebar_state:command_parsed_args(State), - State1 = case proplists:get_value(cover, Opts, false) of +maybe_write_coverdata(State) -> + {RawOpts, _} = rebar_state:command_parsed_args(State), + State1 = case proplists:get_value(cover, RawOpts, false) of true -> rebar_state:set(State, cover_enabled, true); false -> State end, - rebar_prv_cover:maybe_cover_compile(State1, [Dir]). + rebar_prv_cover:maybe_write_coverdata(State1, ?PROVIDER). ct_opts(_State) -> [{dir, undefined, "dir", string, help(dir)}, %% comma-seperated list {suite, undefined, "suite", string, help(suite)}, %% comma-seperated list {group, undefined, "group", string, help(group)}, %% comma-seperated list {testcase, undefined, "case", string, help(testcase)}, %% comma-seperated list - {spec, undefined, "spec", string, help(spec)}, %% comma-seperated list - {join_specs, undefined, "join_specs", boolean, help(join_specs)}, %% Boolean {label, undefined, "label", string, help(label)}, %% String {config, undefined, "config", string, help(config)}, %% comma-seperated list - {userconfig, undefined, "userconfig", string, help(userconfig)}, %% [{CallbackMod, CfgStrings}] | {CallbackMod, CfgStrings} {allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool {logdir, undefined, "logdir", string, help(logdir)}, %% dir - {logopts, undefined, "logopts", string, help(logopts)}, %% enum, no_nl | no_src - {verbosity, undefined, "verbosity", string, help(verbosity)}, %% Integer OR [{Category, VLevel}] - {silent_connections, undefined, "silent_connections", string, - help(silent_connections)}, % all OR %% comma-seperated list - {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% file + {verbosity, undefined, "verbosity", integer, help(verbosity)}, %% Integer {cover, $c, "cover", {boolean, false}, help(cover)}, - {cover_spec, undefined, "cover_spec", string, help(cover_spec)}, %% file - {cover_stop, undefined, "cover_stop", boolean, help(cover_stop)}, %% Boolean - {event_handler, undefined, "event_handler", string, help(event_handler)}, %% EH | [EH] WHERE EH atom() | {atom(), InitArgs} | {[atom()], InitArgs} {include, undefined, "include", string, help(include)}, % comma-seperated list - {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true}, - help(abort_if_missing_suites)}, %% boolean - {multiply_timetraps, undefined, "multiply_timetraps", integer, - help(multiply_timetraps)}, %% integer - {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)}, %% Boolean - {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)}, %% enum: auto_per_run | auto_per_tc | manual_per_tc {repeat, undefined, "repeat", integer, help(repeat)}, %% integer {duration, undefined, "duration", string, help(duration)}, % format: HHMMSS {until, undefined, "until", string, help(until)}, %% format: YYMoMoDD[HHMMSS] - {force_stop, undefined, "force_stop", string, help(force_stop)}, % enum: skip_rest, bool {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Booloean - {ct_hooks, undefined, "ct_hooks", string, help(ct_hooks)}, %% List: [CTHModule | {CTHModule, CTHInitArgs}] where CTHModule is atom CthInitArgs is term - {auto_compile, undefined, "auto_compile", {boolean, false}, help(auto_compile)}, {verbose, $v, "verbose", boolean, help(verbose)} ]. @@ -448,36 +470,26 @@ help(group) -> "List of test groups to run"; help(testcase) -> "List of test cases to run"; -help(spec) -> - "List of test specs to run"; help(label) -> "Test label"; help(config) -> "List of config files"; +help(allow_user_terms) -> + "Allow user defined config values in config files"; help(logdir) -> "Log folder"; help(verbosity) -> "Verbosity"; -help(stylesheet) -> - "Stylesheet to use for test results"; help(cover) -> "Generate cover data"; -help(cover_spec) -> - "Cover file to use"; -help(event_handler) -> - "Event handlers to attach to the runner"; help(include) -> - "Include folder"; -help(abort_if_missing_suites) -> - "Abort if suites are missing"; + "Include folders"; help(repeat) -> "How often to repeat tests"; help(duration) -> "Max runtime (format: HHMMSS)"; help(until) -> "Run until (format: HHMMSS)"; -help(force_stop) -> - "Force stop after time"; help(basic_html) -> "Show basic HTML"; help(verbose) -> @@ -485,77 +497,3 @@ help(verbose) -> help(_) -> "". -transform_opts(Opts) -> - transform_opts(Opts, []). - -transform_opts([], Acc) -> Acc; -%% drop `cover` and `verbose` so they're not passed as an option to common_test -transform_opts([{cover, _}|Rest], Acc) -> - transform_opts(Rest, Acc); -transform_opts([{cover_spec, CoverSpec}|Rest], Acc) -> - transform_opts(Rest, [{cover, CoverSpec}|Acc]); -transform_opts([{verbose, _}|Rest], Acc) -> - transform_opts(Rest, Acc); -transform_opts([{ct_hooks, CtHooks}|Rest], Acc) -> - transform_opts(Rest, [{ct_hooks, parse_term(CtHooks)}|Acc]); -transform_opts([{force_stop, "skip_rest"}|Rest], Acc) -> - transform_opts(Rest, [{force_stop, skip_rest}|Acc]); -transform_opts([{force_stop, _}|Rest], Acc) -> - transform_opts(Rest, [{force_stop, true}|Acc]); -transform_opts([{repeat, Repeat}|Rest], Acc) -> - transform_opts(Rest, [{repeat, - ec_cnv:to_integer(Repeat)}|Acc]); -transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) -> - transform_opts(Rest, [{create_priv_dir, - to_atoms(CreatePrivDir)}|Acc]); -transform_opts([{multiply_timetraps, MultiplyTimetraps}|Rest], Acc) -> - transform_opts(Rest, [{multiply_timetraps, - ec_cnv:to_integer(MultiplyTimetraps)}|Acc]); -transform_opts([{event_handler, EventHandler}|Rest], Acc) -> - transform_opts(Rest, [{event_handler, parse_term(EventHandler)}|Acc]); -transform_opts([{silent_connections, "all"}|Rest], Acc) -> - transform_opts(Rest, [{silent_connections, all}|Acc]); -transform_opts([{silent_connections, SilentConnections}|Rest], Acc) -> - transform_opts(Rest, [{silent_connections, - to_atoms(split_string(SilentConnections))}|Acc]); -transform_opts([{verbosity, Verbosity}|Rest], Acc) -> - transform_opts(Rest, [{verbosity, parse_term(Verbosity)}|Acc]); -transform_opts([{logopts, LogOpts}|Rest], Acc) -> - transform_opts(Rest, [{logopts, to_atoms(split_string(LogOpts))}|Acc]); -transform_opts([{userconfig, UserConfig}|Rest], Acc) -> - transform_opts(Rest, [{userconfig, parse_term(UserConfig)}|Acc]); -transform_opts([{testcase, Testcase}|Rest], Acc) -> - transform_opts(Rest, [{testcase, to_atoms(split_string(Testcase))}|Acc]); -transform_opts([{group, Group}|Rest], Acc) -> % @TODO handle "" - % Input is a list or an atom. It can also be a nested list. - transform_opts(Rest, [{group, parse_term(Group)}|Acc]); -transform_opts([{suite, Suite}|Rest], Acc) -> - transform_opts(Rest, [{suite, split_string(Suite)}|Acc]); -transform_opts([{Key, Val}|Rest], Acc) when is_list(Val) -> - % Default to splitting a string on comma, that works fine for both flat - % lists of which there are many and single-items. - Val1 = case split_string(Val) of - [Val2] -> - Val2; - Val2 -> - Val2 - end, - transform_opts(Rest, [{Key, Val1}|Acc]); -transform_opts([{Key, Val}|Rest], Acc) -> - transform_opts(Rest, [{Key, Val}|Acc]). - -to_atoms(List) -> - lists:map(fun(X) -> list_to_atom(X) end, List). - -split_string(String) -> - string:tokens(String, ","). - -parse_term(String) -> - String1 = "[" ++ String ++ "].", - {ok, Tokens, _} = erl_scan:string(String1), - case erl_parse:parse_term(Tokens) of - {ok, [Terms]} -> - Terms; - Term -> - Term - end. -- cgit v1.1 From f73307bb65bfe83685836ef5f39efcfe5467ef3b Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Fri, 30 Oct 2015 00:17:53 -0700 Subject: support atom suites in `ct_tests` --- src/rebar_prv_common_test.erl | 46 +++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 910a1ca..ede88dc 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -74,14 +74,12 @@ do(State, Tests) -> run_tests(State, Opts) -> T = translate_paths(State, Opts), Opts1 = setup_logdir(State, T), - %% strip test spec if present - Opts2 = strip_test_spec(Opts1), - ?DEBUG("ct_opts ~p", [Opts2]), + ?DEBUG("ct_opts ~p", [Opts1]), {RawOpts, _} = rebar_state:command_parsed_args(State), ok = maybe_write_coverdata(State), case proplists:get_value(verbose, RawOpts, false) of - true -> run_test_verbose(Opts2); - false -> run_test_quiet(Opts2) + true -> run_test_verbose(Opts1); + false -> run_test_quiet(Opts1) end. -spec format_error(any()) -> iolist(). @@ -101,16 +99,16 @@ format_error({multiple_errors, Errors}) -> prepare_tests(State) -> %% command line test options - CmdOpts = opts(State), + CmdOpts = cmdopts(State), %% rebar.config test options - CfgOpts = rebar_state:get(State, ct_tests, []), + CfgOpts = cfgopts(State), ProjectApps = rebar_state:project_apps(State), %% 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 select_tests(State, ProjectApps, CmdOpts, CfgOpts). -opts(State) -> +cmdopts(State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), %% filter out opts common_test doesn't know about and convert %% to ct acceptable forms @@ -148,6 +146,23 @@ transform_opts([Opt|Rest], Acc) -> split_string(String) -> string:tokens(String, [$,]). +cfgopts(State) -> + Opts = rebar_state:get(State, ct_tests, []), + rebar_utils:filtermap(fun filter_opts/1, Opts). + +filter_opts({test_spec, _}) -> + ?WARN("Test specs not supported", []), + false; +filter_opts({suite, Suite}) when is_integer(hd(Suite)) -> true; +filter_opts({suite, Suite}) when is_atom(Suite) -> + {true, {suite, atom_to_list(Suite)}}; +filter_opts({suite, Suites}) -> + {true, {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S); + (S) when is_list(S) -> S + end, + Suites)}}; +filter_opts(_) -> true. + select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> Merged = lists:ukeymerge(1, lists:ukeysort(1, CmdOpts), @@ -240,10 +255,15 @@ test_dirs(State, Apps, Opts) -> case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of {Suites, undefined} -> set_compile_dirs(State, Apps, {suite, Suites}); {undefined, Dirs} -> set_compile_dirs(State, Apps, {dir, Dirs}); - {Suites, [Dir]} -> set_compile_dirs(State, Apps, join(Suites, Dir)); + {Suites, Dir} when is_integer(hd(Dir)) -> + set_compile_dirs(State, Apps, join(Suites, Dir)); + {Suites, [Dir]} when is_integer(hd(Dir)) -> + set_compile_dirs(State, Apps, join(Suites, Dir)); {_Suites, _Dirs} -> {error, "Only a single directory may be specified when specifying suites"} end. +join(Suite, Dir) when is_integer(hd(Suite)) -> + {suite, [filename:join([Dir, Suite])]}; join(Suites, Dir) -> {suite, lists:map(fun(S) -> filename:join([Dir, S]) end, Suites)}. @@ -350,14 +370,6 @@ setup_logdir(State, Opts) -> filelib:ensure_dir(filename:join([Logdir, "dummy.beam"])), [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)]. -strip_test_spec(Opts) -> - case proplists:get_value(test_spec, Opts) of - undefined -> Opts; - _ -> - ?WARN("Test specs not supported", []), - lists:keydelete(test_spec, 1, Opts) - end. - run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)). run_test_quiet(Opts) -> -- cgit v1.1 From 94169fbdf27bbba9c56313e7b32c743ea95be047 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Sun, 1 Nov 2015 18:49:06 -0800 Subject: reenable support for most command line options and rename `ct_tests` to `ct_opts` --- src/rebar_prv_common_test.erl | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index ede88dc..7af2819 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -125,14 +125,16 @@ transform_opts([{testcase, Cases}|Rest], Acc) -> transform_opts(Rest, [{testcase, split_string(Cases)}|Acc]); transform_opts([{config, Configs}|Rest], Acc) -> transform_opts(Rest, [{config, split_string(Configs)}|Acc]); -transform_opts([{include, Includes}|Rest], Acc) -> - transform_opts(Rest, [{include, split_string(Includes)}|Acc]); +transform_opts([{logopts, LogOpts}|Rest], Acc) -> + transform_opts(Rest, [{logopts, lists:map(fun(P) -> list_to_atom(P) end, split_string(LogOpts))}|Acc]); transform_opts([{force_stop, "true"}|Rest], Acc) -> transform_opts(Rest, [{force_stop, true}|Acc]); transform_opts([{force_stop, "false"}|Rest], Acc) -> transform_opts(Rest, [{force_stop, false}|Acc]); transform_opts([{force_stop, "skip_rest"}|Rest], Acc) -> transform_opts(Rest, [{force_stop, skip_rest}|Acc]); +transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) -> + transform_opts(Rest, [{create_priv_dir, list_to_atom(CreatePrivDir)}|Acc]); %% drop cover from opts, ct doesn't care about it transform_opts([{cover, _}|Rest], Acc) -> transform_opts(Rest, Acc); @@ -147,7 +149,7 @@ split_string(String) -> string:tokens(String, [$,]). cfgopts(State) -> - Opts = rebar_state:get(State, ct_tests, []), + Opts = rebar_state:get(State, ct_opts, []), rebar_utils:filtermap(fun filter_opts/1, Opts). filter_opts({test_spec, _}) -> @@ -164,9 +166,10 @@ filter_opts({suite, Suites}) -> filter_opts(_) -> true. select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> + FixedOpts = lists:filter(fun({_, _}) -> true; (V) -> ?WARN("`~p` is not a valid option for `ct_opts`", [V]) end, CfgOpts), Merged = lists:ukeymerge(1, lists:ukeysort(1, CmdOpts), - lists:ukeysort(1, CfgOpts)), + lists:ukeysort(1, FixedOpts)), %% make sure `dir` and/or `suite` from command line go in as %% a pair overriding both `dir` and `suite` from config if %% they exist @@ -464,13 +467,21 @@ ct_opts(_State) -> {config, undefined, "config", string, help(config)}, %% comma-seperated list {allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool {logdir, undefined, "logdir", string, help(logdir)}, %% dir + {logopts, undefined, "logopts", string, help(logopts)}, %% comma seperated list {verbosity, undefined, "verbosity", integer, help(verbosity)}, %% Integer {cover, $c, "cover", {boolean, false}, help(cover)}, - {include, undefined, "include", string, help(include)}, % comma-seperated list {repeat, undefined, "repeat", integer, help(repeat)}, %% integer {duration, undefined, "duration", string, help(duration)}, % format: HHMMSS {until, undefined, "until", string, help(until)}, %% format: YYMoMoDD[HHMMSS] - {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Booloean + {force_stop, undefined, "force_stop", string, help(force_stop)}, %% String + {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Boolean + {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% String + {decrypt_key, undefined, "decrypt_key", string, help(decrypt_key)}, %% String + {decrypt_file, undefined, "decrypt_file", string, help(decrypt_file)}, %% String + {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true}, help(abort_if_missing_suites)}, %% Boolean + {multiply_timetraps, undefined, "multiply_timetraps", integer, help(multiple_timetraps)}, %% Integer + {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)}, + {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)}, {verbose, $v, "verbose", boolean, help(verbose)} ]. @@ -490,20 +501,36 @@ help(allow_user_terms) -> "Allow user defined config values in config files"; help(logdir) -> "Log folder"; +help(logopts) -> + "Options for common test logging"; help(verbosity) -> "Verbosity"; help(cover) -> "Generate cover data"; -help(include) -> - "Include folders"; help(repeat) -> "How often to repeat tests"; help(duration) -> "Max runtime (format: HHMMSS)"; help(until) -> "Run until (format: HHMMSS)"; +help(force_stop) -> + "Force stop on test timeout (true | false | skip_rest)"; help(basic_html) -> "Show basic HTML"; +help(stylesheet) -> + "CSS stylesheet to apply to html output"; +help(decrypt_key) -> + "Path to key for decrypting config"; +help(decrypt_file) -> + "Path to file containing key for decrypting config"; +help(abort_if_missing_suites) -> + "Abort if suites are missing"; +help(multiply_timetraps) -> + "Multiply timetraps"; +help(scale_timetraps) -> + "Scale timetraps"; +help(create_priv_dir) -> + "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)"; help(verbose) -> "Verbose output"; help(_) -> -- cgit v1.1 From 80f5bc61526f50e2fe3d4642faea6abff43cbd96 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Mon, 2 Nov 2015 07:06:06 -0800 Subject: add warning about `auto_compile` --- src/rebar_prv_common_test.erl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 7af2819..7a088e5 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -74,12 +74,13 @@ do(State, Tests) -> run_tests(State, Opts) -> T = translate_paths(State, Opts), Opts1 = setup_logdir(State, T), - ?DEBUG("ct_opts ~p", [Opts1]), + Opts2 = turn_off_auto_compile(Opts1), + ?DEBUG("ct_opts ~p", [Opts2]), {RawOpts, _} = rebar_state:command_parsed_args(State), ok = maybe_write_coverdata(State), case proplists:get_value(verbose, RawOpts, false) of - true -> run_test_verbose(Opts1); - false -> run_test_quiet(Opts1) + true -> run_test_verbose(Opts2); + false -> run_test_quiet(Opts2) end. -spec format_error(any()) -> iolist(). @@ -155,6 +156,9 @@ cfgopts(State) -> filter_opts({test_spec, _}) -> ?WARN("Test specs not supported", []), false; +filter_opts({auto_compile, _}) -> + ?WARN("Auto compile not supported", []), + false; filter_opts({suite, Suite}) when is_integer(hd(Suite)) -> true; filter_opts({suite, Suite}) when is_atom(Suite) -> {true, {suite, atom_to_list(Suite)}}; @@ -373,6 +377,9 @@ setup_logdir(State, Opts) -> filelib:ensure_dir(filename:join([Logdir, "dummy.beam"])), [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)]. +turn_off_auto_compile(Opts) -> + [{auto_compile, false}|lists:keydelete(auto_compile, 1, Opts)]. + run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)). run_test_quiet(Opts) -> -- cgit v1.1 From 0461729fd923f01796510f93f03ad6c848e6c73e Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Thu, 5 Nov 2015 00:38:06 -0800 Subject: calculate coverage info as late as possible in ct provider execution --- src/rebar_prv_common_test.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 7a088e5..977a5f6 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -77,11 +77,12 @@ run_tests(State, Opts) -> Opts2 = turn_off_auto_compile(Opts1), ?DEBUG("ct_opts ~p", [Opts2]), {RawOpts, _} = rebar_state:command_parsed_args(State), - ok = maybe_write_coverdata(State), - case proplists:get_value(verbose, RawOpts, false) of + Result = case proplists:get_value(verbose, RawOpts, false) of true -> run_test_verbose(Opts2); false -> run_test_quiet(Opts2) - end. + end, + ok = maybe_write_coverdata(State), + Result. -spec format_error(any()) -> iolist(). format_error({error, Reason}) -> -- cgit v1.1 From d2cd2710b11d9bc4af5055f8bb614d41fb5a3a42 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Sat, 7 Nov 2015 09:01:48 -0600 Subject: do not sort mib_first_files during merge_opts, order must be kept --- src/rebar_opts.erl | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/rebar_opts.erl b/src/rebar_opts.erl index 47451c5..97f39b8 100644 --- a/src/rebar_opts.erl +++ b/src/rebar_opts.erl @@ -111,6 +111,10 @@ merge_opts(NewOpts, OldOpts) -> NewValue; (profiles, NewValue, OldValue) -> dict:to_list(merge_opts(dict:from_list(NewValue), dict:from_list(OldValue))); + (mib_first_files, Value, Value) -> + Value; + (mib_first_files, NewValue, OldValue) -> + OldValue ++ NewValue; (_Key, NewValue, OldValue) when is_list(NewValue) -> case io_lib:printable_list(NewValue) of true when NewValue =:= [] -> -- cgit v1.1 From 9184a42ef32f1ed06f9f2e3c8c0d4e949cc0c579 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Thu, 12 Nov 2015 19:16:08 -0600 Subject: fix typo of guarantee. h/t evan --- src/rebar_packages.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index 7be3372..99391ed 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -138,7 +138,7 @@ handle_single_vsn(Dep, Vsn, Constraint) -> {ok, Vsn}; false -> ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. " - "Using anyway, but it is not guarenteed to work.", [Dep, Vsn, Constraint]), + "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]), {ok, Vsn} end. -- cgit v1.1 From d31e663a67a483b6a740f973f52db41fd01e5861 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Fri, 13 Nov 2015 14:13:59 +0000 Subject: Prettify all of common test output. This uses cth_readable to: - silence error_logger output to the shell unless a test fails - silence ct:pal output to the shell unless a test fails I have currently not baked in any way to disable this behaviour, but I figured if it is required, there is time to do it before the final 3.0.0 release. --- src/rebar_prv_common_test.erl | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 977a5f6..525ddaf 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -152,7 +152,7 @@ split_string(String) -> cfgopts(State) -> Opts = rebar_state:get(State, ct_opts, []), - rebar_utils:filtermap(fun filter_opts/1, Opts). + add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts)). filter_opts({test_spec, _}) -> ?WARN("Test specs not supported", []), @@ -170,6 +170,18 @@ filter_opts({suite, Suites}) -> Suites)}}; filter_opts(_) -> true. +add_hooks(Opts) -> + %% cth_readable hooks + case lists:keyfind(ct_hooks, 1, Opts) of + false -> + [{ct_hooks, [cth_readable_failonly, cth_readable_shell]} | Opts]; + {ct_hooks, Hooks} -> + %% Make sure hooks are there once only. + ReadableHooks = [cth_readable_failonly, cth_readable_shell], + NewHooks = (Hooks -- ReadableHooks) ++ ReadableHooks, + lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks}) + end. + select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> FixedOpts = lists:filter(fun({_, _}) -> true; (V) -> ?WARN("`~p` is not a valid option for `ct_opts`", [V]) end, CfgOpts), Merged = lists:ukeymerge(1, @@ -249,7 +261,7 @@ inject(Opts, State) -> %% append `ct_compile_opts` to app defined `erl_opts` ErlOpts = rebar_opts:get(Opts, erl_opts, []), CTOpts = rebar_state:get(State, ct_compile_opts, []), - NewErlOpts = CTOpts ++ ErlOpts, + NewErlOpts = add_transforms(CTOpts) ++ ErlOpts, %% append `ct_first_files` to app defined `erl_first_files` FirstFiles = rebar_opts:get(Opts, erl_first_files, []), CTFirstFiles = rebar_state:get(State, ct_first_files, []), @@ -259,6 +271,11 @@ inject(Opts, State) -> Opts, [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]). +add_transforms(CTOpts) -> + ReadableTransform = [{parse_transform, cth_readable_transform}], + (CTOpts -- ReadableTransform) ++ ReadableTransform. + + test_dirs(State, Apps, Opts) -> case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of {Suites, undefined} -> set_compile_dirs(State, Apps, {suite, Suites}); -- cgit v1.1 From d4981b7913e63247a0db0a92742e7937fd804334 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Fri, 13 Nov 2015 16:59:48 +0000 Subject: Add cth_readable to dep list --- src/rebar.app.src | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/rebar.app.src b/src/rebar.app.src index 5ab3ddd..14c08c9 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -25,6 +25,7 @@ bbmustache, ssl_verify_hostname, certifi, + cth_readable, relx, inets]}, {env, [ -- cgit v1.1 From b9c15df8736a8af737fc7fff5eb6f15d2789ea9a Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Fri, 13 Nov 2015 17:07:45 +0000 Subject: Optionally disable readable output --- src/rebar_prv_common_test.erl | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 525ddaf..7a65dd5 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -152,7 +152,7 @@ split_string(String) -> cfgopts(State) -> Opts = rebar_state:get(State, ct_opts, []), - add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts)). + add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts), State). filter_opts({test_spec, _}) -> ?WARN("Test specs not supported", []), @@ -170,12 +170,13 @@ filter_opts({suite, Suites}) -> Suites)}}; filter_opts(_) -> true. -add_hooks(Opts) -> - %% cth_readable hooks - case lists:keyfind(ct_hooks, 1, Opts) of - false -> +add_hooks(Opts, State) -> + case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of + {false, _} -> + Opts; + {true, false} -> [{ct_hooks, [cth_readable_failonly, cth_readable_shell]} | Opts]; - {ct_hooks, Hooks} -> + {true, {ct_hooks, Hooks}} -> %% Make sure hooks are there once only. ReadableHooks = [cth_readable_failonly, cth_readable_shell], NewHooks = (Hooks -- ReadableHooks) ++ ReadableHooks, @@ -261,7 +262,7 @@ inject(Opts, State) -> %% append `ct_compile_opts` to app defined `erl_opts` ErlOpts = rebar_opts:get(Opts, erl_opts, []), CTOpts = rebar_state:get(State, ct_compile_opts, []), - NewErlOpts = add_transforms(CTOpts) ++ ErlOpts, + NewErlOpts = add_transforms(CTOpts, State) ++ ErlOpts, %% append `ct_first_files` to app defined `erl_first_files` FirstFiles = rebar_opts:get(Opts, erl_first_files, []), CTFirstFiles = rebar_state:get(State, ct_first_files, []), @@ -271,10 +272,22 @@ inject(Opts, State) -> Opts, [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]). -add_transforms(CTOpts) -> - ReadableTransform = [{parse_transform, cth_readable_transform}], - (CTOpts -- ReadableTransform) ++ ReadableTransform. +add_transforms(CTOpts, State) -> + case readable(State) of + true -> + ReadableTransform = [{parse_transform, cth_readable_transform}], + (CTOpts -- ReadableTransform) ++ ReadableTransform; + false -> + CTOpts + end. +readable(State) -> + {RawOpts, _} = rebar_state:command_parsed_args(State), + case proplists:get_value(readable, RawOpts) of + true -> true; + false -> false; + undefined -> rebar_state:get(State, ct_readable, true) + end. test_dirs(State, Apps, Opts) -> case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of @@ -507,6 +520,7 @@ ct_opts(_State) -> {multiply_timetraps, undefined, "multiply_timetraps", integer, help(multiple_timetraps)}, %% Integer {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)}, {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)}, + {readable, undefined, "readable", boolean, help(readable)}, {verbose, $v, "verbose", boolean, help(verbose)} ]. @@ -556,6 +570,8 @@ help(scale_timetraps) -> "Scale timetraps"; help(create_priv_dir) -> "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)"; +help(readable) -> + "Shows test case names and only displays logs to shell on failures"; help(verbose) -> "Verbose output"; help(_) -> -- cgit v1.1 From bca4d4070311afb035a72f976edbe4d8023e1517 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Fri, 13 Nov 2015 10:30:13 -0600 Subject: include Sean Cribbs eunit formatter by default --- src/rebar.app.src | 3 ++- src/rebar_prv_eunit.erl | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/rebar.app.src b/src/rebar.app.src index 14c08c9..67973eb 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -27,7 +27,8 @@ certifi, cth_readable, relx, - inets]}, + inets, + eunit_formatters]}, {env, [ %% Default log level {log_level, warn}, diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index d5612e8..0f9976b 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -304,9 +304,20 @@ validate_module(_State, Module) -> resolve_eunit_opts(State) -> {Opts, _} = rebar_state:command_parsed_args(State), EUnitOpts = rebar_state:get(State, eunit_opts, []), - case proplists:get_value(verbose, Opts, false) of - true -> set_verbose(EUnitOpts); - false -> EUnitOpts + EUnitOpts1 = case proplists:get_value(verbose, Opts, false) of + true -> set_verbose(EUnitOpts); + false -> EUnitOpts + end, + case proplists:get_value(eunit_formatters, Opts, true) of + true -> custom_eunit_formatters(EUnitOpts1); + false -> EUnitOpts1 + end. + +custom_eunit_formatters(Opts) -> + %% If `report` is already set then treat that like `eunit_formatters` is false + case lists:keymember(report, 1, Opts) of + true -> Opts; + false -> [no_tty, {report, {eunit_progress, [colored, profile]}} | Opts] end. set_verbose(Opts) -> -- cgit v1.1 From 87d9edc537d2af620f64e453797040b5ec7d0aca Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Fri, 13 Nov 2015 13:01:24 -0600 Subject: add space before end so eyes don't bleed --- src/rebar_prv_eunit.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 0f9976b..1884f02 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -307,7 +307,7 @@ resolve_eunit_opts(State) -> EUnitOpts1 = case proplists:get_value(verbose, Opts, false) of true -> set_verbose(EUnitOpts); false -> EUnitOpts - end, + end, case proplists:get_value(eunit_formatters, Opts, true) of true -> custom_eunit_formatters(EUnitOpts1); false -> EUnitOpts1 -- cgit v1.1 From 748142838cd11ce5cd532dd598d220cab8424f75 Mon Sep 17 00:00:00 2001 From: Geoff Cant Date: Fri, 13 Nov 2015 13:18:09 -0800 Subject: Feature: rebar shell [--script ] Adds the ability to run an escript before starting the apps and interactive shell for a project. This is intended to improve the local development experience for projects by providing an easy way to run companion services (mock rest APIs, databases etc) that the project relies on. This patch also adds {shell, Defaults} to the rebar config file so that a project can supply default values for many of the new or improved 'rebar3 shell' options: * {apps, OTPApps} * {script_file, EscriptFileName} * {config, ConfigFileName} The order of option precedence is command line, rebar.config, relx. --- src/rebar_prv_shell.erl | 160 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 131 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index 4cf1e04..d93a21f 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -56,12 +56,23 @@ init(State) -> {short_desc, "Run shell with project apps and deps in path."}, {desc, info()}, {opts, [{config, undefined, "config", string, - "Path to the config file to use. Defaults to the " - "sys_config defined for relx, if present."}, + "Path to the config file to use. Defaults to " + "{shell, [{config, File}]} and then the relx " + "sys.config file if not specified."}, {name, undefined, "name", atom, "Gives a long name to the node."}, {sname, undefined, "sname", atom, - "Gives a short name to the node."}]} + "Gives a short name to the node."}, + {script_file, undefined, "script", string, + "Path to an escript file to run before " + "starting the project apps. Defaults to " + "rebar.config {shell, [{script_file, File}]} " + "if not specified."}, + {apps, undefined, "apps", string, + "A list of apps to boot before starting the " + "shell. (E.g. --apps app1,app2,app3) Defaults " + "to rebar.config {shell, [{apps, Apps}]} or " + "relx apps if not specified."}]} ]) ), {ok, State1}. @@ -86,6 +97,7 @@ shell(State) -> setup_name(State), setup_paths(State), setup_shell(), + maybe_run_script(State), %% apps must be started after the change in shell because otherwise %% their application masters never gets the new group leader (held in %% their internal state) @@ -134,6 +146,51 @@ setup_paths(State) -> %% add project app test paths ok = add_test_paths(State). +maybe_run_script(State) -> + case first_value([fun find_script_option/1, + fun find_script_rebar/1], State) of + no_value -> + ?DEBUG("No script_file specified.", []), + ok; + "none" -> + ?DEBUG("Shell script execution skipped (--script none).", []), + ok; + RelFile -> + File = filename:absname(RelFile), + try run_script_file(File) + catch + C:E -> + ?ABORT("Couldn't run shell escript ~p - ~p:~p~nStack: ~p", + [File, C, E, erlang:get_stacktrace()]) + end + end. + +-spec find_script_option(rebar_state:t()) -> no_value | list(). +find_script_option(State) -> + {Opts, _} = rebar_state:command_parsed_args(State), + debug_get_value(script_file, Opts, no_value, + "Found script file from command line option."). + +-spec find_script_rebar(rebar_state:t()) -> no_value | list(). +find_script_rebar(State) -> + Config = rebar_state:get(State, shell, []), + %% Either a string, or undefined + debug_get_value(script_file, Config, no_value, + "Found script file from rebar config file."). + +run_script_file(File) -> + ?DEBUG("Extracting escript from ~p", [File]), + {ok, Script} = escript:extract(File, [compile_source]), + Beam = proplists:get_value(source, Script), + Mod = proplists:get_value(module, beam_lib:info(Beam)), + ?DEBUG("Compiled escript as ~p", [Mod]), + FakeFile = "/fake_path/" ++ atom_to_list(Mod), + {module, Mod} = code:load_binary(Mod, FakeFile, Beam), + ?DEBUG("Evaling ~p:main([]).", [Mod]), + Result = Mod:main([]), + ?DEBUG("Result: ~p", [Result]), + Result. + maybe_boot_apps(State) -> case find_apps_to_boot(State) of undefined -> @@ -172,17 +229,42 @@ check_epmd(_) -> find_apps_to_boot(State) -> %% Try the shell_apps option - case rebar_state:get(State, shell_apps, undefined) of - undefined -> - %% Get to the relx tuple instead - case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of - {_, _, Apps} -> Apps; - false -> undefined - end; + case first_value([fun find_apps_option/1, + fun find_apps_rebar/1, + fun find_apps_relx/1], State) of + no_value -> + undefined; Apps -> Apps end. +-spec find_apps_option(rebar_state:t()) -> no_value | [atom()]. +find_apps_option(State) -> + {Opts, _} = rebar_state:command_parsed_args(State), + case debug_get_value(apps, Opts, no_value, + "Found shell apps from command line option.") of + no_value -> no_value; + AppsStr -> + [ list_to_atom(AppStr) + || AppStr <- string:tokens(AppsStr, " ,:") ] + end. + +-spec find_apps_rebar(rebar_state:t()) -> no_value | list(). +find_apps_rebar(State) -> + ShellOpts = rebar_state:get(State, shell, []), + debug_get_value(apps, ShellOpts, no_value, + "Found shell opts from command line option."). + +-spec find_apps_relx(rebar_state:t()) -> no_value | list(). +find_apps_relx(State) -> + case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of + {_, _, Apps} -> + ?DEBUG("Found shell apps from relx.", []), + Apps; + false -> + no_value + end. + load_apps(Apps) -> [case application:load(App) of ok -> @@ -261,31 +343,51 @@ add_test_paths(State) -> % First try the --config flag, then try the relx sys_config -spec find_config(rebar_state:t()) -> [tuple()] | no_config. find_config(State) -> - case find_config_option(State) of - no_config -> - find_config_relx(State); - Result -> - Result + case first_value([fun find_config_option/1, + fun find_config_rebar/1, + fun find_config_relx/1], State) of + no_value -> + no_config; + Filename when is_list(Filename) -> + consult_config(State, Filename) + end. + +-spec first_value([Fun], State) -> no_value | Value when + Value :: any(), + State :: rebar_state:t(), + Fun :: fun ((State) -> no_value | Value). +first_value([], _) -> no_value; +first_value([Fun | Rest], State) -> + case Fun(State) of + no_value -> + first_value(Rest, State); + Value -> + Value end. --spec find_config_option(rebar_state:t()) -> [tuple()] | no_config. +debug_get_value(Key, List, Default, Description) -> + case proplists:get_value(Key, List, Default) of + Default -> Default; + Value -> + ?DEBUG(Description, []), + Value + end. + +-spec find_config_option(rebar_state:t()) -> Filename::list() | no_value. find_config_option(State) -> {Opts, _} = rebar_state:command_parsed_args(State), - case proplists:get_value(config, Opts) of - undefined -> - no_config; - Filename -> - consult_config(State, Filename) - end. + debug_get_value(config, Opts, no_value, + "Found config from command line option."). --spec find_config_relx(rebar_state:t()) -> [tuple()] | no_config. +-spec find_config_rebar(rebar_state:t()) -> [tuple()] | no_value. +find_config_rebar(State) -> + debug_get_value(config, rebar_state:get(State, shell, []), no_value, + "Found config from rebar config file."). + +-spec find_config_relx(rebar_state:t()) -> [tuple()] | no_value. find_config_relx(State) -> - case proplists:get_value(sys_config, rebar_state:get(State, relx, [])) of - undefined -> - no_config; - Filename -> - consult_config(State, Filename) - end. + debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value, + "Found config from relx."). -spec consult_config(rebar_state:t(), string()) -> [tuple()]. consult_config(State, Filename) -> -- cgit v1.1 From 5f7351d78473634e717c956362d233b8000854c4 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Sat, 14 Nov 2015 14:56:56 +0000 Subject: Fix IO locking up in shell apps Application masters are booted at the root of apps, and take over the group leader role to redirect IO. To cut the chain short and properly have their role inherited, they are their own leader, and keep a reference to the old leader in their internal state, which we cannot change. This is done so process ownership to a given application can be established, and allows to properly clean up resources outside the supervision tree when an app is shut down. This patch goes around and finds all processes whose group leaders are application masters older than the new `user' process booted by the shell providers, and swaps them with that new `user'. This lets the application masters survive, and fixes the blocking IO issue (resolving issue #899) This may mean an incomplete clean up is down on application shutdown, but that seems like a fair compromise. --- src/rebar_prv_shell.erl | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index d93a21f..c644930 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -113,20 +113,34 @@ info() -> setup_shell() -> %% scan all processes for any with references to the old user and save them to %% update later - NeedsUpdate = [Pid || Pid <- erlang:processes(), - proplists:get_value(group_leader, erlang:process_info(Pid)) == whereis(user) - ], + OldUser = whereis(user), %% terminate the current user ok = supervisor:terminate_child(kernel_sup, user), %% start a new shell (this also starts a new user under the correct group) _ = user_drv:start(), %% wait until user_drv and user have been registered (max 3 seconds) ok = wait_until_user_started(3000), + NewUser = whereis(user), %% set any process that had a reference to the old user's group leader to the %% new user process. Catch the race condition when the Pid exited after the %% liveness check. - _ = [catch erlang:group_leader(whereis(user), Pid) || Pid <- NeedsUpdate, - is_process_alive(Pid)], + _ = [catch erlang:group_leader(NewUser, Pid) + || Pid <- erlang:processes(), + proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser, + is_process_alive(Pid)], + %% Application masters have the same problem, but they hold the old group + %% leader in their state and hold on to it. Re-point the processes whose + %% leaders are application masters. This can mess up a few things around + %% shutdown time, but is nicer than the current lock-up. + OldMasters = [Pid + || Pid <- erlang:processes(), + Pid < NewUser, % only change old masters + {_,Dict} <- [erlang:process_info(Pid, dictionary)], + {application_master,init,4} == proplists:get_value('$initial_call', Dict)], + _ = [catch erlang:group_leader(NewUser, Pid) + || Pid <- erlang:processes(), + lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)), + OldMasters)], try %% enable error_logger's tty output error_logger:swap_handler(tty), -- cgit v1.1 From 356ac5033d12b8b91ed0d6c4d308b00070ab12b9 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Sat, 14 Nov 2015 15:07:56 -0500 Subject: Bump to beta-4 --- src/rebar.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/rebar.app.src b/src/rebar.app.src index 67973eb..0314218 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -3,7 +3,7 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "git"}, + {vsn, "3.0.0-beta.4"}, {modules, []}, {registered, []}, {applications, [kernel, -- cgit v1.1 From cc11b7bcecfa80f5670cf5fb9e6aa41bb2f1eb54 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Sat, 14 Nov 2015 15:23:39 -0500 Subject: Back to git versions for source --- src/rebar.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/rebar.app.src b/src/rebar.app.src index 0314218..67973eb 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -3,7 +3,7 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "3.0.0-beta.4"}, + {vsn, "git"}, {modules, []}, {registered, []}, {applications, [kernel, -- cgit v1.1 From f84c51b37a63c406ac3051b2e9538593a212e174 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Sat, 14 Nov 2015 21:42:26 +0000 Subject: Fix error reports on missing include paths In some cases (nested includes?) paths end up in such a way that joining them breaks up and hard-crashes rebar3. This patch specifically handles this scenario to fix things by avoiding passing empty lists to filename:join. --- src/rebar_erlc_compiler.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index 57b7387..fb5796c 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -688,7 +688,7 @@ warn_and_find_path(File, Dir) -> true -> [SrcHeader]; false -> - IncludeDir = filename:join(filename:join(rebar_utils:droplast(filename:split(Dir))), "include"), + IncludeDir = filename:join(rebar_utils:droplast(filename:split(Dir))++["include"]), IncludeHeader = filename:join(IncludeDir, File), case filelib:is_regular(IncludeHeader) of true -> -- cgit v1.1 From aea9809bdbeb9d7f1aa2805398d5ae1011ac4836 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Sat, 14 Nov 2015 17:46:46 -0800 Subject: warn on incorrectly specified test options in `rebar.config` when `ct_opts`, `eunit_tests`, `eunit_first_files`, `ct_first_files`, `erl_first_files`, `eunit_compile_opts`, `ct_compile_opts` and `erl_opts` have values that are single non-list terms warn and try wrapping them in a list when processing them in the `eunit` and `ct` providers --- src/rebar_prv_common_test.erl | 27 +++++++++++++++++++++------ src/rebar_prv_eunit.erl | 34 ++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 7a65dd5..608261a 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -151,8 +151,15 @@ split_string(String) -> string:tokens(String, [$,]). cfgopts(State) -> - Opts = rebar_state:get(State, ct_opts, []), - add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts), State). + case rebar_state:get(State, ct_opts, []) of + Opts when is_list(Opts) -> + add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts), State); + Wrong -> + %% probably a single non list term, try wrapping it in a list and + %% continuing + ?WARN("Value `~p' of option `ct_opts' is not a list, trying to adjust and continue", [Wrong]), + add_hooks(rebar_utils:filtermap(fun filter_opts/1, [Wrong]), State) + end. filter_opts({test_spec, _}) -> ?WARN("Test specs not supported", []), @@ -258,14 +265,22 @@ inject_ct_state(State, Tests) -> NewState = rebar_state:opts(State, NewOpts), test_dirs(NewState, ModdedApps, Tests). +opts(Opts, Key, Default) -> + case rebar_opts:get(Opts, Key, Default) of + Vs when is_list(Vs) -> Vs; + Wrong -> + ?WARN("Value `~p' of option `~p' is not a list, trying to adjust and continue", [Wrong, Key]), + [Wrong] + end. + inject(Opts, State) -> %% append `ct_compile_opts` to app defined `erl_opts` - ErlOpts = rebar_opts:get(Opts, erl_opts, []), - CTOpts = rebar_state:get(State, ct_compile_opts, []), + ErlOpts = opts(Opts, erl_opts, []), + CTOpts = opts(Opts, ct_compile_opts, []), NewErlOpts = add_transforms(CTOpts, State) ++ ErlOpts, %% append `ct_first_files` to app defined `erl_first_files` - FirstFiles = rebar_opts:get(Opts, erl_first_files, []), - CTFirstFiles = rebar_state:get(State, ct_first_files, []), + FirstFiles = opts(Opts, erl_first_files, []), + CTFirstFiles = opts(Opts, ct_first_files, []), NewFirstFiles = CTFirstFiles ++ FirstFiles, %% insert the new keys into the opts lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end, diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 1884f02..b754d87 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -121,21 +121,29 @@ compile(_State, Error) -> Error. inject_eunit_state(State, Tests) -> Apps = rebar_state:project_apps(State), ModdedApps = lists:map(fun(App) -> - NewOpts = inject(rebar_app_info:opts(App), State), + NewOpts = inject(rebar_app_info:opts(App)), rebar_app_info:opts(App, NewOpts) end, Apps), - NewOpts = inject(rebar_state:opts(State), State), + NewOpts = inject(rebar_state:opts(State)), NewState = rebar_state:opts(State, NewOpts), test_dirs(NewState, ModdedApps, Tests). -inject(Opts, State) -> +opts(Opts, Key, Default) -> + case rebar_opts:get(Opts, Key, Default) of + Vs when is_list(Vs) -> Vs; + Wrong -> + ?WARN("Value ~p of option `~p' is not a list, trying to adjust and continue", [Wrong, Key]), + [Wrong] + end. + +inject(Opts) -> %% append `eunit_compile_opts` to app defined `erl_opts` - ErlOpts = rebar_opts:get(Opts, erl_opts, []), - EUnitOpts = rebar_state:get(State, eunit_compile_opts, []), + ErlOpts = opts(Opts, erl_opts, []), + EUnitOpts = opts(Opts, eunit_compile_opts, []), NewErlOpts = EUnitOpts ++ ErlOpts, %% append `eunit_first_files` to app defined `erl_first_files` - FirstFiles = rebar_opts:get(Opts, erl_first_files, []), - EUnitFirstFiles = rebar_state:get(State, eunit_first_files, []), + FirstFiles = opts(Opts, erl_first_files, []), + EUnitFirstFiles = opts(Opts, eunit_first_files, []), NewFirstFiles = EUnitFirstFiles ++ FirstFiles, %% insert the new keys into the opts lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end, @@ -180,13 +188,23 @@ inject_test_dir(Opts, Dir) -> prepare_tests(State) -> %% parse and translate command line tests CmdTests = resolve_tests(State), - CfgTests = rebar_state:get(State, eunit_tests, []), + CfgTests = cfg_tests(State), ProjectApps = rebar_state:project_apps(State), %% 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 select_tests(State, ProjectApps, CmdTests, CfgTests). +cfg_tests(State) -> + case rebar_state:get(State, eunit_tests, []) of + Tests when is_list(Tests) -> Tests; + Wrong -> + %% probably a single non list term, try wrapping it in a list and + %% continuing + ?WARN("Value `~p' of option `eunit_tests' is not a list, trying to adjust and continue", [Wrong]), + [Wrong] + end. + resolve_tests(State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), Apps = resolve(app, application, RawOpts), -- cgit v1.1 From 25914c35086beca01aaf879c5227adba7dfe1037 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Sun, 15 Nov 2015 15:52:19 -0800 Subject: error on ct/eunit argument errors instead of warning --- src/rebar_prv_common_test.erl | 113 ++++++++++++++--------- src/rebar_prv_eunit.erl | 210 +++++++++++++++++++++++------------------- 2 files changed, 186 insertions(+), 137 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 608261a..05a1dc6 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -91,6 +91,10 @@ format_error({error_running_tests, Reason}) -> format_error({error, Reason}); format_error({failures_running_tests, {Failed, AutoSkipped}}) -> io_lib:format("Failures occured running tests: ~b", [Failed+AutoSkipped]); +format_error({badconfig, {Msg, {Value, Key}}}) -> + io_lib:format(Msg, [Value, Key]); +format_error({badconfig, Msg}) -> + io_lib:format(Msg, []); format_error({multiple_errors, Errors}) -> io_lib:format(lists:concat(["Error running tests:"] ++ lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []). @@ -153,29 +157,31 @@ split_string(String) -> cfgopts(State) -> case rebar_state:get(State, ct_opts, []) of Opts when is_list(Opts) -> - add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts), State); + ensure_opts(add_hooks(Opts, State), []); Wrong -> - %% probably a single non list term, try wrapping it in a list and - %% continuing - ?WARN("Value `~p' of option `ct_opts' is not a list, trying to adjust and continue", [Wrong]), - add_hooks(rebar_utils:filtermap(fun filter_opts/1, [Wrong]), State) + %% probably a single non list term + ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, ct_opts}}}) end. -filter_opts({test_spec, _}) -> - ?WARN("Test specs not supported", []), - false; -filter_opts({auto_compile, _}) -> - ?WARN("Auto compile not supported", []), - false; -filter_opts({suite, Suite}) when is_integer(hd(Suite)) -> true; -filter_opts({suite, Suite}) when is_atom(Suite) -> - {true, {suite, atom_to_list(Suite)}}; -filter_opts({suite, Suites}) -> - {true, {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S); - (S) when is_list(S) -> S - end, - Suites)}}; -filter_opts(_) -> true. +ensure_opts([], Acc) -> lists:reverse(Acc); +ensure_opts([{test_spec, _}|_Rest], _Acc) -> + ?PRV_ERROR({badconfig, "Test specs not supported"}); +ensure_opts([{auto_compile, _}|_Rest], _Acc) -> + ?PRV_ERROR({badconfig, "Auto compile not supported"}); +ensure_opts([{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) -> + ensure_opts(Rest, [{suite, Suite}|Acc]); +ensure_opts([{suite, Suite}|Rest], Acc) when is_atom(Suite) -> + ensure_opts(Rest, [{suite, atom_to_list(Suite)}|Acc]); +ensure_opts([{suite, Suites}|Rest], Acc) -> + NewSuites = {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S); + (S) when is_list(S) -> S + end, + Suites)}, + ensure_opts(Rest, [NewSuites|Acc]); +ensure_opts([{K, V}|Rest], Acc) -> + ensure_opts(Rest, [{K, V}|Acc]); +ensure_opts([V|_Rest], _Acc) -> + ?PRV_ERROR({badconfig, {"Member `~p' of option `~p' must be a 2-tuple", {V, ct_opts}}}). add_hooks(Opts, State) -> case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of @@ -190,11 +196,12 @@ add_hooks(Opts, State) -> lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks}) end. +select_tests(_, _, {error, _} = Error, _) -> Error; +select_tests(_, _, _, {error, _} = Error) -> Error; select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> - FixedOpts = lists:filter(fun({_, _}) -> true; (V) -> ?WARN("`~p` is not a valid option for `ct_opts`", [V]) end, CfgOpts), Merged = lists:ukeymerge(1, lists:ukeysort(1, CmdOpts), - lists:ukeysort(1, FixedOpts)), + lists:ukeysort(1, CfgOpts)), %% make sure `dir` and/or `suite` from command line go in as %% a pair overriding both `dir` and `suite` from config if %% they exist @@ -235,7 +242,7 @@ application_dirs([App|Rest], Acc) -> false -> application_dirs(Rest, Acc) end. -compile(State, {ok, Tests}) -> +compile(State, {ok, _} = Tests) -> %% inject `ct_first_files` and `ct_compile_opts` into the applications %% to be compiled case inject_ct_state(State, Tests) of @@ -255,46 +262,68 @@ do_compile(State) -> Error -> Error end. -inject_ct_state(State, Tests) -> +inject_ct_state(State, {ok, Tests}) -> Apps = rebar_state:project_apps(State), - ModdedApps = lists:map(fun(App) -> - NewOpts = inject(rebar_app_info:opts(App), State), - rebar_app_info:opts(App, NewOpts) - end, Apps), - NewOpts = inject(rebar_state:opts(State), State), - NewState = rebar_state:opts(State, NewOpts), - test_dirs(NewState, ModdedApps, Tests). + case inject_ct_state(State, Apps, []) of + {ok, {NewState, ModdedApps}} -> + test_dirs(NewState, ModdedApps, Tests); + {error, _} = Error -> Error + end; +inject_ct_state(_State, Error) -> Error. + +inject_ct_state(State, [App|Rest], Acc) -> + case inject(rebar_app_info:opts(App), State) of + {error, _} = Error -> Error; + NewOpts -> + NewApp = rebar_app_info:opts(App, NewOpts), + inject_ct_state(State, Rest, [NewApp|Acc]) + end; +inject_ct_state(State, [], Acc) -> + case inject(rebar_state:opts(State), State) of + {error, _} = Error -> Error; + NewOpts -> {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}} + end. opts(Opts, Key, Default) -> case rebar_opts:get(Opts, Key, Default) of Vs when is_list(Vs) -> Vs; Wrong -> - ?WARN("Value `~p' of option `~p' is not a list, trying to adjust and continue", [Wrong, Key]), - [Wrong] + ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}}) end. -inject(Opts, State) -> +inject(Opts, State) -> erl_opts(Opts, State). + +erl_opts(Opts, State) -> %% append `ct_compile_opts` to app defined `erl_opts` ErlOpts = opts(Opts, erl_opts, []), CTOpts = opts(Opts, ct_compile_opts, []), - NewErlOpts = add_transforms(CTOpts, State) ++ ErlOpts, + case add_transforms(append(CTOpts, ErlOpts), State) of + {error, Error} -> {error, Error}; + NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts)) + end. + +first_files(Opts) -> %% append `ct_first_files` to app defined `erl_first_files` FirstFiles = opts(Opts, erl_first_files, []), CTFirstFiles = opts(Opts, ct_first_files, []), - NewFirstFiles = CTFirstFiles ++ FirstFiles, - %% insert the new keys into the opts - lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end, - Opts, - [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]). + case append(CTFirstFiles, FirstFiles) of + {error, _} = Error -> Error; + NewFirstFiles -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles) + end. + +append({error, _} = Error, _) -> Error; +append(_, {error, _} = Error) -> Error; +append(A, B) -> A ++ B. -add_transforms(CTOpts, State) -> +add_transforms(CTOpts, State) when is_list(CTOpts) -> case readable(State) of true -> ReadableTransform = [{parse_transform, cth_readable_transform}], (CTOpts -- ReadableTransform) ++ ReadableTransform; false -> CTOpts - end. + end; +add_transforms({error, _} = Error, _State) -> Error. readable(State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index b754d87..9af2965 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -9,7 +9,7 @@ do/1, format_error/1]). %% exported solely for tests --export([compile/2, prepare_tests/1, eunit_opts/1, validate_tests/2]). +-export([prepare_tests/1, eunit_opts/1, validate_tests/2]). -include("rebar.hrl"). -include_lib("providers/include/providers.hrl"). @@ -39,10 +39,12 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> Tests = prepare_tests(State), - case compile(State, Tests) of + %% inject `eunit_first_files`, `eunit_compile_opts` and any + %% directories required by tests into the applications + NewState = inject_eunit_state(State, Tests), + case compile(NewState) of %% successfully compiled apps {ok, S} -> do(S, Tests); - %% this should look like a compiler error, not an eunit error Error -> Error end. @@ -95,6 +97,8 @@ format_error({error_running_tests, Reason}) -> format_error({eunit_test_errors, Errors}) -> io_lib:format(lists:concat(["Error Running EUnit Tests:"] ++ lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []); +format_error({badconfig, {Msg, {Value, Key}}}) -> + io_lib:format(Msg, [Value, Key]); format_error({error, Error}) -> format_error({error_running_tests, Error}). @@ -102,53 +106,120 @@ format_error({error, Error}) -> %% Internal functions %% =================================================================== -compile(State, {ok, Tests}) -> - %% inject `eunit_first_files`, `eunit_compile_opts` and any - %% directories required by tests into the applications - NewState = inject_eunit_state(State, Tests), +prepare_tests(State) -> + %% parse and translate command line tests + CmdTests = resolve_tests(State), + CfgTests = cfg_tests(State), + ProjectApps = rebar_state:project_apps(State), + %% 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 + select_tests(State, ProjectApps, CmdTests, CfgTests). - case rebar_prv_compile:do(NewState) of - %% successfully compiled apps - {ok, S} -> - ok = maybe_cover_compile(S), - {ok, S}; - %% this should look like a compiler error, not an eunit error - Error -> Error - end; -%% maybe compile even in the face of errors? -compile(_State, Error) -> Error. +resolve_tests(State) -> + {RawOpts, _} = rebar_state:command_parsed_args(State), + Apps = resolve(app, application, RawOpts), + Applications = resolve(application, RawOpts), + Dirs = resolve(dir, RawOpts), + Files = resolve(file, RawOpts), + Modules = resolve(module, RawOpts), + Suites = resolve(suite, module, RawOpts), + Apps ++ Applications ++ Dirs ++ Files ++ Modules ++ Suites. + +resolve(Flag, RawOpts) -> resolve(Flag, Flag, RawOpts). + +resolve(Flag, EUnitKey, RawOpts) -> + case proplists:get_value(Flag, RawOpts) of + undefined -> []; + Args -> lists:map(fun(Arg) -> normalize(EUnitKey, Arg) end, string:tokens(Args, [$,])) + end. + +normalize(Key, Value) when Key == dir; Key == file -> {Key, Value}; +normalize(Key, Value) -> {Key, list_to_atom(Value)}. + +cfg_tests(State) -> + case rebar_state:get(State, eunit_tests, []) of + Tests when is_list(Tests) -> Tests; + Wrong -> + %% probably a single non list term + ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, eunit_tests}}}) + end. + +select_tests(_State, _ProjectApps, {error, _} = Error, _) -> Error; +select_tests(_State, _ProjectApps, _, {error, _} = Error) -> Error; +select_tests(State, ProjectApps, [], []) -> {ok, default_tests(State, ProjectApps)}; +select_tests(_State, _ProjectApps, [], Tests) -> {ok, Tests}; +select_tests(_State, _ProjectApps, Tests, _) -> {ok, 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, BareTest}|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]). -inject_eunit_state(State, Tests) -> +inject_eunit_state(State, {ok, Tests}) -> Apps = rebar_state:project_apps(State), - ModdedApps = lists:map(fun(App) -> - NewOpts = inject(rebar_app_info:opts(App)), - rebar_app_info:opts(App, NewOpts) - end, Apps), - NewOpts = inject(rebar_state:opts(State)), - NewState = rebar_state:opts(State, NewOpts), - test_dirs(NewState, ModdedApps, Tests). + case inject_eunit_state(State, Apps, []) of + {ok, {NewState, ModdedApps}} -> + test_dirs(NewState, ModdedApps, Tests); + {error, _} = Error -> Error + end; +inject_eunit_state(_State, Error) -> Error. + +inject_eunit_state(State, [App|Rest], Acc) -> + case inject(rebar_app_info:opts(App)) of + {error, _} = Error -> Error; + NewOpts -> + NewApp = rebar_app_info:opts(App, NewOpts), + inject_eunit_state(State, Rest, [NewApp|Acc]) + end; +inject_eunit_state(State, [], Acc) -> + case inject(rebar_state:opts(State)) of + {error, _} = Error -> Error; + NewOpts -> {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}} + end. opts(Opts, Key, Default) -> case rebar_opts:get(Opts, Key, Default) of Vs when is_list(Vs) -> Vs; Wrong -> - ?WARN("Value ~p of option `~p' is not a list, trying to adjust and continue", [Wrong, Key]), - [Wrong] + ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}}) end. -inject(Opts) -> +inject(Opts) -> erl_opts(Opts). + +erl_opts(Opts) -> %% append `eunit_compile_opts` to app defined `erl_opts` ErlOpts = opts(Opts, erl_opts, []), EUnitOpts = opts(Opts, eunit_compile_opts, []), - NewErlOpts = EUnitOpts ++ ErlOpts, + case append(EUnitOpts, ErlOpts) of + {error, _} = Error -> Error; + NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts)) + end. + +first_files(Opts) -> %% append `eunit_first_files` to app defined `erl_first_files` FirstFiles = opts(Opts, erl_first_files, []), EUnitFirstFiles = opts(Opts, eunit_first_files, []), - NewFirstFiles = EUnitFirstFiles ++ FirstFiles, - %% insert the new keys into the opts - lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end, - Opts, - [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]). + case append(EUnitFirstFiles, FirstFiles) of + {error, _} = Error -> Error; + NewFirstFiles -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles) + end. + +append({error, _} = Error, _) -> Error; +append(_, {error, _} = Error) -> Error; +append(A, B) -> A ++ B. test_dirs(State, Apps, []) -> rebar_state:project_apps(State, Apps); test_dirs(State, Apps, [{dir, Dir}|Rest]) -> @@ -182,71 +253,20 @@ maybe_inject_test_dir(State, AppAcc, [], Dir) -> inject_test_dir(Opts, Dir) -> %% append specified test targets to app defined `extra_src_dirs` - ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []), + ExtraSrcDirs = rebar_dir:extra_src_dirs(Opts), rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]). -prepare_tests(State) -> - %% parse and translate command line tests - CmdTests = resolve_tests(State), - CfgTests = cfg_tests(State), - ProjectApps = rebar_state:project_apps(State), - %% 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 - select_tests(State, ProjectApps, CmdTests, CfgTests). - -cfg_tests(State) -> - case rebar_state:get(State, eunit_tests, []) of - Tests when is_list(Tests) -> Tests; - Wrong -> - %% probably a single non list term, try wrapping it in a list and - %% continuing - ?WARN("Value `~p' of option `eunit_tests' is not a list, trying to adjust and continue", [Wrong]), - [Wrong] - end. - -resolve_tests(State) -> - {RawOpts, _} = rebar_state:command_parsed_args(State), - Apps = resolve(app, application, RawOpts), - Applications = resolve(application, RawOpts), - Dirs = resolve(dir, RawOpts), - Files = resolve(file, RawOpts), - Modules = resolve(module, RawOpts), - Suites = resolve(suite, module, RawOpts), - Apps ++ Applications ++ Dirs ++ Files ++ Modules ++ Suites. - -resolve(Flag, RawOpts) -> resolve(Flag, Flag, RawOpts). - -resolve(Flag, EUnitKey, RawOpts) -> - case proplists:get_value(Flag, RawOpts) of - undefined -> []; - Args -> lists:map(fun(Arg) -> normalize(EUnitKey, Arg) end, string:tokens(Args, [$,])) - end. - -normalize(Key, Value) when Key == dir; Key == file -> {Key, Value}; -normalize(Key, Value) -> {Key, list_to_atom(Value)}. - -select_tests(State, ProjectApps, [], []) -> {ok, default_tests(State, ProjectApps)}; -select_tests(_State, _ProjectApps, [], Tests) -> {ok, Tests}; -select_tests(_State, _ProjectApps, Tests, _) -> {ok, 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, BareTest}|Tests]) +compile({error, _} = Error) -> Error; +compile(State) -> + case rebar_prv_compile:do(State) of + %% successfully compiled apps + {ok, S} -> + ok = maybe_cover_compile(S), + {ok, S}; + %% this should look like a compiler error, not an eunit error + Error -> Error 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, {ok, Tests}) -> gather_tests(fun(Elem) -> validate(State, Elem) end, Tests, []); validate_tests(_State, Error) -> Error. -- cgit v1.1 From d4e876500772657a5a6339905d82424c81d73d2a Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Tue, 17 Nov 2015 14:16:58 -0600 Subject: only add package list of versions to registry if it has the right buildtool support --- src/rebar_prv_update.erl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl index 6637ebe..33d757a 100644 --- a/src/rebar_prv_update.erl +++ b/src/rebar_prv_update.erl @@ -92,12 +92,24 @@ hex_to_index(State) -> false -> true end; - ({Pkg, [Vsns]}, _) when is_binary(Pkg) -> - ets:insert(?PACKAGE_TABLE, {Pkg, Vsns}); (_, _) -> true end, true, Registry), + ets:foldl(fun({Pkg, [[]]}, _) when is_binary(Pkg) -> + true; + ({Pkg, [Vsns=[Vsn | _Rest]]}, _) when is_binary(Pkg) -> + %% Verify the package is of the right build tool by checking if the first + %% version exists in the table from the foldl above + case ets:member(?PACKAGE_TABLE, {Pkg, Vsn}) of + true -> + ets:insert(?PACKAGE_TABLE, {Pkg, Vsns}); + false -> + true + end; + (_, _) -> + true + end, true, Registry), ets:insert(?PACKAGE_TABLE, {package_index_version, ?PACKAGE_INDEX_VERSION}), ?INFO("Writing index to ~s", [PackageIndex]), ets:tab2file(?PACKAGE_TABLE, PackageIndex), -- cgit v1.1 From bf347caa55588b1f38cfaed267111d4c0163d522 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Thu, 19 Nov 2015 23:43:50 +0000 Subject: Handle force flags in leading position The checking of flags and the parsing of arguments is separated up. --- src/rebar_prv_new.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/rebar_prv_new.erl b/src/rebar_prv_new.erl index 6574aca..28572a9 100644 --- a/src/rebar_prv_new.erl +++ b/src/rebar_prv_new.erl @@ -32,7 +32,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - case rebar_state:command_args(State) of + case strip_flags(rebar_state:command_args(State)) of ["help"] -> ?CONSOLE("Call `rebar3 new help