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");
109 // Test for serialising a document obtained from a database.
110 DEFINE_TESTCASE(serialise_document2
, writable
) {
111 Xapian::Document origdoc
;
112 origdoc
.add_term("foo", 2);
113 origdoc
.add_posting("foo", 10);
114 origdoc
.add_value(1, "bar");
115 origdoc
.set_data("baz");
116 Xapian::WritableDatabase db
= get_writable_database();
117 db
.add_document(origdoc
);
119 Xapian::Document doc
= db
.get_document(1);
121 Xapian::Document doc2
= Xapian::Document::unserialise(doc
.serialise());
123 TEST_EQUAL(doc
.termlist_count(), doc2
.termlist_count());
124 TEST_EQUAL(doc
.termlist_count(), 1);
125 Xapian::TermIterator i
;
126 Xapian::PositionIterator j
;
127 Xapian::ValueIterator k
;
129 i
= doc
.termlist_begin();
130 TEST_NOT_EQUAL(i
, doc
.termlist_end());
131 TEST_EQUAL(i
.get_wdf(), 3);
132 TEST_EQUAL(*i
, "foo");
133 TEST_EQUAL(i
.positionlist_count(), 1);
134 j
= i
.positionlist_begin();
135 TEST_NOT_EQUAL(j
, i
.positionlist_end());
138 TEST_EQUAL(j
, i
.positionlist_end());
140 TEST_EQUAL(i
, doc
.termlist_end());
142 TEST_EQUAL(doc
.values_count(), 1);
143 k
= doc
.values_begin();
144 TEST_NOT_EQUAL(k
, doc
.values_end());
145 TEST_EQUAL(k
.get_valueno(), 1);
146 TEST_EQUAL(*k
, "bar");
148 TEST_EQUAL(k
, doc
.values_end());
150 TEST_EQUAL(doc
.get_data(), "baz");
152 i
= doc2
.termlist_begin();
153 TEST_NOT_EQUAL(i
, doc2
.termlist_end());
154 TEST_EQUAL(i
.get_wdf(), 3);
155 TEST_EQUAL(*i
, "foo");
156 TEST_EQUAL(i
.positionlist_count(), 1);
157 j
= i
.positionlist_begin();
158 TEST_NOT_EQUAL(j
, i
.positionlist_end());
161 TEST_EQUAL(j
, i
.positionlist_end());
163 TEST_EQUAL(i
, doc2
.termlist_end());
165 TEST_EQUAL(doc2
.values_count(), 1);
166 k
= doc2
.values_begin();
167 TEST_NOT_EQUAL(k
, doc2
.values_end());
168 TEST_EQUAL(k
.get_valueno(), 1);
169 TEST_EQUAL(*k
, "bar");
171 TEST_EQUAL(k
, doc2
.values_end());
173 TEST_EQUAL(doc2
.get_data(), "baz");
176 // Test for serialising a query
177 DEFINE_TESTCASE(serialise_query1
, !backend
) {
179 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise());
180 TEST_EQUAL(q
.get_description(), q2
.get_description());
181 TEST_EQUAL(q
.get_description(), "Query()");
183 q
= Xapian::Query("hello");
184 q2
= Xapian::Query::unserialise(q
.serialise());
185 TEST_EQUAL(q
.get_description(), q2
.get_description());
186 TEST_EQUAL(q
.get_description(), "Query(hello)");
188 q
= Xapian::Query("hello", 1, 1);
189 q2
= Xapian::Query::unserialise(q
.serialise());
190 // Regression test for fix in Xapian 1.0.0.
191 TEST_EQUAL(q
.get_description(), q2
.get_description());
192 TEST_EQUAL(q
.get_description(), "Query(hello@1)");
194 q
= Xapian::Query(q
.OP_OR
, Xapian::Query("hello"), Xapian::Query("world"));
195 q2
= Xapian::Query::unserialise(q
.serialise());
196 TEST_EQUAL(q
.get_description(), q2
.get_description());
197 TEST_EQUAL(q
.get_description(), "Query((hello OR world))");
199 q
= Xapian::Query(q
.OP_OR
,
200 Xapian::Query("hello", 1, 1),
201 Xapian::Query("world", 1, 1));
202 q2
= Xapian::Query::unserialise(q
.serialise());
203 TEST_EQUAL(q
.get_description(), q2
.get_description());
204 TEST_EQUAL(q
.get_description(), "Query((hello@1 OR world@1))");
206 static const char * const phrase
[] = { "shaken", "not", "stirred" };
207 q
= Xapian::Query(q
.OP_PHRASE
, phrase
, phrase
+ 3);
208 q
= Xapian::Query(q
.OP_OR
, Xapian::Query("007"), q
);
209 q
= Xapian::Query(q
.OP_SCALE_WEIGHT
, q
, 3.14);
210 q2
= Xapian::Query::unserialise(q
.serialise());
211 TEST_EQUAL(q
.get_description(), q2
.get_description());
214 // Test for serialising a query which contains a PostingSource.
215 DEFINE_TESTCASE(serialise_query2
, !backend
) {
216 Xapian::ValueWeightPostingSource
s1(10);
217 Xapian::Query
q(&s1
);
218 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise());
219 TEST_EQUAL(q
.get_description(), q2
.get_description());
220 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueWeightPostingSource(slot=10)))");
222 Xapian::ValueMapPostingSource
s2(11);
223 s2
.set_default_weight(5.0);
224 q
= Xapian::Query(&s2
);
225 q2
= Xapian::Query::unserialise(q
.serialise());
226 TEST_EQUAL(q
.get_description(), q2
.get_description());
227 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueMapPostingSource(slot=11)))");
229 Xapian::FixedWeightPostingSource
s3(5.5);
230 q
= Xapian::Query(&s3
);
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::FixedWeightPostingSource(wt=5.5)))");
236 // Test for unserialising a query using the default registry.
237 DEFINE_TESTCASE(serialise_query3
, !backend
) {
238 Xapian::ValueWeightPostingSource
s1(10);
239 Xapian::Query
q(&s1
);
240 Xapian::Registry reg
;
241 Xapian::Query q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
242 TEST_EQUAL(q
.get_description(), q2
.get_description());
243 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueWeightPostingSource(slot=10)))");
245 Xapian::ValueMapPostingSource
s2(11);
246 s2
.set_default_weight(5.0);
247 q
= Xapian::Query(&s2
);
248 q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
249 TEST_EQUAL(q
.get_description(), q2
.get_description());
250 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::ValueMapPostingSource(slot=11)))");
252 Xapian::FixedWeightPostingSource
s3(5.5);
253 q
= Xapian::Query(&s3
);
254 q2
= Xapian::Query::unserialise(q
.serialise(), reg
);
255 TEST_EQUAL(q
.get_description(), q2
.get_description());
256 TEST_EQUAL(q
.get_description(), "Query(PostingSource(Xapian::FixedWeightPostingSource(wt=5.5)))");
259 class MyPostingSource2
: public Xapian::ValuePostingSource
{
262 MyPostingSource2(const std::string
& desc_
)
263 : Xapian::ValuePostingSource(0), desc(desc_
)
267 MyPostingSource2
* clone() const
269 return new MyPostingSource2(desc
);
272 std::string
name() const {
273 return "MyPostingSource2";
276 std::string
serialise() const {
280 MyPostingSource2
* unserialise(const std::string
& s
) const {
281 return new MyPostingSource2(s
);
284 double get_weight() const { return 1.0; }
286 std::string
get_description() const {
287 return "MyPostingSource2(" + desc
+ ")";
291 // Test for unserialising a query which contains a custom PostingSource.
292 DEFINE_TESTCASE(serialise_query4
, !backend
) {
293 MyPostingSource2
s1("foo");
294 Xapian::Query
q(&s1
);
295 TEST_EQUAL(q
.get_description(), "Query(PostingSource(MyPostingSource2(foo)))");
296 std::string serialised
= q
.serialise();
298 TEST_EXCEPTION(Xapian::SerialisationError
, Xapian::Query::unserialise(serialised
));
299 Xapian::Registry reg
;
300 TEST_EXCEPTION(Xapian::SerialisationError
, Xapian::Query::unserialise(serialised
, reg
));
302 reg
.register_posting_source(s1
);
303 Xapian::Query q2
= Xapian::Query::unserialise(serialised
, reg
);
304 TEST_EQUAL(q
.get_description(), q2
.get_description());
307 /// Test for memory leaks when registering posting sources or weights twice.
308 DEFINE_TESTCASE(double_register_leak
, !backend
) {
309 MyPostingSource2
s1("foo");
310 Xapian::BM25Weight w1
;
312 Xapian::Registry reg
;
313 reg
.register_posting_source(s1
);
314 reg
.register_posting_source(s1
);
315 reg
.register_posting_source(s1
);
317 reg
.register_weighting_scheme(w1
);
318 reg
.register_weighting_scheme(w1
);
319 reg
.register_weighting_scheme(w1
);
322 class ExceptionalPostingSource
: public Xapian::PostingSource
{
324 typedef enum { NONE
, CLONE
} failmode
;
328 ExceptionalPostingSource(failmode fail_
) : fail(fail_
) { }
330 string
name() const {
331 return "ExceptionalPostingSource";
334 PostingSource
* clone() const {
337 return new ExceptionalPostingSource(fail
);
340 void init(const Xapian::Database
&) { }
342 Xapian::doccount
get_termfreq_min() const { return 0; }
343 Xapian::doccount
get_termfreq_est() const { return 1; }
344 Xapian::doccount
get_termfreq_max() const { return 2; }
346 void next(double) { }
348 void skip_to(Xapian::docid
, double) { }
350 bool at_end() const { return true; }
351 Xapian::docid
get_docid() const { return 0; }
354 /// Check that exceptions when registering a postingsource are handled well.
355 DEFINE_TESTCASE(registry1
, !backend
) {
356 // Test that a replacement object throwing bad_alloc is handled.
358 Xapian::Registry reg
;
360 ExceptionalPostingSource
eps(ExceptionalPostingSource::NONE
);
361 TEST_EXCEPTION(Xapian::UnimplementedError
, eps
.serialise());
362 TEST_EXCEPTION(Xapian::UnimplementedError
, eps
.unserialise(string()));
363 reg
.register_posting_source(eps
);
365 ExceptionalPostingSource
eps_clone(ExceptionalPostingSource::CLONE
);
366 reg
.register_posting_source(eps_clone
);
367 FAIL_TEST("Expected bad_alloc exception to be thrown");
368 } catch (const bad_alloc
&) {
371 // Either the old entry should be removed, or it should work.
372 const Xapian::PostingSource
* p
;
373 p
= reg
.get_posting_source("ExceptionalPostingSource");
375 TEST_EQUAL(p
->name(), "ExceptionalPostingSource");
380 class ExceptionalWeight
: public Xapian::Weight
{
382 typedef enum { NONE
, CLONE
} failmode
;
386 ExceptionalWeight(failmode fail_
) : fail(fail_
) { }
388 string
name() const {
389 return "ExceptionalWeight";
392 Weight
* clone() const {
395 return new ExceptionalWeight(fail
);
398 void init(double) { }
400 double get_sumpart(Xapian::termcount
, Xapian::termcount
, Xapian::termcount
) const {
403 double get_maxpart() const { return 0; }
405 double get_sumextra(Xapian::termcount
, Xapian::termcount
) const { return 0; }
406 double get_maxextra() const { return 0; }
409 /// Check that exceptions when registering are handled well.
410 DEFINE_TESTCASE(registry2
, !backend
) {
411 // Test that a replacement object throwing bad_alloc is handled.
413 Xapian::Registry reg
;
415 ExceptionalWeight
ewt(ExceptionalWeight::NONE
);
416 reg
.register_weighting_scheme(ewt
);
418 ExceptionalWeight
ewt_clone(ExceptionalWeight::CLONE
);
419 reg
.register_weighting_scheme(ewt_clone
);
420 FAIL_TEST("Expected bad_alloc exception to be thrown");
421 } catch (const bad_alloc
&) {
424 // Either the old entry should be removed, or it should work.
425 const Xapian::Weight
* p
;
426 p
= reg
.get_weighting_scheme("ExceptionalWeight");
428 TEST_EQUAL(p
->name(), "ExceptionalWeight");
433 class ExceptionalMatchSpy
: public Xapian::MatchSpy
{
435 typedef enum { NONE
, CLONE
} failmode
;
439 ExceptionalMatchSpy(failmode fail_
) : fail(fail_
) { }
441 string
name() const {
442 return "ExceptionalMatchSpy";
445 MatchSpy
* clone() const {
448 return new ExceptionalMatchSpy(fail
);
451 void operator()(const Xapian::Document
&, double) {
455 /// Check that exceptions when registering are handled well.
456 DEFINE_TESTCASE(registry3
, !backend
) {
457 // Test that a replacement object throwing bad_alloc is handled.
459 Xapian::Registry reg
;
461 ExceptionalMatchSpy
ems(ExceptionalMatchSpy::NONE
);
462 reg
.register_match_spy(ems
);
464 ExceptionalMatchSpy
ems_clone(ExceptionalMatchSpy::CLONE
);
465 reg
.register_match_spy(ems_clone
);
466 FAIL_TEST("Expected bad_alloc exception to be thrown");
467 } catch (const bad_alloc
&) {
470 // Either the old entry should be removed, or it should work.
471 const Xapian::MatchSpy
* p
;
472 p
= reg
.get_match_spy("ExceptionalMatchSpy");
474 TEST_EQUAL(p
->name(), "ExceptionalMatchSpy");