2 * @brief Tests of serialisation functionality.
4 /* Copyright 2009 Lemur Consulting Ltd
5 * Copyright 2009,2011,2012,2013,2024 Olly Betts
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (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 USA
24 #include "api_serialise.h"
32 #include "testutils.h"
36 // Test for serialising a document
37 DEFINE_TESTCASE(serialise_document1
, !backend
) {
40 // Test serialising and unserialising an empty document.
41 Xapian::Document doc1
= Xapian::Document::unserialise(doc
.serialise());
42 TEST_EQUAL(doc1
.termlist_count(), 0);
43 TEST_EQUAL(doc1
.termlist_begin(), doc1
.termlist_end());
44 TEST_EQUAL(doc1
.values_count(), 0);
45 TEST_EQUAL(doc1
.values_begin(), doc1
.values_end());
46 TEST_EQUAL(doc1
.get_data(), "");
48 // Test serialising a document with things in.
49 doc
.add_term("foo", 2);
50 doc
.add_posting("foo", 10);
51 doc
.add_value(1, "bar");
54 Xapian::Document doc2
= Xapian::Document::unserialise(doc
.serialise());
56 TEST_EQUAL(doc
.termlist_count(), doc2
.termlist_count());
57 TEST_EQUAL(doc
.termlist_count(), 1);
58 Xapian::TermIterator i
;
59 Xapian::PositionIterator j
;
60 Xapian::ValueIterator k
;
62 i
= doc
.termlist_begin();
63 TEST_NOT_EQUAL(i
, doc
.termlist_end());
64 TEST_EQUAL(i
.get_wdf(), 3);
65 TEST_EQUAL(*i
, "foo");
66 TEST_EQUAL(i
.positionlist_count(), 1);
67 j
= i
.positionlist_begin();
68 TEST_NOT_EQUAL(j
, i
.positionlist_end());
71 TEST_EQUAL(j
, i
.positionlist_end());
73 TEST_EQUAL(i
, doc
.termlist_end());
75 TEST_EQUAL(doc
.values_count(), 1);
76 k
= doc
.values_begin();
77 TEST_NOT_EQUAL(k
, doc
.values_end());
78 TEST_EQUAL(k
.get_valueno(), 1);
79 TEST_EQUAL(*k
, "bar");
81 TEST_EQUAL(k
, doc
.values_end());
83 TEST_EQUAL(doc
.get_data(), "baz");
85 i
= doc2
.termlist_begin();
86 TEST_NOT_EQUAL(i
, doc2
.termlist_end());
87 TEST_EQUAL(i
.get_wdf(), 3);
88 TEST_EQUAL(*i
, "foo");
89 TEST_EQUAL(i
.positionlist_count(), 1);
90 j
= i
.positionlist_begin();
91 TEST_NOT_EQUAL(j
, i
.positionlist_end());
94 TEST_EQUAL(j
, i
.positionlist_end());
96 TEST_EQUAL(i
, doc2
.termlist_end());
98 TEST_EQUAL(doc2
.values_count(), 1);
99 k
= doc2
.values_begin();
100 TEST_NOT_EQUAL(k
, doc2
.values_end());
101 TEST_EQUAL(k
.get_valueno(), 1);
102 TEST_EQUAL(*k
, "bar");
104 TEST_EQUAL(k
, doc2
.values_end());
106 TEST_EQUAL(doc2
.get_data(), "baz");
109 // Test for serialising a document obtained from a database.
110 DEFINE_TESTCASE(serialise_document2
, backend
) {
111 Xapian::Database db
= get_database("serialise_document2",
112 [](Xapian::WritableDatabase
& wdb
,
114 Xapian::Document doc
;
115 doc
.add_term("foo", 2);
116 doc
.add_posting("foo", 10);
117 doc
.add_value(1, "bar");
119 wdb
.add_document(doc
);
122 Xapian::Document doc
= db
.get_document(1);
124 Xapian::Document doc2
= Xapian::Document::unserialise(doc
.serialise());
126 TEST_EQUAL(doc
.termlist_count(), doc2
.termlist_count());
127 TEST_EQUAL(doc
.termlist_count(), 1);
128 Xapian::TermIterator i
;
129 Xapian::PositionIterator j
;
130 Xapian::ValueIterator k
;
132 i
= doc
.termlist_begin();
133 TEST_NOT_EQUAL(i
, doc
.termlist_end());
134 TEST_EQUAL(i
.get_wdf(), 3);
135 TEST_EQUAL(*i
, "foo");
136 TEST_EQUAL(i
.positionlist_count(), 1);
137 j
= i
.positionlist_begin();
138 TEST_NOT_EQUAL(j
, i
.positionlist_end());
141 TEST_EQUAL(j
, i
.positionlist_end());
143 TEST_EQUAL(i
, doc
.termlist_end());
145 TEST_EQUAL(doc
.values_count(), 1);
146 k
= doc
.values_begin();
147 TEST_NOT_EQUAL(k
, doc
.values_end());
148 TEST_EQUAL(k
.get_valueno(), 1);
149 TEST_EQUAL(*k
, "bar");
151 TEST_EQUAL(k
, doc
.values_end());
153 TEST_EQUAL(doc
.get_data(), "baz");
155 i
= doc2
.termlist_begin();
156 TEST_NOT_EQUAL(i
, doc2
.termlist_end());
157 TEST_EQUAL(i
.get_wdf(), 3);
158 TEST_EQUAL(*i
, "foo");
159 TEST_EQUAL(i
.positionlist_count(), 1);
160 j
= i
.positionlist_begin();
161 TEST_NOT_EQUAL(j
, i
.positionlist_end());
164 TEST_EQUAL(j
, i
.positionlist_end());
166 TEST_EQUAL(i
, doc2
.termlist_end());
168 TEST_EQUAL(doc2
.values_count(), 1);
169 k
= doc2
.values_begin();
170 TEST_NOT_EQUAL(k
, doc2
.values_end());
171 TEST_EQUAL(k
.get_valueno(), 1);
172 TEST_EQUAL(*k
, "bar");
174 TEST_EQUAL(k
, doc2
.values_end());
176 TEST_EQUAL(doc2
.get_data(), "baz");
179 // Test for serialising a query
180 DEFINE_TESTCASE(serialise_query1
, !backend
) {
182 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise());
183 TEST_EQUAL(q
.get_description(), q2
.get_description());
184 TEST_EQUAL(q
.get_description(), "Query()");
186 q
= Xapian::Query("hello");
187 q2
= Xapian::Query::unserialise(q
.serialise());
188 TEST_EQUAL(q
.get_description(), q2
.get_description());
189 TEST_EQUAL(q
.get_description(), "Query(hello)");
191 q
= Xapian::Query("hello", 1, 1);
192 q2
= Xapian::Query::unserialise(q
.serialise());
193 // Regression test for fix in Xapian 1.0.0.
194 TEST_EQUAL(q
.get_description(), q2
.get_description());
195 TEST_EQUAL(q
.get_description(), "Query(hello@1)");
197 q
= Xapian::Query(q
.OP_OR
, Xapian::Query("hello"), Xapian::Query("world"));
198 q2
= Xapian::Query::unserialise(q
.serialise());
199 TEST_EQUAL(q
.get_description(), q2
.get_description());
200 TEST_EQUAL(q
.get_description(), "Query((hello OR world))");
202 q
= Xapian::Query(q
.OP_OR
,
203 Xapian::Query("hello", 1, 1),
204 Xapian::Query("world", 1, 1));
205 q2
= Xapian::Query::unserialise(q
.serialise());
206 TEST_EQUAL(q
.get_description(), q2
.get_description());
207 TEST_EQUAL(q
.get_description(), "Query((hello@1 OR world@1))");
209 static const char * const phrase
[] = { "shaken", "not", "stirred" };
210 q
= Xapian::Query(q
.OP_PHRASE
, phrase
, phrase
+ 3);
211 q
= Xapian::Query(q
.OP_OR
, Xapian::Query("007"), q
);
212 q
= Xapian::Query(q
.OP_SCALE_WEIGHT
, q
, 3.14);
213 q2
= Xapian::Query::unserialise(q
.serialise());
214 TEST_EQUAL(q
.get_description(), q2
.get_description());
217 // Test for serialising a query which contains a PostingSource.
218 DEFINE_TESTCASE(serialise_query2
, !backend
) {
219 Xapian::ValueWeightPostingSource
s1(10);
220 Xapian::Query
q(&s1
);
221 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise());
222 TEST_EQUAL(q
.get_description(), q2
.get_description());
223 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueWeightPostingSource(slot=10)))");
225 Xapian::ValueMapPostingSource
s2(11);
226 s2
.set_default_weight(5.0);
227 q
= Xapian::Query(&s2
);
228 q2
= Xapian::Query::unserialise(q
.serialise());
229 TEST_EQUAL(q
.get_description(), q2
.get_description());
230 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueMapPostingSource(slot=11)))");
232 Xapian::FixedWeightPostingSource
s3(5.5);
233 q
= Xapian::Query(&s3
);
234 q2
= Xapian::Query::unserialise(q
.serialise());
235 TEST_EQUAL(q
.get_description(), q2
.get_description());
236 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::FixedWeightPostingSource(wt=5.5)))");
239 // Test for unserialising a query using the default registry.
240 DEFINE_TESTCASE(serialise_query3
, !backend
) {
241 Xapian::ValueWeightPostingSource
s1(10);
242 Xapian::Query
q(&s1
);
243 Xapian::Registry reg
;
244 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
245 TEST_EQUAL(q
.get_description(), q2
.get_description());
246 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueWeightPostingSource(slot=10)))");
248 Xapian::ValueMapPostingSource
s2(11);
249 s2
.set_default_weight(5.0);
250 q
= Xapian::Query(&s2
);
251 q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
252 TEST_EQUAL(q
.get_description(), q2
.get_description());
253 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueMapPostingSource(slot=11)))");
255 Xapian::FixedWeightPostingSource
s3(5.5);
256 q
= Xapian::Query(&s3
);
257 q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
258 TEST_EQUAL(q
.get_description(), q2
.get_description());
259 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::FixedWeightPostingSource(wt=5.5)))");
262 class MyPostingSource2
: public Xapian::ValuePostingSource
{
265 MyPostingSource2(const std::string
& desc_
)
266 : Xapian::ValuePostingSource(0), desc(desc_
)
270 MyPostingSource2
* clone() const override
{
271 return new MyPostingSource2(desc
);
274 std::string
name() const override
{
275 return "MyPostingSource2";
278 std::string
serialise() const override
{
282 MyPostingSource2
* unserialise(const std::string
& s
) const override
{
283 return new MyPostingSource2(s
);
286 double get_weight() const override
{ return 1.0; }
288 std::string
get_description() const override
{
289 return "MyPostingSource2(" + desc
+ ")";
293 // Test for unserialising a query which contains a custom PostingSource.
294 DEFINE_TESTCASE(serialise_query4
, !backend
) {
295 MyPostingSource2
s1("foo");
296 Xapian::Query
q(&s1
);
297 TEST_EQUAL(q
.get_description(), "Query(PostingSource(MyPostingSource2(foo)))");
298 std::string serialised
= q
.serialise();
300 TEST_EXCEPTION(Xapian::SerialisationError
, Xapian::Query::unserialise(serialised
));
301 Xapian::Registry reg
;
302 TEST_EXCEPTION(Xapian::SerialisationError
, Xapian::Query::unserialise(serialised
, reg
));
304 reg
.register_posting_source(s1
);
305 Xapian::Query q2
= Xapian::Query::unserialise(serialised
, reg
);
306 TEST_EQUAL(q
.get_description(), q2
.get_description());
309 /// Test for memory leaks when registering posting sources or weights twice.
310 DEFINE_TESTCASE(double_register_leak
, !backend
) {
311 MyPostingSource2
s1("foo");
312 Xapian::BM25Weight w1
;
314 Xapian::Registry reg
;
315 reg
.register_posting_source(s1
);
316 reg
.register_posting_source(s1
);
317 reg
.register_posting_source(s1
);
319 reg
.register_weighting_scheme(w1
);
320 reg
.register_weighting_scheme(w1
);
321 reg
.register_weighting_scheme(w1
);
324 class ExceptionalPostingSource
: public Xapian::PostingSource
{
326 typedef enum { NONE
, CLONE
} failmode
;
330 ExceptionalPostingSource(failmode fail_
) : fail(fail_
) { }
332 string
name() const override
{
333 return "ExceptionalPostingSource";
336 PostingSource
* clone() const override
{
339 return new ExceptionalPostingSource(fail
);
342 void reset(const Xapian::Database
&, Xapian::doccount
) override
{ }
344 Xapian::doccount
get_termfreq_min() const override
{ return 0; }
345 Xapian::doccount
get_termfreq_est() const override
{ return 1; }
346 Xapian::doccount
get_termfreq_max() const override
{ return 2; }
348 void next(double) override
{ }
350 void skip_to(Xapian::docid
, double) override
{ }
352 bool at_end() const override
{ return true; }
353 Xapian::docid
get_docid() const override
{ return 0; }
356 /// Check that exceptions when registering a postingsource are handled well.
357 DEFINE_TESTCASE(registry1
, !backend
) {
358 // Test that a replacement object throwing bad_alloc is handled.
360 Xapian::Registry reg
;
362 ExceptionalPostingSource
eps(ExceptionalPostingSource::NONE
);
363 TEST_EXCEPTION(Xapian::UnimplementedError
, eps
.serialise());
364 TEST_EXCEPTION(Xapian::UnimplementedError
, eps
.unserialise(string()));
365 reg
.register_posting_source(eps
);
367 ExceptionalPostingSource
eps_clone(ExceptionalPostingSource::CLONE
);
368 reg
.register_posting_source(eps_clone
);
369 FAIL_TEST("Expected bad_alloc exception to be thrown");
370 } catch (const bad_alloc
&) {
373 // Either the old entry should be removed, or it should work.
374 const Xapian::PostingSource
* p
;
375 p
= reg
.get_posting_source("ExceptionalPostingSource");
377 TEST_EQUAL(p
->name(), "ExceptionalPostingSource");
382 class ExceptionalWeight
: public Xapian::Weight
{
384 typedef enum { NONE
, CLONE
} failmode
;
388 ExceptionalWeight(failmode fail_
) : fail(fail_
) { }
390 string
name() const override
{
391 return "exceptional";
394 Weight
* clone() const override
{
397 return new ExceptionalWeight(fail
);
400 void init(double) override
{ }
402 double get_sumpart(Xapian::termcount
,
405 Xapian::termcount
) const override
{
408 double get_maxpart() const override
{ return 0; }
411 /// Check that exceptions when registering are handled well.
412 DEFINE_TESTCASE(registry2
, !backend
) {
413 // Test that a replacement object throwing bad_alloc is handled.
415 Xapian::Registry reg
;
417 ExceptionalWeight
ewt(ExceptionalWeight::NONE
);
418 reg
.register_weighting_scheme(ewt
);
420 ExceptionalWeight
ewt_clone(ExceptionalWeight::CLONE
);
421 reg
.register_weighting_scheme(ewt_clone
);
422 FAIL_TEST("Expected bad_alloc exception to be thrown");
423 } catch (const bad_alloc
&) {
426 // Either the old entry should be removed, or it should work.
427 const Xapian::Weight
* p
;
428 p
= reg
.get_weighting_scheme("ExceptionalWeight");
430 TEST_EQUAL(p
->name(), "ExceptionalWeight");
435 class ExceptionalMatchSpy
: public Xapian::MatchSpy
{
437 typedef enum { NONE
, CLONE
} failmode
;
441 ExceptionalMatchSpy(failmode fail_
) : fail(fail_
) { }
443 string
name() const override
{
444 return "ExceptionalMatchSpy";
447 MatchSpy
* clone() const override
{
450 return new ExceptionalMatchSpy(fail
);
453 void operator()(const Xapian::Document
&, double) override
{
457 /// Check that exceptions when registering are handled well.
458 DEFINE_TESTCASE(registry3
, !backend
) {
459 // Test that a replacement object throwing bad_alloc is handled.
461 Xapian::Registry reg
;
463 ExceptionalMatchSpy
ems(ExceptionalMatchSpy::NONE
);
464 reg
.register_match_spy(ems
);
466 ExceptionalMatchSpy
ems_clone(ExceptionalMatchSpy::CLONE
);
467 reg
.register_match_spy(ems_clone
);
468 FAIL_TEST("Expected bad_alloc exception to be thrown");
469 } catch (const bad_alloc
&) {
472 // Either the old entry should be removed, or it should work.
473 const Xapian::MatchSpy
* p
;
474 p
= reg
.get_match_spy("ExceptionalMatchSpy");
476 TEST_EQUAL(p
->name(), "ExceptionalMatchSpy");