diff options
-rwxr-xr-x | bootstrap | 80 | ||||
-rw-r--r-- | priv/shell-completion/bash/rebar3 | 4 | ||||
-rw-r--r-- | priv/shell-completion/fish/rebar3.fish | 1 | ||||
-rw-r--r-- | priv/shell-completion/zsh/_rebar3 | 1 | ||||
-rw-r--r-- | priv/templates/otp_app.app.src | 1 | ||||
-rw-r--r-- | priv/templates/otp_lib.app.src | 1 | ||||
-rw-r--r-- | rebar.config | 6 | ||||
-rw-r--r-- | rebar.lock | 12 | ||||
-rw-r--r-- | src/cth_fail_fast.erl | 118 | ||||
-rw-r--r-- | src/rebar_git_resource.erl | 8 | ||||
-rw-r--r-- | src/rebar_hex_repos.erl | 16 | ||||
-rw-r--r-- | src/rebar_hg_resource.erl | 12 | ||||
-rw-r--r-- | src/rebar_packages.erl | 39 | ||||
-rw-r--r-- | src/rebar_prv_common_test.erl | 26 | ||||
-rw-r--r-- | src/rebar_prv_eunit.erl | 32 | ||||
-rw-r--r-- | src/rebar_prv_packages.erl | 4 | ||||
-rw-r--r-- | test/rebar_eunit_SUITE.erl | 35 | ||||
-rw-r--r-- | test/rebar_pkg_SUITE.erl | 22 | ||||
-rw-r--r-- | test/rebar_pkg_alias_SUITE.erl | 14 | ||||
-rw-r--r-- | test/rebar_pkg_repos_SUITE.erl | 8 |
20 files changed, 361 insertions, 79 deletions
@@ -11,6 +11,10 @@ main(_) -> inets:start(httpc, [{profile, rebar}]), set_httpc_options(), + %% Clear directories for builds since bootstrapping may require + %% a changed structure from an older one + rm_rf("_build/bootstrap"), + %% Fetch and build deps required to build rebar3 BaseDeps = [{providers, []} ,{getopt, []} @@ -27,7 +31,7 @@ main(_) -> bootstrap_rebar3(), %% Build rebar.app from rebar.app.src - {ok, App} = rebar_app_info:new(rebar, "3.7.5", filename:absname("_build/default/lib/rebar/")), + {ok, App} = rebar_app_info:new(rebar, "3.8.0", filename:absname("_build/default/lib/rebar/")), rebar_otp_app:compile(rebar_state:new(), App), %% Because we are compiling files that are loaded already we want to silence @@ -135,13 +139,23 @@ compile(App, FirstFiles) -> filelib:ensure_dir(filename:join([Dir, "ebin", "dummy.beam"])), code:add_path(filename:join(Dir, "ebin")), FirstFilesPaths = [filename:join([Dir, "src", Module]) || Module <- FirstFiles], + LeexFiles = filelib:wildcard(filename:join([Dir, "src", "*.xrl"])), + [compile_xrl_file(X) || X <- LeexFiles], + YeccFiles = filelib:wildcard(filename:join([Dir, "src", "*.yrl"])), + [compile_yrl_file(X) || X <- YeccFiles], Sources = FirstFilesPaths ++ filelib:wildcard(filename:join([Dir, "src", "*.erl"])), - [compile_file(X, [{i, filename:join(Dir, "include")} + [compile_erl_file(X, [{i, filename:join(Dir, "include")} ,debug_info ,{outdir, filename:join(Dir, "ebin")} ,return | additional_defines()]) || X <- Sources]. -compile_file(File, Opts) -> +compile_xrl_file(File) -> + {ok, _} = leex:file(File). + +compile_yrl_file(File) -> + {ok, _} = yecc:file(File). + +compile_erl_file(File, Opts) -> case compile:file(File, Opts) of {ok, _Mod} -> ok; @@ -162,7 +176,7 @@ bootstrap_rebar3() -> filename:absname("_build/default/lib/rebar/src")), true = Res == ok orelse Res == exists, Sources = ["src/rebar_resource_v2.erl", "src/rebar_resource.erl" | filelib:wildcard("src/*.erl")], - [compile_file(X, [{outdir, "_build/default/lib/rebar/ebin/"} + [compile_erl_file(X, [{outdir, "_build/default/lib/rebar/ebin/"} ,return | additional_defines()]) || X <- Sources], code:add_patha(filename:absname("_build/default/lib/rebar/ebin")). @@ -205,6 +219,23 @@ symlink_or_copy(Source, Target) -> end end. +-spec rm_rf(string()) -> 'ok'. +rm_rf(Target) -> + case os:type() of + {unix, _} -> + EscTarget = escape_chars(Target), + {ok, []} = sh(?FMT("rm -rf ~ts", [EscTarget]), + [{use_stdout, false}, abort_on_error]), + ok; + {win32, _} -> + Filelist = filelib:wildcard(Target), + Dirs = [F || F <- Filelist, filelib:is_dir(F)], + Files = Filelist -- Dirs, + ok = delete_each(Files), + ok = delete_each_dir_win32(Dirs), + ok + end. + -spec cp_r(list(string()), file:filename()) -> 'ok'. cp_r([], _Dest) -> ok; @@ -335,6 +366,27 @@ win32_ok({ok, _}) -> true; win32_ok({error, {Rc, _}}) when Rc<9; Rc=:=16 -> true; win32_ok(_) -> false. +%% @private windows rm_rf helpers +delete_each([]) -> + ok; +delete_each([File | Rest]) -> + case file:delete(File) of + ok -> + delete_each(Rest); + {error, enoent} -> + delete_each(Rest); + {error, Reason} -> + io:format("Failed to delete file ~ts: ~p\n", [File, Reason]), + error + end. + +delete_each_dir_win32([]) -> ok; +delete_each_dir_win32([Dir | Rest]) -> + {ok, []} = sh(?FMT("rd /q /s \"~ts\"", + [escape_double_quotes(filename:nativename(Dir))]), + [{use_stdout, false}, return_on_error]), + delete_each_dir_win32(Rest). + %%/rebar_file_utils %%rebar_utils @@ -398,7 +450,14 @@ expand_sh_flag(return_on_error) -> end}; expand_sh_flag(abort_on_error) -> {error_handler, - fun log_and_abort/2}; + %% moved log_and_abort/2 here because some versions somehow had trouble + %% interpreting it and thought `fun log_and_abort/2' was in `erl_eval' + fun(Command, {Rc, Output}) -> + io:format("sh(~ts)~n" + "failed with return code ~w and the following output:~n" + "~ts", [Command, Rc, Output]), + throw(bootstrap_abort) + end}; expand_sh_flag({use_stdout, false}) -> {output_handler, fun(Line, Acc) -> @@ -443,13 +502,6 @@ expand_env_variable(InStr, VarName, RawVarValue) -> re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts) end. --spec log_and_abort(string(), {integer(), string()}) -> no_return(). -log_and_abort(Command, {Rc, Output}) -> - io:format("sh(~ts)~n" - "failed with return code ~w and the following output:~n" - "~ts", [Command, Rc, Output]), - throw(bootstrap_abort). - %%/rebar_utils %%rebar_dir @@ -471,7 +523,7 @@ make_normalized_path(Path) -> AbsPath = make_absolute_path(Path), Components = filename:split(AbsPath), make_normalized_path(Components, []). - + make_absolute_path(Path) -> case filename:pathtype(Path) of absolute -> @@ -636,7 +688,7 @@ join([], Sep) when is_list(Sep) -> join([H|T], Sep) -> H ++ lists:append([Sep ++ X || X <- T]). -%% Same for chr; no non-deprecated equivalent in OTP20+ +%% Same for chr; no non-deprecated equivalent in OTP20+ chr(S, C) when is_integer(C) -> chr(S, C, 1). chr([C|_Cs], C, I) -> I; chr([_|Cs], C, I) -> chr(Cs, C, I+1); diff --git a/priv/shell-completion/bash/rebar3 b/priv/shell-completion/bash/rebar3 index be9af44..41c45f2 100644 --- a/priv/shell-completion/bash/rebar3 +++ b/priv/shell-completion/bash/rebar3 @@ -93,8 +93,8 @@ _rebar3() elif [[ ${prev} == escriptize ]] ; then : elif [[ ${prev} == eunit ]] ; then - sopts="-c -e -v -d -f -m -s" - lopts="--app --application --cover --dir --error_on_warning --file --module --suite --verbose" + sopts="-c -e -v -d -f -m -s -g" + lopts="--app --application --cover --dir --error_on_warning --file --module --suite --generator --verbose" elif [[ ${prev} == help ]] ; then : elif [[ ${prev} == new ]] ; then diff --git a/priv/shell-completion/fish/rebar3.fish b/priv/shell-completion/fish/rebar3.fish index 9cd2c82..e578b96 100644 --- a/priv/shell-completion/fish/rebar3.fish +++ b/priv/shell-completion/fish/rebar3.fish @@ -136,6 +136,7 @@ complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunut' -s e -l error_on_ complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s f -l file -d "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s m -l module -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s s -l suite -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`" +complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s g -l generator -d "Comma separated list of generators (the format is `module:function`) to load tests from. Equivalent to `[{generator, Module, Function}]`" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s v -l verbose -d "Verbose output" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l suite -d "Lists of test suites to run" diff --git a/priv/shell-completion/zsh/_rebar3 b/priv/shell-completion/zsh/_rebar3 index 490a824..8ae8777 100644 --- a/priv/shell-completion/zsh/_rebar3 +++ b/priv/shell-completion/zsh/_rebar3 @@ -95,6 +95,7 @@ _rebar3 () { '(-f --file)'{-f,--file}'[Comma separated list of files to load tests from]:files' \ '(-m --module)'{-m,--module}'[Comma separated list of modules to load tests from]:modules' \ '(-s --suite)'{-s,--suite}'[Comma separated list of modules to load tests from]:modules' \ + '(-g --generator)'{-g,--generator}'[Comma separated list of generators (the format is `module:function`) to load tests from.]:{generator, Module, Function}' \ '(-v --verbose)'{-v,--verbose}'[Verbose output]' \ && ret=0 ;; diff --git a/priv/templates/otp_app.app.src b/priv/templates/otp_app.app.src index 6040089..f6e9d45 100644 --- a/priv/templates/otp_app.app.src +++ b/priv/templates/otp_app.app.src @@ -11,7 +11,6 @@ {env,[]}, {modules, []}, - {maintainers, []}, {licenses, ["Apache 2.0"]}, {links, []} ]}. diff --git a/priv/templates/otp_lib.app.src b/priv/templates/otp_lib.app.src index aa31966..d14752f 100644 --- a/priv/templates/otp_lib.app.src +++ b/priv/templates/otp_lib.app.src @@ -9,7 +9,6 @@ {env,[]}, {modules, []}, - {maintainers, []}, {licenses, ["Apache 2.0"]}, {links, []} ]}. diff --git a/rebar.config b/rebar.config index 61efd8d..75601e9 100644 --- a/rebar.config +++ b/rebar.config @@ -1,17 +1,17 @@ %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 ft=erlang et -{deps, [{erlware_commons, "1.3.0"}, +{deps, [{erlware_commons, "1.3.1"}, {ssl_verify_fun, "1.1.3"}, {certifi, "2.3.1"}, {parse_trans, "3.3.0"}, % force otp-21 compat {providers, "1.7.0"}, {getopt, "1.0.1"}, {bbmustache, "1.6.0"}, - {relx, "3.27.0"}, + {relx, "3.28.0"}, {cf, "0.2.2"}, {cth_readable, "1.4.2"}, - {hex_core, "0.2.0"}, + {hex_core, "0.4.0"}, {eunit_formatters, "0.5.0"}]}. {post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)", @@ -3,13 +3,13 @@ {<<"certifi">>,{pkg,<<"certifi">>,<<"2.3.1">>},0}, {<<"cf">>,{pkg,<<"cf">>,<<"0.2.2">>},0}, {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.4.2">>},0}, - {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.3.0">>},0}, + {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.3.1">>},0}, {<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.5.0">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, - {<<"hex_core">>,{pkg,<<"hex_core">>,<<"0.2.0">>},0}, + {<<"hex_core">>,{pkg,<<"hex_core">>,<<"0.4.0">>},0}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},0}, {<<"providers">>,{pkg,<<"providers">>,<<"1.7.0">>},0}, - {<<"relx">>,{pkg,<<"relx">>,<<"3.27.0">>},0}, + {<<"relx">>,{pkg,<<"relx">>,<<"3.28.0">>},0}, {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.3">>},0}]}. [ {pkg_hash,[ @@ -17,12 +17,12 @@ {<<"certifi">>, <<"D0F424232390BF47D82DA8478022301C561CF6445B5B5FB6A84D49A9E76D2639">>}, {<<"cf">>, <<"7F2913FFF90ABCABD0F489896CFEB0B0674F6C8DF6C10B17A83175448029896C">>}, {<<"cth_readable">>, <<"0F57B4EB7DA7F5438F422312245F9143A1B3118C11B6BAE5C3D1391C9EE88322">>}, - {<<"erlware_commons">>, <<"1705CF2AB4212EF235C21971A55E22E2A39055C05B9C65C8848126865F42A07A">>}, + {<<"erlware_commons">>, <<"0CE192AD69BC6FD0880246D852D0ECE17631E234878011D1586E053641ED4C04">>}, {<<"eunit_formatters">>, <<"6A9133943D36A465D804C1C5B6E6839030434B8879C5600D7DDB5B3BAD4CCB59">>}, {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}, - {<<"hex_core">>, <<"3A7EACCFB8ADD3FF05D950C10ED5BDB5D0C48C988EBBC5D7AE2A55498F0EFF1B">>}, + {<<"hex_core">>, <<"6A0E0B1B519850344292298DA1BFA685E71534FDF4C69434993039F81078C7FA">>}, {<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>}, {<<"providers">>, <<"BBF730563914328EC2511D205E6477A94831DB7297DE313B3872A2B26C562EAB">>}, - {<<"relx">>, <<"96CC7663EDCC02A8117AB0C64FE6D15BE79760C08726ABEAD1DAACE11BFBF75D">>}, + {<<"relx">>, <<"CFC26899E308FAB79B5826C4298EC052A2C1D66D03679AB76E6266D766B56545">>}, {<<"ssl_verify_fun">>, <<"6C49665D4326E26CD4A5B7BD54AA442B33DADFB7C5D59A0D0CD0BF5534BBFBD7">>}]} ]. diff --git a/src/cth_fail_fast.erl b/src/cth_fail_fast.erl new file mode 100644 index 0000000..13b3557 --- /dev/null +++ b/src/cth_fail_fast.erl @@ -0,0 +1,118 @@ +-module(cth_fail_fast). + +%% Callbacks +-export([id/1]). +-export([init/2]). + +-export([pre_init_per_suite/3]). +-export([post_init_per_suite/4]). +-export([pre_end_per_suite/3]). +-export([post_end_per_suite/4]). + +-export([pre_init_per_group/3]). +-export([post_init_per_group/4]). +-export([pre_end_per_group/3]). +-export([post_end_per_group/4]). + +-export([pre_init_per_testcase/3]). +-export([post_end_per_testcase/4]). + +-export([on_tc_fail/3]). +-export([on_tc_skip/3, on_tc_skip/4]). + +-export([terminate/1]). + +%% We work by setting an 'abort' variable on each test case that fails +%% and then triggering the failure before starting the next test. This +%% ensures that all other hooks have run for the same event, and +%% simplifies error reporting. +-record(state, {abort=false}). + +%% @doc Return a unique id for this CTH. +id(_Opts) -> + {?MODULE, make_ref()}. + +%% @doc Always called before any other callback function. Use this to initiate +%% any common state. +init(_Id, _Opts) -> + {ok, #state{}}. + +%% @doc Called before init_per_suite is called. +pre_init_per_suite(_Suite,_Config,#state{abort=true}) -> + abort(); +pre_init_per_suite(_Suite,Config,State) -> + {Config, State}. + +%% @doc Called after init_per_suite. +post_init_per_suite(_Suite,_Config,Return,State) -> + {Return, State}. + +%% @doc Called before end_per_suite. +pre_end_per_suite(_Suite,_Config,#state{abort=true}) -> + abort(); +pre_end_per_suite(_Suite,Config,State) -> + {Config, State}. + +%% @doc Called after end_per_suite. +post_end_per_suite(_Suite,_Config,Return,State) -> + {Return, State}. + +%% @doc Called before each init_per_group. +pre_init_per_group(_Group,_Config,#state{abort=true}) -> + abort(); +pre_init_per_group(_Group,Config,State) -> + {Config, State}. + +%% @doc Called after each init_per_group. +post_init_per_group(_Group,_Config,Return, State) -> + {Return, State}. + +%% @doc Called after each end_per_group. +pre_end_per_group(_Group,_Config,#state{abort=true}) -> + abort(); +pre_end_per_group(_Group,Config,State) -> + {Config, State}. + +%% @doc Called after each end_per_group. +post_end_per_group(_Group,_Config,Return, State) -> + {Return, State}. + +%% @doc Called before each test case. +pre_init_per_testcase(_TC,_Config,#state{abort=true}) -> + abort(); +pre_init_per_testcase(_TC,Config,State) -> + {Config, State}. + +%% @doc Called after each test case. +post_end_per_testcase(_TC,_Config,ok,State) -> + {ok, State}; +post_end_per_testcase(_TC,_Config,Error,State) -> + {Error, State#state{abort=true}}. + +%% @doc Called after post_init_per_suite, post_end_per_suite, post_init_per_group, +%% post_end_per_group and post_end_per_testcase if the suite, group or test case failed. +on_tc_fail(_TC, _Reason, State) -> + State. + +%% @doc Called when a test case is skipped by either user action +%% or due to an init function failing. (>= 19.3) +on_tc_skip(_Suite, _TC, {tc_auto_skip, _}, State) -> + State#state{abort=true}; +on_tc_skip(_Suite, _TC, _Reason, State) -> + State. + +%% @doc Called when a test case is skipped by either user action +%% or due to an init function failing. (Pre-19.3) +on_tc_skip(_TC, {tc_auto_skip, _}, State) -> + State#state{abort=true}; +on_tc_skip(_TC, _Reason, State) -> + State. + +%% @doc Called when the scope of the CTH is done +terminate(#state{}) -> + ok. + +%%% Helpers +abort() -> + io:format(user, "Detected test failure. Aborting~n", []), + halt(1). diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl index 691f1ba..7657699 100644 --- a/src/rebar_git_resource.erl +++ b/src/rebar_git_resource.erl @@ -10,6 +10,10 @@ needs_update/2, make_vsn/2]). +%% For backward compatibilty +-export ([ download/3 + ]). + -include("rebar.hrl"). %% Regex used for parsing scp style remote url @@ -124,6 +128,10 @@ download(TmpDir, AppInfo, State, _) -> {error, Error} end. +%% For backward compatibilty +download(Dir, AppInfo, State) -> + download_(Dir, AppInfo, State). + download_(Dir, {git, Url}, State) -> ?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.", []), download_(Dir, {git, Url, {branch, "master"}}, State); diff --git a/src/rebar_hex_repos.erl b/src/rebar_hex_repos.erl index ebee191..def5f49 100644 --- a/src/rebar_hex_repos.erl +++ b/src/rebar_hex_repos.erl @@ -21,7 +21,8 @@ api_key => binary(), repo_url => binary(), repo_public_key => binary(), - repo_verify => binary()}. + repo_verify => binary(), + repo_verify_origin => binary()}. from_state(BaseConfig, State) -> HexConfig = rebar_state:get(State, hex, []), @@ -67,17 +68,20 @@ repos(HexConfig) -> merge_repos(RepoList ++ [HexDefaultConfig]) end. +%% merge repos must add a field repo_name to work with hex_core 0.4.0 -spec merge_repos([repo()]) -> [repo()]. merge_repos(Repos) -> lists:foldl(fun(R=#{name := Name}, ReposAcc) -> %% private organizations include the parent repo before a : case rebar_string:split(Name, <<":">>) of [Repo, Org] -> + %% hex_core uses repo_name for parent update_repo_list(R#{name => Name, + repo_name => Repo, organization => Org, parent => Repo}, ReposAcc); _ -> - update_repo_list(R, ReposAcc) + update_repo_list(R#{repo_name => Name}, ReposAcc) end end, [], Repos). @@ -104,7 +108,13 @@ update_repo_list(R, []) -> default_repo() -> HexDefaultConfig = hex_core:default_config(), - HexDefaultConfig#{name => ?PUBLIC_HEX_REPO}. + HexDefaultConfig#{name => ?PUBLIC_HEX_REPO, repo_verify_origin => repo_verify_origin()}. + +repo_verify_origin() -> + case os:getenv("REBAR_NO_VERIFY_REPO_ORIGIN") of + "1" -> false; + _ -> true + end. repo_list([]) -> []; diff --git a/src/rebar_hg_resource.erl b/src/rebar_hg_resource.erl index 8139d04..5ae1ee0 100644 --- a/src/rebar_hg_resource.erl +++ b/src/rebar_hg_resource.erl @@ -10,6 +10,11 @@ needs_update/2, make_vsn/2]). + +%% For backward compatibilty +-export([ download/3 + ]). + -include("rebar.hrl"). -spec init(atom(), rebar_state:t()) -> {ok, rebar_resource_v2:resource()}. @@ -72,6 +77,10 @@ download(TmpDir, AppInfo, State, _) -> {error, Error} end. +%% For backward compatibilty +download(Dir, AppInfo, State) -> + download_(Dir, AppInfo, State). + download_(Dir, {hg, Url}, State) -> ?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.", []), download_(Dir, {hg, Url, {branch, "default"}}, State); @@ -191,7 +200,7 @@ check_type_support() -> case get({is_supported, ?MODULE}) of true -> ok; - false -> + _ -> case rebar_utils:sh("hg --version", [{return_on_error, true}, {use_stdout, false}]) of {error, _} -> @@ -201,4 +210,3 @@ check_type_support() -> ok end end. - diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index 757eb86..fc68cab 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -49,10 +49,10 @@ get(Config, Name) -> -spec get_all_names(rebar_state:t()) -> [binary()]. -get_all_names(State) -> +get_all_names(State) -> verify_table(State), lists:usort(ets:select(?PACKAGE_TABLE, [{#package{key={'$1', '_', '_'}, - _='_'}, + _='_'}, [], ['$1']}])). -spec get_package_versions(unicode:unicode_binary(), ec_semver:semver(), @@ -101,14 +101,14 @@ load_and_verify_version(State) -> ?DEBUG("Package index version mismatch. Current version ~p, this rebar3 expecting ~p", [V, ?PACKAGE_INDEX_VERSION]), (catch ets:delete(?PACKAGE_TABLE)), - new_package_table() + new_package_table() end; - _ -> + _ -> new_package_table() end. handle_missing_package(PkgKey, Repo, State, Fun) -> - Name = + Name = case PkgKey of {N, Vsn, _Repo} -> ?DEBUG("Package ~ts-~ts not found. Fetching registry updates for " @@ -121,8 +121,8 @@ handle_missing_package(PkgKey, Repo, State, Fun) -> end, update_package(Name, Repo, State), - try - Fun(State) + try + Fun(State) catch _:_ -> %% Even after an update the package is still missing, time to error out @@ -220,7 +220,7 @@ verify_table(State) -> ets:info(?PACKAGE_TABLE, named_table) =:= true orelse load_and_verify_version(State). parse_deps(Deps) -> - [{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}} + [{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}} || D=#{package := Name, requirement := Constraint} <- Deps]. @@ -233,16 +233,15 @@ parse_checksum(Checksum) -> update_package(Name, RepoConfig=#{name := Repo}, State) -> ?MODULE:verify_table(State), - try hex_repo:get_package(RepoConfig#{repo_key => maps:get(read_key, RepoConfig, <<>>)}, Name) of - {ok, {200, _Headers, #{releases := Releases}}} -> + try hex_repo:get_package(get_package_repo_config(RepoConfig), Name) of + {ok, {200, _Headers, Releases}} -> _ = insert_releases(Name, Releases, Repo, ?PACKAGE_TABLE), {ok, RegistryDir} = rebar_packages:registry_dir(State), PackageIndex = filename:join(RegistryDir, ?INDEX_FILE), ok = ets:tab2file(?PACKAGE_TABLE, PackageIndex); - {ok, {403, _Headers, <<>>}} -> - not_found; - {ok, {404, _Headers, _}} -> - not_found; + {error, unverified} -> + ?WARN(unverified_repo_message(), [Repo]), + fail; Error -> ?DEBUG("Hex get_package request failed: ~p", [Error]), %% TODO: add better log message. hex_core should export a format_error @@ -254,6 +253,18 @@ update_package(Name, RepoConfig=#{name := Repo}, State) -> fail end. +get_package_repo_config(RepoConfig=#{mirror_of := Repo}) -> + get_package_repo_config(maps:remove(mirror_of, RepoConfig#{name => Repo})); +get_package_repo_config(RepoConfig=#{read_key := Key}) -> + get_package_repo_config(maps:remove(read_key, RepoConfig#{repo_key => Key})); +get_package_repo_config(RepoConfig) -> + RepoConfig. + +unverified_repo_message() -> + "The registry repository ~ts uses a record format that has been deprecated for " + "security reasons. The repository should be updated in order to be safer. " + "You can disable this check by setting REBAR_NO_VERIFY_REPO_ORIGIN=1". + insert_releases(Name, Releases, Repo, Table) -> [true = ets:insert(Table, #package{key={Name, ec_semver:parse(Version), Repo}, diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 3d3bd8a..c31c060 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -171,6 +171,9 @@ transform_opts([{cover, _}|Rest], Acc) -> %% drop verbose from opts, ct doesn't care about it transform_opts([{verbose, _}|Rest], Acc) -> transform_opts(Rest, Acc); +%% drop fail_fast from opts, ct doesn't care about it +transform_opts([{fail_fast, _}|Rest], Acc) -> + transform_opts(Rest, Acc); %% getopt should handle anything else transform_opts([Opt|Rest], Acc) -> transform_opts(Rest, [Opt|Acc]). @@ -224,15 +227,21 @@ ensure_opts([V|Rest], Acc) -> ensure_opts(Rest, [V|Acc]). add_hooks(Opts, State) -> + FailFast = case fails_fast(State) of + true -> [cth_fail_fast]; + false -> [] + end, case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of {false, _} -> Opts; {Other, false} -> - [{ct_hooks, [cth_readable_failonly, readable_shell_type(Other), cth_retry]} | Opts]; + [{ct_hooks, [cth_readable_failonly, readable_shell_type(Other), + cth_retry] ++ FailFast} | Opts]; {Other, {ct_hooks, Hooks}} -> %% Make sure hooks are there once only. - ReadableHooks = [cth_readable_failonly, readable_shell_type(Other), cth_retry], - AllReadableHooks = [cth_readable_failonly, cth_retry, + ReadableHooks = [cth_readable_failonly, readable_shell_type(Other), + cth_retry] ++ FailFast, + AllReadableHooks = [cth_readable_failonly, cth_retry, cth_fail_fast, cth_readable_shell, cth_readable_compact_shell], NewHooks = (Hooks -- AllReadableHooks) ++ ReadableHooks, lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks}) @@ -445,6 +454,10 @@ readable(State) -> undefined -> rebar_state:get(State, ct_readable, compact) end. +fails_fast(State) -> + {RawOpts, _} = rebar_state:command_parsed_args(State), + proplists:get_value(fail_fast, RawOpts) == true. + test_dirs(State, Apps, Opts) -> case proplists:get_value(spec, Opts) of undefined -> @@ -773,7 +786,8 @@ ct_opts(_State) -> {setcookie, undefined, "setcookie", atom, help(setcookie)}, {sys_config, undefined, "sys_config", string, help(sys_config)}, %% comma-separated list {compile_only, undefined, "compile_only", boolean, help(compile_only)}, - {retry, undefined, "retry", boolean, help(retry)} + {retry, undefined, "retry", boolean, help(retry)}, + {fail_fast, undefined, "fail_fast", {boolean, false}, help(fail_fast)} ]. help(compile_only) -> @@ -846,5 +860,9 @@ help(setcookie) -> "Sets the cookie if the node is distributed"; help(retry) -> "Experimental feature. If any specification for previously failing test is found, runs them."; +help(fail_fast) -> + "Experimental feature. If any test fails, the run is aborted. Since common test does not " + "support this natively, we abort the rebar3 run on a failure. This May break CT's disk logging and " + "other rebar3 features."; help(_) -> "". diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index f120926..0b00e89 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -105,6 +105,8 @@ format_error({eunit_test_errors, Errors}) -> lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []); format_error({badconfig, {Msg, {Value, Key}}}) -> io_lib:format(Msg, [Value, Key]); +format_error({generator, Value}) -> + io_lib:format("Generator ~p has an invalid format", [Value]); format_error({error, Error}) -> format_error({error_running_tests, Error}). @@ -134,19 +136,34 @@ resolve_tests(State) -> Files = resolve(file, RawOpts), Modules = resolve(module, RawOpts), Suites = resolve(suite, module, RawOpts), - Apps ++ Applications ++ Dirs ++ Files ++ Modules ++ Suites. + Generator = resolve(generator, RawOpts), + Apps ++ Applications ++ Dirs ++ Files ++ Modules ++ Suites ++ Generator. 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, + Args -> normalize(EUnitKey, rebar_string:lexemes(Args, [$,])) end. -normalize(Key, Value) when Key == dir; Key == file -> {Key, Value}; -normalize(Key, Value) -> {Key, list_to_atom(Value)}. +normalize(generator, Args) -> + lists:flatmap(fun(Value) -> normalize_(generator, Value) end, Args); +normalize(EUnitKey, Args) -> + lists:map(fun(Arg) -> normalize_(EUnitKey, Arg) end, Args). + +normalize_(generator, Value) -> + case string:tokens(Value, [$:]) of + [Module0, Functions] -> + Module = list_to_atom(Module0), + lists:map(fun(F) -> {generator, Module, list_to_atom(F)} end, + string:tokens(Functions, [$;])); + _ -> + ?PRV_ERROR({generator, Value}) + 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 @@ -353,6 +370,8 @@ validate(State, {module, Module}) -> validate_module(State, Module); validate(State, {suite, Module}) -> validate_module(State, Module); +validate(State, {generator, Module, Function}) -> + validate_generator(State, Module, Function); validate(State, Module) when is_atom(Module) -> validate_module(State, Module); validate(State, Path) when is_list(Path) -> @@ -395,6 +414,9 @@ validate_module(_State, Module) -> _ -> ok end. +validate_generator(State, Module, _Function) -> + validate_module(State, Module). + resolve_eunit_opts(State) -> {Opts, _} = rebar_state:command_parsed_args(State), EUnitOpts = rebar_state:get(State, eunit_opts, []), @@ -490,6 +512,7 @@ eunit_opts(_State) -> {file, $f, "file", string, help(file)}, {module, $m, "module", string, help(module)}, {suite, $s, "suite", string, help(module)}, + {generator, $g, "generator", string, help(generator)}, {verbose, $v, "verbose", boolean, help(verbose)}, {name, undefined, "name", atom, help(name)}, {sname, undefined, "sname", atom, help(sname)}, @@ -501,6 +524,7 @@ help(cover_export_name) -> "Base name of the coverdata file to write"; help(dir) -> "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`."; help(file) -> "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`."; help(module) -> "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`."; +help(generator) -> "Comma separated list of generators (the format is `module:function`) to load tests from. Equivalent to `[{generator, Module, Function}]`."; help(verbose) -> "Verbose output. Defaults to false."; help(name) -> "Gives a long name to the node"; help(sname) -> "Gives a short name to the node"; diff --git a/src/rebar_prv_packages.erl b/src/rebar_prv_packages.erl index 3e54cdc..a143455 100644 --- a/src/rebar_prv_packages.erl +++ b/src/rebar_prv_packages.erl @@ -72,16 +72,14 @@ print_packages({RepoName, {ok, #{<<"name">> := Name, Description = maps:get(<<"description">>, Meta, ""), Licenses = join(maps:get(<<"licenses">>, Meta, []), <<", ">>), Links = join_map(maps:get(<<"links">>, Meta, []), <<"\n ">>), - Maintainers = join(maps:get(<<"maintainers">>, Meta, []), <<", ">>), Versions = [V || #{<<"version">> := V} <- Releases], VsnStr = join(Versions, <<", ">>), ?CONSOLE("~ts:~n" " Name: ~ts~n" " Description: ~ts~n" " Licenses: ~ts~n" - " Maintainers: ~ts~n" " Links:~n ~ts~n" - " Versions: ~ts~n", [RepoName, Name, Description, Licenses, Maintainers, Links, VsnStr]); + " Versions: ~ts~n", [RepoName, Name, Description, Licenses, Links, VsnStr]); print_packages(_) -> ok. diff --git a/test/rebar_eunit_SUITE.erl b/test/rebar_eunit_SUITE.erl index 1a8bade..87dd3ed 100644 --- a/test/rebar_eunit_SUITE.erl +++ b/test/rebar_eunit_SUITE.erl @@ -13,6 +13,7 @@ -export([single_application_arg/1, multi_application_arg/1, missing_application_arg/1]). -export([single_module_arg/1, multi_module_arg/1, missing_module_arg/1]). -export([single_suite_arg/1, multi_suite_arg/1, missing_suite_arg/1]). +-export([single_generator_arg/1, multi_generator_arg/1, missing_generator_arg/1]). -export([single_file_arg/1, multi_file_arg/1, missing_file_arg/1]). -export([single_dir_arg/1, multi_dir_arg/1, missing_dir_arg/1]). -export([multiple_arg_composition/1, multiple_arg_errors/1]). @@ -47,6 +48,7 @@ groups() -> single_application_arg, multi_application_arg, missing_application_arg, single_module_arg, multi_module_arg, missing_module_arg, single_suite_arg, multi_suite_arg, missing_suite_arg, + single_generator_arg, multi_generator_arg, missing_generator_arg, single_file_arg, multi_file_arg, missing_file_arg, single_dir_arg, multi_dir_arg, missing_dir_arg, multiple_arg_composition, multiple_arg_errors]}]. @@ -239,7 +241,7 @@ multi_app_testset(Config) -> Set = {ok, [{application, multi_app_baz}, {application, multi_app_bar}, {module, multi_app_bar_tests_helper}, - {module, multi_app_baz_tests_helper}, + {module, multi_app_baz_tests_helper}, {module, multi_app_tests}, {module, multi_app_tests_helper}]}, Set = rebar_prv_eunit:prepare_tests(Result). @@ -404,6 +406,37 @@ missing_suite_arg(Config) -> Error = {error, {rebar_prv_eunit, {eunit_test_errors, ["Module `missing_app' not found in project."]}}}, Error = rebar_prv_eunit:validate_tests(State, rebar_prv_eunit:prepare_tests(State)). +%% check that the --generator cmd line opt generates the correct test set +single_generator_arg(Config) -> + S = ?config(result, Config), + + {ok, Args} = getopt:parse(rebar_prv_eunit:eunit_opts(S), ["--generator=module_name:function_name"]), + State = rebar_state:command_parsed_args(S, Args), + + {ok, [{generator, module_name, function_name}]} = rebar_prv_eunit:prepare_tests(State). + +multi_generator_arg(Config) -> + S = ?config(result, Config), + + {ok, Args} = getopt:parse(rebar_prv_eunit:eunit_opts(S), ["--generator=module1:func1;func2,module2:func1;func2"]), + State = rebar_state:command_parsed_args(S, Args), + + Generators = [{generator, module1, func1}, + {generator, module1, func2}, + {generator, module2, func1}, + {generator, module2, func2}], + {ok, Generators} = rebar_prv_eunit:prepare_tests(State). + +%% check that an invalid --suite cmd line opt generates an error +missing_generator_arg(Config) -> + S = ?config(result, Config), + + {ok, Args} = getopt:parse(rebar_prv_eunit:eunit_opts(S), ["--generator=missing_module:func1"]), + State = rebar_state:command_parsed_args(S, Args), + + Error = {error, {rebar_prv_eunit, {eunit_test_errors, ["Module `missing_module' not found in project."]}}}, + Error = rebar_prv_eunit:validate_tests(State, rebar_prv_eunit:prepare_tests(State)). + %% check that the --file cmd line opt generates the correct test set single_file_arg(Config) -> S = ?config(result, Config), diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl index ee74af5..ad06abd 100644 --- a/test/rebar_pkg_SUITE.erl +++ b/test/rebar_pkg_SUITE.erl @@ -99,7 +99,7 @@ init_per_testcase(bad_disconnect=Name, Config0) -> Config = mock_config(Name, Config1), meck:expect(hex_repo, get_tarball, fun(_, _, _) -> {error, econnrefused} - end), + end), Config; init_per_testcase(Name, Config0) -> Config = [{good_cache, false}, @@ -252,7 +252,7 @@ mock_config(Name, Config) -> CacheRoot = filename:join([Priv, "cache", atom_to_list(Name)]), TmpDir = filename:join([Priv, "tmp", atom_to_list(Name)]), Tid = ets:new(registry_table, [public]), - AllDeps = [ + AllDeps = [ {{<<"badindexchk">>,<<"1.0.0">>}, [[], ?bad_checksum, [<<"rebar3">>]]}, {{<<"goodpkg">>,<<"1.0.0">>}, [[], ?good_checksum, [<<"rebar3">>]]}, {{<<"goodpkg">>,<<"1.0.1">>}, [[], ?good_checksum, [<<"rebar3">>]]}, @@ -267,7 +267,7 @@ mock_config(Name, Config) -> ok = ets:tab2file(Tid, filename:join([CacheDir, "registry"])), catch ets:delete(?PACKAGE_TABLE), - rebar_packages:new_package_table(), + rebar_packages:new_package_table(), lists:foreach(fun({{N, Vsn}, [Deps, Checksum, _]}) -> case ets:member(?PACKAGE_TABLE, {ec_cnv:to_binary(N), Vsn, <<"hexpm">>}) of false -> @@ -279,18 +279,18 @@ mock_config(Name, Config) -> ok end end, AllDeps), - + meck:new(hex_repo, [passthrough]), - meck:expect(hex_repo, get_package, + meck:expect(hex_repo, get_package, fun(_Config, PkgName) -> Matches = ets:match_object(Tid, {{PkgName,'_'}, '_'}), - Releases = + Releases = [#{checksum => Checksum, version => Vsn, - dependencies => Deps} || + dependencies => Deps} || {{_, Vsn}, [Deps, Checksum, _]} <- Matches], - {ok, {200, #{}, #{releases => Releases}}} + {ok, {200, #{}, Releases}} end), %% The state returns us a fake registry @@ -321,7 +321,7 @@ mock_config(Name, Config) -> %% Cache fetches are mocked -- we assume the server and clients are %% correctly used. GoodCache = ?config(good_cache, Config), - {Pkg,Vsn} = ?config(pkg, Config), + {Pkg,Vsn} = ?config(pkg, Config), PkgFile = <<Pkg/binary, "-", Vsn/binary, ".tar">>, {ok, PkgContents} = file:read_file(filename:join(?config(data_dir, Config), PkgFile)), @@ -329,8 +329,8 @@ mock_config(Name, Config) -> {ok, {304, #{<<"etag">> => ?good_etag}, <<>>}}; (_, _, _) -> {ok, {200, #{<<"etag">> => ?good_etag}, PkgContents}} - end), - + end), + [{cache_root, CacheRoot}, {cache_dir, CacheDir}, {tmp_dir, TmpDir}, diff --git a/test/rebar_pkg_alias_SUITE.erl b/test/rebar_pkg_alias_SUITE.erl index 079a3fd..1ef5a34 100644 --- a/test/rebar_pkg_alias_SUITE.erl +++ b/test/rebar_pkg_alias_SUITE.erl @@ -224,7 +224,7 @@ mock_config(Name, Config) -> meck:expect(rebar_prv_update, do, fun(State) -> {ok, State} end), catch ets:delete(?PACKAGE_TABLE), - rebar_packages:new_package_table(), + rebar_packages:new_package_table(), lists:foreach(fun({{N, Vsn}, [Deps, Checksum, _]}) -> case ets:member(?PACKAGE_TABLE, {ec_cnv:to_binary(N), Vsn, <<"hexpm">>}) of @@ -238,11 +238,11 @@ mock_config(Name, Config) -> end; ({_N, _Vsns}) -> ok - - end, AllDeps), + + end, AllDeps), meck:new(hex_repo, [passthrough]), - meck:expect(hex_repo, get_package, + meck:expect(hex_repo, get_package, fun(_Config, PkgName) -> Matches = ets:match_object(Tid, {{PkgName,'_'}, '_'}), Releases = @@ -251,13 +251,13 @@ mock_config(Name, Config) -> dependencies => [{DAppName, {pkg, DN, DV, undefined}} || {DN, DV, _, DAppName} <- Deps]} || {{_, Vsn}, [Deps, Checksum, _]} <- Matches], - {ok, {200, #{}, #{releases => Releases}}} + {ok, {200, #{}, Releases}} end), meck:expect(hex_repo, get_tarball, fun(_, _, _) -> {ok, {304, #{<<"etag">> => EtagGood}, <<>>}} - end), - + end), + %% Move all packages to cache NewConf = [{cache_root, CacheRoot}, {cache_dir, CacheDir}, diff --git a/test/rebar_pkg_repos_SUITE.erl b/test/rebar_pkg_repos_SUITE.erl index c808475..55bc020 100644 --- a/test/rebar_pkg_repos_SUITE.erl +++ b/test/rebar_pkg_repos_SUITE.erl @@ -182,23 +182,25 @@ repo_merging(_Config) -> repo_replacing(_Config) -> Repo1 = #{name => <<"repo-1">>, + repo_name => <<"repo-1">>, api_url => <<"repo-1/api">>}, Repo2 = #{name => <<"repo-2">>, + repo_name => <<"repo-2">>, repo_url => <<"repo-2/repo">>, repo_verify => false}, ?assertMatch([Repo1, Repo2, #{name := <<"hexpm">>}], rebar_hex_repos:repos([{repos, [Repo1]}, - {repos, [Repo2]}])), + {repos, [Repo2]}])), %% use of replace is ignored if found in later entries than the first ?assertMatch([Repo1, Repo2, #{name := <<"hexpm">>}], rebar_hex_repos:repos([{repos, [Repo1]}, - {repos, replace, [Repo2]}])), + {repos, replace, [Repo2]}])), ?assertMatch([Repo1], rebar_hex_repos:repos([{repos, replace, [Repo1]}, - {repos, [Repo2]}])). + {repos, [Repo2]}])). auth_merging(_Config) -> Repo1 = #{name => <<"repo-1">>, |