Update for 1.4.20
[xapian.git] / xapian-core / tests / api_serialise.cc
blobd69074bf1e605865dcdecea8623f0e2f1df077ff
1 /** @file
2 * @brief Tests of serialisation functionality.
3 */
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
22 #include <config.h>
24 #include "api_serialise.h"
26 #include <xapian.h>
28 #include <exception>
29 #include <stdexcept>
31 #include "apitest.h"
32 #include "testutils.h"
34 using namespace std;
36 // Test for serialising a document
37 DEFINE_TESTCASE(serialise_document1, !backend) {
38 Xapian::Document doc;
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");
52 doc.set_data("baz");
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());
69 TEST_EQUAL(*j, 10);
70 ++j;
71 TEST_EQUAL(j, i.positionlist_end());
72 ++i;
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");
80 ++k;
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());
92 TEST_EQUAL(*j, 10);
93 ++j;
94 TEST_EQUAL(j, i.positionlist_end());
95 ++i;
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");
103 ++k;
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());
136 TEST_EQUAL(*j, 10);
137 ++j;
138 TEST_EQUAL(j, i.positionlist_end());
139 ++i;
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");
147 ++k;
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());
159 TEST_EQUAL(*j, 10);
160 ++j;
161 TEST_EQUAL(j, i.positionlist_end());
162 ++i;
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");
170 ++k;
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) {
178 Xapian::Query q;
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 {
260 std::string desc;
261 public:
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 {
277 return desc;
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 {
323 public:
324 typedef enum { NONE, CLONE } failmode;
326 failmode fail;
328 ExceptionalPostingSource(failmode fail_) : fail(fail_) { }
330 string name() const {
331 return "ExceptionalPostingSource";
334 PostingSource * clone() const {
335 if (fail == CLONE)
336 throw bad_alloc();
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);
364 try {
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");
374 if (p) {
375 TEST_EQUAL(p->name(), "ExceptionalPostingSource");
380 class ExceptionalWeight : public Xapian::Weight {
381 public:
382 typedef enum { NONE, CLONE } failmode;
384 failmode fail;
386 ExceptionalWeight(failmode fail_) : fail(fail_) { }
388 string name() const {
389 return "ExceptionalWeight";
392 Weight * clone() const {
393 if (fail == CLONE)
394 throw bad_alloc();
395 return new ExceptionalWeight(fail);
398 void init(double) { }
400 double get_sumpart(Xapian::termcount, Xapian::termcount, Xapian::termcount) const {
401 return 0;
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);
417 try {
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");
427 if (p) {
428 TEST_EQUAL(p->name(), "ExceptionalWeight");
433 class ExceptionalMatchSpy : public Xapian::MatchSpy {
434 public:
435 typedef enum { NONE, CLONE } failmode;
437 failmode fail;
439 ExceptionalMatchSpy(failmode fail_) : fail(fail_) { }
441 string name() const {
442 return "ExceptionalMatchSpy";
445 MatchSpy * clone() const {
446 if (fail == CLONE)
447 throw bad_alloc();
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);
463 try {
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");
473 if (p) {
474 TEST_EQUAL(p->name(), "ExceptionalMatchSpy");