Support: quest -f cjk_ngram
[xapian.git] / xapian-core / tests / api_nodb.cc
blob1cc91f69d59cb76a6e4b5d4d67d2548744a52701
1 /* api_nodb.cc: tests which don't use any of the backends
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2010,2015 Olly Betts
6 * Copyright 2006 Lemur Consulting Ltd
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
24 #include <config.h>
26 #include "api_nodb.h"
28 #include <xapian.h>
30 #include "apitest.h"
31 #include "testsuite.h"
32 #include "testutils.h"
34 #include <list>
35 #include <string>
36 #include <vector>
38 using namespace std;
40 // always succeeds
41 DEFINE_TESTCASE(trivial1, !backend) {
42 return true;
45 // tests that get_query_terms() returns the terms in the right order
46 DEFINE_TESTCASE(getqterms1, !backend) {
47 list<string> answers_list;
48 answers_list.push_back("one");
49 answers_list.push_back("two");
50 answers_list.push_back("three");
51 answers_list.push_back("four");
53 Xapian::Query myquery(Xapian::Query::OP_OR,
54 Xapian::Query(Xapian::Query::OP_AND,
55 Xapian::Query("one", 1, 1),
56 Xapian::Query("three", 1, 3)),
57 Xapian::Query(Xapian::Query::OP_OR,
58 Xapian::Query("four", 1, 4),
59 Xapian::Query("two", 1, 2)));
61 list<string> list1;
63 Xapian::TermIterator t;
64 for (t = myquery.get_terms_begin(); t != myquery.get_terms_end(); ++t)
65 list1.push_back(*t);
67 TEST(list1 == answers_list);
68 list<string> list2(myquery.get_terms_begin(), myquery.get_terms_end());
69 TEST(list2 == answers_list);
70 return true;
73 // tests that get_query_terms() doesn't SEGV on an empty query
74 // (regression test for bug in 0.9.0)
75 DEFINE_TESTCASE(getqterms2, !backend) {
76 Xapian::Query empty_query;
77 TEST_EQUAL(empty_query.get_terms_begin(), empty_query.get_terms_end());
78 return true;
81 // tests that empty queries work correctly
82 DEFINE_TESTCASE(emptyquery2, !backend) {
83 // test that Query::empty() is true for an empty query.
84 TEST(Xapian::Query().empty());
85 // test that an empty query has length 0
86 TEST(Xapian::Query().get_length() == 0);
87 vector<Xapian::Query> v;
88 TEST(Xapian::Query(Xapian::Query::OP_OR, v.begin(), v.end()).empty());
89 TEST(Xapian::Query(Xapian::Query::OP_OR, v.begin(), v.end()).get_length() == 0);
90 return true;
93 /// Regression test for behaviour for an empty query with AND_NOT.
94 DEFINE_TESTCASE(emptyquery3, !backend) {
95 static const Xapian::Query::op ops[] = {
96 Xapian::Query::OP_AND,
97 Xapian::Query::OP_OR,
98 Xapian::Query::OP_XOR,
99 Xapian::Query::OP_AND_MAYBE,
100 Xapian::Query::OP_AND_NOT
103 for (size_t i = 0; i < sizeof(ops) / sizeof(ops[0]); ++i) {
104 tout << "Testing op #" << i << endl;
105 Xapian::Query empty;
106 Xapian::Query q("test");
107 Xapian::Query qcombine(ops[i], empty, q);
108 tout << qcombine.get_description() << endl;
109 Xapian::Query qcombine2(ops[i], q, empty);
110 tout << qcombine2.get_description() << endl;
111 Xapian::Query qcombine3(ops[i], empty, empty);
112 tout << qcombine3.get_description() << endl;
115 return true;
118 // tests that query lengths are calculated correctly
119 DEFINE_TESTCASE(querylen1, !backend) {
120 // test that a simple query has the right length
121 Xapian::Query myquery;
122 myquery = Xapian::Query(Xapian::Query::OP_OR,
123 Xapian::Query("foo"),
124 Xapian::Query("bar"));
125 myquery = Xapian::Query(Xapian::Query::OP_AND,
126 myquery,
127 Xapian::Query(Xapian::Query::OP_OR,
128 Xapian::Query("wibble"),
129 Xapian::Query("spoon")));
131 TEST_EQUAL(myquery.get_length(), 4);
132 TEST(!myquery.empty());
133 return true;
136 // tests that query lengths are calculated correctly
137 DEFINE_TESTCASE(querylen2, !backend) {
138 // test with an even bigger and strange query
139 string terms[3] = {
140 "foo",
141 "bar",
142 "baz"
144 Xapian::Query queries[3] = {
145 Xapian::Query("wibble"),
146 Xapian::Query("wobble"),
147 Xapian::Query(Xapian::Query::OP_OR, string("jelly"), string("belly"))
150 Xapian::Query myquery;
151 vector<string> v1(terms, terms + 3);
152 vector<Xapian::Query> v2(queries, queries + 3);
153 vector<Xapian::Query *> v3;
154 Xapian::Query query1(Xapian::Query::OP_AND, string("ball"), string("club"));
155 Xapian::Query query2("ring");
156 v3.push_back(&query1);
157 v3.push_back(&query2);
159 Xapian::Query myq1 = Xapian::Query(Xapian::Query::OP_AND, v1.begin(), v1.end());
160 tout << "myq1=" << myq1 << "\n";
161 TEST_EQUAL(myq1.get_length(), 3);
163 Xapian::Query myq2_1 = Xapian::Query(Xapian::Query::OP_OR, v2.begin(), v2.end());
164 tout << "myq2_1=" << myq2_1 << "\n";
165 TEST_EQUAL(myq2_1.get_length(), 4);
167 Xapian::Query myq2_2 = Xapian::Query(Xapian::Query::OP_AND, v3.begin(), v3.end());
168 tout << "myq2_2=" << myq2_2 << "\n";
169 TEST_EQUAL(myq2_2.get_length(), 3);
171 Xapian::Query myq2 = Xapian::Query(Xapian::Query::OP_OR, myq2_1, myq2_2);
172 tout << "myq2=" << myq2 << "\n";
173 TEST_EQUAL(myq2.get_length(), 7);
175 myquery = Xapian::Query(Xapian::Query::OP_OR, myq1, myq2);
176 tout << "myquery=" << myquery << "\n";
177 TEST_EQUAL(myquery.get_length(), 10);
179 return true;
182 // tests that queries validate correctly
183 DEFINE_TESTCASE(queryvalid1, !backend) {
184 Xapian::Query q2(Xapian::Query::OP_XOR, Xapian::Query("foo"), Xapian::Query("bar"));
185 tout << "XOR (\"foo\", \"bar\") checked" << endl;
186 return true;
189 /** Check we no longer flatten subqueries combined with the same operator.
191 * Prior to 1.3.0 we did flatten these, but it's simpler to just handle this
192 * when we convert the query to a PostList tree, and that works better with
193 * Query objects being immutable.
195 DEFINE_TESTCASE(dontflattensubqueries1, !backend) {
196 Xapian::Query queries1[3] = {
197 Xapian::Query("wibble"),
198 Xapian::Query("wobble"),
199 Xapian::Query(Xapian::Query::OP_OR, string("jelly"), string("belly"))
202 Xapian::Query queries2[3] = {
203 Xapian::Query(Xapian::Query::OP_AND, string("jelly"), string("belly")),
204 Xapian::Query("wibble"),
205 Xapian::Query("wobble")
208 vector<Xapian::Query> vec1(queries1, queries1 + 3);
209 Xapian::Query myquery1(Xapian::Query::OP_OR, vec1.begin(), vec1.end());
210 TEST_EQUAL(myquery1.get_description(),
211 "Query((wibble OR wobble OR (jelly OR belly)))");
213 vector<Xapian::Query> vec2(queries2, queries2 + 3);
214 Xapian::Query myquery2(Xapian::Query::OP_AND, vec2.begin(), vec2.end());
215 TEST_EQUAL(myquery2.get_description(),
216 "Query(((jelly AND belly) AND wibble AND wobble))");
218 return true;
221 // test behaviour when creating a query from an empty vector
222 DEFINE_TESTCASE(emptyquerypart1, !backend) {
223 vector<string> emptyterms;
224 Xapian::Query query(Xapian::Query::OP_OR, emptyterms.begin(), emptyterms.end());
225 TEST(Xapian::Query(Xapian::Query::OP_AND, query, Xapian::Query("x")).empty());
226 TEST(Xapian::Query(Xapian::Query::OP_AND, query, Xapian::Query("x")).get_length() == 0);
227 TEST(!Xapian::Query(Xapian::Query::OP_OR, query, Xapian::Query("x")).empty());
228 TEST(Xapian::Query(Xapian::Query::OP_OR, query, Xapian::Query("x")).get_length() == 1);
229 return true;
232 DEFINE_TESTCASE(stemlangs1, !backend) {
233 string langs = Xapian::Stem::get_available_languages();
234 tout << "available languages '" << langs << "'" << endl;
235 TEST(!langs.empty());
237 // Also test the language codes and none.
238 langs += " da nl en fi fr de hu it no pt ro ru es sv tr none";
240 string::size_type i = 0;
241 while (true) {
242 string::size_type spc = langs.find(' ', i);
243 // The only spaces in langs should be a single one between each pair
244 // of language names.
245 TEST_NOT_EQUAL(i, spc);
247 // Try making a stemmer for this language. We should be able to create
248 // it without an exception being thrown.
249 string language(langs, i, spc - i);
250 tout << "checking language code '" << language << "' works" << endl;
251 Xapian::Stem stemmer(language);
252 if (language.size() > 2) {
253 string expected("Xapian::Stem(");
254 expected += language;
255 expected += ')';
256 TEST_EQUAL(stemmer.get_description(), expected);
259 if (spc == string::npos) break;
260 i = spc + 1;
263 // Stem("") should give an object which doesn't change any input.
264 Xapian::Stem stem_nothing = Xapian::Stem("");
265 TEST_EQUAL(stem_nothing.get_description(), "Xapian::Stem(none)");
267 return true;
270 // Some simple tests of the built in weighting schemes.
271 DEFINE_TESTCASE(weight1, !backend) {
272 Xapian::Weight * wt;
274 Xapian::BoolWeight boolweight;
275 TEST_EQUAL(boolweight.name(), "Xapian::BoolWeight");
276 wt = Xapian::BoolWeight().unserialise(boolweight.serialise());
277 TEST_EQUAL(boolweight.serialise(), wt->serialise());
278 delete wt;
280 Xapian::TradWeight tradweight_dflt;
281 Xapian::TradWeight tradweight(1.0);
282 TEST_EQUAL(tradweight.name(), "Xapian::TradWeight");
283 TEST_EQUAL(tradweight_dflt.serialise(), tradweight.serialise());
284 wt = Xapian::TradWeight().unserialise(tradweight.serialise());
285 TEST_EQUAL(tradweight.serialise(), wt->serialise());
286 delete wt;
288 Xapian::TradWeight tradweight2(2.0);
289 TEST_NOT_EQUAL(tradweight.serialise(), tradweight2.serialise());
291 Xapian::BM25Weight bm25weight_dflt;
292 Xapian::BM25Weight bm25weight(1, 0, 1, 0.5, 0.5);
293 TEST_EQUAL(bm25weight.name(), "Xapian::BM25Weight");
294 TEST_EQUAL(bm25weight_dflt.serialise(), bm25weight.serialise());
295 wt = Xapian::BM25Weight().unserialise(bm25weight.serialise());
296 TEST_EQUAL(bm25weight.serialise(), wt->serialise());
297 delete wt;
299 Xapian::BM25Weight bm25weight2(1, 0.5, 1, 0.5, 0.5);
300 TEST_NOT_EQUAL(bm25weight.serialise(), bm25weight2.serialise());
302 Xapian::TfIdfWeight tfidfweight_dflt;
303 Xapian::TfIdfWeight tfidfweight("ntn");
304 TEST_EQUAL(tfidfweight.name(), "Xapian::TfIdfWeight");
305 TEST_EQUAL(tfidfweight_dflt.serialise(), tfidfweight.serialise());
306 wt = Xapian::TfIdfWeight().unserialise(tfidfweight.serialise());
307 TEST_EQUAL(tfidfweight.serialise(), wt->serialise());
308 delete wt;
310 Xapian::TfIdfWeight tfidfweight2("bpn");
311 TEST_NOT_EQUAL(tfidfweight.serialise(), tfidfweight2.serialise());
313 Xapian::InL2Weight inl2weight_dflt;
314 Xapian::InL2Weight inl2weight(1.0);
315 TEST_EQUAL(inl2weight.name(), "Xapian::InL2Weight");
316 TEST_EQUAL(inl2weight_dflt.serialise(), inl2weight.serialise());
317 wt = Xapian::InL2Weight().unserialise(inl2weight.serialise());
318 TEST_EQUAL(inl2weight.serialise(), wt->serialise());
319 delete wt;
321 Xapian::InL2Weight inl2weight2(2.0);
322 TEST_NOT_EQUAL(inl2weight.serialise(), inl2weight2.serialise());
324 Xapian::IfB2Weight ifb2weight_dflt;
325 Xapian::IfB2Weight ifb2weight(1.0);
326 TEST_EQUAL(ifb2weight.name(), "Xapian::IfB2Weight");
327 TEST_EQUAL(ifb2weight_dflt.serialise(), ifb2weight.serialise());
328 wt = Xapian::IfB2Weight().unserialise(ifb2weight.serialise());
329 TEST_EQUAL(ifb2weight.serialise(), wt->serialise());
330 delete wt;
332 Xapian::IfB2Weight ifb2weight2(2.0);
333 TEST_NOT_EQUAL(ifb2weight.serialise(), ifb2weight2.serialise());
335 Xapian::IneB2Weight ineb2weight_dflt;
336 Xapian::IneB2Weight ineb2weight(1.0);
337 TEST_EQUAL(ineb2weight.name(), "Xapian::IneB2Weight");
338 TEST_EQUAL(ineb2weight_dflt.serialise(), ineb2weight.serialise());
339 wt = Xapian::IneB2Weight().unserialise(ineb2weight.serialise());
340 TEST_EQUAL(ineb2weight.serialise(), wt->serialise());
341 delete wt;
343 Xapian::IneB2Weight ineb2weight2(2.0);
344 TEST_NOT_EQUAL(ineb2weight.serialise(), ineb2weight2.serialise());
346 Xapian::BB2Weight bb2weight_dflt;
347 Xapian::BB2Weight bb2weight(1.0);
348 TEST_EQUAL(bb2weight.name(), "Xapian::BB2Weight");
349 TEST_EQUAL(bb2weight_dflt.serialise(), bb2weight.serialise());
350 wt = Xapian::BB2Weight().unserialise(bb2weight.serialise());
351 TEST_EQUAL(bb2weight.serialise(), wt->serialise());
352 delete wt;
354 Xapian::BB2Weight bb2weight2(2.0);
355 TEST_NOT_EQUAL(bb2weight.serialise(), bb2weight2.serialise());
357 Xapian::DLHWeight dlhweight;
358 TEST_EQUAL(dlhweight.name(), "Xapian::DLHWeight");
359 wt = Xapian::DLHWeight().unserialise(dlhweight.serialise());
360 TEST_EQUAL(dlhweight.serialise(), wt->serialise());
361 delete wt;
363 Xapian::PL2Weight pl2weight_dflt;
364 Xapian::PL2Weight pl2weight(1.0);
365 TEST_EQUAL(pl2weight.name(), "Xapian::PL2Weight");
366 TEST_EQUAL(pl2weight_dflt.serialise(), pl2weight.serialise());
367 wt = Xapian::PL2Weight().unserialise(pl2weight.serialise());
368 TEST_EQUAL(pl2weight.serialise(), wt->serialise());
369 delete wt;
371 Xapian::PL2Weight pl2weight2(2.0);
372 TEST_NOT_EQUAL(pl2weight.serialise(), pl2weight2.serialise());
374 Xapian::DPHWeight dphweight;
375 TEST_EQUAL(dphweight.name(), "Xapian::DPHWeight");
376 wt = Xapian::DPHWeight().unserialise(dphweight.serialise());
377 TEST_EQUAL(dphweight.serialise(), wt->serialise());
378 delete wt;
380 Xapian::LMWeight unigramlmweight_dflt;
381 Xapian::LMWeight unigramlmweight(32000, Xapian::Weight::DIRICHLET_SMOOTHING, 2034.0, 0.0);
382 TEST_EQUAL(unigramlmweight.name(), "Xapian::LMWeight");
383 TEST_NOT_EQUAL(unigramlmweight_dflt.serialise(), unigramlmweight.serialise());
384 wt = Xapian::LMWeight().unserialise(unigramlmweight.serialise());
385 TEST_EQUAL(unigramlmweight.serialise(), wt->serialise());
386 delete wt;
388 return true;
391 // Regression test.
392 DEFINE_TESTCASE(nosuchdb1, !backend) {
393 // This is a "nodb" test because it doesn't test a particular backend.
394 try {
395 Xapian::Database db("NOsuChdaTabASe");
396 } catch (const Xapian::DatabaseOpeningError & e) {
397 // We don't really require this exact message, but in Xapian <= 1.1.0
398 // this gave "Couldn't detect type of database".
399 TEST_STRINGS_EQUAL(e.get_msg(), "Couldn't stat 'NOsuChdaTabASe'");
402 return true;
405 // Feature tests for value manipulations.
406 DEFINE_TESTCASE(addvalue1, !backend) {
407 // Regression test for add_value on an existing value (bug#82).
408 Xapian::Document doc;
409 doc.add_value(1, "original");
410 doc.add_value(1, "replacement");
411 TEST_EQUAL(doc.get_value(1), "replacement");
413 doc.add_value(2, "too");
414 doc.add_value(3, "free");
415 doc.add_value(4, "for");
417 doc.remove_value(2);
418 doc.remove_value(4);
419 TEST_EQUAL(doc.get_value(0), "");
420 TEST_EQUAL(doc.get_value(1), "replacement");
421 TEST_EQUAL(doc.get_value(2), "");
422 TEST_EQUAL(doc.get_value(3), "free");
423 TEST_EQUAL(doc.get_value(4), "");
425 return true;
428 // tests that the collapsing on termpos optimisation gives correct query length
429 DEFINE_TESTCASE(poscollapse2, !backend) {
430 Xapian::Query q(Xapian::Query::OP_OR, Xapian::Query("this", 1, 1), Xapian::Query("this", 1, 1));
431 TEST_EQUAL(q.get_length(), 2);
432 return true;
435 // regression test of querying an uninitialised database: should report an
436 // error; used to segfault with 1.0.0.
437 DEFINE_TESTCASE(uninitdb1, !backend) {
438 Xapian::Database db;
439 TEST_EXCEPTION(Xapian::InvalidArgumentError,
440 Xapian::Enquire enq(db));
441 return true;
444 // Test a scaleweight query applied to a match nothing query
445 DEFINE_TESTCASE(scaleweight3, !backend) {
446 Xapian::Query matchnothing(Xapian::Query::MatchNothing);
447 Xapian::Query query(Xapian::Query::OP_SCALE_WEIGHT, matchnothing, 3.0);
448 TEST_EQUAL(query.get_description(), "Query()");
449 return true;
452 // Regression test - before 1.1.0, you could add docid 0 to an RSet.
453 DEFINE_TESTCASE(rset3, !backend) {
454 Xapian::RSet rset;
455 TEST_EXCEPTION(Xapian::InvalidArgumentError, rset.add_document(0));
456 TEST(rset.empty());
457 TEST_EQUAL(rset.size(), 0);
458 rset.add_document(1);
459 rset.add_document(static_cast<Xapian::docid>(-1));
460 TEST_EXCEPTION(Xapian::InvalidArgumentError, rset.add_document(0));
461 TEST(!rset.empty());
462 TEST_EQUAL(rset.size(), 2);
463 return true;
466 // Regression test - RSet::get_description() gave a malformed answer in 1.0.7.
467 DEFINE_TESTCASE(rset4, !backend) {
468 Xapian::RSet rset;
469 rset.add_document(1);
470 // In 1.0.7 this gave: RSet(RSet(RSet::Internal(, 1))
471 TEST_STRINGS_EQUAL(rset.get_description(), "RSet(RSet::Internal(1))");
472 return true;
475 // Direct test of ValueSetMatchDecider
476 DEFINE_TESTCASE(valuesetmatchdecider1, !backend) {
477 Xapian::ValueSetMatchDecider vsmd1(0, true);
478 vsmd1.add_value("42");
479 Xapian::ValueSetMatchDecider vsmd2(0, false);
480 vsmd2.remove_value("nosuch"); // Test removing a value which isn't present.
481 vsmd2.add_value("42");
482 Xapian::ValueSetMatchDecider vsmd3(0, true);
483 vsmd3.add_value("42");
484 vsmd3.add_value("blah");
486 Xapian::Document doc;
487 TEST(!vsmd1(doc));
488 TEST(vsmd2(doc));
489 TEST(!vsmd3(doc));
490 doc.add_value(0, "42");
491 TEST(vsmd1(doc));
492 TEST(!vsmd2(doc));
493 TEST(vsmd3(doc));
494 doc.add_value(0, "blah");
495 TEST(!vsmd1(doc));
496 TEST(vsmd2(doc));
497 TEST(vsmd3(doc));
499 vsmd3.remove_value("nosuch"); // Test removing a value which isn't present.
500 vsmd3.remove_value("blah");
501 TEST(!vsmd1(doc));
502 TEST(vsmd2(doc));
503 TEST(!vsmd3(doc));
504 doc.add_value(0, "42");
505 TEST(vsmd1(doc));
506 TEST(!vsmd2(doc));
507 TEST(vsmd3(doc));
509 return true;
512 // Test that asking for the termfreq on an empty mset raises an exception.
513 DEFINE_TESTCASE(emptymset1, !backend) {
514 Xapian::MSet emptymset;
515 TEST_EXCEPTION(Xapian::InvalidOperationError,
516 emptymset.get_termfreq("foo"));
517 return true;
520 DEFINE_TESTCASE(expanddeciderfilterprefix1, !backend) {
521 string prefix = "tw";
522 Xapian::ExpandDeciderFilterPrefix decider(prefix);
523 TEST(!decider("one"));
524 TEST(!decider("t"));
525 TEST(!decider(""));
526 TEST(!decider("Two"));
527 TEST(decider("two"));
528 TEST(decider("twitter"));
529 TEST(decider(prefix));
531 return true;