diff options
-rw-r--r-- | THANKS | 1 | ||||
-rwxr-xr-x | bootstrap | 32 | ||||
-rw-r--r-- | priv/templates/gitignore | 1 | ||||
-rw-r--r-- | rebar.config | 6 | ||||
-rw-r--r-- | rebar.lock | 12 | ||||
-rw-r--r-- | src/rebar.app.src | 1 | ||||
-rw-r--r-- | src/rebar3.erl | 3 | ||||
-rw-r--r-- | src/rebar_base_compiler.erl | 2 | ||||
-rw-r--r-- | src/rebar_erlc_compiler.erl | 2 | ||||
-rw-r--r-- | src/rebar_git_resource.erl | 72 | ||||
-rw-r--r-- | src/rebar_opts.erl | 93 | ||||
-rw-r--r-- | src/rebar_pkg_resource.erl | 4 | ||||
-rw-r--r-- | src/rebar_prv_common_test.erl | 221 | ||||
-rw-r--r-- | src/rebar_prv_eunit.erl | 11 | ||||
-rw-r--r-- | src/rebar_prv_get_deps.erl | 37 | ||||
-rw-r--r-- | src/rebar_prv_install_deps.erl | 4 | ||||
-rw-r--r-- | src/rebar_prv_shell.erl | 6 | ||||
-rw-r--r-- | src/rebar_templater.erl | 2 | ||||
-rw-r--r-- | src/rebar_utils.erl | 24 | ||||
-rw-r--r-- | test/rebar_ct_SUITE.erl | 322 | ||||
-rw-r--r-- | test/rebar_profiles_SUITE.erl | 104 | ||||
-rw-r--r-- | test/rebar_utils_SUITE.erl | 42 |
22 files changed, 841 insertions, 161 deletions
@@ -137,3 +137,4 @@ Stefan Grundmann Carlos Eduardo de Paula Derek Brown Heinz N. Gies +Roberto Aloi @@ -24,7 +24,7 @@ main(_) -> bootstrap_rebar3(), %% Build rebar.app from rebar.app.src - {ok, App} = rebar_app_info:new(rebar, "3.3.2", filename:absname("_build/default/lib/rebar/")), + {ok, App} = rebar_app_info:new(rebar, "3.3.3", 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 @@ -99,8 +99,9 @@ fetch({pkg, Name, Vsn}, App) -> {ok, Binary} -> {ok, Contents} = extract(Binary), ok = erl_tar:extract({binary, Contents}, [{cwd, Dir}, compressed]); - _ -> - io:format("Error: Unable to fetch package ~p ~p~n", [Name, Vsn]) + {error, {Reason, _}} -> + ReasonText = re:replace(atom_to_list(Reason), "_", " ", [global,{return,list}]), + io:format("Error: Unable to fetch package ~s ~s: ~s~n", [Name, Vsn, ReasonText]) end; true -> io:format("Dependency ~s already exists~n", [Name]) @@ -112,8 +113,10 @@ extract(Binary) -> {ok, Contents}. request(Url) -> + HttpOptions = [{relaxed, true} | get_proxy_auth()], + case httpc:request(get, {Url, []}, - [{relaxed, true}], + HttpOptions, [{body_format, binary}], rebar) of {ok, {{_Version, 200, _Reason}, _Headers, Body}} -> @@ -147,8 +150,9 @@ set_httpc_options(_, []) -> ok; set_httpc_options(Scheme, Proxy) -> - {ok, {_, _, Host, Port, _, _}} = http_uri:parse(Proxy), - httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar). + {ok, {_, UserInfo, Host, Port, _, _}} = http_uri:parse(Proxy), + httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar), + set_proxy_auth(UserInfo). compile(App, FirstFiles) -> Dir = filename:join(filename:absname("_build/default/lib/"), App), @@ -402,3 +406,19 @@ otp_release1(Rel) -> binary:bin_to_list(Vsn, {0, Size - 1}) end end. + +set_proxy_auth([]) -> + ok; +set_proxy_auth(UserInfo) -> + Idx = string:chr(UserInfo, $:), + Username = string:sub_string(UserInfo, 1, Idx-1), + Password = string:sub_string(UserInfo, Idx+1), + %% password may contain url encoded characters, need to decode them first + put(proxy_auth, [{proxy_auth, {Username, http_uri:decode(Password)}}]). + +get_proxy_auth() -> + case get(proxy_auth) of + undefined -> []; + ProxyAuth -> ProxyAuth + end. + diff --git a/priv/templates/gitignore b/priv/templates/gitignore index 121a4de..468614d 100644 --- a/priv/templates/gitignore +++ b/priv/templates/gitignore @@ -14,3 +14,4 @@ erl_crash.dump logs _build .idea +rebar3.crashdump diff --git a/rebar.config b/rebar.config index f61d3e5..a48fa41 100644 --- a/rebar.config +++ b/rebar.config @@ -1,14 +1,14 @@ %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 ft=erlang et -{deps, [{erlware_commons, "0.21.0"}, +{deps, [{erlware_commons, "0.22.0"}, {ssl_verify_fun, "1.1.1"}, {certifi, "0.4.0"}, {providers, "1.6.0"}, {getopt, "0.8.2"}, {bbmustache, "1.3.0"}, - {relx, "3.21.1"}, - {cf, "0.2.1"}, + {relx, "3.22.0"}, + {cf, "0.2.2"}, {cth_readable, "1.2.3"}, {eunit_formatters, "0.3.1"}]}. @@ -1,24 +1,24 @@ {"1.1.0", [{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.3.0">>},0}, {<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},0}, - {<<"cf">>,{pkg,<<"cf">>,<<"0.2.1">>},0}, + {<<"cf">>,{pkg,<<"cf">>,<<"0.2.2">>},0}, {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.3">>},0}, - {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.21.0">>},0}, + {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.22.0">>},0}, {<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.3.1">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0}, {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0}, - {<<"relx">>,{pkg,<<"relx">>,<<"3.21.1">>},0}, + {<<"relx">>,{pkg,<<"relx">>,<<"3.22.0">>},0}, {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.1">>},0}]}. [ {pkg_hash,[ {<<"bbmustache">>, <<"2010ADAE78830992A4C69680115ECD7D475DD03A72C076BBADDCCBF2D4B32035">>}, {<<"certifi">>, <<"A7966EFB868B179023618D29A407548F70C52466BF1849B9E8EBD0E34B7EA11F">>}, - {<<"cf">>, <<"69D0B1349FD4D7D4DC55B7F407D29D7A840BF9A1EF5AF529F1EBE0CE153FC2AB">>}, + {<<"cf">>, <<"7F2913FFF90ABCABD0F489896CFEB0B0674F6C8DF6C10B17A83175448029896C">>}, {<<"cth_readable">>, <<"293120673DFF82F0768612C5282E35C40CACC1B6F94FE99077438FD3749D0E27">>}, - {<<"erlware_commons">>, <<"A04433071AD7D112EDEFC75AC77719DD3E6753E697AC09428FC83D7564B80B15">>}, + {<<"erlware_commons">>, <<"051BED79A34E66678C1CBEEBC7B66360C827D081A0C5E2528878011E31DDCDCA">>}, {<<"eunit_formatters">>, <<"7A6FC351EB5B873E2356B8852EB751E20C13A72FBCA03393CF682B8483509573">>}, {<<"getopt">>, <<"B17556DB683000BA50370B16C0619DF1337E7AF7ECBF7D64FBF8D1D6BCE3109B">>}, {<<"providers">>, <<"DB0E2F9043AE60C0155205FCD238D68516331D0E5146155E33D1E79DC452964A">>}, - {<<"relx">>, <<"F989DC520730EFD9075E9F4DEBCB8BA1D7D1E86B018B0BCF45A2EB80270B4AD6">>}, + {<<"relx">>, <<"FF7E2B5924B754A63BA1A46BA8C1901A2D6AE1418982E189CFED5937DACE18DA">>}, {<<"ssl_verify_fun">>, <<"28A4D65B7F59893BC2C7DE786DEC1E1555BD742D336043FE644AE956C3497FBE">>}]} ]. diff --git a/src/rebar.app.src b/src/rebar.app.src index 5b735cf..74efe97 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -52,6 +52,7 @@ rebar_prv_edoc, rebar_prv_escriptize, rebar_prv_eunit, + rebar_prv_get_deps, rebar_prv_help, rebar_prv_install_deps, rebar_prv_local_install, diff --git a/src/rebar3.erl b/src/rebar3.erl index fa26ab2..56bf3e8 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -170,6 +170,8 @@ run_aux(State, RawArgs) -> %% to find config files, and so on, and return an internal rebar3 state term. -spec init_config() -> rebar_state:t(). init_config() -> + rebar_utils:set_httpc_options(), + %% Initialize logging system Verbosity = log_level(), ok = rebar_log:init(command_line, Verbosity), @@ -366,7 +368,6 @@ ensure_running(App, Caller) -> -spec state_from_global_config([term()], file:filename()) -> rebar_state:t(). state_from_global_config(Config, GlobalConfigFile) -> - rebar_utils:set_httpc_options(), GlobalConfigTerms = rebar_config:consult_file(GlobalConfigFile), GlobalConfig = rebar_state:new(GlobalConfigTerms), diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl index 02d567c..dcb1975 100644 --- a/src/rebar_base_compiler.erl +++ b/src/rebar_base_compiler.erl @@ -98,7 +98,7 @@ run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, Compile3Fn, Opts) -> %% Convert simple extension to proper regex - SourceExtRe = "^[^._].*\\" ++ SourceExt ++ [$$], + SourceExtRe = "^(?!\\._).*\\" ++ SourceExt ++ [$$], Recursive = proplists:get_value(recursive, Opts, true), %% Find all possible source files diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index 325bb4f..95573fd 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -48,7 +48,7 @@ -type compile_opt() :: {recursive, boolean()}. -define(DEFAULT_OUTDIR, "ebin"). --define(RE_PREFIX, "^[^._]"). +-define(RE_PREFIX, "^(?!\\._)"). %% =================================================================== %% Public API diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl index acb9ec0..201b8b6 100644 --- a/src/rebar_git_resource.erl +++ b/src/rebar_git_resource.erl @@ -107,28 +107,50 @@ download(Dir, {git, Url, ""}, State) -> download(Dir, {git, Url, {branch, "master"}}, State); download(Dir, {git, Url, {branch, Branch}}, _State) -> ok = filelib:ensure_dir(Dir), - rebar_utils:sh(?FMT("git clone ~s ~s -b ~s --single-branch", - [rebar_utils:escape_chars(Url), - rebar_utils:escape_chars(filename:basename(Dir)), - rebar_utils:escape_chars(Branch)]), - [{cd, filename:dirname(Dir)}]); + git_clone(branch, git_vsn(), Url, Dir, Branch); download(Dir, {git, Url, {tag, Tag}}, _State) -> ok = filelib:ensure_dir(Dir), - rebar_utils:sh(?FMT("git clone ~s ~s -b ~s --single-branch", - [rebar_utils:escape_chars(Url), - rebar_utils:escape_chars(filename:basename(Dir)), - rebar_utils:escape_chars(Tag)]), - [{cd, filename:dirname(Dir)}]); + git_clone(tag, git_vsn(), Url, Dir, Tag); download(Dir, {git, Url, {ref, Ref}}, _State) -> ok = filelib:ensure_dir(Dir), + git_clone(ref, git_vsn(), Url, Dir, Ref); +download(Dir, {git, Url, Rev}, _State) -> + ?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.", []), + ok = filelib:ensure_dir(Dir), + git_clone(rev, git_vsn(), Url, Dir, Rev). + +%% Use different git clone commands depending on git --version +git_clone(branch,Vsn,Url,Dir,Branch) when Vsn >= {1,7,10}; Vsn =:= undefined -> + rebar_utils:sh(?FMT("git clone ~s ~s -b ~s --single-branch", + [rebar_utils:escape_chars(Url), + rebar_utils:escape_chars(filename:basename(Dir)), + rebar_utils:escape_chars(Branch)]), + [{cd, filename:dirname(Dir)}]); +git_clone(branch,_Vsn,Url,Dir,Branch) -> + rebar_utils:sh(?FMT("git clone ~s ~s -b ~s", + [rebar_utils:escape_chars(Url), + rebar_utils:escape_chars(filename:basename(Dir)), + rebar_utils:escape_chars(Branch)]), + [{cd, filename:dirname(Dir)}]); +git_clone(tag,Vsn,Url,Dir,Tag) when Vsn >= {1,7,10}; Vsn =:= undefined -> + rebar_utils:sh(?FMT("git clone ~s ~s -b ~s --single-branch", + [rebar_utils:escape_chars(Url), + rebar_utils:escape_chars(filename:basename(Dir)), + rebar_utils:escape_chars(Tag)]), + [{cd, filename:dirname(Dir)}]); +git_clone(tag,_Vsn,Url,Dir,Tag) -> + rebar_utils:sh(?FMT("git clone ~s ~s -b ~s", + [rebar_utils:escape_chars(Url), + rebar_utils:escape_chars(filename:basename(Dir)), + rebar_utils:escape_chars(Tag)]), + [{cd, filename:dirname(Dir)}]); +git_clone(ref,_Vsn,Url,Dir,Ref) -> rebar_utils:sh(?FMT("git clone -n ~s ~s", [rebar_utils:escape_chars(Url), rebar_utils:escape_chars(filename:basename(Dir))]), [{cd, filename:dirname(Dir)}]), rebar_utils:sh(?FMT("git checkout -q ~s", [Ref]), [{cd, Dir}]); -download(Dir, {git, Url, Rev}, _State) -> - ?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.", []), - ok = filelib:ensure_dir(Dir), +git_clone(rev,_Vsn,Url,Dir,Rev) -> rebar_utils:sh(?FMT("git clone -n ~s ~s", [rebar_utils:escape_chars(Url), rebar_utils:escape_chars(filename:basename(Dir))]), @@ -136,6 +158,30 @@ download(Dir, {git, Url, Rev}, _State) -> rebar_utils:sh(?FMT("git checkout -q ~s", [rebar_utils:escape_chars(Rev)]), [{cd, Dir}]). +git_vsn() -> + case application:get_env(rebar, git_vsn) of + {ok, Vsn} -> Vsn; + undefined -> + Vsn = git_vsn_fetch(), + application:set_env(rebar, git_vsn, Vsn), + Vsn + end. + +git_vsn_fetch() -> + case rebar_utils:sh("git --version",[]) of + {ok, VsnStr} -> + case re:run(VsnStr, "git version\\h+(\\d)\\.(\\d)\\.(\\d).*",[{capture,[1,2,3],list}]) of + {match,[Maj,Min,Patch]} -> + {list_to_integer(Maj), + list_to_integer(Min), + list_to_integer(Patch)}; + nomatch -> + undefined + end; + {error, _} -> + undefined + end. + make_vsn(Dir) -> case collect_default_refcount(Dir) of Vsn={plain, _} -> diff --git a/src/rebar_opts.erl b/src/rebar_opts.erl index b02a504..444b760 100644 --- a/src/rebar_opts.erl +++ b/src/rebar_opts.erl @@ -101,43 +101,70 @@ merge_opts(Profile, NewOpts, OldOpts) -> end. merge_opts(NewOpts, OldOpts) -> - dict:merge(fun(deps, _NewValue, OldValue) -> - OldValue; - ({deps, _}, NewValue, _OldValue) -> - NewValue; - (plugins, NewValue, _OldValue) -> - NewValue; - ({plugins, _}, NewValue, _OldValue) -> - 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; - (relx, NewValue, OldValue) -> - rebar_utils:tup_umerge(OldValue, NewValue); - (_Key, NewValue, OldValue) when is_list(NewValue) -> - case io_lib:printable_list(NewValue) of - true when NewValue =:= [] -> - case io_lib:printable_list(OldValue) of - true -> - NewValue; - false -> - OldValue - end; - true -> - NewValue; - false -> - rebar_utils:tup_umerge(NewValue, OldValue) - end; - (_Key, NewValue, _OldValue) -> - NewValue - end, NewOpts, OldOpts). + dict:merge(fun merge_opt/3, NewOpts, OldOpts). %% Internal functions %% +%% Function for dict:merge/3 (in merge_opts/2) to merge options by priority. +%% +merge_opt(deps, _NewValue, OldValue) -> + OldValue; +merge_opt({deps, _}, NewValue, _OldValue) -> + NewValue; +merge_opt(plugins, NewValue, _OldValue) -> + NewValue; +merge_opt({plugins, _}, NewValue, _OldValue) -> + NewValue; +merge_opt(profiles, NewValue, OldValue) -> + dict:to_list(merge_opts(dict:from_list(NewValue), dict:from_list(OldValue))); +merge_opt(mib_first_files, Value, Value) -> + Value; +merge_opt(mib_first_files, NewValue, OldValue) -> + OldValue ++ NewValue; +merge_opt(relx, NewValue, OldValue) -> + rebar_utils:tup_umerge(OldValue, NewValue); +merge_opt(Key, NewValue, OldValue) + when Key == erl_opts; Key == eunit_compile_opts; Key == ct_compile_opts -> + merge_erl_opts(lists:reverse(OldValue), NewValue); +merge_opt(_Key, NewValue, OldValue) when is_list(NewValue) -> + case io_lib:printable_list(NewValue) of + true when NewValue =:= [] -> + case io_lib:printable_list(OldValue) of + true -> + NewValue; + false -> + OldValue + end; + true -> + NewValue; + false -> + rebar_utils:tup_umerge(NewValue, OldValue) + end; +merge_opt(_Key, NewValue, _OldValue) -> + NewValue. + +%% +%% Merge Erlang compiler options such that the result +%% a) Doesn't contain duplicates. +%% b) Resulting options are ordered by increasing precedence as expected by +%% the compiler. +%% The first parameter is the lower precedence options, in reverse order, to +%% be merged with the higher-precedence options in the second parameter. +%% +merge_erl_opts([Opt | Opts], []) -> + merge_erl_opts(Opts, [Opt]); +merge_erl_opts([Opt | Opts], Merged) -> + case lists:member(Opt, Merged) of + true -> + merge_erl_opts(Opts, Merged); + _ -> + merge_erl_opts(Opts, [Opt | Merged]) + end; +merge_erl_opts([], Merged) -> + Merged. + +%% %% Filter a list of erl_opts platform_define options such that only %% those which match the provided architecture regex are returned. %% diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl index 8a84ce3..88419bd 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -107,8 +107,10 @@ make_vsn(_) -> {error, "Replacing version of type pkg not supported."}. request(Url, ETag) -> + HttpOptions = [{ssl, ssl_opts(Url)}, {relaxed, true} | rebar_utils:get_proxy_auth()], + case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]++[{"User-Agent", rebar_utils:user_agent()}]}, - [{ssl, ssl_opts(Url)}, {relaxed, true}], + HttpOptions, [{body_format, binary}], rebar) of {ok, {{_Version, 200, _Reason}, Headers, Body}} -> diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index be31e8c..2ac8fc7 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -100,7 +100,9 @@ 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)), []). + lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []); +format_error({error_reading_testspec, Reason}) -> + io_lib:format("Error reading testspec: ~p", [Reason]). %% =================================================================== %% Internal functions @@ -227,20 +229,42 @@ select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> rebar_utils:reread_config(Configs), code:set_path(OldPath), - 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, + Opts = merge_opts(CmdOpts,CfgOpts), discover_tests(State, ProjectApps, Opts). +%% Merge the option lists from command line and rebar.config: +%% +%% - Options set on the command line will replace the same options if +%% set in rebar.config. +%% +%% - Special care is taken with options that select which tests to +%% run - ANY such option on the command line will replace ALL such +%% options in the config. +%% +%% Note that if 'spec' is given, common_test will ignore all 'dir', +%% 'suite', 'group' and 'case', so there is no need to explicitly +%% remove any options from the command line. +%% +%% All faulty combinations of options are also handled by +%% common_test and are not taken into account here. +merge_opts(CmdOpts0, CfgOpts0) -> + TestSelectOpts = [spec,dir,suite,group,testcase], + CmdOpts = lists:ukeysort(1, CmdOpts0), + CfgOpts1 = lists:ukeysort(1, CfgOpts0), + CfgOpts = case is_any_defined(TestSelectOpts,CmdOpts) of + false -> + CfgOpts1; + true -> + [Opt || Opt={K,_} <- CfgOpts1, + not lists:member(K,TestSelectOpts)] + end, + lists:ukeymerge(1, CmdOpts, CfgOpts). + +is_any_defined([Key|Keys],Opts) -> + proplists:is_defined(Key,Opts) orelse is_any_defined(Keys,Opts); +is_any_defined([],_Opts) -> + false. + sys_config_list(CmdOpts, CfgOpts) -> CmdSysConfigs = split_string(proplists:get_value(sys_config, CmdOpts, "")), case proplists:get_value(sys_config, CfgOpts, []) of @@ -253,11 +277,10 @@ sys_config_list(CmdOpts, CfgOpts) -> end. 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} + case is_any_defined([spec,dir,suite],Opts) of + %% no tests defined, try using `$APP/test` and `$ROOT/test` as dirs + false -> {ok, [default_tests(State, ProjectApps)|Opts]}; + true -> {ok, Opts} end. default_tests(State, ProjectApps) -> @@ -397,14 +420,29 @@ readable(State) -> end. 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} 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"} + case proplists:get_value(spec, Opts) of + undefined -> + 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} 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; + Specs0 -> + case get_dirs_from_specs(Specs0) of + {ok,{Specs,SuiteDirs}} -> + {State1,Apps1} = set_compile_dirs1(State, Apps, + {dir, SuiteDirs}), + {State2,Apps2} = set_compile_dirs1(State1, Apps1, + {spec, Specs}), + [maybe_copy_spec(State2,Apps2,S) || S <- Specs], + {ok, rebar_state:project_apps(State2, Apps2)}; + Error -> + Error + end end. join(Suite, Dir) when is_integer(hd(Suite)) -> @@ -412,27 +450,28 @@ join(Suite, Dir) when is_integer(hd(Suite)) -> 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)) -> +set_compile_dirs(State, Apps, What) -> + {NewState,NewApps} = set_compile_dirs1(State, Apps, What), + {ok, rebar_state:project_apps(NewState, NewApps)}. + +set_compile_dirs1(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}) -> + maybe_inject_test_dir(State, [], Apps, Dir); +set_compile_dirs1(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), + lists:foldl(F, {State, Apps}, Dirs); +set_compile_dirs1(State, Apps, {Type, Files}) when Type==spec; Type==suite -> + %% specs or suites with dir component + Dirs = find_file_dirs(Files), 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)}. + lists:foldl(F, {State, Apps}, Dirs). -find_suite_dirs(Suites) -> - AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites), +find_file_dirs(Files) -> + AllDirs = lists:map(fun(F) -> filename:dirname(filename:absname(F)) end, Files), %% eliminate duplicates lists:usort(AllDirs). @@ -480,52 +519,82 @@ copy_bare_suites(From, To) -> ok = rebar_file_utils:cp_r(SrcFiles, To), rebar_file_utils:cp_r(DataDirs, To). +maybe_copy_spec(State, [App|Apps], Spec) -> + case rebar_file_utils:path_from_ancestor(filename:dirname(Spec), rebar_app_info:dir(App)) of + {ok, []} -> + ok = rebar_file_utils:cp_r([Spec],rebar_app_info:out_dir(App)); + {ok,_} -> + ok; + {error,badparent} -> + maybe_copy_spec(State, Apps, Spec) + end; +maybe_copy_spec(State, [], Spec) -> + case rebar_file_utils:path_from_ancestor(filename:dirname(Spec), rebar_state:dir(State)) of + {ok, []} -> + ExtrasDir = filename:join([rebar_dir:base_dir(State), "extras"]), + ok = rebar_file_utils:cp_r([Spec],ExtrasDir); + _R -> + ok + 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]). +get_dirs_from_specs(Specs) -> + case get_tests_from_specs(Specs) of + {ok,Tests} -> + {SpecLists,NodeRunSkipLists} = lists:unzip(Tests), + SpecList = lists:append(SpecLists), + NodeRunSkipList = lists:append(NodeRunSkipLists), + RunList = lists:append([R || {_,R,_} <- NodeRunSkipList]), + DirList = [element(1,R) || R <- RunList], + {ok,{SpecList,DirList}}; + {error,Reason} -> + {error,{?MODULE,{error_reading_testspec,Reason}}} + end. + +get_tests_from_specs(Specs) -> + _ = ct_testspec:module_info(), % make sure ct_testspec is loaded + case erlang:function_exported(ct_testspec,get_tests,1) of + true -> + ct_testspec:get_tests(Specs); + false -> + case ct_testspec:collect_tests_from_file(Specs,true) of + Tests when is_list(Tests) -> + {ok,[{S,ct_testspec:prepare_tests(R)} || {S,R} <- Tests]}; + R when is_tuple(R), element(1,R)==testspec -> + %% R15 + {ok,[{Specs,ct_testspec:prepare_tests(R)}]}; + Error -> + Error + end + end. + 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, []) + case proplists:get_value(spec, Opts) of + undefined -> + case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of + {_Suites, undefined} -> translate_paths(State, suite, Opts, []); + {undefined, _Dirs} -> translate_paths(State, dir, Opts, []); + %% both dirs and suites are defined, only translate dir paths + _ -> translate_paths(State, dir, Opts, []) + end; + _Specs -> + translate_paths(State, spec, 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 +translate_paths(_State, _Type, [], Acc) -> lists:reverse(Acc); +translate_paths(State, Type, [{Type, Val}|Rest], Acc) when is_integer(hd(Val)) -> + %% single file or dir + translate_paths(State, Type, [{Type, [Val]}|Rest], Acc); +translate_paths(State, Type, [{Type, Files}|Rest], Acc) -> 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_suite(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_suite(State, Apps, Suite) end, Suites)}, - translate_suites(State, Rest, [NewSuites|Acc]); -translate_suites(State, [Test|Rest], Acc) -> - translate_suites(State, Rest, [Test|Acc]). - -translate_suite(State, Apps, Suite) -> - Dirname = filename:dirname(Suite), - Basename = filename:basename(Suite), - case Dirname of - "." -> Suite; - _ -> filename:join([translate(State, Apps, Dirname), Basename]) - end. + New = {Type, lists:map(fun(File) -> translate(State, Apps, File) end, Files)}, + translate_paths(State, Type, Rest, [New|Acc]); +translate_paths(State, Type, [Test|Rest], Acc) -> + translate_paths(State, Type, Rest, [Test|Acc]). translate(State, [App|Rest], Path) -> case rebar_file_utils:path_from_ancestor(Path, rebar_app_info:dir(App)) of diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 0908ec9..7d44137 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -18,7 +18,7 @@ %% we need to modify app_info state before compile -define(DEPS, [lock]). --define(DEFAULT_TEST_REGEX, "^[^._].*\\.erl\$"). +-define(DEFAULT_TEST_REGEX, "^(?!\\._).*\\.erl\$"). %% =================================================================== %% Public API @@ -195,17 +195,18 @@ gather_src([Dir|Rest], Regex, Srcs) -> gather_src(Rest, Regex, Srcs ++ rebar_utils:find_files(Dir, Regex, true)). dedupe_tests({AppMods, TestMods}) -> + UniqueTestMods = lists:usort(TestMods) -- AppMods, %% for each modules in TestMods create a test if there is not a module %% in AppMods that will trigger it - F = fun(Mod) -> - M = filename:basename(Mod, ".erl"), - MatchesTest = fun(Dir) -> filename:basename(Dir, ".erl") ++ "_tests" == M end, + F = fun(TestMod) -> + M = filename:rootname(filename:basename(TestMod)), + MatchesTest = fun(AppMod) -> filename:rootname(filename:basename(AppMod)) ++ "_tests" == M end, case lists:any(MatchesTest, AppMods) of false -> {true, {module, list_to_atom(M)}}; true -> false end end, - lists:usort(rebar_utils:filtermap(F, TestMods)). + rebar_utils:filtermap(F, UniqueTestMods). inject_eunit_state(State, {ok, Tests}) -> Apps = rebar_state:project_apps(State), diff --git a/src/rebar_prv_get_deps.erl b/src/rebar_prv_get_deps.erl new file mode 100644 index 0000000..020e50b --- /dev/null +++ b/src/rebar_prv_get_deps.erl @@ -0,0 +1,37 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_prv_get_deps). + +-behaviour(provider). + +-export([init/1, + do/1, + format_error/1]). + +-define(PROVIDER, 'get-deps'). +-define(DEPS, [lock]). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + Provider = providers:create([{name, ?PROVIDER}, + {module, ?MODULE}, + {deps, ?DEPS}, + {bare, true}, + {example, "rebar3 get-deps"}, + {short_desc, "Fetch dependencies."}, + {desc, "Fetch project dependencies."}, + {opts, []}, + {profiles, []}]), + {ok, rebar_state:add_provider(State, Provider)}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()}. +do(State) -> {ok, State}. + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]).
\ No newline at end of file diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index 9c5e8ac..c9fe0ad 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -120,9 +120,9 @@ format_error({missing_package, Package}) -> format_error({cycles, Cycles}) -> Prints = [["applications: ", [io_lib:format("~s ", [Dep]) || Dep <- Cycle], - "depend on each other~n"] + "depend on each other\n"] || Cycle <- Cycles], - ["Dependency cycle(s) detected:~n", Prints]; + ["Dependency cycle(s) detected:\n", Prints]; format_error(Reason) -> io_lib:format("~p", [Reason]). diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index 31b2e17..72efcf1 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -339,7 +339,7 @@ reread_config(State) -> boot_apps(Apps) -> ?WARN("The rebar3 shell is a development tool; to deploy " "applications in production, consider using releases " - "(http://www.rebar3.org/v3.0/docs/releases)", []), + "(http://www.rebar3.org/docs/releases)", []), Normalized = normalize_boot_apps(Apps), Res = [application:ensure_all_started(App) || App <- Normalized], _ = [?INFO("Booted ~p", [App]) @@ -352,11 +352,15 @@ boot_apps(Apps) -> normalize_load_apps([]) -> []; normalize_load_apps([{App, _} | T]) -> [App | normalize_load_apps(T)]; normalize_load_apps([{App, _Vsn, load} | T]) -> [App | normalize_load_apps(T)]; +normalize_load_apps([{App, _Vsn, Operator} | T]) when is_atom(Operator) -> + [App | normalize_load_apps(T)]; normalize_load_apps([App | T]) when is_atom(App) -> [App | normalize_load_apps(T)]. normalize_boot_apps([]) -> []; normalize_boot_apps([{_App, load} | T]) -> normalize_boot_apps(T); normalize_boot_apps([{_App, _Vsn, load} | T]) -> normalize_boot_apps(T); +normalize_boot_apps([{App, _Vsn, Operator} | T]) when is_atom(Operator) -> + [App | normalize_boot_apps(T)]; normalize_boot_apps([{App, _Vsn} | T]) -> [App | normalize_boot_apps(T)]; normalize_boot_apps([App | T]) when is_atom(App) -> [App | normalize_boot_apps(T)]. diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 299b957..e64ce71 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -33,7 +33,7 @@ -include("rebar.hrl"). --define(TEMPLATE_RE, "^[^._].*\\.template\$"). +-define(TEMPLATE_RE, "^(?!\\._).*\\.template\$"). %% =================================================================== %% Public API diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index d92f119..c357e94 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -70,7 +70,9 @@ info_useless/2, list_dir/1, user_agent/0, - reread_config/1]). + reread_config/1, + get_proxy_auth/0]). + %% for internal use only -export([otp_release/0]). @@ -838,8 +840,9 @@ set_httpc_options(_, []) -> ok; set_httpc_options(Scheme, Proxy) -> - {ok, {_, _, Host, Port, _, _}} = http_uri:parse(Proxy), - httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar). + {ok, {_, UserInfo, Host, Port, _, _}} = http_uri:parse(Proxy), + httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar), + set_proxy_auth(UserInfo). url_append_path(Url, ExtraPath) -> case http_uri:parse(Url) of @@ -878,3 +881,18 @@ list_dir(Dir) -> true -> file:list_dir_all(Dir); false -> file:list_dir(Dir) end. + +set_proxy_auth([]) -> + ok; +set_proxy_auth(UserInfo) -> + Idx = string:chr(UserInfo, $:), + Username = string:sub_string(UserInfo, 1, Idx-1), + Password = string:sub_string(UserInfo, Idx+1), + %% password may contain url encoded characters, need to decode them first + application:set_env(rebar, proxy_auth, [{proxy_auth, {Username, http_uri:decode(Password)}}]). + +get_proxy_auth() -> + case application:get_env(rebar, proxy_auth) of + undefined -> []; + {ok, ProxyAuth} -> ProxyAuth + end. diff --git a/test/rebar_ct_SUITE.erl b/test/rebar_ct_SUITE.erl index 8e989b5..06dc76e 100644 --- a/test/rebar_ct_SUITE.erl +++ b/test/rebar_ct_SUITE.erl @@ -51,7 +51,11 @@ cover_compiled/1, misspecified_ct_opts/1, misspecified_ct_compile_opts/1, - misspecified_ct_first_files/1]). + misspecified_ct_first_files/1, + testspec/1, + testspec_at_root/1, + testspec_parse_error/1, + cmd_vs_cfg_opts/1]). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -67,7 +71,11 @@ all() -> [{group, basic_app}, cfg_atom_suites, misspecified_ct_opts, misspecified_ct_compile_opts, - misspecified_ct_first_files]. + misspecified_ct_first_files, + testspec, + testspec_at_root, + testspec_parse_error, + cmd_vs_cfg_opts]. groups() -> [{basic_app, [], [basic_app_default_dirs, basic_app_default_beams, @@ -689,7 +697,33 @@ suite_at_root(Config) -> true = filelib:is_dir(DataDir), DataFile = filename:join([AppDir, "_build", "test", "extras", "root_SUITE_data", "some_data.txt"]), - true = filelib:is_file(DataFile). + true = filelib:is_file(DataFile), + + %% Same test again, but using relative path to the suite from the + %% project root + {ok,Cwd} = file:get_cwd(), + ok = file:set_cwd(AppDir), + rebar_file_utils:rm_rf("_build"), + + {ok, GetOptResult2} = getopt:parse(GetOptSpec, ["--suite=" ++ "root_SUITE"]), + + State3 = rebar_state:command_parsed_args(State1, GetOptResult2), + + Tests2 = rebar_prv_common_test:prepare_tests(State3), + {ok, NewState2} = rebar_prv_common_test:compile(State3, Tests2), + {ok, T2} = Tests2, + Opts2 = rebar_prv_common_test:translate_paths(NewState2, T2), + + ok = file:set_cwd(Cwd), + + Suite2 = proplists:get_value(suite, Opts2), + [Expected] = Suite2, + true = filelib:is_file(TestHrl), + true = filelib:is_file(TestBeam), + true = filelib:is_dir(DataDir), + true = filelib:is_file(DataFile), + + ok. suite_at_app_root(Config) -> AppDir = ?config(apps, Config), @@ -726,7 +760,32 @@ suite_at_app_root(Config) -> true = filelib:is_dir(DataDir), DataFile = filename:join([AppDir, "_build", "test", "lib", Name2, "app_root_SUITE_data", "some_data.txt"]), - true = filelib:is_file(DataFile). + true = filelib:is_file(DataFile), + + %% Same test again using relative path to the suite from the project root + {ok,Cwd} = file:get_cwd(), + ok = file:set_cwd(AppDir), + rebar_file_utils:rm_rf("_build"), + + {ok, GetOptResult2} = getopt:parse(GetOptSpec, ["--suite=" ++ filename:join(["apps", Name2, "app_root_SUITE"])]), + + State3 = rebar_state:command_parsed_args(State1, GetOptResult2), + + Tests2 = rebar_prv_common_test:prepare_tests(State3), + {ok, NewState2} = rebar_prv_common_test:compile(State3, Tests2), + {ok, T2} = Tests2, + Opts2 = rebar_prv_common_test:translate_paths(NewState2, T2), + + ok = file:set_cwd(Cwd), + + Suite2 = proplists:get_value(suite, Opts2), + [Expected] = Suite2, + true = filelib:is_file(TestHrl), + true = filelib:is_file(TestBeam), + true = filelib:is_dir(DataDir), + true = filelib:is_file(DataFile), + + ok. %% this test probably only fails when this suite is run via rebar3 with the --cover flag data_dir_correct(Config) -> @@ -1234,6 +1293,261 @@ misspecified_ct_first_files(Config) -> {badconfig, {"Value `~p' of option `~p' must be a list", {some_file, ct_first_files}}} = Error. +testspec(Config) -> + C = rebar_test_utils:init_rebar_state(Config, "ct_testspec_"), + + AppDir = ?config(apps, C), + + Name = rebar_test_utils:create_random_name("ct_testspec_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + Spec1 = filename:join([AppDir, "test", "some.spec"]), + ok = filelib:ensure_dir(Spec1), + ok = file:write_file(Spec1, "{suites,\".\",all}.\n"), + Spec2 = filename:join([AppDir, "specs", "another.spec"]), + ok = filelib:ensure_dir(Spec2), + Suites2 = filename:join([AppDir,"suites","*"]), + ok = filelib:ensure_dir(Suites2), + ok = file:write_file(Spec2, "{suites,\"../suites/\",all}.\n"), + + {ok,Wd} = file:get_cwd(), + ok = file:set_cwd(AppDir), + + {ok, State} = rebar_test_utils:run_and_check(C, + [], + ["as", "test", "lock"], + return), + + Providers = rebar_state:providers(State), + Namespace = rebar_state:namespace(State), + CommandProvider = providers:get_provider(ct, Providers, Namespace), + GetOptSpec = providers:opts(CommandProvider), + + %% Testspec in "test" directory + {ok, GetOptResult1} = getopt:parse(GetOptSpec, ["--spec","test/some.spec"]), + State1 = rebar_state:command_parsed_args(State, GetOptResult1), + Tests1 = rebar_prv_common_test:prepare_tests(State1), + {ok, NewState1} = rebar_prv_common_test:compile(State1, Tests1), + {ok, T1} = Tests1, + Opts1= rebar_prv_common_test:translate_paths(NewState1, T1), + + %% check that extra src dir is added + [App1] = rebar_state:project_apps(NewState1), + ["test"] = rebar_dir:extra_src_dirs(rebar_app_info:opts(App1)), + + %% check that path is translated + ExpectedSpec1 = filename:join([AppDir, "_build", "test", "lib", Name, + "test", "some.spec"]), + [ExpectedSpec1] = proplists:get_value(spec, Opts1), + + + %% Testspec in directory other than "test" + {ok, GetOptResult2} = getopt:parse(GetOptSpec, + ["--spec","specs/another.spec"]), + State2 = rebar_state:command_parsed_args(State, GetOptResult2), + Tests2 = {ok, T2} =rebar_prv_common_test:prepare_tests(State2), + {ok, NewState2} = rebar_prv_common_test:compile(State2, Tests2), + Opts2= rebar_prv_common_test:translate_paths(NewState2, T2), + + %% check that extra src dirs are added + [App2] = rebar_state:project_apps(NewState2), + ["specs","suites","test"] = + lists:sort(rebar_dir:extra_src_dirs(rebar_app_info:opts(App2))), + + %% check that paths are translated + ExpectedSpec2 = filename:join([AppDir, "_build", "test", "lib", Name, + "specs", "another.spec"]), + [ExpectedSpec2] = proplists:get_value(spec, Opts2), + + ok = file:set_cwd(Wd), + + ok. + +testspec_at_root(Config) -> + C = rebar_test_utils:init_rebar_state(Config, "ct_testspec_at_root_"), + + AppDir = ?config(apps, C), + + Name = rebar_test_utils:create_random_name("ct_testspec_at_root_"), + Vsn = rebar_test_utils:create_random_vsn(), + AppDir1 = filename:join([AppDir, "apps", Name]), + rebar_test_utils:create_app(AppDir1, Name, Vsn, [kernel, stdlib]), + + Spec1 = filename:join([AppDir, "root.spec"]), + ok = filelib:ensure_dir(Spec1), + ok = file:write_file(Spec1, "{suites,\"test\",all}."), + Spec2 = filename:join([AppDir, "root1.spec"]), + ok = file:write_file(Spec2, "{suites,\".\",all}."), + Spec3 = filename:join([AppDir, "root2.spec"]), + ok = file:write_file(Spec3, "{suites,\"suites\",all}."), + Suite1 = filename:join(AppDir,"root_SUITE.erl"), + ok = file:write_file(Suite1, test_suite("root")), + Suite2 = filename:join([AppDir,"suites","test_SUITE.erl"]), + ok = filelib:ensure_dir(Suite2), + ok = file:write_file(Suite2, test_suite("test")), + + {ok, State} = rebar_test_utils:run_and_check(C, + [], + ["as", "test", "lock"], + return), + + Providers = rebar_state:providers(State), + Namespace = rebar_state:namespace(State), + CommandProvider = providers:get_provider(ct, Providers, Namespace), + GetOptSpec = providers:opts(CommandProvider), + + SpecArg1 = string:join([Spec1,Spec2,Spec3],","), + {ok, GetOptResult1} = getopt:parse(GetOptSpec, ["--spec",SpecArg1]), + State1 = rebar_state:command_parsed_args(State, GetOptResult1), + Tests1 = rebar_prv_common_test:prepare_tests(State1), + {ok, NewState1} = rebar_prv_common_test:compile(State1, Tests1), + {ok, T1} = Tests1, + Opts1= rebar_prv_common_test:translate_paths(NewState1, T1), + + %% check that extra src dir is added + ExtraDir = filename:join([AppDir, "_build", "test", "extras"]), + [ExtraDir,"suites","test"] = + rebar_dir:extra_src_dirs(rebar_state:opts(NewState1)), + + %% check that path is translated + ExpectedSpec1 = filename:join([AppDir, "_build", "test", + "extras", "root.spec"]), + ExpectedSpec2 = filename:join([AppDir, "_build", "test", + "extras", "root1.spec"]), + ExpectedSpec3 = filename:join([AppDir, "_build", "test", + "extras", "root2.spec"]), + [ExpectedSpec1,ExpectedSpec2,ExpectedSpec3] = + lists:sort(proplists:get_value(spec, Opts1)), + + %% check that test specs are copied + [ExpectedSpec1,ExpectedSpec2,ExpectedSpec3] = + lists:sort(filelib:wildcard(filename:join([AppDir, "_build", "test", + "extras", "*.spec"]))), + + %% Same test again, using relative path + {ok,Cwd} = file:get_cwd(), + ok = file:set_cwd(AppDir), + ok = rebar_file_utils:rm_rf("_build"), + + SpecArg2 = "root.spec,root1.spec,root2.spec", + {ok, GetOptResult2} = getopt:parse(GetOptSpec, ["--spec",SpecArg2]), + State2 = rebar_state:command_parsed_args(State, GetOptResult2), + Tests2 = rebar_prv_common_test:prepare_tests(State2), + {ok, NewState2} = rebar_prv_common_test:compile(State2, Tests2), + {ok, T2} = Tests2, + Opts2= rebar_prv_common_test:translate_paths(NewState2, T2), + + %% check that extra src dir is added + [ExtraDir,"suites","test"] = + rebar_dir:extra_src_dirs(rebar_state:opts(NewState2)), + + %% check that path is translated + [ExpectedSpec1,ExpectedSpec2,ExpectedSpec3] = + lists:sort(proplists:get_value(spec, Opts2)), + + %% check that test specs are copied + [ExpectedSpec1,ExpectedSpec2,ExpectedSpec3] = + lists:sort(filelib:wildcard(filename:join([AppDir, "_build", "test", + "extras", "root*.spec"]))), + + ok = file:set_cwd(Cwd), + + ok. + +testspec_parse_error(Config) -> + C = rebar_test_utils:init_rebar_state(Config, "ct_testspec_error"), + + AppDir = ?config(apps, C), + + Name = rebar_test_utils:create_random_name("ct_testspec_error"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + Spec1 = filename:join([AppDir, "test", "nonexisting.spec"]), + Spec2 = filename:join([AppDir, "test", "some.spec"]), + ok = filelib:ensure_dir(Spec2), + ok = file:write_file(Spec2, ".\n"), + + {ok, State} = rebar_test_utils:run_and_check(C, + [], + ["as", "test", "lock"], + return), + + Providers = rebar_state:providers(State), + Namespace = rebar_state:namespace(State), + CommandProvider = providers:get_provider(ct, Providers, Namespace), + GetOptSpec = providers:opts(CommandProvider), + + %% Non existing testspec + {ok, GetOptResult1} = getopt:parse(GetOptSpec, ["--spec",Spec1]), + State1 = rebar_state:command_parsed_args(State, GetOptResult1), + Tests1 = rebar_prv_common_test:prepare_tests(State1), + {error, + {rebar_prv_common_test, + {error_reading_testspec, + {Spec1,"no such file or directory"}}}} = + rebar_prv_common_test:compile(State1, Tests1), + + %% Syntax error + {ok, GetOptResult2} = getopt:parse(GetOptSpec, ["--spec",Spec2]), + State2 = rebar_state:command_parsed_args(State, GetOptResult2), + Tests2 = rebar_prv_common_test:prepare_tests(State2), + {error, + {rebar_prv_common_test, + {error_reading_testspec, + {Spec2,"1: syntax error before: '.'"}}}} = + rebar_prv_common_test:compile(State2, Tests2), + + ok. + +cmd_vs_cfg_opts(Config) -> + C = rebar_test_utils:init_rebar_state(Config, "ct_cmd_vs_cfg_opts_"), + + AppDir = ?config(apps, C), + + Name = rebar_test_utils:create_random_name("ct_cmd_vs_cfg_opts_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + RebarConfig = [{ct_opts, [{spec,"mytest.spec"}, + {dir,"test"}, + {suite,"some_SUITE"}, + {group,"some_group"}, + {testcase,"some_test"}]}], + + {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return), + + {ok, TestOpts} = rebar_prv_common_test:prepare_tests(State), + true = lists:member({spec, "mytest.spec"}, TestOpts), + true = lists:member({dir, "test"}, TestOpts), + true = lists:member({suite, "some_SUITE"}, TestOpts), + true = lists:member({group, "some_group"}, TestOpts), + true = lists:member({testcase, "some_test"}, TestOpts), + + Providers = rebar_state:providers(State), + Namespace = rebar_state:namespace(State), + CommandProvider = providers:get_provider(ct, Providers, Namespace), + GetOptSpec = providers:opts(CommandProvider), + + {ok, GetOptResult1} = getopt:parse(GetOptSpec, ["--spec","test/some.spec"]), + State1 = rebar_state:command_parsed_args(State, GetOptResult1), + {ok, TestOpts1} = rebar_prv_common_test:prepare_tests(State1), + true = lists:member({spec, ["test/some.spec"]}, TestOpts1), + false = lists:keymember(dir, 1, TestOpts1), + false = lists:keymember(suite, 1, TestOpts1), + false = lists:keymember(group, 1, TestOpts1), + false = lists:keymember(testcase, 1, TestOpts1), + + {ok, GetOptResult2} = getopt:parse(GetOptSpec, ["--suite","test/some_SUITE"]), + State2 = rebar_state:command_parsed_args(State, GetOptResult2), + {ok, TestOpts2} = rebar_prv_common_test:prepare_tests(State2), + true = lists:member({suite, ["test/some_SUITE"]}, TestOpts2), + false = lists:keymember(spec, 1, TestOpts2), + false = lists:keymember(dir, 1, TestOpts2), + false = lists:keymember(group, 1, TestOpts2), + false = lists:keymember(testcase, 1, TestOpts2), + + ok. + %% helper for generating test data test_suite(Name) -> io_lib:format("-module(~ts_SUITE).\n" diff --git a/test/rebar_profiles_SUITE.erl b/test/rebar_profiles_SUITE.erl index a31a4c9..ed492a9 100644 --- a/test/rebar_profiles_SUITE.erl +++ b/test/rebar_profiles_SUITE.erl @@ -20,7 +20,12 @@ test_profile_applied_at_completion/1, test_profile_applied_before_compile/1, test_profile_applied_before_eunit/1, - test_profile_applied_to_apps/1]). + test_profile_applied_to_apps/1, + test_profile_erl_opts_order_1/1, + test_profile_erl_opts_order_2/1, + test_profile_erl_opts_order_3/1, + test_profile_erl_opts_order_4/1, + test_profile_erl_opts_order_5/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -36,7 +41,12 @@ all() -> test_profile_applied_at_completion, test_profile_applied_before_compile, test_profile_applied_before_eunit, - test_profile_applied_to_apps]. + test_profile_applied_to_apps, + test_profile_erl_opts_order_1, + test_profile_erl_opts_order_2, + test_profile_erl_opts_order_3, + test_profile_erl_opts_order_4, + test_profile_erl_opts_order_5]. init_per_suite(Config) -> application:start(meck), @@ -432,3 +442,93 @@ test_profile_applied_to_apps(Config) -> ErlOpts = dict:fetch(erl_opts, Opts), true = lists:member({d, 'TEST'}, ErlOpts) end, Apps). + +test_profile_erl_opts_order_1(Config) -> + Opts = get_compiled_profile_erl_opts([default], Config), + Opt = last_erl_opt(Opts, [warn_export_all, nowarn_export_all], undefined), + undefined = Opt. + +test_profile_erl_opts_order_2(Config) -> + Opts = get_compiled_profile_erl_opts([strict], Config), + Opt = last_erl_opt(Opts, [warn_export_all, nowarn_export_all], undefined), + warn_export_all = Opt. + +test_profile_erl_opts_order_3(Config) -> + Opts = get_compiled_profile_erl_opts([loose], Config), + Opt = last_erl_opt(Opts, [warn_export_all, nowarn_export_all], undefined), + nowarn_export_all = Opt. + +test_profile_erl_opts_order_4(Config) -> + Opts = get_compiled_profile_erl_opts([strict, loose], Config), + Opt = last_erl_opt(Opts, [warn_export_all, nowarn_export_all], undefined), + nowarn_export_all = Opt. + +test_profile_erl_opts_order_5(Config) -> + Opts = get_compiled_profile_erl_opts([loose, strict], Config), + Opt = last_erl_opt(Opts, [warn_export_all, nowarn_export_all], undefined), + warn_export_all = Opt. + +get_compiled_profile_erl_opts(Profiles, Config) -> + AppDir = ?config(apps, Config), + PStrs = [atom_to_list(P) || P <- Profiles], + + Name = rebar_test_utils:create_random_name( + lists:flatten(["erl_opts_order_" | [[S, $_] || S <- PStrs]])), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + RebarConfig = [ + {erl_opts, [warnings_as_errors, {d, profile_default}]}, + {profiles, [ + {strict, [{erl_opts, [warn_export_all, {d, profile_strict}]}]}, + {loose, [{erl_opts, [nowarn_export_all, {d, profile_loose}]}]} ]}], + rebar_test_utils:create_config(AppDir, RebarConfig), + + Command = case Profiles of + [] -> + ["compile"]; + [default] -> + ["compile"]; + _ -> + ["as", string:join(PStrs, ","), "compile"] + end, + {ok, State} = rebar_test_utils:run_and_check( + Config, RebarConfig, Command, {ok, [{app, Name}]}), + code:add_paths(rebar_state:code_paths(State, all_deps)), + Mod = list_to_atom(Name), + proplists:get_value(options, Mod:module_info(compile), []). + +% macro definitions get special handling +last_erl_opt([{d, Macro} = Opt | Opts], Targets, Last) -> + case lists:any(erl_opt_macro_match_fun(Macro), Targets) of + true -> + last_erl_opt(Opts, Targets, Opt); + _ -> + last_erl_opt(Opts, Targets, Last) + end; +last_erl_opt([{d, Macro, _} = Opt | Opts], Targets, Last) -> + case lists:any(erl_opt_macro_match_fun(Macro), Targets) of + true -> + last_erl_opt(Opts, Targets, Opt); + _ -> + last_erl_opt(Opts, Targets, Last) + end; +last_erl_opt([Opt | Opts], Targets, Last) -> + case lists:member(Opt, Targets) of + true -> + last_erl_opt(Opts, Targets, Opt); + _ -> + last_erl_opt(Opts, Targets, Last) + end; +last_erl_opt([], _, Last) -> + Last. + +erl_opt_macro_match_fun(Macro) -> + fun({d, M}) -> + M == Macro; + ({d, M, _}) -> + M == Macro; + (_) -> + false + end. + diff --git a/test/rebar_utils_SUITE.erl b/test/rebar_utils_SUITE.erl index b32992d..8b8769b 100644 --- a/test/rebar_utils_SUITE.erl +++ b/test/rebar_utils_SUITE.erl @@ -31,7 +31,8 @@ nonblacklisted_otp_version/1, blacklisted_otp_version/1, sh_does_not_miss_messages/1, - tup_merge/1]). + tup_merge/1, + proxy_auth/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -46,7 +47,8 @@ end_per_testcase(_, _Config) -> all() -> [{group, args_to_tasks}, sh_does_not_miss_messages, - tup_merge]. + tup_merge, + proxy_auth]. groups() -> [{args_to_tasks, [], [empty_arglist, @@ -272,3 +274,39 @@ tup_merge(_Config) -> rebar_utils:tup_sort([{a,a},{a,a,a},a,{b,a,a},b,{z,a},{z,a,a},{b,a},z]) ) ). + +proxy_auth(_Config) -> + proxy_auth(_Config, "http_proxy"), + proxy_auth(_Config, "https_proxy"). + +proxy_auth(_Config, ProxyEnvKey) -> + Host = "host:", + Port = "1234", + + %% remember current proxy specification + OldProxySpec = os:getenv(ProxyEnvKey), + + %% proxy auth not set + application:unset_env(rebar, proxy_auth), + ?assertEqual([], rebar_utils:get_proxy_auth()), + + %% proxy auth with regular username/password + os:putenv(ProxyEnvKey, "http://Username:Password@" ++ Host ++ Port), + rebar_utils:set_httpc_options(), + ?assertEqual([{proxy_auth, {"Username", "Password"}}], + rebar_utils:get_proxy_auth()), + + %% proxy auth with username missing and url encoded password + os:putenv(ProxyEnvKey, "http://:%3F!abc%23%24@" ++ Host ++ Port), + rebar_utils:set_httpc_options(), + ?assertEqual([{proxy_auth, {"", "?!abc#$"}}], + rebar_utils:get_proxy_auth()), + + %% restore original proxy specification if any + restore_proxy_env(ProxyEnvKey, OldProxySpec), + application:unset_env(rebar, proxy_auth). + +restore_proxy_env(ProxyEnvKey, false) -> + os:putenv(ProxyEnvKey, ""); +restore_proxy_env(ProxyEnvKey, ProxySpec) -> + os:putenv(ProxyEnvKey, ProxySpec). |