diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cth_fail_fast.erl | 118 | ||||
| -rw-r--r-- | src/rebar_git_resource.erl | 13 | ||||
| -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 | 
8 files changed, 228 insertions, 32 deletions
| 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 0ca6627..c5031df 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 @@ -32,10 +36,11 @@ lock_(AppDir, {git, Url}) ->      {ok, VsnString} =          case os:type() of              {win32, _} -> -                rebar_utils:sh("git --git-dir=\"" ++ Dir ++ "/.git\" --work-tree=\"" ++ Dir ++ "\" rev-parse --verify HEAD", +                rebar_utils:sh("git --git-dir='" ++ Dir ++ "/.git' " +                               "--work-tree='" ++ Dir ++ "' rev-parse --verify HEAD",                      [{use_stdout, false}, {debug_abort_on_error, AbortMsg}]);              _ -> -                rebar_utils:sh("git --git-dir=\"" ++ Dir ++ "/.git\" rev-parse --verify HEAD", +                rebar_utils:sh("git --git-dir='" ++ Dir ++ "/.git' rev-parse --verify HEAD",                      [{use_stdout, false}, {debug_abort_on_error, AbortMsg}])          end,      Ref = rebar_string:trim(VsnString, both, "\n"), @@ -123,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. | 
