1 /** @file api_backend.cc
2 * @brief Backend-related tests.
4 /* Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016 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"
53 /// Regression test - lockfile should honour umask, was only user-readable.
54 DEFINE_TESTCASE(lockfileumask1
, chert
|| glass
) {
55 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
56 mode_t old_umask
= umask(022);
58 Xapian::WritableDatabase db
= get_named_writable_database("lockfileumask1");
60 string path
= get_named_writable_database_path("lockfileumask1");
64 TEST(stat(path
.c_str(), &statbuf
) == 0);
65 TEST_EQUAL(statbuf
.st_mode
& 0777, 0644);
77 /// Check that the backend handles total document length > 0xffffffff.
78 DEFINE_TESTCASE(totaldoclen1
, writable
) {
79 Xapian::WritableDatabase db
= get_writable_database();
81 doc
.add_posting("foo", 1, 2000000000);
83 Xapian::Document doc2
;
84 doc2
.add_posting("bar", 1, 2000000000);
85 db
.add_document(doc2
);
86 TEST_EQUAL(db
.get_avlength(), 2000000000);
88 TEST_EQUAL(db
.get_avlength(), 2000000000);
89 for (int i
= 0; i
!= 20; ++i
) {
90 Xapian::Document doc3
;
91 doc3
.add_posting("count" + str(i
), 1, 2000000000);
92 db
.add_document(doc3
);
94 TEST_EQUAL(db
.get_avlength(), 2000000000);
96 TEST_EQUAL(db
.get_avlength(), 2000000000);
97 if (get_dbtype() != "inmemory") {
98 // InMemory doesn't support get_writable_database_as_database().
99 Xapian::Database dbr
= get_writable_database_as_database();
100 TEST_EQUAL(dbr
.get_avlength(), 2000000000);
105 // Check that exceeding 32bit in combined database doesn't cause a problem
106 // when using 64bit docids.
107 DEFINE_TESTCASE(exceed32bitcombineddb1
, writable
) {
108 // Test case is for 64-bit Xapian::docid.
109 // FIXME: Though we should check that the overflow is handled gracefully
111 if (sizeof(Xapian::docid
) == 4) return true;
113 // The InMemory backend uses a vector for the documents, so trying to add
114 // a document with the maximum docid is likely to fail because we can't
115 // allocate enough memory!
116 SKIP_TEST_FOR_BACKEND("inmemory");
118 Xapian::WritableDatabase db1
= get_writable_database();
119 Xapian::WritableDatabase db2
= get_writable_database();
120 Xapian::Document doc
;
121 doc
.set_data("prose");
122 doc
.add_term("word");
124 Xapian::docid max_id
= 0xffffffff;
126 db1
.replace_document(max_id
, doc
);
127 db2
.replace_document(max_id
, doc
);
130 db
.add_database(db1
);
131 db
.add_database(db2
);
133 Xapian::Enquire
enquire(db
);
134 enquire
.set_query(Xapian::Query::MatchAll
);
135 Xapian::MSet mymset
= enquire
.get_mset(0, 10);
137 TEST_EQUAL(2, mymset
.size());
139 for (Xapian::MSetIterator i
= mymset
.begin(); i
!= mymset
.end(); ++i
) {
140 TEST_EQUAL("prose", i
.get_document().get_data());
146 DEFINE_TESTCASE(dbstats1
, backend
) {
147 Xapian::Database db
= get_database("etext");
149 // Use precalculated values to avoid expending CPU cycles to calculate
150 // these every time without improving test coverage.
151 const Xapian::termcount min_len
= 2;
152 const Xapian::termcount max_len
= 532;
153 const Xapian::termcount max_wdf
= 22;
155 if (get_dbtype() != "inmemory") {
156 // Should be exact as no deletions have happened.
157 TEST_EQUAL(db
.get_doclength_upper_bound(), max_len
);
158 TEST_EQUAL(db
.get_doclength_lower_bound(), min_len
);
160 // For inmemory, we usually give rather loose bounds.
161 TEST_REL(db
.get_doclength_upper_bound(),>=,max_len
);
162 TEST_REL(db
.get_doclength_lower_bound(),<=,min_len
);
165 if (get_dbtype() != "inmemory" && !startswith(get_dbtype(), "remote")) {
166 TEST_EQUAL(db
.get_wdf_upper_bound("the"), max_wdf
);
168 // For inmemory and remote backends, we usually give rather loose
169 // bounds (remote matches use tighter bounds, but querying the
170 // wdf bound gives a looser one).
171 TEST_REL(db
.get_wdf_upper_bound("the"),>=,max_wdf
);
174 // This failed with an assertion during development between 1.3.1 and
176 TEST_EQUAL(db
.get_wdf_upper_bound(""), 0);
181 // Check stats with a single document. In a multi-database situation, this
182 // gave 0 for get-_doclength_lower_bound() in 1.3.2.
183 DEFINE_TESTCASE(dbstats2
, backend
) {
184 Xapian::Database db
= get_database("apitest_onedoc");
186 // Use precalculated values to avoid expending CPU cycles to calculate
187 // these every time without improving test coverage.
188 const Xapian::termcount min_len
= 15;
189 const Xapian::termcount max_len
= 15;
190 const Xapian::termcount max_wdf
= 7;
192 if (get_dbtype() != "inmemory") {
193 // Should be exact as no deletions have happened.
194 TEST_EQUAL(db
.get_doclength_upper_bound(), max_len
);
195 TEST_EQUAL(db
.get_doclength_lower_bound(), min_len
);
197 // For inmemory, we usually give rather loose bounds.
198 TEST_REL(db
.get_doclength_upper_bound(),>=,max_len
);
199 TEST_REL(db
.get_doclength_lower_bound(),<=,min_len
);
202 if (get_dbtype() != "inmemory" && !startswith(get_dbtype(), "remote")) {
203 TEST_EQUAL(db
.get_wdf_upper_bound("word"), max_wdf
);
205 // For inmemory and remote backends, we usually give rather loose
206 // bounds (remote matches use tighter bounds, but querying the
207 // wdf bound gives a looser one).
208 TEST_REL(db
.get_wdf_upper_bound("word"),>=,max_wdf
);
211 TEST_EQUAL(db
.get_wdf_upper_bound(""), 0);
216 /// Check handling of alldocs on an empty database.
217 DEFINE_TESTCASE(alldocspl3
, backend
) {
218 Xapian::Database db
= get_database(string());
220 TEST_EQUAL(db
.get_termfreq(string()), 0);
221 TEST_EQUAL(db
.get_collection_freq(string()), 0);
222 TEST(db
.postlist_begin(string()) == db
.postlist_end(string()));
227 /// Regression test for bug#392 in ModifiedPostList iteration, fixed in 1.0.15.
228 DEFINE_TESTCASE(modifiedpostlist1
, writable
) {
229 Xapian::WritableDatabase db
= get_writable_database();
230 Xapian::Document a
, b
;
231 Xapian::Enquire
enq(db
);
234 enq
.set_query(Xapian::Query("T"));
236 db
.replace_document(2, a
);
238 db
.replace_document(1, a
);
239 db
.replace_document(1, b
);
241 mset_expect_order(enq
.get_mset(0, 2), 2);
246 /// Regression test for chert bug fixed in 1.1.3 (ticket#397).
247 DEFINE_TESTCASE(doclenaftercommit1
, writable
) {
248 Xapian::WritableDatabase db
= get_writable_database();
249 TEST_EXCEPTION(Xapian::DocNotFoundError
, db
.get_doclength(1));
250 TEST_EXCEPTION(Xapian::DocNotFoundError
, db
.get_unique_terms(1));
251 db
.replace_document(1, Xapian::Document());
253 TEST_EQUAL(db
.get_doclength(1), 0);;
254 TEST_EQUAL(db
.get_unique_terms(1), 0);;
258 DEFINE_TESTCASE(valuesaftercommit1
, writable
) {
259 Xapian::WritableDatabase db
= get_writable_database();
260 Xapian::Document doc
;
261 doc
.add_value(0, "value");
262 db
.replace_document(2, doc
);
264 db
.replace_document(1, doc
);
265 db
.replace_document(3, doc
);
266 TEST_EQUAL(db
.get_document(3).get_value(0), "value");
268 TEST_EQUAL(db
.get_document(3).get_value(0), "value");
272 DEFINE_TESTCASE(lockfilefd0or1
, chert
|| glass
) {
273 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
274 int old_stdin
= dup(0);
275 int old_stdout
= dup(1);
277 // With fd 0 available.
280 Xapian::WritableDatabase db
= get_writable_database();
281 TEST_EXCEPTION(Xapian::DatabaseLockError
,
282 (void)get_writable_database_again());
284 // With fd 0 and fd 1 available.
287 Xapian::WritableDatabase db
= get_writable_database();
288 TEST_EXCEPTION(Xapian::DatabaseLockError
,
289 (void)get_writable_database_again());
291 // With fd 1 available.
294 Xapian::WritableDatabase db
= get_writable_database();
295 TEST_EXCEPTION(Xapian::DatabaseLockError
,
296 (void)get_writable_database_again());
314 /// Regression test for bug fixed in 1.2.13 and 1.3.1.
315 DEFINE_TESTCASE(lockfilealreadyopen1
, chert
|| glass
) {
316 string path
= get_named_writable_database_path("lockfilealreadyopen1");
317 int fd
= ::open((path
+ "/flintlock").c_str(), O_RDONLY
);
319 Xapian::WritableDatabase
db(path
, Xapian::DB_CREATE_OR_OPEN
);
320 TEST_EXCEPTION(Xapian::DatabaseLockError
,
321 Xapian::WritableDatabase
db2(path
, Xapian::DB_CREATE_OR_OPEN
)
332 struct MyMatchDecider
: public Xapian::MatchDecider
{
335 MyMatchDecider() : called(false) { }
337 bool operator()(const Xapian::Document
&) const {
343 /// Test Xapian::MatchDecider with remote backend fails.
344 DEFINE_TESTCASE(matchdecider4
, remote
) {
345 Xapian::Database
db(get_database("apitest_simpledata"));
346 Xapian::Enquire
enquire(db
);
347 enquire
.set_query(Xapian::Query("paragraph"));
349 MyMatchDecider mdecider
;
352 TEST_EXCEPTION(Xapian::UnimplementedError
,
353 mset
= enquire
.get_mset(0, 10, NULL
, &mdecider
));
354 TEST(!mdecider
.called
);
359 /** Check that replacing an unmodified document doesn't increase the automatic
360 * flush counter. Regression test for bug fixed in 1.1.4/1.0.18.
362 DEFINE_TESTCASE(replacedoc7
, writable
&& !inmemory
&& !remote
) {
363 // The inmemory backend doesn't batch changes, so there's nothing to
366 // The remote backend doesn't implement the lazy replacement of documents
367 // optimisation currently.
368 Xapian::WritableDatabase
db(get_writable_database());
369 Xapian::Document doc
;
370 doc
.set_data("fish");
371 doc
.add_term("Hlocalhost");
372 doc
.add_posting("hello", 1);
373 doc
.add_posting("world", 2);
374 doc
.add_value(1, "myvalue");
375 db
.add_document(doc
);
378 // We add a second document, and then replace the first document with
379 // itself 10000 times. If the document count for the database reopened
380 // read-only is 2, then we triggered an automatic commit.
382 doc
.add_term("XREV2");
383 db
.add_document(doc
);
385 for (int i
= 0; i
< 10000; ++i
) {
386 doc
= db
.get_document(1);
387 db
.replace_document(1, doc
);
390 Xapian::Database
rodb(get_writable_database_as_database());
391 TEST_EQUAL(rodb
.get_doccount(), 1);
396 TEST_EQUAL(rodb
.get_doccount(), 2);
400 /** Check that replacing a document deleted since the last flush works.
401 * Prior to 1.1.4/1.0.18, this failed to update the collection frequency and
402 * wdf, and caused an assertion failure when assertions were enabled.
404 DEFINE_TESTCASE(replacedoc8
, writable
) {
405 Xapian::WritableDatabase
db(get_writable_database());
407 Xapian::Document doc
;
408 doc
.set_data("fish");
409 doc
.add_term("takeaway");
410 db
.add_document(doc
);
412 db
.delete_document(1);
414 Xapian::Document doc
;
415 doc
.set_data("chips");
416 doc
.add_term("takeaway", 2);
417 db
.replace_document(1, doc
);
420 TEST_EQUAL(db
.get_collection_freq("takeaway"), 2);
421 Xapian::PostingIterator p
= db
.postlist_begin("takeaway");
422 TEST(p
!= db
.postlist_end("takeaway"));
423 TEST_EQUAL(p
.get_wdf(), 2);
427 /// Test coverage for DatabaseModifiedError.
428 DEFINE_TESTCASE(databasemodified1
, writable
&& !inmemory
&& !remote
) {
429 // The inmemory backend doesn't support revisions.
431 // The remote backend doesn't work as expected here, I think due to
432 // test harness issues.
433 Xapian::WritableDatabase
db(get_writable_database());
434 Xapian::Document doc
;
435 doc
.set_data("cargo");
440 for (int i
= 0; i
< N
; ++i
) {
441 db
.add_document(doc
);
445 Xapian::Database
rodb(get_writable_database_as_database());
446 db
.add_document(doc
);
449 db
.add_document(doc
);
452 db
.add_document(doc
);
454 TEST_EQUAL(*rodb
.termlist_begin(N
- 1), "abc");
456 } catch (const Xapian::DatabaseModifiedError
&) {
460 Xapian::Enquire
enq(rodb
);
461 enq
.set_query(Xapian::Query("abc"));
462 Xapian::MSet mset
= enq
.get_mset(0, 10);
464 } catch (const Xapian::DatabaseModifiedError
&) {
470 /// Regression test for bug#462 fixed in 1.0.19 and 1.1.5.
471 DEFINE_TESTCASE(qpmemoryleak1
, writable
&& !inmemory
) {
472 // Inmemory never throws DatabaseModifiedError.
473 Xapian::WritableDatabase
wdb(get_writable_database());
474 Xapian::Document doc
;
477 for (int i
= 100; i
< 120; ++i
) {
478 doc
.add_term(str(i
));
481 for (int j
= 0; j
< 50; ++j
) {
482 wdb
.add_document(doc
);
486 Xapian::Database
database(get_writable_database_as_database());
487 Xapian::QueryParser queryparser
;
488 queryparser
.set_database(database
);
489 TEST_EXCEPTION(Xapian::DatabaseModifiedError
,
490 for (int k
= 0; k
< 1000; ++k
) {
491 wdb
.add_document(doc
);
493 (void)queryparser
.parse_query("1", queryparser
.FLAG_PARTIAL
);
495 SKIP_TEST("didn't manage to trigger DatabaseModifiedError");
502 make_msize1_db(Xapian::WritableDatabase
&db
, const string
&)
504 const char * value0
=
505 "ABBCDEFGHIJKLMMNOPQQRSTTUUVVWXYZZaabcdefghhijjkllmnopqrsttuvwxyz";
506 const char * value1
=
507 "EMLEMMMMMMMNMMLMELEDNLEDMLMLDMLMLMLMEDGFHPOPBAHJIQJNGRKCGF";
509 Xapian::Document doc
;
510 doc
.add_value(0, string(1, *value0
++));
512 doc
.add_value(1, string(1, *value1
++));
515 db
.add_document(doc
);
519 /// Regression test for ticket#464, fixed in 1.1.6 and 1.0.20.
520 DEFINE_TESTCASE(msize1
, generated
) {
521 Xapian::Database db
= get_database("msize1", make_msize1_db
);
522 Xapian::Enquire
enq(db
);
523 enq
.set_sort_by_value(1, false);
524 enq
.set_collapse_key(0);
525 enq
.set_query(Xapian::Query("K1"));
527 Xapian::MSet mset
= enq
.get_mset(0, 10, 1000);
528 Xapian::doccount lb
= mset
.get_matches_lower_bound();
529 Xapian::doccount ub
= mset
.get_matches_upper_bound();
530 Xapian::doccount est
= mset
.get_matches_estimated();
534 Xapian::MSet mset2
= enq
.get_mset(50, 10, 1000);
535 Xapian::doccount lb2
= mset2
.get_matches_lower_bound();
536 Xapian::doccount ub2
= mset2
.get_matches_upper_bound();
537 Xapian::doccount est2
= mset2
.get_matches_estimated();
538 TEST_EQUAL(lb2
, ub2
);
539 TEST_EQUAL(lb2
, est2
);
540 TEST_EQUAL(est
, est2
);
542 Xapian::MSet mset3
= enq
.get_mset(0, 60);
543 Xapian::doccount lb3
= mset3
.get_matches_lower_bound();
544 Xapian::doccount ub3
= mset3
.get_matches_upper_bound();
545 Xapian::doccount est3
= mset3
.get_matches_estimated();
546 TEST_EQUAL(lb3
, ub3
);
547 TEST_EQUAL(lb3
, est3
);
548 TEST_EQUAL(est
, est3
);
554 make_msize2_db(Xapian::WritableDatabase
&db
, const string
&)
556 const char * value0
= "AAABCDEEFGHIIJJKLLMNNOOPPQQRSTTUVWXYZ";
557 const char * value1
= "MLEMNMLMLMEDEDEMLEMLMLMLPOAHGF";
559 Xapian::Document doc
;
560 doc
.add_value(0, string(1, *value0
++));
562 doc
.add_value(1, string(1, *value1
++));
565 db
.add_document(doc
);
569 /// Regression test for bug related to ticket#464, fixed in 1.1.6 and 1.0.20.
570 DEFINE_TESTCASE(msize2
, generated
) {
571 Xapian::Database db
= get_database("msize2", make_msize2_db
);
572 Xapian::Enquire
enq(db
);
573 enq
.set_sort_by_value(1, false);
574 enq
.set_collapse_key(0);
575 enq
.set_query(Xapian::Query("K1"));
577 Xapian::MSet mset
= enq
.get_mset(0, 10, 1000);
578 Xapian::doccount lb
= mset
.get_matches_lower_bound();
579 Xapian::doccount ub
= mset
.get_matches_upper_bound();
580 Xapian::doccount est
= mset
.get_matches_estimated();
584 Xapian::MSet mset2
= enq
.get_mset(50, 10, 1000);
585 Xapian::doccount lb2
= mset2
.get_matches_lower_bound();
586 Xapian::doccount ub2
= mset2
.get_matches_upper_bound();
587 Xapian::doccount est2
= mset2
.get_matches_estimated();
588 TEST_EQUAL(lb2
, ub2
);
589 TEST_EQUAL(lb2
, est2
);
590 TEST_EQUAL(est
, est2
);
592 Xapian::MSet mset3
= enq
.get_mset(0, 60);
593 Xapian::doccount lb3
= mset3
.get_matches_lower_bound();
594 Xapian::doccount ub3
= mset3
.get_matches_upper_bound();
595 Xapian::doccount est3
= mset3
.get_matches_estimated();
596 TEST_EQUAL(lb3
, ub3
);
597 TEST_EQUAL(lb3
, est3
);
598 TEST_EQUAL(est
, est3
);
604 make_xordecay1_db(Xapian::WritableDatabase
&db
, const string
&)
606 for (int n
= 1; n
!= 50; ++n
) {
607 Xapian::Document doc
;
608 for (int i
= 1; i
!= 50; ++i
) {
610 doc
.add_term("N" + str(i
));
612 db
.add_document(doc
);
616 /// Regression test for bug in decay of XOR, fixed in 1.2.1 and 1.0.21.
617 DEFINE_TESTCASE(xordecay1
, generated
) {
618 Xapian::Database db
= get_database("xordecay1", make_xordecay1_db
);
619 Xapian::Enquire
enq(db
);
620 enq
.set_query(Xapian::Query(Xapian::Query::OP_XOR
,
621 Xapian::Query("N10"),
622 Xapian::Query(Xapian::Query::OP_OR
,
624 Xapian::Query("N3"))));
625 Xapian::MSet mset1
= enq
.get_mset(0, 1);
626 Xapian::MSet msetall
= enq
.get_mset(0, db
.get_doccount());
628 TEST(mset_range_is_same(mset1
, 0, msetall
, 0, mset1
.size()));
633 make_ordecay_db(Xapian::WritableDatabase
&db
, const string
&)
635 const char * p
= "VJ=QC]LUNTaARLI;715RR^];A4O=P4ZG<2CS4EM^^VS[A6QENR";
636 for (int d
= 0; p
[d
]; ++d
) {
637 int l
= int(p
[d
] - '0');
638 Xapian::Document doc
;
639 for (int n
= 1; n
< l
; ++n
) {
640 doc
.add_term("N" + str(n
));
641 if (n
% (d
+ 1) == 0) {
642 doc
.add_term("M" + str(n
));
645 db
.add_document(doc
);
649 /// Regression test for bug in decay of OR to AND, fixed in 1.2.1 and 1.0.21.
650 DEFINE_TESTCASE(ordecay1
, generated
) {
651 Xapian::Database db
= get_database("ordecay", make_ordecay_db
);
652 Xapian::Enquire
enq(db
);
653 enq
.set_query(Xapian::Query(Xapian::Query::OP_OR
,
654 Xapian::Query("N20"),
655 Xapian::Query("N21")));
657 Xapian::MSet msetall
= enq
.get_mset(0, db
.get_doccount());
658 for (unsigned int i
= 1; i
< msetall
.size(); ++i
) {
659 Xapian::MSet submset
= enq
.get_mset(0, i
);
660 TEST(mset_range_is_same(submset
, 0, msetall
, 0, submset
.size()));
665 /** Regression test for bug in decay of OR to AND_MAYBE, fixed in 1.2.1 and
668 DEFINE_TESTCASE(ordecay2
, generated
) {
669 Xapian::Database db
= get_database("ordecay", make_ordecay_db
);
670 Xapian::Enquire
enq(db
);
671 std::vector
<Xapian::Query
> q
;
672 q
.push_back(Xapian::Query("M20"));
673 q
.push_back(Xapian::Query("N21"));
674 q
.push_back(Xapian::Query("N22"));
675 enq
.set_query(Xapian::Query(Xapian::Query::OP_OR
,
676 Xapian::Query("N25"),
677 Xapian::Query(Xapian::Query::OP_AND
,
681 Xapian::MSet msetall
= enq
.get_mset(0, db
.get_doccount());
682 for (unsigned int i
= 1; i
< msetall
.size(); ++i
) {
683 Xapian::MSet submset
= enq
.get_mset(0, i
);
684 TEST(mset_range_is_same(submset
, 0, msetall
, 0, submset
.size()));
690 make_orcheck_db(Xapian::WritableDatabase
&db
, const string
&)
692 static const int t1
[6] = {2, 4, 6, 8, 10, 0};
693 static const int t2
[11] = {6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 0};
694 static const int t3
[11] = {3, 7, 8, 11, 12, 13, 14, 15, 16, 17, 0};
696 for (unsigned i
= 1; i
<= 17; ++i
) {
697 Xapian::Document doc
;
698 db
.replace_document(i
, doc
);
700 for (const int * p
= t1
; *p
!= 0; ++p
) {
701 Xapian::Document
doc(db
.get_document(*p
));
703 db
.replace_document(*p
, doc
);
705 for (const int * p
= t2
; *p
!= 0; ++p
) {
706 Xapian::Document
doc(db
.get_document(*p
));
709 doc
.add_term("T2_lowfreq");
711 doc
.add_value(2, "1");
712 db
.replace_document(*p
, doc
);
714 for (const int * p
= t3
; *p
!= 0; ++p
) {
715 Xapian::Document
doc(db
.get_document(*p
));
718 doc
.add_term("T3_lowfreq");
720 doc
.add_value(3, "1");
721 db
.replace_document(*p
, doc
);
725 /** Regression test for bugs in the check() method of OrPostList. (ticket #485)
726 * Bugs introduced and fixed between 1.2.0 and 1.2.1 (never in a release).
728 DEFINE_TESTCASE(orcheck1
, generated
) {
729 Xapian::Database db
= get_database("orcheck1", make_orcheck_db
);
730 Xapian::Enquire
enq(db
);
731 Xapian::Query
q1("T1");
732 Xapian::Query
q2("T2");
733 Xapian::Query
q2l("T2_lowfreq");
734 Xapian::Query
q3("T3");
735 Xapian::Query
q3l("T3_lowfreq");
736 Xapian::Query
v2(Xapian::Query::OP_VALUE_RANGE
, 2, "0", "2");
737 Xapian::Query
v3(Xapian::Query::OP_VALUE_RANGE
, 3, "0", "2");
739 tout
<< "Checking q2 OR q3\n";
740 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
741 Xapian::Query(Xapian::Query::OP_OR
, q2
, q3
)));
742 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
744 tout
<< "Checking q2l OR q3\n";
745 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
746 Xapian::Query(Xapian::Query::OP_OR
, q2l
, q3
)));
747 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
749 tout
<< "Checking q2 OR q3l\n";
750 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
751 Xapian::Query(Xapian::Query::OP_OR
, q2
, q3l
)));
752 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
754 tout
<< "Checking v2 OR q3\n";
755 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
756 Xapian::Query(Xapian::Query::OP_OR
, v2
, q3
)));
757 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 8, 6);
759 tout
<< "Checking q2 OR v3\n";
760 enq
.set_query(Xapian::Query(Xapian::Query::OP_AND
, q1
,
761 Xapian::Query(Xapian::Query::OP_OR
, q2
, v3
)));
762 // Order of results in this one is different, because v3 gives no weight,
763 // both documents are in q2, and document 8 has a higher length.
764 mset_expect_order(enq
.get_mset(0, db
.get_doccount()), 6, 8);
769 /** Regression test for bug fixed in 1.2.1 and 1.0.21.
771 * We failed to mark the Btree as unmodified after cancel().
773 DEFINE_TESTCASE(failedreplace1
, chert
|| glass
) {
774 Xapian::WritableDatabase
db(get_writable_database());
775 Xapian::Document doc
;
777 db
.add_document(doc
);
778 Xapian::docid did
= db
.add_document(doc
);
780 doc
.add_term(string(1000, 'm'));
782 TEST_EXCEPTION(Xapian::InvalidArgumentError
, db
.replace_document(did
, doc
));
784 TEST_EQUAL(db
.get_doccount(), 0);
785 TEST_EQUAL(db
.get_termfreq("foo"), 0);
789 DEFINE_TESTCASE(failedreplace2
, chert
|| glass
) {
790 Xapian::WritableDatabase
db(get_writable_database("apitest_simpledata"));
792 Xapian::doccount db_size
= db
.get_doccount();
793 Xapian::Document doc
;
794 doc
.set_data("wibble");
796 doc
.add_value(0, "seven");
797 db
.add_document(doc
);
798 Xapian::docid did
= db
.add_document(doc
);
800 doc
.add_term(string(1000, 'm'));
802 doc
.add_value(0, "six");
803 TEST_EXCEPTION(Xapian::InvalidArgumentError
, db
.replace_document(did
, doc
));
805 TEST_EQUAL(db
.get_doccount(), db_size
);
806 TEST_EQUAL(db
.get_termfreq("foo"), 0);
810 /// Coverage for SelectPostList::skip_to().
811 DEFINE_TESTCASE(phrase3
, positional
) {
812 Xapian::Database db
= get_database("apitest_phrase");
814 const char * phrase_words
[] = { "phrase", "near" };
815 Xapian::Query
q(Xapian::Query::OP_NEAR
, phrase_words
, phrase_words
+ 2, 12);
816 q
= Xapian::Query(Xapian::Query::OP_AND_MAYBE
, Xapian::Query("pad"), q
);
818 Xapian::Enquire
enquire(db
);
819 enquire
.set_query(q
);
820 Xapian::MSet mset
= enquire
.get_mset(0, 5);
825 /// Check that get_mset(<large number>, 10) doesn't exhaust memory needlessly.
826 // Regression test for fix in 1.2.4.
827 DEFINE_TESTCASE(msetfirst2
, backend
) {
828 Xapian::Database
db(get_database("apitest_simpledata"));
829 Xapian::Enquire
enquire(db
);
830 enquire
.set_query(Xapian::Query("paragraph"));
832 // Before the fix, this tried to allocated too much memory.
833 mset
= enquire
.get_mset(0xfffffff0, 1);
834 TEST_EQUAL(mset
.get_firstitem(), 0xfffffff0);
835 // Check that the number of documents gets clamped too.
836 mset
= enquire
.get_mset(1, 0xfffffff0);
837 TEST_EQUAL(mset
.get_firstitem(), 1);
838 // Another regression test - MatchNothing used to give an MSet with
839 // get_firstitem() returning 0.
840 enquire
.set_query(Xapian::Query::MatchNothing
);
841 mset
= enquire
.get_mset(1, 1);
842 TEST_EQUAL(mset
.get_firstitem(), 1);
846 DEFINE_TESTCASE(bm25weight2
, backend
) {
847 Xapian::Database
db(get_database("etext"));
848 Xapian::Enquire
enquire(db
);
849 enquire
.set_query(Xapian::Query("the"));
850 enquire
.set_weighting_scheme(Xapian::BM25Weight(0, 0, 0, 0, 1));
851 Xapian::MSet mset
= enquire
.get_mset(0, 100);
852 TEST_REL(mset
.size(),>=,2);
853 double weight0
= mset
[0].get_weight();
854 for (size_t i
= 1; i
!= mset
.size(); ++i
) {
855 TEST_EQUAL(weight0
, mset
[i
].get_weight());
860 DEFINE_TESTCASE(unigramlmweight2
, backend
) {
861 Xapian::Database
db(get_database("etext"));
862 Xapian::Enquire
enquire(db
);
863 enquire
.set_query(Xapian::Query("the"));
864 enquire
.set_weighting_scheme(Xapian::LMWeight());
865 Xapian::MSet mset
= enquire
.get_mset(0, 100);
866 TEST_REL(mset
.size(),>=,2);
870 DEFINE_TESTCASE(tradweight2
, backend
) {
871 Xapian::Database
db(get_database("etext"));
872 Xapian::Enquire
enquire(db
);
873 enquire
.set_query(Xapian::Query("the"));
874 enquire
.set_weighting_scheme(Xapian::TradWeight(0));
875 Xapian::MSet mset
= enquire
.get_mset(0, 100);
876 TEST_REL(mset
.size(),>=,2);
877 double weight0
= mset
[0].get_weight();
878 for (size_t i
= 1; i
!= mset
.size(); ++i
) {
879 TEST_EQUAL(weight0
, mset
[i
].get_weight());
884 // Regression test for bug fix in 1.2.9.
885 DEFINE_TESTCASE(emptydb1
, backend
) {
886 Xapian::Database
db(get_database(string()));
887 static const Xapian::Query::op ops
[] = {
888 Xapian::Query::OP_AND
,
889 Xapian::Query::OP_OR
,
890 Xapian::Query::OP_AND_NOT
,
891 Xapian::Query::OP_XOR
,
892 Xapian::Query::OP_AND_MAYBE
,
893 Xapian::Query::OP_FILTER
,
894 Xapian::Query::OP_NEAR
,
895 Xapian::Query::OP_PHRASE
,
896 Xapian::Query::OP_ELITE_SET
898 const Xapian::Query::op
* p
;
899 for (p
= ops
; p
- ops
!= sizeof(ops
) / sizeof(*ops
); ++p
) {
901 Xapian::Enquire
enquire(db
);
902 Xapian::Query
query(*p
, Xapian::Query("a"), Xapian::Query("b"));
903 enquire
.set_query(query
);
904 Xapian::MSet mset
= enquire
.get_mset(0, 10);
905 TEST_EQUAL(mset
.get_matches_estimated(), 0);
906 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
907 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
912 /// Test error opening non-existent stub databases.
913 // Regression test for bug fixed in 1.3.1 and 1.2.11.
914 DEFINE_TESTCASE(stubdb7
, !backend
) {
915 TEST_EXCEPTION(Xapian::DatabaseOpeningError
,
916 Xapian::Database("nosuchdirectory", Xapian::DB_BACKEND_STUB
));
917 TEST_EXCEPTION(Xapian::DatabaseOpeningError
,
918 Xapian::WritableDatabase("nosuchdirectory",
919 Xapian::DB_OPEN
|Xapian::DB_BACKEND_STUB
));
923 /// Test which checks the weights are as expected.
924 // This runs for multi_* too, so serves to check that we get the same weights
925 // with multiple databases as without.
926 DEFINE_TESTCASE(msetweights1
, backend
) {
927 Xapian::Database db
= get_database("apitest_simpledata");
928 Xapian::Enquire
enq(db
);
929 Xapian::Query
q(Xapian::Query::OP_OR
,
930 Xapian::Query("paragraph"),
931 Xapian::Query("word"));
933 // 5 documents match, and the 4th and 5th have the same weight, so ask for
934 // 4 as that's a good test that we get the right one in this case.
935 Xapian::MSet mset
= enq
.get_mset(0, 4);
937 static const struct { Xapian::docid did
; double wt
; } expected
[] = {
938 { 2, 1.2058248004573934864 },
939 { 4, 0.81127876655507624726 },
940 { 1, 0.17309550762546158098 },
941 { 3, 0.14609528172558261527 }
944 TEST_EQUAL(mset
.size(), sizeof(expected
) / sizeof(expected
[0]));
945 for (size_t i
= 0; i
< mset
.size(); ++i
) {
946 TEST_EQUAL(*mset
[i
], expected
[i
].did
);
947 TEST_EQUAL_DOUBLE(mset
[i
].get_weight(), expected
[i
].wt
);
950 // Now test a query which matches only even docids, so in the multi case
951 // one subdatabase doesn't match.
952 enq
.set_query(Xapian::Query("one"));
953 mset
= enq
.get_mset(0, 3);
955 static const struct { Xapian::docid did
; double wt
; } expected2
[] = {
956 { 6, 0.73354729848273669823 },
957 { 2, 0.45626501034348893038 }
960 TEST_EQUAL(mset
.size(), sizeof(expected2
) / sizeof(expected2
[0]));
961 for (size_t i
= 0; i
< mset
.size(); ++i
) {
962 TEST_EQUAL(*mset
[i
], expected2
[i
].did
);
963 TEST_EQUAL_DOUBLE(mset
[i
].get_weight(), expected2
[i
].wt
);
969 DEFINE_TESTCASE(itorskiptofromend1
, backend
) {
970 Xapian::Database db
= get_database("apitest_simpledata");
972 Xapian::TermIterator t
= db
.termlist_begin(1);
974 TEST(t
== db
.termlist_end(1));
975 // This worked in 1.2.x but segfaulted in 1.3.1.
978 Xapian::PostingIterator p
= db
.postlist_begin("one");
980 TEST(p
== db
.postlist_end("one"));
981 // This segfaulted prior to 1.3.2.
984 Xapian::PositionIterator i
= db
.positionlist_begin(6, "one");
986 TEST(i
== db
.positionlist_end(6, "one"));
987 // This segfaulted prior to 1.3.2.
990 Xapian::ValueIterator v
= db
.valuestream_begin(1);
992 TEST(v
== db
.valuestream_end(1));
993 // These segfaulted prior to 1.3.2.
1000 /// Check handling of invalid block sizes.
1001 // Regression test for bug fixed in 1.2.17 and 1.3.2 - the size gets fixed
1002 // but the uncorrected size was passed to the base file. Also, abort() was
1004 DEFINE_TESTCASE(blocksize1
, chert
|| glass
) {
1005 string db_dir
= "." + get_dbtype();
1006 mkdir(db_dir
.c_str(), 0755);
1007 db_dir
+= "/db__blocksize1";
1009 if (get_dbtype() == "chert") {
1010 flags
= Xapian::DB_CREATE
|Xapian::DB_BACKEND_CHERT
;
1012 flags
= Xapian::DB_CREATE
|Xapian::DB_BACKEND_GLASS
;
1014 static const unsigned bad_sizes
[] = {
1015 65537, 8000, 2000, 1024, 16, 7, 3, 1, 0
1017 for (size_t i
= 0; i
< sizeof(bad_sizes
) / sizeof(bad_sizes
[0]); ++i
) {
1018 size_t block_size
= bad_sizes
[i
];
1020 Xapian::WritableDatabase
db(db_dir
, flags
, block_size
);
1021 Xapian::Document doc
;
1022 doc
.add_term("XYZ");
1023 doc
.set_data("foo");
1024 db
.add_document(doc
);
1030 /// Feature test for Xapian::DB_NO_TERMLIST.
1031 DEFINE_TESTCASE(notermlist1
, glass
) {
1032 string db_dir
= "." + get_dbtype();
1033 mkdir(db_dir
.c_str(), 0755);
1034 db_dir
+= "/db__notermlist1";
1035 int flags
= Xapian::DB_CREATE
|Xapian::DB_NO_TERMLIST
;
1036 if (get_dbtype() == "chert") {
1037 flags
|= Xapian::DB_BACKEND_CHERT
;
1039 flags
|= Xapian::DB_BACKEND_GLASS
;
1042 Xapian::WritableDatabase
db(db_dir
, flags
);
1043 Xapian::Document doc
;
1044 doc
.add_term("hello");
1045 doc
.add_value(42, "answer");
1046 db
.add_document(doc
);
1048 TEST(!file_exists(db_dir
+ "/termlist.glass"));
1049 TEST_EXCEPTION(Xapian::FeatureUnavailableError
, db
.termlist_begin(1));
1053 /// Regression test for bug starting a new glass freelist block.
1054 DEFINE_TESTCASE(newfreelistblock1
, writable
) {
1055 Xapian::Document doc
;
1056 doc
.add_term("foo");
1057 for (int i
= 100; i
< 120; ++i
) {
1058 doc
.add_term(str(i
));
1061 Xapian::WritableDatabase
wdb(get_writable_database());
1062 for (int j
= 0; j
< 50; ++j
) {
1063 wdb
.add_document(doc
);
1067 for (int k
= 0; k
< 1000; ++k
) {
1068 wdb
.add_document(doc
);
1075 /** Check that the parent directory for the database doesn't need to be
1076 * writable. Regression test for early versions on the glass new btree
1077 * branch which failed to append a "/" when generating a temporary filename
1078 * from the database directory.
1080 DEFINE_TESTCASE(readonlyparentdir1
, chert
|| glass
) {
1081 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1082 string path
= get_named_writable_database_path("readonlyparentdir1");
1083 // Fix permissions if the previous test was killed.
1084 (void)chmod(path
.c_str(), 0700);
1085 mkdir(path
.c_str(), 0777);
1086 mkdir((path
+ "/sub").c_str(), 0777);
1087 Xapian::WritableDatabase db
= get_named_writable_database("readonlyparentdir1/sub");
1088 TEST(chmod(path
.c_str(), 0500) == 0);
1090 Xapian::Document doc
;
1091 doc
.add_term("hello");
1092 doc
.set_data("some text");
1093 db
.add_document(doc
);
1096 // Attempt to fix the permissions, otherwise things like "rm -rf" on
1097 // the source tree will fail.
1098 (void)chmod(path
.c_str(), 0700);
1101 TEST(chmod(path
.c_str(), 0700) == 0);
1107 make_phrasebug1_db(Xapian::WritableDatabase
&db
, const string
&)
1109 Xapian::Document doc
;
1110 doc
.add_posting("hurricane", 199881);
1111 doc
.add_posting("hurricane", 203084);
1112 doc
.add_posting("katrina", 199882);
1113 doc
.add_posting("katrina", 202473);
1114 doc
.add_posting("katrina", 203085);
1115 db
.add_document(doc
);
1118 /// Regression test for ticket#653, fixed in 1.3.2 and 1.2.19.
1119 DEFINE_TESTCASE(phrasebug1
, generated
&& positional
) {
1120 Xapian::Database db
= get_database("phrasebug1", make_phrasebug1_db
);
1121 const char * qterms
[] = { "katrina", "hurricane" };
1122 Xapian::Enquire
e(db
);
1123 Xapian::Query
q(Xapian::Query::OP_PHRASE
, qterms
, qterms
+ 2, 5);
1125 Xapian::MSet mset
= e
.get_mset(0, 100);
1126 TEST_EQUAL(mset
.size(), 0);
1127 const char * qterms2
[] = { "hurricane", "katrina" };
1128 Xapian::Query
q2(Xapian::Query::OP_PHRASE
, qterms2
, qterms2
+ 2, 5);
1130 mset
= e
.get_mset(0, 100);
1131 TEST_EQUAL(mset
.size(), 1);
1135 /// Feature test for Xapian::DB_RETRY_LOCK
1136 DEFINE_TESTCASE(retrylock1
, writable
&& !inmemory
&& !remote
) {
1137 // FIXME: Can't see an easy way to test this for remote databases - the
1138 // harness doesn't seem to provide a suitable way to reopen a remote.
1139 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
1141 if (socketpair(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, PF_UNSPEC
, fds
) < 0) {
1142 FAIL_TEST("socketpair() failed");
1144 if (fcntl(fds
[1], F_SETFL
, O_NONBLOCK
) < 0)
1145 FAIL_TEST("fcntl() failed to set O_NONBLOCK");
1146 pid_t child
= fork();
1148 FAIL_TEST("fork() failed");
1150 // Wait for signal that parent has opened the database.
1152 while (read(fds
[0], &ch
, 1) < 0) { }
1155 Xapian::WritableDatabase
db2(get_named_writable_database_path("retrylock1"),
1156 Xapian::DB_OPEN
|Xapian::DB_RETRY_LOCK
);
1157 if (write(fds
[0], "y", 1)) { }
1158 } catch (const Xapian::DatabaseLockError
&) {
1159 if (write(fds
[0], "l", 1)) { }
1160 } catch (const Xapian::Error
&e
) {
1161 const string
& m
= e
.get_description();
1162 if (write(fds
[0], m
.data(), m
.size())) { }
1164 if (write(fds
[0], "o", 1)) { }
1171 Xapian::WritableDatabase db
= get_named_writable_database("retrylock1");
1172 if (write(fds
[1], "", 1) != 1)
1173 FAIL_TEST("Failed to signal to child process");
1176 int r
= read(fds
[1], result
, sizeof(result
));
1178 if (errno
== EAGAIN
) {
1183 tout
<< "errno=" << errno
<< ": " << strerror(errno
) << endl
;
1187 } else if (r
>= 1) {
1188 if (result
[0] == 'y') {
1189 // Child process managed to also get write lock!
1201 kill(child
, SIGKILL
);
1203 while (waitpid(child
, &status
, 0) < 0) {
1204 if (errno
!= EINTR
) break;
1209 if (result
[0] == 'y') {
1216 int sr
= select(fds
[1] + 1, &f
, NULL
, &f
, &tv
);
1222 r
= read(fds
[1], result
, sizeof(result
));
1225 tout
<< "errno=" << errno
<< ": " << strerror(errno
) << endl
;
1228 } else if (r
== 0) {
1238 kill(child
, SIGKILL
);
1240 while (waitpid(child
, &status
, 0) < 0) {
1241 if (errno
!= EINTR
) break;
1244 tout
<< string(result
, r
) << endl
;
1245 TEST_EQUAL(result
[0], 'y');
1251 // Opening a WritableDatabase with low fds available - it should avoid them.
1252 DEFINE_TESTCASE(dbfilefd012
, chert
|| glass
) {
1253 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1255 for (int i
= 0; i
< 3; ++i
) {
1259 for (int j
= 0; j
< 3; ++j
) {
1261 TEST_EQUAL(lseek(j
, 0, SEEK_CUR
), -1);
1262 TEST_EQUAL(errno
, EBADF
);
1265 Xapian::WritableDatabase db
= get_writable_database();
1267 // Check we didn't use any of those low fds for tables, as that risks
1268 // data corruption if some other code in the same process tries to
1269 // write to them (see #651).
1270 for (int fd
= 0; fd
< 3; ++fd
) {
1271 // Check that the fd is still closed, or isn't open O_RDWR (the
1272 // lock file gets opened O_WRONLY), or it's a pipe (if we're using
1273 // a child process to hold a non-OFD fcntl lock).
1274 int flags
= fcntl(fd
, F_GETFL
);
1276 TEST_EQUAL(errno
, EBADF
);
1277 } else if ((flags
& O_ACCMODE
) != O_RDWR
) {
1281 TEST_NOT_EQUAL(fstat(fd
, &sb
), -1);
1283 TEST(S_ISSOCK(sb
.st_mode
));
1285 // If we can't check it is a socket, at least check it is not a
1287 TEST(!S_ISREG(sb
.st_mode
));
1292 for (int j
= 0; j
< 3; ++j
) {
1299 for (int j
= 0; j
< 3; ++j
) {
1308 /// Regression test for #675, fixed in 1.3.3 and 1.2.21.
1309 DEFINE_TESTCASE(cursorbug1
, chert
|| glass
) {
1310 Xapian::WritableDatabase wdb
= get_writable_database();
1311 Xapian::Database db
= get_writable_database_as_database();
1312 Xapian::Enquire
enq(db
);
1313 enq
.set_query(Xapian::Query::MatchAll
);
1315 // The original problem triggers for chert and glass on repeat==7.
1316 for (int repeat
= 0; repeat
< 10; ++repeat
) {
1318 tout
<< "iteration #" << repeat
<< endl
;
1320 const int ITEMS
= 10;
1321 int free_id
= db
.get_doccount();
1322 int offset
= max(free_id
, ITEMS
* 2) - (ITEMS
* 2);
1323 int limit
= offset
+ (ITEMS
* 2);
1325 mset
= enq
.get_mset(offset
, limit
);
1326 for (Xapian::MSetIterator m1
= mset
.begin(); m1
!= mset
.end(); ++m1
) {
1327 (void)m1
.get_document().get_value(0);
1330 for (int i
= free_id
; i
<= free_id
+ ITEMS
; ++i
) {
1331 Xapian::Document doc
;
1332 const string
& id
= str(i
);
1333 string qterm
= "Q" + id
;
1334 doc
.add_value(0, id
);
1335 doc
.add_boolean_term(qterm
);
1336 wdb
.replace_document(qterm
, doc
);
1341 mset
= enq
.get_mset(offset
, limit
);
1342 for (Xapian::MSetIterator m2
= mset
.begin(); m2
!= mset
.end(); ++m2
) {
1343 (void)m2
.get_document().get_value(0);
1350 // Regression test for #674, fixed in 1.2.21 and 1.3.3.
1351 DEFINE_TESTCASE(sortvalue2
, backend
) {
1352 Xapian::Database db
= get_database("apitest_simpledata");
1353 db
.add_database(get_database("apitest_simpledata2"));
1354 Xapian::Enquire
enq(db
);
1355 enq
.set_query(Xapian::Query::MatchAll
);
1356 enq
.set_sort_by_value(0, false);
1357 Xapian::MSet mset
= enq
.get_mset(0, 50);
1359 // Check all results are in key order - the bug was that they were sorted
1360 // by docid instead with multiple remote databases.
1362 for (Xapian::MSetIterator i
= mset
.begin(); i
!= mset
.end(); ++i
) {
1363 string key
= db
.get_document(*i
).get_value(0);
1364 TEST(old_key
<= key
);
1370 /// Check behaviour of Enquire::get_query().
1371 DEFINE_TESTCASE(enquiregetquery1
, backend
) {
1372 Xapian::Database db
= get_database("apitest_simpledata");
1373 Xapian::Enquire
enq(db
);
1374 TEST_EQUAL(enq
.get_query().get_description(), "Query()");
1378 DEFINE_TESTCASE(embedded1
, singlefile
) {
1379 // In reality you should align the embedded database to a multiple of
1380 // database block size, but any offset is meant to work.
1381 off_t offset
= 1234;
1383 Xapian::Database db
= get_database("apitest_simpledata");
1384 const string
& db_path
= get_database_path("apitest_simpledata");
1385 const string
& tmp_path
= db_path
+ "-embedded";
1386 ofstream
out(tmp_path
, fstream::trunc
|fstream::binary
);
1388 out
<< ifstream(db_path
, fstream::binary
).rdbuf();
1392 int fd
= open(tmp_path
.c_str(), O_RDONLY
|O_BINARY
);
1393 lseek(fd
, offset
, SEEK_SET
);
1394 Xapian::Database
db_embedded(fd
);
1395 TEST_EQUAL(db
.get_doccount(), db_embedded
.get_doccount());
1399 int fd
= open(tmp_path
.c_str(), O_RDONLY
|O_BINARY
);
1400 lseek(fd
, offset
, SEEK_SET
);
1401 size_t check_errors
=
1402 Xapian::Database::check(fd
, Xapian::DBCHECK_SHOW_STATS
, &tout
);
1403 TEST_EQUAL(check_errors
, 0);
1409 /// Regression test for bug fixed in 1.3.7.
1410 DEFINE_TESTCASE(exactxor1
, backend
) {
1411 Xapian::Database db
= get_database("apitest_simpledata");
1412 Xapian::Enquire
enq(db
);
1414 const char * words
[4] = { "blank", "test", "paragraph", "banana" };
1415 Xapian::Query
q(Xapian::Query::OP_XOR
, words
, words
+ 4);
1417 enq
.set_weighting_scheme(Xapian::BoolWeight());
1418 Xapian::MSet mset
= enq
.get_mset(0, 0);
1419 // A reversed conditional gave us 5 in this case.
1420 TEST_EQUAL(mset
.get_matches_upper_bound(), 6);
1421 // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1422 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
1424 const char * words2
[4] = { "queri", "test", "paragraph", "word" };
1425 Xapian::Query
q2(Xapian::Query::OP_XOR
, words2
, words2
+ 4);
1427 enq
.set_weighting_scheme(Xapian::BoolWeight());
1428 mset
= enq
.get_mset(0, 0);
1429 // A reversed conditional gave us 6 in this case.
1430 TEST_EQUAL(mset
.get_matches_upper_bound(), 5);
1431 // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1432 TEST_EQUAL(mset
.get_matches_lower_bound(), 1);
1437 /// Feature test for Database::get_revision().
1438 DEFINE_TESTCASE(getrevision1
, chert
|| glass
) {
1439 Xapian::WritableDatabase db
= get_writable_database();
1440 TEST_EQUAL(db
.get_revision(), 0);
1442 TEST_EQUAL(db
.get_revision(), 0);
1443 Xapian::Document doc
;
1444 doc
.add_term("hello");
1445 db
.add_document(doc
);
1446 TEST_EQUAL(db
.get_revision(), 0);
1448 TEST_EQUAL(db
.get_revision(), 1);
1450 TEST_EQUAL(db
.get_revision(), 1);
1451 db
.add_document(doc
);
1453 TEST_EQUAL(db
.get_revision(), 2);