iimprove SWI compat

This commit is contained in:
Vítor Santos Costa 2011-03-15 23:49:28 +00:00
parent 8421af32e8
commit 36a0023fbf
22 changed files with 80 additions and 3139 deletions

3
.gitmodules vendored
View File

@ -25,3 +25,6 @@
[submodule "packages/semweb"] [submodule "packages/semweb"]
path = packages/semweb path = packages/semweb
url = ssh://vsc@yap.git.sourceforge.net/gitroot/yap/semweb url = ssh://vsc@yap.git.sourceforge.net/gitroot/yap/semweb
[submodule "packages/plunit"]
path = packages/plunit
url = ssh://vsc@yap.git.sourceforge.net/gitroot/yap/plunit

View File

@ -1427,105 +1427,106 @@ execute_cargs_back(PredEntry *pe, CPredicate exec_code, struct foreign_context *
case 1: case 1:
{ {
CBPredicate1 code1 = (CBPredicate1)exec_code; CBPredicate1 code1 = (CBPredicate1)exec_code;
return ((code1)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code1)(&B->cp_a1-LCL0,
ctx)); ctx));
} }
case 2: case 2:
{ {
CBPredicate2 code2 = (CBPredicate2)exec_code; CBPredicate2 code2 = (CBPredicate2)exec_code;
return ((code2)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code2)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
ctx)); ctx));
} }
case 3: case 3:
{ {
CBPredicate3 code3 = (CBPredicate3)exec_code; CBPredicate3 code3 = (CBPredicate3)exec_code;
return ((code3)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code3)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
ctx)); ctx));
} }
case 4: case 4:
{ {
CBPredicate4 code4 = (CBPredicate4)exec_code; CBPredicate4 code4 = (CBPredicate4)exec_code;
return ((code4)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code4)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
Yap_InitSlot(Deref(ARG4) PASS_REGS), &B->cp_a4-LCL0,
ctx)); ctx));
} }
case 5: case 5:
{ {
CBPredicate5 code5 = (CBPredicate5)exec_code; CBPredicate5 code5 = (CBPredicate5)exec_code;
return ((code5)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code5)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
Yap_InitSlot(Deref(ARG4) PASS_REGS), &B->cp_a4-LCL0,
Yap_InitSlot(Deref(ARG5) PASS_REGS), ctx)); &B->cp_a5-LCL0,
ctx));
} }
case 6: case 6:
{ {
CBPredicate6 code6 = (CBPredicate6)exec_code; CBPredicate6 code6 = (CBPredicate6)exec_code;
return ((code6)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code6)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
Yap_InitSlot(Deref(ARG4) PASS_REGS), &B->cp_a4-LCL0,
Yap_InitSlot(Deref(ARG5) PASS_REGS), &B->cp_a5-LCL0,
Yap_InitSlot(Deref(ARG6) PASS_REGS), &B->cp_a6-LCL0,
ctx)); ctx));
} }
case 7: case 7:
{ {
CBPredicate7 code7 = (CBPredicate7)exec_code; CBPredicate7 code7 = (CBPredicate7)exec_code;
return ((code7)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code7)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
Yap_InitSlot(Deref(ARG4) PASS_REGS), &B->cp_a4-LCL0,
Yap_InitSlot(Deref(ARG5) PASS_REGS), &B->cp_a5-LCL0,
Yap_InitSlot(Deref(ARG6) PASS_REGS), &B->cp_a6-LCL0,
Yap_InitSlot(Deref(ARG7) PASS_REGS), &B->cp_a7-LCL0,
ctx)); ctx));
} }
case 8: case 8:
{ {
CBPredicate8 code8 = (CBPredicate8)exec_code; CBPredicate8 code8 = (CBPredicate8)exec_code;
return ((code8)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code8)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
Yap_InitSlot(Deref(ARG4) PASS_REGS), &B->cp_a4-LCL0,
Yap_InitSlot(Deref(ARG5) PASS_REGS), &B->cp_a5-LCL0,
Yap_InitSlot(Deref(ARG6) PASS_REGS), &B->cp_a6-LCL0,
Yap_InitSlot(Deref(ARG7) PASS_REGS), &B->cp_a7-LCL0,
Yap_InitSlot(Deref(ARG8) PASS_REGS), &B->cp_a8-LCL0,
ctx)); ctx));
} }
case 9: case 9:
{ {
CBPredicate9 code9 = (CBPredicate9)exec_code; CBPredicate9 code9 = (CBPredicate9)exec_code;
return ((code9)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code9)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
Yap_InitSlot(Deref(ARG4) PASS_REGS), &B->cp_a4-LCL0,
Yap_InitSlot(Deref(ARG5) PASS_REGS), &B->cp_a5-LCL0,
Yap_InitSlot(Deref(ARG6) PASS_REGS), &B->cp_a6-LCL0,
Yap_InitSlot(Deref(ARG7) PASS_REGS), &B->cp_a7-LCL0,
Yap_InitSlot(Deref(ARG8) PASS_REGS), &B->cp_a8-LCL0,
Yap_InitSlot(Deref(ARG9) PASS_REGS), &B->cp_a9-LCL0,
ctx)); ctx));
} }
case 10: case 10:
{ {
CBPredicate10 code10 = (CBPredicate10)exec_code; CBPredicate10 code10 = (CBPredicate10)exec_code;
return ((code10)(Yap_InitSlot(Deref(ARG1) PASS_REGS), return ((code10)(&B->cp_a1-LCL0,
Yap_InitSlot(Deref(ARG2) PASS_REGS), &B->cp_a2-LCL0,
Yap_InitSlot(Deref(ARG3) PASS_REGS), &B->cp_a3-LCL0,
Yap_InitSlot(Deref(ARG4) PASS_REGS), &B->cp_a4-LCL0,
Yap_InitSlot(Deref(ARG5) PASS_REGS), &B->cp_a5-LCL0,
Yap_InitSlot(Deref(ARG6) PASS_REGS), &B->cp_a6-LCL0,
Yap_InitSlot(Deref(ARG7) PASS_REGS), &B->cp_a7-LCL0,
Yap_InitSlot(Deref(ARG8) PASS_REGS), &B->cp_a8-LCL0,
Yap_InitSlot(Deref(ARG9) PASS_REGS), &B->cp_a9-LCL0,
Yap_InitSlot(Deref(ARG10) PASS_REGS), &B->cp_a10-LCL0,
ctx)); ctx));
} }
default: default:
@ -1595,7 +1596,7 @@ YAP_ExecuteFirst(PredEntry *pe, CPredicate exec_code)
if (pe->PredFlags & CArgsPredFlag) { if (pe->PredFlags & CArgsPredFlag) {
val = execute_cargs_back(pe, exec_code, ctx PASS_REGS); val = execute_cargs_back(pe, exec_code, ctx PASS_REGS);
} else { } else {
val = ((codev)((&ARG1)-LCL0,0,ctx)); val = ((codev)(B->cp_args-LCL0,0,ctx));
} }
/* make sure we clean up the frames left by the user */ /* make sure we clean up the frames left by the user */
while (execution != oexec) while (execution != oexec)
@ -1710,7 +1711,7 @@ YAP_ExecuteNext(PredEntry *pe, CPredicate exec_code)
if (pe->PredFlags & CArgsPredFlag) { if (pe->PredFlags & CArgsPredFlag) {
val = execute_cargs_back(pe, exec_code, ctx PASS_REGS); val = execute_cargs_back(pe, exec_code, ctx PASS_REGS);
} else { } else {
val = ((codev)((&ARG1)-LCL0,0,ctx)); val = ((codev)(B->cp_args-LCL0,0,ctx));
} }
/* make sure we clean up the frames left by the user */ /* make sure we clean up the frames left by the user */
while (execution != oexec) while (execution != oexec)
@ -1732,12 +1733,10 @@ YAP_ExecuteNext(PredEntry *pe, CPredicate exec_code)
} else if (val == 1) { /* TRUE */ } else if (val == 1) { /* TRUE */
cut_succeed(); cut_succeed();
} else { } else {
/* if ((val & REDO_PTR) == REDO_PTR)
if ((val & REDO_PTR) == REDO_PTR)
ctx->context = (int *)(val & ~REDO_PTR); ctx->context = (int *)(val & ~REDO_PTR);
else else
ctx->context = (int *)((val & ~REDO_PTR)>>FRG_REDO_BITS); ctx->context = (int *)((val & ~REDO_PTR)>>FRG_REDO_BITS);
*/
} }
return TRUE; return TRUE;
} else { } else {

View File

@ -952,6 +952,8 @@ typedef struct choicept {
#define cp_a6 cp_args[5] #define cp_a6 cp_args[5]
#define cp_a7 cp_args[6] #define cp_a7 cp_args[6]
#define cp_a8 cp_args[7] #define cp_a8 cp_args[7]
#define cp_a9 cp_args[8]
#define cp_a10 cp_args[9]
#define EXTRA_CBACK_ARG(Arity,Offset) B->cp_args[(Arity)+(Offset)-1] #define EXTRA_CBACK_ARG(Arity,Offset) B->cp_args[(Arity)+(Offset)-1]
#else #else
/* Otherwise, we need a very dirty trick to access the arguments */ /* Otherwise, we need a very dirty trick to access the arguments */

View File

@ -5,7 +5,6 @@
:- module(system, [concat_atom/2, :- module(system, [concat_atom/2,
concat_atom/3, concat_atom/3,
setenv/2,
read_clause/1, read_clause/1,
string/1, string/1,
chdir/1, chdir/1,
@ -154,8 +153,6 @@ concat_atom(List, New) :-
atomic_concat(List, New). atomic_concat(List, New).
setenv(X,Y) :- unix(putenv(X,Y)).
read_clause(X,Y) :- read_clause(X,Y) :-
read_term(X,Y,[singetons(warning)]). read_term(X,Y,[singetons(warning)]).

View File

@ -2621,25 +2621,15 @@ PL_eval_expression_to_int64_ex(term_t t, int64_t *val)
} }
foreign_t foreign_t
_PL_retry(intptr_t n) _PL_retry(intptr_t v)
{ {
CACHE_REGS return (((uintptr_t)(v)<<FRG_REDO_BITS)|REDO_INT);
/* first we need to get the pointer to the predicate */
PredEntry *pe = B->cp_ap->u.OtapFs.p;
struct foreign_context *ctx = (struct foreign_context *)(&EXTRA_CBACK_ARG(pe->ArityOfPE,1));
ctx->context = n;
return LCL0-(CELL *)ctx;
} }
foreign_t foreign_t
_PL_retry_address(void *addr) _PL_retry_address(void *addr)
{ {
CACHE_REGS return (((uintptr_t)(addr))|REDO_PTR);
/* first we need to get the pointer to the predicate */
PredEntry *pe = B->cp_ap->u.OtapFs.p;
struct foreign_context *ctx = (struct foreign_context *)(&EXTRA_CBACK_ARG(pe->ArityOfPE,1));
ctx->context = (intptr_t)addr;
return LCL0-(CELL *)ctx;
} }

@ -1 +1 @@
Subproject commit 613a7199e420a734717f2887c17b4fc3ef30f67e Subproject commit 220431a82d88734dfcde0201655e9f67adfdd4a1

@ -1 +1 @@
Subproject commit b9631d198164fbeebda627ed440ce2a36be84914 Subproject commit 449c7a945cba6452634222592eef25f845d3e677

1
packages/plunit Submodule

@ -0,0 +1 @@
Subproject commit 52469bccc1cc81d06e47a3a781128689f4658542

View File

@ -1,172 +0,0 @@
[Jul 29 2009]
* ENHANCED: System and library modules now use =system= as their parent module rather than =user=.
[Jun 15 2009]
* DOC: Item#483: Broken link in PlUnit. Ulrich Neumerkel.
[May 14 2009]
* DOC: module specifier, typos
[Mar 19 2009]
* CLEANUP: Removed all trailing whitespace from all source-files. This avoids many GIT (merge) warnings.
[Mar 12 2009]
* MODIFIED: Renamed concat_atom/2,3 into atomic_list_concat/2,3 for compatibility. Moved the old predicates into library(backcomp). It is adviced to update your code, but 99% of the code will remain working because the old definitions are autoloaded.
[Feb 11 2009]
* CLEANUP: Module declaration code
[Sep 11 2008]
* PORT: Add AC_PREREQ to configure.h for systems that auto-select autoconf
versions. After tip by Ulrich Neumerkel.
[Aug 11 2008]
* INSTALL: Remove all configure files from the git repository
[Jul 10 2008]
* FIXED: PlUnit: Perform goal_expansion/2 on setup, condition and cleanup options.
Ivo Swartjes.
[Jun 26 2008]
* ADDED: forall option to unit tests that allows running the same test on
different data sets.
* FIXED: wrong handling of gloabl option sto(false)
[May 31 2008]
* FIXED: Properly support tests with condition and setup
[May 14 2008]
* ENHANCED: PlUnit: Test options in set_test_options/1; fix documentation.
[May 9 2008]
* ADDED: Allow specifying sto mode for an entire test set
[May 1 2008]
* DOC: plunit and non termination
[Apr 28 2008]
* ADDED: flag tests using `fixme'. Suggested under the name `desired' by
Ulrich Neumerkel. Let us see how this works out in practice!
[Apr 24 2008]
* ENHANCED: When interrupting a thread, indicate which one.
* ADDED: PlUnit: running_tests/0 to find what test is running.
* FIXED: wrong dynamic declaration in plunit.pl
[Mar 11 2008]
* ENHANCED: Provide file/line info for errors in setup and condition
[Feb 4 2008]
* FIXED: STO independent of timings, Item#316
[Jan 31 2008]
* DOC: formatting
[Jan 30 2008]
* ENHANCED: PlUnit: run_tests(Unit) raise error on non-existing unit.
Item#305, Ulrich Neumerkel.
* ENHANCED: PlUnit: reporting STO and setting STO checking options
[Jan 29 2008]
* CLEANUP: PlUnit: some reorganisation of the test loop, preparing for further
enhancements.
[Jan 28 2008]
* ADDED: subsumes/2 and subsumes_chk/2 as built-in predicates.
* ENHANCED: Really load no tests if not asked to do and provide better feedback
if no tests where loaded or all tests passed.
* FIXED: DCG: cut transparency, output unification, preserve erroneous non-terminals
* ENHANCED: DCG: more tests
* FIXED: PlUnit version confusion
[2008-01-26]
* FIXED: portability for SICStus 3.12
* ENHANCED: new option sto
[Jan 14 2008]
* FIXED: Non-determinism in run_tests/0 if there are blocked tests.
[Dec 21 2007]
* FIXED: PlUnit printing of wrong answers if there are attributes. Ulrich
Neumerkel, item#242
Oct 30, 2007
* ENHANCED: Report error or failure in cleanup handler (Mark van Assem)
* ENHANCED: Do not report '% All tests passed' if no tests are
executed after a make/0.
Sep 21, 2007
* ENHANCED: Verify test-set and test options.
* ADDED: Allow for Var == Value as shorthand for true(Var == Value).
Sep 20, 2007
* ENHANCED: Message on wrong error
* DOCS: Fixed typos. Ulrich Neumerkel.
* FIXED: avoid a loop if a test throws an error holding a cyclic term.
* MODIFIED: errors are now verified using subsumes_chk/2 instead of =@=.
* PORT: =@= is now correctly bound to variant/2 for SICStus.
Aug 1, 2007
* FIXED: Ran failing tests twice due to unwanted choicepoint.
Jul 30, 2007
* ADDED: allow for error(Error) as alias for throws(error(Error, _)).
Mar 28, 2007
* ADDED: option silent(Bool) to force completely silent operation.
Mar 6, 2007
* Allow for single option without list
Nov 23, 2006
* Force start of line after ....
Oct 22, 2006
* Allow tracing body of tests. Suggested by Lukas Degener.
Oct 17, 2006
* Portability with SICStus 3.12.
Oct 13, 2006
* Better message for tests that succeeded but should have failed

View File

@ -1,137 +0,0 @@
################################################################
# SWI-Prolog `PlDoc' package
# Author: Jan Wielemaker. wielemak@science.uva.nl
# Copyright: GPL (see COPYING or www.gnu.org
################################################################
.SUFFIXES: .tex .dvi .doc .pl
SHELL=@SHELL@
ifeq (@PROLOG_SYSTEM@,yap)
prefix = @prefix@
exec_prefix = @exec_prefix@
ROOTDIR = $(prefix)
EROOTDIR = @exec_prefix@
srcdir=@srcdir@
BINDIR = $(EROOTDIR)/bin
LIBDIR=@libdir@
YAPLIBDIR=@libdir@/Yap
SHAREDIR=$(EROOTDIR)/share/Yap
PL=@INSTALL_ENV@ $(DESTDIR)$(BINDIR)/yap $(DESTDIR)$(YAPLIBDIR)/startup.yss
LN_S=@LN_S@
EXDIR=$(LIBDIR)/examples/plunit
INSTALLDIR=$(SHAREDIR)
else # SWI
srcdir=.
PLBASE=@PLBASE@
PLARCH=@PLARCH@
PL=@PL@
XPCEBASE=$(PLBASE)/xpce
PKGDOC=$(PLBASE)/doc/packages
PCEHOME=../xpce
LIBDIR=$(PLBASE)/library
EXDIR=$(PKGDOC)/examples/plunit
INSTALLDIR=$(LIBDIR)
endif
DESTDIR=
DOCTOTEX=$(PCEHOME)/bin/doc2tex
PLTOTEX=$(PCEHOME)/bin/pl2tex
vLATEX=latex
DOC=plunit
TEX=$(DOC).tex
DVI=$(DOC).dvi
PDF=$(DOC).pdf
HTML=$(DOC).html
INSTALL=@INSTALL@
INSTALL_PROGRAM=@INSTALL_PROGRAM@
INSTALL_DATA=@INSTALL_DATA@
LIBPL= $(srcdir)/plunit.pl $(srcdir)/test_wizard.pl $(srcdir)/test_cover.pl
LIBALL= $(LIBPL) $(SUPPORT)
EXAMPLES= $(srcdir)/read.pl $(srcdir)/simple.pl
all:
@echo "Nothing to do for this package"
install: $(LIBPL)
mkdir -p $(DESTDIR)$(INSTALLDIR)
$(INSTALL_DATA) $(LIBALL) $(DESTDIR)$(INSTALLDIR)
$(PL) -f none -g make -t halt
ln-install: $(LIBPL)
mkdir -p $(DESTDIR)$(INSTALLDIR)
for f in $(LIBALL); do \
rm -f $(DESTDIR)$(INSTALLDIR)/$$f; \
ln -s `pwd`/$$f $(DESTDIR)$(INSTALLDIR); \
done
$(PL) -f none -g make -t halt
rpm-install: install
pdf-install: install-examples
$(INSTALL_DATA) $(DOC).pdf $(DESTDIR)$(PKGDOC)
html-install: install-examples
$(INSTALL_DATA) $(DOC).html $(DESTDIR)$(PKGDOC)
install-examples::
mkdir -p $(DESTDIR)$(EXDIR)
(cd examples && $(INSTALL_DATA) $(EXAMPLES) $(DESTDIR)$(EXDIR))
uninstall::
rm -f $(DESTDIR)$(INSTALLDIR)/plunit.pl
$(PL) -f none -g make -t halt
check::
true
################################################################
# Documentation
################################################################
doc: $(PDF) $(HTML)
pdf: $(PDF)
html: $(HTML)
$(HTML): $(TEX)
latex2html $(DOC)
mv html/index.html $@
rmdir html
$(PDF): $(TEX)
../../man/runtex --pdf $(DOC)
$(TEX): $(DOCTOTEX)
.doc.tex:
$(DOCTOTEX) $*.doc > $*.tex
.pl.tex:
$(PLTOTEX) $*.pl > $*.tex
################################################################
# Clean
################################################################
clean:
rm -f *~ *% config.log
# rm -f $(TEX)
# ../../man/runtex --clean $(DOC)
# rm -rf html
distclean: clean
rm -f $(TARGETS) config.cache config.status Makefile

View File

@ -1,46 +0,0 @@
Prolog Unit Tests
=================
---++ TBD
* Add options to test units
- Condition, setup, cleanup, blocked [OK]
* Run the tests on make, etc. [OK]
* Report generation options
---++ What to do with loaded tests?
* Keep them around
* Remove them
* Flag module as `volatile', so it is not saved.
---++ Distinguish user/system/library tests
---++ Deal with seperate test-files:
* load_test_files(+Options)
---++ Volatile modules?
---++ Conversion of system tests:
- test.pl internals [OK]
- scripts called from test.pl [OK]
- tests for packages
---++ Run tests concurrently?
+ Tests applications for concurrent execution
+ Saves time, notably on tests doing networking, timeout, etc.
- Might be more complicated to understand
---++ Test wizard
* Write tests to .plt files
* Merge with existing content of .plt files
- Read with comments and write back?
---++ Coverage analysis
* See cover.pl

View File

@ -1,27 +0,0 @@
:- use_module(library(plunit)).
:- begin_tests(read).
:- use_module(library(system)).
:- use_module(library(readutil)).
create_file(Tmp) :-
tmp_file(plunit, Tmp),
open(Tmp, write, Out),
write(Out, 'hello(World).\n'),
close(Out).
test(read, [ setup(create_file(Tmp)),
cleanup(delete_file(Tmp))
]) :-
read_file_to_terms(Tmp, Terms, []),
Terms =@= [hello(_)].
:- end_tests(read).
:- trace,run_tests.

View File

@ -1,37 +0,0 @@
:- module(ex_simple, []).
:- use_module(library(plunit)).
:- begin_tests(lists).
test(true) :-
true.
test(fail) :-
\+ fail.
test(fail, [fail]) :-
fail.
test(member) :-
member(a, [a]), !.
test(member, [nondet]) :-
member(_, [a]).
test(member, [true(X == a)]) :-
member(X, [a]), !.
test(member, [all(V == [a,b,c])]) :-
member(V, [a,b,c]).
test(append) :-
append("aap", "noot", X),
X == "aapnoot".
:- end_tests(lists).
:- run_tests.
:- run_tests(lists:member).

View File

@ -1,671 +0,0 @@
\documentclass[11pt]{article}
\usepackage{times}
\usepackage{pl}
\usepackage{html}
\usepackage{plpage}
\sloppy
\makeindex
\onefile
\htmloutput{html} % Output directory
\htmlmainfile{index} % Main document file
\bodycolor{white} % Page colour
\renewcommand{\runningtitle}{Prolog Unit Tests}
\begin{document}
\title{Prolog Unit Tests}
\author{Jan Wielemaker \\
HCS, \\
University of Amsterdam \\
The Netherlands \\
E-mail: \email{wielemak@science.uva.nl}}
\maketitle
\begin{abstract}
This document describes a Prolog unit-test framework. This framework was
initially developed for \url[SWI-Prolog]{http://www.swi-prolog.org}. The
current version also runs on \url[SICStus
Prolog]{http://www.sics.se/sicstus/}, providing a portable testing
framework. See \secref{sicstus}.
\end{abstract}
\pagebreak
\tableofcontents
\vfill
\vfill
\newpage
\section{Introduction}
\label{sec:intro}
There is really no excuse not to write tests!
Automatic testing of software during development is probably the most
important Quality Assurance measure. Tests can validate the final
system, which is nice for your users. However, most (Prolog) developers
forget that it is not just a burden during development.
\begin{itemize}
\item Tests document how the code is supposed to be used.
\item Tests can validate claims you make on the Prolog
implementation. Writing a test makes the claim
explicit.
\item Tests avoid big applications saying `No' after
modifications. This saves time during development,
and it saves \emph{a lot} of time if you must return
to the application a few years later or you must
modify and debug someone else's application.
\end{itemize}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{A Unit Test box}
\label{sec:unitbox}
Tests are written in pure Prolog and enclosed within the directives
begin_tests/1,2 and end_tests/1. They can be embedded inside a normal
source module, or be placed in a separate test-file that loads the files
to be tested. Code inside a test box is normal Prolog code. The
entry points are defined by rules using the head \term{test}{Name} or
\term{test}{Name, Options}, where \arg{Name} is a ground term and
\arg{Options} is a list describing additional properties of the test.
Here is a very simple example:
\begin{code}
:- begin_tests(lists).
:- use_module(library(lists)).
test(reverse) :-
reverse([a,b], [b,a]).
:- end_tests(lists).
\end{code}
The optional second argument of the test-head defines additional processing
options. Defined options are:
\begin{description}
\termitem{blocked}{+Reason:atom}
The test is currently disabled. Tests are flagged as blocked if they
cannot be run for some reason. E.g.\ they crash Prolog, they rely on
some service that is not available, they take too much resources, etc.
Tests that fail but do not crash, etc.\ should be flagged using
\term{fixme}{Fixme}.
\termitem{fixme}{+Reason:atom}
Similar to \term{blocked}{Reason}, but the test it executed anyway. If
it fails, a \const{-} is printed instead of the \const{.} character. If
it passes a \const{+} and if it passes with a choicepoint, \const{!}.
A summary is printed at the end of the test run and the goal
\term{test_report}{fixme} can be used to get details.
\termitem{condition}{:Goal}
Pre-condition for running the test. If the condition fails
the test is skipped. The condition can be used as an alternative
to the \const{setup} option. The only difference is that failure
of a condition skips the test and is considered an error when using
the \const{setup} option.
\termitem{cleanup}{:Goal}
\arg{Goal} is always called after completion of the test-body,
regardless of whether it fails, succeeds or throws an exception. This
option or call_cleanup/2 must be used by tests that require side-effects
that must be reverted after the test completes. \arg{Goal} may share
variables with the test body.
\begin{code}
create_file(Tmp) :-
tmp_file(plunit, Tmp),
open(Tmp, write, Out),
write(Out, 'hello(World).\n'),
close(Out).
test(read, [ setup(create_file(Tmp)),
cleanup(delete_file(Tmp))
]) :-
read_file_to_terms(Tmp, Terms, []),
Term = hello(_).
\end{code}
\termitem{setup}{:Goal}
\arg{Goal} is run before the test-body. Typically used together with
the \const{cleanup} option to create and destroy the required execution
environment.
\termitem{forall}{:Generator}
Run the same test for each solution of \arg{Generator}. Each run invokes
the setup and cleanup handlers. This can be used to run the same test
with different inputs. If an error occurs, the test is reported as
\mbox{\texttt{name (forall bindings = } <vars> \texttt{)}}, where
<vars> indicates the bindings of variables in \arg{Generator}.
\termitem{true}{AnswerTerm Cmp Value}
Body must succeed deterministically. AnswerTerm is compared to Value
using the comparison operator \arg{Cmp}. \arg{Cmp} is typically one of
=/2, ==/2, =:=/2 or =@=/2,%
\footnote{The =@= predicate (denoted \emph{structural equivalence})
is the same as variant/2 in SICStus.}
but any test can be used. This is the same as inserting the test at the
end of the conjunction, but it allows the test engine to distinguish
between failure of copy_term/2 and producing the wrong value. Multiple
variables must be combined in an arbitrary compound term. E.g.\
\verb$A1-A2 == v1-v2$
\begin{code}
test(copy, [ true(Copy =@= hello(X,X))
]) :-
copy_term(hello(Y,Y), Copy).
\end{code}
\termitem{AnswerTerm Cmp Value}
Equivalent to \term{true}{AnswerTerm Cmp Value} if \arg{Cmp} is one
of the comparison operators given above.
\termitem{fail}{}
Body must fail.
\termitem{throws}{Error}
Body must throw \arg{Error}. The error is verified using
\term{subsumes_chk}{Error, Generated}. I.e.\ the generated error
must be more specific than the specified \arg{Error}.
\termitem{error}{Error}
Body must throw \term{error}{Error, _Context}. See \const{throws}
for details.
\termitem{all}{AnswerTerm Cmp Instances}
Similar to \term{true}{AnswerTerm Cmp Values}, but used for non-deterministic
predicates. Each element is compared using \arg{Cmp}. Order matters. For
example:
\begin{code}
test(or, all(X == [1,2])) :-
( X = 1 ; X = 2 ).
\end{code}
\termitem{set}{AnswerTerm Cmp Instances}
Similar to \term{all}{AnswerTerm Cmp Instances}, but ignores order and
duplicates with respect to \arg{Cmp} in the comparison. Each element is
compared using \arg{Cmp}.
\termitem{nondet}{}
If this keyword appears in the option list, non-deterministic success
of the body is not considered an error.
\termitem{sto}{Terms}
Declares that executing body is subject to occurs-check (STO). The
test is executed with \arg{Terms}. \arg{Terms} is either
\const{rational_trees} or \const{finite_trees}. STO programs are not
portable between different kinds of terms. Only programs \emph{not}
subject to occurs-check (NSTO) are portable\footnote{See 7.3.3 of
ISO/IEC 13211-1 PROLOG: Part 1 - General Core, for a detailed
discussion of STO and NSTO}. Fortunately, most practical programs are
NSTO. Writing tests that are STO is still useful to ensure the
robustness of a predicate. In case sto4 and sto5 below, an infinite
list (a rational tree) is created prior to calling the actual
predicate. Ideally, such cases produce a type error or fail silently.
\begin{code}
test(sto1, [sto(rational_trees)]) :-
X=s(X).
test(sto2, [sto(finite_trees),fail]) :-
X=s(X).
test(sto3, [sto(rational_trees), fail]) :-
X=s(X), fail.
test(sto4, [sto(rational_trees),error(type_error(list,L))]) :-
L = [_|L], length(L,_).
test(sto5, [sto(rational_trees),fail]) :-
L = [_|L], length(L,3).
\end{code}
Programs that depend on STO cases tend to be inefficient, even
incorrect, are hard to understand and debug, and terminate poorly. It
is therefore advisable to avoid STO programs whenever possible.
SWI's Prolog flag \prologflag{occurs_check} must not be modified
within plunit tests.
\end{description}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Test Unit options}
\label{sec:unitoptions}
\begin{description}
\predicate{begin_tests}{1}{+Name}
Start named test-unit. Same as \verb$begin_tests(Name, [])$.
\predicate{begin_tests}{2}{+Name, +Options}
Start named test-unit with options. Options provide conditional
processing, setup and cleanup similar to individual tests (second
argument of test/2 rules).
Defined options are:
\begin{description}
\termitem{blocked}{+Reason}
Test-unit has been blocked for the given \arg{Reason}.
\termitem{condition}{:Goal}
Executed before executing any of the tests. If \arg{Goal} fails,
the test of this unit is skipped.
\termitem{setup}{:Goal}
Executed before executing any of the tests.
\termitem{cleanup}{:Goal}
Executed after completion of all tests in the unit.
\termitem{sto}{+Terms}
Specify default for subject-to-occurs-check mode. See \secref{unitbox}
for details on the sto option.
\end{description}
\end{description}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Writing the test body}
\label{sec:testbody}
The test-body is ordinary Prolog code. Without any options, the body
must be designed to succeed \emph{deterministically}. Any other result
is considered a failure. One of the options \const{fail}, \const{true},
\const{throws}, \const{all} or \const{set} can be used to specify a
different expected result. See \secref{unitbox} for details. In this
section we illustrate typical test-scenarios by testing SWI-Prolog
built-in and library predicates.
\subsubsection{Testing deterministic predicates}
\label{sec:testdet}
Deterministic predicates are predicates that must succeed exactly once
and, for well behaved predicates, leave no choicepoints. Typically they
have zero or more input- and zero or more output arguments. The test
goal supplies proper values for the input arguments and verifies the
output arguments. Verification can use test-options or be explicit in
the body. The tests in the example below are equivalent.
\begin{code}
test(add) :-
A is 1 + 2,
A =:= 3.
test(add, [true(A =:= 3)]) :-
A is 1 + 2.
\end{code}
The test engine verifies that the test-body does not leave a
choicepoint. We illustrate that using the test below:
\begin{code}
test(member) :-
member(b, [a,b,c]).
\end{code}
Although this test succeeds, member/2 leaves a choicepoint which is
reported by the test subsystem. To make the test silent, use one of
the alternatives below.
\begin{code}
test(member) :-
member(b, [a,b,c]), !.
test(member, [nondet]) :-
member(b, [a,b,c]).
\end{code}
\subsubsection{Testing semi-deterministic predicates}
\label{sec:testsemidet}
Semi-deterministic predicates are predicates that either fail or succeed
exactly once and, for well behaved predicates, leave no choicepoints.
Testing such predicates is the same as testing deterministic
predicates. Negative tests must be specified using the option
\const{fail} or by negating the body using \verb$\+/1$.
\begin{code}
test(is_set) :-
\+ is_set([a,a]).
test(is_set, [fail]) :-
is_set([a,a]).
\end{code}
\subsubsection{Testing non-deterministic predicates}
\label{sec:testnondet}
Non-deterministic predicates succeed zero or more times. Their results
are tested either using findall/3 or setof/3 followed by a value-check
or using the \const{all} or \const{set} options. The following are
equivalent tests:
\begin{code}
test(member) :-
findall(X, member(X, [a,b,c]), Xs),
Xs == [a,b,c].
test(member, all(X == [a,b,c])) :-
member(X, [a,b,c]).
\end{code}
\subsubsection{Testing error conditions}
\label{sec:testerror}
Error-conditions are tested using the option \term{throws}{Error} or
by wrapping the test in a catch/3. The following tests are equivalent:
\begin{code}
test(div0) :-
catch(A is 1/0, error(E, _), true),
E =@= evaluation_error(zero_divisor).
test(div0, [error(evaluation_error(zero_divisor))]) :-
A is 1/0.
\end{code}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Using separate test files}
\label{sec:testfiles}
Test-units can be embedded in normal Prolog source-files. Alternatively,
tests for a source-file can be placed in another file alongside the file
to be tested. Test files use the extension \fileext{plt}. The predicate
load_test_files/1 can load all files that are related to source-files
loaded into the current project.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Running the test-suite}
\label{sec:running}
At any time, the tests can be executed by loading the program and
running run_tests/0 or run_tests(+Unit).
\begin{description}
\predicate{run_tests}{0}{}
Run all test-units.
\predicate{run_tests}{1}{+Spec}
Run only the specified tests. \arg{Spec} can be a list to run multiple
tests. A single specification is either the name of a test unit or
a term <Unit>:<Tests>, running only the specified test. <Tests> is
either the name of a test or a list of names. Running particular
tests is particularly useful for tracing a test:%
\footnote{Unfortunately the body of the test is called through
meta-calling, so it cannot be traced. The called user-code can be traced
normally though.}
\begin{code}
?- gtrace, run_tests(lists:member).
\end{code}
\end{description}
To identify nonterminating tests, interrupt the looping process with
\emph{Control-C}. The test name and location will be displayed.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Tests and production systems}
\label{sec:state}
Most applications do not want the test-suite to end up in the
final application. There are several ways to achieve this. One
is to place all tests in separate files and not to load the tests
when creating the production environment. Alternatively, use the
directive below before loading the application.
\begin{code}
:- set_test_options([load(never)]).
\end{code}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Controlling the test suite}
\label{sec:options}
\begin{description}
\predicate{set_test_options}{1}{+Options}
Defined options are:
\begin{description}
\termitem{load}{+Load}
Determines whether or not tests are loaded. When \const{never},
everything between begin_tests/1 and end_tests/1 is simply ignored.
When \const{always}, tests are always loaded. Finally, when using
the default value \const{normal}, tests are loaded if the code is
not compiled with optimisation turned on.
\termitem{run}{+Run}
Specifies when tests are run. Using \const{manual}, tests can only be
run using run_tests/0 or run_tests/1. Using \const{make}, tests will be
run for reloaded files, but not for files loaded the first time. Using
\const{make(all)} make/0 will run all test-suites, not only those that
belong to files that are reloaded.
\termitem{silent}{+Bool}
When \const{true} (default is \const{false}), send informational
messages using the `silent' level. In practice this means there
is no output except for errors.
\termitem{sto}{+Bool}
When \const{true} (default \const{false}), assume tests are not subject
to occurs check (non-STO) and verify this if the Prolog implementation
supports testing this.
\end{description}
\predicate{load_test_files}{1}{+Options}
Load \fileext{plt} test-files that belong to the currently loaded
sources.
\predicate{running_tests}{0}{}
Print all currently running tests to the terminal. It can be used
to find running thread in multi-threaded test operation or find the
currently running test if a test appears to be blocking.
\predicate{test_report}{1}{+What}
Print report on the executed tests. \arg{What} defines the type
of report. Currently this only supports \const{fixme}, providing
details on how the fixme-flagged tests proceeded.
\end{description}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Auto-generating tests}
\label{sec:wizard}
Prolog is an interactive environment. Where users of non-interactive
systems tend to write tests as code, Prolog developers tend to run
queries interactively during development. This interactive testing is
generally faster, but the disadvantage is that the tests are lost at the
end of the session. The test-wizard tries to combine the advantages. It
collects toplevel queries and saves them to a specified file. Later,
it extracts these queries from the file and locates the predicates that
are tested by the queries. It runs the query and creates a test clause
from the query.
Auto-generating test cases is experimentally supported through the
library \pllib{test_wizard}. We briefly introduce the functionality
using examples. First step is to log the queries into a file. This is
accomplished with the commands below. \file{Queries.pl} is the name in
which to store all queries. The user can choose any filename for this
purpose. Multiple Prolog instances can share the same name, as data
is appended to this file and write is properly locked to avoid file
corruption.
\begin{code}
:- use_module(library(test_wizard)).
:- set_prolog_flag(log_query_file, 'Queries.pl').
\end{code}
Next, we will illustrate using the library by testing the predicates
from library \pllib{lists}. To generate test cases we just make calls
on the terminal. Note that all queries are recorded and the system will
select the appropriate ones when generating the test unit for a
particular module.
\begin{code}
?- member(b, [a,b]).
Yes
?- reverse([a,b], [b|A]).
A = [a] ;
No
\end{code}
Now we can generate the test-cases for the module list using
make_tests/3:
\begin{code}
?- make_tests(lists, 'Queries.pl', current_output).
:- begin_tests(lists).
test(member, [nondet]) :-
member(b, [a, b]).
test(reverse, [true(A==[a])]) :-
reverse([a, b], [b|A]).
:- end_tests(lists).
\end{code}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Coverage analysis}
\label{sec:cover}
An important aspect of tests is to know which parts of program
is used (\emph{covered}) by the tests. An experimental analysis
is provided by the library \pllib{test_cover}.
\begin{description}
\predicate{show_coverage}{1}{:Goal}
Run \arg{Goal} and write a report on which percentage of the clauses in
each file are used by the program and which percentage of the clauses
always fail.
\end{description}
We illustrate this here using CHAT, a natural language question and
answer application by David H.D. Warren and Fernando C.N. Pereira.
\begin{code}
1 ?- show_coverage(test_chat).
Chat Natural Language Question Answering Test
...
==================================================================
Coverage by File
==================================================================
File Clauses %Cov %Fail
==================================================================
/staff/jan/lib/prolog/chat/xgrun.pl 5 100.0 0.0
/staff/jan/lib/prolog/chat/newg.pl 186 89.2 18.3
/staff/jan/lib/prolog/chat/clotab.pl 28 89.3 0.0
/staff/jan/lib/prolog/chat/newdic.pl 275 35.6 0.0
/staff/jan/lib/prolog/chat/slots.pl 128 74.2 1.6
/staff/jan/lib/prolog/chat/scopes.pl 132 70.5 3.0
/staff/jan/lib/prolog/chat/templa.pl 67 55.2 1.5
/staff/jan/lib/prolog/chat/qplan.pl 106 75.5 0.9
/staff/jan/lib/prolog/chat/talkr.pl 60 20.0 1.7
/staff/jan/lib/prolog/chat/ndtabl.pl 42 59.5 0.0
/staff/jan/lib/prolog/chat/aggreg.pl 47 48.9 2.1
/staff/jan/lib/prolog/chat/world0.pl 131 71.8 1.5
/staff/jan/lib/prolog/chat/rivers.pl 41 100.0 0.0
/staff/jan/lib/prolog/chat/cities.pl 76 43.4 0.0
/staff/jan/lib/prolog/chat/countr.pl 156 100.0 0.0
/staff/jan/lib/prolog/chat/contai.pl 334 100.0 0.0
/staff/jan/lib/prolog/chat/border.pl 857 98.6 0.0
/staff/jan/lib/prolog/chat/chattop.pl 139 43.9 0.7
==================================================================
\end{code}
Using \verb$?- show_coverage(run_tests).$, this library currently only
shows some rough quality measure for test-suite. Later versions should
provide a report to the developer identifying which clauses are covered,
not covered and always failed.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Portability of the test-suite}
\label{sec:porting}
One of the reasons to have tests is to simplify migrating code between
Prolog implementations. Unfortunately creating a portable test-suite
implies a poor integration into the development environment. Luckily,
the specification of the test-system proposed here can be ported quite
easily to most Prolog systems sufficiently compatible to SWI-Prolog to
consider porting your application. Most important is to have support for
term_expansion/2.
In the current system, test units are compiled into sub-modules of the
module in which they appear. Few Prolog systems allow for sub-modules
and therefore ports may have to fall-back to inject the code in the
surrounding module. This implies that support predicates used inside
the test unit should not conflict with predicates of the module being
tested.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{PlUnit on SICStus}
\label{sec:sicstus}
The directory of \file{plunit.pl} and \file{swi.pl} must be in the
\const{library} search-path. With PLUNITDIR replaced accordingly,
add the following into your \file{.sicstusrc} or \file{sicstus.ini}.
\begin{code}
:- set_prolog_flag(language, iso). % for maximal compatibility
library_directory('PLUNITDIR').
\end{code}
The current version runs under SICStus 3. Open issues:
\begin{itemize}
\item Some messages are unformatted because SICStus 3 reports
all ISO errors as instantiation errors.
\item Only \file{plunit.pl}. Both coverage analysis and the test
generation wizard currently require SWI-Prolog.
\item The \const{load} option \const{normal} is the same as \const{always}.
Use \exam{set_test_options(load, never)} to avoid loading the
test suites.
\item The \const{run} option is not supported.
\item Tests are loaded into the enclosing module instead of a separate
test module. This means that predicates in the test module must
not conflict with the enclosing module, nor with other test
modules loaded into the same module.
\end{itemize}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Motivation of choices}
\label{sec:motivation}
\subsection*{Easy to understand and flexible}
There are two approaches for testing. In one extreme the tests are
written using declarations dealing with setup, cleanup, running and
testing the result. In the other extreme a test is simply a Prolog goal
that is supposed to succeed. We have chosen to allow for any mixture of
these approaches. Written down as test/1 we opt for the simple
succeeding goal approach. Using options to the test the user can choose
for a more declarative specification. The user can mix both approaches.
The body of the test appears at the position of a clause-body. This
simplifies identification of the test body and ensures proper layout and
colouring support from the editor without the need for explicit support
of the unit test module. Only clauses of test/1 and test/2 may be marked
as non-called in environments that perform cross-referencing.
%\subsection*{Well integrated}
\printindex
\end{document}

File diff suppressed because it is too large Load Diff

View File

@ -1,251 +0,0 @@
/* $Id$
Part of SWI-Prolog
Author: Jan Wielemaker
E-mail: wielemak@science.uva.nl
WWW: http://www.swi-prolog.org
Copyright (C): 1985-2006, University of Amsterdam
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
As a special exception, if you link this library with other files,
compiled with a Free Software compiler, to produce an executable, this
library does not by itself cause the resulting executable to be covered
by the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
*/
:- module(prolog_cover,
[ show_coverage/1, % :Goal
covered_clauses/4 % +Goal, -Result, -Succeeded, -Failed
]).
:- use_module(library(ordsets)).
:- set_prolog_flag(generate_debug_info, false).
/** <module> Clause cover analysis
The purpose of this module is to find which part of the program has been
use by a certain goal. Usage is defined in terms of clauses that have
fired, seperated in clauses that succeeded at least once and clauses
that failed on each occasion.
This module relies on the SWI-Prolog tracer hooks. It modifies these
hooks and collects the results, after which it restores the debugging
environment. This has some limitations:
* The performance degrades significantly (about 10 times)
* It is not possible to use the debugger using coverage analysis
* The cover analysis tool is currently not thread-safe.
The result is represented as a list of clause-references. As the
references to clauses of dynamic predicates cannot be guaranteed, these
are omitted from the result.
@bug Relies heavily on SWI-Prolog internals. We have considered using
a meta-interpreter for this purpose, but it is nearly impossible
to do 100% complete meta-interpretation of Prolog. Example
problem areas include handling cuts in control-structures
and calls from non-interpreted meta-predicates.
*/
:- dynamic
entered/1, % clauses entered
exited/1. % clauses completed
:- module_transparent
covering/1,
covering/4.
%% show_coverage(Goal)
%
% Report on coverage by Goal
show_coverage(Goal) :-
covered_clauses(Goal, Result, Succeeded, Failed),
file_coverage(Succeeded, Failed),
return(Result).
return(true).
return(fail) :- !, fail.
return(error(E)) :-
throw(E).
%% covered_clauses(:Goal, -Result, -Succeeded, -Failed) is det.
%
% Run Goal as once/1. Unify Result with one of =true=, =fail= or
% error(Error).
%
% @param Succeeded Ordered set of succeeded clauses
% @param Failed Ordered set of clauses that are entered but
% never succeeded.
covered_clauses(Goal, Result, Succeeded, Failed) :-
asserta(user:prolog_trace_interception(Port, Frame, _, continue) :-
prolog_cover:assert_cover(Port, Frame), Ref),
port_mask([unify,exit], Mask),
'$visible'(V, Mask),
'$leash'(L, Mask),
trace,
call_with_result(Goal, Result),
set_prolog_flag(debug, false),
covered(Ref, V, L, Succeeded, Failed).
%% call_with_result(:Goal, -Result) is det.
%
% Run Goal as once/1. Unify Result with one of =true=, =fail= or
% error(Error).
call_with_result(Goal, Result) :-
( catch(Goal, E, true)
-> ( var(E)
-> Result = true
; Result = error(E)
)
; Result = false
).
port_mask([], 0).
port_mask([H|T], Mask) :-
port_mask(T, M0),
'$syspreds':'$port_bit'(H, Bit), % Private stuff
Mask is M0 \/ Bit.
%% assert_cover(+Port, +Frame) is det.
%
% Assert coverage of the current clause. We monitor two ports: the
% _unify_ port to see which clauses we entered, and the _exit_
% port to see which completed successfully.
assert_cover(unify, Frame) :-
running_static_pred(Frame),
prolog_frame_attribute(Frame, clause, Cl), !,
assert_entered(Cl).
assert_cover(exit, Frame) :-
running_static_pred(Frame),
prolog_frame_attribute(Frame, clause, Cl), !,
assert_exited(Cl).
assert_cover(_, _).
%% running_static_pred(+Frame) is semidet.
%
% True if Frame is not running a dynamic predicate.
running_static_pred(Frame) :-
prolog_frame_attribute(Frame, goal, Goal),
\+ predicate_property(Goal, dynamic).
%% assert_entered(+Ref) is det.
%% assert_exited(+Ref) is det.
%
% Add Ref to the set of entered or exited clauses.
assert_entered(Cl) :-
entered(Cl), !.
assert_entered(Cl) :-
assert(entered(Cl)).
assert_exited(Cl) :-
exited(Cl), !.
assert_exited(Cl) :-
assert(exited(Cl)).
%% covered(+Ref, +VisibleMask, +LeashMask, -Succeeded, -Failed) is det.
%
% Restore state and collect failed and succeeded clauses.
covered(Ref, V, L, Succeeded, Failed) :-
'$visible'(_, V),
'$leash'(_, L),
erase(Ref),
findall(Cl, (entered(Cl), \+exited(Cl)), Failed0),
findall(Cl, retract(exited(Cl)), Succeeded0),
retractall(entered(Cl)),
sort(Failed0, Failed),
sort(Succeeded0, Succeeded).
/*******************************
* REPORTING *
*******************************/
%% file_coverage(+Succeeded, +Failed) is det.
%
% Write a report on the clauses covered organised by file to
% current output.
file_coverage(Succeeded, Failed) :-
format('~N~n~`=t~78|~n'),
format('~tCoverage by File~t~78|~n'),
format('~`=t~78|~n'),
format('~w~t~w~64|~t~w~72|~t~w~78|~n',
['File', 'Clauses', '%Cov', '%Fail']),
format('~`=t~78|~n'),
forall(source_file(File),
file_coverage(File, Succeeded, Failed)),
format('~`=t~78|~n').
file_coverage(File, Succeeded, Failed) :-
findall(Cl, clause_source(Cl, File, _), Clauses),
sort(Clauses, All),
( ord_intersect(All, Succeeded)
-> true
; ord_intersect(All, Failed)
), !,
ord_intersection(All, Failed, FailedInFile),
ord_intersection(All, Succeeded, SucceededInFile),
ord_subtract(All, SucceededInFile, UnCov1),
ord_subtract(UnCov1, FailedInFile, Uncovered),
length(All, AC),
length(Uncovered, UC),
length(FailedInFile, FC),
CP is 100-100*UC/AC,
FCP is 100*FC/AC,
summary(File, 56, SFile),
format('~w~t ~D~64| ~t~1f~72| ~t~1f~78|~n', [SFile, AC, CP, FCP]).
file_coverage(_,_,_).
summary(Atom, MaxLen, Summary) :-
atom_length(Atom, Len),
( Len < MaxLen
-> Summary = Atom
; SLen is MaxLen - 5,
sub_atom(Atom, _, SLen, 0, End),
atom_concat('...', End, Summary)
).
%% clause_source(+Clause, -File, -Line) is det.
%% clause_source(-Clause, +File, -Line) is det.
clause_source(Clause, File, Line) :-
nonvar(Clause), !,
clause_property(Clause, file(File)),
clause_property(Clause, line_count(Line)).
clause_source(Clause, File, Line) :-
source_file(Pred, File),
\+ predicate_property(Pred, multifile),
nth_clause(Pred, _Index, Clause),
clause_property(Clause, line_count(Line)).
clause_source(Clause, File, Line) :-
Pred = _:_,
predicate_property(Pred, multifile),
nth_clause(Pred, _Index, Clause),
clause_property(Clause, file(File)),
clause_property(Clause, line_count(Line)).

