Fix testcase unsupportedcheck1 for --disable-backend-remote
[xapian.git] / xapian-core / tests / api_none.cc
blobf3d493afacc5cfa7ca545134f156f775e766e5e9
1 /** @file
2 * @brief tests which don't need a backend
3 */
4 /* Copyright (C) 2009 Richard Boulton
5 * Copyright (C) 2009-2023 Olly Betts
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
20 * USA
23 #include <config.h>
25 #include "api_none.h"
27 #define XAPIAN_DEPRECATED(D) D
28 #include <xapian.h>
30 #include "apitest.h"
31 #include "str.h"
32 #include "testsuite.h"
33 #include "testutils.h"
35 #include <string_view>
37 using namespace std;
39 // Check the version functions give consistent results.
40 DEFINE_TESTCASE(version1, !backend) {
41 string version = str(Xapian::major_version());
42 version += '.';
43 version += str(Xapian::minor_version());
44 version += '.';
45 version += str(Xapian::revision());
46 TEST_EQUAL(Xapian::version_string(), version);
49 // Regression test: various methods on Database() used to segfault or cause
50 // division by 0. Fixed in 1.1.4 and 1.0.18. Ticket#415.
51 DEFINE_TESTCASE(nosubdatabases1, !backend) {
52 Xapian::Database db;
53 TEST(db.get_metadata("foo").empty());
54 TEST_EQUAL(db.metadata_keys_begin(), db.metadata_keys_end());
55 TEST_EXCEPTION(Xapian::InvalidOperationError, db.termlist_begin(1));
56 TEST_EQUAL(db.allterms_begin(), db.allterms_end());
57 TEST_EQUAL(db.allterms_begin("foo"), db.allterms_end("foo"));
58 TEST_EXCEPTION(Xapian::InvalidOperationError, db.positionlist_begin(1, "foo"));
59 TEST_EQUAL(db.get_lastdocid(), 0);
60 TEST_EQUAL(db.valuestream_begin(7), db.valuestream_end(7));
61 TEST_EXCEPTION(Xapian::InvalidOperationError, db.get_doclength(1));
62 TEST_EXCEPTION(Xapian::InvalidOperationError, db.get_unique_terms(1));
63 TEST_EXCEPTION(Xapian::InvalidOperationError, db.get_document(1));
65 Xapian::WritableDatabase wdb;
66 TEST_EXCEPTION(Xapian::InvalidOperationError, wdb.begin_transaction());
67 TEST_EXCEPTION(Xapian::InvalidOperationError, wdb.commit_transaction());
68 TEST_EXCEPTION(Xapian::InvalidOperationError, wdb.cancel_transaction());
71 /// Feature test for Document::add_boolean_term(), new in 1.0.18/1.1.4.
72 DEFINE_TESTCASE(document1, !backend) {
73 Xapian::Document doc;
74 doc.add_boolean_term("Hxapian.org");
75 TEST_EQUAL(doc.termlist_count(), 1);
76 Xapian::TermIterator t = doc.termlist_begin();
77 TEST(t != doc.termlist_end());
78 TEST_EQUAL(*t, "Hxapian.org");
79 TEST_EQUAL(t.get_wdf(), 0);
80 TEST(++t == doc.termlist_end());
81 doc.remove_term("Hxapian.org");
82 TEST_EQUAL(doc.termlist_count(), 0);
83 TEST(doc.termlist_begin() == doc.termlist_end());
86 /// Regression test - the docid wasn't initialised prior to 1.0.22/1.2.4.
87 DEFINE_TESTCASE(document2, !backend) {
88 Xapian::Document doc;
89 // The return value is uninitialised, so running under valgrind this
90 // will fail reliably prior to the fix.
91 TEST_EQUAL(doc.get_docid(), 0);
94 /// Feature tests for Document::clear_terms().
95 DEFINE_TESTCASE(documentclearterms1, !backend) {
97 Xapian::Document doc;
98 doc.add_boolean_term("Hlocalhost");
99 doc.add_term("hello");
100 doc.add_term("there", 2);
101 doc.add_posting("positional", 1);
102 doc.add_posting("information", 2, 3);
103 TEST_EQUAL(doc.termlist_count(), 5);
104 TEST(doc.termlist_begin() != doc.termlist_end());
105 doc.clear_terms();
106 TEST_EQUAL(doc.termlist_count(), 0);
107 TEST(doc.termlist_begin() == doc.termlist_end());
108 // Test clear_terms() when there are no terms.
109 doc.clear_terms();
110 TEST_EQUAL(doc.termlist_count(), 0);
111 TEST(doc.termlist_begin() == doc.termlist_end());
115 // Test clear_terms() when there have never been any terms.
116 Xapian::Document doc;
117 doc.clear_terms();
118 TEST_EQUAL(doc.termlist_count(), 0);
119 TEST(doc.termlist_begin() == doc.termlist_end());
123 /// Feature tests for Document::clear_values().
124 DEFINE_TESTCASE(documentclearvalues1, !backend) {
126 Xapian::Document doc;
127 doc.add_value(37, "hello");
128 doc.add_value(42, "world");
129 TEST_EQUAL(doc.values_count(), 2);
130 TEST(doc.values_begin() != doc.values_end());
131 doc.clear_values();
132 TEST_EQUAL(doc.values_count(), 0);
133 TEST(doc.values_begin() == doc.values_end());
134 // Test clear_values() when there are no values.
135 doc.clear_values();
136 TEST_EQUAL(doc.values_count(), 0);
137 TEST(doc.values_begin() == doc.values_end());
141 // Test clear_values() when there have never been any values.
142 Xapian::Document doc;
143 doc.clear_values();
144 TEST_EQUAL(doc.values_count(), 0);
145 TEST(doc.termlist_begin() == doc.termlist_end());
149 /// Feature tests for errors for empty terms.
150 DEFINE_TESTCASE(documentemptyterm1, !backend) {
151 Xapian::Document doc;
152 TEST_EXCEPTION(Xapian::InvalidArgumentError,
153 doc.add_boolean_term(string()));
154 TEST_EXCEPTION(Xapian::InvalidArgumentError,
155 doc.add_term(string()));
156 TEST_EXCEPTION(Xapian::InvalidArgumentError,
157 doc.add_posting(string(), 1));
158 TEST_EXCEPTION(Xapian::InvalidArgumentError,
159 doc.add_posting(string(), 2, 3));
160 TEST_EXCEPTION(Xapian::InvalidArgumentError,
161 doc.remove_term(string()));
162 TEST_EXCEPTION(Xapian::InvalidArgumentError,
163 doc.remove_posting(string(), 1));
164 TEST_EXCEPTION(Xapian::InvalidArgumentError,
165 doc.remove_posting(string(), 2, 3));
166 TEST_EXCEPTION(Xapian::InvalidArgumentError,
167 doc.remove_postings(string(), 2, 3));
168 TEST_EXCEPTION(Xapian::InvalidArgumentError,
169 doc.remove_postings(string(), 2, 3, 4));
172 DEFINE_TESTCASE(emptyquery4, !backend) {
173 // Test we get an empty query from applying any of the following ops to
174 // an empty list of subqueries.
175 Xapian::Query q;
176 TEST(Xapian::Query(q.OP_AND, &q, &q).empty());
177 TEST(Xapian::Query(q.OP_OR, &q, &q).empty());
178 TEST(Xapian::Query(q.OP_AND_NOT, &q, &q).empty());
179 TEST(Xapian::Query(q.OP_XOR, &q, &q).empty());
180 TEST(Xapian::Query(q.OP_AND_MAYBE, &q, &q).empty());
181 TEST(Xapian::Query(q.OP_FILTER, &q, &q).empty());
182 TEST(Xapian::Query(q.OP_NEAR, &q, &q).empty());
183 TEST(Xapian::Query(q.OP_PHRASE, &q, &q).empty());
184 TEST(Xapian::Query(q.OP_ELITE_SET, &q, &q).empty());
185 TEST(Xapian::Query(q.OP_SYNONYM, &q, &q).empty());
186 TEST(Xapian::Query(q.OP_MAX, &q, &q).empty());
189 DEFINE_TESTCASE(singlesubquery1, !backend) {
190 // Test that we get just the subquery if we apply any of the following
191 // ops to just that subquery.
192 #define singlesubquery1_(OP) \
193 TEST_STRINGS_EQUAL(Xapian::Query(q->OP, q, q + 1).get_description(),\
194 "Query(test)")
195 Xapian::Query q[1] = { Xapian::Query("test") };
196 singlesubquery1_(OP_AND);
197 singlesubquery1_(OP_OR);
198 singlesubquery1_(OP_AND_NOT);
199 singlesubquery1_(OP_XOR);
200 singlesubquery1_(OP_AND_MAYBE);
201 singlesubquery1_(OP_FILTER);
202 singlesubquery1_(OP_NEAR);
203 singlesubquery1_(OP_PHRASE);
204 singlesubquery1_(OP_ELITE_SET);
205 singlesubquery1_(OP_SYNONYM);
206 singlesubquery1_(OP_MAX);
209 DEFINE_TESTCASE(singlesubquery2, !backend) {
210 // Like the previous test, but using MatchNothing as the subquery.
211 #define singlesubquery2_(OP) \
212 TEST_STRINGS_EQUAL(Xapian::Query(q->OP, q, q + 1).get_description(),\
213 "Query()")
214 Xapian::Query q[1] = { Xapian::Query::MatchNothing };
215 singlesubquery2_(OP_AND);
216 singlesubquery2_(OP_OR);
217 singlesubquery2_(OP_AND_NOT);
218 singlesubquery2_(OP_XOR);
219 singlesubquery2_(OP_AND_MAYBE);
220 singlesubquery2_(OP_FILTER);
221 singlesubquery2_(OP_NEAR);
222 singlesubquery2_(OP_PHRASE);
223 singlesubquery2_(OP_ELITE_SET);
224 singlesubquery2_(OP_SYNONYM);
225 singlesubquery2_(OP_MAX);
228 DEFINE_TESTCASE(singlesubquery3, !backend) {
229 // Like the previous test, but using MatchAll as the subquery.
230 #define singlesubquery3_(OP) \
231 TEST_STRINGS_EQUAL(Xapian::Query(q->OP, q, q + 1).get_description(),\
232 "Query(<alldocuments>)")
233 Xapian::Query q[1] = { Xapian::Query::MatchAll };
234 singlesubquery3_(OP_AND);
235 singlesubquery3_(OP_OR);
236 singlesubquery3_(OP_AND_NOT);
237 singlesubquery3_(OP_XOR);
238 singlesubquery3_(OP_AND_MAYBE);
239 singlesubquery3_(OP_FILTER);
240 // OP_NEAR and OP_PHRASE over MatchAll doesn't really make sense.
241 singlesubquery3_(OP_ELITE_SET);
242 singlesubquery3_(OP_SYNONYM);
243 singlesubquery3_(OP_MAX);
246 /// Check we no longer combine wqf for same term at the same position.
247 DEFINE_TESTCASE(combinewqfnomore1, !backend) {
248 Xapian::Query q(Xapian::Query::OP_OR,
249 Xapian::Query("beer", 1, 1),
250 Xapian::Query("beer", 1, 1));
251 // Prior to 1.3.0, we would have given beer@2, but we decided that wasn't
252 // really useful or helpful.
253 TEST_EQUAL(q.get_description(), "Query((beer@1 OR beer@1))");
256 class DestroyedFlag {
257 bool & destroyed;
259 public:
260 DestroyedFlag(bool & destroyed_) : destroyed(destroyed_) {
261 destroyed = false;
264 ~DestroyedFlag() {
265 destroyed = true;
269 class TestRangeProcessor : public Xapian::RangeProcessor {
270 DestroyedFlag destroyed;
272 public:
273 TestRangeProcessor(bool & destroyed_)
274 : Xapian::RangeProcessor(0), destroyed(destroyed_) { }
276 Xapian::Query operator()(const std::string&, const std::string&) override {
277 return Xapian::Query::MatchAll;
281 /// Check reference counting of user-subclassable classes.
282 DEFINE_TESTCASE(subclassablerefcount1, !backend) {
283 bool gone_auto, gone;
284 #ifdef _MSC_VER
285 // MSVC incorrectly warns these are potentially uninitialised. It's
286 // unhelpful to always initialise these as that could mask if a genuine bug
287 // were introduced (which currently would likely be caught by a warning
288 // from a smarter compiler).
289 gone_auto = gone = false;
290 #endif
292 // Simple test of release().
294 Xapian::RangeProcessor * rp = new TestRangeProcessor(gone);
295 TEST(!gone);
296 Xapian::QueryParser qp;
297 qp.add_rangeprocessor(rp->release());
298 TEST(!gone);
300 TEST(gone);
302 // Check a second call to release() has no effect.
304 Xapian::RangeProcessor * rp = new TestRangeProcessor(gone);
305 TEST(!gone);
306 Xapian::QueryParser qp;
307 qp.add_rangeprocessor(rp->release());
308 rp->release();
309 TEST(!gone);
311 TEST(gone);
313 // Test reference counting works, and that a RangeProcessor with automatic
314 // storage works OK.
316 TestRangeProcessor rp_auto(gone_auto);
317 TEST(!gone_auto);
319 Xapian::QueryParser qp1;
321 Xapian::QueryParser qp2;
322 Xapian::RangeProcessor * rp;
323 rp = new TestRangeProcessor(gone);
324 TEST(!gone);
325 qp1.add_rangeprocessor(rp->release());
326 TEST(!gone);
327 qp2.add_rangeprocessor(rp);
328 TEST(!gone);
329 qp2.add_rangeprocessor(&rp_auto);
330 TEST(!gone);
331 TEST(!gone_auto);
333 TEST(!gone);
335 TEST(gone);
336 TEST(!gone_auto);
338 TEST(gone_auto);
340 // Regression test for initial implementation, where ~opt_intrusive_ptr()
341 // checked the reference of the object, which may have already been deleted
342 // if it wasn't been reference counted.
344 Xapian::QueryParser qp;
346 Xapian::RangeProcessor * rp = new TestRangeProcessor(gone);
347 TEST(!gone);
348 qp.add_rangeprocessor(rp);
349 delete rp;
350 TEST(gone);
352 // At the end of this block, qp is destroyed, but mustn't dereference
353 // the pointer it has to rp. If it does, that should get caught
354 // when tests are run under valgrind.
358 class TestFieldProcessor : public Xapian::FieldProcessor {
359 DestroyedFlag destroyed;
361 public:
362 TestFieldProcessor(bool & destroyed_) : destroyed(destroyed_) { }
364 Xapian::Query operator()(const string& str) override {
365 return Xapian::Query(str);
369 /// Check reference counting of user-subclassable classes.
370 DEFINE_TESTCASE(subclassablerefcount2, !backend) {
371 bool gone_auto, gone;
372 #ifdef _MSC_VER
373 // MSVC incorrectly warns these are potentially uninitialised. It's
374 // unhelpful to always initialise these as that could mask if a genuine bug
375 // were introduced (which currently would likely be caught by a warning
376 // from a smarter compiler).
377 gone_auto = gone = false;
378 #endif
380 // Simple test of release().
382 Xapian::FieldProcessor * proc = new TestFieldProcessor(gone);
383 TEST(!gone);
384 Xapian::QueryParser qp;
385 qp.add_prefix("foo", proc->release());
386 TEST(!gone);
388 TEST(gone);
390 // Check a second call to release() has no effect.
392 Xapian::FieldProcessor * proc = new TestFieldProcessor(gone);
393 TEST(!gone);
394 Xapian::QueryParser qp;
395 qp.add_prefix("foo", proc->release());
396 proc->release();
397 TEST(!gone);
399 TEST(gone);
401 // Test reference counting works, and that a FieldProcessor with automatic
402 // storage works OK.
404 TestFieldProcessor proc_auto(gone_auto);
405 TEST(!gone_auto);
407 Xapian::QueryParser qp1;
409 Xapian::QueryParser qp2;
410 Xapian::FieldProcessor * proc;
411 proc = new TestFieldProcessor(gone);
412 TEST(!gone);
413 qp1.add_prefix("foo", proc->release());
414 TEST(!gone);
415 qp2.add_prefix("foo", proc);
416 TEST(!gone);
417 qp2.add_prefix("bar", &proc_auto);
418 TEST(!gone);
419 TEST(!gone_auto);
421 TEST(!gone);
423 TEST(gone);
424 TEST(!gone_auto);
426 TEST(gone_auto);
429 class TestMatchSpy : public Xapian::MatchSpy {
430 DestroyedFlag destroyed;
432 public:
433 TestMatchSpy(bool & destroyed_) : destroyed(destroyed_) { }
435 void operator()(const Xapian::Document&, double) override { }
438 /// Check reference counting of MatchSpy.
439 DEFINE_TESTCASE(subclassablerefcount3, backend) {
440 Xapian::Database db = get_database("apitest_simpledata");
442 bool gone_auto, gone;
443 #ifdef _MSC_VER
444 // MSVC incorrectly warns these are potentially uninitialised. It's
445 // unhelpful to always initialise these as that could mask if a genuine bug
446 // were introduced (which currently would likely be caught by a warning
447 // from a smarter compiler).
448 gone_auto = gone = false;
449 #endif
451 // Simple test of release().
453 Xapian::MatchSpy * spy = new TestMatchSpy(gone);
454 TEST(!gone);
455 Xapian::Enquire enquire(db);
456 enquire.add_matchspy(spy->release());
457 TEST(!gone);
459 TEST(gone);
461 // Check a second call to release() has no effect.
463 Xapian::MatchSpy * spy = new TestMatchSpy(gone);
464 TEST(!gone);
465 Xapian::Enquire enquire(db);
466 enquire.add_matchspy(spy->release());
467 spy->release();
468 TEST(!gone);
470 TEST(gone);
472 // Test reference counting works, and that a MatchSpy with automatic
473 // storage works OK.
475 TestMatchSpy spy_auto(gone_auto);
476 TEST(!gone_auto);
478 Xapian::Enquire enq1(db);
480 Xapian::Enquire enq2(db);
481 Xapian::MatchSpy * spy;
482 spy = new TestMatchSpy(gone);
483 TEST(!gone);
484 enq1.add_matchspy(spy->release());
485 TEST(!gone);
486 enq2.add_matchspy(spy);
487 TEST(!gone);
488 enq2.add_matchspy(&spy_auto);
489 TEST(!gone);
490 TEST(!gone_auto);
492 TEST(!gone);
494 TEST(gone);
495 TEST(!gone_auto);
497 TEST(gone_auto);
500 class TestStopper : public Xapian::Stopper {
501 DestroyedFlag destroyed;
503 public:
504 TestStopper(bool & destroyed_) : destroyed(destroyed_) { }
506 bool operator()(const std::string&) const override { return true; }
509 /// Check reference counting of Stopper with QueryParser.
510 DEFINE_TESTCASE(subclassablerefcount4, !backend) {
511 bool gone_auto, gone;
512 #ifdef _MSC_VER
513 // MSVC incorrectly warns these are potentially uninitialised. It's
514 // unhelpful to always initialise these as that could mask if a genuine bug
515 // were introduced (which currently would likely be caught by a warning
516 // from a smarter compiler).
517 gone_auto = gone = false;
518 #endif
520 // Simple test of release().
522 Xapian::Stopper * stopper = new TestStopper(gone);
523 TEST(!gone);
524 Xapian::QueryParser qp;
525 qp.set_stopper(stopper->release());
526 TEST(!gone);
528 TEST(gone);
530 // Test that setting a new stopper causes the previous one to be released.
532 bool gone0;
533 Xapian::Stopper * stopper0 = new TestStopper(gone0);
534 TEST(!gone0);
535 Xapian::QueryParser qp;
536 qp.set_stopper(stopper0->release());
537 TEST(!gone0);
539 Xapian::Stopper * stopper = new TestStopper(gone);
540 TEST(!gone);
541 qp.set_stopper(stopper->release());
542 TEST(gone0);
543 TEST(!gone);
545 TEST(gone);
547 // Check a second call to release() has no effect.
549 Xapian::Stopper * stopper = new TestStopper(gone);
550 TEST(!gone);
551 Xapian::QueryParser qp;
552 qp.set_stopper(stopper->release());
553 stopper->release();
554 TEST(!gone);
556 TEST(gone);
558 // Test reference counting works, and that a Stopper with automatic
559 // storage works OK.
561 TestStopper stopper_auto(gone_auto);
562 TEST(!gone_auto);
564 Xapian::QueryParser qp1;
566 Xapian::QueryParser qp2;
567 Xapian::Stopper * stopper;
568 stopper = new TestStopper(gone);
569 TEST(!gone);
570 qp1.set_stopper(stopper->release());
571 TEST(!gone);
572 qp2.set_stopper(stopper);
573 TEST(!gone);
574 qp2.set_stopper(&stopper_auto);
575 TEST(!gone);
576 TEST(!gone_auto);
578 TEST(!gone);
580 TEST(gone);
581 TEST(!gone_auto);
583 TEST(gone_auto);
586 /// Check reference counting of Stopper with TermGenerator.
587 DEFINE_TESTCASE(subclassablerefcount5, !backend) {
588 bool gone_auto, gone;
589 #ifdef _MSC_VER
590 // MSVC incorrectly warns these are potentially uninitialised. It's
591 // unhelpful to always initialise these as that could mask if a genuine bug
592 // were introduced (which currently would likely be caught by a warning
593 // from a smarter compiler).
594 gone_auto = gone = false;
595 #endif
597 // Simple test of release().
599 Xapian::Stopper * stopper = new TestStopper(gone);
600 TEST(!gone);
601 Xapian::TermGenerator indexer;
602 indexer.set_stopper(stopper->release());
603 TEST(!gone);
605 TEST(gone);
607 // Test that setting a new stopper causes the previous one to be released.
609 bool gone0;
610 Xapian::Stopper * stopper0 = new TestStopper(gone0);
611 TEST(!gone0);
612 Xapian::TermGenerator indexer;
613 indexer.set_stopper(stopper0->release());
614 TEST(!gone0);
616 Xapian::Stopper * stopper = new TestStopper(gone);
617 TEST(!gone);
618 indexer.set_stopper(stopper->release());
619 TEST(gone0);
620 TEST(!gone);
622 TEST(gone);
624 // Check a second call to release() has no effect.
626 Xapian::Stopper * stopper = new TestStopper(gone);
627 TEST(!gone);
628 Xapian::TermGenerator indexer;
629 indexer.set_stopper(stopper->release());
630 stopper->release();
631 TEST(!gone);
633 TEST(gone);
635 // Test reference counting works, and that a Stopper with automatic
636 // storage works OK.
638 TestStopper stopper_auto(gone_auto);
639 TEST(!gone_auto);
641 Xapian::TermGenerator indexer1;
643 Xapian::TermGenerator indexer2;
644 Xapian::Stopper * stopper;
645 stopper = new TestStopper(gone);
646 TEST(!gone);
647 indexer1.set_stopper(stopper->release());
648 TEST(!gone);
649 indexer2.set_stopper(stopper);
650 TEST(!gone);
651 indexer2.set_stopper(&stopper_auto);
652 TEST(!gone);
653 TEST(!gone_auto);
655 TEST(!gone);
657 TEST(gone);
658 TEST(!gone_auto);
660 TEST(gone_auto);
663 class TestKeyMaker : public Xapian::KeyMaker {
664 DestroyedFlag destroyed;
666 public:
667 TestKeyMaker(bool & destroyed_) : destroyed(destroyed_) { }
669 string operator()(const Xapian::Document&) const override {
670 return string();
674 /// Check reference counting of KeyMaker.
675 DEFINE_TESTCASE(subclassablerefcount6, backend) {
676 Xapian::Database db = get_database("apitest_simpledata");
678 bool gone_auto, gone;
679 #ifdef _MSC_VER
680 // MSVC incorrectly warns these are potentially uninitialised. It's
681 // unhelpful to always initialise these as that could mask if a genuine bug
682 // were introduced (which currently would likely be caught by a warning
683 // from a smarter compiler).
684 gone_auto = gone = false;
685 #endif
687 // Simple test of release().
689 Xapian::KeyMaker * keymaker = new TestKeyMaker(gone);
690 TEST(!gone);
691 Xapian::Enquire enq(db);
692 enq.set_sort_by_key(keymaker->release(), false);
693 TEST(!gone);
695 TEST(gone);
697 // Test that setting a new keymaker causes the previous one to be released.
699 bool gone0;
700 Xapian::KeyMaker * keymaker0 = new TestKeyMaker(gone0);
701 TEST(!gone0);
702 Xapian::Enquire enq(db);
703 enq.set_sort_by_key(keymaker0->release(), false);
704 TEST(!gone0);
706 Xapian::KeyMaker * keymaker = new TestKeyMaker(gone);
707 TEST(!gone);
708 enq.set_sort_by_key_then_relevance(keymaker->release(), false);
709 TEST(gone0);
710 TEST(!gone);
712 TEST(gone);
714 // Check a second call to release() has no effect.
716 Xapian::KeyMaker * keymaker = new TestKeyMaker(gone);
717 TEST(!gone);
718 Xapian::Enquire enq(db);
719 enq.set_sort_by_key(keymaker->release(), false);
720 keymaker->release();
721 TEST(!gone);
723 TEST(gone);
725 // Test reference counting works, and that a KeyMaker with automatic
726 // storage works OK.
728 TestKeyMaker keymaker_auto(gone_auto);
729 TEST(!gone_auto);
731 Xapian::Enquire enq1(db);
733 Xapian::Enquire enq2(db);
734 Xapian::KeyMaker * keymaker;
735 keymaker = new TestKeyMaker(gone);
736 TEST(!gone);
737 enq1.set_sort_by_key(keymaker->release(), false);
738 TEST(!gone);
739 enq2.set_sort_by_relevance_then_key(keymaker, false);
740 TEST(!gone);
741 enq2.set_sort_by_key_then_relevance(&keymaker_auto, false);
742 TEST(!gone);
743 TEST(!gone_auto);
745 TEST(!gone);
747 TEST(gone);
748 TEST(!gone_auto);
750 TEST(gone_auto);
753 class TestExpandDecider : public Xapian::ExpandDecider {
754 DestroyedFlag destroyed;
756 public:
757 TestExpandDecider(bool & destroyed_) : destroyed(destroyed_) { }
759 bool operator()(const string&) const override { return true; }
762 /// Check reference counting of ExpandDecider.
763 DEFINE_TESTCASE(subclassablerefcount7, backend) {
764 Xapian::Database db = get_database("apitest_simpledata");
765 Xapian::Enquire enq(db);
766 Xapian::RSet rset;
767 rset.add_document(1);
769 bool gone_auto, gone;
770 #ifdef _MSC_VER
771 // MSVC incorrectly warns these are potentially uninitialised. It's
772 // unhelpful to always initialise these as that could mask if a genuine bug
773 // were introduced (which currently would likely be caught by a warning
774 // from a smarter compiler).
775 gone_auto = gone = false;
776 #endif
778 for (int flags = 0;
779 flags <= Xapian::Enquire::INCLUDE_QUERY_TERMS;
780 flags += Xapian::Enquire::INCLUDE_QUERY_TERMS) {
781 // Test of auto lifetime ExpandDecider.
783 TestExpandDecider edecider_auto(gone_auto);
784 TEST(!gone_auto);
785 (void)enq.get_eset(5, rset, 0, &edecider_auto);
786 TEST(!gone_auto);
788 TEST(gone_auto);
790 // Simple test of release().
792 Xapian::ExpandDecider * edecider = new TestExpandDecider(gone);
793 TEST(!gone);
794 (void)enq.get_eset(5, rset, 0, edecider);
795 TEST(!gone);
796 delete edecider;
797 TEST(gone);
800 // Test that a released ExpandDecider gets cleaned up by get_eset().
802 Xapian::ExpandDecider * edecider = new TestExpandDecider(gone);
803 TEST(!gone);
804 (void)enq.get_eset(5, rset, 0, edecider->release());
805 TEST(gone);
808 // Check a second call to release() has no effect.
810 Xapian::ExpandDecider * edecider = new TestExpandDecider(gone);
811 TEST(!gone);
812 edecider->release();
813 TEST(!gone);
814 (void)enq.get_eset(5, rset, 0, edecider->release());
815 TEST(gone);
819 // Test combinations of released/non-released with ExpandDeciderAnd.
821 TestExpandDecider edecider_auto(gone_auto);
822 TEST(!gone_auto);
823 Xapian::ExpandDecider * edecider = new TestExpandDecider(gone);
824 TEST(!gone);
825 (void)enq.get_eset(5, rset, 0,
826 (new Xapian::ExpandDeciderAnd(
827 &edecider_auto,
828 edecider->release()))->release());
829 TEST(!gone_auto);
830 TEST(gone);
832 TEST(gone_auto);
834 TestExpandDecider edecider_auto(gone_auto);
835 TEST(!gone_auto);
836 Xapian::ExpandDecider * edecider = new TestExpandDecider(gone);
837 TEST(!gone);
838 (void)enq.get_eset(5, rset, 0,
839 (new Xapian::ExpandDeciderAnd(
840 edecider->release(),
841 &edecider_auto))->release());
842 TEST(!gone_auto);
843 TEST(gone);
845 TEST(gone_auto);
848 /// Check encoding of non-UTF8 document data.
849 DEFINE_TESTCASE(nonutf8docdesc1, !backend) {
850 Xapian::Document doc;
851 doc.set_data("\xc0\x80\xf5\x80\x80\x80\xfe\xff");
852 TEST_EQUAL(doc.get_description(),
853 "Document(docid=0, data=\\xc0\\x80\\xf5\\x80\\x80\\x80\\xfe\\xff)");
854 doc.set_data(string("\x00\x1f", 2));
855 TEST_EQUAL(doc.get_description(),
856 "Document(docid=0, data=\\x00\\x1f)");
857 // Check that backslashes are encoded so output isn't ambiguous.
858 doc.set_data("back\\slash");
859 TEST_EQUAL(doc.get_description(),
860 "Document(docid=0, data=back\\x5cslash)");
863 DEFINE_TESTCASE(orphaneddoctermitor1, !backend) {
864 Xapian::TermIterator t;
866 Xapian::Document doc;
867 doc.add_term("foo");
868 t = doc.termlist_begin();
870 TEST_EQUAL(*t, "foo");
873 /** Test removal of terms from a document while iterating over them.
875 * Prior to 1.5.0 and 1.4.6 the underlying iterator was invalidated when
876 * preinc == false, leading to undefined behaviour (typically a segmentation
877 * fault).
879 DEFINE_TESTCASE(deletewhileiterating1, !backend) {
880 for (bool preinc : { false, true }) {
881 Xapian::Document doc;
882 Xapian::TermGenerator indexer;
883 indexer.set_document(doc);
884 indexer.index_text("Pull the rug out from under ourselves", 1, "S");
885 Xapian::TermIterator term_iterator = doc.termlist_begin();
886 term_iterator.skip_to("S");
887 while (term_iterator != doc.termlist_end()) {
888 const string& term = *term_iterator;
889 if (!startswith(term, "S")) {
890 break;
892 if (preinc) ++term_iterator;
893 doc.remove_term(term);
894 if (!preinc) ++term_iterator;
896 TEST_EQUAL(doc.termlist_count(), 0);
897 TEST(doc.termlist_begin() == doc.termlist_end());
901 /// Feature test for Document::remove_postings().
902 DEFINE_TESTCASE(removepostings, !backend) {
903 Xapian::Document doc;
904 // Add Fibonacci sequence as positions.
905 Xapian::termpos prev_pos = 1;
906 Xapian::termpos pos = 1;
907 while (pos < 1000) {
908 doc.add_posting("foo", pos);
909 auto new_pos = prev_pos + pos;
910 prev_pos = pos;
911 pos = new_pos;
914 // Check we added exactly one term.
915 TEST_EQUAL(doc.termlist_count(), 1);
917 Xapian::TermIterator t = doc.termlist_begin();
918 auto num_pos = t.positionlist_count();
919 TEST_EQUAL(t.get_wdf(), num_pos);
921 Xapian::PositionIterator pi = t.positionlist_begin();
923 // Out of order is a no-op.
924 TEST_EQUAL(doc.remove_postings("foo", 2, 1), 0);
925 t = doc.termlist_begin();
926 TEST_EQUAL(t.positionlist_count(), num_pos);
927 TEST_EQUAL(t.get_wdf(), num_pos);
929 // 6 and 7 aren't in the sequence.
930 TEST_EQUAL(doc.remove_postings("foo", 6, 7), 0);
931 t = doc.termlist_begin();
932 TEST_EQUAL(t.positionlist_count(), num_pos);
933 TEST_EQUAL(t.get_wdf(), num_pos);
935 // Beyond the end of the positions.
936 TEST_EQUAL(doc.remove_postings("foo", 1000, 2000), 0);
937 t = doc.termlist_begin();
938 TEST_EQUAL(t.positionlist_count(), num_pos);
939 TEST_EQUAL(t.get_wdf(), num_pos);
941 // 1, 2, 3 are in the sequence, 4 isn't.
942 TEST_EQUAL(doc.remove_postings("foo", 1, 4), 3);
943 t = doc.termlist_begin();
944 TEST_EQUAL(t.positionlist_count(), num_pos - 3);
945 TEST_EQUAL(t.get_wdf(), num_pos - 3);
947 // Test an existing PositionIterator isn't affected.
948 TEST_EQUAL(*pi, 1);
949 TEST_EQUAL(*++pi, 2);
950 TEST_EQUAL(*++pi, 3);
951 TEST_EQUAL(*++pi, 5);
953 // Remove the end position.
954 TEST_EQUAL(doc.remove_postings("foo", 876, 987), 1);
955 t = doc.termlist_begin();
956 TEST_EQUAL(t.positionlist_count(), num_pos - 4);
957 TEST_EQUAL(t.get_wdf(), num_pos - 4);
959 // Remove a range in the middle.
960 TEST_EQUAL(doc.remove_postings("foo", 33, 233), 5);
961 t = doc.termlist_begin();
962 TEST_EQUAL(t.positionlist_count(), num_pos - 9);
963 TEST_EQUAL(t.get_wdf(), num_pos - 9);
965 // Check the expected positions are left.
966 t = doc.termlist_begin();
967 static const Xapian::termpos expected[] = { 5, 8, 13, 21, 377, 610, 9999 };
968 const Xapian::termpos* expect = expected;
969 for (auto p = t.positionlist_begin(); p != t.positionlist_end(); ++p) {
970 TEST_EQUAL(*p, *expect);
971 ++expect;
973 TEST_EQUAL(*expect, 9999);
974 TEST_EQUAL(t.get_wdf(), 6);
976 pi = t.positionlist_begin();
978 // Test remove_position().
979 doc.remove_posting("foo", 8);
981 // Test an existing PositionIterator isn't affected.
982 TEST_EQUAL(*pi, 5);
983 TEST_EQUAL(*++pi, 8);
985 // Check removing all positions removes the term too if the wdf reaches 0
986 // (since 1.5.0).
987 TEST_EQUAL(doc.remove_postings("foo", 5, 1000), 5);
988 t = doc.termlist_begin();
989 TEST(t == doc.termlist_end());
991 // Test the removing all positions doesn't remove the term if the wdf is
992 // still non-zero.
993 doc.add_posting("foo", 123, 2);
994 TEST_EQUAL(doc.remove_postings("foo", 1, 200), 1);
995 t = doc.termlist_begin();
996 TEST(t != doc.termlist_end());
997 TEST(t.positionlist_begin() == t.positionlist_end());
998 TEST_EQUAL(t.get_wdf(), 1);
1000 // Test removing the last posting is handled correctly (this case is
1001 // special-cased internally to be O(1)).
1002 t = doc.termlist_begin();
1003 doc.add_posting("foo", 12);
1004 doc.add_posting("foo", 23);
1005 pi = t.positionlist_begin();
1006 doc.remove_posting("foo", 23);
1007 TEST_EQUAL(*pi, 12);
1008 ++pi;
1009 TEST_EQUAL(*pi, 23);
1010 ++pi;
1011 TEST(pi == t.positionlist_end());
1014 [[noreturn]]
1015 static void
1016 errorcopyctor_helper(Xapian::Error& error)
1018 // GCC 9 was giving a warning on the next line with -Wdeprecated-copy
1019 // (which is enabled by -Wextra).
1020 throw error;
1023 /// Regression test for warning with GCC 9.
1024 DEFINE_TESTCASE(errorcopyctor, !backend) {
1025 Xapian::RangeError e("test");
1026 try {
1027 errorcopyctor_helper(e);
1028 } catch (Xapian::Error&) {
1029 return;
1031 FAIL_TEST("Expected exception to be thrown");
1034 DEFINE_TESTCASE(emptydbbounds, !backend) {
1035 Xapian::Database db;
1036 TEST_EQUAL(db.get_doclength_lower_bound(), 0);
1037 TEST_EQUAL(db.get_doclength_upper_bound(), 0);
1038 // We always returned 1 here in the initial implementation.
1039 TEST_EQUAL(db.get_unique_terms_lower_bound(), 0);
1040 TEST_EQUAL(db.get_unique_terms_upper_bound(), 0);