diff options
| -rw-r--r-- | src/rebar_prv_common_test.erl | 221 | ||||
| -rw-r--r-- | test/rebar_ct_SUITE.erl | 322 | 
2 files changed, 463 insertions, 80 deletions
| 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/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" | 