View File

@ -1,211 +0,0 @@
:- module(test_wizard,
[ make_tests/3, % +Module, +File, +Out
make_test/3 % +Callable, -Module, -Test
]).
:- use_module(library(time)).
:- use_module(library(lists)).
:- use_module(library(listing)).
:- use_module(library(readutil)).
/** <module> Test Generation Wizard
Tasks
* Accumulate user queries
* Suggest tests from user queries
*/
setting(max_time(5)).
/*******************************
* UNIT GENERATION *
*******************************/
%% make_tests(+Module, +File, +Out) is det.
%
% Create tests from queries stored in File and write the tests for
% Module to the stream Out.
make_tests(Module, File, Out) :-
read_file_to_terms(File, Queries, []),
findall(Test, ( member(Q, Queries),
make_test(Q, Module, Test)), Tests),
( Tests == []
-> true
; format(Out, ':- begin_tests(~q).~n~n', [Module]),
maplist(portray_clause(Out), Tests),
format(Out, '~n:- end_tests(~q).~n', [Module])
).
/*******************************
* TEST GENERATION *
*******************************/
%% make_test(+Query:callable, -Module, -Test:term) is det.
%
% Generate a test from a query. Test is returned as a clause of
% test/1 or test/2 to be inserted between begin_tests and
% end_tests.
make_test(Query0, Module, (test(Name, Options) :- Query)) :-
find_test_module(Query0, Module, Query),
pred_name(Query, Name),
setting(max_time(Max)),
test_result(Module:Query, Max, Options).
%% find_test_module(+QuerySpec, ?Module, -Query).
%
% Find module to test from a query. Note that it is very common
% for toplevel usage to rely on SWI-Prolog's DWIM.
%
% @tbd What if multiple modules match? We can select the
% local one or ask the user.
find_test_module(Var, _, _) :-
var(Var), !, fail.
find_test_module(M:Query, M0, Query) :- !,
M0 = M.
find_test_module(Query, M, Query) :-
current_predicate(_, M:Query),
\+ predicate_property(M:Query, imported_from(_M2)).
%% pred_name(+Callable, -Name) is det.
%
% Suggest a name for the test. In the plunit framework the name
% needs not be unique, so we simply take the predicate name.
pred_name(Callable, Name) :-
strip_module(Callable, _, Term),
functor(Term, Name, _Arity).
%% test_result(+Callable, +Maxtime, -Result) is det.
%
% Try running goal and get meaningful results. Results are:
%
% * true(Templ == Var)
% * fail
% * all(Templ == Bindings)
% * throws(Error)
% * timeout
test_result(Callable, Maxtime, Result) :-
term_variables(Callable, Vars),
make_template(Vars, Templ),
catch(call_with_time_limit(Maxtime,
findall(Templ-Det,
call_test(Callable, Det),
Bindings)),
E, true),
( var(E)
-> success(Bindings, Templ, Result)
; error(E, Result)
).
%% success(+Bindings, +Templ, -Result) is det.
%
% Create test-results from non-error cases.
success([], _, [fail]) :- !.
success([[]-true], _, []) :- !.
success([S1-true], Templ, [ true(Templ == S1) ]) :- !.
success([[]-false], _, [ nondet ]) :- !.
success([S1-false], Templ, [ true(Templ == S1), nondet ]) :- !.
success(ListDet, Templ, [all(Templ == List)]) :-
strip_det(ListDet, List).
strip_det([], []).
strip_det([H-_|T0], [H|T]) :-
strip_det(T0, T).
%% error(+ErrorTerm, -Result)
error(Error0, [throws(Error)]) :-
generalise_error(Error0, Error).
generalise_error(error(Formal, _), error(Formal, _)) :- !.
generalise_error(Term, Term).
%% make_template(+Vars, -Template) is det.
%
% Make a nice looking template
make_template([], []) :- !.
make_template([One], One) :- !.
make_template([One, Two], One-Two) :- !.
make_template(List, Vars) :-
Vars =.. [v|List].
%% call_test(:Goal, -Det) is nondet.
%
% True if Goal succeeded. Det is unified to =true= if Goal left
% no choicepoints and =false= otherwise.
call_test(Goal, Det) :-
Goal,
deterministic(Det).
/*******************************
* COLLECT *
*******************************/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Collect toplevel queries if the Prolog flag log_query_file points to the
name of a writeable file. The file is opened in append-mode for
exclusive write to allow for concurrent operation from multiple Prolog
systems using the same logfile.
The file is written in UTF-8 encoding and using ignore_ops(true) to
ensure it can be read.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- multifile
user:message_hook/3.
user:message_hook(toplevel_goal(Goal0, Bindings), _Level, _Lines) :-
open_query_log(Out),
bind_vars(Bindings),
clean_goal(Goal0, Goal),
call_cleanup(format(Out, '~W.~n', [Goal, [ numbervars(true),
quoted(true),
ignore_ops(true)
]]), close(Out)),
fail.
clean_goal(Var, _) :-
var(Var), !, fail.
clean_goal(user:Goal, Goal) :- !.
clean_goal(Goal, Goal).
bind_vars([]).
bind_vars([Name=Var|T]) :-
Var = '$VAR'(Name),
bind_vars(T).
open_query_log(Out) :-
current_prolog_flag(log_query_file, File),
exists_file(File), !,
open(File, append, Out,
[ encoding(utf8),
lock(write)
]).
open_query_log(Out) :-
current_prolog_flag(log_query_file, File),
access_file(File, write), !,
open(File, write, Out,
[ encoding(utf8),
lock(write),
bom(true)
]),
format(Out,
'/* SWI-Prolog query log. This file contains all syntactically\n \
correct queries issued in this directory. It is used by the\n \
test wizard to generate unit tests.\n\
*/~n~n', []).

