1 /** @file api_backend.cc
2 * @brief Backend-related tests.
4 /* Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2018 Olly Betts
5 * Copyright (C) 2010 Richard Boulton
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
25 #include "api_backend.h"
27 #define XAPIAN_DEPRECATED(X) X
30 #include "backendmanager.h"
31 #include "filetests.h"
33 #include "testrunner.h"
34 #include "testsuite.h"
35 #include "testutils.h"
40 #include "safefcntl.h"
41 #include "safesysstat.h"
42 #include "safeunistd.h"
43 #ifdef HAVE_SOCKETPAIR
44 # include "safesyssocket.h"
46 # include "safesyswait.h"
54 /// Regression test - lockfile should honour umask, was only user-readable.
55 DEFINE_TESTCASE(lockfileumask1
, chert
|| glass
) {
56 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
57 mode_t old_umask
= umask(022);
59 Xapian::WritableDatabase db
= get_named_writable_database("lockfileumask1");
61 string path
= get_named_writable_database_path("lockfileumask1");
65 TEST(stat(path
.c_str(), &statbuf
) == 0);
66 TEST_EQUAL(statbuf
.st_mode
& 0777, 0644);
78 /// Check that the backend handles total document length > 0xffffffff.
79 DEFINE_TESTCASE(totaldoclen1
, writable
) {
80 Xapian::WritableDatabase db
= get_writable_database();
82 doc
.add_posting("foo", 1, 2000000000);
84 Xapian::Document doc2
;
85 doc2
.add_posting("bar", 1, 2000000000);
86 db
.add_document(doc2
);
87 TEST_EQUAL(db
.get_avlength(), 2000000000);
88 TEST_EQUAL(db
.get_total_length(), 4000000000ull);
90 TEST_EQUAL(db
.get_avlength(), 2000000000);
91 TEST_EQUAL(db
.get_total_length(), 4000000000ull);
92 for (int i
= 0; i
!= 20; ++i
) {
93 Xapian::Document doc3
;
94 doc3
.add_posting("count" + str(i
), 1, 2000000000);
95 db
.add_document(doc3
);
97 TEST_EQUAL(db
.get_avlength(), 2000000000);
98 TEST_EQUAL(db
.get_total_length(), 44000000000ull);
100 TEST_EQUAL(db
.get_avlength(), 2000000000);
101 TEST_EQUAL(db
.get_total_length(), 44000000000ull);
102 if (get_dbtype() != "inmemory") {
103 // InMemory doesn't support get_writable_database_as_database().
104 Xapian::Database dbr
= get_writable_database_as_database();
105 TEST_EQUAL(dbr
.get_avlength(), 2000000000);
106 TEST_EQUAL(dbr
.get_total_length(), 44000000000ull);
111 // Check that exceeding 32bit in combined database doesn't cause a problem
112 // when using 64bit docids.
113 DEFINE_TESTCASE(exceed32bitcombineddb1
, writable
) {
114 // Test case is for 64-bit Xapian::docid.
115 // FIXME: Though we should check that the overflow is handled gracefully
117 if (sizeof(Xapian::docid
) == 4) return true;
119 // The InMemory backend uses a vector for the documents, so trying to add
120 // a document with the maximum docid is likely to fail because we can't
121 // allocate enough memory!
122 SKIP_TEST_FOR_BACKEND("inmemory");
124 Xapian::WritableDatabase db1
= get_writable_database();
125 Xapian::WritableDatabase db2
= get_writable_database();
126 Xapian::Document doc
;
127 doc
.set_data("prose");
128 doc
.add_term("word");
130 Xapian::docid max_id
= 0xffffffff;
132 db1
.replace_document(max_id
, doc
);
133 db2
.replace_document(max_id
, doc
);
136 db
.add_database(db1
);
137 db
.add_database(db2
);
139 Xapian::Enquire
enquire(db
);
140 enquire
.set_query(Xapian::Query::MatchAll
);
141 Xapian::MSet mymset
= enquire
.get_mset(0, 10);
143 TEST_EQUAL(2, mymset
.size());
145 for (Xapian::MSetIterator i
= mymset
.begin(); i
!= mymset
.end(); ++i
) {
146 TEST_EQUAL("prose", i
.get_document().get_data());
152 DEFINE_TESTCASE(dbstats1
, backend
) {
153 Xapian::Database db
= get_database("etext");
155 // Use precalculated values to avoid expending CPU cycles to calculate
156 // these every time without improving test coverage.
157 const Xapian::termcount min_len
= 2;
158 const Xapian::termcount max_len
= 532;
159 const Xapian::termcount max_wdf
= 22;
161 if (get_dbtype() != "inmemory") {
162 // Should be exact as no deletions have happened.
163 TEST_EQUAL(db
.get_doclength_upper_bound(), max_len
);
164 TEST_EQUAL(db
.get_doclength_lower_bound(), min_len
);
166 // For inmemory, we usually give rather loose bounds.
167 TEST_REL(db
.get_doclength_upper_bound(),>=,max_len
);
168 TEST_REL(db
.get_doclength_lower_bound(),<=,min_len
);
171 if (get_dbtype() != "inmemory" && !startswith(get_dbtype(), "remote")) {
172 TEST_EQUAL(db
.get_wdf_upper_bound("the"), max_wdf
);
174 // For inmemory and remote backends, we usually give rather loose
175 // bounds (remote matches use tighter bounds, but querying the
176 // wdf bound gives a looser one).
177 TEST_REL(db
.get_wdf_upper_bound("the"),>=,max_wdf
);
180 // This failed with an assertion during development between 1.3.1 and
182 TEST_EQUAL(db
.get_wdf_upper_bound(""), 0);
187 // Check stats with a single document. In a multi-database situation, this
188 // gave 0 for get-_doclength_lower_bound() in 1.3.2.
189 DEFINE_TESTCASE(dbstats2
, backend
) {
190 Xapian::Database db
= get_database("apitest_onedoc");
192 // Use precalculated values to avoid expending CPU cycles to calculate
193 // these every time without improving test coverage.
194 const Xapian::termcount min_len
= 15;
195 const Xapian::termcount max_len
= 15;
196 const Xapian::termcount max_wdf
= 7;
198 if (get_dbtype() != "inmemory") {
199 // Should be exact as no deletions have happened.
200 TEST_EQUAL(db
.get_doclength_upper_bound(), max_len
);
201 TEST_EQUAL(db
.get_doclength_lower_bound(), min_len
);
203 // For inmemory, we usually give rather loose bounds.
204 TEST_REL(db
.get_doclength_upper_bound(),>=,max_len
);
205 TEST_REL(db
.get_doclength_lower_bound(),<=,min_len
);
208 if (get_dbtype() != "inmemory" && !startswith(get_dbtype(), "remote")) {
209 TEST_EQUAL(db
.get_wdf_upper_bound("word"), max_wdf
);
211 // For inmemory and remote backends, we usually give rather loose
212 // bounds (remote matches use tighter bounds, but querying the
213 // wdf bound gives a looser one).
214 TEST_REL(db
.get_wdf_upper_bound("word"),>=,max_wdf
);
217 TEST_EQUAL(db
.get_wdf_upper_bound(""), 0);
222 /// Check handling of alldocs on an empty database.
223 DEFINE_TESTCASE(alldocspl3
, backend
) {
224 Xapian::Database db
= get_database(string());
226 TEST_EQUAL(db
.get_termfreq(string()), 0);
227 TEST_EQUAL(db
.get_collection_freq(string()), 0);
228 TEST(db
.postlist_begin(string()) == db
.postlist_end(string()));
233 /// Regression test for bug#392 in ModifiedPostList iteration, fixed in 1.0.15.
234 DEFINE_TESTCASE(modifiedpostlist1
, writable
) {
235 Xapian::WritableDatabase db
= get_writable_database();
236 Xapian::Document a
, b
;
237 Xapian::Enquire
enq(db
);
240 enq
.set_query(Xapian::Query("T"));
242 db
.replace_document(2, a
);
244 db
.replace_document(1, a
);
245 db
.replace_document(1, b
);
247 mset_expect_order(enq
.get_mset(0, 2), 2);
252 /// Regression test for chert bug fixed in 1.1.3 (ticket#397).
253 DEFINE_TESTCASE(doclenaftercommit1
, writable
) {
254 Xapian::WritableDatabase db
= get_writable_database();
255 TEST_EXCEPTION(Xapian::DocNotFoundError
, db
.get_doclength(1));
256 TEST_EXCEPTION(Xapian::DocNotFoundError
, db
.get_unique_terms(1));
257 db
.replace_document(1, Xapian::Document());
259 TEST_EQUAL(db
.get_doclength(1), 0);
260 TEST_EQUAL(db
.get_unique_terms(1), 0);
264 DEFINE_TESTCASE(valuesaftercommit1
, writable
) {
265 Xapian::WritableDatabase db
= get_writable_database();
266 Xapian::Document doc
;
267 doc
.add_value(0, "value");
268 db
.replace_document(2, doc
);
270 db
.replace_document(1, doc
);
271 db
.replace_document(3, doc
);
272 TEST_EQUAL(db
.get_document(3).get_value(0), "value");
274 TEST_EQUAL(db
.get_document(3).get_value(0), "value");
278 DEFINE_TESTCASE(lockfilefd0or1
, chert
|| glass
) {
279 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
280 int old_stdin
= dup(0);
281 int old_stdout
= dup(1);
283 // With fd 0 available.
286 Xapian::WritableDatabase db
= get_writable_database();
287 TEST_EXCEPTION(Xapian::DatabaseLockError
,
288 (void)get_writable_database_again());
290 // With fd 0 and fd 1 available.
293 Xapian::WritableDatabase db
= get_writable_database();
294 TEST_EXCEPTION(Xapian::DatabaseLockError
,
295 (void)get_writable_database_again());
297 // With fd 1 available.
300 Xapian::WritableDatabase db
= get_writable_database();
301 TEST_EXCEPTION(Xapian::DatabaseLockError
,
302 (void)get_writable_database_again());
320 /// Regression test for bug fixed in 1.2.13 and 1.3.1.
321 DEFINE_TESTCASE(lockfilealreadyopen1
, chert
|| glass
) {
322 // Ensure database has been created.
323 (void)get_named_writable_database("lockfilealreadyopen1");
324 string path
= get_named_writable_database_path("lockfilealreadyopen1");
325 int fd
= ::open((path
+ "/flintlock").c_str(), O_RDONLY
);
328 Xapian::WritableDatabase
db(path
, Xapian::DB_CREATE_OR_OPEN
);
329 TEST_EXCEPTION(Xapian::DatabaseLockError
,
330 Xapian::WritableDatabase
db2(path
, Xapian::DB_CREATE_OR_OPEN
)
341 /// Feature tests for Database::locked().
342 DEFINE_TESTCASE(testlock1
, chert
|| glass
) {
343 Xapian::Database rdb
;
346 Xapian::WritableDatabase db
= get_named_writable_database("testlock1");
348 Xapian::Database db_as_database
= db
;
349 TEST(db_as_database
.locked());
351 rdb
= get_writable_database_as_database();
353 TEST(db_as_database
.locked());
356 } catch (const Xapian::FeatureUnavailableError
&) {
357 SKIP_TEST("Database::locked() not supported on this platform");
359 db_as_database
= rdb
;
361 TEST(db_as_database
.locked());
363 db_as_database
.close();
366 // After close(), locked() should either work as if close() hadn't been
367 // called or throw Xapian::DatabaseError.
369 TEST(db_as_database
.locked());
370 } catch (const Xapian::DatabaseError
&) {
375 TEST(!db_as_database
.locked());
376 } catch (const Xapian::DatabaseError
&) {
383 /// Test that locked() returns false for backends which don't support update.
384 /// Regression test for bug fixed in 1.4.6.
385 DEFINE_TESTCASE(testlock2
, backend
&& !writable
&& !multi
) {
386 Xapian::Database db
= get_database("apitest_simpledata");
391 class CheckMatchDecider
: public Xapian::MatchDecider
{
395 CheckMatchDecider() : called(false) { }
397 bool operator()(const Xapian::Document
&) const {
402 bool was_called() const { return called
; }
405 /// Test Xapian::MatchDecider with remote backend fails.
406 DEFINE_TESTCASE(matchdecider4
, remote
) {
407 Xapian::Database
db(get_database("apitest_simpledata"));
408 Xapian::Enquire
enquire(db
);
409 enquire
.set_query(Xapian::Query("paragraph"));
411 CheckMatchDecider mdecider
;
414 TEST_EXCEPTION(Xapian::UnimplementedError
,
415 mset
= enquire
.get_mset(0, 10, NULL
, &mdecider
));
416 TEST(!mdecider
.was_called());
421 /** Check that replacing an unmodified document doesn't increase the automatic
422 * flush counter. Regression test for bug fixed in 1.1.4/1.0.18.
424 DEFINE_TESTCASE(replacedoc7
, writable
&& !inmemory
&& !remote
) {
425 // The inmemory backend doesn't batch changes, so there's nothing to
428 // The remote backend doesn't implement the lazy replacement of documents
429 // optimisation currently.
430 Xapian::WritableDatabase
db(get_writable_database());
431 Xapian::Document doc
;
432 doc
.set_data("fish");
433 doc
.add_term("Hlocalhost");
434 doc
.add_posting("hello", 1);
435 doc
.add_posting("world", 2);
436 doc
.add_value(1, "myvalue");
437 db
.add_document(doc
);
440 // We add a second document, and then replace the first document with
441 // itself 10000 times. If the document count for the database reopened
442 // read-only is 2, then we triggered an automatic commit.
444 doc
.add_term("XREV2");
445 db
.add_document(doc
);
447 for (int i
= 0; i
< 10000; ++i
) {
448 doc
= db
.get_document(1);
449 db
.replace_document(1, doc
);
452 Xapian::Database
rodb(get_writable_database_as_database());
453 TEST_EQUAL(rodb
.get_doccount(), 1);
458 TEST_EQUAL(rodb
.get_doccount(), 2);
462 /** Check that replacing a document deleted since the last flush works.
463 * Prior to 1.1.4/1.0.18, this failed to update the collection frequency and
464 * wdf, and caused an assertion failure when assertions were enabled.
466 DEFINE_TESTCASE(replacedoc8
, writable
) {
467 Xapian::WritableDatabase
db(get_writable_database());
469 Xapian::Document doc
;
470 doc
.set_data("fish");
471 doc
.add_term("takeaway");
472 db
.add_document(doc
);
474 db
.delete_document(1);
476 Xapian::Document doc
;
477 doc
.set_data("chips");
478 doc
.add_term("takeaway", 2);
479 db
.replace_document(1, doc
);
482 TEST_EQUAL(db
.get_collection_freq("takeaway"), 2);
483 Xapian::PostingIterator p
= db
.postlist_begin("takeaway");
484 TEST(p
!= db
.postlist_end("takeaway"));
485 TEST_EQUAL(p
.get_wdf(), 2);
489 /// Test coverage for DatabaseModifiedError.
490 DEFINE_TESTCASE(databasemodified1
, writable
&& !inmemory
&& !remote
&& !multi
) {
491 // The inmemory backend doesn't support revisions.
493 // The remote backend doesn't work as expected here, I think due to
494 // test harness issues.
496 // With multi, DatabaseModifiedError doesn't trigger as easily.
497 Xapian::WritableDatabase
db(get_writable_database());
498 Xapian::Document doc
;
499 doc
.set_data("cargo");
504 for (int i
= 0; i
< N
; ++i
) {
505 db
.add_document(doc
);
509 Xapian::Database
rodb(get_writable_database_as_database());
510 db
.add_document(doc
);
513 db
.add_document(doc
);
516 db
.add_document(doc
);
518 TEST_EQUAL(*rodb
.termlist_begin(N
- 1), "abc");
520 } catch (const Xapian::DatabaseModifiedError
&) {
524 Xapian::Enquire
enq(rodb
);
525 enq
.set_query(Xapian::Query("abc"));
526 Xapian::MSet mset
= enq
.get_mset(0, 10);
528 } catch (const Xapian::DatabaseModifiedError
&) {
534 /// Regression test for bug#462 fixed in 1.0.19 and 1.1.5.
535 DEFINE_TESTCASE(qpmemoryleak1
, writable
&& !inmemory
) {
536 // Inmemory never throws DatabaseModifiedError.
537 Xapian::WritableDatabase
wdb(get_writable_database());
538 Xapian::Document doc
;
541 for (int i
= 100; i
< 120; ++i
) {
542 doc
.add_term(str(i
));
545 for (int j
= 0; j
< 50; ++j
) {
546 wdb
.add_document(doc
);
550 Xapian::Database
database(get_writable_database_as_database());
551 Xapian::QueryParser queryparser
;
552 queryparser
.set_database(database
);
553 TEST_EXCEPTION(Xapian::DatabaseModifiedError
,
554 for (int k
= 0; k
< 1000; ++k
) {
555 wdb
.add_document(doc
);
557 (void)queryparser
.parse_query("1", queryparser
.FLAG_PARTIAL
);
559 SKIP_TEST("didn't manage to trigger DatabaseModifiedError");
566 make_msize1_db(Xapian::WritableDatabase
&db
, const string
&)
568 const char * value0
=
569 "ABBCDEFGHIJKLMMNOPQQRSTTUUVVWXYZZaabcdefghhijjkllmnopqrsttuvwxyz";
570 const char * value1
=
571 "EMLEMMMMMMMNMMLMELEDNLEDMLMLDMLMLMLMEDGFHPOPBAHJIQJNGRKCGF";
573 Xapian::Document doc
;
574 doc
.add_value(0, string(1, *value0
++));
576 doc
.add_value(1, string(1, *value1
++));
579 db
.add_document(doc
);
583 /// Regression test for ticket#464, fixed in 1.1.6 and 1.0.20.
584 DEFINE_TESTCASE(msize1
, generated
) {
585 Xapian::Database db
= get_database("msize1", make_msize1_db
);
586 Xapian::Enquire
enq(db
);
587 enq
.set_sort_by_value(1, false);
588 enq
.set_collapse_key(0);
589 enq
.set_query(Xapian::Query("K1"));
591 Xapian::MSet mset
= enq
.get_mset(0, 60);
592 Xapian::doccount lb
= mset
.get_matches_lower_bound();
593 Xapian::doccount ub
= mset
.get_matches_upper_bound();
594 Xapian::doccount est
= mset
.get_matches_estimated();
598 Xapian::MSet mset2
= enq
.get_mset(50, 10, 1000);
599 Xapian::doccount lb2
= mset2
.get_matches_lower_bound();
600 Xapian::doccount ub2
= mset2
.get_matches_upper_bound();
601 Xapian::doccount est2
= mset2
.get_matches_estimated();
602 TEST_EQUAL(lb2
, ub2
);
603 TEST_EQUAL(lb2
, est2
);
604 TEST_EQUAL(est
, est2
);
606 Xapian::MSet mset3
= enq
.get_mset(0, 10, 1000);
607 Xapian::doccount lb3
= mset3
.get_matches_lower_bound();
608 Xapian::doccount ub3
= mset3
.get_matches_upper_bound();
609 Xapian::doccount est3
= mset3
.get_matches_estimated();
610 TEST_EQUAL(lb3
, ub3
);
611 TEST_EQUAL(lb3
, est3
);
612 TEST_EQUAL(est
, est3
);
618 make_msize2_db(Xapian::WritableDatabase
&db
, const string
&)
620 const char * value0
= "AAABCDEEFGHIIJJKLLMNNOOPPQQRSTTUVWXYZ";
621 const char * value1
= "MLEMNMLMLMEDEDEMLEMLMLMLPOAHGF";
623 Xapian::Document doc
;
624 doc
.add_value(0, string(1, *value0
++));
626 doc
.add_value(1, string(1, *value1
++));
629 db
.add_document(doc
);
633 /// Regression test for bug related to ticket#464, fixed in 1.1.6 and 1.0.20.
634 DEFINE_TESTCASE(msize2
, generated
) {
635 Xapian::Database db
= get_database("msize2", make_msize2_db
);
636 Xapian::Enquire
enq(db
);
637 enq
.set_sort_by_value(1, false);
638 enq
.set_collapse_key(0);
639 enq
.set_query(Xapian::Query("K1"));
641 Xapian::MSet mset
= enq
.get_mset(0, 60);
642 Xapian::doccount lb
= mset
.get_matches_lower_bound();
643 Xapian::doccount ub
= mset
.get_matches_upper_bound();
644 Xapian::doccount est
= mset
.get_matches_estimated();
648 Xapian::MSet mset2
= enq
.get_mset(50, 10, 1000);
649 Xapian::doccount lb2
= mset2
.get_matches_lower_bound();
650 Xapian::doccount ub2
= mset2
.get_matches_upper_bound();
651 Xapian::doccount est2
= mset2
.get_matches_estimated();
652 TEST_EQUAL(lb2
, ub2
);
653 TEST_EQUAL(lb2
, est2
);
654 TEST_EQUAL(est
, est2
);
656 Xapian::MSet mset3
= enq
.get_mset(0, 10, 1000);
657 Xapian::doccount lb3
= mset3
.get_matches_lower_bound();
658 Xapian::doccount ub3
= mset3
.get_matches_upper_bound();
659 Xapian::doccount est3
= mset3
.get_matches_estimated();
660 TEST_EQUAL(lb3
, ub3
);
661 TEST_EQUAL(lb3
, est3
);
662 TEST_EQUAL(est
, est3
);
668 make_xordecay1_db(Xapian::WritableDatabase
&db
, const string
&)
670 for (int n
= 1; n
!= 50; ++n
) {
671 Xapian::Document doc
;
672 for (int i
= 1; i
!= 50; ++i
) {
674 doc
.add_term("N" + str(i
));
676 db
.add_document(doc
);
680 /// Regression test for bug in decay of XOR, fixed in 1.2.1 and 1.0.21.
681 DEFINE_TESTCASE(xordecay1
, generated
) {
682 Xapian::Database db
= get_database("xordecay1", make_xordecay1_db
);
683 Xapian::Enquire
enq(db
);
684 enq
.set_query(Xapian::Query(Xapian::Query::OP_XOR
,
685 Xapian::Query("N10"),
686 Xapian::Query(Xapian::Query::OP_OR
,
688 Xapian::Query("N3"))));
689 Xapian::MSet mset1
= enq
.get_mset(0, 1);
690 Xapian::MSet msetall
= enq
.get_mset(0, db
.get_doccount());
692 TEST(mset_range_is_same(mset1
, 0, msetall
, 0, mset1
.size()));
697 make_ordecay_db(Xapian::WritableDatabase
&db
, const string
&)
699 const char * p
= "VJ=QC]LUNTaARLI;715RR^];A4O=P4ZG<2CS4EM^^VS[A6QENR";
700 for (int d
= 0; p
[d
]; ++d
) {
701 int l
= int(p
[d
] - '0');
702 Xapian::Document doc
;
703 for (int n
= 1; n
< l
; ++n
) {
704 doc
.add_term("N" + str(n
));
705 if (n
% (d
+ 1) == 0) {
706 doc
.add_term("M" + str(n
));
709 db
.add_document(doc
);
713 /// Regression test for bug in decay of OR to AND, fixed in 1.2.1 and 1.0.21.
714 DEFINE_TESTCASE(ordecay1
, generated
) {
715 Xapian::Database db
= get_database("ordecay", make_ordecay_db
);
716 Xapian::Enquire
enq(db
);
717 enq
.set_query(Xapian::Query(Xapian::Query::OP_OR
,
718 Xapian::Query("N20"),
719 Xapian::Query("N21")));
721 Xapian::MSet msetall
= enq
.get_mset(0, db
.get_doccount());
722 for (unsigned int i
= 1; i
< msetall
.size(); ++i
) {
723 Xapian::MSet submset
= enq
.get_mset(0, i
);
724 TEST(mset_range_is_same(submset
, 0, msetall
, 0, submset
.size()));
729 /** Regression test for bug in decay of OR to AND_MAYBE, fixed in 1.2.1 and
732 DEFINE_TESTCASE(ordecay2
, generated
) {
733 Xapian::Database db
= get_database("ordecay", make_ordecay_db
);
734 Xapian::Enquire
enq(db
);
735 std::vector
<Xapian::Query
> q
;
736 q
.push_back(Xapian::Query("M20"));
737 q
.push_back(Xapian::Query("N21"));
738 q
.push_back(Xapian::Query("N22"));
739 enq
.set_query(Xapian::Query(Xapian::Query::OP_OR
,
740 Xapian::Query("N25"),
741 Xapian::Query(Xapian::Query::OP_AND
,
745 Xapian::MSet msetall
= enq
.get_mset(0, db
.get_doccount());
746 for (unsigned int i
= 1; i
< msetall
.size(); ++i
) {
747 Xapian::MSet submset
= enq
.get_mset(0, i
);
748 TEST(mset_range_is_same(submset
, 0, msetall
, 0, submset
.size()));
754 make_orcheck_db(Xapian::WritableDatabase
&db
, const string
&)
756 static const unsigned t1
[] = {2, 4, 6, 8, 10};
757 static const unsigned t2
[] = {6, 7, 8, 11, 12, 13, 14, 15, 16, 17};
758 static const unsigned t3
[] = {3, 7, 8, 11, 12, 13, 14, 15, 16, 17};
760 for (unsigned i
= 1; i
<= 17; ++i
) {
761 Xapian::Document doc
;
762 db
.replace_document(i
, doc
);
764 for (unsigned i
: t1
) {
765 Xapian::Document
doc(db
.get_document(i
));
767 db
.replace_document(i
, doc
);
769 for (unsigned i
: t2
) {
770 Xapian::Document
doc(db
.get_document(i
));
773 doc
.add_term("T2_lowfreq");
775 doc
.add_value(2, "1");
776 db
.replace_document(i
, doc
);
778 for (unsigned i
: t3
) {
779 Xapian::Document
doc(db
.get_document(i
));
782 doc
.add_term("T3_lowfreq");
784 doc
.add_value(3, "1");
785 db
.replace_document(i
, doc
);
789 /** Regression test for bugs in the check() method of OrPostList. (ticket #485)
790 * Bugs introduced and fixed between 1.2.0 and 1.2.1 (never in a release).
792 DEFINE_TESTCASE(orcheck1
, generated
) {
793 Xapian::Database db
= get_database("orcheck1", make_orcheck_db
);
794 Xapian::Enquire
enq(db
);
795 Xapian::Query
q1("T1");
796 Xapian::Query
q2("T2");
797 Xapian::Query
q2l("T2_lowfreq");
798 Xapian::Query
q3("T3");
799 Xapian::Query
q3l("T3_lowfreq");
800 Xapian::Query
v2(Xapian::Query::OP_VALUE_RANGE
, 2, "0", "2");
801 Xapian::Query
v3(Xapian::Query::OP_VALUE_RANGE
, 3, "0", "2");
803 tout
<< "Checking q2 OR q3\n";
804 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
805 Xapian::Query(Xapian::Query::OP_OR
, q2
, q3
)));
806 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
808 tout
<< "Checking q2l OR q3\n";
809 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
810 Xapian::Query(Xapian::Query::OP_OR
, q2l
, q3
)));
811 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
813 tout
<< "Checking q2 OR q3l\n";
814 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
815 Xapian::Query(Xapian::Query::OP_OR
, q2
, q3l
)));
816 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
818 tout
<< "Checking v2 OR q3\n";
819 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
820 Xapian::Query(Xapian::Query::OP_OR
, v2
, q3
)));
821 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
823 tout
<< "Checking q2 OR v3\n";
824 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
825 Xapian::Query(Xapian::Query::OP_OR
, q2
, v3
)));
826 // Order of results in this one is different, because v3 gives no weight,
827 // both documents are in q2, and document 8 has a higher length.
828 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 6, 8);
833 /** Regression test for bug fixed in 1.2.1 and 1.0.21.
835 * We failed to mark the Btree as unmodified after cancel().
837 DEFINE_TESTCASE(failedreplace1
, chert
|| glass
) {
838 Xapian::WritableDatabase
db(get_writable_database());
839 Xapian::Document doc
;
841 db
.add_document(doc
);
842 Xapian::docid did
= db
.add_document(doc
);
844 doc
.add_term(string(1000, 'm'));
846 TEST_EXCEPTION(Xapian::InvalidArgumentError
, db
.replace_document(did
, doc
));
848 TEST_EQUAL(db
.get_doccount(), 0);
849 TEST_EQUAL(db
.get_termfreq("foo"), 0);
853 DEFINE_TESTCASE(failedreplace2
, chert
|| glass
) {
854 Xapian::WritableDatabase
db(get_writable_database("apitest_simpledata"));
856 Xapian::doccount db_size
= db
.get_doccount();
857 Xapian::Document doc
;
858 doc
.set_data("wibble");
860 doc
.add_value(0, "seven");
861 db
.add_document(doc
);
862 Xapian::docid did
= db
.add_document(doc
);
864 doc
.add_term(string(1000, 'm'));
866 doc
.add_value(0, "six");
867 TEST_EXCEPTION(Xapian::InvalidArgumentError
, db
.replace_document(did
, doc
));
869 TEST_EQUAL(db
.get_doccount(), db_size
);
870 TEST_EQUAL(db
.get_termfreq("foo"), 0);
874 /// Coverage for SelectPostList::skip_to().
875 DEFINE_TESTCASE(phrase3
, positional
) {
876 Xapian::Database db
= get_database("apitest_phrase");
878 static const char * const phrase_words
[] = { "phrase", "near" };
879 Xapian::Query
q(Xapian::Query::OP_NEAR
, phrase_words
, phrase_words
+ 2, 12);
880 q
= Xapian::Query(Xapian::Query::OP_AND_MAYBE
, Xapian::Query("pad"), q
);
882 Xapian::Enquire
enquire(db
);
883 enquire
.set_query(q
);
884 Xapian::MSet mset
= enquire
.get_mset(0, 5);
889 /// Check that get_mset(<large number>, 10) doesn't exhaust memory needlessly.
890 // Regression test for fix in 1.2.4.
891 DEFINE_TESTCASE(msetfirst2
, backend
) {
892 Xapian::Database
db(get_database("apitest_simpledata"));
893 Xapian::Enquire
enquire(db
);
894 enquire
.set_query(Xapian::Query("paragraph"));
896 // Before the fix, this tried to allocate too much memory.
897 mset
= enquire
.get_mset(0xfffffff0, 1);
898 TEST_EQUAL(mset
.get_firstitem(), 0xfffffff0);
899 // Check that the number of documents gets clamped too.
900 mset
= enquire
.get_mset(1, 0xfffffff0);
901 TEST_EQUAL(mset
.get_firstitem(), 1);
902 // Another regression test - MatchNothing used to give an MSet with
903 // get_firstitem() returning 0.
904 enquire
.set_query(Xapian::Query::MatchNothing
);
905 mset
= enquire
.get_mset(1, 1);
906 TEST_EQUAL(mset
.get_firstitem(), 1);
910 DEFINE_TESTCASE(bm25weight2
, backend
) {
911 Xapian::Database
db(get_database("etext"));
912 Xapian::Enquire
enquire(db
);
913 enquire
.set_query(Xapian::Query("the"));
914 enquire
.set_weighting_scheme(Xapian::BM25Weight(0, 0, 0, 0, 1));
915 Xapian::MSet mset
= enquire
.get_mset(0, 100);
916 TEST_REL(mset
.size(),>=,2);
917 double weight0
= mset
[0].get_weight();
918 for (size_t i
= 1; i
!= mset
.size(); ++i
) {
919 TEST_EQUAL(weight0
, mset
[i
].get_weight());
924 DEFINE_TESTCASE(unigramlmweight2
, backend
) {
925 Xapian::Database
db(get_database("etext"));
926 Xapian::Enquire
enquire(db
);
927 enquire
.set_query(Xapian::Query("the"));
928 enquire
.set_weighting_scheme(Xapian::LMWeight());
929 Xapian::MSet mset
= enquire
.get_mset(0, 100);
930 TEST_REL(mset
.size(),>=,2);
934 DEFINE_TESTCASE(tradweight2
, backend
) {
935 Xapian::Database
db(get_database("etext"));
936 Xapian::Enquire
enquire(db
);
937 enquire
.set_query(Xapian::Query("the"));
938 enquire
.set_weighting_scheme(Xapian::TradWeight(0));
939 Xapian::MSet mset
= enquire
.get_mset(0, 100);
940 TEST_REL(mset
.size(),>=,2);
941 double weight0
= mset
[0].get_weight();
942 for (size_t i
= 1; i
!= mset
.size(); ++i
) {
943 TEST_EQUAL(weight0
, mset
[i
].get_weight());
948 // Regression test for bug fix in 1.2.9.
949 DEFINE_TESTCASE(emptydb1
, backend
) {
950 Xapian::Database
db(get_database(string()));
951 static const Xapian::Query::op ops
[] = {
952 Xapian::Query::OP_AND
,
953 Xapian::Query::OP_OR
,
954 Xapian::Query::OP_AND_NOT
,
955 Xapian::Query::OP_XOR
,
956 Xapian::Query::OP_AND_MAYBE
,
957 Xapian::Query::OP_FILTER
,
958 Xapian::Query::OP_NEAR
,
959 Xapian::Query::OP_PHRASE
,
960 Xapian::Query::OP_ELITE_SET
962 const Xapian::Query::op
* p
;
963 for (p
= ops
; p
- ops
!= sizeof(ops
) / sizeof(*ops
); ++p
) {
965 Xapian::Enquire
enquire(db
);
966 Xapian::Query
query(*p
, Xapian::Query("a"), Xapian::Query("b"));
967 enquire
.set_query(query
);
968 Xapian::MSet mset
= enquire
.get_mset(0, 10);
969 TEST_EQUAL(mset
.get_matches_estimated(), 0);
970 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
971 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
976 /// Test error opening non-existent stub databases.
977 // Regression test for bug fixed in 1.3.1 and 1.2.11.
978 DEFINE_TESTCASE(stubdb7
, !backend
) {
979 TEST_EXCEPTION(Xapian::DatabaseOpeningError
,
980 Xapian::Database("nosuchdirectory", Xapian::DB_BACKEND_STUB
));
981 TEST_EXCEPTION(Xapian::DatabaseOpeningError
,
982 Xapian::WritableDatabase("nosuchdirectory",
983 Xapian::DB_OPEN
|Xapian::DB_BACKEND_STUB
));
987 /// Test which checks the weights are as expected.
988 // This runs for multi_* too, so serves to check that we get the same weights
989 // with multiple databases as without.
990 DEFINE_TESTCASE(msetweights1
, backend
) {
991 Xapian::Database db
= get_database("apitest_simpledata");
992 Xapian::Enquire
enq(db
);
993 Xapian::Query
q(Xapian::Query::OP_OR
,
994 Xapian::Query("paragraph"),
995 Xapian::Query("word"));
997 // 5 documents match, and the 4th and 5th have the same weight, so ask for
998 // 4 as that's a good test that we get the right one in this case.
999 Xapian::MSet mset
= enq
.get_mset(0, 4);
1001 static const struct { Xapian::docid did
; double wt
; } expected
[] = {
1002 { 2, 1.2058248004573934864 },
1003 { 4, 0.81127876655507624726 },
1004 { 1, 0.17309550762546158098 },
1005 { 3, 0.14609528172558261527 }
1008 TEST_EQUAL(mset
.size(), sizeof(expected
) / sizeof(expected
[0]));
1009 for (size_t i
= 0; i
< mset
.size(); ++i
) {
1010 TEST_EQUAL(*mset
[i
], expected
[i
].did
);
1011 TEST_EQUAL_DOUBLE(mset
[i
].get_weight(), expected
[i
].wt
);
1014 // Now test a query which matches only even docids, so in the multi case
1015 // one subdatabase doesn't match.
1016 enq
.set_query(Xapian::Query("one"));
1017 mset
= enq
.get_mset(0, 3);
1019 static const struct { Xapian::docid did
; double wt
; } expected2
[] = {
1020 { 6, 0.73354729848273669823 },
1021 { 2, 0.45626501034348893038 }
1024 TEST_EQUAL(mset
.size(), sizeof(expected2
) / sizeof(expected2
[0]));
1025 for (size_t i
= 0; i
< mset
.size(); ++i
) {
1026 TEST_EQUAL(*mset
[i
], expected2
[i
].did
);
1027 TEST_EQUAL_DOUBLE(mset
[i
].get_weight(), expected2
[i
].wt
);
1033 DEFINE_TESTCASE(itorskiptofromend1
, backend
) {
1034 Xapian::Database db
= get_database("apitest_simpledata");
1036 Xapian::TermIterator t
= db
.termlist_begin(1);
1038 TEST(t
== db
.termlist_end(1));
1039 // This worked in 1.2.x but segfaulted in 1.3.1.
1040 t
.skip_to("zzzzzz");
1042 Xapian::PostingIterator p
= db
.postlist_begin("one");
1044 TEST(p
== db
.postlist_end("one"));
1045 // This segfaulted prior to 1.3.2.
1048 Xapian::PositionIterator i
= db
.positionlist_begin(6, "one");
1050 TEST(i
== db
.positionlist_end(6, "one"));
1051 // This segfaulted prior to 1.3.2.
1054 Xapian::ValueIterator v
= db
.valuestream_begin(1);
1056 TEST(v
== db
.valuestream_end(1));
1057 // These segfaulted prior to 1.3.2.
1064 /// Check handling of invalid block sizes.
1065 // Regression test for bug fixed in 1.2.17 and 1.3.2 - the size gets fixed
1066 // but the uncorrected size was passed to the base file. Also, abort() was
1068 DEFINE_TESTCASE(blocksize1
, chert
|| glass
) {
1069 string db_dir
= "." + get_dbtype();
1070 mkdir(db_dir
.c_str(), 0755);
1071 db_dir
+= "/db__blocksize1";
1073 if (get_dbtype() == "chert") {
1074 flags
= Xapian::DB_CREATE
|Xapian::DB_BACKEND_CHERT
;
1076 flags
= Xapian::DB_CREATE
|Xapian::DB_BACKEND_GLASS
;
1078 static const unsigned bad_sizes
[] = {
1079 65537, 8000, 2000, 1024, 16, 7, 3, 1, 0
1081 for (size_t i
= 0; i
< sizeof(bad_sizes
) / sizeof(bad_sizes
[0]); ++i
) {
1082 size_t block_size
= bad_sizes
[i
];
1084 Xapian::WritableDatabase
db(db_dir
, flags
, block_size
);
1085 Xapian::Document doc
;
1086 doc
.add_term("XYZ");
1087 doc
.set_data("foo");
1088 db
.add_document(doc
);
1094 /// Feature test for Xapian::DB_NO_TERMLIST.
1095 DEFINE_TESTCASE(notermlist1
, glass
) {
1096 string db_dir
= "." + get_dbtype();
1097 mkdir(db_dir
.c_str(), 0755);
1098 db_dir
+= "/db__notermlist1";
1099 int flags
= Xapian::DB_CREATE
|Xapian::DB_NO_TERMLIST
;
1100 if (get_dbtype() == "chert") {
1101 flags
|= Xapian::DB_BACKEND_CHERT
;
1103 flags
|= Xapian::DB_BACKEND_GLASS
;
1106 Xapian::WritableDatabase
db(db_dir
, flags
);
1107 Xapian::Document doc
;
1108 doc
.add_term("hello");
1109 doc
.add_value(42, "answer");
1110 db
.add_document(doc
);
1112 TEST(!file_exists(db_dir
+ "/termlist.glass"));
1113 TEST_EXCEPTION(Xapian::FeatureUnavailableError
, db
.termlist_begin(1));
1117 /// Regression test for bug starting a new glass freelist block.
1118 DEFINE_TESTCASE(newfreelistblock1
, writable
) {
1119 Xapian::Document doc
;
1120 doc
.add_term("foo");
1121 for (int i
= 100; i
< 120; ++i
) {
1122 doc
.add_term(str(i
));
1125 Xapian::WritableDatabase
wdb(get_writable_database());
1126 for (int j
= 0; j
< 50; ++j
) {
1127 wdb
.add_document(doc
);
1131 for (int k
= 0; k
< 1000; ++k
) {
1132 wdb
.add_document(doc
);
1139 /** Check that the parent directory for the database doesn't need to be
1140 * writable. Regression test for early versions on the glass new btree
1141 * branch which failed to append a "/" when generating a temporary filename
1142 * from the database directory.
1144 DEFINE_TESTCASE(readonlyparentdir1
, chert
|| glass
) {
1145 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1146 string path
= get_named_writable_database_path("readonlyparentdir1");
1147 // Fix permissions if the previous test was killed.
1148 (void)chmod(path
.c_str(), 0700);
1149 mkdir(path
.c_str(), 0777);
1150 mkdir((path
+ "/sub").c_str(), 0777);
1151 Xapian::WritableDatabase db
= get_named_writable_database("readonlyparentdir1/sub");
1152 TEST(chmod(path
.c_str(), 0500) == 0);
1154 Xapian::Document doc
;
1155 doc
.add_term("hello");
1156 doc
.set_data("some text");
1157 db
.add_document(doc
);
1160 // Attempt to fix the permissions, otherwise things like "rm -rf" on
1161 // the source tree will fail.
1162 (void)chmod(path
.c_str(), 0700);
1165 TEST(chmod(path
.c_str(), 0700) == 0);
1171 make_phrasebug1_db(Xapian::WritableDatabase
&db
, const string
&)
1173 Xapian::Document doc
;
1174 doc
.add_posting("hurricane", 199881);
1175 doc
.add_posting("hurricane", 203084);
1176 doc
.add_posting("katrina", 199882);
1177 doc
.add_posting("katrina", 202473);
1178 doc
.add_posting("katrina", 203085);
1179 db
.add_document(doc
);
1182 /// Regression test for ticket#653, fixed in 1.3.2 and 1.2.19.
1183 DEFINE_TESTCASE(phrasebug1
, generated
&& positional
) {
1184 Xapian::Database db
= get_database("phrasebug1", make_phrasebug1_db
);
1185 static const char * const qterms
[] = { "katrina", "hurricane" };
1186 Xapian::Enquire
e(db
);
1187 Xapian::Query
q(Xapian::Query::OP_PHRASE
, qterms
, qterms
+ 2, 5);
1189 Xapian::MSet mset
= e
.get_mset(0, 100);
1190 TEST_EQUAL(mset
.size(), 0);
1191 static const char * const qterms2
[] = { "hurricane", "katrina" };
1192 Xapian::Query
q2(Xapian::Query::OP_PHRASE
, qterms2
, qterms2
+ 2, 5);
1194 mset
= e
.get_mset(0, 100);
1195 TEST_EQUAL(mset
.size(), 1);
1199 /// Feature test for Xapian::DB_RETRY_LOCK
1200 DEFINE_TESTCASE(retrylock1
, writable
&& !inmemory
&& !remote
) {
1201 // FIXME: Can't see an easy way to test this for remote databases - the
1202 // harness doesn't seem to provide a suitable way to reopen a remote.
1203 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
1205 if (socketpair(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, PF_UNSPEC
, fds
) < 0) {
1206 FAIL_TEST("socketpair() failed");
1208 if (fcntl(fds
[1], F_SETFL
, O_NONBLOCK
) < 0)
1209 FAIL_TEST("fcntl() failed to set O_NONBLOCK");
1210 pid_t child
= fork();
1212 FAIL_TEST("fork() failed");
1214 // Wait for signal that parent has opened the database.
1216 while (read(fds
[0], &ch
, 1) < 0) { }
1219 Xapian::WritableDatabase
db2(get_named_writable_database_path("retrylock1"),
1220 Xapian::DB_OPEN
|Xapian::DB_RETRY_LOCK
);
1221 if (write(fds
[0], "y", 1)) { }
1222 } catch (const Xapian::DatabaseLockError
&) {
1223 if (write(fds
[0], "l", 1)) { }
1224 } catch (const Xapian::Error
&e
) {
1225 const string
& m
= e
.get_description();
1226 if (write(fds
[0], m
.data(), m
.size())) { }
1228 if (write(fds
[0], "o", 1)) { }
1235 Xapian::WritableDatabase db
= get_named_writable_database("retrylock1");
1236 if (write(fds
[1], "", 1) != 1)
1237 FAIL_TEST("Failed to signal to child process");
1240 int r
= read(fds
[1], result
, sizeof(result
));
1242 if (errno
== EAGAIN
) {
1247 tout
<< "errno=" << errno
<< ": " << strerror(errno
) << endl
;
1251 } else if (r
>= 1) {
1252 if (result
[0] == 'y') {
1253 // Child process managed to also get write lock!
1265 kill(child
, SIGKILL
);
1267 while (waitpid(child
, &status
, 0) < 0) {
1268 if (errno
!= EINTR
) break;
1273 if (result
[0] == 'y') {
1280 FD_SET(fds
[1], &fr
);
1282 FD_SET(fds
[1], &fe
);
1283 int sr
= select(fds
[1] + 1, &fr
, NULL
, &fe
, &tv
);
1288 } else if (sr
== -1) {
1289 if (errno
== EINTR
|| errno
== EAGAIN
)
1291 tout
<< "select() failed with errno=" << errno
<< ": " << strerror(errno
) << endl
;
1295 r
= read(fds
[1], result
, sizeof(result
));
1298 tout
<< "read failed with errno=" << errno
<< ": " << strerror(errno
) << endl
;
1301 } else if (r
== 0) {
1311 kill(child
, SIGKILL
);
1313 while (waitpid(child
, &status
, 0) < 0) {
1314 if (errno
!= EINTR
) break;
1317 tout
<< string(result
, r
) << endl
;
1318 TEST_EQUAL(result
[0], 'y');
1324 // Opening a WritableDatabase with low fds available - it should avoid them.
1325 DEFINE_TESTCASE(dbfilefd012
, chert
|| glass
) {
1326 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1328 for (int i
= 0; i
< 3; ++i
) {
1332 for (int j
= 0; j
< 3; ++j
) {
1334 TEST_REL(lseek(j
, 0, SEEK_CUR
), <, 0);
1335 TEST_EQUAL(errno
, EBADF
);
1338 Xapian::WritableDatabase db
= get_writable_database();
1340 // Check we didn't use any of those low fds for tables, as that risks
1341 // data corruption if some other code in the same process tries to
1342 // write to them (see #651).
1343 for (int fd
= 0; fd
< 3; ++fd
) {
1344 // Check that the fd is still closed, or isn't open O_RDWR (the
1345 // lock file gets opened O_WRONLY), or it's a pipe (if we're using
1346 // a child process to hold a non-OFD fcntl lock).
1347 int flags
= fcntl(fd
, F_GETFL
);
1349 TEST_EQUAL(errno
, EBADF
);
1350 } else if ((flags
& O_ACCMODE
) != O_RDWR
) {
1354 TEST_NOT_EQUAL(fstat(fd
, &sb
), -1);
1356 TEST(S_ISSOCK(sb
.st_mode
));
1358 // If we can't check it is a socket, at least check it is not a
1360 TEST(!S_ISREG(sb
.st_mode
));
1365 for (int j
= 0; j
< 3; ++j
) {
1372 for (int j
= 0; j
< 3; ++j
) {
1381 /// Regression test for #675, fixed in 1.3.3 and 1.2.21.
1382 DEFINE_TESTCASE(cursorbug1
, chert
|| glass
) {
1383 Xapian::WritableDatabase wdb
= get_writable_database();
1384 Xapian::Database db
= get_writable_database_as_database();
1385 Xapian::Enquire
enq(db
);
1386 enq
.set_query(Xapian::Query::MatchAll
);
1388 // The original problem triggers for chert and glass on repeat==7.
1389 for (int repeat
= 0; repeat
< 10; ++repeat
) {
1391 tout
<< "iteration #" << repeat
<< endl
;
1393 const int ITEMS
= 10;
1394 int free_id
= db
.get_doccount();
1395 int offset
= max(free_id
, ITEMS
* 2) - (ITEMS
* 2);
1396 int limit
= offset
+ (ITEMS
* 2);
1398 mset
= enq
.get_mset(offset
, limit
);
1399 for (Xapian::MSetIterator m1
= mset
.begin(); m1
!= mset
.end(); ++m1
) {
1400 (void)m1
.get_document().get_value(0);
1403 for (int i
= free_id
; i
<= free_id
+ ITEMS
; ++i
) {
1404 Xapian::Document doc
;
1405 const string
& id
= str(i
);
1406 string qterm
= "Q" + id
;
1407 doc
.add_value(0, id
);
1408 doc
.add_boolean_term(qterm
);
1409 wdb
.replace_document(qterm
, doc
);
1414 mset
= enq
.get_mset(offset
, limit
);
1415 for (Xapian::MSetIterator m2
= mset
.begin(); m2
!= mset
.end(); ++m2
) {
1416 (void)m2
.get_document().get_value(0);
1423 // Regression test for #674, fixed in 1.2.21 and 1.3.3.
1424 DEFINE_TESTCASE(sortvalue2
, backend
) {
1425 Xapian::Database db
= get_database("apitest_simpledata");
1426 db
.add_database(get_database("apitest_simpledata2"));
1427 Xapian::Enquire
enq(db
);
1428 enq
.set_query(Xapian::Query::MatchAll
);
1429 enq
.set_sort_by_value(0, false);
1430 Xapian::MSet mset
= enq
.get_mset(0, 50);
1432 // Check all results are in key order - the bug was that they were sorted
1433 // by docid instead with multiple remote databases.
1435 for (Xapian::MSetIterator i
= mset
.begin(); i
!= mset
.end(); ++i
) {
1436 string key
= db
.get_document(*i
).get_value(0);
1437 TEST(old_key
<= key
);
1443 /// Check behaviour of Enquire::get_query().
1444 DEFINE_TESTCASE(enquiregetquery1
, backend
) {
1445 Xapian::Database db
= get_database("apitest_simpledata");
1446 Xapian::Enquire
enq(db
);
1447 TEST_EQUAL(enq
.get_query().get_description(), "Query()");
1451 DEFINE_TESTCASE(embedded1
, singlefile
) {
1452 // In reality you should align the embedded database to a multiple of
1453 // database block size, but any offset is meant to work.
1454 off_t offset
= 1234;
1456 Xapian::Database db
= get_database("apitest_simpledata");
1457 const string
& db_path
= get_database_path("apitest_simpledata");
1458 const string
& tmp_path
= db_path
+ "-embedded";
1459 ofstream
out(tmp_path
, fstream::trunc
|fstream::binary
);
1461 out
<< ifstream(db_path
, fstream::binary
).rdbuf();
1465 int fd
= open(tmp_path
.c_str(), O_RDONLY
|O_BINARY
);
1466 lseek(fd
, offset
, SEEK_SET
);
1467 Xapian::Database
db_embedded(fd
);
1468 TEST_EQUAL(db
.get_doccount(), db_embedded
.get_doccount());
1472 int fd
= open(tmp_path
.c_str(), O_RDONLY
|O_BINARY
);
1473 lseek(fd
, offset
, SEEK_SET
);
1474 size_t check_errors
=
1475 Xapian::Database::check(fd
, Xapian::DBCHECK_SHOW_STATS
, &tout
);
1476 TEST_EQUAL(check_errors
, 0);
1482 /// Regression test for bug fixed in 1.3.7.
1483 DEFINE_TESTCASE(exactxor1
, backend
) {
1484 Xapian::Database db
= get_database("apitest_simpledata");
1485 Xapian::Enquire
enq(db
);
1487 static const char * const words
[4] = {
1488 "blank", "test", "paragraph", "banana"
1490 Xapian::Query
q(Xapian::Query::OP_XOR
, words
, words
+ 4);
1492 enq
.set_weighting_scheme(Xapian::BoolWeight());
1493 Xapian::MSet mset
= enq
.get_mset(0, 0);
1494 // A reversed conditional gave us 5 in this case.
1495 TEST_EQUAL(mset
.get_matches_upper_bound(), 6);
1496 // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1497 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
1499 static const char * const words2
[4] = {
1500 "queri", "test", "paragraph", "word"
1502 Xapian::Query
q2(Xapian::Query::OP_XOR
, words2
, words2
+ 4);
1504 enq
.set_weighting_scheme(Xapian::BoolWeight());
1505 mset
= enq
.get_mset(0, 0);
1506 // A reversed conditional gave us 6 in this case.
1507 TEST_EQUAL(mset
.get_matches_upper_bound(), 5);
1508 // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1509 TEST_EQUAL(mset
.get_matches_lower_bound(), 1);
1514 /// Feature test for Database::get_revision().
1515 DEFINE_TESTCASE(getrevision1
, chert
|| glass
) {
1516 Xapian::WritableDatabase db
= get_writable_database();
1517 TEST_EQUAL(db
.get_revision(), 0);
1519 TEST_EQUAL(db
.get_revision(), 0);
1520 Xapian::Document doc
;
1521 doc
.add_term("hello");
1522 db
.add_document(doc
);
1523 TEST_EQUAL(db
.get_revision(), 0);
1525 TEST_EQUAL(db
.get_revision(), 1);
1527 TEST_EQUAL(db
.get_revision(), 1);
1528 db
.add_document(doc
);
1530 TEST_EQUAL(db
.get_revision(), 2);
1534 /// Feature test for DOC_ASSUME_VALID.
1535 DEFINE_TESTCASE(getdocumentlazy1
, backend
) {
1536 Xapian::Database db
= get_database("apitest_simpledata");
1537 Xapian::Document doc_lazy
= db
.get_document(2, Xapian::DOC_ASSUME_VALID
);
1538 Xapian::Document doc
= db
.get_document(2);
1539 TEST_EQUAL(doc
.get_data(), doc_lazy
.get_data());
1540 TEST_EQUAL(doc
.get_value(0), doc_lazy
.get_value(0));
1544 /// Feature test for DOC_ASSUME_VALID for a docid that doesn't actually exist.
1545 DEFINE_TESTCASE(getdocumentlazy2
, backend
) {
1546 Xapian::Database db
= get_database("apitest_simpledata");
1547 Xapian::Document doc
;
1549 doc
= db
.get_document(db
.get_lastdocid() + 1, Xapian::DOC_ASSUME_VALID
);
1550 } catch (const Xapian::DocNotFoundError
&) {
1551 // DOC_ASSUME_VALID is really just a hint, so ignoring is OK (the
1552 // remote backend currently does).
1554 TEST(doc
.get_data().empty());
1555 TEST_EXCEPTION(Xapian::DocNotFoundError
,
1556 doc
= db
.get_document(db
.get_lastdocid() + 1);
1562 gen_uniqterms_gt_doclen_db(Xapian::WritableDatabase
& db
, const string
&)
1564 Xapian::Document doc
;
1565 doc
.add_term("foo");
1566 doc
.add_boolean_term("bar");
1567 db
.add_document(doc
);
1568 Xapian::Document doc2
;
1569 doc2
.add_posting("foo", 0, 2);
1570 doc2
.add_term("foo2");
1571 doc2
.add_boolean_term("baz");
1572 doc2
.add_boolean_term("baz2");
1573 db
.add_document(doc2
);
1576 DEFINE_TESTCASE(getuniqueterms1
, generated
) {
1577 Xapian::Database db
=
1578 get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db
);
1580 auto unique1
= db
.get_unique_terms(1);
1581 TEST_REL(unique1
, <=, db
.get_doclength(1));
1582 TEST_REL(unique1
, <, db
.get_document(1).termlist_count());
1583 // Ideally it'd be equal to 1, and in this case it is, but the current
1584 // backends can't always efficiently ensure an exact answer.
1585 TEST_REL(unique1
, >=, 1);
1587 auto unique2
= db
.get_unique_terms(2);
1588 TEST_REL(unique2
, <=, db
.get_doclength(2));
1589 TEST_REL(unique2
, <, db
.get_document(2).termlist_count());
1590 // Ideally it'd be equal to 2, but the current backends can't always
1591 // efficiently ensure an exact answer and here it is actually 3.
1592 TEST_REL(unique2
, >=, 2);
1597 /** Regression test for bug fixed in 1.4.6.
1599 * OP_NEAR would think a term without positional information occurred at
1600 * position 1 if it had the lowest term frequency amongst the OP_NEAR's
1603 DEFINE_TESTCASE(nopositionbug1
, generated
) {
1604 Xapian::Database db
=
1605 get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db
);
1607 // Test both orders.
1608 static const char* const terms1
[] = { "foo", "baz" };
1609 static const char* const terms2
[] = { "baz", "foo" };
1611 Xapian::Enquire
enq(db
);
1612 enq
.set_query(Xapian::Query(Xapian::Query::OP_NEAR
,
1613 begin(terms1
), end(terms1
), 10));
1614 TEST_EQUAL(enq
.get_mset(0, 5).size(), 0);
1616 enq
.set_query(Xapian::Query(Xapian::Query::OP_NEAR
,
1617 begin(terms2
), end(terms2
), 10));
1618 TEST_EQUAL(enq
.get_mset(0, 5).size(), 0);
1620 enq
.set_query(Xapian::Query(Xapian::Query::OP_PHRASE
,
1621 begin(terms1
), end(terms1
), 10));
1622 TEST_EQUAL(enq
.get_mset(0, 5).size(), 0);
1624 enq
.set_query(Xapian::Query(Xapian::Query::OP_PHRASE
,
1625 begin(terms2
), end(terms2
), 10));
1626 TEST_EQUAL(enq
.get_mset(0, 5).size(), 0);
1628 // Exercise exact phrase case too.
1629 enq
.set_query(Xapian::Query(Xapian::Query::OP_PHRASE
,
1630 begin(terms1
), end(terms1
), 2));
1631 TEST_EQUAL(enq
.get_mset(0, 5).size(), 0);
1633 enq
.set_query(Xapian::Query(Xapian::Query::OP_PHRASE
,
1634 begin(terms2
), end(terms2
), 2));
1635 TEST_EQUAL(enq
.get_mset(0, 5).size(), 0);
1640 /// Check estimate is rounded to suitable number of S.F. - new in 1.4.3.
1641 DEFINE_TESTCASE(estimaterounding1
, backend
) {
1642 Xapian::Database db
= get_database("etext");
1643 Xapian::Enquire
enquire(db
);
1644 enquire
.set_query(Xapian::Query("the") | Xapian::Query("road"));
1645 Xapian::MSet mset
= enquire
.get_mset(0, 10);
1646 // MSet::get_description() includes bounds and raw estimate.
1647 tout
<< mset
.get_description() << endl
;
1648 // Bounds are 411-439, raw estimate is 419.
1649 TEST_EQUAL(mset
.get_matches_estimated() % 10, 0);
1650 enquire
.set_query(Xapian::Query("king") | Xapian::Query("prussia"));
1651 mset
= enquire
.get_mset(0, 10);
1652 tout
<< mset
.get_description() << endl
;
1653 // Bounds are 111-138, raw estimate is 133.
1654 TEST_EQUAL(mset
.get_matches_estimated() % 10, 0);
1658 /** Regression test for bug with get_mset(0, 0, N) (N > 0).
1660 * Fixed in 1.5.0 and 1.4.6.
1662 DEFINE_TESTCASE(checkatleast4
, backend
) {
1663 Xapian::Database db
= get_database("apitest_simpledata");
1664 Xapian::Enquire
enq(db
);
1665 enq
.set_query(Xapian::Query("paragraph"));
1666 // This used to cause access to an element in an empty vector.
1667 Xapian::MSet mset
= enq
.get_mset(0, 0, 4);
1668 TEST_EQUAL(mset
.size(), 0);
1672 /// Regression test for glass bug fixed in 1.4.6 and 1.5.0.
1673 DEFINE_TESTCASE(nodocs1
, transactions
&& !remote
) {
1675 Xapian::WritableDatabase db
= get_named_writable_database("nodocs1");
1676 db
.set_metadata("foo", "bar");
1678 Xapian::Document doc
;
1679 doc
.add_term("baz");
1680 db
.add_document(doc
);
1684 size_t check_errors
=
1685 Xapian::Database::check(get_named_writable_database_path("nodocs1"),
1686 Xapian::DBCHECK_SHOW_STATS
, &tout
);
1687 TEST_EQUAL(check_errors
, 0);
1692 /// Regression test for split position handling - broken in 1.4.8.
1693 DEFINE_TESTCASE(splitpostings1
, writable
) {
1694 Xapian::WritableDatabase db
= get_writable_database();
1695 Xapian::Document doc
;
1696 // Add postings to create a split internally.
1697 for (Xapian::termpos pos
= 0; pos
<= 100; pos
+= 10) {
1698 doc
.add_posting("foo", pos
);
1700 for (Xapian::termpos pos
= 5; pos
<= 100; pos
+= 20) {
1701 doc
.add_posting("foo", pos
);
1703 db
.add_document(doc
);
1706 Xapian::termpos expect
= 0;
1707 Xapian::termpos pos
= 0;
1708 for (auto p
= db
.positionlist_begin(1, "foo");
1709 p
!= db
.positionlist_end(1, "foo"); ++p
) {
1710 TEST_REL(expect
, <=, 100);
1712 TEST_EQUAL(pos
, expect
);
1714 if (expect
% 20 == 15) expect
+= 5;
1716 TEST_EQUAL(pos
, 100);