2 * @brief tests which don't need a backend
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
27 #define XAPIAN_DEPRECATED(D) D
32 #include "testsuite.h"
33 #include "testutils.h"
35 #include <string_view>
39 // Check the version functions give consistent results.
40 DEFINE_TESTCASE(version1
, !backend
) {
41 string version
= str(Xapian::major_version());
43 version
+= str(Xapian::minor_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
) {
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
) {
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
) {
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
) {
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());
106 TEST_EQUAL(doc
.termlist_count(), 0);
107 TEST(doc
.termlist_begin() == doc
.termlist_end());
108 // Test clear_terms() when there are no 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
;
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());
132 TEST_EQUAL(doc
.values_count(), 0);
133 TEST(doc
.values_begin() == doc
.values_end());
134 // Test clear_values() when there are no 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
;
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.
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(),\
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(),\
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
{
260 DestroyedFlag(bool & destroyed_
) : destroyed(destroyed_
) {
269 class TestRangeProcessor
: public Xapian::RangeProcessor
{
270 DestroyedFlag destroyed
;
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
;
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;
292 // Simple test of release().
294 Xapian::RangeProcessor
* rp
= new TestRangeProcessor(gone
);
296 Xapian::QueryParser qp
;
297 qp
.add_rangeprocessor(rp
->release());
302 // Check a second call to release() has no effect.
304 Xapian::RangeProcessor
* rp
= new TestRangeProcessor(gone
);
306 Xapian::QueryParser qp
;
307 qp
.add_rangeprocessor(rp
->release());
313 // Test reference counting works, and that a RangeProcessor with automatic
316 TestRangeProcessor
rp_auto(gone_auto
);
319 Xapian::QueryParser qp1
;
321 Xapian::QueryParser qp2
;
322 Xapian::RangeProcessor
* rp
;
323 rp
= new TestRangeProcessor(gone
);
325 qp1
.add_rangeprocessor(rp
->release());
327 qp2
.add_rangeprocessor(rp
);
329 qp2
.add_rangeprocessor(&rp_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
);
348 qp
.add_rangeprocessor(rp
);
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
;
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
;
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;
380 // Simple test of release().
382 Xapian::FieldProcessor
* proc
= new TestFieldProcessor(gone
);
384 Xapian::QueryParser qp
;
385 qp
.add_prefix("foo", proc
->release());
390 // Check a second call to release() has no effect.
392 Xapian::FieldProcessor
* proc
= new TestFieldProcessor(gone
);
394 Xapian::QueryParser qp
;
395 qp
.add_prefix("foo", proc
->release());
401 // Test reference counting works, and that a FieldProcessor with automatic
404 TestFieldProcessor
proc_auto(gone_auto
);
407 Xapian::QueryParser qp1
;
409 Xapian::QueryParser qp2
;
410 Xapian::FieldProcessor
* proc
;
411 proc
= new TestFieldProcessor(gone
);
413 qp1
.add_prefix("foo", proc
->release());
415 qp2
.add_prefix("foo", proc
);
417 qp2
.add_prefix("bar", &proc_auto
);
429 class TestMatchSpy
: public Xapian::MatchSpy
{
430 DestroyedFlag destroyed
;
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
;
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;
451 // Simple test of release().
453 Xapian::MatchSpy
* spy
= new TestMatchSpy(gone
);
455 Xapian::Enquire
enquire(db
);
456 enquire
.add_matchspy(spy
->release());
461 // Check a second call to release() has no effect.
463 Xapian::MatchSpy
* spy
= new TestMatchSpy(gone
);
465 Xapian::Enquire
enquire(db
);
466 enquire
.add_matchspy(spy
->release());
472 // Test reference counting works, and that a MatchSpy with automatic
475 TestMatchSpy
spy_auto(gone_auto
);
478 Xapian::Enquire
enq1(db
);
480 Xapian::Enquire
enq2(db
);
481 Xapian::MatchSpy
* spy
;
482 spy
= new TestMatchSpy(gone
);
484 enq1
.add_matchspy(spy
->release());
486 enq2
.add_matchspy(spy
);
488 enq2
.add_matchspy(&spy_auto
);
500 class TestStopper
: public Xapian::Stopper
{
501 DestroyedFlag destroyed
;
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
;
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;
520 // Simple test of release().
522 Xapian::Stopper
* stopper
= new TestStopper(gone
);
524 Xapian::QueryParser qp
;
525 qp
.set_stopper(stopper
->release());
530 // Test that setting a new stopper causes the previous one to be released.
533 Xapian::Stopper
* stopper0
= new TestStopper(gone0
);
535 Xapian::QueryParser qp
;
536 qp
.set_stopper(stopper0
->release());
539 Xapian::Stopper
* stopper
= new TestStopper(gone
);
541 qp
.set_stopper(stopper
->release());
547 // Check a second call to release() has no effect.
549 Xapian::Stopper
* stopper
= new TestStopper(gone
);
551 Xapian::QueryParser qp
;
552 qp
.set_stopper(stopper
->release());
558 // Test reference counting works, and that a Stopper with automatic
561 TestStopper
stopper_auto(gone_auto
);
564 Xapian::QueryParser qp1
;
566 Xapian::QueryParser qp2
;
567 Xapian::Stopper
* stopper
;
568 stopper
= new TestStopper(gone
);
570 qp1
.set_stopper(stopper
->release());
572 qp2
.set_stopper(stopper
);
574 qp2
.set_stopper(&stopper_auto
);
586 /// Check reference counting of Stopper with TermGenerator.
587 DEFINE_TESTCASE(subclassablerefcount5
, !backend
) {
588 bool gone_auto
, gone
;
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;
597 // Simple test of release().
599 Xapian::Stopper
* stopper
= new TestStopper(gone
);
601 Xapian::TermGenerator indexer
;
602 indexer
.set_stopper(stopper
->release());
607 // Test that setting a new stopper causes the previous one to be released.
610 Xapian::Stopper
* stopper0
= new TestStopper(gone0
);
612 Xapian::TermGenerator indexer
;
613 indexer
.set_stopper(stopper0
->release());
616 Xapian::Stopper
* stopper
= new TestStopper(gone
);
618 indexer
.set_stopper(stopper
->release());
624 // Check a second call to release() has no effect.
626 Xapian::Stopper
* stopper
= new TestStopper(gone
);
628 Xapian::TermGenerator indexer
;
629 indexer
.set_stopper(stopper
->release());
635 // Test reference counting works, and that a Stopper with automatic
638 TestStopper
stopper_auto(gone_auto
);
641 Xapian::TermGenerator indexer1
;
643 Xapian::TermGenerator indexer2
;
644 Xapian::Stopper
* stopper
;
645 stopper
= new TestStopper(gone
);
647 indexer1
.set_stopper(stopper
->release());
649 indexer2
.set_stopper(stopper
);
651 indexer2
.set_stopper(&stopper_auto
);
663 class TestKeyMaker
: public Xapian::KeyMaker
{
664 DestroyedFlag destroyed
;
667 TestKeyMaker(bool & destroyed_
) : destroyed(destroyed_
) { }
669 string
operator()(const Xapian::Document
&) const override
{
674 /// Check reference counting of KeyMaker.
675 DEFINE_TESTCASE(subclassablerefcount6
, backend
) {
676 Xapian::Database db
= get_database("apitest_simpledata");
678 bool gone_auto
, gone
;
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;
687 // Simple test of release().
689 Xapian::KeyMaker
* keymaker
= new TestKeyMaker(gone
);
691 Xapian::Enquire
enq(db
);
692 enq
.set_sort_by_key(keymaker
->release(), false);
697 // Test that setting a new keymaker causes the previous one to be released.
700 Xapian::KeyMaker
* keymaker0
= new TestKeyMaker(gone0
);
702 Xapian::Enquire
enq(db
);
703 enq
.set_sort_by_key(keymaker0
->release(), false);
706 Xapian::KeyMaker
* keymaker
= new TestKeyMaker(gone
);
708 enq
.set_sort_by_key_then_relevance(keymaker
->release(), false);
714 // Check a second call to release() has no effect.
716 Xapian::KeyMaker
* keymaker
= new TestKeyMaker(gone
);
718 Xapian::Enquire
enq(db
);
719 enq
.set_sort_by_key(keymaker
->release(), false);
725 // Test reference counting works, and that a KeyMaker with automatic
728 TestKeyMaker
keymaker_auto(gone_auto
);
731 Xapian::Enquire
enq1(db
);
733 Xapian::Enquire
enq2(db
);
734 Xapian::KeyMaker
* keymaker
;
735 keymaker
= new TestKeyMaker(gone
);
737 enq1
.set_sort_by_key(keymaker
->release(), false);
739 enq2
.set_sort_by_relevance_then_key(keymaker
, false);
741 enq2
.set_sort_by_key_then_relevance(&keymaker_auto
, false);
753 class TestExpandDecider
: public Xapian::ExpandDecider
{
754 DestroyedFlag destroyed
;
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
);
767 rset
.add_document(1);
769 bool gone_auto
, gone
;
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;
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
);
785 (void)enq
.get_eset(5, rset
, 0, &edecider_auto
);
790 // Simple test of release().
792 Xapian::ExpandDecider
* edecider
= new TestExpandDecider(gone
);
794 (void)enq
.get_eset(5, rset
, 0, edecider
);
800 // Test that a released ExpandDecider gets cleaned up by get_eset().
802 Xapian::ExpandDecider
* edecider
= new TestExpandDecider(gone
);
804 (void)enq
.get_eset(5, rset
, 0, edecider
->release());
808 // Check a second call to release() has no effect.
810 Xapian::ExpandDecider
* edecider
= new TestExpandDecider(gone
);
814 (void)enq
.get_eset(5, rset
, 0, edecider
->release());
819 // Test combinations of released/non-released with ExpandDeciderAnd.
821 TestExpandDecider
edecider_auto(gone_auto
);
823 Xapian::ExpandDecider
* edecider
= new TestExpandDecider(gone
);
825 (void)enq
.get_eset(5, rset
, 0,
826 (new Xapian::ExpandDeciderAnd(
828 edecider
->release()))->release());
834 TestExpandDecider
edecider_auto(gone_auto
);
836 Xapian::ExpandDecider
* edecider
= new TestExpandDecider(gone
);
838 (void)enq
.get_eset(5, rset
, 0,
839 (new Xapian::ExpandDeciderAnd(
841 &edecider_auto
))->release());
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
;
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
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")) {
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;
908 doc
.add_posting("foo", pos
);
909 auto new_pos
= prev_pos
+ 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.
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
);
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.
983 TEST_EQUAL(*++pi
, 8);
985 // Check removing all positions removes the term too if the wdf reaches 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
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);
1009 TEST_EQUAL(*pi
, 23);
1011 TEST(pi
== t
.positionlist_end());
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).
1023 /// Regression test for warning with GCC 9.
1024 DEFINE_TESTCASE(errorcopyctor
, !backend
) {
1025 Xapian::RangeError
e("test");
1027 errorcopyctor_helper(e
);
1028 } catch (Xapian::Error
&) {
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);