@ -1 +1 @@
Subproject commit 4ce1e49b493a8571a2159660195adcf5f353af79 Subproject commit 82c00310523e08d1bd314374aed335e4b511c823

@ -1 +1 @@
Subproject commit 3a23a5129738fa08327922bda35bb5458191aaa5 Subproject commit 28338edf66fc4618518fa4be9bf6e28bc81a7aaf

View File

@ -1045,3 +1045,4 @@ make.
'$fetch_stream_alias'(OldStream,Alias) :- '$fetch_stream_alias'(OldStream,Alias) :-
stream_property(OldStream, alias(Alias)), !. stream_property(OldStream, alias(Alias)), !.
'$require'(_Ps, _M).

View File

@ -48,6 +48,7 @@
'$directive'(parallel). '$directive'(parallel).
'$directive'(public(_)). '$directive'(public(_)).
'$directive'(op(_,_,_)). '$directive'(op(_,_,_)).
'$directive'(require(_)).
'$directive'(set_prolog_flag(_,_)). '$directive'(set_prolog_flag(_,_)).
'$directive'(reconsult(_)). '$directive'(reconsult(_)).
'$directive'(reexport(_)). '$directive'(reexport(_)).
@ -104,6 +105,8 @@
'$module_transparent'(P, M). '$module_transparent'(P, M).
'$exec_directive'(noprofile(P), _, M) :- '$exec_directive'(noprofile(P), _, M) :-
'$noprofile'(P, M). '$noprofile'(P, M).
'$exec_directive'(require(Ps), _, M) :-
'$require'(Ps, M).
'$exec_directive'(dynamic(P), _, M) :- '$exec_directive'(dynamic(P), _, M) :-
'$dynamic'(P, M). '$dynamic'(P, M).
'$exec_directive'(thread_local(P), _, M) :- '$exec_directive'(thread_local(P), _, M) :-

View File

@ -301,6 +301,9 @@ putenv(Na,Val) :-
getenv(Na,Val) :- getenv(Na,Val) :-
'$getenv'(Na,Val). '$getenv'(Na,Val).
setenv(Na,Val) :-
'$putenv'(Na,Val).
%%% Saving and restoring a computation %%% Saving and restoring a computation
save(A) :- save(A,_). save(A) :- save(A,_).