Don't force the user to specify the metric
[xapian.git] / xapian-core / tests / api_serialise.cc
blobece3931fa35b328def4a0887de19ff23a80e36b9
1 /** @file api_serialise.cc
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");
108 return true;
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());
138 TEST_EQUAL(*j, 10);
139 ++j;
140 TEST_EQUAL(j, i.positionlist_end());
141 ++i;
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");
149 ++k;
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());
161 TEST_EQUAL(*j, 10);
162 ++j;
163 TEST_EQUAL(j, i.positionlist_end());
164 ++i;
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");
172 ++k;
173 TEST_EQUAL(k, doc2.values_end());
175 TEST_EQUAL(doc2.get_data(), "baz");
177 return true;
180 // Test for serialising a query
181 DEFINE_TESTCASE(serialise_query1, !backend) {
182 Xapian::Query q;
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());
217 return true;
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)))");
241 return true;
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)))");
266 return true;
269 class MyPostingSource2 : public Xapian::ValuePostingSource {
270 std::string desc;
271 public:
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 {
287 return desc;
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());
316 return true;
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);
333 return true;
336 class ExceptionalPostingSource : public Xapian::PostingSource {
337 public:
338 typedef enum { NONE, CLONE } failmode;
340 failmode fail;
342 ExceptionalPostingSource(failmode fail_) : fail(fail_) { }
344 string name() const {
345 return "ExceptionalPostingSource";
348 PostingSource * clone() const {
349 if (fail == CLONE)
350 throw bad_alloc();
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);
378 try {
379 ExceptionalPostingSource eps_clone(ExceptionalPostingSource::CLONE);
380 reg.register_posting_source(eps_clone);
381 return false;
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");
388 if (p) {
389 TEST_EQUAL(p->name(), "ExceptionalPostingSource");
393 return true;
396 class ExceptionalWeight : public Xapian::Weight {
397 public:
398 typedef enum { NONE, CLONE } failmode;
400 failmode fail;
402 ExceptionalWeight(failmode fail_) : fail(fail_) { }
404 string name() const {
405 return "ExceptionalWeight";
408 Weight * clone() const {
409 if (fail == CLONE)
410 throw bad_alloc();
411 return new ExceptionalWeight(fail);
414 void init(double) { }
416 double get_sumpart(Xapian::termcount, Xapian::termcount) const {
417 return 0;
419 double get_sumpart(Xapian::termcount, Xapian::termcount, Xapian::termcount) const {
420 return 0;
422 double get_maxpart() const { return 0; }
424 double get_sumextra(Xapian::termcount, Xapian::termcount) const { return 0; }
425 double get_maxextra() const { return 0; }
428 /// Check that exceptions when registering are handled well.
429 DEFINE_TESTCASE(registry2, !backend) {
430 // Test that a replacement object throwing bad_alloc is handled.
432 Xapian::Registry reg;
434 ExceptionalWeight ewt(ExceptionalWeight::NONE);
435 reg.register_weighting_scheme(ewt);
436 try {
437 ExceptionalWeight ewt_clone(ExceptionalWeight::CLONE);
438 reg.register_weighting_scheme(ewt_clone);
439 return false;
440 } catch (const bad_alloc &) {
443 // Either the old entry should be removed, or it should work.
444 const Xapian::Weight * p;
445 p = reg.get_weighting_scheme("ExceptionalWeight");
446 if (p) {
447 TEST_EQUAL(p->name(), "ExceptionalWeight");
451 return true;
454 class ExceptionalMatchSpy : public Xapian::MatchSpy {
455 public:
456 typedef enum { NONE, CLONE } failmode;
458 failmode fail;
460 ExceptionalMatchSpy(failmode fail_) : fail(fail_) { }
462 string name() const {
463 return "ExceptionalMatchSpy";
466 MatchSpy * clone() const {
467 if (fail == CLONE)
468 throw bad_alloc();
469 return new ExceptionalMatchSpy(fail);
472 void operator()(const Xapian::Document &, double) {
476 /// Check that exceptions when registering are handled well.
477 DEFINE_TESTCASE(registry3, !backend) {
478 // Test that a replacement object throwing bad_alloc is handled.
480 Xapian::Registry reg;
482 ExceptionalMatchSpy ems(ExceptionalMatchSpy::NONE);
483 reg.register_match_spy(ems);
484 try {
485 ExceptionalMatchSpy ems_clone(ExceptionalMatchSpy::CLONE);
486 reg.register_match_spy(ems_clone);
487 return false;
488 } catch (const bad_alloc &) {
491 // Either the old entry should be removed, or it should work.
492 const Xapian::MatchSpy * p;
493 p = reg.get_match_spy("ExceptionalMatchSpy");
494 if (p) {
495 TEST_EQUAL(p->name(), "ExceptionalMatchSpy");
499 return true;