1%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- 2%% ex: ts=4 sw=4 et 3 4-module(rebar_prv_eunit). 5 6-behaviour(provider). 7 8-export([init/1, 9 do/1, 10 format_error/1]). 11%% exported solely for tests 12-export([prepare_tests/1, eunit_opts/1, validate_tests/2]). 13 14-include("rebar.hrl"). 15-include_lib("providers/include/providers.hrl"). 16 17-define(PROVIDER, eunit). 18%% we need to modify app_info state before compile 19-define(DEPS, [lock]). 20 21-define(DEFAULT_TEST_REGEX, "^(?!\\._).*\\.erl\$"). 22 23%% =================================================================== 24%% Public API 25%% =================================================================== 26 27-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. 28init(State) -> 29 Provider = providers:create([{name, ?PROVIDER}, 30 {module, ?MODULE}, 31 {deps, ?DEPS}, 32 {bare, true}, 33 {example, "rebar3 eunit"}, 34 {short_desc, "Run EUnit Tests."}, 35 {desc, "Run EUnit Tests."}, 36 {opts, eunit_opts(State)}, 37 {profiles, [test]}]), 38 State1 = rebar_state:add_provider(State, Provider), 39 {ok, State1}. 40 41-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. 42do(State) -> 43 Tests = prepare_tests(State), 44 %% inject `eunit_first_files`, `eunit_compile_opts` and any 45 %% directories required by tests into the applications 46 NewState = inject_eunit_state(State, Tests), 47 case compile(NewState) of 48 %% successfully compiled apps 49 {ok, S} -> do(S, Tests); 50 Error -> Error 51 end. 52 53do(State, Tests) -> 54 ?INFO("Performing EUnit tests...", []), 55 56 setup_name(State), 57 rebar_paths:set_paths([deps, plugins], State), 58 59 %% Run eunit provider prehooks 60 Providers = rebar_state:providers(State), 61 Cwd = rebar_dir:get_cwd(), 62 rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State), 63 64 case validate_tests(State, Tests) of 65 {ok, T} -> 66 case run_tests(State, T) of 67 {ok, State1} -> 68 %% Run eunit provider posthooks 69 rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State1), 70 rebar_paths:set_paths([plugins, deps], State), 71 {ok, State1}; 72 Error -> 73 rebar_paths:set_paths([plugins, deps], State), 74 Error 75 end; 76 Error -> 77 rebar_paths:set_paths([plugins, deps], State), 78 Error 79 end. 80 81run_tests(State, Tests) -> 82 T = translate_paths(State, Tests), 83 EUnitOpts = resolve_eunit_opts(State), 84 ?DEBUG("finding tests in:~n\t{eunit_tests, ~p}.", [T]), 85 ?DEBUG("with options:~n\t{eunit_opts, ~p}.", [EUnitOpts]), 86 apply_sys_config(State), 87 try eunit:test(T, EUnitOpts) of 88 Result -> 89 ok = maybe_write_coverdata(State), 90 case handle_results(Result) of 91 {error, Reason} -> 92 ?PRV_ERROR(Reason); 93 ok -> 94 {ok, State} 95 end 96 catch error:badarg -> ?PRV_ERROR({error, badarg}) 97 end. 98 99-spec format_error(any()) -> iolist(). 100format_error(unknown_error) -> 101 io_lib:format("Error running tests", []); 102format_error({error_running_tests, Reason}) -> 103 io_lib:format("Error running tests: ~p", [Reason]); 104format_error({eunit_test_errors, Errors}) -> 105 io_lib:format(lists:concat(["Error Running EUnit Tests:"] ++ 106 lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []); 107format_error({badconfig, {Msg, {Value, Key}}}) -> 108 io_lib:format(Msg, [Value, Key]); 109format_error({generator, Value}) -> 110 io_lib:format("Generator ~p has an invalid format", [Value]); 111format_error({error, Error}) -> 112 format_error({error_running_tests, Error}). 113 114%% =================================================================== 115%% Internal functions 116%% =================================================================== 117 118setup_name(State) -> 119 {Long, Short, Opts} = rebar_dist_utils:find_options(State), 120 rebar_dist_utils:either(Long, Short, Opts). 121 122prepare_tests(State) -> 123 %% parse and translate command line tests 124 CmdTests = resolve_tests(State), 125 CfgTests = cfg_tests(State), 126 ProjectApps = rebar_state:project_apps(State), 127 %% prioritize tests to run first trying any command line specified 128 %% tests falling back to tests specified in the config file finally 129 %% running a default set if no other tests are present 130 select_tests(State, ProjectApps, CmdTests, CfgTests). 131 132resolve_tests(State) -> 133 {RawOpts, _} = rebar_state:command_parsed_args(State), 134 Apps = resolve(app, application, RawOpts), 135 Applications = resolve(application, RawOpts), 136 Dirs = resolve(dir, RawOpts), 137 Files = resolve(file, RawOpts), 138 Modules = resolve(module, RawOpts), 139 Suites = resolve(suite, module, RawOpts), 140 Generator = resolve(generator, RawOpts), 141 Apps ++ Applications ++ Dirs ++ Files ++ Modules ++ Suites ++ Generator. 142 143resolve(Flag, RawOpts) -> resolve(Flag, Flag, RawOpts). 144 145resolve(Flag, EUnitKey, RawOpts) -> 146 case proplists:get_value(Flag, RawOpts) of 147 undefined -> []; 148 Args -> normalize(EUnitKey, 149 rebar_string:lexemes(Args, [$,])) 150 end. 151 152normalize(generator, Args) -> 153 lists:flatmap(fun(Value) -> normalize_(generator, Value) end, Args); 154normalize(EUnitKey, Args) -> 155 lists:map(fun(Arg) -> normalize_(EUnitKey, Arg) end, Args). 156 157normalize_(generator, Value) -> 158 case string:tokens(Value, [$:]) of 159 [Module0, Functions] -> 160 Module = list_to_atom(Module0), 161 lists:map(fun(F) -> {generator, Module, list_to_atom(F)} end, 162 string:tokens(Functions, [$;])); 163 _ -> 164 ?PRV_ERROR({generator, Value}) 165 end; 166normalize_(Key, Value) when Key == dir; Key == file -> {Key, Value}; 167normalize_(Key, Value) -> {Key, list_to_atom(Value)}. 168 169cfg_tests(State) -> 170 case rebar_state:get(State, eunit_tests, []) of 171 Tests when is_list(Tests) -> 172 lists:map(fun({app, App}) -> {application, App}; (T) -> T end, Tests); 173 Wrong -> 174 %% probably a single non list term 175 ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, eunit_tests}}}) 176 end. 177 178select_tests(_State, _ProjectApps, _, {error, _} = Error) -> Error; 179select_tests(State, ProjectApps, [], []) -> {ok, default_tests(State, ProjectApps)}; 180select_tests(_State, _ProjectApps, [], Tests) -> {ok, Tests}; 181select_tests(_State, _ProjectApps, Tests, _) -> {ok, Tests}. 182 183default_tests(State, Apps) -> 184 %% use `{application, App}` for each app in project 185 AppTests = set_apps(Apps), 186 %% additional test modules in `test` dir of each app 187 ModTests = set_modules(Apps, State), 188 AppTests ++ ModTests. 189 190set_apps(Apps) -> set_apps(Apps, []). 191 192set_apps([], Acc) -> Acc; 193set_apps([App|Rest], Acc) -> 194 AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))), 195 set_apps(Rest, [{application, AppName}|Acc]). 196 197set_modules(Apps, State) -> set_modules(Apps, State, {[], []}). 198 199set_modules([], State, {AppAcc, TestAcc}) -> 200 Regex = rebar_state:get(State, eunit_test_regex, ?DEFAULT_TEST_REGEX), 201 BareTestDir = [filename:join([rebar_state:dir(State), "test"])], 202 TestSrc = gather_src(BareTestDir, Regex), 203 dedupe_tests({AppAcc, TestAcc ++ TestSrc}); 204set_modules([App|Rest], State, {AppAcc, TestAcc}) -> 205 F = fun(Dir) -> filename:join([rebar_app_info:dir(App), Dir]) end, 206 AppDirs = lists:map(F, rebar_dir:src_dirs(rebar_app_info:opts(App), ["src"])), 207 Regex = rebar_state:get(State, eunit_test_regex, ?DEFAULT_TEST_REGEX), 208 AppSrc = gather_src(AppDirs, Regex), 209 TestDirs = [filename:join([rebar_app_info:dir(App), "test"])], 210 TestSrc = gather_src(TestDirs, Regex), 211 set_modules(Rest, State, {AppSrc ++ AppAcc, TestSrc ++ TestAcc}). 212 213gather_src(Dirs, Regex) -> gather_src(Dirs, Regex, []). 214 215gather_src([], _Regex, Srcs) -> Srcs; 216gather_src([Dir|Rest], Regex, Srcs) -> 217 gather_src(Rest, Regex, Srcs ++ rebar_utils:find_files(Dir, Regex, true)). 218 219dedupe_tests({AppMods, TestMods}) -> 220 UniqueTestMods = lists:usort(TestMods) -- AppMods, 221 %% for each modules in TestMods create a test if there is not a module 222 %% in AppMods that will trigger it 223 F = fun(TestMod) -> 224 M = filename:rootname(filename:basename(TestMod)), 225 MatchesTest = fun(AppMod) -> filename:rootname(filename:basename(AppMod)) ++ "_tests" == M end, 226 case lists:any(MatchesTest, AppMods) of 227 false -> {true, {module, list_to_atom(M)}}; 228 true -> false 229 end 230 end, 231 rebar_utils:filtermap(F, UniqueTestMods). 232 233inject_eunit_state(State, {ok, Tests}) -> 234 Apps = rebar_state:project_apps(State), 235 case inject_eunit_state(State, Apps, []) of 236 {ok, {NewState, ModdedApps}} -> 237 test_dirs(NewState, ModdedApps, Tests); 238 {error, _} = Error -> Error 239 end; 240inject_eunit_state(_State, Error) -> Error. 241 242inject_eunit_state(State, [App|Rest], Acc) -> 243 case inject(rebar_app_info:opts(App)) of 244 {error, _} = Error -> Error; 245 NewOpts -> 246 NewApp = rebar_app_info:opts(App, NewOpts), 247 inject_eunit_state(State, Rest, [NewApp|Acc]) 248 end; 249inject_eunit_state(State, [], Acc) -> 250 case inject(rebar_state:opts(State)) of 251 {error, _} = Error -> Error; 252 NewOpts -> {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}} 253 end. 254 255opts(Opts, Key, Default) -> 256 case rebar_opts:get(Opts, Key, Default) of 257 Vs when is_list(Vs) -> Vs; 258 Wrong -> 259 ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}}) 260 end. 261 262inject(Opts) -> erl_opts(Opts). 263 264erl_opts(Opts) -> 265 %% append `eunit_compile_opts` to app defined `erl_opts` 266 ErlOpts = opts(Opts, erl_opts, []), 267 EUnitOpts = opts(Opts, eunit_compile_opts, []), 268 case append(EUnitOpts, ErlOpts) of 269 {error, _} = Error -> Error; 270 NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts)) 271 end. 272 273first_files(Opts) -> 274 %% append `eunit_first_files` to app defined `erl_first_files` 275 FirstFiles = opts(Opts, erl_first_files, []), 276 EUnitFirstFiles = opts(Opts, eunit_first_files, []), 277 case append(EUnitFirstFiles, FirstFiles) of 278 {error, _} = Error -> Error; 279 NewFirstFiles -> eunit_macro(rebar_opts:set(Opts, erl_first_files, NewFirstFiles)) 280 end. 281 282eunit_macro(Opts) -> 283 ErlOpts = opts(Opts, erl_opts, []), 284 NewOpts = safe_define_eunit_macro(ErlOpts), 285 rebar_opts:set(Opts, erl_opts, NewOpts). 286 287safe_define_eunit_macro(Opts) -> 288 %% defining a compile macro twice results in an exception so 289 %% make sure 'EUNIT' is only defined once 290 case test_defined(Opts) of 291 true -> Opts; 292 false -> [{d, 'EUNIT'}|Opts] 293 end. 294 295test_defined([{d, 'EUNIT'}|_]) -> true; 296test_defined([{d, 'EUNIT', true}|_]) -> true; 297test_defined([_|Rest]) -> test_defined(Rest); 298test_defined([]) -> false. 299 300append({error, _} = Error, _) -> Error; 301append(_, {error, _} = Error) -> Error; 302append(A, B) -> A ++ B. 303 304test_dirs(State, Apps, []) -> rebar_state:project_apps(State, Apps); 305test_dirs(State, Apps, [{dir, Dir}|Rest]) -> 306 %% insert `Dir` into an app if relative, or the base state if not 307 %% app relative but relative to the root or not at all if outside 308 %% project scope 309 {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir), 310 test_dirs(NewState, NewApps, Rest); 311test_dirs(State, Apps, [{file, File}|Rest]) -> 312 Dir = filename:dirname(File), 313 {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir), 314 test_dirs(NewState, NewApps, Rest); 315test_dirs(State, Apps, [_|Rest]) -> test_dirs(State, Apps, Rest). 316 317maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) -> 318 case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of 319 {ok, Path} -> 320 Opts = inject_test_dir(rebar_app_info:opts(App), Path), 321 {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest}; 322 {error, badparent} -> 323 maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir) 324 end; 325maybe_inject_test_dir(State, AppAcc, [], Dir) -> 326 case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of 327 {ok, Path} -> 328 Opts = inject_test_dir(rebar_state:opts(State), Path), 329 {rebar_state:opts(State, Opts), AppAcc}; 330 {error, badparent} -> 331 {State, AppAcc} 332 end. 333 334inject_test_dir(Opts, Dir) -> 335 %% append specified test targets to app defined `extra_src_dirs` 336 ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []), 337 rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]). 338 339compile({error, _} = Error) -> Error; 340compile(State) -> 341 {ok, S} = rebar_prv_compile:do(State), 342 ok = maybe_cover_compile(S), 343 {ok, S}. 344 345validate_tests(State, {ok, Tests}) -> 346 gather_tests(fun(Elem) -> validate(State, Elem) end, Tests, []); 347validate_tests(_State, Error) -> Error. 348 349gather_tests(_F, [], Acc) -> {ok, lists:reverse(Acc)}; 350gather_tests(F, [Test|Rest], Acc) -> 351 case F(Test) of 352 ok -> gather_tests(F, Rest, [Test|Acc]); 353 %% failure mode, switch to gathering errors 354 {error, Error} -> gather_errors(F, Rest, [Error]) 355 end. 356 357gather_errors(_F, [], Acc) -> ?PRV_ERROR({eunit_test_errors, lists:reverse(Acc)}); 358gather_errors(F, [Test|Rest], Acc) -> 359 case F(Test) of 360 ok -> gather_errors(F, Rest, Acc); 361 {error, Error} -> gather_errors(F, Rest, [Error|Acc]) 362 end. 363 364validate(State, {application, App}) -> 365 validate_app(State, App); 366validate(State, {dir, Dir}) -> 367 validate_dir(State, Dir); 368validate(State, {file, File}) -> 369 validate_file(State, File); 370validate(State, {module, Module}) -> 371 validate_module(State, Module); 372validate(State, {suite, Module}) -> 373 validate_module(State, Module); 374validate(State, {generator, Module, Function}) -> 375 validate_generator(State, Module, Function); 376validate(State, Module) when is_atom(Module) -> 377 validate_module(State, Module); 378validate(State, Path) when is_list(Path) -> 379 case ec_file:is_dir(Path) of 380 true -> validate(State, {dir, Path}); 381 false -> validate(State, {file, Path}) 382 end; 383%% unrecognized tests should be included. if they're invalid eunit will error 384%% and rebar.config may contain arbitrarily complex tests that are effectively 385%% unvalidatable 386validate(_State, _Test) -> ok. 387 388validate_app(State, AppName) -> 389 ProjectApps = rebar_state:project_apps(State), 390 validate_app(State, ProjectApps, AppName). 391 392validate_app(_State, [], AppName) -> 393 {error, lists:concat(["Application `", AppName, "' not found in project."])}; 394validate_app(State, [App|Rest], AppName) -> 395 case AppName == binary_to_atom(rebar_app_info:name(App), unicode) of 396 true -> ok; 397 false -> validate_app(State, Rest, AppName) 398 end. 399 400validate_dir(State, Dir) -> 401 case ec_file:is_dir(filename:join([rebar_state:dir(State), Dir])) of 402 true -> ok; 403 false -> {error, lists:concat(["Directory `", Dir, "' not found."])} 404 end. 405 406validate_file(State, File) -> 407 case ec_file:exists(filename:join([rebar_state:dir(State), File])) of 408 true -> ok; 409 false -> {error, lists:concat(["File `", File, "' not found."])} 410 end. 411 412validate_module(_State, Module) -> 413 case code:which(Module) of 414 non_existing -> {error, lists:concat(["Module `", Module, "' not found in project."])}; 415 _ -> ok 416 end. 417 418validate_generator(State, Module, _Function) -> 419 validate_module(State, Module). 420 421resolve_eunit_opts(State) -> 422 {Opts, _} = rebar_state:command_parsed_args(State), 423 EUnitOpts = rebar_state:get(State, eunit_opts, []), 424 EUnitOpts1 = case proplists:get_value(verbose, Opts, false) of 425 true -> set_verbose(EUnitOpts); 426 false -> EUnitOpts 427 end, 428 EUnitOpts2 = case proplists:get_value(profile, Opts, false) of 429 true -> set_profile(EUnitOpts1); 430 false -> EUnitOpts1 431 end, 432 IsVerbose = lists:member(verbose, EUnitOpts2), 433 case proplists:get_value(eunit_formatters, Opts, not IsVerbose) of 434 true -> custom_eunit_formatters(EUnitOpts2); 435 false -> EUnitOpts2 436 end. 437 438custom_eunit_formatters(Opts) -> 439 ReportOpts = custom_eunit_report_options(Opts), 440 %% If `report` is already set then treat that like `eunit_formatters` is false 441 case lists:keymember(report, 1, Opts) of 442 true -> Opts; 443 false -> [no_tty, {report, {eunit_progress, ReportOpts}} | Opts] 444 end. 445 446custom_eunit_report_options(Opts) -> 447 case lists:member(profile, Opts) of 448 true -> [colored, profile]; 449 false -> [colored] 450 end. 451 452set_profile(Opts) -> 453 %% if `profile` is already set don't set it again 454 case lists:member(profile, Opts) of 455 true -> Opts; 456 false -> [profile] ++ Opts 457 end. 458 459set_verbose(Opts) -> 460 %% if `verbose` is already set don't set it again 461 case lists:member(verbose, Opts) of 462 true -> Opts; 463 false -> [verbose] ++ Opts 464 end. 465 466translate_paths(State, Tests) -> translate_paths(State, Tests, []). 467 468translate_paths(_State, [], Acc) -> lists:reverse(Acc); 469translate_paths(State, [{K, _} = Path|Rest], Acc) when K == file; K == dir -> 470 Apps = rebar_state:project_apps(State), 471 translate_paths(State, Rest, [translate(State, Apps, Path)|Acc]); 472translate_paths(State, [Test|Rest], Acc) -> 473 translate_paths(State, Rest, [Test|Acc]). 474 475translate(State, [App|Rest], {dir, Dir}) -> 476 case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of 477 {ok, Path} -> {dir, filename:join([rebar_app_info:out_dir(App), Path])}; 478 {error, badparent} -> translate(State, Rest, {dir, Dir}) 479 end; 480translate(State, [App|Rest], {file, FilePath}) -> 481 Dir = filename:dirname(FilePath), 482 File = filename:basename(FilePath), 483 case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of 484 {ok, Path} -> {file, filename:join([rebar_app_info:out_dir(App), Path, File])}; 485 {error, badparent} -> translate(State, Rest, {file, FilePath}) 486 end; 487translate(State, [], {dir, Dir}) -> 488 case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of 489 {ok, Path} -> {dir, filename:join([rebar_dir:base_dir(State), "extras", Path])}; 490 %% not relative, leave as is 491 {error, badparent} -> {dir, Dir} 492 end; 493translate(State, [], {file, FilePath}) -> 494 Dir = filename:dirname(FilePath), 495 File = filename:basename(FilePath), 496 case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of 497 {ok, Path} -> {file, filename:join([rebar_dir:base_dir(State), "extras", Path, File])}; 498 %% not relative, leave as is 499 {error, badparent} -> {file, FilePath} 500 end. 501 502apply_sys_config(State) -> 503 CfgSysCfg = case rebar_state:get(State, eunit_opts, []) of 504 Opts when is_list(Opts) -> 505 case proplists:get_value(sys_config, Opts, []) of 506 [] -> []; 507 L when is_list(hd(L)) -> L; 508 S when is_list(S) -> [S] 509 end; 510 _ -> 511 [] 512 end, 513 {RawOpts, _} = rebar_state:command_parsed_args(State), 514 SysCfgs = rebar_string:lexemes( 515 proplists:get_value(sys_config, RawOpts, ""), 516 [$,] 517 ) ++ CfgSysCfg, 518 Configs = lists:flatmap( 519 fun(Filename) -> rebar_file_utils:consult_config(State, Filename) end, 520 SysCfgs 521 ), 522 %% NB: load the applications (from user directories too) to support OTP < 17 523 %% to our best ability. 524 rebar_paths:set_paths([deps, plugins], State), 525 [application:load(Application) || Config <- Configs, {Application, _} <- Config], 526 rebar_utils:reread_config(Configs, [update_logger]), 527 ok. 528 529maybe_cover_compile(State) -> 530 {RawOpts, _} = rebar_state:command_parsed_args(State), 531 State1 = case proplists:get_value(cover, RawOpts, false) of 532 true -> rebar_state:set(State, cover_enabled, true); 533 false -> State 534 end, 535 rebar_prv_cover:maybe_cover_compile(State1). 536 537maybe_write_coverdata(State) -> 538 {RawOpts, _} = rebar_state:command_parsed_args(State), 539 State1 = case proplists:get_value(cover, RawOpts, false) of 540 true -> rebar_state:set(State, cover_enabled, true); 541 false -> State 542 end, 543 Name = proplists:get_value(cover_export_name, RawOpts, ?PROVIDER), 544 rebar_prv_cover:maybe_write_coverdata(State1, Name). 545 546handle_results(ok) -> ok; 547handle_results(error) -> 548 {error, unknown_error}; 549handle_results({error, Reason}) -> 550 {error, {error_running_tests, Reason}}. 551 552eunit_opts(_State) -> 553 [{app, undefined, "app", string, help(app)}, 554 {application, undefined, "application", string, help(app)}, 555 {cover, $c, "cover", boolean, help(cover)}, 556 {cover_export_name, undefined, "cover_export_name", string, help(cover_export_name)}, 557 {profile, $p, "profile", boolean, help(profile)}, 558 {dir, $d, "dir", string, help(dir)}, 559 {file, $f, "file", string, help(file)}, 560 {module, $m, "module", string, help(module)}, 561 {suite, $s, "suite", string, help(module)}, 562 {generator, $g, "generator", string, help(generator)}, 563 {verbose, $v, "verbose", boolean, help(verbose)}, 564 {name, undefined, "name", atom, help(name)}, 565 {sname, undefined, "sname", atom, help(sname)}, 566 {sys_config, undefined, "sys_config", string, help(sys_config)}, %% comma-separated list 567 {setcookie, undefined, "setcookie", atom, help(setcookie)}]. 568 569help(app) -> "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`."; 570help(cover) -> "Generate cover data. Defaults to false."; 571help(cover_export_name) -> "Base name of the coverdata file to write"; 572help(profile) -> "Show the slowest tests. Defaults to false."; 573help(dir) -> "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`."; 574help(file) -> "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`."; 575help(module) -> "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`."; 576help(generator) -> "Comma separated list of generators (the format is `module:function`) to load tests from. Equivalent to `[{generator, Module, Function}]`."; 577help(verbose) -> "Verbose output. Defaults to false."; 578help(name) -> "Gives a long name to the node"; 579help(sname) -> "Gives a short name to the node"; 580help(setcookie) -> "Sets the cookie if the node is distributed"; 581help(sys_config) -> "List of application config files". 582