1 # Simple test to ensure that we can load the xapian module and exercise basic
2 # functionality successfully.
4 # Copyright (C) 2004,2005,2006,2007,2008,2010,2011,2012,2013,2014,2015,2016,2017,2019 Olly Betts
5 # Copyright (C) 2007 Lemur Consulting Ltd
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation; either version 2 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
26 from testsuite
import *
30 # Stemmer which strips English vowels.
31 class MyStemmer(xapian
.StemImplementation
):
35 super(MyStemmer
, self
).__init
__()
36 mystemmers
.add(mystemmer_id
)
37 self
._id
= mystemmer_id
40 def __call__(self
, s
):
41 return re
.sub(br
'[aeiou]', b
'', s
)
45 if self
._id
not in mystemmers
:
46 raise TestFail("MyStemmer #%d deleted more than once" % self
._id
)
47 mystemmers
.remove(self
._id
)
50 # Test the version number reporting functions give plausible results.
51 v
= "%d.%d.%d" % (xapian
.major_version(),
52 xapian
.minor_version(),
54 v2
= xapian
.version_string()
55 expect(v2
, v
, "Unexpected version output")
57 # A regexp check would be better, but seems to create a bogus "leak" of -1
58 # objects in Python 3.
59 expect(len(xapian
.__version
__.split('.')), 3, 'xapian.__version__ not X.Y.Z')
60 expect((xapian
.__version
__.split('.'))[0], '1', 'xapian.__version__ not "1.Y.Z"')
64 print("Unhandled constants: ", res
)
67 # Check that SWIG isn't generating cvar (regression test for ticket#297).
69 # Python 3.5 generates a different exception message here to earlier
70 # versions, so we need a check which matches both.
71 expect_exception(AttributeError,
72 lambda msg
: msg
.find("has no attribute 'cvar'") != -1,
75 stem
= xapian
.Stem(b
"english")
76 expect(str(stem
), "Xapian::Stem(english)", "Unexpected str(stem)")
78 doc
= xapian
.Document()
80 if doc
.get_data() == b
"a":
81 raise TestFail("get_data+set_data truncates at a zero byte")
82 expect(doc
.get_data(), b
"a\0b", "get_data+set_data doesn't transparently handle a zero byte")
83 doc
.set_data(b
"is there anybody out there?")
84 doc
.add_term(b
"XYzzy")
85 doc
.add_posting(stem(b
"is"), 1)
86 doc
.add_posting(stem(b
"there"), 2)
87 doc
.add_posting(stem(b
"anybody"), 3)
88 doc
.add_posting(stem(b
"out"), 4)
89 doc
.add_posting(stem(b
"there"), 5)
91 db
= xapian
.WritableDatabase('', xapian
.DB_BACKEND_INMEMORY
)
93 expect(db
.get_doccount(), 1, "Unexpected db.get_doccount()")
94 terms
= ["smoke", "test", "terms"]
95 expect_query(xapian
.Query(xapian
.Query
.OP_OR
, [t
.encode('utf-8') for t
in terms
]),
96 "(smoke OR test OR terms)")
97 query1
= xapian
.Query(xapian
.Query
.OP_PHRASE
, (b
"smoke", b
"test", b
"tuple"))
98 query2
= xapian
.Query(xapian
.Query
.OP_XOR
, (xapian
.Query(b
"smoke"), query1
, b
"string"))
99 expect_query(query1
, "(smoke PHRASE 3 test PHRASE 3 tuple)")
100 expect_query(query2
, "(smoke XOR (smoke PHRASE 3 test PHRASE 3 tuple) XOR string)")
102 expect_query(xapian
.Query(xapian
.Query
.OP_OR
, [s
.encode('utf-8') for s
in subqs
]), "(a OR b)")
103 expect_query(xapian
.Query(xapian
.Query
.OP_VALUE_RANGE
, 0, b
'1', b
'4'),
106 # Check database factory functions are wrapped as expected (or not wrapped
107 # in the first cases):
109 expect_exception(AttributeError,
110 lambda msg
: msg
.find("has no attribute 'open_stub'") != -1,
111 lambda : xapian
.open_stub(b
"nosuchdir/nosuchdb"))
112 expect_exception(AttributeError,
113 lambda msg
: msg
.find("has no attribute 'open_stub'") != -1,
114 lambda : xapian
.open_stub(b
"nosuchdir/nosuchdb", xapian
.DB_OPEN
))
116 expect_exception(AttributeError,
117 lambda msg
: msg
.find("has no attribute 'chert_open'") != -1,
118 lambda : xapian
.chert_open(b
"nosuchdir/nosuchdb"))
119 expect_exception(AttributeError,
120 lambda msg
: msg
.find("has no attribute 'chert_open'") != -1,
121 lambda : xapian
.chert_open(b
"nosuchdir/nosuchdb", xapian
.DB_CREATE
))
123 expect_exception(xapian
.DatabaseNotFoundError
, None,
124 lambda : xapian
.Database(b
"nosuchdir/nosuchdb", xapian
.DB_BACKEND_STUB
))
125 expect_exception(xapian
.DatabaseNotFoundError
, None,
126 lambda : xapian
.WritableDatabase(b
"nosuchdir/nosuchdb", xapian
.DB_OPEN|xapian
.DB_BACKEND_STUB
))
128 expect_exception(xapian
.DatabaseNotFoundError
, None,
129 lambda : xapian
.Database(b
"nosuchdir/nosuchdb", xapian
.DB_BACKEND_GLASS
))
130 expect_exception(xapian
.DatabaseCreateError
, None,
131 lambda : xapian
.WritableDatabase(b
"nosuchdir/nosuchdb", xapian
.DB_CREATE|xapian
.DB_BACKEND_GLASS
))
133 expect_exception(xapian
.DatabaseNotFoundError
, None,
134 lambda : xapian
.Database(b
"nosuchdir/nosuchdb", xapian
.DB_BACKEND_CHERT
))
135 expect_exception(xapian
.DatabaseCreateError
, None,
136 lambda : xapian
.WritableDatabase(b
"nosuchdir/nosuchdb", xapian
.DB_CREATE|xapian
.DB_BACKEND_CHERT
))
138 expect_exception(xapian
.NetworkError
, None,
139 xapian
.remote_open
, b
"/bin/false", b
"")
140 expect_exception(xapian
.NetworkError
, None,
141 xapian
.remote_open_writable
, b
"/bin/false", b
"")
143 expect_exception(xapian
.NetworkError
, None,
144 xapian
.remote_open
, b
"127.0.0.1", 0, 1)
145 expect_exception(xapian
.NetworkError
, None,
146 xapian
.remote_open_writable
, b
"127.0.0.1", 0, 1)
148 # Check wrapping of MatchAll and MatchNothing:
150 expect_query(xapian
.Query
.MatchAll
, "<alldocuments>")
151 expect_query(xapian
.Query
.MatchNothing
, "")
153 # Feature test for Query.__iter__
157 expect(term_count
, 4, "Unexpected number of terms in query2")
159 enq
= xapian
.Enquire(db
)
161 # Check Xapian::BAD_VALUENO is wrapped suitably.
162 enq
.set_collapse_key(xapian
.BAD_VALUENO
)
164 enq
.set_query(xapian
.Query(xapian
.Query
.OP_OR
, b
"there", b
"is"))
165 mset
= enq
.get_mset(0, 10)
166 expect(mset
.size(), 1, "Unexpected mset.size()")
167 expect(len(mset
), 1, "Unexpected mset.size()")
169 # Feature test for Enquire.matching_terms(docid)
171 for term
in enq
.matching_terms(mset
.get_hit(0)):
173 expect(term_count
, 2, "Unexpected number of matching terms")
175 # Feature test for MSet.__iter__
179 expect(msize
, mset
.size(), "Unexpected number of entries in mset")
181 terms
= b
" ".join(enq
.matching_terms(mset
.get_hit(0)))
182 expect(terms
, b
"is there", "Unexpected terms")
184 # Feature test for ESet.__iter__
187 eset
= enq
.get_eset(10, rset
)
191 expect(term_count
, 3, "Unexpected number of expand terms")
193 # Feature test for Database.__iter__
197 expect(term_count
, 5, "Unexpected number of terms in db")
199 # Feature test for Database.allterms
201 for term
in db
.allterms():
203 expect(term_count
, 5, "Unexpected number of terms in db.allterms")
205 # Feature test for Database.postlist
207 for posting
in db
.postlist(b
"there"):
209 expect(count
, 1, "Unexpected number of entries in db.postlist('there')")
211 # Feature test for Database.postlist with empty term (alldocspostlist)
213 for posting
in db
.postlist(b
""):
215 expect(count
, 1, "Unexpected number of entries in db.postlist('')")
217 # Feature test for Database.termlist
219 for term
in db
.termlist(1):
221 expect(count
, 5, "Unexpected number of entries in db.termlist(1)")
223 # Feature test for Database.positionlist
225 for term
in db
.positionlist(1, b
"there"):
227 expect(count
, 2, "Unexpected number of entries in db.positionlist(1, 'there')")
229 # Feature test for Document.termlist
231 for term
in doc
.termlist():
233 expect(count
, 5, "Unexpected number of entries in doc.termlist()")
235 # Feature test for TermIter.skip_to
236 term
= doc
.termlist()
241 except StopIteration:
244 raise TestFail("TermIter.skip_to didn't skip term '%s'" % x
.term
.decode('utf-8'))
246 # Feature test for Document.values
248 for term
in list(doc
.values()):
250 expect(count
, 0, "Unexpected number of entries in doc.values")
252 # Check exception handling for Xapian::DocNotFoundError
253 expect_exception(xapian
.DocNotFoundError
, "Docid 3 not found", db
.get_document
, 3)
255 # Check value of OP_ELITE_SET
256 expect(xapian
.Query
.OP_ELITE_SET
, 10, "Unexpected value for OP_ELITE_SET")
258 # Feature test for MatchDecider
259 doc
= xapian
.Document()
261 doc
.add_posting(stem(b
"out"), 1)
262 doc
.add_posting(stem(b
"outside"), 1)
263 doc
.add_posting(stem(b
"source"), 2)
264 doc
.add_value(0, b
"yes")
267 class testmatchdecider(xapian
.MatchDecider
):
268 def __call__(self
, doc
):
269 return doc
.get_value(0) == b
"yes"
271 query
= xapian
.Query(stem(b
"out"))
272 enquire
= xapian
.Enquire(db
)
273 enquire
.set_query(query
)
274 mset
= enquire
.get_mset(0, 10, None, testmatchdecider())
275 expect(mset
.size(), 1, "Unexpected number of documents returned by match decider")
276 expect(mset
.get_docid(0), 2, "MatchDecider mset has wrong docid in")
278 # Feature test for ExpandDecider
279 class testexpanddecider(xapian
.ExpandDecider
):
280 def __call__(self
, term
):
281 return (not term
.startswith(b
'a'))
283 enquire
= xapian
.Enquire(db
)
286 eset
= enquire
.get_eset(10, rset
, xapian
.Enquire
.USE_EXACT_TERMFREQ
, 1.0, testexpanddecider())
287 eset_terms
= [item
.term
for item
in eset
]
288 expect(len(eset_terms
), eset
.size(), "Unexpected number of terms returned by expand")
289 if [t
for t
in eset_terms
if t
.startswith(b
'a')]:
290 raise TestFail("ExpandDecider was not used")
292 # Check min_wt argument to get_eset() works (new in 1.2.5).
293 eset
= enquire
.get_eset(100, rset
, xapian
.Enquire
.USE_EXACT_TERMFREQ
)
294 expect([i
.weight
for i
in eset
][-1] < 1.9, True, "test get_eset() without min_wt")
295 eset
= enquire
.get_eset(100, rset
, xapian
.Enquire
.USE_EXACT_TERMFREQ
, 1.0, None, 1.9)
296 expect([i
.weight
for i
in eset
][-1] >= 1.9, True, "test get_eset() min_wt")
298 # Check QueryParser parsing error.
299 qp
= xapian
.QueryParser()
300 expect_exception(xapian
.QueryParserError
, "Syntax: <expression> AND <expression>", qp
.parse_query
, b
"test AND")
302 # Check QueryParser pure NOT option
303 qp
= xapian
.QueryParser()
304 expect_query(qp
.parse_query(b
"NOT test", qp
.FLAG_BOOLEAN
+ qp
.FLAG_PURE_NOT
),
305 "(<alldocuments> AND_NOT test@1)")
307 # Check QueryParser partial option
308 qp
= xapian
.QueryParser()
310 qp
.set_default_op(xapian
.Query
.OP_AND
)
311 qp
.set_stemming_strategy(qp
.STEM_SOME
)
312 qp
.set_stemmer(xapian
.Stem(b
'en'))
313 expect_query(qp
.parse_query(b
"foo o", qp
.FLAG_PARTIAL
),
314 "(Zfoo@1 AND (WILDCARD SYNONYM o OR Zo@2))")
316 expect_query(qp
.parse_query(b
"foo outside", qp
.FLAG_PARTIAL
),
317 "(Zfoo@1 AND (WILDCARD SYNONYM outside OR Zoutsid@2))")
319 # Test supplying unicode strings
320 expect_query(xapian
.Query(xapian
.Query
.OP_OR
, (b
'foo', b
'bar')),
322 expect_query(xapian
.Query(xapian
.Query
.OP_OR
, (b
'foo', b
'bar\xa3')),
324 expect_query(xapian
.Query(xapian
.Query
.OP_OR
, (b
'foo', b
'bar\xc2\xa3')),
325 '(foo OR bar\u00a3)')
326 expect_query(xapian
.Query(xapian
.Query
.OP_OR
, b
'foo', b
'bar'),
329 expect_query(qp
.parse_query(b
"NOT t\xe9st", qp
.FLAG_BOOLEAN
+ qp
.FLAG_PURE_NOT
),
330 "(<alldocuments> AND_NOT Zt\u00e9st@1)")
332 doc
= xapian
.Document()
333 doc
.set_data(b
"Unicode with an acc\xe9nt")
334 doc
.add_posting(stem(b
"out\xe9r"), 1)
335 expect(doc
.get_data(), b
"Unicode with an acc\xe9nt")
336 term
= next(doc
.termlist()).term
337 expect(term
, b
"out\xe9r")
339 # Check simple stopper
340 stop
= xapian
.SimpleStopper()
342 expect(stop(b
'a'), False)
343 expect_query(qp
.parse_query(b
"foo bar a", qp
.FLAG_BOOLEAN
),
344 "(Zfoo@1 AND Zbar@2 AND Za@3)")
347 expect(stop(b
'a'), True)
348 expect_query(qp
.parse_query(b
"foo bar a", qp
.FLAG_BOOLEAN
),
349 "(Zfoo@1 AND Zbar@2)")
351 # Feature test for custom Stopper
352 class my_b_stopper(xapian
.Stopper
):
353 def __call__(self
, term
):
356 def get_description(self
):
357 return "my_b_stopper"
359 stop
= my_b_stopper()
360 expect(stop
.get_description(), "my_b_stopper")
362 expect(stop(b
'a'), False)
363 expect_query(qp
.parse_query(b
"foo bar a", qp
.FLAG_BOOLEAN
),
364 "(Zfoo@1 AND Zbar@2 AND Za@3)")
366 expect(stop(b
'b'), True)
367 expect_query(qp
.parse_query(b
"foo bar b", qp
.FLAG_BOOLEAN
),
368 "(Zfoo@1 AND Zbar@2)")
371 termgen
= xapian
.TermGenerator()
372 doc
= xapian
.Document()
373 termgen
.set_document(doc
)
374 termgen
.index_text(b
'foo bar baz foo')
375 expect([(item
.term
, item
.wdf
, [pos
for pos
in item
.positer
]) for item
in doc
.termlist()], [(b
'bar', 1, [2]), (b
'baz', 1, [3]), (b
'foo', 2, [1, 4])])
378 # Check DateValueRangeProcessor works
379 context("checking that DateValueRangeProcessor works")
380 qp
= xapian
.QueryParser()
381 vrpdate
= xapian
.DateValueRangeProcessor(1, 1, 1960)
382 qp
.add_valuerangeprocessor(vrpdate
)
383 query
= qp
.parse_query(b
'12/03/99..12/04/01')
384 expect(str(query
), 'Query(VALUE_RANGE 1 19991203 20011204)')
386 # Regression test for bug#193, fixed in 1.0.3.
387 context("running regression test for bug#193")
388 vrp
= xapian
.NumberValueRangeProcessor(0, b
'$', True)
391 slot
, a
, b
= vrp(a
, b
.encode('utf-8'))
393 expect(xapian
.sortable_unserialise(a
), 10)
394 expect(xapian
.sortable_unserialise(b
), 20)
396 # Feature test for xapian.FieldProcessor
397 context("running feature test for xapian.FieldProcessor")
398 class testfieldprocessor(xapian
.FieldProcessor
):
399 def __call__(self
, s
):
401 raise Exception('already spam')
402 return xapian
.Query("spam")
404 qp
.add_prefix('spam', testfieldprocessor())
405 qp
.add_boolean_prefix('boolspam', testfieldprocessor())
406 qp
.add_boolean_prefix('boolspam2', testfieldprocessor(), False) # Old-style
407 qp
.add_boolean_prefix('boolspam3', testfieldprocessor(), '')
408 qp
.add_boolean_prefix('boolspam4', testfieldprocessor(), 'group')
409 qp
.add_boolean_prefix('boolspam5', testfieldprocessor(), None)
410 query
= qp
.parse_query('spam:ignored')
411 expect(str(query
), 'Query(spam)')
413 # FIXME: This doesn't currently work:
414 # expect_exception(Exception, 'already spam', qp.parse_query, 'spam:spam')
416 # Regression tests copied from PHP (probably always worked in python, but
418 context("running regression tests for issues which were found in PHP")
420 # PHP overload resolution involving boolean types failed.
421 enq
.set_sort_by_value(1, True)
423 # Regression test - fixed in 0.9.10.1.
424 oqparser
= xapian
.QueryParser()
425 oquery
= oqparser
.parse_query(b
"I like tea")
427 # Regression test for bug fixed in 1.4.4:
428 # https://bugs.debian.org/849722
429 oqparser
.add_boolean_prefix('tag', 'K', '')
430 # Make sure other cases also work:
431 oqparser
.add_boolean_prefix('zag', 'XR', False) # Old-style
432 oqparser
.add_boolean_prefix('rag', 'XR', None)
433 oqparser
.add_boolean_prefix('nag', 'XB', '')
434 oqparser
.add_boolean_prefix('bag', 'XB', 'blergh')
435 oqparser
.add_boolean_prefix('jag', 'XB', b
'blergh')
437 # Regression test for bug#192 - fixed in 1.0.3.
440 # Test setting and getting metadata
441 expect(db
.get_metadata(b
'Foo'), b
'')
442 db
.set_metadata(b
'Foo', b
'Foo')
443 expect(db
.get_metadata(b
'Foo'), b
'Foo')
444 expect_exception(xapian
.InvalidArgumentError
, "Empty metadata keys are invalid", db
.get_metadata
, b
'')
445 expect_exception(xapian
.InvalidArgumentError
, "Empty metadata keys are invalid", db
.set_metadata
, b
'', b
'Foo')
446 expect_exception(xapian
.InvalidArgumentError
, "Empty metadata keys are invalid", db
.get_metadata
, b
'')
448 # Test OP_SCALE_WEIGHT and corresponding constructor
449 expect_query(xapian
.Query(xapian
.Query
.OP_SCALE_WEIGHT
, xapian
.Query(b
'foo'), 5),
454 stem
= xapian
.Stem(mystem
)
455 expect(stem(b
'test'), b
'tst')
456 stem2
= xapian
.Stem(mystem
)
457 expect(stem2(b
'toastie'), b
'tst')
459 indexer
= xapian
.TermGenerator()
460 indexer
.set_stemmer(xapian
.Stem(MyStemmer()))
462 doc
= xapian
.Document()
463 indexer
.set_document(doc
)
464 indexer
.index_text(b
'hello world')
467 for t
in doc
.termlist():
468 s
+= t
.term
.decode('utf-8')
470 expect(s
, '/Zhll/Zwrld/hello/world/')
472 parser
= xapian
.QueryParser()
473 parser
.set_stemmer(xapian
.Stem(MyStemmer()))
474 parser
.set_stemming_strategy(xapian
.QueryParser
.STEM_ALL
)
475 expect_query(parser
.parse_query(b
'color television'), '(clr@1 OR tlvsn@2)')
477 def test_internal_enums_not_wrapped():
478 leaf_constants
= [c
for c
in dir(xapian
.Query
) if c
.startswith('LEAF_')]
479 expect(leaf_constants
, [])
481 def test_internals_not_wrapped():
483 for c
in dir(xapian
):
484 # Skip Python stuff like __file__ and __version__.
485 if c
.startswith('__'): continue
486 if c
.endswith('_'): internals
.append(c
)
488 if not c
[0].isupper(): continue
489 cls
= eval('xapian.' + c
)
490 if type(cls
) != type(object): continue
492 if m
.startswith('__'): continue
493 if m
.endswith('_'): internals
.append(c
+ '.' + m
)
495 expect(internals
, [])
497 def test_zz9_check_leaks():
501 raise TestFail("%d MyStemmer objects not deleted" % len(mystemmers
))
503 # Run all tests (ie, callables with names starting "test_").
504 if not runtests(globals()):
507 # vim:syntax=python:set expandtab: