|  | %% Unit test helpers | 
|  |  | 
|  | % Write one line message. | 
|  | msg(A) :- write(A), nl. | 
|  | msg(A,B) :- write(A), msg(B). | 
|  | msg(A,B,C) :- write(A), msg(B,C). | 
|  | msg(A,B,C,D) :- write(A), msg(B,C,D). | 
|  | msg(A,B,C,D,E) :- write(A), msg(B,C,D,E). | 
|  | msg(A,B,C,D,E,F) :- write(A), msg(B,C,D,E,F). | 
|  |  | 
|  | % Redefine a caluse. | 
|  | redefine(Atom,Arity,Clause) :- abolish(Atom/Arity), assertz(Clause). | 
|  |  | 
|  | % Increment/decrement of pass/fail counters. | 
|  | set_counters(N,X,Y) :- redefine(test_count,3,test_count(N,X,Y)). | 
|  | get_counters(N,X,Y) :- clause(test_count(N,X,Y), _) -> true ; (X=0, Y=0). | 
|  | inc_pass_count :- get_counters(N,P,F), P1 is P + 1, set_counters(N,P1,F). | 
|  | inc_fail_count :- get_counters(N,P,F), F1 is F + 1, set_counters(N,P,F1). | 
|  |  | 
|  | % Report pass or fail of G. | 
|  | pass_1(G) :- msg('PASS: ', G), inc_pass_count. | 
|  | fail_1(G) :- msg('FAIL: ', G), inc_fail_count. | 
|  |  | 
|  | % Report pass or fail of not(G). | 
|  | pass_0(G) :- msg('PASS: not(', G, ')'), inc_pass_count. | 
|  | fail_0(G) :- msg('FAIL: not(', G, ')'), inc_fail_count. | 
|  |  | 
|  | % Report a test as failed if it passed 2 or more times | 
|  | pass_twice(G) :- | 
|  | msg('FAIL: (pass twice): ', G), | 
|  | inc_fail_count. | 
|  | pass_many(G) :- | 
|  | G = [A,B|_], | 
|  | length(G, N), | 
|  | msg('FAIL: (pass ', N, ' times): ', [A,B,'...']), | 
|  | inc_fail_count. | 
|  |  | 
|  | % Test if G fails. | 
|  | test0(G) :- once(G) -> fail_0(G) ; pass_0(G). | 
|  |  | 
|  | % Test if G passes exactly once. | 
|  | test1(G) :- | 
|  | findall(G, G, S), length(S, N), | 
|  | (N == 0 | 
|  | -> fail_1(G) | 
|  | ;  (N == 1 | 
|  | -> pass_1(S) | 
|  | ;  (N == 2 -> pass_twice(S) ; pass_many(S)) | 
|  | ) | 
|  | ). | 
|  |  | 
|  | % Report the begin of test N. | 
|  | begin_tests(N) :- | 
|  | nl, | 
|  | msg('BEGIN test ',N), | 
|  | set_counters(N,0,0). | 
|  |  | 
|  | % Repot the end of test N and total pass/fail counts, | 
|  | % and check if the numbers are as exected OutP/OutF. | 
|  | end_tests(OutP,OutF) :- | 
|  | get_counters(N,P,F), | 
|  | (OutP = P | 
|  | -> msg('Expected #PASS: ', OutP) | 
|  | ;  (msg('ERROR: expected #PASS is ',OutP), !, fail) | 
|  | ), | 
|  | (OutF = F | 
|  | -> msg('Expected #FAIL: ', OutF) | 
|  | ;  (msg('ERROR: expected #FAIL is ',OutF), !, fail) | 
|  | ), | 
|  | msg('END test ', N), | 
|  | nl. | 
|  |  | 
|  | % Repot the end of test N and total pass/fail counts. | 
|  | end_tests(N) :- end_tests(N,_,_). | 
|  |  | 
|  | % Call end_tests/2 and halt if the fail count is unexpected. | 
|  | end_tests_or_halt(ExpectedFails) :- | 
|  | end_tests(_,ExpectedFails); (flush_output, halt(1)). |