[ci] Fix clang-santisers job for GHA change
[xapian.git] / xapian-core / tests / api_serialise.cc
blobd9a9ddfddcd6757643a93d9250ba71bf0bc32bb7
1 /** @file
2 * @brief Tests of serialisation functionality.
3 */
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
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, backend) {
111 Xapian::Database db = get_database("serialise_document2",
112 [](Xapian::WritableDatabase& wdb,
113 const string&) {
114 Xapian::Document doc;
115 doc.add_term("foo", 2);
116 doc.add_posting("foo", 10);
117 doc.add_value(1, "bar");
118 doc.set_data("baz");
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());
139 TEST_EQUAL(*j, 10);
140 ++j;
141 TEST_EQUAL(j, i.positionlist_end());
142 ++i;
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");
150 ++k;
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());
162 TEST_EQUAL(*j, 10);
163 ++j;
164 TEST_EQUAL(j, i.positionlist_end());
165 ++i;
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");
173 ++k;
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) {
181 Xapian::Query q;
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 {
263 std::string desc;
264 public:
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 {
279 return desc;
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 {
325 public:
326 typedef enum { NONE, CLONE } failmode;
328 failmode fail;
330 ExceptionalPostingSource(failmode fail_) : fail(fail_) { }
332 string name() const override {
333 return "ExceptionalPostingSource";
336 PostingSource* clone() const override {
337 if (fail == CLONE)
338 throw bad_alloc();
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);
366 try {
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");
376 if (p) {
377 TEST_EQUAL(p->name(), "ExceptionalPostingSource");
382 class ExceptionalWeight : public Xapian::Weight {
383 public:
384 typedef enum { NONE, CLONE } failmode;
386 failmode fail;
388 ExceptionalWeight(failmode fail_) : fail(fail_) { }
390 string name() const override {
391 return "exceptional";
394 Weight* clone() const override {
395 if (fail == CLONE)
396 throw bad_alloc();
397 return new ExceptionalWeight(fail);
400 void init(double) override { }
402 double get_sumpart(Xapian::termcount,
403 Xapian::termcount,
404 Xapian::termcount,
405 Xapian::termcount) const override {
406 return 0;
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);
419 try {
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");
429 if (p) {
430 TEST_EQUAL(p->name(), "ExceptionalWeight");
435 class ExceptionalMatchSpy : public Xapian::MatchSpy {
436 public:
437 typedef enum { NONE, CLONE } failmode;
439 failmode fail;
441 ExceptionalMatchSpy(failmode fail_) : fail(fail_) { }
443 string name() const override {
444 return "ExceptionalMatchSpy";
447 MatchSpy* clone() const override {
448 if (fail == CLONE)
449 throw bad_alloc();
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);
465 try {
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");
475 if (p) {
476 TEST_EQUAL(p->name(), "ExceptionalMatchSpy");