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
|
link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
|
||||||
Prolog interpreter shell.
|
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]
|
[NOTE]
|
||||||
The interactive shell is just a prolog shell, it does not load
|
The interactive shell is just a prolog shell, it does not load
|
||||||
a gerrit server environment and thus is not intended for
|
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