diff options
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | CONTRIBUTING.md | 31 | ||||
-rw-r--r-- | THANKS | 1 | ||||
-rwxr-xr-x | bootstrap | 16 | ||||
-rw-r--r-- | dialyzer_reference | 2 | ||||
-rw-r--r-- | rebar.config | 6 | ||||
-rw-r--r-- | rebar.config.sample | 5 | ||||
-rw-r--r-- | src/rebar_config.erl | 16 | ||||
-rw-r--r-- | src/rebar_core.erl | 3 | ||||
-rw-r--r-- | src/rebar_deps.erl | 49 | ||||
-rw-r--r-- | src/rebar_erlc_compiler.erl | 45 | ||||
-rw-r--r-- | src/rebar_eunit.erl | 171 | ||||
-rw-r--r-- | src/rebar_utils.erl | 40 | ||||
-rw-r--r-- | test/rebar_eunit_tests.erl | 40 |
14 files changed, 326 insertions, 100 deletions
diff --git a/.travis.yml b/.travis.yml index a7eedb4..602266b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,5 @@ otp_release: - R15B - R14B04 - R14B03 + - 17.0 script: "make travis" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30693d8..e0de0eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,37 @@ Do not commit to master in your fork. Provide a clean branch without merge commits. +Tests +----- + +As a general rule, any behavioral change to rebar requires a test to go with it. If there's +already a test case, you may have to modify that one. If there isn't a test case or a test +suite, add a new test case or suite in `inttest/`. [retest](https://github.com/dizzyd/retest) based tests are preferred, but +we also have EUnit tests in `test/`. + +Say you've added a new test case in `inttest/erlc`. To only execute the modified suite, +you would do the following: +```sh +# First we build rebar and its deps to also get `deps/retest/retest` +$ make debug deps +# Now we can test the modified erlc suite +$ deps/retest/retest -v inttest/erlc +``` + +To test EUnit tests, you would do: +```sh +$ make debug +$ ./rebar -v eunit +``` + +You can also run `make test` to execute both EUnit and [retest](https://github.com/dizzyd/retest) tests as `make check` does. + +Credit +------ + +To give everyone proper credit in addition to the git history, please feel free to append +your name to `THANKS` in your first contribution. + Committing your changes ----------------------- @@ -125,3 +125,4 @@ YeJun Su Yuki Ito alisdair sullivan Alexander Verbitsky +Pierre Fenoll @@ -28,10 +28,20 @@ main(Args) -> %% Extract the system info of the version of OTP we use to compile rebar OtpInfo = string:strip(erlang:system_info(otp_release), both, $\n), + %% Types dict:dict() and digraph:digraph() have been introduced in Erlang 17. + %% At the same time, their counterparts dict() and digraph() are to be deprecated + %% in Erlang 18. namespaced_types option is used to select proper type name + %% depending of the OTP version used. + NamespacedTypes = case is_otp(OtpInfo, "^[0-9]+") of + true -> {d, namespaced_types}; + false -> undefined + end, + %% Compile all src/*.erl to ebin case make:files(filelib:wildcard("src/*.erl"), [{outdir, "ebin"}, {i, "include"}, DebugFlag, + NamespacedTypes, {d, 'BUILD_TIME', Built}, {d, 'VCS_INFO', VcsInfo}, {d, 'OTP_INFO', OtpInfo}]) of @@ -79,6 +89,12 @@ main(Args) -> "Place this script anywhere in your path\n" "and you can use rebar to build OTP-compliant apps.\n"). +is_otp(OtpInfo, Regex) -> + case re:run(OtpInfo, Regex, [{capture, none}]) of + match -> true; + nomatch -> false + end. + rm(Path) -> NativePath = filename:nativename(Path), Cmd = case os:type() of diff --git a/dialyzer_reference b/dialyzer_reference index 7fbe609..88909a6 100644 --- a/dialyzer_reference +++ b/dialyzer_reference @@ -1,3 +1,3 @@ -rebar_eunit.erl:434: Call to missing or unexported function eunit_test:function_wrapper/2 +rebar_eunit.erl:469: Call to missing or unexported function eunit_test:function_wrapper/2 rebar_utils.erl:164: Call to missing or unexported function escript:foldl/3 diff --git a/rebar.config b/rebar.config index 9028737..bf4ef4f 100644 --- a/rebar.config +++ b/rebar.config @@ -4,7 +4,11 @@ %% escript_incl_extra is for internal rebar-private use only. %% Do not use outside rebar. Config interface is not stable. {escript_incl_extra, [{"priv/templates/*", "."}]}. -{erl_opts, [warnings_as_errors]}. +%% Types dict:dict() and digraph:digraph() have been introduced in Erlang 17. +%% At the same time, their counterparts dict() and digraph() are to be deprecated +%% in Erlang 18. namespaced_types option is used to select proper type name +%% depending of the OTP version used. +{erl_opts, [{platform_define, "^[0-9]+", namespaced_types}, warnings_as_errors]}. {xref_checks, []}. {xref_queries, [{"(XC - UC) || (XU - X - B diff --git a/rebar.config.sample b/rebar.config.sample index 515ed00..47812c1 100644 --- a/rebar.config.sample +++ b/rebar.config.sample @@ -155,7 +155,7 @@ %% name as an atom, eg. mochiweb, a name and a version (from the .app file), or %% an application name, a version and the SCM details on how to fetch it (SCM %% type, location and revision). -%% Rebar currently supports git, hg, bzr, svn, rsync, and fossil. +%% Rebar currently supports git, hg, bzr, svn, rsync, fossil, and p4. {deps, [app_name, {rebar, "1.0.*"}, {rebar, ".*", @@ -188,7 +188,8 @@ {app_name, ".*", {svn, "svn://svn.example.org/url"}}, {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}}, {app_name, ".*", {fossil, "https://www.example.org/url"}}, - {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}]}. + {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, + {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]}. %% == Subdirectories == diff --git a/src/rebar_config.erl b/src/rebar_config.erl index 10c6483..1c90d22 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -39,13 +39,21 @@ -include("rebar.hrl"). +-ifdef(namespaced_types). +% dict:dict() exists starting from Erlang 17. +-type rebar_dict() :: dict:dict(). +-else. +% dict() has been obsoleted in Erlang 17 and deprecated in 18. +-type rebar_dict() :: dict(). +-endif. + -record(config, { dir :: file:filename(), opts = [] :: list(), - globals = new_globals() :: dict(), - envs = new_env() :: dict(), + globals = new_globals() :: rebar_dict(), + envs = new_env() :: rebar_dict(), %% cross-directory/-command config - skip_dirs = new_skip_dirs() :: dict(), - xconf = new_xconf() :: dict() }). + skip_dirs = new_skip_dirs() :: rebar_dict(), + xconf = new_xconf() :: rebar_dict() }). -export_type([config/0]). diff --git a/src/rebar_core.erl b/src/rebar_core.erl index 3a4f205..212365b 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -496,8 +496,9 @@ run_modules([Module | Rest], Command, Config, File) -> {Module, Error} end. -apply_hooks(Mode, Config, Command, Env) -> +apply_hooks(Mode, Config, Command, Env0) -> Hooks = rebar_config:get_local(Config, Mode, []), + Env = rebar_utils:patch_env(Config, Env0), lists:foreach(fun apply_hook/1, [{Env, Hook} || Hook <- Hooks, element(1, Hook) =:= Command orelse diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl index 68fb40c..bd94921 100644 --- a/src/rebar_deps.erl +++ b/src/rebar_deps.erl @@ -278,7 +278,8 @@ info_help(Description) -> {app_name, ".*", {svn, "svn://svn.example.org/url"}}, {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}}, {app_name, ".*", {fossil, "https://www.example.org/url"}}, - {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}]} + {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, + {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]} ]). %% Added because of trans deps, @@ -508,6 +509,40 @@ use_source(Config, Dep, Count) -> use_source(Config, Dep#dep { dir = TargetDir }, Count-1) end. +-record(p4_settings, { + client=undefined, + transport="tcp4:perforce:1666", + username, + password + }). +init_p4_settings(Basename) -> + #p4_settings{client = + case inet:gethostname() of + {ok,HostName} -> + HostName ++ "-" + ++ os:getenv("USER") ++ "-" + ++ Basename + ++ "-Rebar-automated-download" + end}. + +download_source(AppDir, {p4, Url}) -> + download_source(AppDir, {p4, Url, "#head"}); +download_source(AppDir, {p4, Url, Rev}) -> + download_source(AppDir, {p4, Url, Rev, init_p4_settings(filename:basename(AppDir))}); +download_source(AppDir, {p4, Url, _Rev, Settings}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh_send("p4 client -i", + ?FMT("Client: ~s~n" + ++"Description: generated by Rebar~n" + ++"Root: ~s~n" + ++"View:~n" + ++" ~s/... //~s/...~n", + [Settings#p4_settings.client, + AppDir, + Url, + Settings#p4_settings.client]), + []), + rebar_utils:sh(?FMT("p4 -c ~s sync -f", [Settings#p4_settings.client]), []); download_source(AppDir, {hg, Url, Rev}) -> ok = filelib:ensure_dir(AppDir), rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]), @@ -574,6 +609,8 @@ update_source(Config, Dep) -> Dep end. +update_source1(AppDir, Args) when element(1, Args) =:= p4 -> + download_source(AppDir, Args); update_source1(AppDir, {git, Url}) -> update_source1(AppDir, {git, Url, {branch, "HEAD"}}); update_source1(AppDir, {git, Url, ""}) -> @@ -697,7 +734,7 @@ source_engine_avail(Source) -> source_engine_avail(Name, Source) when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync; - Name == fossil -> + Name == fossil; Name == p4 -> case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of true -> true; @@ -718,6 +755,7 @@ vcs_client_vsn(Path, VsnArg, VsnRegex) -> false end. +required_vcs_client_vsn(p4) -> {2013, 1}; required_vcs_client_vsn(hg) -> {1, 1}; required_vcs_client_vsn(git) -> {1, 5}; required_vcs_client_vsn(bzr) -> {2, 0}; @@ -725,6 +763,9 @@ required_vcs_client_vsn(svn) -> {1, 6}; required_vcs_client_vsn(rsync) -> {2, 0}; required_vcs_client_vsn(fossil) -> {1, 0}. +vcs_client_vsn(p4) -> + vcs_client_vsn(rebar_utils:find_executable("p4"), " -V", + "Rev\\. .*/(\\d+)\\.(\\d)/"); vcs_client_vsn(hg) -> vcs_client_vsn(rebar_utils:find_executable("hg"), " --version", "version (\\d+).(\\d+)"); @@ -744,6 +785,8 @@ vcs_client_vsn(fossil) -> vcs_client_vsn(rebar_utils:find_executable("fossil"), " version", "version (\\d+).(\\d+)"). +has_vcs_dir(p4, _) -> + true; has_vcs_dir(git, Dir) -> filelib:is_dir(filename:join(Dir, ".git")); has_vcs_dir(hg, Dir) -> @@ -761,6 +804,8 @@ has_vcs_dir(_, _) -> print_source(#dep{app=App, source=Source}) -> ?CONSOLE("~s~n", [format_source(App, Source)]). +format_source(App, {p4, Url}) -> + format_source(App, {p4, Url, "#head"}); format_source(App, {git, Url}) -> ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); format_source(App, {git, Url, ""}) -> diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index b797137..16a7a35 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -47,6 +47,14 @@ info = {[], []} :: erlc_info() }). +-ifdef(namespaced_types). +% digraph:digraph() exists starting from Erlang 17. +-type rebar_digraph() :: digraph:digraph(). +-else. +% digraph() has been obsoleted in Erlang 17 and deprecated in 18. +-type rebar_digraph() :: digraph(). +-endif. + %% =================================================================== %% Public API %% =================================================================== @@ -111,8 +119,7 @@ clean(Config, _AppFile) -> YrlFiles = rebar_utils:find_files("src", "^.*\\.[x|y]rl\$"), rebar_file_utils:delete_each( - [ binary_to_list(iolist_to_binary(re:replace(F, "\\.[x|y]rl$", ".erl"))) - || F <- YrlFiles ]), + [re:replace(F, "\\.[xy]rl$", ".erl", [{return,list}]) || F <- YrlFiles]), %% Delete the build graph, if any rebar_file_utils:rm_rf(erlcinfo_file(Config)), @@ -141,11 +148,9 @@ test_compile(Config, Cmd, OutDir) -> %% in src but in a subdirectory of src. Cover only looks in cwd and ../src %% for source files. Also copy files from src_dirs. SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts1)), - SrcErls = lists:foldl( - fun(Dir, Acc) -> - Files = rebar_utils:find_files(Dir, ".*\\.erl\$"), - lists:append(Acc, Files) - end, [], SrcDirs), + SrcErls = lists:flatmap( + fun (Dir) -> rebar_utils:find_files(Dir, ".*\\.erl$") end, + SrcDirs), %% If it is not the first time rebar eunit or rebar qc is executed, %% there will be source files already present in OutDir. Since some @@ -156,17 +161,17 @@ test_compile(Config, Cmd, OutDir) -> %% rebar_file_utils:cp_r. %% Get the full path to a file that was previously copied in OutDir - ToCleanUp = fun(F, Acc) -> + ToCleanUp = fun(F) -> F2 = filename:basename(F), F3 = filename:join([OutDir, F2]), case filelib:is_regular(F3) of - true -> [F3|Acc]; - false -> Acc + true -> F3; + false -> [] end end, - ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], TestErls)), - ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], SrcErls)), + ok = rebar_file_utils:delete_each(lists:flatmap(ToCleanUp, TestErls)), + ok = rebar_file_utils:delete_each(lists:flatmap(ToCleanUp, SrcErls)), ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, OutDir), @@ -409,17 +414,17 @@ init_erlcinfo(Config, Erls) -> update_erlcinfo(G, Source, Dirs) -> case digraph:vertex(G, Source) of {_, LastUpdated} -> - LastModified = filelib:last_modified(Source), - if LastModified == 0 -> + case filelib:last_modified(Source) of + 0 -> %% The file doesn't exist anymore, %% erase it from the graph. %% All the edges will be erased automatically. digraph:del_vertex(G, Source), modified; - LastUpdated < LastModified -> - modify_erlcinfo(G, Source, Dirs); + LastModified when LastUpdated < LastModified -> + modify_erlcinfo(G, Source, Dirs), modified; - true -> + _ -> unmodified end; false -> @@ -522,19 +527,19 @@ expand_file_names(Files, Dirs) -> end end, Files). --spec get_parents(digraph(), file:filename()) -> [file:filename()]. +-spec get_parents(rebar_digraph(), file:filename()) -> [file:filename()]. get_parents(G, Source) -> %% Return all files which the Source depends upon. digraph_utils:reachable_neighbours([Source], G). --spec get_children(digraph(), file:filename()) -> [file:filename()]. +-spec get_children(rebar_digraph(), file:filename()) -> [file:filename()]. get_children(G, Source) -> %% Return all files dependent on the Source. digraph_utils:reaching_neighbours([Source], G). -spec internal_erl_compile(rebar_config:config(), file:filename(), file:filename(), list(), - digraph()) -> 'ok' | 'skipped'. + rebar_digraph()) -> 'ok' | 'skipped'. internal_erl_compile(Config, Source, OutDir, ErlOpts, G) -> %% Determine the target name and includes list by inspecting the source file Module = filename:basename(Source, ".erl"), diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index d969f96..8532af1 100644 --- a/src/rebar_eunit.erl +++ b/src/rebar_eunit.erl @@ -155,12 +155,10 @@ run_eunit(Config, CodePath, SrcErls) -> [filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles], ModuleBeamFiles = randomize_suites(Config, BeamFiles ++ OtherBeamFiles), - %% Get modules to be run in eunit + %% Get matching tests and modules AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles], - {SuitesProvided, FilteredModules} = filter_suites(Config, AllModules), - - %% Get matching tests - Tests = get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules), + {Tests, CoverageModules} = + get_tests_and_modules(Config, ModuleBeamFiles, AllModules), SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls], @@ -169,7 +167,7 @@ run_eunit(Config, CodePath, SrcErls) -> StatusBefore = status_before_eunit(), EunitResult = perform_eunit(Config, Tests), - perform_cover(Config, FilteredModules, SrcModules), + perform_cover(Config, CoverageModules, SrcModules), cover_close(CoverLog), case proplists:get_value(reset_after_eunit, get_eunit_opts(Config), @@ -214,18 +212,23 @@ setup_code_path() -> CodePath. %% -%% == filter suites == +%% == get matching tests == %% +get_tests_and_modules(Config, ModuleBeamFiles, AllModules) -> + SelectedSuites = get_selected_suites(Config, AllModules), + {Tests, QualifiedTests} = get_qualified_and_unqualified_tests(Config), + Modules = get_test_modules(SelectedSuites, Tests, + QualifiedTests, ModuleBeamFiles), + CoverageModules = get_coverage_modules(AllModules, Modules, QualifiedTests), + MatchedTests = get_matching_tests(Modules, Tests, QualifiedTests), + {MatchedTests, CoverageModules}. -filter_suites(Config, Modules) -> +%% +%% == get suites specified via 'suites' option == +%% +get_selected_suites(Config, Modules) -> RawSuites = get_suites(Config), - SuitesProvided = RawSuites =/= "", Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")], - {SuitesProvided, filter_suites1(Modules, Suites)}. - -filter_suites1(Modules, []) -> - Modules; -filter_suites1(Modules, Suites) -> [M || M <- Suites, lists:member(M, Modules)]. get_suites(Config) -> @@ -236,6 +239,32 @@ get_suites(Config) -> Suites end. +get_qualified_and_unqualified_tests(Config) -> + RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""), + FunctionNames = [FunctionName || + FunctionName <- string:tokens(RawFunctions, ",")], + get_qualified_and_unqualified_tests1(FunctionNames, [], []). + +get_qualified_and_unqualified_tests1([], Functions, QualifiedFunctions) -> + {Functions, QualifiedFunctions}; +get_qualified_and_unqualified_tests1([TestName|TestNames], Functions, + QualifiedFunctions) -> + case string:tokens(TestName, ":") of + [TestName] -> + Function = list_to_atom(TestName), + get_qualified_and_unqualified_tests1( + TestNames, [Function|Functions], QualifiedFunctions); + [ModuleName, FunctionName] -> + M = list_to_atom(ModuleName), + F = list_to_atom(FunctionName), + get_qualified_and_unqualified_tests1(TestNames, Functions, + [{M, F}|QualifiedFunctions]); + _ -> + ?ABORT("Unsupported test function specification: ~s~n", [TestName]) + end. + +%% Provide modules which are to be searched for tests. +%% Several scenarios are possible: %% %% == randomize suites == %% @@ -265,60 +294,66 @@ randomize_suites1(Modules, Seed) -> %% %% == get matching tests == +%% 1) Specific tests have been provided and/or +%% no unqualified tests have been specified and +%% there were some qualified tests, then we can search for +%% functions in specified suites (or in empty set of suites). %% -get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) -> - Modules = case SuitesProvided of - false -> - %% No specific suites have been provided, use - %% ModuleBeamFiles which filters out "*_tests" modules - %% so eunit won't doubly run them and cover only - %% calculates coverage on production code. However, - %% keep "*_tests" modules that are not automatically - %% included by eunit. - %% - %% From 'Primitives' in the EUnit User's Guide - %% http://www.erlang.org/doc/apps/eunit/chapter.html - %% "In addition, EUnit will also look for another - %% module whose name is ModuleName plus the suffix - %% _tests, and if it exists, all the tests from that - %% module will also be added. (If ModuleName already - %% contains the suffix _tests, this is not done.) E.g., - %% the specification {module, mymodule} will run all - %% tests in the modules mymodule and mymodule_tests. - %% Typically, the _tests module should only contain - %% test cases that use the public interface of the main - %% module (and no other code)." - [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || - N <- ModuleBeamFiles]; - true -> - %% Specific suites have been provided, return the - %% filtered modules - FilteredModules - end, - get_matching_tests(Config, Modules). - -get_tests(Config) -> - case rebar_config:get_global(Config, tests, "") of - "" -> - rebar_config:get_global(Config, test, ""); - Suites -> - Suites +%% 2) Neither specific suites nor qualified test names have been +%% provided use ModuleBeamFiles which filters out "*_tests" +%% modules so EUnit won't doubly run them and cover only +%% calculates coverage on production code. However, +%% keep "*_tests" modules that are not automatically +%% included by EUnit. +%% +%% From 'Primitives' in the EUnit User's Guide +%% http://www.erlang.org/doc/apps/eunit/chapter.html +%% "In addition, EUnit will also look for another +%% module whose name is ModuleName plus the suffix +%% _tests, and if it exists, all the tests from that +%% module will also be added. (If ModuleName already +%% contains the suffix _tests, this is not done.) E.g., +%% the specification {module, mymodule} will run all +%% tests in the modules mymodule and mymodule_tests. +%% Typically, the _tests module should only contain +%% test cases that use the public interface of the main +%% module (and no other code)." +get_test_modules(SelectedSuites, Tests, QualifiedTests, ModuleBeamFiles) -> + SuitesProvided = SelectedSuites =/= [], + OnlyQualifiedTestsProvided = QualifiedTests =/= [] andalso Tests =:= [], + if + SuitesProvided orelse OnlyQualifiedTestsProvided -> + SelectedSuites; + true -> + [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || + N <- ModuleBeamFiles] end. -get_matching_tests(Config, Modules) -> - RawFunctions = get_tests(Config), - Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")], - case Tests of - [] -> - Modules; - Functions -> - case get_matching_tests1(Modules, Functions, []) of - [] -> - []; - RawTests -> - make_test_primitives(RawTests) - end - end. +get_coverage_modules(AllModules, Modules, QualifiedTests) -> + ModuleFilterMapper = + fun({M, _}) -> + case lists:member(M, AllModules) of + true -> {true, M}; + _-> false + end + end, + ModulesFromQualifiedTests = lists:zf(ModuleFilterMapper, QualifiedTests), + lists:usort(Modules ++ ModulesFromQualifiedTests). + +get_matching_tests(Modules, [], []) -> + Modules; +get_matching_tests(Modules, [], QualifiedTests) -> + FilteredQualifiedTests = filter_qualified_tests(Modules, QualifiedTests), + lists:merge(Modules, make_test_primitives(FilteredQualifiedTests)); +get_matching_tests(Modules, Tests, QualifiedTests) -> + AllTests = lists:merge(QualifiedTests, + get_matching_tests1(Modules, Tests, [])), + make_test_primitives(AllTests). + +filter_qualified_tests(Modules, QualifiedTests) -> + TestsFilter = fun({Module, _Function}) -> + lists:all(fun(M) -> M =/= Module end, Modules) end, + lists:filter(TestsFilter, QualifiedTests). get_matching_tests1([], _Functions, TestFunctions) -> TestFunctions; @@ -608,9 +643,9 @@ align_notcovered_count(Module, Covered, NotCovered, true) -> cover_write_index(Coverage, SrcModules) -> {ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]), ok = file:write(F, "<!DOCTYPE HTML><html>\n" - "<head><meta charset=\"utf-8\">" - "<title>Coverage Summary</title></head>\n" - "<body>\n"), + "<head><meta charset=\"utf-8\">" + "<title>Coverage Summary</title></head>\n" + "<body>\n"), IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end, {SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage), cover_write_index_section(F, "Source", SrcCoverage), diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 517ac33..fa35fed 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -31,6 +31,7 @@ get_arch/0, wordsize/0, sh/2, + sh_send/3, find_files/2, find_files/3, now_str/0, ensure_dir/1, @@ -53,7 +54,8 @@ src_dirs/1, ebin_dir/0, base_dir/1, - processing_base_dir/1, processing_base_dir/2]). + processing_base_dir/1, processing_base_dir/2, + patch_env/2]). -include("rebar.hrl"). @@ -87,6 +89,24 @@ wordsize() -> integer_to_list(8 * erlang:system_info(wordsize)) end. +sh_send(Command0, String, Options0) -> + ?INFO("sh_send info:\n\tcwd: ~p\n\tcmd: ~s < ~s\n", [get_cwd(), Command0, String]), + ?DEBUG("\topts: ~p\n", [Options0]), + + DefaultOptions = [use_stdout, abort_on_error], + Options = [expand_sh_flag(V) + || V <- proplists:compact(Options0 ++ DefaultOptions)], + + Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])), + PortSettings = proplists:get_all_values(port_settings, Options) ++ + [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], + Port = open_port({spawn, Command}, PortSettings), + + %% allow us to send some data to the shell command's STDIN + %% Erlang doesn't let us get any reply after sending an EOF, though... + Port ! {self(), {command, String}}, + port_close(Port). + %% %% Options = [Option] -- defaults to [use_stdout, abort_on_error] %% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env} @@ -319,6 +339,23 @@ processing_base_dir(Config, Dir) -> AbsDir = filename:absname(Dir), AbsDir =:= base_dir(Config). +%% @doc Returns the list of environment variables including 'REBAR' which points to the +%% rebar executable used to execute the currently running command. The environment is +%% not modified if rebar was invoked programmatically. +-spec patch_env(rebar_config:config(), [{string(), string()}]) -> [{string(), string()}]. +patch_env(Config, []) -> + % if we reached an empty list the env did not contain the REBAR variable + case rebar_config:get_xconf(Config, escript, "") of + "" -> % rebar was invoked programmatically + []; + Path -> + [{"REBAR", Path}] + end; +patch_env(_Config, [{"REBAR", _} | _]=All) -> + All; +patch_env(Config, [E | Rest]) -> + [E | patch_env(Config, Rest)]. + %% ==================================================================== %% Internal functions %% ==================================================================== @@ -480,6 +517,7 @@ vcs_vsn_1(Vcs, Dir) -> end. vcs_vsn_cmd(git) -> "git describe --always --tags"; +vcs_vsn_cmd(p4) -> "echo #head"; vcs_vsn_cmd(hg) -> "hg identify -i"; vcs_vsn_cmd(bzr) -> "bzr revno"; vcs_vsn_cmd(svn) -> "svnversion"; diff --git a/test/rebar_eunit_tests.erl b/test/rebar_eunit_tests.erl index 61a9bbf..bb64507 100644 --- a/test/rebar_eunit_tests.erl +++ b/test/rebar_eunit_tests.erl @@ -191,6 +191,46 @@ eunit_with_suites_and_tests_test_() -> {"Selected suite tests is run once", ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}] + end}, + {"Ensure EUnit runs a specific test by qualified function name", + setup, + fun() -> + setup_project_with_multiple_modules(), + rebar("-v eunit tests=myapp_mymod:myprivate_test") + end, + fun teardown/1, + fun(RebarOut) -> + [{"Selected test is run", + [?_assert(string:str(RebarOut, + "myapp_mymod:myprivate_test/0") + =/= 0)]}, + + {"Only selected test is run", + [?_assert(string:str(RebarOut, + "Test passed.") =/= 0)]}] + end}, + {"Ensure EUnit runs a specific test by qualified function " + ++ "name and tests from other module", + setup, + fun() -> + setup_project_with_multiple_modules(), + rebar("-v eunit suites=myapp_mymod3 " + ++ "tests=myapp_mymod:myprivate_test") + end, + fun teardown/1, + fun(RebarOut) -> + [{"Selected test is run", + [?_assert(string:str(RebarOut, + "myapp_mymod:myprivate_test/0") =/= 0)]}, + + {"Tests from module are run", + [?_assert(string:str(RebarOut, + "myapp_mymod3:") =/= 0)]}, + + {"Only selected tests are run", + [?_assert(string:str(RebarOut, + "Failed: 1. Skipped: 0. Passed: 1") + =/= 0)]}] end}]. cover_test_() -> |