1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2006-2025, University of Amsterdam 7 VU University Amsterdam 8 CWI, Amsterdam 9 SWI-Prolog Solutions b.v. 10 All rights reserved. 11 12 Redistribution and use in source and binary forms, with or without 13 modification, are permitted provided that the following conditions 14 are met: 15 16 1. Redistributions of source code must retain the above copyright 17 notice, this list of conditions and the following disclaimer. 18 19 2. Redistributions in binary form must reproduce the above copyright 20 notice, this list of conditions and the following disclaimer in 21 the documentation and/or other materials provided with the 22 distribution. 23 24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 POSSIBILITY OF SUCH DAMAGE. 36*/ 37 38:- module(plunit, 39 [ set_test_options/1, % +Options 40 begin_tests/1, % +Name 41 begin_tests/2, % +Name, +Options 42 end_tests/1, % +Name 43 run_tests/0, % Run all tests 44 run_tests/1, % +Tests 45 run_tests/2, % +Tests, +Options 46 load_test_files/1, % +Options 47 running_tests/0, % Prints currently running test 48 current_test/5, % ?Unit,?Test,?Line,?Body,?Options 49 current_test_unit/2, % ?Unit,?Options 50 test_report/1 % +What 51 ]).
59:- autoload(library(statistics), [call_time/2]). 60:- autoload(library(apply), 61 [maplist/3, include/3, maplist/2, foldl/4, partition/4]). 62:- autoload(library(lists), [member/2, append/2, flatten/2, append/3]). 63:- autoload(library(option), [ option/3, option/2, select_option/3 ]). 64:- autoload(library(ordsets), [ord_intersection/3]). 65:- autoload(library(error), [must_be/2, domain_error/2]). 66:- autoload(library(aggregate), [aggregate_all/3]). 67:- autoload(library(streams), [with_output_to/3]). 68:- autoload(library(ansi_term), [ansi_format/3]). 69:- if(exists_source(library(time))). 70:- autoload(library(time), [call_with_time_limit/2]). 71:- endif. 72 73:- public 74 unit_module/2. 75 76:- meta_predicate 77 valid_options(, ), 78 count(, ). 79 80 /******************************* 81 * CONDITIONAL COMPILATION * 82 *******************************/ 83 84swi :- catch(current_prolog_flag(dialect, swi), _, fail). 85sicstus :- catch(current_prolog_flag(dialect, sicstus), _, fail). 86 87throw_error(Error_term,Impldef) :- 88 throw(error(Error_term,context(Impldef,_))). 89 90:- set_prolog_flag(generate_debug_info, false). 91current_test_flag(optimise, Value) => 92 current_prolog_flag(optimise, Value). 93current_test_flag(occurs_check, Value) => 94 ( current_prolog_flag(plunit_occurs_check, Value0) 95 -> Value = Value0 96 ; current_prolog_flag(occurs_check, Value) 97 ). 98current_test_flag(Name, Value), atom(Name) => 99 atom_concat(plunit_, Name, Flag), 100 current_prolog_flag(Flag, Value). 101current_test_flag(Name, Value), var(Name) => 102 global_test_option(Opt, _, _Type, _Default), 103 functor(Opt, Name, 1), 104 current_test_flag(Name, Value). 105 106set_test_flag(Name, Value) :- 107 Opt =.. [Name, Value], 108 global_test_option(Opt), 109 !, 110 atom_concat(plunit_, Name, Flag), 111 set_prolog_flag(Flag, Value). 112set_test_flag(Name, _) :- 113 domain_error(test_flag, Name). 114 115current_test_flags(Flags) :- 116 findall(Flag, current_test_flag(Flag), Flags). 117 118current_test_flag(Opt) :- 119 current_test_flag(Name, Value), 120 Opt =.. [Name, Value]. 121 122% ensure expansion to avoid tracing 123goal_expansion(forall(C,A), 124 \+ (C, \+ A)). 125goal_expansion(current_module(Module,File), 126 module_property(Module, file(File))). 127 128 129 /******************************* 130 * IMPORTS * 131 *******************************/ 132 133:- initialization init_flags. 134 135init_flags :- 136 ( global_test_option(Option, _Value, Type, Default), 137 Default \== (-), 138 Option =.. [Name,_], 139 atom_concat(plunit_, Name, Flag), 140 flag_type(Type, FlagType), 141 create_prolog_flag(Flag, Default, [type(FlagType), keep(true)]), 142 fail 143 ; true 144 ). 145 146flag_type(boolean, FlagType) => FlagType = boolean. 147flag_type(Type, FlagType), Type = oneof(Atoms), maplist(atom, Atoms) => 148 FlagType = Type. 149flag_type(oneof(_), FlagType) => FlagType = term. 150flag_type(positive_integer, FlagType) => FlagType = integer. 151flag_type(number, FlagType) => FlagType = float.
never, always, normal (only if not optimised)manual, make
or make(all).tty or log. tty uses terminal
control to overwrite successful tests, allowing the
user to see the currently running tests and output
from failed tests. This is the default of the output
is a tty. log prints a full log of the executed
tests and their result and is intended for non-interactive
usage.always, emit all output as it is produced, if never,
suppress all output and if on_failure, emit the output
if the test fails.occurs_check flag during
testing.true (default =false), cleanup report at the end
of run_tests/1. Used to improve cooperation with
memory debuggers such as dmalloc.203set_test_options(Options) :- 204 flatten([Options], List), 205 maplist(set_test_option, List). 206 207set_test_option(sto(true)) => 208 print_message(warning, plunit(sto(true))). 209set_test_option(jobs(Jobs)) => 210 must_be(positive_integer, Jobs), 211 set_test_option_flag(jobs(Jobs)). 212set_test_option(Option), 213 compound(Option), global_test_option(Option) => 214 set_test_option_flag(Option). 215set_test_option(Option) => 216 domain_error(option, Option). 217 218global_test_option(Opt) :- 219 global_test_option(Opt, Value, Type, _Default), 220 must_be(Type, Value). 221 222global_test_option(load(Load), Load, oneof([never,always,normal]), normal). 223global_test_option(output(Cond), Cond, oneof([always,on_failure]), on_failure). 224global_test_option(format(Feedback), Feedback, oneof([tty,log]), tty). 225global_test_option(silent(Silent), Silent, boolean, false). 226global_test_option(show_blocked(Blocked), Blocked, boolean, false). 227global_test_option(run(When), When, oneof([manual,make,make(all)]), make). 228global_test_option(occurs_check(Mode), Mode, oneof([false,true,error]), -). 229global_test_option(cleanup(Bool), Bool, boolean, true). 230global_test_option(jobs(Count), Count, positive_integer, 1). 231global_test_option(timeout(Number), Number, number, 3600). 232 233set_test_option_flag(Option) :- 234 Option =.. [Name, Value], 235 set_test_flag(Name, Value).
241loading_tests :- 242 current_test_flag(load, Load), 243 ( Load == always 244 -> true 245 ; Load == normal, 246 \+ current_test_flag(optimise, true) 247 ). 248 249 /******************************* 250 * MODULE * 251 *******************************/ 252 253:- thread_local 254 loading_unit/4. % Unit, Module, File, OldSource 255:- dynamic 256 current_unit/4, % Unit, Module, Context, Options 257 test_file_for/2. % ?TestFile, ?PrologFile
end_tests(UnitName).265begin_tests(Unit) :- 266 begin_tests(Unit, []). 267 268begin_tests(Unit, Options) :- 269 must_be(atom, Unit), 270 map_sto_option(Options, Options1), 271 valid_options(test_set_option, Options1), 272 make_unit_module(Unit, Name), 273 source_location(File, Line), 274 begin_tests(Unit, Name, File:Line, Options1). 275 276map_sto_option(Options0, Options) :- 277 select_option(sto(Mode), Options0, Options1), 278 !, 279 map_sto(Mode, Flag), 280 Options = [occurs_check(Flag)|Options1]. 281map_sto_option(Options, Options). 282 283map_sto(rational_trees, Flag) => Flag = false. 284map_sto(finite_trees, Flag) => Flag = true. 285map_sto(Mode, _) => domain_error(sto, Mode). 286 287 288:- if(swi). 289begin_tests(Unit, Name, File:Line, Options) :- 290 loading_tests, 291 !, 292 '$set_source_module'(Context, Context), 293 ( current_unit(Unit, Name, Context, Options) 294 -> true 295 ; retractall(current_unit(Unit, Name, _, _)), 296 assert(current_unit(Unit, Name, Context, Options)) 297 ), 298 '$set_source_module'(Old, Name), 299 '$declare_module'(Name, test, Context, File, Line, false), 300 discontiguous(Name:'unit test'/4), 301 '$set_predicate_attribute'(Name:'unit test'/4, trace, false), 302 discontiguous(Name:'unit body'/2), 303 asserta(loading_unit(Unit, Name, File, Old)). 304begin_tests(Unit, Name, File:_Line, _Options) :- 305 '$set_source_module'(Old, Old), 306 asserta(loading_unit(Unit, Name, File, Old)). 307 308:- else. 309 310% we cannot use discontiguous as a goal in SICStus Prolog. 311 312userterm_expansion((:- begin_tests(Set)), 313 [ (:- begin_tests(Set)), 314 (:- discontiguous(test/2)), 315 (:- discontiguous('unit body'/2)), 316 (:- discontiguous('unit test'/4)) 317 ]). 318 319begin_tests(Unit, Name, File:_Line, Options) :- 320 loading_tests, 321 !, 322 ( current_unit(Unit, Name, _, Options) 323 -> true 324 ; retractall(current_unit(Unit, Name, _, _)), 325 assert(current_unit(Unit, Name, -, Options)) 326 ), 327 asserta(loading_unit(Unit, Name, File, -)). 328begin_tests(Unit, Name, File:_Line, _Options) :- 329 asserta(loading_unit(Unit, Name, File, -)). 330 331:- endif.
340end_tests(Unit) :- 341 loading_unit(StartUnit, _, _, _), 342 !, 343 ( Unit == StartUnit 344 -> once(retract(loading_unit(StartUnit, _, _, Old))), 345 '$set_source_module'(_, Old) 346 ; throw_error(context_error(plunit_close(Unit, StartUnit)), _) 347 ). 348end_tests(Unit) :- 349 throw_error(context_error(plunit_close(Unit, -)), _).
354:- if(swi). 355 356unit_module(Unit, Module) :- 357 atom_concat('plunit_', Unit, Module). 358 359make_unit_module(Unit, Module) :- 360 unit_module(Unit, Module), 361 ( current_module(Module), 362 \+ current_unit(_, Module, _, _), 363 predicate_property(Module:H, _P), 364 \+ predicate_property(Module:H, imported_from(_M)) 365 -> throw_error(permission_error(create, plunit, Unit), 366 'Existing module') 367 ; true 368 ). 369 370:- else. 371 372:- dynamic 373 unit_module_store/2. 374 375unit_module(Unit, Module) :- 376 unit_module_store(Unit, Module), 377 !. 378 379make_unit_module(Unit, Module) :- 380 prolog_load_context(module, Module), 381 assert(unit_module_store(Unit, Module)). 382 383:- endif. 384 385 /******************************* 386 * EXPANSION * 387 *******************************/
test(Name, Options) :- Body into a clause for
'unit test'/4 and 'unit body'/2.394expand_test(Name, Options0, Body, 395 [ 'unit test'(Name, Line, Options, Module:'unit body'(Id, Vars)), 396 ('unit body'(Id, Vars) :- !, Body) 397 ]) :- 398 source_location(_File, Line), 399 prolog_load_context(module, Module), 400 ( prolog_load_context(variable_names, Bindings) 401 -> true 402 ; Bindings = [] 403 ), 404 atomic_list_concat([Name, '@line ', Line], Id), 405 term_variables(Options0, OptionVars0), sort(OptionVars0, OptionVars), 406 term_variables(Body, BodyVars0), sort(BodyVars0, BodyVars), 407 ord_intersection(OptionVars, BodyVars, VarList), 408 Vars =.. [vars|VarList], 409 ( is_list(Options0) % allow for single option without list 410 -> Options1 = Options0 411 ; Options1 = [Options0] 412 ), 413 maplist(expand_option(Bindings), Options1, Options2), 414 join_true_options(Options2, Options3), 415 map_sto_option(Options3, Options4), 416 valid_options(test_option, Options4), 417 valid_test_mode(Options4, Options). 418 419expand_option(_, Var, _) :- 420 var(Var), 421 !, 422 throw_error(instantiation_error,_). 423expand_option(Bindings, Cmp, true(Cond)) :- 424 cmp(Cmp), 425 !, 426 var_cmp(Bindings, Cmp, Cond). 427expand_option(_, error(X), throws(error(X, _))) :- !. 428expand_option(_, exception(X), throws(X)) :- !. % SICStus 4 compatibility 429expand_option(_, error(F,C), throws(error(F,C))) :- !. % SICStus 4 compatibility 430expand_option(_, true, true(true)) :- !. 431expand_option(_, O, O). 432 433cmp(_ == _). 434cmp(_ = _). 435cmp(_ =@= _). 436cmp(_ =:= _). 437 438var_cmp(Bindings, Expr, cmp(Name, Expr)) :- 439 arg(_, Expr, Var), 440 var(Var), 441 member(Name=V, Bindings), 442 V == Var, 443 !. 444var_cmp(_, Expr, Expr). 445 446join_true_options(Options0, Options) :- 447 partition(true_option, Options0, True, Rest), 448 True \== [], 449 !, 450 maplist(arg(1), True, Conds0), 451 flatten(Conds0, Conds), 452 Options = [true(Conds)|Rest]. 453join_true_options(Options, Options). 454 455true_option(true(_)). 456 457valid_test_mode(Options0, Options) :- 458 include(test_mode, Options0, Tests), 459 ( Tests == [] 460 -> Options = [true([true])|Options0] 461 ; Tests = [_] 462 -> Options = Options0 463 ; throw_error(plunit(incompatible_options, Tests), _) 464 ). 465 466test_mode(true(_)). 467test_mode(all(_)). 468test_mode(set(_)). 469test_mode(fail). 470test_mode(throws(_)).
475expand(end_of_file, _) :- 476 loading_unit(Unit, _, _, _), 477 !, 478 end_tests(Unit), % warn? 479 fail. 480expand((:-end_tests(_)), _) :- 481 !, 482 fail. 483expand(_Term, []) :- 484 \+ loading_tests. 485expand((test(Name) :- Body), Clauses) :- 486 !, 487 expand_test(Name, [], Body, Clauses). 488expand((test(Name, Options) :- Body), Clauses) :- 489 !, 490 expand_test(Name, Options, Body, Clauses). 491expand(test(Name), _) :- 492 !, 493 throw_error(existence_error(body, test(Name)), _). 494expand(test(Name, _Options), _) :- 495 !, 496 throw_error(existence_error(body, test(Name)), _). 497 498:- multifile 499 system:term_expansion/2. 500 501systemterm_expansion(Term, Expanded) :- 502 ( loading_unit(_, _, File, _) 503 -> source_location(ThisFile, _), 504 ( File == ThisFile 505 -> true 506 ; source_file_property(ThisFile, included_in(File, _)) 507 ), 508 expand(Term, Expanded) 509 ). 510 511 512 /******************************* 513 * OPTIONS * 514 *******************************/
523valid_options(Pred, Options) :- 524 must_be(list, Options), 525 verify_options(Options, Pred). 526 527verify_options([], _). 528verify_options([H|T], Pred) :- 529 ( call(Pred, H) 530 -> verify_options(T, Pred) 531 ; throw_error(domain_error(Pred, H), _) 532 ). 533 534valid_options(Pred, Options0, Options, Rest) :- 535 must_be(list, Options0), 536 partition(Pred, Options0, Options, Rest).
test(Name, Options).542test_option(Option) :- 543 test_set_option(Option), 544 !. 545test_option(true(_)). 546test_option(fail). 547test_option(throws(_)). 548test_option(all(_)). 549test_option(set(_)). 550test_option(nondet). 551test_option(fixme(_)). 552test_option(forall(X)) :- 553 must_be(callable, X). 554test_option(timeout(Seconds)) :- 555 must_be(number, Seconds).
begin_tests(Name,
Options).562test_set_option(blocked(X)) :- 563 must_be(ground, X). 564test_set_option(condition(X)) :- 565 must_be(callable, X). 566test_set_option(setup(X)) :- 567 must_be(callable, X). 568test_set_option(cleanup(X)) :- 569 must_be(callable, X). 570test_set_option(occurs_check(V)) :- 571 must_be(oneof([false,true,error]), V). 572test_set_option(concurrent(V)) :- 573 must_be(boolean, V), 574 print_message(informational, plunit(concurrent)). 575test_set_option(timeout(Seconds)) :- 576 must_be(number, Seconds). 577 578 /******************************* 579 * UTIL * 580 *******************************/ 581 582:- meta_predicate 583 reify_tmo(, , ), 584 reify(, ), 585 capture_output(,), 586 capture_output(,,), 587 got_messages(,).
591:- if(current_predicate(call_with_time_limit/2)). 592reify_tmo(Goal, Result, Options) :- 593 option(timeout(Time), Options), 594 Time > 0, 595 !, 596 reify(call_with_time_limit(Time, Goal), Result0), 597 ( Result0 = throw(time_limit_exceeded) 598 -> Result = throw(time_limit_exceeded(Time)) 599 ; Result = Result0 600 ). 601:- endif. 602reify_tmo(Goal, Result, _Options) :- 603 reify(Goal, Result).
true, false or
throw(E).
610reify(Goal, Result) :-
611 ( catch(Goal, E, true)
612 -> ( var(E)
613 -> Result = true
614 ; Result = throw(E)
615 )
616 ; Result = false
617 ).626capture_output(Goal, Output) :- 627 current_test_flag(output, OutputMode), 628 capture_output(Goal, Output, [output(OutputMode)]). 629 630capture_output(Goal, Msgs-Output, Options) :- 631 option(output(How), Options, always), 632 ( How == always 633 -> call(Goal), 634 Msgs = false % irrelavant 635 ; with_output_to(string(Output), got_messages(Goal, Msgs), 636 [ capture([user_output, user_error]), 637 color(true) 638 ]) 639 ).
643got_messages(Goal, Result) :- 644 ( current_prolog_flag(on_warning, status) 645 ; current_prolog_flag(on_error, status) 646 ), !, 647 nb_delete(plunit_got_message), 648 setup_call_cleanup( 649 asserta(( user:thread_message_hook(_Term, Kind, _Lines) :- 650 got_message(Kind), fail), Ref), 651 Goal, 652 erase(Ref)), 653 ( nb_current(plunit_got_message, true) 654 -> Result = true 655 ; Result = false 656 ). 657got_messages(Goal, false) :- 658 call(Goal). 659 660:- public got_message/1. 661got_message(warning) :- 662 current_prolog_flag(on_warning, status), !, 663 nb_setval(plunit_got_message, true). 664got_message(error) :- 665 current_prolog_flag(on_error, status), !, 666 nb_setval(plunit_got_message, true). 667 668 669 /******************************* 670 * RUNNING TOPLEVEL * 671 *******************************/ 672 673:- dynamic 674 output_streams/2, % Output, Error 675 test_count/1, % Count 676 passed/5, % Unit, Test, Line, Det, Time 677 failed/5, % Unit, Test, Line, Reason, Time 678 timeout/5, % Unit, Test, Line, Limit, Time 679 failed_assertion/7, % Unit, Test, Line, ALoc, STO, Reason, Goal 680 blocked/4, % Unit, Test, Line, Reason 681 fixme/5, % Unit, Test, Line, Reason, Status 682 running/5, % Unit, Test, Line, STO, Thread 683 forall_failures/2. % Nth, Failures
The predicate run_tests/2 is synchronized. Concurrent testing may be achieved using the relevant options. See set_test_options/1. Options are passed to set_test_options/1. In addition the following options are processed:
715run_tests :- 716 run_tests(all). 717 718run_tests(Set) :- 719 run_tests(Set, []). 720 721run_tests(all, Options) :- 722 !, 723 findall(Unit, current_test_unit(Unit,_), Units), 724 run_tests(Units, Options). 725run_tests(Set, Options) :- 726 valid_options(global_test_option, Options, Global, Rest), 727 current_test_flags(Old), 728 setup_call_cleanup( 729 set_test_options(Global), 730 ( flatten([Set], List), 731 maplist(runnable_tests, List, Units), 732 with_mutex(plunit, run_tests_sync(Units, Rest)) 733 ), 734 set_test_options(Old)). 735 736run_tests_sync(Units0, Options) :- 737 cleanup, 738 count_tests(Units0, Units, Count), 739 asserta(test_count(Count)), 740 save_output_state, 741 setup_call_cleanup( 742 setup_trap_assertions(Ref), 743 call_time(setup_jobs_and_run_units(Count, Units, Summary, Options), 744 Time), 745 report_and_cleanup(Ref, Time, Options)), 746 ( option(summary(Summary), Options) 747 -> true 748 ; test_summary_passed(Summary) % fail if some test failed 749 ). 750 751setup_jobs_and_run_units(Count, Units, Summary, Options) :- 752 setup_call_cleanup( 753 setup_jobs(Count), 754 ( run_units(Units, Options), 755 test_summary(_All, Summary) 756 ), 757 cleanup_jobs).
764report_and_cleanup(Ref, Time, Options) :-
765 cleanup_trap_assertions(Ref),
766 report(Time, Options),
767 cleanup_after_test.
774run_units(Units, _Options) :-
775 maplist(schedule_unit, Units),
776 job_wait(_).Unit:Tests lists, where
blocked tests or tests whose condition fails are already removed.
Each test in Tests is a term @(Test,Line), which serves as a
unique identifier of the test.785:- det(runnable_tests/2). 786runnable_tests(Spec, Unit:RunnableTests) :- 787 unit_from_spec(Spec, Unit, Tests, Module, UnitOptions), 788 ( option(blocked(Reason), UnitOptions) 789 -> info(plunit(blocked(unit(Unit, Reason)))), 790 RunnableTests = [] 791 ; \+ condition(Module, unit(Unit), UnitOptions) 792 -> RunnableTests = [] 793 ; var(Tests) 794 -> findall(TestID, 795 runnable_test(Unit, _Test, Module, TestID), 796 RunnableTests) 797 ; flatten([Tests], TestList), 798 findall(TestID, 799 ( member(Test, TestList), 800 runnable_test(Unit,Test,Module, TestID) 801 ), 802 RunnableTests) 803 ). 804 805runnable_test(Unit, Name, Module, @(Test,Line)) :- 806 current_test(Unit, Name, Line, _Body, TestOptions), 807 ( option(blocked(Reason), TestOptions) 808 -> Test = blocked(Name, Reason) 809 ; condition(Module, test(Unit,Name,Line), TestOptions), 810 Test = Name 811 ). 812 813unit_from_spec(Unit0:Tests0, Unit, Tests, Module, Options), atom(Unit0) => 814 Unit = Unit0, 815 Tests = Tests0, 816 ( current_unit(Unit, Module, _Supers, Options) 817 -> true 818 ; throw_error(existence_error(unit_test, Unit), _) 819 ). 820unit_from_spec(Unit0, Unit, _, Module, Options), atom(Unit0) => 821 Unit = Unit0, 822 ( current_unit(Unit, Module, _Supers, Options) 823 -> true 824 ; throw_error(existence_error(unit_test, Unit), _) 825 ).
forall(Generator, Test) counts
as a single test. During the execution, the concrete tests of the
forall are considered "sub tests".833count_tests(Units0, Units, Count) :- 834 count_tests(Units0, Units, 0, Count). 835 836count_tests([], T, C0, C) => 837 T = [], 838 C = C0. 839count_tests([_:[]|T0], T, C0, C) => 840 count_tests(T0, T, C0, C). 841count_tests([Unit:Tests|T0], T, C0, C) => 842 partition(is_blocked, Tests, Blocked, Use), 843 maplist(assert_blocked(Unit), Blocked), 844 ( Use == [] 845 -> count_tests(T0, T, C0, C) 846 ; length(Use, N), 847 C1 is C0+N, 848 T = [Unit:Use|T1], 849 count_tests(T0, T1, C1, C) 850 ). 851 852is_blocked(@(blocked(_,_),_)) => true. 853is_blocked(_) => fail. 854 855assert_blocked(Unit, @(blocked(Test, Reason), Line)) => 856 assert(blocked(Unit, Test, Line, Reason)).
863run_unit(_Unit:[]) => 864 true. 865run_unit(Unit:Tests) => 866 unit_module(Unit, Module), 867 unit_options(Unit, UnitOptions), 868 ( setup(Module, unit(Unit), UnitOptions) 869 -> begin_unit(Unit), 870 call_time(run_unit_2(Unit, Tests), Time), 871 test_summary(Unit, Summary), 872 end_unit(Unit, Summary.put(time, Time)), 873 cleanup(Module, UnitOptions) 874 ; job_info(end(unit(Unit, _{error:setup_failed}))) 875 ). 876 877begin_unit(Unit) :- 878 job_info(begin(unit(Unit))), 879 job_feedback(informational, begin(Unit)). 880 881end_unit(Unit, Summary) :- 882 job_info(end(unit(Unit, Summary))), 883 job_feedback(informational, end(Unit, Summary)). 884 885run_unit_2(Unit, Tests) :- 886 forall(member(Test, Tests), 887 run_test(Unit, Test)). 888 889 890unit_options(Unit, Options) :- 891 current_unit(Unit, _Module, _Supers, Options). 892 893 894cleanup :- 895 set_flag(plunit_test, 1), 896 retractall(output_streams(_,_)), 897 retractall(test_count(_)), 898 retractall(passed(_, _, _, _, _)), 899 retractall(failed(_, _, _, _, _)), 900 retractall(timeout(_, _, _, _, _)), 901 retractall(failed_assertion(_, _, _, _, _, _, _)), 902 retractall(blocked(_, _, _, _)), 903 retractall(fixme(_, _, _, _, _)), 904 retractall(running(_,_,_,_,_)), 905 retractall(forall_failures(_,_)). 906 907cleanup_after_test :- 908 ( current_test_flag(cleanup, true) 909 -> cleanup 910 ; true 911 ).
918run_tests_in_files(Files) :- 919 findall(Unit, unit_in_files(Files, Unit), Units), 920 ( Units == [] 921 -> true 922 ; run_tests(Units) 923 ). 924 925unit_in_files(Files, Unit) :- 926 is_list(Files), 927 !, 928 member(F, Files), 929 absolute_file_name(F, Source, 930 [ file_type(prolog), 931 access(read), 932 file_errors(fail) 933 ]), 934 unit_file(Unit, Source). 935 936 937 /******************************* 938 * HOOKING MAKE/0 * 939 *******************************/
945make_run_tests(Files) :- 946 current_test_flag(run, When), 947 ( When == make 948 -> run_tests_in_files(Files) 949 ; When == make(all) 950 -> run_tests 951 ; true 952 ). 953 954 /******************************* 955 * ASSERTION HANDLING * 956 *******************************/ 957 958:- if(swi). 959 960:- dynamic prolog:assertion_failed/2. 961 962setup_trap_assertions(Ref) :- 963 asserta((prolog:assertion_failed(Reason, Goal) :- 964 test_assertion_failed(Reason, Goal)), 965 Ref). 966 967cleanup_trap_assertions(Ref) :- 968 erase(Ref). 969 970test_assertion_failed(Reason, Goal) :- 971 thread_self(Me), 972 running(Unit, Test, Line, Progress, Me), 973 ( catch(get_prolog_backtrace(10, Stack), _, fail), 974 assertion_location(Stack, AssertLoc) 975 -> true 976 ; AssertLoc = unknown 977 ), 978 report_failed_assertion(Unit:Test, Line, AssertLoc, 979 Progress, Reason, Goal), 980 assert_cyclic(failed_assertion(Unit, Test, Line, AssertLoc, 981 Progress, Reason, Goal)). 982 983assertion_location(Stack, File:Line) :- 984 append(_, [AssertFrame,CallerFrame|_], Stack), 985 prolog_stack_frame_property(AssertFrame, 986 predicate(prolog_debug:assertion/1)), 987 !, 988 prolog_stack_frame_property(CallerFrame, location(File:Line)). 989 990report_failed_assertion(UnitTest, Line, AssertLoc, 991 Progress, Reason, Goal) :- 992 print_message( 993 error, 994 plunit(failed_assertion(UnitTest, Line, AssertLoc, 995 Progress, Reason, Goal))). 996 997:- else. 998 999setup_trap_assertions(_). 1000cleanup_trap_assertions(_). 1001 1002:- endif. 1003 1004 1005 /******************************* 1006 * RUNNING A TEST * 1007 *******************************/
1013run_test(Unit, @(Test,Line)) :-
1014 unit_module(Unit, Module),
1015 Module:'unit test'(Test, Line, TestOptions, Body),
1016 unit_options(Unit, UnitOptions),
1017 run_test(Unit, Test, Line, UnitOptions, TestOptions, Body).forall(Generator, Test)1023run_test(Unit, Name, Line, UnitOptions, Options, Body) :- 1024 option(forall(Generator), Options), 1025 !, 1026 unit_module(Unit, Module), 1027 start_test(Unit, @(Name,Line), Nth), 1028 State = state(0), 1029 call_time(forall(Module:Generator, % may become concurrent 1030 ( incr_forall(State, I), 1031 run_test_once6(Unit, Name, 1032 forall(Generator, Nth-I), Line, 1033 UnitOptions, Options, Body) 1034 )), 1035 Time), 1036 arg(1, State, Generated), 1037 progress(Unit:Name, Nth, forall(end, Nth, Generated), Time). 1038run_test(Unit, Name, Line, UnitOptions, Options, Body) :- 1039 start_test(Unit, @(Name,Line), Nth), 1040 run_test_once6(Unit, Name, Nth, Line, UnitOptions, Options, Body). 1041 1042start_test(_Unit, _TestID, Nth) :- 1043 flag(plunit_test, Nth, Nth+1). 1044 1045incr_forall(State, I) :- 1046 arg(1, State, I0), 1047 I is I0+1, 1048 nb_setarg(1, State, I).
timeout and occurs_check option (Global -> Unit -> Test).1055run_test_once6(Unit, Name, Progress, Line, UnitOptions, Options, Body) :- 1056 current_test_flag(timeout, DefTimeOut), 1057 current_test_flag(occurs_check, DefOccurs), 1058 inherit_option(timeout, Options, [UnitOptions], DefTimeOut, Options1), 1059 inherit_option(occurs_check, Options1, [UnitOptions], DefOccurs, Options2), 1060 run_test_once(Unit, Name, Progress, Line, Options2, Body). 1061 1062inherit_option(Name, Options0, Chain, Default, Options) :- 1063 Term =.. [Name,_Value], 1064 ( option(Term, Options0) 1065 -> Options = Options0 1066 ; member(Opts, Chain), 1067 option(Term, Opts) 1068 -> Options = [Term|Options0] 1069 ; Default == (-) 1070 -> Options = Options0 1071 ; Opt =.. [Name,Default], 1072 Options = [Opt|Options0] 1073 ).
1080run_test_once(Unit, Name, Progress, Line, Options, Body) :- 1081 option(occurs_check(Occurs), Options), 1082 !, 1083 begin_test(Unit, Name, Line, Progress), 1084 setup_call_cleanup( 1085 push_prolog_flag(occurs_check, Occurs), 1086 capture_output(run_test_6(Unit, Name, Line, Options, Body, Result), 1087 Output), 1088 pop_prolog_flag(occurs_check)), 1089 end_test(Unit, Name, Line, Progress), 1090 report_result(Result, Progress, Output, Options). 1091run_test_once(Unit, Name, Progress, Line, Options, Body) :- 1092 begin_test(Unit, Name, Line, Progress), 1093 capture_output(run_test_6(Unit, Name, Line, Options, Body, Result), 1094 Output), 1095 end_test(Unit, Name, Line, Progress), 1096 report_result(Result, Progress, Output, Options).
1100:- det(report_result/4). 1101report_result(failure(Unit, Name, Line, How, Time), 1102 Progress, Output, Options) => 1103 failure(Unit, Name, Progress, Line, How, Time, Output, Options). 1104report_result(success(Unit, Name, Line, Determinism, Time), 1105 Progress, Output, Options) => 1106 success(Unit, Name, Progress, Line, Determinism, Time, Output, Options). 1107report_result(setup_failed(Unit, Name, Line, Time, Output, Result), 1108 Progress, _Output, Options) => 1109 failure(Unit, Name, Progress, Line, 1110 setup_failed(Result), Time, Output, Options).
time_limit_exceeded(Limit)cmp_error(Cmp, E)wrong_answer(Cmp)wrong_error(Expect, E)wrong_answer(Expected, Bindings)1132run_test_6(Unit, Name, Line, Options, Body, Result) :- 1133 option(setup(Setup), Options), 1134 !, 1135 unit_module(Unit, Module), 1136 capture_output(call_time(reify(call_ex(Module, Setup), SetupResult), 1137 Time), 1138 Output), 1139 ( SetupResult == true 1140 -> run_test_7(Unit, Name, Line, Options, Body, Result), 1141 cleanup(Module, Options) 1142 ; Result = setup_failed(Unit, Name, Line, Time, Output, SetupResult) 1143 ). 1144run_test_6(Unit, Name, Line, Options, Body, Result) :- 1145 unit_module(Unit, Module), 1146 run_test_7(Unit, Name, Line, Options, Body, Result), 1147 cleanup(Module, Options).
1156run_test_7(Unit, Name, Line, Options, Body, Result) :- 1157 option(true(Cmp), Options), % expected success 1158 !, 1159 unit_module(Unit, Module), 1160 call_time(reify_tmo(call_det(Module:Body, Det), Result0, Options), Time), 1161 ( Result0 == true 1162 -> cmp_true(Cmp, Module, CmpResult), 1163 ( CmpResult == [] 1164 -> Result = success(Unit, Name, Line, Det, Time) 1165 ; Result = failure(Unit, Name, Line, CmpResult, Time) 1166 ) 1167 ; Result0 == false 1168 -> Result = failure(Unit, Name, Line, failed, Time) 1169 ; Result0 = throw(E2) 1170 -> Result = failure(Unit, Name, Line, throw(E2), Time) 1171 ). 1172run_test_7(Unit, Name, Line, Options, Body, Result) :- 1173 option(fail, Options), % expected failure 1174 !, 1175 unit_module(Unit, Module), 1176 call_time(reify_tmo(Module:Body, Result0, Options), Time), 1177 ( Result0 == true 1178 -> Result = failure(Unit, Name, Line, succeeded, Time) 1179 ; Result0 == false 1180 -> Result = success(Unit, Name, Line, true, Time) 1181 ; Result0 = throw(E) 1182 -> Result = failure(Unit, Name, Line, throw(E), Time) 1183 ). 1184run_test_7(Unit, Name, Line, Options, Body, Result) :- 1185 option(throws(Expect), Options), % Expected error 1186 !, 1187 unit_module(Unit, Module), 1188 call_time(reify_tmo(Module:Body, Result0, Options), Time), 1189 ( Result0 == true 1190 -> Result = failure(Unit, Name, Line, no_exception, Time) 1191 ; Result0 == false 1192 -> Result = failure(Unit, Name, Line, failed, Time) 1193 ; Result0 = throw(E) 1194 -> ( match_error(Expect, E) 1195 -> Result = success(Unit, Name, Line, true, Time) 1196 ; Result = failure(Unit, Name, Line, wrong_error(Expect, E), Time) 1197 ) 1198 ). 1199run_test_7(Unit, Name, Line, Options, Body, Result) :- 1200 option(all(Answer), Options), % all(Bindings) 1201 !, 1202 nondet_test(all(Answer), Unit, Name, Line, Options, Body, Result). 1203run_test_7(Unit, Name, Line, Options, Body, Result) :- 1204 option(set(Answer), Options), % set(Bindings) 1205 !, 1206 nondet_test(set(Answer), Unit, Name, Line, Options, Body, Result).
1212nondet_test(Expected, Unit, Name, Line, Options, Body, Result) :- 1213 unit_module(Unit, Module), 1214 result_vars(Expected, Vars), 1215 ( call_time(reify_tmo(findall(Vars, Module:Body, Bindings), 1216 Result0, Options), Time) 1217 -> ( Result0 == true 1218 -> ( nondet_compare(Expected, Bindings, Unit, Name, Line) 1219 -> Result = success(Unit, Name, Line, true, Time) 1220 ; Result = failure(Unit, Name, Line, 1221 [wrong_answer(Expected, Bindings)], Time) 1222 ) 1223 ; Result0 = throw(E) 1224 -> Result = failure(Unit, Name, Line, throw(E), Time) 1225 ) 1226 ). 1227 1228cmp_true([], _, L) => 1229 L = []. 1230cmp_true([Cmp|T], Module, L) => 1231 E = error(Formal,_), 1232 cmp_goal(Cmp, Goal), 1233 ( catch(Module:Goal, E, true) 1234 -> ( var(Formal) 1235 -> cmp_true(T, Module, L) 1236 ; L = [cmp_error(Cmp,E)|L1], 1237 cmp_true(T, Module, L1) 1238 ) 1239 ; L = [wrong_answer(Cmp)|L1], 1240 cmp_true(T, Module, L1) 1241 ). 1242 1243cmp_goal(cmp(_Var, Expr), Goal) => Goal = Expr. 1244cmp_goal(Expr, Goal) => Goal = Expr.
v(V1, ...) containing all variables at the left
side of the comparison operator on Expected.
1252result_vars(Expected, Vars) :-
1253 arg(1, Expected, CmpOp),
1254 arg(1, CmpOp, Vars).1264nondet_compare(all(Cmp), Bindings, _Unit, _Name, _Line) :- 1265 cmp(Cmp, _Vars, Op, Values), 1266 cmp_list(Values, Bindings, Op). 1267nondet_compare(set(Cmp), Bindings0, _Unit, _Name, _Line) :- 1268 cmp(Cmp, _Vars, Op, Values0), 1269 sort(Bindings0, Bindings), 1270 sort(Values0, Values), 1271 cmp_list(Values, Bindings, Op). 1272 1273cmp_list([], [], _Op). 1274cmp_list([E0|ET], [V0|VT], Op) :- 1275 call(Op, E0, V0), 1276 cmp_list(ET, VT, Op).
1280cmp(Var == Value, Var, ==, Value). 1281cmp(Var =:= Value, Var, =:=, Value). 1282cmp(Var = Value, Var, =, Value). 1283:- if(swi). 1284cmp(Var =@= Value, Var, =@=, Value). 1285:- else. 1286:- if(sicstus). 1287cmp(Var =@= Value, Var, variant, Value). % variant/2 is the same =@= 1288:- endif. 1289:- endif.
true if Goal left
no choicepoints and false otherwise.1297:- if((swi;sicstus)). 1298call_det(Goal, Det) :- 1299 call_cleanup(Goal,Det0=true), 1300 ( var(Det0) -> Det = false ; Det = true ). 1301:- else. 1302call_det(Goal, true) :- 1303 call(Goal). 1304:- endif.
1311match_error(Expect, Rec) :-
1312 subsumes_term(Expect, Rec).1325setup(Module, Context, Options) :- 1326 option(setup(Setup), Options), 1327 !, 1328 capture_output(reify(call_ex(Module, Setup), Result), Output), 1329 ( Result == true 1330 -> true 1331 ; print_message(error, 1332 plunit(error(setup, Context, Output, Result))), 1333 fail 1334 ). 1335setup(_,_,_).
1341condition(Module, Context, Options) :- 1342 option(condition(Cond), Options), 1343 !, 1344 capture_output(reify(call_ex(Module, Cond), Result), Output), 1345 ( Result == true 1346 -> true 1347 ; Result == false 1348 -> fail 1349 ; print_message(error, 1350 plunit(error(condition, Context, Output, Result))), 1351 fail 1352 ). 1353condition(_, _, _).
1360call_ex(Module, Goal) :-
1361 Module:(expand_goal(Goal, GoalEx),
1362 GoalEx).1369cleanup(Module, Options) :- 1370 option(cleanup(Cleanup), Options, true), 1371 ( catch(call_ex(Module, Cleanup), E, true) 1372 -> ( var(E) 1373 -> true 1374 ; print_message(warning, E) 1375 ) 1376 ; print_message(warning, goal_failed(Cleanup, '(cleanup handler)')) 1377 ). 1378 1379success(Unit, Name, Progress, Line, Det, Time, _Output, Options) :- 1380 option(fixme(Reason), Options), 1381 !, 1382 ( ( Det == true 1383 ; memberchk(nondet, Options) 1384 ) 1385 -> progress(Unit:Name, Progress, fixme(passed), Time), 1386 Ok = passed 1387 ; progress(Unit:Name, Progress, fixme(nondet), Time), 1388 Ok = nondet 1389 ), 1390 flush_output(user_error), 1391 assert(fixme(Unit, Name, Line, Reason, Ok)). 1392success(Unit, Name, Progress, Line, _, Time, Output, Options) :- 1393 failed_assertion(Unit, Name, Line, _,Progress,_,_), 1394 !, 1395 failure(Unit, Name, Progress, Line, assertion, Time, Output, Options). 1396success(Unit, Name, Progress, Line, _, Time, Output, Options) :- 1397 Output = true-_, 1398 !, 1399 failure(Unit, Name, Progress, Line, message, Time, Output, Options). 1400success(Unit, Name, Progress, Line, Det, Time, _Output, Options) :- 1401 assert(passed(Unit, Name, Line, Det, Time)), 1402 ( ( Det == true 1403 ; memberchk(nondet, Options) 1404 ) 1405 -> progress(Unit:Name, Progress, passed, Time) 1406 ; unit_file(Unit, File), 1407 print_message(warning, plunit(nondet(File:Line, Unit:Name, Progress))) 1408 ).
1415failure(Unit, Name, Progress, Line, _, Time, _Output, Options), 1416 memberchk(fixme(Reason), Options) => 1417 assert(fixme(Unit, Name, Line, Reason, failed)), 1418 progress(Unit:Name, Progress, fixme(failed), Time). 1419failure(Unit, Name, Progress, Line, time_limit_exceeded(Limit), Time, 1420 Output, Options) => 1421 assert_cyclic(timeout(Unit, Name, Line, Limit, Time)), 1422 progress(Unit:Name, Progress, timeout(Limit), Time), 1423 report_failure(Unit, Name, Progress, Line, timeout(Limit), Time, Output, Options). 1424failure(Unit, Name, Progress, Line, E, Time, Output, Options) => 1425 assert_cyclic(failed(Unit, Name, Line, E, Time)), 1426 progress(Unit:Name, Progress, failed, Time), 1427 report_failure(Unit, Name, Progress, Line, E, Time, Output, Options).
1437:- if(swi). 1438assert_cyclic(Term) :- 1439 acyclic_term(Term), 1440 !, 1441 assert(Term). 1442assert_cyclic(Term) :- 1443 Term =.. [Functor|Args], 1444 recorda(cyclic, Args, Id), 1445 functor(Term, _, Arity), 1446 length(NewArgs, Arity), 1447 Head =.. [Functor|NewArgs], 1448 assert(( :- recorded(_, Var, Id), Var = NewArgs)). 1449:- else. 1450:- if(sicstus). 1451:- endif. 1452assert_cyclic(Term) :- 1453 assert(Term). 1454:- endif. 1455 1456 1457 /******************************* 1458 * JOBS * 1459 *******************************/ 1460 1461:- if(current_prolog_flag(threads, true)). 1462 1463:- dynamic 1464 job_data/2, % Queue, Threads 1465 scheduled_unit/1. 1466 1467schedule_unit(_:[]) :- 1468 !. 1469schedule_unit(UnitAndTests) :- 1470 UnitAndTests = Unit:_Tests, 1471 job_data(Queue, _), 1472 !, 1473 assertz(scheduled_unit(Unit)), 1474 thread_send_message(Queue, unit(UnitAndTests)). 1475schedule_unit(Unit) :- 1476 run_unit(Unit).
1482setup_jobs(Count) :- 1483 ( current_test_flag(jobs, Jobs0), 1484 integer(Jobs0) 1485 -> true 1486 ; current_prolog_flag(cpu_count, Jobs0) 1487 ), 1488 Jobs is min(Count, Jobs0), 1489 Jobs > 1, 1490 !, 1491 message_queue_create(Q, [alias(plunit_jobs)]), 1492 length(TIDs, Jobs), 1493 foldl(create_plunit_job(Q), TIDs, 1, _), 1494 asserta(job_data(Q, TIDs)), 1495 job_feedback(informational, jobs(Jobs)). 1496setup_jobs(_) :- 1497 job_feedback(informational, jobs(1)). 1498 1499create_plunit_job(Q, TID, N, N1) :- 1500 N1 is N + 1, 1501 atom_concat(plunit_job_, N, Alias), 1502 thread_create(plunit_job(Q), TID, [alias(Alias)]). 1503 1504plunit_job(Queue) :- 1505 repeat, 1506 ( catch(thread_get_message(Queue, Job, 1507 [ timeout(10) 1508 ]), 1509 error(_,_), fail) 1510 -> job(Job), 1511 fail 1512 ; ! 1513 ). 1514 1515job(unit(Unit:Tests)) => 1516 run_unit(Unit:Tests). 1517job(test(Unit, Test)) => 1518 run_test(Unit, Test). 1519 1520cleanup_jobs :- 1521 retract(job_data(Queue, TIDSs)), 1522 !, 1523 message_queue_destroy(Queue), 1524 maplist(thread_join, TIDSs). 1525cleanup_jobs.
1531job_wait(Unit) :- 1532 thread_wait(\+ scheduled_unit(Unit), 1533 [ wait_preds([scheduled_unit/1]), 1534 timeout(1) 1535 ]), 1536 !. 1537job_wait(Unit) :- 1538 job_data(_Queue, TIDs), 1539 member(TID, TIDs), 1540 thread_property(TID, status(running)), 1541 !, 1542 job_wait(Unit). 1543job_wait(_). 1544 1545 1546job_info(begin(unit(Unit))) => 1547 print_message(silent, plunit(begin(Unit))). 1548job_info(end(unit(Unit, Summary))) => 1549 retractall(scheduled_unit(Unit)), 1550 print_message(silent, plunit(end(Unit, Summary))). 1551 1552:- else. % No jobs 1553 1554schedule_unit(Unit) :- 1555 run_unit(Unit). 1556 1557setup_jobs(_) :- 1558 print_message(silent, plunit(jobs(1))). 1559cleanup_jobs. 1560job_wait(_). 1561job_info(_). 1562 1563:- endif. 1564 1565 1566 1567 /******************************* 1568 * REPORTING * 1569 *******************************/
silent message:
plunit(begin(Unit:Test, File:Line, Progress))plunit(end(Unit:Test, File:Line, Progress))1582begin_test(Unit, Test, Line, Progress) :- 1583 thread_self(Me), 1584 assert(running(Unit, Test, Line, Progress, Me)), 1585 unit_file(Unit, File), 1586 test_count(Total), 1587 job_feedback(information, begin(Unit:Test, File:Line, Progress/Total)). 1588 1589end_test(Unit, Test, Line, Progress) :- 1590 thread_self(Me), 1591 retractall(running(_,_,_,_,Me)), 1592 unit_file(Unit, File), 1593 test_count(Total), 1594 job_feedback(information, end(Unit:Test, File:Line, Progress/Total)).
1600running_tests :- 1601 running_tests(Running), 1602 print_message(informational, plunit(running(Running))). 1603 1604running_tests(Running) :- 1605 test_count(Total), 1606 findall(running(Unit:Test, File:Line, Progress/Total, Thread), 1607 ( running(Unit, Test, Line, Progress, Thread), 1608 unit_file(Unit, File) 1609 ), Running).
1616current_test(Unit, Test, Line, Body, Options) :-
1617 current_unit(Unit, Module, _Supers, _UnitOptions),
1618 Module:'unit test'(Test, Line, Options, Body).1624current_test_unit(Unit, UnitOptions) :- 1625 current_unit(Unit, _Module, _Supers, UnitOptions). 1626 1627 1628count(Goal, Count) :- 1629 aggregate_all(count, Goal, Count).
1636test_summary(Unit, Summary) :- 1637 count(failed(Unit, _0Test, _0Line, _Reason, _0Time), Failed), 1638 count(timeout(Unit, _0Test, _0Line, _Limit, _0Time), Timeout), 1639 count(passed(Unit, _0Test, _0Line, _Det, _0Time), Passed), 1640 count(blocked(Unit, _0Test, _0Line, _0Reason), Blocked), 1641 count(fixme(Unit, _0Test, _0Line, _0Reason, _0How), Fixme), 1642 test_count(Total), 1643 Summary = plunit{total:Total, 1644 passed:Passed, 1645 failed:Failed, 1646 timeout:Timeout, 1647 blocked:Blocked, 1648 fixme:Fixme}. 1649 1650test_summary_passed(Summary) :- 1651 _{failed: 0} :< Summary.
1657report(Time, _Options) :- 1658 test_summary(_, Summary), 1659 print_message(silent, plunit(Summary)), 1660 _{ passed:Passed, 1661 failed:Failed, 1662 timeout:Timeout, 1663 blocked:Blocked, 1664 fixme:Fixme 1665 } :< Summary, 1666 ( Passed+Failed+Timeout+Blocked+Fixme =:= 0 1667 -> info(plunit(no_tests)) 1668 ; Failed+Timeout =:= 0 1669 -> report_blocked(Blocked), 1670 report_fixme, 1671 test_count(Total), 1672 info(plunit(all_passed(Total, Passed, Time))) 1673 ; report_blocked(Blocked), 1674 report_fixme, 1675 report_failed(Failed), 1676 report_timeout(Timeout), 1677 info(plunit(passed(Passed))), 1678 info(plunit(total_time(Time))) 1679 ). 1680 1681report_blocked(0) => 1682 true. 1683report_blocked(Blocked) => 1684 findall(blocked(Unit:Name, File:Line, Reason), 1685 ( blocked(Unit, Name, Line, Reason), 1686 unit_file(Unit, File) 1687 ), 1688 BlockedTests), 1689 info(plunit(blocked(Blocked, BlockedTests))). 1690 1691report_failed(Failed) :- 1692 print_message(error, plunit(failed(Failed))). 1693 1694report_timeout(Count) :- 1695 print_message(warning, plunit(timeout(Count))). 1696 1697report_fixme :- 1698 report_fixme(_,_,_). 1699 1700report_fixme(TuplesF, TuplesP, TuplesN) :- 1701 fixme(failed, TuplesF, Failed), 1702 fixme(passed, TuplesP, Passed), 1703 fixme(nondet, TuplesN, Nondet), 1704 print_message(informational, plunit(fixme(Failed, Passed, Nondet))). 1705 1706 1707fixme(How, Tuples, Count) :- 1708 findall(fixme(Unit, Name, Line, Reason, How), 1709 fixme(Unit, Name, Line, Reason, How), Tuples), 1710 length(Tuples, Count). 1711 1712report_failure(Unit, Name, Progress, Line, Error, 1713 Time, Output, _Options) => 1714 test_count(Total), 1715 job_feedback(error, failed(Unit:Name, Progress/Total, Line, 1716 Error, Time, Output)).
fixme for What.1724test_report(fixme) :- 1725 !, 1726 report_fixme(TuplesF, TuplesP, TuplesN), 1727 append([TuplesF, TuplesP, TuplesN], Tuples), 1728 print_message(informational, plunit(fixme(Tuples))). 1729test_report(What) :- 1730 throw_error(domain_error(report_class, What), _). 1731 1732 1733 /******************************* 1734 * INFO * 1735 *******************************/
1742unit_file(Unit, File), nonvar(Unit) => 1743 unit_file_(Unit, File), 1744 !. 1745unit_file(Unit, File) => 1746 unit_file_(Unit, File). 1747 1748unit_file_(Unit, File) :- 1749 current_unit(Unit, Module, _Context, _Options), 1750 module_property(Module, file(File)). 1751unit_file_(Unit, PlFile) :- 1752 test_file_for(TestFile, PlFile), 1753 module_property(Module, file(TestFile)), 1754 current_unit(Unit, Module, _Context, _Options). 1755 1756 1757 /******************************* 1758 * FILES * 1759 *******************************/
1766load_test_files(_Options) :- 1767 State = state(0,0), 1768 ( source_file(File), 1769 file_name_extension(Base, Old, File), 1770 Old \== plt, 1771 file_name_extension(Base, plt, TestFile), 1772 exists_file(TestFile), 1773 inc_arg(1, State), 1774 ( test_file_for(TestFile, File) 1775 -> true 1776 ; load_files(TestFile, 1777 [ if(changed), 1778 imports([]) 1779 ]), 1780 inc_arg(2, State), 1781 asserta(test_file_for(TestFile, File)) 1782 ), 1783 fail 1784 ; State = state(Total, Loaded), 1785 print_message(informational, plunit(test_files(Total, Loaded))) 1786 ). 1787 1788inc_arg(Arg, State) :- 1789 arg(Arg, State, N0), 1790 N is N0+1, 1791 nb_setarg(Arg, State, N). 1792 1793 1794 /******************************* 1795 * MESSAGES * 1796 *******************************/
print_message(Level, Term), where Level is one of silent or
informational (default).
1803info(Term) :-
1804 message_level(Level),
1805 print_message(Level, Term).forall(Gen,Test) set. Mapped
to forall(FTotal, FFailed)1822progress(UnitTest, _Progress, forall(end, Nth, FTotal), Time) => 1823 ( retract(forall_failures(Nth, FFailed)) 1824 -> true 1825 ; FFailed = 0 1826 ), 1827 test_count(Total), 1828 job_feedback(information, progress(UnitTest, forall(FTotal,FFailed), Nth/Total, Time)). 1829progress(UnitTest, Progress, Result, Time), Progress = forall(_Gen, Nth-_I) => 1830 with_mutex(plunit_forall_counter, 1831 update_forall_failures(Nth, Result)), 1832 test_count(Total), 1833 job_feedback(information, progress(UnitTest, Result, Progress/Total, Time)). 1834progress(UnitTest, Progress, Result, Time) => 1835 test_count(Total), 1836 job_feedback(information, progress(UnitTest, Result, Progress/Total, Time)). 1837 1838update_forall_failures(_Nth, passed) => 1839 true. 1840update_forall_failures(Nth, _) => 1841 ( retract(forall_failures(Nth, Failed0)) 1842 -> true 1843 ; Failed0 = 0 1844 ), 1845 Failed is Failed0+1, 1846 asserta(forall_failures(Nth, Failed)). 1847 1848message_level(Level) :- 1849 ( current_test_flag(silent, true) 1850 -> Level = silent 1851 ; Level = informational 1852 ). 1853 1854locationprefix(File:Line) --> 1855 !, 1856 [ url(File:Line), ':'-[], nl, ' ' ]. 1857locationprefix(test(Unit,_Test,Line)) --> 1858 !, 1859 { unit_file(Unit, File) }, 1860 locationprefix(File:Line). 1861locationprefix(unit(Unit)) --> 1862 !, 1863 [ 'PL-Unit: unit ~w: '-[Unit] ]. 1864locationprefix(FileLine) --> 1865 { throw_error(type_error(locationprefix,FileLine), _) }. 1866 1867:- discontiguous 1868 message//1. 1869:- '$hide'(message//1). 1870 1871message(error(context_error(plunit_close(Name, -)), _)) --> 1872 [ 'PL-Unit: cannot close unit ~w: no open unit'-[Name] ]. 1873message(error(context_error(plunit_close(Name, Start)), _)) --> 1874 [ 'PL-Unit: cannot close unit ~w: current unit is ~w'-[Name, Start] ]. 1875message(plunit(nondet(Pos, Test, Progress))) --> 1876 locationprefix(Pos), 1877 test_name(Test, Progress), 1878 [ ': Test succeeded with choicepoint'-[] ]. 1879message(error(plunit(incompatible_options, Tests), _)) --> 1880 [ 'PL-Unit: incompatible test-options: ~p'-[Tests] ]. 1881message(plunit(sto(true))) --> 1882 [ 'Option sto(true) is ignored. See `occurs_check` option.'-[] ]. 1883message(plunit(test_files(Total, Loaded))) --> 1884 [ 'Found ~D .plt test files, loaded ~D'-[Total, Loaded] ]. 1885 1886 % Unit start/end 1887message(plunit(jobs(1))) --> 1888 !. 1889message(plunit(jobs(N))) --> 1890 [ 'Testing with ~D parallel jobs'-[N] ]. 1891message(plunit(begin(_Unit))) --> 1892 { tty_feedback }, 1893 !. 1894message(plunit(begin(Unit))) --> 1895 [ 'Start unit: ~w~n'-[Unit], flush ]. 1896message(plunit(end(_Unit, _Summary))) --> 1897 { tty_feedback }, 1898 !. 1899message(plunit(end(Unit, Summary))) --> 1900 ( {test_summary_passed(Summary)} 1901 -> [ 'End unit ~w: passed (~3f sec CPU)'-[Unit, Summary.time.cpu] ] 1902 ; [ ansi(error, 'End unit ~w: **FAILED (~3f sec CPU)', [Unit, Summary.time.cpu]) ] 1903 ). 1904message(plunit(blocked(unit(Unit, Reason)))) --> 1905 [ 'PL-Unit: ~w blocked: ~w'-[Unit, Reason] ]. 1906message(plunit(running([]))) --> 1907 !, 1908 [ 'PL-Unit: no tests running' ]. 1909message(plunit(running([One]))) --> 1910 !, 1911 [ 'PL-Unit: running ' ], 1912 running(One). 1913message(plunit(running(More))) --> 1914 !, 1915 [ 'PL-Unit: running tests:', nl ], 1916 running(More). 1917message(plunit(fixme([]))) --> !. 1918message(plunit(fixme(Tuples))) --> 1919 !, 1920 fixme_message(Tuples). 1921message(plunit(total_time(Time))) --> 1922 [ 'Test run completed'-[] ], 1923 test_time(Time). 1924 1925 % Blocked tests 1926message(plunit(blocked(1, Tests))) --> 1927 !, 1928 [ 'one test is blocked'-[] ], 1929 blocked_tests(Tests). 1930message(plunit(blocked(N, Tests))) --> 1931 [ '~D tests are blocked'-[N] ], 1932 blocked_tests(Tests). 1933 1934blocked_tests(Tests) --> 1935 { current_test_flag(show_blocked, true) }, 1936 !, 1937 [':'-[]], 1938 list_blocked(Tests). 1939blocked_tests(_) --> 1940 [ ' (use run_tests/2 with ', ansi(code, 'show_blocked(true)', []), 1941 ' for details)'-[] 1942 ]. 1943 1944list_blocked([]) --> !. 1945list_blocked([blocked(Unit:Test, Pos, Reason)|T]) --> 1946 [nl], 1947 locationprefix(Pos), 1948 test_name(Unit:Test, -), 1949 [ ': ~w'-[Reason] ], 1950 list_blocked(T). 1951 1952 % fail/success 1953message(plunit(no_tests)) --> 1954 !, 1955 [ 'No tests to run' ]. 1956message(plunit(all_passed(1, 1, Time))) --> 1957 !, 1958 [ 'test passed' ], 1959 test_time(Time). 1960message(plunit(all_passed(Total, Total, Time))) --> 1961 !, 1962 [ 'All ~D tests passed'-[Total] ], 1963 test_time(Time). 1964message(plunit(all_passed(Total, Count, Time))) --> 1965 !, 1966 { SubTests is Count-Total }, 1967 [ 'All ~D (+~D sub-tests) tests passed'- [Total, SubTests] ], 1968 test_time(Time). 1969 1970test_time(Time) --> 1971 { var(Time) }, !. 1972test_time(Time) --> 1973 [ ' in ~3f seconds (~3f cpu)'-[Time.wall, Time.cpu] ]. 1974 1975message(plunit(passed(Count))) --> 1976 !, 1977 [ '~D tests passed'-[Count] ]. 1978message(plunit(failed(0))) --> 1979 !, 1980 []. 1981message(plunit(failed(1))) --> 1982 !, 1983 [ '1 test failed'-[] ]. 1984message(plunit(failed(N))) --> 1985 [ '~D tests failed'-[N] ]. 1986message(plunit(timeout(0))) --> 1987 !, 1988 []. 1989message(plunit(timeout(N))) --> 1990 [ '~D tests timed out'-[N] ]. 1991message(plunit(fixme(0,0,0))) --> 1992 []. 1993message(plunit(fixme(Failed,0,0))) --> 1994 !, 1995 [ 'all ~D tests flagged FIXME failed'-[Failed] ]. 1996message(plunit(fixme(Failed,Passed,0))) --> 1997 [ 'FIXME: ~D failed; ~D passed'-[Failed, Passed] ]. 1998message(plunit(fixme(Failed,Passed,Nondet))) --> 1999 { TotalPassed is Passed+Nondet }, 2000 [ 'FIXME: ~D failed; ~D passed; (~D nondet)'- 2001 [Failed, TotalPassed, Nondet] ]. 2002 2003message(plunit(begin(Unit:Test, _Location, Progress))) --> 2004 { tty_columns(SummaryWidth, _Margin), 2005 test_name_summary(Unit:Test, SummaryWidth, NameS), 2006 progress_string(Progress, ProgressS) 2007 }, 2008 ( { tty_feedback, 2009 tty_clear_to_eol(CE) 2010 } 2011 -> [ at_same_line, '\r[~w] ~w ..~w'-[ProgressS, NameS, 2012 CE], flush ] 2013 ; { jobs(_) } 2014 -> [ '[~w] ~w ..'-[ProgressS, NameS] ] 2015 ; [ '[~w] ~w ..'-[ProgressS, NameS], flush ] 2016 ). 2017message(plunit(end(_UnitTest, _Location, _Progress))) --> 2018 []. 2019message(plunit(progress(_UnitTest, Status, _Progress, _Time))) --> 2020 { Status = forall(_Gen,_NthI) 2021 ; Status == assertion 2022 }, 2023 !. 2024message(plunit(progress(Unit:Test, Status, Progress, Time))) --> 2025 { jobs(_), 2026 !, 2027 tty_columns(SummaryWidth, Margin), 2028 test_name_summary(Unit:Test, SummaryWidth, NameS), 2029 progress_string(Progress, ProgressS), 2030 progress_tag(Status, Tag, _Keep, Style) 2031 }, 2032 [ ansi(Style, '[~w] ~w ~`.t ~w (~3f sec)~*|', 2033 [ProgressS, NameS, Tag, Time.wall, Margin]) ]. 2034message(plunit(progress(_UnitTest, Status, _Progress, Time))) --> 2035 { tty_columns(_SummaryWidth, Margin), 2036 progress_tag(Status, Tag, _Keep, Style) 2037 }, 2038 [ at_same_line, ansi(Style, '~`.t ~w (~3f sec)~*|', 2039 [Tag, Time.wall, Margin]) ], 2040 ( { tty_feedback } 2041 -> [flush] 2042 ; [] 2043 ). 2044message(plunit(failed(Unit:Test, Progress, Line, Failure, _Time, Output))) --> 2045 { unit_file(Unit, File) }, 2046 locationprefix(File:Line), 2047 test_name(Unit:Test, Progress), 2048 [': '-[] ], 2049 failure(Failure), 2050 test_output(Output). 2051message(plunit(timeout(Unit:Test, Progress, Line, Limit, Output))) --> 2052 { unit_file(Unit, File) }, 2053 locationprefix(File:Line), 2054 test_name(Unit:Test, Progress), 2055 [': '-[] ], 2056 timeout(Limit), 2057 test_output(Output). 2058:- if(swi). 2059message(plunit(failed_assertion(Unit:Test, Line, AssertLoc, 2060 Progress, Reason, Goal))) --> 2061 { unit_file(Unit, File) }, 2062 locationprefix(File:Line), 2063 test_name(Unit:Test, Progress), 2064 [ ': assertion'-[] ], 2065 assertion_location(AssertLoc, File), 2066 assertion_reason(Reason), ['\n\t'], 2067 assertion_goal(Unit, Goal). 2068 2069assertion_location(File:Line, File) --> 2070 [ ' at line ~w'-[Line] ]. 2071assertion_location(File:Line, _) --> 2072 [ ' at ', url(File:Line) ]. 2073assertion_location(unknown, _) --> 2074 []. 2075 2076assertion_reason(fail) --> 2077 !, 2078 [ ' failed'-[] ]. 2079assertion_reason(Error) --> 2080 { message_to_string(Error, String) }, 2081 [ ' raised "~w"'-[String] ]. 2082 2083assertion_goal(Unit, Goal) --> 2084 { unit_module(Unit, Module), 2085 unqualify(Goal, Module, Plain) 2086 }, 2087 [ 'Assertion: ~p'-[Plain] ]. 2088 2089unqualify(Var, _, Var) :- 2090 var(Var), 2091 !. 2092unqualify(M:Goal, Unit, Goal) :- 2093 nonvar(M), 2094 unit_module(Unit, M), 2095 !. 2096unqualify(M:Goal, _, Goal) :- 2097 callable(Goal), 2098 predicate_property(M:Goal, imported_from(system)), 2099 !. 2100unqualify(Goal, _, Goal). 2101 2102test_output(Msgs-String) --> 2103 { nonvar(Msgs) }, 2104 !, 2105 test_output(String). 2106test_output("") --> []. 2107test_output(Output) --> 2108 [ ansi(code, '~N~s', [Output]) ]. 2109 2110:- endif. 2111 % Setup/condition errors 2112message(plunit(error(Where, Context, _Output, throw(Exception)))) --> 2113 locationprefix(Context), 2114 { message_to_string(Exception, String) }, 2115 [ 'error in ~w: ~w'-[Where, String] ]. 2116message(plunit(error(Where, Context, _Output, false))) --> 2117 locationprefix(Context), 2118 [ 'setup failed in ~w'-[Where] ]. 2119 2120 % delayed output 2121message(plunit(test_output(_, Output))) --> 2122 [ '~s'-[Output] ]. 2123 % Interrupts (SWI) 2124:- if(swi). 2125message(interrupt(begin)) --> 2126 { thread_self(Me), 2127 running(Unit, Test, Line, Progress, Me), 2128 !, 2129 unit_file(Unit, File), 2130 restore_output_state 2131 }, 2132 [ 'Interrupted test '-[] ], 2133 running(running(Unit:Test, File:Line, Progress, Me)), 2134 [nl], 2135 '$messages':prolog_message(interrupt(begin)). 2136message(interrupt(begin)) --> 2137 '$messages':prolog_message(interrupt(begin)). 2138:- endif. 2139 2140message(concurrent) --> 2141 [ 'concurrent(true) at the level of units is currently ignored.', nl, 2142 'See set_test_options/1 with jobs(Count) for concurrent testing.' 2143 ]. 2144 2145test_name(Name, forall(Generator, _Nth-I)/_Total) --> 2146 !, 2147 test_name(Name, -), 2148 [ ' (~d-th forall generator = '-[I], 2149 ansi(code, '~p', [Generator]), ')'-[] 2150 ]. 2151test_name(Name, _) --> 2152 !, 2153 [ 'test ', ansi(code, '~q', [Name]) ]. 2154 2155running(running(Unit:Test, File:Line, _Progress, Thread)) --> 2156 thread(Thread), 2157 [ '~q:~q at '-[Unit, Test], url(File:Line) ]. 2158running([H|T]) --> 2159 ['\t'], running(H), 2160 ( {T == []} 2161 -> [] 2162 ; [nl], running(T) 2163 ). 2164 2165thread(main) --> !. 2166thread(Other) --> 2167 [' [~w] '-[Other] ]. 2168 2169:- if(swi). 2170write_term(T, OPS) --> 2171 ['~W'-[T,OPS] ]. 2172:- else. 2173write_term(T, _OPS) --> 2174 ['~q'-[T]]. 2175:- endif. 2176 2177expected_got_ops_(Ex, E, OPS, Goals) --> 2178 [' Expected: '-[]], write_term(Ex, OPS), [nl], 2179 [' Got: '-[]], write_term(E, OPS), [], 2180 ( { Goals = [] } -> [] 2181 ; [nl, ' with: '-[]], write_term(Goals, OPS), [] 2182 ). 2183 2184 2185failure(List) --> 2186 { is_list(List) }, 2187 !, 2188 [ nl ], 2189 failures(List). 2190failure(Var) --> 2191 { var(Var) }, 2192 !, 2193 [ 'Unknown failure?' ]. 2194failure(succeeded(Time)) --> 2195 !, 2196 [ 'must fail but succeeded in ~2f seconds~n'-[Time] ]. 2197failure(wrong_error(Expected, Error)) --> 2198 !, 2199 { copy_term(Expected-Error, Ex-E, Goals), 2200 numbervars(Ex-E-Goals, 0, _), 2201 write_options(OPS) 2202 }, 2203 [ 'wrong error'-[], nl ], 2204 expected_got_ops_(Ex, E, OPS, Goals). 2205failure(wrong_answer(cmp(Var, Cmp))) --> 2206 { Cmp =.. [Op,Answer,Expected], 2207 !, 2208 copy_term(Expected-Answer, Ex-A, Goals), 2209 numbervars(Ex-A-Goals, 0, _), 2210 write_options(OPS) 2211 }, 2212 [ 'wrong answer for ', ansi(code, '~w', [Var]), 2213 ' (compared using ~w)'-[Op], nl ], 2214 expected_got_ops_(Ex, A, OPS, Goals). 2215failure(wrong_answer(Cmp)) --> 2216 { Cmp =.. [Op,Answer,Expected], 2217 !, 2218 copy_term(Expected-Answer, Ex-A, Goals), 2219 numbervars(Ex-A-Goals, 0, _), 2220 write_options(OPS) 2221 }, 2222 [ 'wrong answer (compared using ~w)'-[Op], nl ], 2223 expected_got_ops_(Ex, A, OPS, Goals). 2224failure(wrong_answer(CmpExpected, Bindings)) --> 2225 { ( CmpExpected = all(Cmp) 2226 -> Cmp =.. [_Op1,_,Expected], 2227 Got = Bindings, 2228 Type = all 2229 ; CmpExpected = set(Cmp), 2230 Cmp =.. [_Op2,_,Expected0], 2231 sort(Expected0, Expected), 2232 sort(Bindings, Got), 2233 Type = set 2234 ) 2235 }, 2236 [ 'wrong "~w" answer:'-[Type] ], 2237 [ nl, ' Expected: ~q'-[Expected] ], 2238 [ nl, ' Found: ~q'-[Got] ]. 2239:- if(swi). 2240failure(cmp_error(_Cmp, Error)) --> 2241 { message_to_string(Error, Message) }, 2242 [ 'Comparison error: ~w'-[Message] ]. 2243failure(throw(Error)) --> 2244 { Error = error(_,_), 2245 !, 2246 message_to_string(Error, Message) 2247 }, 2248 [ 'received error: ~w'-[Message] ]. 2249:- endif. 2250failure(message) --> 2251 !, 2252 [ 'Generated unexpected warning or error'-[] ]. 2253failure(setup_failed(throw(Error))) --> 2254 { Error = error(_,_), 2255 !, 2256 message_to_string(Error, Message) 2257 }, 2258 [ 'test setup goal raised error: ~w'-[Message] ]. 2259failure(setup_failed(_)) --> 2260 !, 2261 [ 'test setup goal failed' ]. 2262failure(Why) --> 2263 [ '~p'-[Why] ]. 2264 2265failures([]) --> 2266 !. 2267failures([H|T]) --> 2268 !, 2269 failure(H), [nl], 2270 failures(T). 2271 2272timeout(Limit) --> 2273 [ 'Timeout exceeeded (~2f sec)'-[Limit] ]. 2274 2275fixme_message([]) --> []. 2276fixme_message([fixme(Unit, _Name, Line, Reason, How)|T]) --> 2277 { unit_file(Unit, File) }, 2278 fixme_message(File:Line, Reason, How), 2279 ( {T == []} 2280 -> [] 2281 ; [nl], 2282 fixme_message(T) 2283 ). 2284 2285fixme_message(Location, Reason, failed) --> 2286 [ 'FIXME: ~w: ~w'-[Location, Reason] ]. 2287fixme_message(Location, Reason, passed) --> 2288 [ 'FIXME: ~w: passed ~w'-[Location, Reason] ]. 2289fixme_message(Location, Reason, nondet) --> 2290 [ 'FIXME: ~w: passed (nondet) ~w'-[Location, Reason] ]. 2291 2292 2293write_options([ numbervars(true), 2294 quoted(true), 2295 portray(true), 2296 max_depth(100), 2297 attributes(portray) 2298 ]).
2305test_name_summary(Term, MaxLen, Summary) :- 2306 summary_string(Term, Text), 2307 atom_length(Text, Len), 2308 ( Len =< MaxLen 2309 -> Summary = Text 2310 ; End is MaxLen//2, 2311 Pre is MaxLen - End - 2, 2312 sub_string(Text, 0, Pre, _, PreText), 2313 sub_string(Text, _, End, 0, PostText), 2314 format(string(Summary), '~w..~w', [PreText,PostText]) 2315 ). 2316 2317summary_string(Unit:Test, String) => 2318 summary_string(Test, String1), 2319 atomics_to_string([Unit, String1], :, String). 2320summary_string(@(Name,Vars), String) => 2321 format(string(String), '~W (using ~W)', 2322 [ Name, [numbervars(true), quoted(false)], 2323 Vars, [numbervars(true), portray(true), quoted(true)] 2324 ]). 2325summary_string(Name, String) => 2326 term_string(Name, String, [numbervars(true), quoted(false)]).
2332progress_string(forall(_Vars, N-I)/Total, S) => 2333 format(string(S), '~w-~w/~w', [N,I,Total]). 2334progress_string(Progress, S) => 2335 term_string(Progress, S).
2343progress_tag(passed, Tag, Keep, Style) => 2344 Tag = passed, Keep = false, Style = comment. 2345progress_tag(fixme(passed), Tag, Keep, Style) => 2346 Tag = passed, Keep = false, Style = comment. 2347progress_tag(fixme(_), Tag, Keep, Style) => 2348 Tag = fixme, Keep = true, Style = warning. 2349progress_tag(nondet, Tag, Keep, Style) => 2350 Tag = '**NONDET', Keep = true, Style = warning. 2351progress_tag(timeout(_Limit), Tag, Keep, Style) => 2352 Tag = '**TIMEOUT', Keep = true, Style = warning. 2353progress_tag(assertion, Tag, Keep, Style) => 2354 Tag = '**FAILED', Keep = true, Style = error. 2355progress_tag(failed, Tag, Keep, Style) => 2356 Tag = '**FAILED', Keep = true, Style = error. 2357progress_tag(forall(_,0), Tag, Keep, Style) => 2358 Tag = passed, Keep = false, Style = comment. 2359progress_tag(forall(_,_), Tag, Keep, Style) => 2360 Tag = '**FAILED', Keep = true, Style = error. 2361 2362 2363 /******************************* 2364 * OUTPUT * 2365 *******************************/ 2366 2367save_output_state :- 2368 stream_property(Output, alias(user_output)), 2369 stream_property(Error, alias(user_error)), 2370 asserta(output_streams(Output, Error)). 2371 2372restore_output_state :- 2373 output_streams(Output, Error), 2374 !, 2375 set_stream(Output, alias(user_output)), 2376 set_stream(Error, alias(user_error)). 2377restore_output_state. 2378 2379 2380 2381 /******************************* 2382 * CONCURRENT STATUS * 2383 *******************************/ 2384 2385/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2386This part deals with interactive feedback when we are running multiple 2387threads. The terminal window cannot work on top of the Prolog message 2388infrastructure and (thus) we have to use more low-level means. 2389- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 2390 2391:- dynamic 2392 jobs/1, % Count 2393 job_window/1, % Count 2394 job_status_line/3. % Job, Format, Args 2395 2396job_feedback(_, jobs(Jobs)) :- 2397 retractall(jobs(_)), 2398 Jobs > 1, 2399 asserta(jobs(Jobs)), 2400 tty_feedback, 2401 !, 2402 retractall(job_window(_)), 2403 asserta(job_window(Jobs)), 2404 retractall(job_status_line(_,_,_)), 2405 jobs_redraw. 2406job_feedback(_, jobs(Jobs)) :- 2407 !, 2408 retractall(job_window(_)), 2409 info(plunit(jobs(Jobs))). 2410job_feedback(_, Msg) :- 2411 job_window(_), 2412 !, 2413 with_mutex(plunit_feedback, job_feedback(Msg)). 2414job_feedback(Level, Msg) :- 2415 print_message(Level, plunit(Msg)). 2416 2417job_feedback(begin(Unit:Test, _Location, Progress)) => 2418 tty_columns(SummaryWidth, _Margin), 2419 test_name_summary(Unit:Test, SummaryWidth, NameS), 2420 progress_string(Progress, ProgressS), 2421 tty_clear_to_eol(CE), 2422 job_format(comment, '\r[~w] ~w ..~w', 2423 [ProgressS, NameS, CE]), 2424 flush_output. 2425job_feedback(end(_UnitTest, _Location, _Progress)) => 2426 true. 2427job_feedback(progress(_UnitTest, Status, _Progress, Time)) => 2428 ( hide_progress(Status) 2429 -> true 2430 ; tty_columns(_SummaryWidth, Margin), 2431 progress_tag(Status, Tag, _Keep, Style), 2432 job_finish(Style, '~`.t ~w (~3f sec)~*|', 2433 [Tag, Time.wall, Margin]) 2434 ). 2435job_feedback(failed(UnitTest, Progress, Line, Error, Time, Output)) => 2436 tty_columns(_SummaryWidth, Margin), 2437 progress_tag(failed, Tag, _Keep, Style), 2438 job_finish(Style, '~`.t ~w (~3f sec)~*|', 2439 [Tag, Time.wall, Margin]), 2440 print_test_output(Error, Output), 2441 ( ( Error = timeout(_) % Status line suffices 2442 ; Error == assertion % We will get an failed test later 2443 ) 2444 -> true 2445 ; print_message(Style, plunit(failed(UnitTest, Progress, Line, 2446 Error, Time, ""))) 2447 ), 2448 jobs_redraw. 2449job_feedback(begin(_Unit)) => true. 2450job_feedback(end(_Unit, _Summary)) => true. 2451 2452hide_progress(assertion). 2453hide_progress(forall(_,_)). 2454hide_progress(failed). 2455hide_progress(timeout(_)). 2456 2457print_test_output(Error, _Msgs-Output) => 2458 print_test_output(Error, Output). 2459print_test_output(_, "") => true. 2460print_test_output(assertion, Output) => 2461 print_message(debug, plunit(test_output(error, Output))). 2462print_test_output(message, Output) => 2463 print_message(debug, plunit(test_output(error, Output))). 2464print_test_output(_, Output) => 2465 print_message(debug, plunit(test_output(informational, Output))).
2471jobs_redraw :- 2472 job_window(N), 2473 !, 2474 tty_columns(_, Width), 2475 tty_header_line(Width), 2476 forall(between(1,N,Line), job_redraw_worker(Line)), 2477 tty_header_line(Width). 2478jobs_redraw. 2479 2480job_redraw_worker(Line) :- 2481 ( job_status_line(Line, Fmt, Args) 2482 -> ansi_format(comment, Fmt, Args) 2483 ; true 2484 ), 2485 nl.
2493job_format(Style, Fmt, Args) :-
2494 job_self(Job),
2495 job_format(Job, Style, Fmt, Args, true).2503job_finish(Style, Fmt, Args) :- 2504 job_self(Job), 2505 job_finish(Job, Style, Fmt, Args). 2506 2507:- det(job_finish/4). 2508job_finish(Job, Style, Fmt, Args) :- 2509 retract(job_status_line(Job, Fmt0, Args0)), 2510 !, 2511 string_concat(Fmt0, Fmt, Fmt1), 2512 append(Args0, Args, Args1), 2513 job_format(Job, Style, Fmt1, Args1, false). 2514 2515:- det(job_format/5). 2516job_format(Job, Style, Fmt, Args, Save) :- 2517 job_window(Jobs), 2518 Up is Jobs+2-Job, 2519 flush_output(user_output), 2520 tty_up_and_clear(Up), 2521 ansi_format(Style, Fmt, Args), 2522 ( Save == true 2523 -> retractall(job_status_line(Job, _, _)), 2524 asserta(job_status_line(Job, Fmt, Args)) 2525 ; true 2526 ), 2527 tty_down_and_home(Up), 2528 flush_output(user_output). 2529 2530:- det(job_self/1). 2531job_self(Job) :- 2532 job_window(N), 2533 N > 1, 2534 thread_self(Me), 2535 split_string(Me, '_', '', [_,_,S]), 2536 number_string(Job, S).
tty format, which reuses the current
output line if the test is successful.2543tty_feedback :- 2544 has_tty, 2545 current_test_flag(format, tty). 2546 2547has_tty :- 2548 stream_property(user_output, tty(true)). 2549 2550tty_columns(SummaryWidth, Margin) :- 2551 tty_width(W), 2552 Margin is W-8, 2553 SummaryWidth is max(20,Margin-34). 2554 2555tty_width(W) :- 2556 current_predicate(tty_size/2), 2557 catch(tty_size(_Rows, Cols), error(_,_), fail), 2558 Cols > 25, 2559 !, 2560 W = Cols. 2561tty_width(80). 2562 2563tty_header_line(Width) :- 2564 ansi_format(comment, '~N~`\u2015t~*|~n', [Width]). 2565 2566:- if(current_predicate(tty_get_capability/3)). 2567tty_clear_to_eol(S) :- 2568 getenv('TERM', _), 2569 catch(tty_get_capability(ce, string, S), 2570 error(_,_), 2571 fail), 2572 !. 2573:- endif. 2574tty_clear_to_eol('\e[K'). 2575 2576tty_up_and_clear(Lines) :- 2577 format(user_output, '\e[~dA\r\e[K', [Lines]). 2578 2579tty_down_and_home(Lines) :- 2580 format(user_output, '\e[~dB\r', [Lines]). 2581 2582:- if(swi). 2583 2584:- multifile 2585 prolog:message/3, 2586 prolog:message_action/2. 2587 2588prologmessage(Term) --> 2589 message(Term). 2590 2591% prolog:message_action(+Term, +Kind) 2592 2593prolog:message_action(make(done(Files)), _) :- 2594 make_run_tests(Files). 2595 2596:- endif. 2597 2598:- if(sicstus). 2599 2600usergenerate_message_hook(Message) --> 2601 message(Message), 2602 [nl]. % SICStus requires nl at the end
2611user:message_hook(informational, plunit(begin(Unit)), _Lines) :- 2612 format(user_error, '% PL-Unit: ~w ', [Unit]), 2613 flush_output(user_error). 2614user:message_hook(informational, plunit(end(_Unit)), _Lines) :- 2615 format(user, ' done~n', []). 2616 2617:- endif. 2618 2619 /******************************* 2620 * SUPPRESS AUTOLOAD WARNINGS * 2621 *******************************/ 2622 2623'$nowarn_autoload'(set_test_options/1, _). 2624'$nowarn_autoload'(run_tests/0, _). 2625'$nowarn_autoload'(run_tests/1, _). 2626'$nowarn_autoload'(run_tests/2, _). 2627'$nowarn_autoload'(running_tests/0, _). 2628'$nowarn_autoload'(current_test/5, _). 2629'$nowarn_autoload'(current_test_unit/2, _). 2630'$nowarn_autoload'(test_report/1, _)
Unit Testing
Unit testing environment for SWI-Prolog and SICStus Prolog. For usage, please visit https://www.swi-prolog.org/pldoc/package/plunit.