Prolog Example Unit Tests
These small examples are designed to run quickly with the Gerrit prolog-shell, but not depending on a local Gerrit repository server. Change-Id: I8f58a6740c6f2c79ae1314f2ae593409ee60440d
This commit is contained in:
@@ -74,6 +74,9 @@ For interactive testing and playing with Prolog, Gerrit provides the
|
||||
link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
|
||||
Prolog interpreter shell.
|
||||
|
||||
For batch or unit tests, see the examples in Gerrit source directory
|
||||
link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/prologtests/examples/[prologtests/examples].
|
||||
|
||||
[NOTE]
|
||||
The interactive shell is just a prolog shell, it does not load
|
||||
a gerrit server environment and thus is not intended for
|
||||
|
7
prologtests/examples/BUILD
Normal file
7
prologtests/examples/BUILD
Normal file
@@ -0,0 +1,7 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
sh_test(
|
||||
name = "test_examples",
|
||||
srcs = ["run.sh"],
|
||||
data = glob(["*.pl"]) + ["//:gerrit.war"],
|
||||
)
|
54
prologtests/examples/README.md
Normal file
54
prologtests/examples/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Prolog Unit Test Examples
|
||||
|
||||
## Run all examples
|
||||
|
||||
Build a local gerrit.war and then run the script:
|
||||
|
||||
./run.sh
|
||||
|
||||
Note that a local Gerrit server is not needed because
|
||||
these unit test examples redefine wrappers of the `gerrit:change\*`
|
||||
rules to provide mocked change data.
|
||||
|
||||
## Add a new unit test
|
||||
|
||||
Please follow the pattern in `t1.pl`, `t2.pl`, or `t3.pl`.
|
||||
|
||||
* Put code to be tested in a file, e.g. `rules.pl`.
|
||||
For easy unit testing, split long clauses into short ones
|
||||
and test every positive and negative path.
|
||||
|
||||
* Create a new unit test file, e.g. `t1.pl`,
|
||||
which should _load_ the test source file and `utils.pl`.
|
||||
|
||||
% First load all source files and the utils.pl.
|
||||
:- load([aosp_rules,utils]).
|
||||
|
||||
:- begin_tests(t1). % give this test any name
|
||||
|
||||
% Use test0/1 or test1/1 to verify failed/passed goals.
|
||||
|
||||
:- end_tests(_,0). % check total pass/fail counts
|
||||
|
||||
* Optionally replace calls to gerrit functions that depend on repository.
|
||||
For example, define the following wrappers and in source code, use
|
||||
`change_branch/1` instead of `gerrti:change_branch/1`.
|
||||
|
||||
change_branch(X) :- gerrit:change_branch(X).
|
||||
commit_label(L,U) :- gerrit:commit_label(L,U).
|
||||
|
||||
* In unit test file, redefine the gerrit function wrappers and test.
|
||||
For example, in `t3.pl`, we have:
|
||||
|
||||
:- redefine(uploader,1,uploader(user(42))). % mocked uploader
|
||||
:- test1(uploader(user(42))).
|
||||
:- test0(is_exempt_uploader).
|
||||
|
||||
% is_exempt_uploader/0 is expected to fail because it is
|
||||
% is_exempt_uploader :- uploader(user(Id)), memberchk(Id, [104, 106]).
|
||||
|
||||
% Note that gerrit:remove_label does not depend on Gerrit repository,
|
||||
% so its caller remove_label/1 is tested without any redefinition.
|
||||
|
||||
:- test1(remove_label('MyReview',[],[])).
|
||||
:- test1(remove_label('MyReview',submit(),submit())).
|
148
prologtests/examples/aosp_rules.pl
Normal file
148
prologtests/examples/aosp_rules.pl
Normal file
@@ -0,0 +1,148 @@
|
||||
% A simplified and mocked AOSP rules.pl
|
||||
|
||||
%%%%% wrapper functions for unit tests
|
||||
|
||||
change_branch(X) :- gerrit:change_branch(X).
|
||||
change_project(X) :- gerrit:change_project(X).
|
||||
commit_author(U,N,M) :- gerrit:commit_author(U,N,M).
|
||||
commit_delta(X) :- gerrit:commit_delta(X).
|
||||
commit_label(L,U) :- gerrit:commit_label(L,U).
|
||||
uploader(X) :- gerrit:uploader(X).
|
||||
|
||||
%%%%% true/false conditions
|
||||
|
||||
% Special auto-merger accounts.
|
||||
is_exempt_uploader :-
|
||||
uploader(user(Id)),
|
||||
memberchk(Id, [104, 106]).
|
||||
|
||||
% Build cop overrides everything.
|
||||
has_build_cop_override :-
|
||||
commit_label(label('Build-Cop-Override', 1), _).
|
||||
|
||||
is_exempt_from_reviews :-
|
||||
or(is_exempt_uploader, has_build_cop_override).
|
||||
|
||||
% Some files in selected projects need API review.
|
||||
needs_api_review :-
|
||||
commit_delta('^(.*/)?api/|^(system-api/)'),
|
||||
change_project(Project),
|
||||
memberchk(Project, [
|
||||
'platform/external/apache-http',
|
||||
'platform/frameworks/base',
|
||||
'platform/frameworks/support',
|
||||
'platform/packages/services/Car',
|
||||
'platform/prebuilts/sdk'
|
||||
]).
|
||||
|
||||
% Some branches need DrNo review.
|
||||
needs_drno_review :-
|
||||
change_branch(Branch),
|
||||
memberchk(Branch, [
|
||||
'refs/heads/my-alpha-dev',
|
||||
'refs/heads/my-beta-dev'
|
||||
]).
|
||||
|
||||
% Some author email addresses need Qualcomm-Review.
|
||||
needs_qualcomm_review :-
|
||||
commit_author(_, _, M),
|
||||
regex_matches(
|
||||
'.*@(qti.qualcomm.com|qca.qualcomm.com|quicinc.com|qualcomm.com)', M).
|
||||
|
||||
% Special projects, branches, user accounts
|
||||
% can opt out owners review.
|
||||
opt_out_find_owners :-
|
||||
change_branch(Branch),
|
||||
memberchk(Branch, [
|
||||
'refs/heads/my-beta-testing',
|
||||
'refs/heads/my-testing'
|
||||
]).
|
||||
|
||||
% Special projects, branches, user accounts
|
||||
% can opt in owners review.
|
||||
% Note that opt_out overrides opt_in.
|
||||
opt_in_find_owners :- true.
|
||||
|
||||
|
||||
%%%%% Simple list filters.
|
||||
|
||||
remove_label(X, In, Out) :-
|
||||
gerrit:remove_label(In, label(X, _), Out).
|
||||
|
||||
% Slow but simple for short input list.
|
||||
remove_review_categories(In, Out) :-
|
||||
remove_label('API-Review', In, L1),
|
||||
remove_label('Code-Review', L1, L2),
|
||||
remove_label('DrNo-Review', L2, L3),
|
||||
remove_label('Owner-Review-Vote', L3, L4),
|
||||
remove_label('Qualcomm-Review', L4, L5),
|
||||
remove_label('Verified', L5, Out).
|
||||
|
||||
|
||||
%%%%% Missing rules in Gerrit Prolog Cafe.
|
||||
|
||||
or(InA, InB) :- once((A;B)).
|
||||
|
||||
not(Goal) :- Goal -> false ; true.
|
||||
|
||||
% memberchk(+Element, +List)
|
||||
memberchk(X, [H|T]) :-
|
||||
(X = H -> true ; memberchk(X, T)).
|
||||
|
||||
maplist(Functor, In, Out) :-
|
||||
(In = []
|
||||
-> Out = []
|
||||
; (In = [X1|T1],
|
||||
Out = [X2|T2],
|
||||
Goal =.. [Functor, X1, X2],
|
||||
once(Goal),
|
||||
maplist(Functor, T1, T2)
|
||||
)
|
||||
).
|
||||
|
||||
|
||||
%%%%% Conditional rules and filters.
|
||||
|
||||
submit_filter(In, Out) :-
|
||||
(is_exempt_from_reviews
|
||||
-> remove_review_categories(In, Out)
|
||||
; (check_review(needs_api_review,
|
||||
'API_Review', In, L1),
|
||||
check_review(needs_drno_review,
|
||||
'DrNo-Review', L1, L2),
|
||||
check_review(needs_qualcomm_review,
|
||||
'Qualcomm-Review', L2, L3),
|
||||
check_find_owners(L3, Out)
|
||||
)
|
||||
).
|
||||
|
||||
check_review(NeedReview, Label, In, Out) :-
|
||||
(NeedReview
|
||||
-> Out = In
|
||||
; remove_label(Label, In, Out)
|
||||
).
|
||||
|
||||
% If opt_out_find_owners is true,
|
||||
% remove all 'Owner-Review-Vote' label;
|
||||
% else if opt_in_find_owners is true,
|
||||
% call find_owners:submit_filter;
|
||||
% else default to no find_owners filter.
|
||||
check_find_owners(In, Out) :-
|
||||
(opt_out_find_owners
|
||||
-> remove_label('Owner-Review-Vote', In, Temp)
|
||||
; (opt_in_find_owners
|
||||
-> find_owners:submit_filter(In, Temp)
|
||||
; In = Temp
|
||||
)
|
||||
),
|
||||
Temp =.. [submit | L1],
|
||||
remove_label('Owner-Approved', L1, L2),
|
||||
maplist(owner_may_to_need, L2, L3),
|
||||
Out =.. [submit | L3].
|
||||
|
||||
% change may(_) to need(_) to block submit.
|
||||
owner_may_to_need(In, Out) :-
|
||||
(In = label('Owner-Review-Vote', may(_))
|
||||
-> Out = label('Owner-Review-Vote', need(_))
|
||||
; Out = In
|
||||
).
|
26
prologtests/examples/load.pl
Normal file
26
prologtests/examples/load.pl
Normal file
@@ -0,0 +1,26 @@
|
||||
% If you have 1.4.3 or older Prolog-Cafe, you need to
|
||||
% use (consult(load), load(load)) to get definition of load.
|
||||
% Then use load([f1,f2,...]) to load multiple source files.
|
||||
|
||||
% Input is a list of file names or a single file name.
|
||||
% Use a conditional expression style without cut operator.
|
||||
load(X) :-
|
||||
( (X = [])
|
||||
-> true
|
||||
; ( (X = [H|T])
|
||||
-> (load_file(H), load(T))
|
||||
; load_file(X)
|
||||
)
|
||||
).
|
||||
|
||||
% load_file is '$consult' without the bug of unbound 'File' variable.
|
||||
% For repeated unit tests, skip statistics and print_message.
|
||||
load_file(F) :- atom(F), !,
|
||||
'$prolog_file_name'(F, PF),
|
||||
open(PF, read, In),
|
||||
% print_message(info, [loading,PF,'...']),
|
||||
% statistics(runtime, _),
|
||||
consult_stream(PF, In),
|
||||
% statistics(runtime, [_,T]),
|
||||
% print_message(info, [PF,'loaded in',T,msec]),
|
||||
close(In).
|
29
prologtests/examples/rules.pl
Normal file
29
prologtests/examples/rules.pl
Normal file
@@ -0,0 +1,29 @@
|
||||
% An example source file to be tested.
|
||||
|
||||
% Add common rules missing in Prolog Cafe.
|
||||
memberchk(X, [H|T]) :-
|
||||
(X = H) -> true ; memberchk(X, T).
|
||||
|
||||
% A rule that can succeed/backtrack multiple times.
|
||||
super_users(1001).
|
||||
super_users(1002).
|
||||
|
||||
% Deterministic rule that pass/fail only once.
|
||||
is_super_user(X) :- memberchk(X, [1001, 1002]).
|
||||
|
||||
% Another rule that can pass 5 times.
|
||||
multi_users(101).
|
||||
multi_users(102).
|
||||
multi_users(103).
|
||||
multi_users(104).
|
||||
multi_users(105).
|
||||
|
||||
% Okay, single deterministic fact.
|
||||
single_user(abc).
|
||||
|
||||
% Wrap calls to gerrit repository, to be redefined in tests.
|
||||
change_owner(X) :- gerrit:change_owner(X).
|
||||
|
||||
% To test is_owner without gerrit:change_owner,
|
||||
% we should redefine change_owner.
|
||||
is_owner(X) :- change_owner(X).
|
62
prologtests/examples/run.sh
Executable file
62
prologtests/examples/run.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
TESTS="t1 t2 t3"
|
||||
|
||||
# Note that both t1.pl and t2.pl test code in rules.pl.
|
||||
# Unit tests are usually longer than the tested code.
|
||||
# So it is common to test one source file with multiple
|
||||
# unit test files.
|
||||
|
||||
LF=$'\n'
|
||||
PASS=""
|
||||
FAIL=""
|
||||
|
||||
echo "#### TEST_SRCDIR = ${TEST_SRCDIR}"
|
||||
|
||||
if [ "${TEST_SRCDIR}" == "" ]; then
|
||||
# Assume running alone
|
||||
GERRIT_WAR="../../bazel-bin/gerrit.war"
|
||||
SRCDIR="."
|
||||
else
|
||||
# Assume running from bazel
|
||||
GERRIT_WAR=`pwd`/gerrit.war
|
||||
SRCDIR="prologtests/examples"
|
||||
fi
|
||||
|
||||
# Default GERRIT_TMP is ~/.gerritcodereview/tmp,
|
||||
# which won't be writable in a bazel test sandbox.
|
||||
/bin/mkdir -p /tmp/gerrit
|
||||
export GERRIT_TMP=/tmp/gerrit
|
||||
|
||||
for T in $TESTS
|
||||
do
|
||||
|
||||
pushd $SRCDIR
|
||||
|
||||
# Unit tests do not need to define clauses in packages.
|
||||
# Use one prolog-shell per unit test, to avoid name collision.
|
||||
echo "### Running test ${T}.pl"
|
||||
echo "[$T]." | java -jar ${GERRIT_WAR} prolog-shell -q -s load.pl
|
||||
|
||||
if [ "x$?" != "x0" ]; then
|
||||
echo "### Test ${T}.pl failed."
|
||||
FAIL="${FAIL}${LF}FAIL: Test ${T}.pl"
|
||||
else
|
||||
PASS="${PASS}${LF}PASS: Test ${T}.pl"
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
# java -jar ../../bazel-bin/gerrit.war prolog-shell -s $T < /dev/null
|
||||
# Calling prolog-shell with -s flag works for small files,
|
||||
# but got run-time exception with t3.pl.
|
||||
# com.googlecode.prolog_cafe.exceptions.ReductionLimitException:
|
||||
# exceeded reduction limit of 1048576
|
||||
done
|
||||
|
||||
echo "$PASS"
|
||||
|
||||
if [ "$FAIL" != "" ]; then
|
||||
echo "$FAIL"
|
||||
exit 1
|
||||
fi
|
20
prologtests/examples/t1.pl
Normal file
20
prologtests/examples/t1.pl
Normal file
@@ -0,0 +1,20 @@
|
||||
:- load([rules,utils]).
|
||||
:- begin_tests(t1).
|
||||
|
||||
:- test1(true). % expect true to pass
|
||||
:- test0(false). % expect false to fail
|
||||
|
||||
:- test1(X = 3). % unification should pass
|
||||
:- test1(_ = 3). % unification should pass
|
||||
:- test0(X \= 3). % not-unified should fail
|
||||
|
||||
% (7-4) should have expected result
|
||||
:- test1((X is (7-4), X =:= 3)).
|
||||
:- test1((X is (7-4), X =\= 4)).
|
||||
|
||||
% memberchk should pass/fail exactly once
|
||||
:- test1(memberchk(3,[1,3,5,3])).
|
||||
:- test0(memberchk(2,[1,3,5,3])).
|
||||
:- test0(memberchk(2,[])).
|
||||
|
||||
:- end_tests_or_halt(0). % expect no failure
|
25
prologtests/examples/t2.pl
Normal file
25
prologtests/examples/t2.pl
Normal file
@@ -0,0 +1,25 @@
|
||||
:- load([rules,utils]).
|
||||
:- begin_tests(t2).
|
||||
|
||||
% expected to pass or fail once.
|
||||
:- test0(super_users(1000)).
|
||||
:- test1(super_users(1001)).
|
||||
|
||||
:- test1(is_super_user(1001)).
|
||||
:- test1(is_super_user(1002)).
|
||||
:- test0(is_super_user(1003)).
|
||||
|
||||
:- test1(super_users(X)). % expected fail (pass twice)
|
||||
:- test1(multi_users(X)). % expected fail (pass many times)
|
||||
|
||||
:- test1(single_user(X)). % expected pass once
|
||||
|
||||
% Redefine change_owner, skip gerrit:change_owner,
|
||||
% then test is_owner without a gerrit repository.
|
||||
|
||||
:- redefine(change_owner,1,(change_owner(42))).
|
||||
:- test1(is_owner(42)).
|
||||
:- test1(is_owner(X)).
|
||||
:- test0(is_owner(24)).
|
||||
|
||||
:- end_tests_or_halt(2). % expect 2 failures
|
69
prologtests/examples/t3.pl
Normal file
69
prologtests/examples/t3.pl
Normal file
@@ -0,0 +1,69 @@
|
||||
:- load([aosp_rules,utils]).
|
||||
|
||||
:- begin_tests(t3_basic_conditions).
|
||||
|
||||
%% A negative test of is_exempt_uploader.
|
||||
:- redefine(uploader,1,uploader(user(42))). % mocked uploader
|
||||
:- test1(uploader(user(42))).
|
||||
:- test0(is_exempt_uploader).
|
||||
|
||||
%% Helper functions for positive test of is_exempt_uploader.
|
||||
test_is_exempt_uploader(List) :- maplist(test1_uploader, List, _).
|
||||
test1_uploader(X,_) :-
|
||||
redefine(uploader,1,uploader(user(X))),
|
||||
test1(uploader(user(X))),
|
||||
test1(is_exempt_uploader).
|
||||
:- test_is_exempt_uploader([104, 106]).
|
||||
|
||||
%% Test has_build_cop_override.
|
||||
:- redefine(commit_label,2,commit_label(label('Code-Review',1),user(102))).
|
||||
:- test0(has_build_cop_override).
|
||||
commit_label(label('Build-Cop-Override',1),user(101)). % mocked 2nd label
|
||||
:- test1(has_build_cop_override).
|
||||
:- test1(commit_label(label(_,_),_)). % expect fail, two matches
|
||||
:- test1(commit_label(label('Build-Cop-Override',_),_)). % good, one pass
|
||||
|
||||
%% TODO: more test for is_exempt_from_reviews.
|
||||
|
||||
%% Test needs_api_review, which checks commit_delta and project.
|
||||
% Helper functions:
|
||||
test_needs_api_review(File, Project, Tester) :-
|
||||
redefine(commit_delta,1,(commit_delta(R) :- regex_matches(R, File))),
|
||||
redefine(change_project,1,change_project(Project)),
|
||||
Goal =.. [Tester, needs_api_review],
|
||||
msg('# check CL with changed file ', File, ' in ', Project),
|
||||
once((Goal ; true)). % do not backtrack
|
||||
|
||||
:- test_needs_api_review('apio/test.cc', 'platform/art', test0).
|
||||
:- test_needs_api_review('api/test.cc', 'platform/art', test0).
|
||||
:- test_needs_api_review('api/test.cc', 'platform/prebuilts/sdk', test1).
|
||||
:- test_needs_api_review('d1/d2/api/test.cc', 'platform/prebuilts/sdk', test1).
|
||||
:- test_needs_api_review('system-api/d/t.c', 'platform/external/apache-http', test1).
|
||||
|
||||
%% TODO: Test needs_drno_review, needs_qualcomm_review
|
||||
|
||||
%% TODO: Test opt_out_find_owners.
|
||||
|
||||
:- test1(opt_in_find_owners). % default, unless opt_out_find_owners
|
||||
|
||||
:- end_tests_or_halt(1). % expect 1 failure of multiple commit_label
|
||||
|
||||
%% Test remove_label
|
||||
:- begin_tests(t3_remove_label).
|
||||
|
||||
:- test1(remove_label('MyReview',[],[])).
|
||||
:- test1(remove_label('MyReview',submit(),submit())).
|
||||
:- test1(remove_label(myR,[label(a,X)],[label(a,X)])).
|
||||
:- test1(remove_label(myR,[label(myR,_)],[])).
|
||||
:- test1(remove_label(myR,[label(a,X),label(myR,_)],[label(a,X)])).
|
||||
:- test1(remove_label(myR,submit(label(a,X)),submit(label(a,X)))).
|
||||
:- test1(remove_label(myR,submit(label(myR,_)),submit())).
|
||||
|
||||
%% Test maplist
|
||||
double(X,Y) :- Y is X * X.
|
||||
:- test1(maplist(double, [2,4,6], [4,16,36])).
|
||||
:- test1(maplist(double, [], [])).
|
||||
|
||||
:- end_tests_or_halt(0). % expect no failure
|
||||
|
||||
%% TODO: Add more tests.
|
78
prologtests/examples/utils.pl
Normal file
78
prologtests/examples/utils.pl
Normal file
@@ -0,0 +1,78 @@
|
||||
%% 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)).
|
Reference in New Issue
Block a user