1 /** @file api_serialise.cc
2 * @brief Tests of serialisation functionality.
4 /* Copyright 2009 Lemur Consulting Ltd
5 * Copyright 2009,2011,2012,2013 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");
111 // Test for serialising a document obtained from a database.
112 DEFINE_TESTCASE(serialise_document2
, writable
) {
113 Xapian::Document origdoc
;
114 origdoc
.add_term("foo", 2);
115 origdoc
.add_posting("foo", 10);
116 origdoc
.add_value(1, "bar");
117 origdoc
.set_data("baz");
118 Xapian::WritableDatabase db
= get_writable_database();
119 db
.add_document(origdoc
);
121 Xapian::Document doc
= db
.get_document(1);
123 Xapian::Document doc2
= Xapian::Document::unserialise(doc
.serialise());
125 TEST_EQUAL(doc
.termlist_count(), doc2
.termlist_count());
126 TEST_EQUAL(doc
.termlist_count(), 1);
127 Xapian::TermIterator i
;
128 Xapian::PositionIterator j
;
129 Xapian::ValueIterator k
;
131 i
= doc
.termlist_begin();
132 TEST_NOT_EQUAL(i
, doc
.termlist_end());
133 TEST_EQUAL(i
.get_wdf(), 3);
134 TEST_EQUAL(*i
, "foo");
135 TEST_EQUAL(i
.positionlist_count(), 1);
136 j
= i
.positionlist_begin();
137 TEST_NOT_EQUAL(j
, i
.positionlist_end());
140 TEST_EQUAL(j
, i
.positionlist_end());
142 TEST_EQUAL(i
, doc
.termlist_end());
144 TEST_EQUAL(doc
.values_count(), 1);
145 k
= doc
.values_begin();
146 TEST_NOT_EQUAL(k
, doc
.values_end());
147 TEST_EQUAL(k
.get_valueno(), 1);
148 TEST_EQUAL(*k
, "bar");
150 TEST_EQUAL(k
, doc
.values_end());
152 TEST_EQUAL(doc
.get_data(), "baz");
154 i
= doc2
.termlist_begin();
155 TEST_NOT_EQUAL(i
, doc2
.termlist_end());
156 TEST_EQUAL(i
.get_wdf(), 3);
157 TEST_EQUAL(*i
, "foo");
158 TEST_EQUAL(i
.positionlist_count(), 1);
159 j
= i
.positionlist_begin();
160 TEST_NOT_EQUAL(j
, i
.positionlist_end());
163 TEST_EQUAL(j
, i
.positionlist_end());
165 TEST_EQUAL(i
, doc2
.termlist_end());
167 TEST_EQUAL(doc2
.values_count(), 1);
168 k
= doc2
.values_begin();
169 TEST_NOT_EQUAL(k
, doc2
.values_end());
170 TEST_EQUAL(k
.get_valueno(), 1);
171 TEST_EQUAL(*k
, "bar");
173 TEST_EQUAL(k
, doc2
.values_end());
175 TEST_EQUAL(doc2
.get_data(), "baz");
180 // Test for serialising a query
181 DEFINE_TESTCASE(serialise_query1
, !backend
) {
183 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise());
184 TEST_EQUAL(q
.get_description(), q2
.get_description());
185 TEST_EQUAL(q
.get_description(), "Query()");
187 q
= Xapian::Query("hello");
188 q2
= Xapian::Query::unserialise(q
.serialise());
189 TEST_EQUAL(q
.get_description(), q2
.get_description());
190 TEST_EQUAL(q
.get_description(), "Query(hello)");
192 q
= Xapian::Query("hello", 1, 1);
193 q2
= Xapian::Query::unserialise(q
.serialise());
194 // Regression test for fix in Xapian 1.0.0.
195 TEST_EQUAL(q
.get_description(), q2
.get_description());
196 TEST_EQUAL(q
.get_description(), "Query(hello@1)");
198 q
= Xapian::Query(q
.OP_OR
, Xapian::Query("hello"), Xapian::Query("world"));
199 q2
= Xapian::Query::unserialise(q
.serialise());
200 TEST_EQUAL(q
.get_description(), q2
.get_description());
201 TEST_EQUAL(q
.get_description(), "Query((hello OR world))");
203 q
= Xapian::Query(q
.OP_OR
,
204 Xapian::Query("hello", 1, 1),
205 Xapian::Query("world", 1, 1));
206 q2
= Xapian::Query::unserialise(q
.serialise());
207 TEST_EQUAL(q
.get_description(), q2
.get_description());
208 TEST_EQUAL(q
.get_description(), "Query((hello@1 OR world@1))");
210 static const char * phrase
[] = { "shaken", "not", "stirred" };
211 q
= Xapian::Query(q
.OP_PHRASE
, phrase
, phrase
+ 3);
212 q
= Xapian::Query(q
.OP_OR
, Xapian::Query("007"), q
);
213 q
= Xapian::Query(q
.OP_SCALE_WEIGHT
, q
, 3.14);
214 q2
= Xapian::Query::unserialise(q
.serialise());
215 TEST_EQUAL(q
.get_description(), q2
.get_description());
220 // Test for serialising a query which contains a PostingSource.
221 DEFINE_TESTCASE(serialise_query2
, !backend
) {
222 Xapian::ValueWeightPostingSource
s1(10);
223 Xapian::Query
q(&s1
);
224 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise());
225 TEST_EQUAL(q
.get_description(), q2
.get_description());
226 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueWeightPostingSource(slot=10)))");
228 Xapian::ValueMapPostingSource
s2(11);
229 s2
.set_default_weight(5.0);
230 q
= Xapian::Query(&s2
);
231 q2
= Xapian::Query::unserialise(q
.serialise());
232 TEST_EQUAL(q
.get_description(), q2
.get_description());
233 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueMapPostingSource(slot=11)))");
235 Xapian::FixedWeightPostingSource
s3(5.5);
236 q
= Xapian::Query(&s3
);
237 q2
= Xapian::Query::unserialise(q
.serialise());
238 TEST_EQUAL(q
.get_description(), q2
.get_description());
239 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::FixedWeightPostingSource(wt=5.5)))");
244 // Test for unserialising a query using the default registry.
245 DEFINE_TESTCASE(serialise_query3
, !backend
) {
246 Xapian::ValueWeightPostingSource
s1(10);
247 Xapian::Query
q(&s1
);
248 Xapian::Registry reg
;
249 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
250 TEST_EQUAL(q
.get_description(), q2
.get_description());
251 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueWeightPostingSource(slot=10)))");
253 Xapian::ValueMapPostingSource
s2(11);
254 s2
.set_default_weight(5.0);
255 q
= Xapian::Query(&s2
);
256 q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
257 TEST_EQUAL(q
.get_description(), q2
.get_description());
258 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueMapPostingSource(slot=11)))");
260 Xapian::FixedWeightPostingSource
s3(5.5);
261 q
= Xapian::Query(&s3
);
262 q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
263 TEST_EQUAL(q
.get_description(), q2
.get_description());
264 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::FixedWeightPostingSource(wt=5.5)))");
269 class MyPostingSource2
: public Xapian::ValuePostingSource
{
272 MyPostingSource2(const std::string
& desc_
)
273 : Xapian::ValuePostingSource(0), desc(desc_
)
277 MyPostingSource2
* clone() const
279 return new MyPostingSource2(desc
);
282 std::string
name() const {
283 return "MyPostingSource2";
286 std::string
serialise() const {
290 MyPostingSource2
* unserialise(const std::string
& s
) const {
291 return new MyPostingSource2(s
);
294 double get_weight() const { return 1.0; }
296 std::string
get_description() const {
297 return "MyPostingSource2(" + desc
+ ")";
301 // Test for unserialising a query which contains a custom PostingSource.
302 DEFINE_TESTCASE(serialise_query4
, !backend
) {
303 MyPostingSource2
s1("foo");
304 Xapian::Query
q(&s1
);
305 TEST_EQUAL(q
.get_description(), "Query(PostingSource(MyPostingSource2(foo)))");
306 std::string serialised
= q
.serialise();
308 TEST_EXCEPTION(Xapian::SerialisationError
, Xapian::Query::unserialise(serialised
));
309 Xapian::Registry reg
;
310 TEST_EXCEPTION(Xapian::SerialisationError
, Xapian::Query::unserialise(serialised
, reg
));
312 reg
.register_posting_source(s1
);
313 Xapian::Query q2
= Xapian::Query::unserialise(serialised
, reg
);
314 TEST_EQUAL(q
.get_description(), q2
.get_description());
319 /// Test for memory leaks when registering posting sources or weights twice.
320 DEFINE_TESTCASE(double_register_leak
, !backend
) {
321 MyPostingSource2
s1("foo");
322 Xapian::BM25Weight w1
;
324 Xapian::Registry reg
;
325 reg
.register_posting_source(s1
);
326 reg
.register_posting_source(s1
);
327 reg
.register_posting_source(s1
);
329 reg
.register_weighting_scheme(w1
);
330 reg
.register_weighting_scheme(w1
);
331 reg
.register_weighting_scheme(w1
);
336 class ExceptionalPostingSource
: public Xapian::PostingSource
{
338 typedef enum { NONE
, CLONE
} failmode
;
342 ExceptionalPostingSource(failmode fail_
) : fail(fail_
) { }
344 string
name() const {
345 return "ExceptionalPostingSource";
348 PostingSource
* clone() const {
351 return new ExceptionalPostingSource(fail
);
354 void init(const Xapian::Database
&) { }
356 Xapian::doccount
get_termfreq_min() const { return 0; }
357 Xapian::doccount
get_termfreq_est() const { return 1; }
358 Xapian::doccount
get_termfreq_max() const { return 2; }
360 void next(double) { }
362 void skip_to(Xapian::docid
, double) { }
364 bool at_end() const { return true; }
365 Xapian::docid
get_docid() const { return 0; }
368 /// Check that exceptions when registering a postingsource are handled well.
369 DEFINE_TESTCASE(registry1
, !backend
) {
370 // Test that a replacement object throwing bad_alloc is handled.
372 Xapian::Registry reg
;
374 ExceptionalPostingSource
eps(ExceptionalPostingSource::NONE
);
375 TEST_EXCEPTION(Xapian::UnimplementedError
, eps
.serialise());
376 TEST_EXCEPTION(Xapian::UnimplementedError
, eps
.unserialise(string()));
377 reg
.register_posting_source(eps
);
379 ExceptionalPostingSource
eps_clone(ExceptionalPostingSource::CLONE
);
380 reg
.register_posting_source(eps_clone
);
382 } catch (const bad_alloc
&) {
385 // Either the old entry should be removed, or it should work.
386 const Xapian::PostingSource
* p
;
387 p
= reg
.get_posting_source("ExceptionalPostingSource");
389 TEST_EQUAL(p
->name(), "ExceptionalPostingSource");
396 class ExceptionalWeight
: public Xapian::Weight
{
398 typedef enum { NONE
, CLONE
} failmode
;
402 ExceptionalWeight(failmode fail_
) : fail(fail_
) { }
404 string
name() const {
405 return "ExceptionalWeight";
408 Weight
* clone() const {
411 return new ExceptionalWeight(fail
);
414 void init(double) { }
416 double get_sumpart(Xapian::termcount
, Xapian::termcount
, Xapian::termcount
) const {
419 double get_maxpart() const { return 0; }
421 double get_sumextra(Xapian::termcount
, Xapian::termcount
) const { return 0; }
422 double get_maxextra() const { return 0; }
425 /// Check that exceptions when registering are handled well.
426 DEFINE_TESTCASE(registry2
, !backend
) {
427 // Test that a replacement object throwing bad_alloc is handled.
429 Xapian::Registry reg
;
431 ExceptionalWeight
ewt(ExceptionalWeight::NONE
);
432 reg
.register_weighting_scheme(ewt
);
434 ExceptionalWeight
ewt_clone(ExceptionalWeight::CLONE
);
435 reg
.register_weighting_scheme(ewt_clone
);
437 } catch (const bad_alloc
&) {
440 // Either the old entry should be removed, or it should work.
441 const Xapian::Weight
* p
;
442 p
= reg
.get_weighting_scheme("ExceptionalWeight");
444 TEST_EQUAL(p
->name(), "ExceptionalWeight");
451 class ExceptionalMatchSpy
: public Xapian::MatchSpy
{
453 typedef enum { NONE
, CLONE
} failmode
;
457 ExceptionalMatchSpy(failmode fail_
) : fail(fail_
) { }
459 string
name() const {
460 return "ExceptionalMatchSpy";
463 MatchSpy
* clone() const {
466 return new ExceptionalMatchSpy(fail
);
469 void operator()(const Xapian::Document
&, double) {
473 /// Check that exceptions when registering are handled well.
474 DEFINE_TESTCASE(registry3
, !backend
) {
475 // Test that a replacement object throwing bad_alloc is handled.
477 Xapian::Registry reg
;
479 ExceptionalMatchSpy
ems(ExceptionalMatchSpy::NONE
);
480 reg
.register_match_spy(ems
);
482 ExceptionalMatchSpy
ems_clone(ExceptionalMatchSpy::CLONE
);
483 reg
.register_match_spy(ems_clone
);
485 } catch (const bad_alloc
&) {
488 // Either the old entry should be removed, or it should work.
489 const Xapian::MatchSpy
* p
;
490 p
= reg
.get_match_spy("ExceptionalMatchSpy");
492 TEST_EQUAL(p
->name(), "ExceptionalMatchSpy");