@ -2,19 +2,21 @@
% % ex : ts = 4 sw = 4 et
- module ( rebar_prv_common_test ) .
- behaviour ( provider ) .
- export ( [ init / 1 ,
do / 1 ,
format_error / 1 ] ) .
% % exported for test purposes , consider private
- export ( [ setup_ct / 1 ] ) .
- export ( [ compile / 2 , prepare_tests / 1 , translate_paths / 2 ] ) .
- include ( " rebar.hrl " ) .
- include_lib ( " providers/include/providers.hrl " ) .
- define ( PROVIDER , ct ) .
- define ( DEPS , [ compile ] ) .
% % we need to modify app_info state before compile
- define ( DEPS , [ lock ] ) .
% % == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
% % Public API
@ -31,12 +33,19 @@ init(State) ->
{ desc , " Run Common Tests. " } ,
{ opts , ct_opts ( State ) } ,
{ profiles , [ test ] } ] ) ,
State1 = rebar_state : add_provider ( State , Provider ) ,
State2 = rebar_state : add_to_profile ( State1 , test , test_state ( State1 ) ) ,
{ ok , State2 } .
{ ok , rebar_state : add_provider ( State , Provider ) } .
- spec do ( rebar_state : t ( ) ) - > { ok , rebar_state : t ( ) } | { error , string ( ) } .
do ( State ) - >
Tests = prepare_tests ( State ) ,
case compile ( State , Tests ) of
% % successfully compiled apps
{ ok , S } - > do ( S , Tests ) ;
% % this should look like a compiler error , not a ct error
Error - > Error
end .
do ( State , Tests ) - >
? INFO ( " Running Common Test suites... " , [ ] ) ,
rebar_utils : update_code ( rebar_state : code_paths ( State , all_deps ) , [ soft_purge ] ) ,
@ -45,61 +54,308 @@ do(State) ->
Cwd = rebar_dir : get_cwd ( ) ,
rebar_hooks : run_all_hooks ( Cwd , pre , ? PROVIDER , Providers , State ) ,
try run_test ( State ) of
{ ok , State1 } = Result - >
% % Run ct provider posthooks
rebar_hooks : run_all_hooks ( Cwd , post , ? PROVIDER , Providers , State1 ) ,
rebar_utils : cleanup_code_path ( rebar_state : code_paths ( State , default ) ) ,
Result ;
? PRV_ERROR ( _ ) = Error - >
case Tests of
{ ok , T } - >
case run_tests ( State , T ) of
ok - >
% % Run ct provider posthooks
rebar_hooks : run_all_hooks ( Cwd , post , ? PROVIDER , Providers , State ) ,
rebar_utils : cleanup_code_path ( rebar_state : code_paths ( State , default ) ) ,
{ ok , State } ;
Error - >
rebar_utils : cleanup_code_path ( rebar_state : code_paths ( State , default ) ) ,
Error
end ;
Error - >
rebar_utils : cleanup_code_path ( rebar_state : code_paths ( State , default ) ) ,
Error
catch
throw : { error , Reason } - >
rebar_utils : cleanup_code_path ( rebar_state : code_paths ( State , default ) ) ,
? PRV_ERROR ( Reason )
end .
run_tests ( State , Opts ) - >
T = translate_paths ( State , Opts ) ,
Opts1 = setup_logdir ( State , T ) ,
% % strip test spec if present
Opts2 = strip_test_spec ( Opts1 ) ,
? DEBUG ( " ct_opts ~p " , [ Opts2 ] ) ,
{ RawOpts , _ } = rebar_state : command_parsed_args ( State ) ,
ok = maybe_write_coverdata ( State ) ,
case proplists : get_value ( verbose , RawOpts , false ) of
true - > run_test_verbose ( Opts2 ) ;
false - > run_test_quiet ( Opts2 )
end .
- spec format_error ( any ( ) ) - > iolist ( ) .
format_error ( { multiple_dirs_and_suites , Opts } ) - >
io_lib : format ( " Multiple dirs declared alongside suite in opts: ~p " , [ Opts ] ) ;
format_error ( { bad_dir_or_suite , Opts } ) - >
io_lib : format ( " Bad value for dir or suite in opts: ~p " , [ Opts ] ) ;
format_error ( { error , Reason } ) - >
io_lib : format ( " Error running tests: ~n ~p " , [ Reason ] ) ;
format_error ( { error_running_tests , Reason } ) - >
format_error ( { error , Reason } ) ;
format_error ( { failures_running_tests , { Failed , AutoSkipped } } ) - >
io_lib : format ( " Failures occured running tests: ~b " , [ Failed + AutoSkipped ] ) ;
format_error ( { error_running_tests , Reason } ) - >
io_lib : format ( " Error running tests: ~p " , [ Reason ] ) ;
format_error ( suite_at_project_root ) - >
io_lib : format ( " Test suites can't be located in project root " , [ ] ) ;
format_error ( { error , Reason } ) - >
io_lib : format ( " Unknown error: ~p " , [ Reason ] ) .
format_error ( { multiple_errors , Errors } ) - >
io_lib : format ( lists : concat ( [ " Error running tests: " ] ++
lists : map ( fun ( Error ) - > " ~n " ++ Error end , Errors ) ) , [ ] ) .
% % == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
% % Internal functions
% % == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
run_test ( State ) - >
case setup_ct ( State ) of
{ error , { no_tests_specified , Opts } } - >
? WARN ( " No tests specified in opts: ~p " , [ Opts ] ) ,
{ ok , State } ;
Opts - >
Opts1 = setup_logdir ( State , Opts ) ,
? DEBUG ( " common test opts: ~p " , [ Opts1 ] ) ,
run_test ( State , Opts1 )
end .
prepare_tests ( State ) - >
% % command line test options
CmdOpts = opts ( State ) ,
% % rebar . config test options
CfgOpts = rebar_state : get ( State , ct_tests , [ ] ) ,
ProjectApps = rebar_state : project_apps ( State ) ,
% % prioritize tests to run first trying any command line specified
% % tests falling back to tests specified in the config file finally
% % running a default set if no other tests are present
select_tests ( State , ProjectApps , CmdOpts , CfgOpts ) .
run_test ( State , Opts ) - >
opts ( State ) - >
{ RawOpts , _ } = rebar_state : command_parsed_args ( State ) ,
ok = rebar_prv_cover : maybe_cover_compile ( State , apps ) ,
Result = case proplists : get_value ( verbose , RawOpts , false ) of
true - > run_test_verbose ( Opts ) ;
false - > run_test_quiet ( Opts )
% % filter out opts common_test doesn ' t know about and convert
% % to ct acceptable forms
transform_opts ( RawOpts , [ ] ) .
transform_opts ( [ ] , Acc ) - > lists : reverse ( Acc ) ;
transform_opts ( [ { dir , Dirs } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { dir , split_string ( Dirs ) } | Acc ] ) ;
transform_opts ( [ { suite , Suites } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { suite , split_string ( Suites ) } | Acc ] ) ;
transform_opts ( [ { group , Groups } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { group , split_string ( Groups ) } | Acc ] ) ;
transform_opts ( [ { testcase , Cases } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { testcase , split_string ( Cases ) } | Acc ] ) ;
transform_opts ( [ { config , Configs } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { config , split_string ( Configs ) } | Acc ] ) ;
transform_opts ( [ { include , Includes } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { include , split_string ( Includes ) } | Acc ] ) ;
transform_opts ( [ { force_stop , " true " } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { force_stop , true } | Acc ] ) ;
transform_opts ( [ { force_stop , " false " } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { force_stop , false } | Acc ] ) ;
transform_opts ( [ { force_stop , " skip_rest " } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { force_stop , skip_rest } | Acc ] ) ;
% % drop cover from opts , ct doesn ' t care about it
transform_opts ( [ { cover , _ } | Rest ] , Acc ) - >
transform_opts ( Rest , Acc ) ;
% % drop verbose from opts , ct doesn ' t care about it
transform_opts ( [ { verbose , _ } | Rest ] , Acc ) - >
transform_opts ( Rest , Acc ) ;
% % getopt should handle anything else
transform_opts ( [ Opt | Rest ] , Acc ) - >
transform_opts ( Rest , [ Opt | Acc ] ) .
split_string ( String ) - >
string : tokens ( String , [ $, ] ) .
select_tests ( State , ProjectApps , CmdOpts , CfgOpts ) - >
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 ,
ok = rebar_prv_cover : maybe_write_coverdata ( State , ? PROVIDER ) ,
case Result of
ok - > { ok , State } ;
Error - > Error
discover_tests ( State , ProjectApps , Opts ) .
discover_tests ( State , ProjectApps , Opts ) - >
case { proplists : get_value ( suite , Opts ) , proplists : get_value ( dir , Opts ) } of
% % no dirs or suites defined , try using ` $A PP / test ` and ` $R OOT / test `
% % as suites
{ undefined , undefined } - > { ok , [ default_tests ( State , ProjectApps ) | Opts ] } ;
{ _ , _ } - > { ok , Opts }
end .
default_tests ( State , ProjectApps ) - >
BareTest = filename : join ( [ rebar_state : dir ( State ) , " test " ] ) ,
F = fun ( App ) - > rebar_app_info : dir ( App ) == rebar_state : dir ( State ) end ,
AppTests = application_dirs ( ProjectApps , [ ] ) ,
case filelib : is_dir ( BareTest ) andalso not lists : any ( F , ProjectApps ) of
% % ` test ` dir at root of project is already scheduled to be
% % included or ` test ` does not exist
false - > { dir , AppTests } ;
% % need to add ` test ` dir at root to dirs to be included
true - > { dir , AppTests ++ [ BareTest ] }
end .
application_dirs ( [ ] , [ ] ) - > [ ] ;
application_dirs ( [ ] , Acc ) - > lists : reverse ( Acc ) ;
application_dirs ( [ App | Rest ] , Acc ) - >
TestDir = filename : join ( [ rebar_app_info : dir ( App ) , " test " ] ) ,
case filelib : is_dir ( TestDir ) of
true - > application_dirs ( Rest , [ TestDir | Acc ] ) ;
false - > application_dirs ( Rest , Acc )
end .
compile ( State , { ok , Tests } ) - >
% % inject ` ct_first_files ` and ` ct_compile_opts ` into the applications
% % to be compiled
case inject_ct_state ( State , Tests ) of
{ ok , NewState } - > do_compile ( NewState ) ;
Error - > Error
end ;
% % maybe compile even in the face of errors ?
compile ( _ State , Error ) - > Error .
do_compile ( State ) - >
case rebar_prv_compile : do ( State ) of
% % successfully compiled apps
{ ok , S } - >
ok = maybe_cover_compile ( S ) ,
{ ok , S } ;
% % this should look like a compiler error , not an eunit error
Error - > Error
end .
inject_ct_state ( State , Tests ) - >
Apps = rebar_state : project_apps ( State ) ,
ModdedApps = lists : map ( fun ( App ) - >
NewOpts = inject ( rebar_app_info : opts ( App ) , State ) ,
rebar_app_info : opts ( App , NewOpts )
end , Apps ) ,
NewOpts = inject ( rebar_state : opts ( State ) , State ) ,
NewState = rebar_state : opts ( State , NewOpts ) ,
test_dirs ( NewState , ModdedApps , Tests ) .
inject ( Opts , State ) - >
% % append ` ct_compile_opts ` to app defined ` erl_opts `
ErlOpts = rebar_opts : get ( Opts , erl_opts , [ ] ) ,
CTOpts = rebar_state : get ( State , ct_compile_opts , [ ] ) ,
NewErlOpts = CTOpts ++ ErlOpts ,
% % append ` ct_first_files ` to app defined ` erl_first_files `
FirstFiles = rebar_opts : get ( Opts , erl_first_files , [ ] ) ,
CTFirstFiles = rebar_state : get ( State , ct_first_files , [ ] ) ,
NewFirstFiles = CTFirstFiles ++ FirstFiles ,
% % insert the new keys into the opts
lists : foldl ( fun ( { K , V } , NewOpts ) - > rebar_opts : set ( NewOpts , K , V ) end ,
Opts ,
[ { erl_opts , NewErlOpts } , { erl_first_files , NewFirstFiles } ] ) .
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 ] } - > set_compile_dirs ( State , Apps , join ( Suites , Dir ) ) ;
{ _ Suites , _ Dirs } - > { error , " Only a single directory may be specified when specifying suites " }
end .
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 ) ) - >
% % 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 } ) - >
% % 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 ) ,
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 ) } .
find_suite_dirs ( Suites ) - >
AllDirs = lists : map ( fun ( S ) - > filename : dirname ( filename : absname ( S ) ) end , Suites ) ,
% % eliminate duplicates
lists : usort ( AllDirs ) .
maybe_inject_test_dir ( State , AppAcc , [ App | Rest ] , Dir ) - >
case rebar_file_utils : path_from_ancestor ( Dir , rebar_app_info : dir ( App ) ) of
{ ok , Path } - >
Opts = inject_test_dir ( rebar_app_info : opts ( App ) , Path ) ,
{ State , AppAcc ++ [ rebar_app_info : opts ( App , Opts ) ] ++ Rest } ;
{ error , badparent } - >
maybe_inject_test_dir ( State , AppAcc ++ [ App ] , Rest , Dir )
end ;
maybe_inject_test_dir ( State , AppAcc , [ ] , Dir ) - >
case rebar_file_utils : path_from_ancestor ( Dir , rebar_state : dir ( State ) ) of
{ ok , [ ] } - >
? WARN ( " Can't have suites in root of project dir, dropping from tests " , [ ] ) ,
{ State , AppAcc } ;
{ ok , Path } - >
Opts = inject_test_dir ( rebar_state : opts ( State ) , Path ) ,
{ rebar_state : opts ( State , Opts ) , AppAcc } ;
{ error , badparent } - >
{ State , AppAcc }
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 ] ) .
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 , [ ] )
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
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 ( 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 ( State , Apps , Suite ) end , Suites ) } ,
translate_suites ( State , Rest , [ NewSuites | Acc ] ) ;
translate_suites ( State , [ Test | Rest ] , Acc ) - >
translate_suites ( State , Rest , [ Test | Acc ] ) .
translate ( State , [ App | Rest ] , Path ) - >
case rebar_file_utils : path_from_ancestor ( Path , rebar_app_info : dir ( App ) ) of
{ ok , P } - > filename : join ( [ rebar_app_info : out_dir ( App ) , P ] ) ;
{ error , badparent } - > translate ( State , Rest , Path )
end ;
translate ( State , [ ] , Path ) - >
case rebar_file_utils : path_from_ancestor ( Path , rebar_state : dir ( State ) ) of
{ ok , P } - > filename : join ( [ rebar_dir : base_dir ( State ) , " extras " , P ] ) ;
% % not relative , leave as is
{ error , badparent } - > Path
end .
setup_logdir ( State , Opts ) - >
Logdir = case proplists : get_value ( logdir , Opts ) of
undefined - > filename : join ( [ rebar_dir : base_dir ( State ) , " logs " ] ) ;
Dir - > Dir
end ,
filelib : ensure_dir ( filename : join ( [ Logdir , " dummy.beam " ] ) ) ,
[ { logdir , Logdir } | lists : keydelete ( logdir , 1 , Opts ) ] .
strip_test_spec ( Opts ) - >
case proplists : get_value ( test_spec , Opts ) of
undefined - > Opts ;
_ - >
? WARN ( " Test specs not supported " , [ ] ) ,
lists : keydelete ( test_spec , 1 , Opts )
end .
run_test_verbose ( Opts ) - > handle_results ( ct : run_test ( Opts ) ) .
@ -171,272 +427,38 @@ format_skipped({0, 0}) ->
format_skipped ( { User , Auto } ) - >
io_lib : format ( " Skipped ~p ( ~p , ~p ) tests. " , [ User + Auto , User , Auto ] ) .
test_state ( State ) - >
TestOpts = case rebar_state : get ( State , ct_compile_opts , [ ] ) of
[ ] - > [ ] ;
Opts - > [ { erl_opts , Opts } ]
end ,
[ first_files ( State ) | TestOpts ] .
first_files ( State ) - >
CTFirst = rebar_state : get ( State , ct_first_files , [ ] ) ,
{ erl_first_files , CTFirst } .
setup_ct ( State ) - >
Opts = resolve_ct_opts ( State ) ,
Opts1 = discover_tests ( State , Opts ) ,
copy_and_compile_tests ( State , Opts1 ) .
resolve_ct_opts ( State ) - >
{ RawOpts , _ } = rebar_state : command_parsed_args ( State ) ,
CmdOpts = transform_opts ( RawOpts ) ,
CfgOpts = rebar_state : get ( State , ct_opts , [ ] ) ,
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
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 .
discover_tests ( State , Opts ) - >
case proplists : get_value ( spec , Opts ) of
undefined - > discover_dirs_and_suites ( State , Opts ) ;
TestSpec - > discover_testspec ( TestSpec , Opts )
end .
discover_dirs_and_suites ( State , Opts ) - >
case { proplists : get_value ( dir , Opts ) , proplists : get_value ( suite , Opts ) } of
% % no dirs or suites defined , try using ` $A PP / test ` and ` $R OOT / test `
% % as suites
{ undefined , undefined } - > test_dirs ( State , Opts ) ;
% % no dirs defined
{ undefined , _ } - > Opts ;
% % no suites defined
{ _ , undefined } - > Opts ;
% % a single dir defined , this is ok
{ Dirs , Suites } when is_integer ( hd ( Dirs ) ) , is_list ( Suites ) - > Opts ;
% % still a single dir defined , adjust to make acceptable to ct
{ [ Dir ] , Suites } when is_integer ( hd ( Dir ) ) , is_list ( Suites ) - >
[ { dir , Dir } | lists : keydelete ( dir , 1 , Opts ) ] ;
% % multiple dirs and suites , error now to simplify later steps
{ _ , _ } - > throw ( { error , { multiple_dirs_and_suites , Opts } } )
end .
discover_testspec ( _ TestSpec , Opts ) - >
lists : keydelete ( auto_compile , 1 , Opts ) .
copy_and_compile_tests ( State , Opts ) - >
% % possibly enable cover
maybe_cover_compile ( State ) - >
{ RawOpts , _ } = rebar_state : command_parsed_args ( State ) ,
State1 = case proplists : get_value ( cover , RawOpts , false ) of
true - > rebar_state : set ( State , cover_enabled , true ) ;
false - > State
end ,
copy_and_compile_test_suites ( State1 , Opts ) .
copy_and_compile_test_suites ( State , Opts ) - >
case proplists : get_value ( suite , Opts ) of
% % no suites , try dirs
undefined - > copy_and_compile_test_dirs ( State , Opts ) ;
Suites - >
Dir = proplists : get_value ( dir , Opts , undefined ) ,
AllSuites = join ( Dir , Suites ) ,
Dirs = find_suite_dirs ( AllSuites ) ,
lists : foreach ( fun ( S ) - >
NewPath = copy ( State , S ) ,
compile_dir ( State , NewPath )
end , Dirs ) ,
NewSuites = lists : map ( fun ( S ) - > retarget_path ( State , S ) end , AllSuites ) ,
[ { suite , NewSuites } | lists : keydelete ( suite , 1 , Opts ) ]
end .
copy_and_compile_test_dirs ( State , Opts ) - >
case proplists : get_value ( dir , Opts ) of
undefined - > { error , { no_tests_specified , Opts } } ;
% % dir is a single directory
Dir when is_list ( Dir ) , is_integer ( hd ( Dir ) ) - >
NewPath = copy ( State , Dir ) ,
[ { dir , compile_dir ( State , NewPath ) } | lists : keydelete ( dir , 1 , Opts ) ] ;
% % dir is a list of directories
Dirs when is_list ( Dirs ) - >
NewDirs = lists : map ( fun ( Dir ) - >
NewPath = copy ( State , Dir ) ,
compile_dir ( State , NewPath )
end , Dirs ) ,
[ { dir , NewDirs } | lists : keydelete ( dir , 1 , Opts ) ]
end .
join ( undefined , Suites ) - > Suites ;
join ( Dir , Suites ) when is_list ( Dir ) , is_integer ( hd ( Dir ) ) - >
lists : map ( fun ( S ) - > filename : join ( [ Dir , S ] ) end , Suites ) ;
% % multiple dirs or a bad dir argument , try to continue anyways
join ( _ , Suites ) - > Suites .
find_suite_dirs ( Suites ) - >
AllDirs = lists : map ( fun ( S ) - > filename : dirname ( filename : absname ( S ) ) end , Suites ) ,
% % eliminate duplicates
lists : usort ( AllDirs ) .
copy ( State , Dir ) - >
From = reduce_path ( Dir ) ,
retarget_path ( State , From ) .
compile_dir ( State , Dir ) - >
NewState = replace_src_dirs ( State , [ filename : absname ( Dir ) ] ) ,
ok = rebar_erlc_compiler : compile ( rebar_state : opts ( NewState ) , rebar_dir : base_dir ( State ) , Dir ) ,
ok = maybe_cover_compile ( State , Dir ) ,
Dir .
retarget_path ( State , Path ) - >
ProjectApps = rebar_state : project_apps ( State ) ,
retarget_path ( State , Path , ProjectApps ) .
% % not relative to any apps in project , check to see it ' s relative to
% % project root
retarget_path ( State , Path , [ ] ) - >
case relative_path ( reduce_path ( Path ) , rebar_state : dir ( State ) ) of
{ ok , NewPath } - > filename : join ( [ rebar_dir : base_dir ( State ) , NewPath ] ) ;
% % not relative to project root , don ' t modify
{ error , not_relative } - > Path
end ;
% % relative to current app , retarget to the same dir relative to
% % the app ' s out_dir
retarget_path ( State , Path , [ App | Rest ] ) - >
case relative_path ( reduce_path ( Path ) , rebar_app_info : dir ( App ) ) of
{ ok , NewPath } - > filename : join ( [ rebar_app_info : out_dir ( App ) , NewPath ] ) ;
{ error , not_relative } - > retarget_path ( State , Path , Rest )
end .
relative_path ( Target , To ) - >
relative_path1 ( filename : split ( filename : absname ( Target ) ) ,
filename : split ( filename : absname ( To ) ) ) .
relative_path1 ( [ Part | Target ] , [ Part | To ] ) - > relative_path1 ( Target , To ) ;
relative_path1 ( [ ] , [ ] ) - > { ok , " " } ;
relative_path1 ( Target , [ ] ) - > { ok , filename : join ( Target ) } ;
relative_path1 ( _ , _ ) - > { error , not_relative } .
reduce_path ( Dir ) - > reduce_path ( [ ] , filename : split ( filename : absname ( Dir ) ) ) .
reduce_path ( [ ] , [ ] ) - > filename : nativename ( " / " ) ;
reduce_path ( Acc , [ ] ) - > filename : join ( lists : reverse ( Acc ) ) ;
reduce_path ( Acc , [ " . " | Rest ] ) - > reduce_path ( Acc , Rest ) ;
reduce_path ( [ _ | Acc ] , [ " .. " | Rest ] ) - > reduce_path ( Acc , Rest ) ;
reduce_path ( [ ] , [ " .. " | Rest ] ) - > reduce_path ( [ ] , Rest ) ;
reduce_path ( Acc , [ Component | Rest ] ) - > reduce_path ( [ Component | Acc ] , Rest ) .
replace_src_dirs ( State , Dirs ) - >
% % replace any ` src_dirs ` with the test dirs
ErlOpts = rebar_state : get ( State , erl_opts , [ ] ) ,
StrippedErlOpts = filter_src_dirs ( ErlOpts ) ,
State1 = rebar_state : set ( State , erl_opts , StrippedErlOpts ) ,
State2 = rebar_state : set ( State1 , src_dirs , [ ] ) ,
rebar_state : set ( State2 , extra_src_dirs , Dirs ) .
rebar_prv_cover : maybe_cover_compile ( State1 ) .
filter_src_dirs ( ErlOpts ) - >
lists : filter ( fun ( { src_dirs , _ } ) - > false ; ( { extra_src_dirs , _ } ) - > false ; ( _ ) - > true end , ErlOpts ) .
test_dirs ( State , Opts ) - >
BareTest = filename : join ( [ rebar_state : dir ( State ) , " test " ] ) ,
F = fun ( App ) - > rebar_app_info : dir ( App ) == rebar_state : dir ( State ) end ,
TestApps = project_apps ( State ) ,
case filelib : is_dir ( BareTest ) andalso not lists : any ( F , TestApps ) of
% % ` test ` dir at root of project is already scheduled to be
% % included or ` test ` does not exist
false - > application_dirs ( TestApps , Opts , [ ] ) ;
% % need to add ` test ` dir at root to dirs to be included
true - > application_dirs ( TestApps , Opts , [ BareTest ] )
end .
project_apps ( State ) - >
filter_checkouts ( rebar_state : project_apps ( State ) ) .
filter_checkouts ( Apps ) - > filter_checkouts ( Apps , [ ] ) .
filter_checkouts ( [ ] , Acc ) - > lists : reverse ( Acc ) ;
filter_checkouts ( [ App | Rest ] , Acc ) - >
case rebar_app_info : is_checkout ( App ) of
true - > filter_checkouts ( Rest , Acc ) ;
false - > filter_checkouts ( Rest , [ App | Acc ] )
end .
application_dirs ( [ ] , Opts , [ ] ) - > Opts ;
application_dirs ( [ ] , Opts , [ Acc ] ) - > [ { dir , Acc } | Opts ] ;
application_dirs ( [ ] , Opts , Acc ) - > [ { dir , lists : reverse ( Acc ) } | Opts ] ;
application_dirs ( [ App | Rest ] , Opts , Acc ) - >
TestDir = filename : join ( [ rebar_app_info : dir ( App ) , " test " ] ) ,
case filelib : is_dir ( TestDir ) of
true - > application_dirs ( Rest , Opts , [ TestDir | Acc ] ) ;
false - > application_dirs ( Rest , Opts , Acc )
end .
setup_logdir ( State , Opts ) - >
Logdir = case proplists : get_value ( logdir , Opts ) of
undefined - > filename : join ( [ rebar_dir : base_dir ( State ) , " logs " ] ) ;
Dir - > Dir
end ,
ensure_dir ( [ Logdir ] ) ,
[ { logdir , Logdir } | lists : keydelete ( logdir , 1 , Opts ) ] .
ensure_dir ( [ ] ) - > ok ;
ensure_dir ( [ Dir | Rest ] ) - >
case ec_file : is_dir ( Dir ) of
true - >
ok ;
false - >
ec_file : mkdir_path ( Dir )
end ,
ensure_dir ( Rest ) .
maybe_cover_compile ( State , Dir ) - >
{ Opts , _ } = rebar_state : command_parsed_args ( State ) ,
State1 = case proplists : get_value ( cover , Opts , false ) of
maybe_write_coverdata ( State ) - >
{ RawOpts , _ } = rebar_state : command_parsed_args ( State ) ,
State1 = case proplists : get_value ( cover , RawOpts , false ) of
true - > rebar_state : set ( State , cover_enabled , true ) ;
false - > State
end ,
rebar_prv_cover : maybe_cover_compile ( State1 , [ Dir ] ) .
rebar_prv_cover : maybe_write_coverdata ( State1 , ? PROVIDER ) .
ct_opts ( _ State ) - >
[ { dir , undefined , " dir " , string , help ( dir ) } , % % comma - seperated list
{ suite , undefined , " suite " , string , help ( suite ) } , % % comma - seperated list
{ group , undefined , " group " , string , help ( group ) } , % % comma - seperated list
{ testcase , undefined , " case " , string , help ( testcase ) } , % % comma - seperated list
{ spec , undefined , " spec " , string , help ( spec ) } , % % comma - seperated list
{ join_specs , undefined , " join_specs " , boolean , help ( join_specs ) } , % % Boolean
{ label , undefined , " label " , string , help ( label ) } , % % String
{ config , undefined , " config " , string , help ( config ) } , % % comma - seperated list
{ userconfig , undefined , " userconfig " , string , help ( userconfig ) } , % % [ { CallbackMod , CfgStrings } ] | { CallbackMod , CfgStrings }
{ allow_user_terms , undefined , " allow_user_terms " , boolean , help ( allow_user_terms ) } , % % Bool
{ logdir , undefined , " logdir " , string , help ( logdir ) } , % % dir
{ logopts , undefined , " logopts " , string , help ( logopts ) } , % % enum , no_nl | no_src
{ verbosity , undefined , " verbosity " , string , help ( verbosity ) } , % % Integer OR [ { Category , VLevel } ]
{ silent_connections , undefined , " silent_connections " , string ,
help ( silent_connections ) } , % all OR % % comma - seperated list
{ stylesheet , undefined , " stylesheet " , string , help ( stylesheet ) } , % % file
{ verbosity , undefined , " verbosity " , integer , help ( verbosity ) } , % % Integer
{ cover , $c , " cover " , { boolean , false } , help ( cover ) } ,
{ cover_spec , undefined , " cover_spec " , string , help ( cover_spec ) } , % % file
{ cover_stop , undefined , " cover_stop " , boolean , help ( cover_stop ) } , % % Boolean
{ event_handler , undefined , " event_handler " , string , help ( event_handler ) } , % % EH | [ EH ] WHERE EH atom ( ) | { atom ( ) , InitArgs } | { [ atom ( ) ] , InitArgs }
{ include , undefined , " include " , string , help ( include ) } , % comma - seperated list
{ abort_if_missing_suites , undefined , " abort_if_missing_suites " , { boolean , true } ,
help ( abort_if_missing_suites ) } , % % boolean
{ multiply_timetraps , undefined , " multiply_timetraps " , integer ,
help ( multiply_timetraps ) } , % % integer
{ scale_timetraps , undefined , " scale_timetraps " , boolean , help ( scale_timetraps ) } , % % Boolean
{ create_priv_dir , undefined , " create_priv_dir " , string , help ( create_priv_dir ) } , % % enum : auto_per_run | auto_per_tc | manual_per_tc
{ repeat , undefined , " repeat " , integer , help ( repeat ) } , % % integer
{ duration , undefined , " duration " , string , help ( duration ) } , % format : HHMMSS
{ until , undefined , " until " , string , help ( until ) } , % % format : YYMoMoDD [ HHMMSS ]
{ force_stop , undefined , " force_stop " , string , help ( force_stop ) } , % enum : skip_rest , bool
{ basic_html , undefined , " basic_html " , boolean , help ( basic_html ) } , % % Booloean
{ ct_hooks , undefined , " ct_hooks " , string , help ( ct_hooks ) } , % % List : [ CTHModule | { CTHModule , CTHInitArgs } ] where CTHModule is atom CthInitArgs is term
{ auto_compile , undefined , " auto_compile " , { boolean , false } , help ( auto_compile ) } ,
{ verbose , $v , " verbose " , boolean , help ( verbose ) }
] .
@ -448,36 +470,26 @@ help(group) ->
" List of test groups to run " ;
help ( testcase ) - >
" List of test cases to run " ;
help ( spec ) - >
" List of test specs to run " ;
help ( label ) - >
" Test label " ;
help ( config ) - >
" List of config files " ;
help ( allow_user_terms ) - >
" Allow user defined config values in config files " ;
help ( logdir ) - >
" Log folder " ;
help ( verbosity ) - >
" Verbosity " ;
help ( stylesheet ) - >
" Stylesheet to use for test results " ;
help ( cover ) - >
" Generate cover data " ;
help ( cover_spec ) - >
" Cover file to use " ;
help ( event_handler ) - >
" Event handlers to attach to the runner " ;
help ( include ) - >
" Include folder " ;
help ( abort_if_missing_suites ) - >
" Abort if suites are missing " ;
" Include folders " ;
help ( repeat ) - >
" How often to repeat tests " ;
help ( duration ) - >
" Max runtime (format: HHMMSS) " ;
help ( until ) - >
" Run until (format: HHMMSS) " ;
help ( force_stop ) - >
" Force stop after time " ;
help ( basic_html ) - >
" Show basic HTML " ;
help ( verbose ) - >
@ -485,77 +497,3 @@ help(verbose) ->
help ( _ ) - >
" " .
transform_opts ( Opts ) - >
transform_opts ( Opts , [ ] ) .
transform_opts ( [ ] , Acc ) - > Acc ;
% % drop ` cover ` and ` verbose ` so they ' re not passed as an option to common_test
transform_opts ( [ { cover , _ } | Rest ] , Acc ) - >
transform_opts ( Rest , Acc ) ;
transform_opts ( [ { cover_spec , CoverSpec } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { cover , CoverSpec } | Acc ] ) ;
transform_opts ( [ { verbose , _ } | Rest ] , Acc ) - >
transform_opts ( Rest , Acc ) ;
transform_opts ( [ { ct_hooks , CtHooks } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { ct_hooks , parse_term ( CtHooks ) } | Acc ] ) ;
transform_opts ( [ { force_stop , " skip_rest " } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { force_stop , skip_rest } | Acc ] ) ;
transform_opts ( [ { force_stop , _ } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { force_stop , true } | Acc ] ) ;
transform_opts ( [ { repeat , Repeat } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { repeat ,
ec_cnv : to_integer ( Repeat ) } | Acc ] ) ;
transform_opts ( [ { create_priv_dir , CreatePrivDir } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { create_priv_dir ,
to_atoms ( CreatePrivDir ) } | Acc ] ) ;
transform_opts ( [ { multiply_timetraps , MultiplyTimetraps } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { multiply_timetraps ,
ec_cnv : to_integer ( MultiplyTimetraps ) } | Acc ] ) ;
transform_opts ( [ { event_handler , EventHandler } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { event_handler , parse_term ( EventHandler ) } | Acc ] ) ;
transform_opts ( [ { silent_connections , " all " } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { silent_connections , all } | Acc ] ) ;
transform_opts ( [ { silent_connections , SilentConnections } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { silent_connections ,
to_atoms ( split_string ( SilentConnections ) ) } | Acc ] ) ;
transform_opts ( [ { verbosity , Verbosity } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { verbosity , parse_term ( Verbosity ) } | Acc ] ) ;
transform_opts ( [ { logopts , LogOpts } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { logopts , to_atoms ( split_string ( LogOpts ) ) } | Acc ] ) ;
transform_opts ( [ { userconfig , UserConfig } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { userconfig , parse_term ( UserConfig ) } | Acc ] ) ;
transform_opts ( [ { testcase , Testcase } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { testcase , to_atoms ( split_string ( Testcase ) ) } | Acc ] ) ;
transform_opts ( [ { group , Group } | Rest ] , Acc ) - > % @ TODO handle " "
% Input is a list or an atom . It can also be a nested list .
transform_opts ( Rest , [ { group , parse_term ( Group ) } | Acc ] ) ;
transform_opts ( [ { suite , Suite } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { suite , split_string ( Suite ) } | Acc ] ) ;
transform_opts ( [ { Key , Val } | Rest ] , Acc ) when is_list ( Val ) - >
% Default to splitting a string on comma , that works fine for both flat
% lists of which there are many and single - items .
Val1 = case split_string ( Val ) of
[ Val2 ] - >
Val2 ;
Val2 - >
Val2
end ,
transform_opts ( Rest , [ { Key , Val1 } | Acc ] ) ;
transform_opts ( [ { Key , Val } | Rest ] , Acc ) - >
transform_opts ( Rest , [ { Key , Val } | Acc ] ) .
to_atoms ( List ) - >
lists : map ( fun ( X ) - > list_to_atom ( X ) end , List ) .
split_string ( String ) - >
string : tokens ( String , " , " ) .
parse_term ( String ) - >
String1 = " [ " ++ String ++ " ]. " ,
{ ok , Tokens , _ } = erl_scan : string ( String1 ) ,
case erl_parse : parse_term ( Tokens ) of
{ ok , [ Terms ] } - >
Terms ;
Term - >
Term
end .