1%% Licensed under the Apache License, Version 2.0 (the "License"); you may 2%% not use this file except in compliance with the License. You may obtain 3%% a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0> 4%% 5%% Unless required by applicable law or agreed to in writing, software 6%% distributed under the License is distributed on an "AS IS" BASIS, 7%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8%% See the License for the specific language governing permissions and 9%% limitations under the License. 10%% 11%% Alternatively, you may use this file under the terms of the GNU Lesser 12%% General Public License (the "LGPL") as published by the Free Software 13%% Foundation; either version 2.1, or (at your option) any later version. 14%% If you wish to allow use of your version of this file only under the 15%% terms of the LGPL, you should delete the provisions above and replace 16%% them with the notice and other provisions required by the LGPL; see 17%% <http://www.gnu.org/licenses/>. If you do not delete the provisions 18%% above, a recipient may use your version of this file under the terms of 19%% either the Apache License or the LGPL. 20%% 21%% @author Richard Carlsson <carlsson.richard@gmail.com> 22%% @copyright 2006 Richard Carlsson 23%% @private 24%% @see eunit 25%% @doc Interpretation of symbolic test representation 26 27-module(eunit_data). 28 29-include("eunit.hrl"). 30-include("eunit_internal.hrl"). 31 32-include_lib("kernel/include/file.hrl"). 33 34-export([iter_init/2, iter_next/1, iter_prev/1, iter_id/1, 35 enter_context/3, get_module_tests/1]). 36 37-import(lists, [foldr/3]). 38 39-define(TICKS_PER_SECOND, 1000). 40 41%% @type tests() = 42%% SimpleTest 43%% | [tests()] 44%% | moduleName() 45%% | {module, moduleName()} 46%% | {application, appName()} 47%% | {application, appName(), [term()]} 48%% | fileName() 49%% | {file, fileName()} 50%% | {string(), tests()} 51%% | {generator, () -> tests()} 52%% | {generator, M::moduleName(), F::functionName()} 53%% | {spawn, tests()} 54%% | {spawn, Node::atom(), tests()} 55%% | {timeout, T::number(), tests()} 56%% | {inorder, tests()} 57%% | {inparallel, tests()} 58%% | {inparallel, N::integer(), tests()} 59%% | {with, X::any(), [AbstractTestFunction]} 60%% | {setup, Where::local | spawn | {spawn, Node::atom()}, 61%% Setup::() -> (R::any()), 62%% Cleanup::(R::any()) -> any(), 63%% tests() | Instantiator 64%% } 65%% | {setup, Setup, Cleanup, tests() | Instantiator} 66%% | {setup, Where, Setup, tests() | Instantiator} 67%% | {setup, Setup, tests() | Instantiator} 68%% | {foreach, Where::local | spawn | {spawn, Node::atom()}, 69%% Setup::() -> (R::any()), 70%% Cleanup::(R::any()) -> any(), 71%% [tests() | Instantiator] 72%% } 73%% | {foreach, Setup, Cleanup, [tests() | Instantiator]} 74%% | {foreach, Where, Setup, [tests() | Instantiator]} 75%% | {foreach, Setup, [tests() | Instantiator]} 76%% | {foreachx, Where::local | spawn | {spawn, Node::atom()}, 77%% SetupX::(X::any()) -> (R::any()), 78%% CleanupX::(X::any(), R::any()) -> any(), 79%% Pairs::[{X::any(), 80%% (X::any(), R::any()) -> tests()}] 81%% } 82%% | {foreachx, SetupX, CleanupX, Pairs} 83%% | {foreachx, Where, SetupX, Pairs} 84%% | {foreachx, SetupX, Pairs} 85%% | {node, Node::atom(), tests() | Instantiator} 86%% | {node, Node, Args::string(), tests() | Instantiator} 87%% 88%% SimpleTest = TestFunction | {Line::integer(), SimpleTest} 89%% 90%% TestFunction = () -> any() 91%% | {test, M::moduleName(), F::functionName()} 92%% | {M::moduleName(), F::functionName()}. 93%% 94%% AbstractTestFunction = (X::any()) -> any() 95%% 96%% Instantiator = (R::any()) -> tests() 97%% | {with, [AbstractTestFunction]} 98%% 99%% Note that `{string(), ...}' is a short-hand for `{string(), {...}}' 100%% if the tuple contains more than two elements. 101%% 102%% @type moduleName() = atom() 103%% @type functionName() = atom() 104%% @type appName() = atom() 105%% @type fileName() = string() 106 107%% TODO: Can we mark up tests as known-failures? 108%% TODO: Is it possible to handle known timout/setup failures? 109%% TODO: Add diagnostic tests which never fail, but may cause warnings? 110 111%% --------------------------------------------------------------------- 112%% Abstract test set iterator 113 114-record(iter, 115 {prev = [], 116 next = [], 117 tests = [], 118 pos = 0, 119 parent = []}). 120 121%% @spec (tests(), [integer()]) -> testIterator() 122%% @type testIterator() 123 124iter_init(Tests, ParentID) -> 125 #iter{tests = Tests, parent = lists:reverse(ParentID)}. 126 127%% @spec (testIterator()) -> [integer()] 128 129iter_id(#iter{pos = N, parent = Ns}) -> 130 lists:reverse(Ns, [N]). 131 132%% @spec (testIterator()) -> none | {testItem(), testIterator()} 133 134iter_next(I = #iter{next = []}) -> 135 case next(I#iter.tests) of 136 {T, Tests} -> 137 {T, I#iter{prev = [T | I#iter.prev], 138 tests = Tests, 139 pos = I#iter.pos + 1}}; 140 none -> 141 none 142 end; 143iter_next(I = #iter{next = [T | Ts]}) -> 144 {T, I#iter{next = Ts, 145 prev = [T | I#iter.prev], 146 pos = I#iter.pos + 1}}. 147 148%% @spec (testIterator()) -> none | {testItem(), testIterator()} 149 150iter_prev(#iter{prev = []}) -> 151 none; 152iter_prev(#iter{prev = [T | Ts]} = I) -> 153 {T, I#iter{prev = Ts, 154 next = [T | I#iter.next], 155 pos = I#iter.pos - 1}}. 156 157 158%% --------------------------------------------------------------------- 159%% Concrete test set representation iterator 160 161%% @spec (tests()) -> none | {testItem(), tests()} 162%% @type testItem() = #test{} | #group{} 163%% @throws {bad_test, term()} 164%% | {generator_failed, {{M::atom(),F::atom(),A::integer()}, 165%% exception()}} 166%% | {no_such_function, mfa()} 167%% | {module_not_found, moduleName()} 168%% | {application_not_found, appName()} 169%% | {file_read_error, {Reason::atom(), Message::string(), 170%% fileName()}} 171 172next(Tests) -> 173 case eunit_lib:dlist_next(Tests) of 174 [T | Ts] -> 175 case parse(T) of 176 {data, T1} -> 177 next([T1 | Ts]); 178 T1 -> 179 {T1, Ts} 180 end; 181 [] -> 182 none 183 end. 184 185%% this returns either a #test{} or #group{} record, or {data, T} to 186%% signal that T has been substituted for the given representation 187 188parse({foreach, S, Fs}) when is_function(S), is_list(Fs) -> 189 parse({foreach, S, fun ok/1, Fs}); 190parse({foreach, S, C, Fs}) 191 when is_function(S), is_function(C), is_list(Fs) -> 192 parse({foreach, ?DEFAULT_SETUP_PROCESS, S, C, Fs}); 193parse({foreach, P, S, Fs}) 194 when is_function(S), is_list(Fs) -> 195 parse({foreach, P, S, fun ok/1, Fs}); 196parse({foreach, P, S, C, Fs} = T) 197 when is_function(S), is_function(C), is_list(Fs) -> 198 check_arity(S, 0, T), 199 check_arity(C, 1, T), 200 case Fs of 201 [F | Fs1] -> 202 {data, [{setup, P, S, C, F}, {foreach, P, S, C, Fs1}]}; 203 [] -> 204 {data, []} 205 end; 206parse({foreachx, S1, Ps}) when is_function(S1), is_list(Ps) -> 207 parse({foreachx, S1, fun ok/2, Ps}); 208parse({foreachx, S1, C1, Ps}) 209 when is_function(S1), is_function(C1), is_list(Ps) -> 210 parse({foreachx, ?DEFAULT_SETUP_PROCESS, S1, C1, Ps}); 211parse({foreachx, P, S1, Ps}) 212 when is_function(S1), is_list(Ps) -> 213 parse({foreachx, P, S1, fun ok/2, Ps}); 214parse({foreachx, P, S1, C1, Ps} = T) 215 when is_function(S1), is_function(C1), is_list(Ps) -> 216 check_arity(S1, 1, T), 217 check_arity(C1, 2, T), 218 case Ps of 219 [{X, F1} | Ps1] when is_function(F1) -> 220 check_arity(F1, 2, T), 221 S = fun () -> S1(X) end, 222 C = fun (R) -> C1(X, R) end, 223 F = fun (R) -> F1(X, R) end, 224 {data, [{setup, P, S, C, F}, {foreachx, P, S1, C1, Ps1}]}; 225 [_|_] -> 226 bad_test(T); 227 [] -> 228 {data, []} 229 end; 230parse({generator, F}) when is_function(F) -> 231 {module, M} = erlang:fun_info(F, module), 232 {name, N} = erlang:fun_info(F, name), 233 {arity, A} = erlang:fun_info(F, arity), 234 parse({generator, F, {M,N,A}}); 235parse({generator, F, {M,N,A}} = T) 236 when is_function(F), is_atom(M), is_atom(N), is_integer(A) -> 237 check_arity(F, 0, T), 238 %% use run_testfun/1 to handle wrapper exceptions 239 case eunit_test:run_testfun(F) of 240 {ok, T1} -> 241 case eunit_lib:is_not_test(T1) of 242 true -> throw({bad_generator, {{M,N,A}, T1}}); 243 false -> ok 244 end, 245 {data, T1}; 246 {error, {Class, Reason, Trace}} -> 247 throw({generator_failed, {{M,N,A}, {Class, Reason, Trace}}}) 248 end; 249parse({generator, M, F}) when is_atom(M), is_atom(F) -> 250 parse({generator, eunit_test:mf_wrapper(M, F), {M,F,0}}); 251parse({inorder, T}) -> 252 group(#group{tests = T, order = inorder}); 253parse({inparallel, T}) -> 254 parse({inparallel, 0, T}); 255parse({inparallel, N, T}) when is_integer(N), N >= 0 -> 256 group(#group{tests = T, order = {inparallel, N}}); 257parse({timeout, N, T}) when is_number(N), N >= 0 -> 258 group(#group{tests = T, timeout = round(N * ?TICKS_PER_SECOND)}); 259parse({spawn, T}) -> 260 group(#group{tests = T, spawn = local}); 261parse({spawn, N, T}) when is_atom(N) -> 262 group(#group{tests = T, spawn = {remote, N}}); 263parse({setup, S, I}) when is_function(S); is_list(S) -> 264 parse({setup, ?DEFAULT_SETUP_PROCESS, S, I}); 265parse({setup, S, C, I}) when is_function(S), is_function(C) -> 266 parse({setup, ?DEFAULT_SETUP_PROCESS, S, C, I}); 267parse({setup, P, S, I}) when is_function(S) -> 268 parse({setup, P, S, fun ok/1, I}); 269parse({setup, P, L, I} = T) when is_list(L) -> 270 check_setup_list(L, T), 271 {S, C} = eunit_test:multi_setup(L), 272 parse({setup, P, S, C, I}); 273parse({setup, P, S, C, I} = T) 274 when is_function(S), is_function(C), is_function(I) -> 275 check_arity(S, 0, T), 276 check_arity(C, 1, T), 277 case erlang:fun_info(I, arity) of 278 {arity, 0} -> 279 %% if I is nullary, it is a plain test 280 parse({setup, S, C, fun (_) -> I end}); 281 _ -> 282 %% otherwise, I must be an instantiator function 283 check_arity(I, 1, T), 284 case P of 285 local -> ok; 286 spawn -> ok; 287 {spawn, N} when is_atom(N) -> ok; 288 _ -> bad_test(T) 289 end, 290 group(#group{tests = I, 291 context = #context{setup = S, cleanup = C, 292 process = P}}) 293 end; 294parse({setup, P, S, C, {with, As}}) when is_list(As) -> 295 parse({setup, P, S, C, fun (X) -> {with, X, As} end}); 296parse({setup, P, S, C, T}) when is_function(S), is_function(C) -> 297 parse({setup, P, S, C, fun (_) -> T end}); 298parse({node, N, T}) when is_atom(N) -> 299 parse({node, N, "", T}); 300parse({node, N, A, T1}=T) when is_atom(N) -> 301 case eunit_lib:is_string(A) of 302 true -> 303 %% TODO: better stack traces for internal funs like these 304 parse({setup, 305 fun () -> 306 %% TODO: auto-start net_kernel if needed 307 StartedNet = false, 308%% The following is commented out because of problems when running 309%% eunit as part of the init sequence (from the command line): 310%% StartedNet = 311%% case whereis(net_kernel) of 312%% undefined -> 313%% M = list_to_atom(atom_to_list(N) 314%% ++ "_master"), 315%% case net_kernel:start([M]) of 316%% {ok, _} -> 317%% true; 318%% {error, E} -> 319%% throw({net_kernel_start, E}) 320%% end; 321%% _ -> false 322%% end, 323%% ?debugVal({started, StartedNet}), 324 {Name, Host} = eunit_lib:split_node(N), 325 {ok, Node} = slave:start_link(Host, Name, A), 326 {Node, StartedNet} 327 end, 328 fun ({Node, StopNet}) -> 329%% ?debugVal({stop, StopNet}), 330 slave:stop(Node), 331 case StopNet of 332 true -> net_kernel:stop(); 333 false -> ok 334 end 335 end, 336 T1}); 337 false -> 338 bad_test(T) 339 end; 340parse({module, M}) when is_atom(M) -> 341 {data, {"module '" ++ atom_to_list(M) ++ "'", get_module_tests(M)}}; 342parse({application, A}) when is_atom(A) -> 343 try parse({file, atom_to_list(A)++".app"}) 344 catch 345 {file_read_error,{enoent,_,_}} -> 346 case code:lib_dir(A) of 347 Dir when is_list(Dir) -> 348 %% add "ebin" if it exists, like code_server does 349 BinDir = filename:join(Dir, "ebin"), 350 case file:read_file_info(BinDir) of 351 {ok, #file_info{type=directory}} -> 352 parse({dir, BinDir}); 353 _ -> 354 parse({dir, Dir}) 355 end; 356 _ -> 357 throw({application_not_found, A}) 358 end 359 end; 360parse({application, A, Info}=T) when is_atom(A) -> 361 case proplists:get_value(modules, Info) of 362 Ms when is_list(Ms) -> 363 case [M || M <- Ms, not is_atom(M)] of 364 [] -> 365 {data, {"application '" ++ atom_to_list(A) ++ "'", Ms}}; 366 _ -> 367 bad_test(T) 368 end; 369 _ -> 370 bad_test(T) 371 end; 372parse({file, F} = T) when is_list(F) -> 373 case eunit_lib:is_string(F) of 374 true -> 375 {data, {"file \"" ++ F ++ "\"", get_file_tests(F)}}; 376 false -> 377 bad_test(T) 378 end; 379parse({dir, D}=T) when is_list(D) -> 380 case eunit_lib:is_string(D) of 381 true -> 382 {data, {"directory \"" ++ D ++ "\"", 383 get_directory_module_tests(D)}}; 384 false -> 385 bad_test(T) 386 end; 387parse({with, X, As}=T) when is_list(As) -> 388 case As of 389 [A | As1] -> 390 check_arity(A, 1, T), 391 {data, [{eunit_lib:fun_parent(A), fun () -> A(X) end}, 392 {with, X, As1}]}; 393 [] -> 394 {data, []} 395 end; 396parse({S, T1} = T) when is_list(S) -> 397 case eunit_lib:is_string(S) of 398 true -> 399 group(#group{tests = T1, desc = unicode:characters_to_binary(S)}); 400 false -> 401 bad_test(T) 402 end; 403parse({S, T1}) when is_binary(S) -> 404 group(#group{tests = T1, desc = S}); 405parse(T) when is_tuple(T), size(T) > 2, is_list(element(1, T)) -> 406 [S | Es] = tuple_to_list(T), 407 parse({S, list_to_tuple(Es)}); 408parse(T) when is_tuple(T), size(T) > 2, is_binary(element(1, T)) -> 409 [S | Es] = tuple_to_list(T), 410 parse({S, list_to_tuple(Es)}); 411parse(M) when is_atom(M) -> 412 parse({module, M}); 413parse(T) when is_list(T) -> 414 case eunit_lib:is_string(T) of 415 true -> 416 try parse({dir, T}) 417 catch 418 {file_read_error,{R,_,_}} 419 when R =:= enotdir; R =:= enoent -> 420 parse({file, T}) 421 end; 422 false -> 423 bad_test(T) 424 end; 425parse(T) -> 426 parse_simple(T). 427 428%% parse_simple always produces a #test{} record 429 430parse_simple({L, F}) when is_integer(L), L >= 0 -> 431 (parse_simple(F))#test{line = L}; 432parse_simple({{M,N,A}=Loc, F}) when is_atom(M), is_atom(N), is_integer(A) -> 433 (parse_simple(F))#test{location = Loc}; 434parse_simple(F) -> 435 parse_function(F). 436 437parse_function(F) when is_function(F) -> 438 check_arity(F, 0, F), 439 #test{f = F, location = eunit_lib:fun_parent(F)}; 440parse_function({test, M, F}) when is_atom(M), is_atom(F) -> 441 #test{f = eunit_test:mf_wrapper(M, F), location = {M, F, 0}}; 442parse_function({M, F}) when is_atom(M), is_atom(F) -> 443 %% {M,F} is now considered obsolete; use {test,M,F} instead 444 parse_function({test, M, F}); 445parse_function(F) -> 446 bad_test(F). 447 448check_arity(F, N, _) when is_function(F, N) -> 449 ok; 450check_arity(_, _, T) -> 451 bad_test(T). 452 453check_setup_list([{Tag, S, C} | Es], T) 454 when is_atom(Tag), is_function(S), is_function(C) -> 455 check_arity(S, 0, T), 456 check_arity(C, 1, T), 457 check_setup_list(Es, T); 458check_setup_list([{Tag, S} | Es], T) 459 when is_atom(Tag), is_function(S) -> 460 check_arity(S, 0, T), 461 check_setup_list(Es, T); 462check_setup_list([], _T) -> 463 ok; 464check_setup_list(_, T) -> 465 bad_test(T). 466 467bad_test(T) -> 468 throw({bad_test, T}). 469 470ok(_) -> ok. 471ok(_, _) -> ok. 472 473%% This does some look-ahead and folds nested groups and tests where 474%% possible. E.g., {String, Test} -> Test#test{desc = String}. 475 476group(#group{context = #context{}} = G) -> 477 %% leave as it is - the test body is an instantiator, which is not 478 %% suitable for lookahead (and anyway, properties of the setup 479 %% should not be merged with properties of its body, e.g. spawn) 480 G; 481group(#group{tests = T0, desc = Desc, order = Order, context = Context, 482 spawn = Spawn, timeout = Timeout} = G) -> 483 {T1, Ts} = lookahead(T0), 484 {T2, _} = lookahead(Ts), 485 case T1 of 486 #test{desc = Desc1, timeout = Timeout1} 487 when T2 =:= none, Spawn =:= undefined, Context =:= undefined, 488 ((Desc =:= undefined) or (Desc1 =:= undefined)), 489 ((Timeout =:= undefined) or (Timeout1 =:= undefined)) -> 490 %% a single test within a non-spawn/setup group: put the 491 %% information directly on the test; drop the order 492 T1#test{desc = join_properties(Desc, Desc1), 493 timeout = join_properties(Timeout, Timeout1)}; 494 495 #test{timeout = undefined} 496 when T2 =:= none, Timeout =/= undefined, Context =:= undefined -> 497 %% a single test without timeout, within a non-joinable 498 %% group with a timeout and no fixture: push the timeout to 499 %% the test 500 G#group{tests = {timeout, (Timeout div ?TICKS_PER_SECOND), T0}, 501 timeout = undefined}; 502 503 #group{desc = Desc1, order = Order1, context = Context1, 504 spawn = Spawn1, timeout = Timeout1} 505 when T2 =:= none, 506 ((Desc =:= undefined) or (Desc1 =:= undefined)), 507 ((Order =:= undefined) or (Order1 =:= undefined)), 508 ((Context =:= undefined) or (Context1 =:= undefined)), 509 ((Spawn =:= undefined) or (Spawn1 =:= undefined)), 510 ((Timeout =:= undefined) or (Timeout1 =:= undefined)) -> 511 %% two nested groups with non-conflicting properties 512 group(T1#group{desc = join_properties(Desc, Desc1), 513 order = join_properties(Order, Order1), 514 context = join_properties(Context, Context1), 515 spawn = join_properties(Spawn, Spawn1), 516 timeout = join_properties(Timeout, Timeout1)}); 517 518 #group{order = Order1, timeout = Timeout1} 519 when T2 =:= none -> 520 %% two nested groups that cannot be joined: try to push the 521 %% timeout and ordering properties to the inner group 522 push_order(Order, Order1, push_timeout(Timeout, Timeout1, G)); 523 524 _ -> 525 %% leave the group as it is and discard the lookahead 526 G 527 end. 528 529lookahead(T) -> 530 case next(T) of 531 {T1, Ts} -> {T1, Ts}; 532 none -> {none, []} 533 end. 534 535join_properties(undefined, X) -> X; 536join_properties(X, undefined) -> X. 537 538push_timeout(Timeout, undefined, G=#group{context=undefined}) 539 when Timeout =/= undefined -> 540 %% A timeout on a context (fixture) includes the setup/cleanup time 541 %% and must not be propagated into the body 542 G#group{tests = {timeout, (Timeout div ?TICKS_PER_SECOND), G#group.tests}, 543 timeout = undefined}; 544push_timeout(_, _, G) -> 545 G. 546 547push_order(inorder, undefined, G) -> 548 G#group{tests = {inorder, G#group.tests}, order = undefined}; 549push_order({inparallel, N}, undefined, G) -> 550 G#group{tests = {inparallel, N, G#group.tests}, order = undefined}; 551push_order(_, _, G) -> 552 G. 553 554%% --------------------------------------------------------------------- 555%% Extracting test funs from a module 556 557%% @throws {module_not_found, moduleName()} 558 559get_module_tests(M) -> 560 try M:module_info(exports) of 561 Es -> 562 Fs = get_module_tests_1(M, Es), 563 W = ?DEFAULT_MODULE_WRAPPER_NAME, 564 case lists:member({W,1}, Es) of 565 false -> Fs; 566 true -> {generator, fun () -> M:W(Fs) end} 567 end 568 catch 569 error:undef -> 570 throw({module_not_found, M}) 571 end. 572 573get_module_tests_1(M, Es) -> 574 Fs = testfuns(Es, M, ?DEFAULT_TEST_SUFFIX, 575 ?DEFAULT_GENERATOR_SUFFIX), 576 Name = atom_to_list(M), 577 case lists:suffix(?DEFAULT_TESTMODULE_SUFFIX, Name) of 578 false -> 579 Name1 = Name ++ ?DEFAULT_TESTMODULE_SUFFIX, 580 M1 = list_to_atom(Name1), 581 try get_module_tests(M1) of 582 Fs1 -> 583 Fs ++ [{"module '" ++ Name1 ++ "'", Fs1}] 584 catch 585 {module_not_found, M1} -> 586 Fs 587 end; 588 true -> 589 Fs 590 end. 591 592testfuns(Es, M, TestSuffix, GeneratorSuffix) -> 593 foldr(fun ({F, 0}, Fs) -> 594 N = atom_to_list(F), 595 case lists:suffix(TestSuffix, N) of 596 true -> 597 [{test, M, F} | Fs]; 598 false -> 599 case lists:suffix(GeneratorSuffix, N) of 600 true -> 601 [{generator, M, F} | Fs]; 602 false -> 603 Fs 604 end 605 end; 606 (_, Fs) -> 607 Fs 608 end, 609 [], 610 Es). 611 612 613%% --------------------------------------------------------------------- 614%% Getting a test set from a file (text file or object file) 615 616%% @throws {file_read_error, {Reason::atom(), Message::string(), 617%% fileName()}} 618 619get_file_tests(F) -> 620 case is_module_filename(F) of 621 true -> 622 %% look relative to current dir first 623 case file:read_file_info(F) of 624 {ok, #file_info{type=regular}} -> 625 objfile_test(F); 626 _ -> 627 %% (where_is_file/1 does not take a path argument) 628 case code:where_is_file(F) of 629 non_existing -> 630 %% this will produce a suitable error message 631 objfile_test(F); 632 Path -> 633 objfile_test(Path) 634 end 635 end; 636 false -> 637 eunit_lib:consult_file(F) 638 end. 639 640is_module_filename(F) -> 641 filename:extension(F) =:= code:objfile_extension(). 642 643objfile_test({M, File}) -> 644 {setup, 645 fun () -> 646 %% TODO: better error/stacktrace for this internal fun 647 code:purge(M), 648 {module,M} = code:load_abs(filename:rootname(File)), 649 ok 650 end, 651 {module, M}}; 652objfile_test(File) -> 653 objfile_test({objfile_module(File), File}). 654 655objfile_module(File) -> 656 try 657 {value, {module, M}} = lists:keysearch(module, 1, 658 beam_lib:info(File)), 659 M 660 catch 661 _:_ -> 662 throw({file_read_error, 663 {undefined, "extracting module name failed", File}}) 664 end. 665 666 667%% --------------------------------------------------------------------- 668%% Getting a set of module tests from the object files in a directory 669 670%% @throws {file_read_error, 671%% {Reason::atom(), Message::string(), fileName()}} 672 673get_directory_module_tests(D) -> 674 Ms = get_directory_modules(D), 675 %% for all 'm' in the set, remove 'm_tests' if present 676 F = fun ({M,_}, S) -> 677 Name = atom_to_list(M), 678 case lists:suffix(?DEFAULT_TESTMODULE_SUFFIX, Name) of 679 false -> 680 Name1 = Name ++ ?DEFAULT_TESTMODULE_SUFFIX, 681 M1 = list_to_atom(Name1), 682 dict:erase(M1, S); 683 true -> 684 S 685 end 686 end, 687 [objfile_test(Obj) 688 || Obj <- dict:to_list(lists:foldl(F, dict:from_list(Ms), Ms))]. 689 690%% TODO: handle packages (recursive search for files) 691get_directory_modules(D) -> 692 [begin 693 F1 = filename:join(D, F), 694 {objfile_module(F1), F1} 695 end 696 || F <- eunit_lib:list_dir(D), is_module_filename(F)]. 697 698 699 700%% --------------------------------------------------------------------- 701%% Entering a setup-context, with guaranteed cleanup. 702 703%% @spec (Tests::#context{}, Instantiate, Callback) -> any() 704%% Instantiate = (any()) -> tests() 705%% Callback = (tests()) -> any() 706%% @throws {context_error, Error, eunit_lib:exception()} 707%% Error = setup_failed | instantiation_failed | cleanup_failed 708 709enter_context(#context{setup = S, cleanup = C, process = P}, I, F) -> 710 F1 = case P of 711 local -> F; 712 spawn -> fun (X) -> F({spawn, X}) end; 713 {spawn, N} -> fun (T) -> F({spawn, N, T}) end 714 end, 715 eunit_test:enter_context(S, C, I, F1). 716 717 718-ifdef(TEST). 719generator_exported_() -> 720 generator(). 721 722generator() -> 723 T = ?_test(ok), 724 [T, T, T]. 725 726echo_proc() -> 727 receive {P,X} -> P ! X, echo_proc() end. 728 729ping(P) -> 730 P ! {self(),ping}, receive ping -> ok end. 731 732data_test_() -> 733 Setup = fun () -> spawn(fun echo_proc/0) end, 734 Cleanup = fun (Pid) -> exit(Pid, kill) end, 735 Fail = ?_test(throw(eunit)), 736 T = ?_test(ok), 737 Tests = [T,T,T], 738 [?_assertMatch(ok, eunit:test(T)), 739 ?_assertMatch(error, eunit:test(Fail)), 740 ?_assertMatch(ok, eunit:test({test, ?MODULE, trivial_test})), 741 ?_assertMatch(ok, eunit:test({generator, fun () -> Tests end})), 742 ?_assertMatch(ok, eunit:test({generator, fun generator/0})), 743 ?_assertMatch(ok, eunit:test({generator, ?MODULE, generator_exported_})), 744 ?_assertMatch(ok, eunit:test({inorder, Tests})), 745 ?_assertMatch(ok, eunit:test({inparallel, Tests})), 746 ?_assertMatch(ok, eunit:test({timeout, 10, Tests})), 747 ?_assertMatch(ok, eunit:test({spawn, Tests})), 748 ?_assertMatch(ok, eunit:test({setup, Setup, Cleanup, 749 fun (P) -> ?_test(ok = ping(P)) end})), 750 %%?_assertMatch(ok, eunit:test({node, test@localhost, Tests})), 751 ?_assertMatch(ok, eunit:test({module, eunit_lib})), 752 ?_assertMatch(ok, eunit:test(eunit_lib)), 753 ?_assertMatch(ok, eunit:test("examples/tests.txt")) 754 755 %%?_test({foreach, Setup, [T, T, T]}) 756 ]. 757 758trivial_test() -> 759 ok. 760 761trivial_generator_test_() -> 762 [?_test(ok)]. 763 764lazy_test_() -> 765 {spawn, [?_test(undefined = put(count, 0)), 766 lazy_gen(7), 767 ?_assertMatch(7, get(count))]}. 768 769-dialyzer({no_improper_lists, lazy_gen/1}). 770lazy_gen(N) -> 771 {generator, 772 fun () -> 773 if N > 0 -> 774 [?_test(put(count,1+get(count))) 775 | lazy_gen(N-1)]; 776 true -> 777 [] 778 end 779 end}. 780-endif. 781