Adjust stubdb2 to allow for NetBSD oddity
[xapian.git] / xapian-core / tests / api_db.cc
blob4b6d39d313a70e41b0c763836008adb017f41bb4
1 /* api_db.cc: tests which need a backend
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2011,2012,2013,2015,2016,2017 Olly Betts
6 * Copyright 2006,2007,2008,2009 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_db.h"
28 #include <algorithm>
29 #include <fstream>
30 #include <map>
31 #include <string>
32 #include <vector>
33 #include "safenetdb.h" // For gai_strerror().
34 #include "safesysstat.h" // For mkdir().
35 #include "safeunistd.h" // For sleep().
37 #include <xapian.h>
39 #include "backendmanager.h"
40 #include "backendmanager_local.h"
41 #include "testsuite.h"
42 #include "testutils.h"
43 #include "unixcmds.h"
45 #include "apitest.h"
47 using namespace std;
49 static Xapian::Query
50 query(const string &t)
52 return Xapian::Query(Xapian::Stem("english")(t));
55 // #######################################################################
56 // # Tests start here
58 // tests Xapian::Database::get_termfreq() and Xapian::Database::term_exists()
59 DEFINE_TESTCASE(termstats, backend) {
60 Xapian::Database db(get_database("apitest_simpledata"));
62 TEST(!db.term_exists("corn"));
63 TEST_EQUAL(db.get_termfreq("corn"), 0);
64 TEST(db.term_exists("banana"));
65 TEST_EQUAL(db.get_termfreq("banana"), 1);
66 TEST(db.term_exists("paragraph"));
67 TEST_EQUAL(db.get_termfreq("paragraph"), 5);
69 return true;
72 // Check that stub databases work.
73 DEFINE_TESTCASE(stubdb1, backend && !inmemory && !remote) {
74 // Only works for backends which have a path.
75 mkdir(".stub", 0755);
76 const char * dbpath = ".stub/stubdb1";
77 ofstream out(dbpath);
78 TEST(out.is_open());
79 out << "auto ../" << get_database_path("apitest_simpledata") << endl;
80 out.close();
83 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
84 Xapian::Enquire enquire(db);
85 enquire.set_query(Xapian::Query("word"));
86 enquire.get_mset(0, 10);
89 Xapian::Database db(dbpath);
90 Xapian::Enquire enquire(db);
91 enquire.set_query(Xapian::Query("word"));
92 enquire.get_mset(0, 10);
95 return true;
98 // Check that stub databases work remotely.
99 DEFINE_TESTCASE(stubdb2, backend && !inmemory && !remote) {
100 // Only works for backends which have a path.
101 mkdir(".stub", 0755);
102 const char * dbpath = ".stub/stubdb2";
103 ofstream out(dbpath);
104 TEST(out.is_open());
105 out << "remote :" << BackendManager::get_xapian_progsrv_command()
106 << ' ' << get_database_path("apitest_simpledata") << endl;
107 out.close();
109 try {
110 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
111 Xapian::Enquire enquire(db);
112 enquire.set_query(Xapian::Query("word"));
113 enquire.get_mset(0, 10);
114 } catch (Xapian::FeatureUnavailableError&) {
115 #ifdef XAPIAN_HAS_REMOTE_BACKEND
116 throw;
117 #endif
120 try {
121 Xapian::Database db(dbpath);
122 Xapian::Enquire enquire(db);
123 enquire.set_query(Xapian::Query("word"));
124 enquire.get_mset(0, 10);
125 } catch (Xapian::FeatureUnavailableError&) {
126 #ifdef XAPIAN_HAS_REMOTE_BACKEND
127 throw;
128 #endif
131 out.open(dbpath);
132 TEST(out.is_open());
133 out << "remote" << endl;
134 out.close();
136 // Quietly ignored prior to 1.4.1.
137 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
138 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB)
141 // Quietly ignored prior to 1.4.1.
142 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
143 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB)
146 #ifdef XAPIAN_HAS_REMOTE_BACKEND
147 # define EXPECTED_EXCEPTION Xapian::DatabaseOpeningError
148 #else
149 # define EXPECTED_EXCEPTION Xapian::FeatureUnavailableError
150 #endif
152 out.open(dbpath);
153 TEST(out.is_open());
154 out << "remote foo" << endl;
155 out.close();
157 // Quietly ignored prior to 1.4.1.
158 TEST_EXCEPTION(EXPECTED_EXCEPTION,
159 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB)
162 // Quietly ignored prior to 1.4.1.
163 TEST_EXCEPTION(EXPECTED_EXCEPTION,
164 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB)
167 #ifdef XAPIAN_HAS_REMOTE_BACKEND
168 out.open(dbpath);
169 TEST(out.is_open());
170 out << "remote [::1]:65535" << endl;
171 out.close();
173 try {
174 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
175 } catch (const Xapian::NetworkError& e) {
176 // 1.4.0 threw (on Linux) the confusing message:
177 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
178 // 1.4.1 throws (because we don't actually support IPv6 yet) on Linux (EAI_ADDRFAMILY):
179 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (nodename nor servname provided, or not known)
180 // or on OS X (EAI_NONAME):
181 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (Address family for hostname not supported)
183 // But NetBSD seems to resolve ::1 to an IPv4 address and then tries
184 // to connect to it (which hopefully fails), so just test the message
185 // doesn't match the bad 1.4.0 result.
186 TEST(e.get_msg().find("host [") == string::npos);
189 try {
190 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB);
191 } catch (const Xapian::NetworkError& e) {
192 // 1.4.0 threw (Linux):
193 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
194 // 1.4.1 throws (because we don't actually support IPv6 yet) on Linux (EAI_ADDRFAMILY):
195 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (nodename nor servname provided, or not known)
196 // or on OS X (EAI_NONAME):
197 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (Address family for hostname not supported)
198 // So we test the message instead of the error string for portability.
200 // But NetBSD seems to resolve ::1 to an IPv4 address and then tries
201 // to connect to it (which hopefully fails), so just test the message
202 // doesn't match the bad 1.4.0 result.
203 TEST(e.get_msg().find("host [") == string::npos);
205 #endif
207 out.open(dbpath);
208 TEST(out.is_open());
209 // Invalid - the port number is required.
210 out << "remote [::1]" << endl;
211 out.close();
213 // 1.4.0 threw:
214 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
215 TEST_EXCEPTION(EXPECTED_EXCEPTION,
216 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
219 // 1.4.0 threw:
220 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
221 TEST_EXCEPTION(EXPECTED_EXCEPTION,
222 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB);
225 return true;
228 // Regression test - bad entries were ignored after a good entry prior to 1.0.8.
229 DEFINE_TESTCASE(stubdb3, backend && !inmemory && !remote) {
230 // Only works for backends which have a path.
231 mkdir(".stub", 0755);
232 const char * dbpath = ".stub/stubdb3";
233 ofstream out(dbpath);
234 TEST(out.is_open());
235 out << "auto ../" << get_database_path("apitest_simpledata") << "\n"
236 "bad line here\n";
237 out.close();
239 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
240 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB));
242 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
243 Xapian::Database db(dbpath));
245 return true;
248 // Test a stub database with just a bad entry.
249 DEFINE_TESTCASE(stubdb4, backend && !inmemory && !remote) {
250 // Only works for backends which have a path.
251 mkdir(".stub", 0755);
252 const char * dbpath = ".stub/stubdb4";
253 ofstream out(dbpath);
254 TEST(out.is_open());
255 out << "bad line here\n";
256 out.close();
258 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
259 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB));
261 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
262 Xapian::Database db(dbpath));
264 return true;
267 // Test a stub database with a bad entry with no spaces (prior to 1.1.0 this
268 // was deliberately allowed, though not documented.
269 DEFINE_TESTCASE(stubdb5, backend && !inmemory && !remote) {
270 // Only works for backends which have a path.
271 mkdir(".stub", 0755);
272 const char * dbpath = ".stub/stubdb5";
273 ofstream out(dbpath);
274 TEST(out.is_open());
275 out << "bad\n"
276 "auto ../" << get_database_path("apitest_simpledata") << endl;
277 out.close();
279 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
280 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB));
282 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
283 Xapian::Database db(dbpath));
285 return true;
288 // Test a stub database with an inmemory database (new feature in 1.1.0).
289 DEFINE_TESTCASE(stubdb6, inmemory) {
290 mkdir(".stub", 0755);
291 const char * dbpath = ".stub/stubdb6";
292 ofstream out(dbpath);
293 TEST(out.is_open());
294 out << "inmemory\n";
295 out.close();
297 // Read-only tests:
299 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
300 TEST_EQUAL(db.get_doccount(), 0);
301 Xapian::Enquire enquire(db);
302 enquire.set_query(Xapian::Query("word"));
303 Xapian::MSet mset = enquire.get_mset(0, 10);
304 TEST(mset.empty());
307 Xapian::Database db(dbpath);
308 TEST_EQUAL(db.get_doccount(), 0);
309 Xapian::Enquire enquire(db);
310 enquire.set_query(Xapian::Query("word"));
311 Xapian::MSet mset = enquire.get_mset(0, 10);
312 TEST(mset.empty());
315 // Writable tests:
317 Xapian::WritableDatabase db(dbpath,
318 Xapian::DB_OPEN|Xapian::DB_BACKEND_STUB);
319 TEST_EQUAL(db.get_doccount(), 0);
320 db.add_document(Xapian::Document());
321 TEST_EQUAL(db.get_doccount(), 1);
324 Xapian::WritableDatabase db(dbpath,
325 Xapian::DB_OPEN|Xapian::DB_BACKEND_STUB);
326 TEST_EQUAL(db.get_doccount(), 0);
327 db.add_document(Xapian::Document());
328 TEST_EQUAL(db.get_doccount(), 1);
331 return true;
334 /// Test error running Database::check() on a stub database.
335 // Regression test - in 1.4.3 and earlier this threw
336 // Xapian::DatabaseError.
337 DEFINE_TESTCASE(stubdb8, inmemory) {
338 mkdir(".stub", 0755);
339 const char * dbpath = ".stub/stubdb8";
340 ofstream out(dbpath);
341 TEST(out.is_open());
342 out << "inmemory\n";
343 out.close();
345 try {
346 Xapian::Database::check(dbpath);
347 FAIL_TEST("Managed to check inmemory stub");
348 } catch (const Xapian::DatabaseOpeningError & e) {
349 // Check the message is appropriate.
350 TEST_STRINGS_EQUAL(e.get_msg(),
351 "File is not a Xapian database or database table");
353 return true;
356 #if 0 // the "force error" mechanism is no longer in place...
357 class MyErrorHandler : public Xapian::ErrorHandler {
358 public:
359 int count;
361 bool handle_error(Xapian::Error & error) {
362 ++count;
363 tout << "Error handling caught: " << error.get_description()
364 << ", count is now " << count << "\n";
365 return true;
368 MyErrorHandler() : count (0) {}
371 // tests error handler in multimatch().
372 DEFINE_TESTCASE(multierrhandler1, backend) {
373 MyErrorHandler myhandler;
375 Xapian::Database mydb2(get_database("apitest_simpledata"));
376 Xapian::Database mydb3(get_database("apitest_simpledata2"));
377 int errcount = 1;
378 for (int testcount = 0; testcount < 14; testcount ++) {
379 tout << "testcount=" << testcount << "\n";
380 Xapian::Database mydb4(get_database("-e", "apitest_termorder"));
381 Xapian::Database mydb5(get_network_database("apitest_termorder", 1));
382 Xapian::Database mydb6(get_database("-e2", "apitest_termorder"));
383 Xapian::Database mydb7(get_database("-e3", "apitest_simpledata"));
385 Xapian::Database dbs;
386 switch (testcount) {
387 case 0:
388 dbs.add_database(mydb2);
389 dbs.add_database(mydb3);
390 dbs.add_database(mydb4);
391 break;
392 case 1:
393 dbs.add_database(mydb4);
394 dbs.add_database(mydb2);
395 dbs.add_database(mydb3);
396 break;
397 case 2:
398 dbs.add_database(mydb3);
399 dbs.add_database(mydb4);
400 dbs.add_database(mydb2);
401 break;
402 case 3:
403 dbs.add_database(mydb2);
404 dbs.add_database(mydb3);
405 dbs.add_database(mydb5);
406 sleep(1);
407 break;
408 case 4:
409 dbs.add_database(mydb5);
410 dbs.add_database(mydb2);
411 dbs.add_database(mydb3);
412 sleep(1);
413 break;
414 case 5:
415 dbs.add_database(mydb3);
416 dbs.add_database(mydb5);
417 dbs.add_database(mydb2);
418 sleep(1);
419 break;
420 case 6:
421 dbs.add_database(mydb2);
422 dbs.add_database(mydb3);
423 dbs.add_database(mydb6);
424 break;
425 case 7:
426 dbs.add_database(mydb6);
427 dbs.add_database(mydb2);
428 dbs.add_database(mydb3);
429 break;
430 case 8:
431 dbs.add_database(mydb3);
432 dbs.add_database(mydb6);
433 dbs.add_database(mydb2);
434 break;
435 case 9:
436 dbs.add_database(mydb2);
437 dbs.add_database(mydb3);
438 dbs.add_database(mydb7);
439 break;
440 case 10:
441 dbs.add_database(mydb7);
442 dbs.add_database(mydb2);
443 dbs.add_database(mydb3);
444 break;
445 case 11:
446 dbs.add_database(mydb3);
447 dbs.add_database(mydb7);
448 dbs.add_database(mydb2);
449 break;
450 case 12:
451 dbs.add_database(mydb2);
452 dbs.add_database(mydb6);
453 dbs.add_database(mydb7);
454 break;
455 case 13:
456 dbs.add_database(mydb2);
457 dbs.add_database(mydb7);
458 dbs.add_database(mydb6);
459 break;
461 tout << "db=" << dbs << "\n";
462 Xapian::Enquire enquire(dbs, &myhandler);
464 // make a query
465 Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
466 enquire.set_weighting_scheme(Xapian::BoolWeight());
467 enquire.set_query(myquery);
469 tout << "query=" << myquery << "\n";
470 // retrieve the top ten results
471 Xapian::MSet mymset = enquire.get_mset(0, 10);
473 switch (testcount) {
474 case 0: case 3: case 6: case 9:
475 mset_expect_order(mymset, 2, 4, 10);
476 break;
477 case 1: case 4: case 7: case 10:
478 mset_expect_order(mymset, 3, 5, 11);
479 break;
480 case 2: case 5: case 8: case 11:
481 mset_expect_order(mymset, 1, 6, 12);
482 break;
483 case 12:
484 case 13:
485 mset_expect_order(mymset, 4, 10);
486 errcount += 1;
487 break;
489 TEST_EQUAL(myhandler.count, errcount);
490 errcount += 1;
493 return true;
495 #endif
497 class GrepMatchDecider : public Xapian::MatchDecider {
498 string needle;
499 public:
500 explicit GrepMatchDecider(const string& needle_)
501 : needle(needle_) {}
503 bool operator()(const Xapian::Document &doc) const {
504 // Note that this is not recommended usage of get_data()
505 return doc.get_data().find(needle) != string::npos;
509 // Test Xapian::MatchDecider functor.
510 DEFINE_TESTCASE(matchdecider1, backend && !remote) {
511 Xapian::Database db(get_database("apitest_simpledata"));
512 Xapian::Enquire enquire(db);
513 enquire.set_query(Xapian::Query("this"));
515 GrepMatchDecider myfunctor("This is");
517 Xapian::MSet mymset = enquire.get_mset(0, 100, 0, &myfunctor);
519 vector<bool> docid_checked(db.get_lastdocid());
521 // Check that we get the expected number of matches, and that they
522 // satisfy the condition.
523 Xapian::MSetIterator i = mymset.begin();
524 TEST(i != mymset.end());
525 TEST_EQUAL(mymset.size(), 3);
526 TEST_EQUAL(mymset.get_matches_lower_bound(), 3);
527 TEST_EQUAL(mymset.get_matches_upper_bound(), 3);
528 TEST_EQUAL(mymset.get_matches_estimated(), 3);
529 TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 3);
530 TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 3);
531 TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 3);
532 for ( ; i != mymset.end(); ++i) {
533 const Xapian::Document doc(i.get_document());
534 TEST(myfunctor(doc));
535 docid_checked[*i] = true;
538 // Check that there are some documents which aren't accepted by the match
539 // decider.
540 mymset = enquire.get_mset(0, 100);
541 TEST(mymset.size() > 3);
543 // Check that the bounds are appropriate even if we don't ask for any
544 // actual matches.
545 mymset = enquire.get_mset(0, 0, 0, &myfunctor);
546 TEST_EQUAL(mymset.size(), 0);
547 TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
548 TEST_EQUAL(mymset.get_matches_upper_bound(), 6);
549 TEST_REL(mymset.get_matches_estimated(),>,0);
550 TEST_REL(mymset.get_matches_estimated(),<=,6);
551 TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 0);
552 TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 6);
553 TEST_REL(mymset.get_uncollapsed_matches_estimated(),>,0);
554 TEST_REL(mymset.get_uncollapsed_matches_estimated(),<=,6);
556 // Check that the bounds are appropriate if we ask for only one hit.
557 // (Regression test - until SVN 10256, we didn't reduce the lower_bound
558 // appropriately, and returned 6 here.)
559 mymset = enquire.get_mset(0, 1, 0, &myfunctor);
560 TEST_EQUAL(mymset.size(), 1);
561 TEST_REL(mymset.get_matches_lower_bound(),>=,1);
562 TEST_REL(mymset.get_matches_lower_bound(),<=,3);
563 TEST_REL(mymset.get_matches_upper_bound(),>=,3);
564 TEST_REL(mymset.get_matches_upper_bound(),<=,6);
565 TEST_REL(mymset.get_matches_estimated(),>,0);
566 TEST_REL(mymset.get_matches_estimated(),<=,6);
567 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),>=,1);
568 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),<=,3);
569 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),>=,3);
570 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),<=,6);
571 TEST_REL(mymset.get_uncollapsed_matches_estimated(),>,0);
572 TEST_REL(mymset.get_uncollapsed_matches_estimated(),<=,6);
574 // Check that the other documents don't satisfy the condition.
575 for (Xapian::docid did = 1; did < docid_checked.size(); ++did) {
576 if (!docid_checked[did]) {
577 TEST(!myfunctor(db.get_document(did)));
581 // Check that the bounds are appropriate if a collapse key is used.
582 // Use a value which is never set so we don't actually discard anything.
583 enquire.set_collapse_key(99);
584 mymset = enquire.get_mset(0, 1, 0, &myfunctor);
585 TEST_EQUAL(mymset.size(), 1);
586 TEST_REL(mymset.get_matches_lower_bound(),>=,1);
587 TEST_REL(mymset.get_matches_lower_bound(),<=,3);
588 TEST_REL(mymset.get_matches_upper_bound(),>=,3);
589 TEST_REL(mymset.get_matches_upper_bound(),<=,6);
590 TEST_REL(mymset.get_matches_estimated(),>,0);
591 TEST_REL(mymset.get_matches_estimated(),<=,6);
592 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),>=,1);
593 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),<=,3);
594 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),>=,3);
595 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),<=,6);
596 TEST_REL(mymset.get_uncollapsed_matches_estimated(),>,0);
597 TEST_REL(mymset.get_uncollapsed_matches_estimated(),<=,6);
599 // Check that the bounds are appropriate if a percentage cutoff is in
600 // use. Set a 1% threshold so we don't actually discard anything.
601 enquire.set_collapse_key(Xapian::BAD_VALUENO);
602 enquire.set_cutoff(1);
603 mymset = enquire.get_mset(0, 1, 0, &myfunctor);
604 TEST_EQUAL(mymset.size(), 1);
605 TEST_REL(mymset.get_matches_lower_bound(),>=,1);
606 TEST_REL(mymset.get_matches_lower_bound(),<=,3);
607 TEST_REL(mymset.get_matches_upper_bound(),>=,3);
608 TEST_REL(mymset.get_matches_upper_bound(),<=,6);
609 TEST_REL(mymset.get_matches_estimated(),>,0);
610 TEST_REL(mymset.get_matches_estimated(),<=,6);
611 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),>=,1);
612 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),<=,3);
613 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),>=,3);
614 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),<=,6);
615 TEST_REL(mymset.get_uncollapsed_matches_estimated(),>,0);
616 TEST_REL(mymset.get_uncollapsed_matches_estimated(),<=,6);
618 // And now with both a collapse key and percentage cutoff.
619 enquire.set_collapse_key(99);
620 mymset = enquire.get_mset(0, 1, 0, &myfunctor);
621 TEST_EQUAL(mymset.size(), 1);
622 TEST_REL(mymset.get_matches_lower_bound(),>=,1);
623 TEST_REL(mymset.get_matches_lower_bound(),<=,3);
624 TEST_REL(mymset.get_matches_upper_bound(),>=,3);
625 TEST_REL(mymset.get_matches_upper_bound(),<=,6);
626 TEST_REL(mymset.get_matches_estimated(),>,0);
627 TEST_REL(mymset.get_matches_estimated(),<=,6);
628 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),>=,1);
629 TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),<=,3);
630 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),>=,3);
631 TEST_REL(mymset.get_uncollapsed_matches_upper_bound(),<=,6);
632 TEST_REL(mymset.get_uncollapsed_matches_estimated(),>,0);
633 TEST_REL(mymset.get_uncollapsed_matches_estimated(),<=,6);
635 return true;
638 // Test Xapian::MatchDecider functor used as a match spy.
639 DEFINE_TESTCASE(matchdecider2, backend && !remote) {
640 Xapian::Database db(get_database("apitest_simpledata"));
641 Xapian::Enquire enquire(db);
642 enquire.set_query(Xapian::Query("this"));
644 GrepMatchDecider myfunctor("This is");
646 Xapian::MSet mymset = enquire.get_mset(0, 100, 0, NULL, &myfunctor);
648 vector<bool> docid_checked(db.get_lastdocid());
650 // Check that we get the expected number of matches, and that they
651 // satisfy the condition.
652 Xapian::MSetIterator i = mymset.begin();
653 TEST(i != mymset.end());
654 TEST_EQUAL(mymset.size(), 3);
655 for ( ; i != mymset.end(); ++i) {
656 const Xapian::Document doc(i.get_document());
657 TEST(myfunctor(doc));
658 docid_checked[*i] = true;
661 // Check that the other documents don't satisfy the condition.
662 for (Xapian::docid did = 1; did < docid_checked.size(); ++did) {
663 if (!docid_checked[did]) {
664 TEST(!myfunctor(db.get_document(did)));
668 return true;
671 // Regression test for lower bound using functor, sorting and collapsing.
672 DEFINE_TESTCASE(matchdecider3, backend && !remote) {
673 Xapian::Database db(get_database("etext"));
674 Xapian::Enquire enquire(db);
675 enquire.set_query(Xapian::Query(""));
676 enquire.set_collapse_key(12);
677 enquire.set_sort_by_value(11, true);
679 GrepMatchDecider myfunctor("We produce");
681 Xapian::MSet mset1 = enquire.get_mset(0, 2, 0, NULL, &myfunctor);
682 Xapian::MSet mset2 = enquire.get_mset(0, 1000, 0, NULL, &myfunctor);
684 // mset2 should contain all the hits, so the statistics should be exact.
685 TEST_EQUAL(mset2.get_matches_estimated(), mset2.size());
686 TEST_EQUAL(mset2.get_matches_lower_bound(), mset2.get_matches_estimated());
687 TEST_EQUAL(mset2.get_matches_estimated(), mset2.get_matches_upper_bound());
689 TEST_REL(mset2.get_uncollapsed_matches_lower_bound(),<=,mset2.get_uncollapsed_matches_estimated());
690 TEST_REL(mset2.get_uncollapsed_matches_estimated(),<=,mset2.get_uncollapsed_matches_upper_bound());
692 // Check that the lower bound in mset1 is not greater than the known
693 // number of hits. This failed until revision 10811.
694 TEST_REL(mset1.get_matches_lower_bound(),<=,mset2.size());
696 // Check that the bounds for mset1 make sense.
697 TEST_REL(mset1.get_matches_lower_bound(),<=,mset1.get_matches_estimated());
698 TEST_REL(mset1.get_matches_estimated(),<=,mset1.get_matches_upper_bound());
699 TEST_REL(mset1.size(),<=,mset1.get_matches_upper_bound());
701 TEST_REL(mset1.get_uncollapsed_matches_lower_bound(),<=,mset1.get_uncollapsed_matches_estimated());
702 TEST_REL(mset1.get_uncollapsed_matches_estimated(),<=,mset1.get_uncollapsed_matches_upper_bound());
704 // The uncollapsed match would match all documents but the one the
705 // matchdecider rejects.
706 TEST_REL(mset1.get_uncollapsed_matches_upper_bound(),>=,db.get_doccount() - 1);
707 TEST_REL(mset1.get_uncollapsed_matches_upper_bound(),<=,db.get_doccount());
708 TEST_REL(mset2.get_uncollapsed_matches_upper_bound(),>=,db.get_doccount() - 1);
709 TEST_REL(mset2.get_uncollapsed_matches_upper_bound(),<=,db.get_doccount());
711 return true;
714 // tests that mset iterators on msets compare correctly.
715 DEFINE_TESTCASE(msetiterator1, backend) {
716 Xapian::Enquire enquire(get_database("apitest_simpledata"));
717 enquire.set_query(Xapian::Query("this"));
718 Xapian::MSet mymset = enquire.get_mset(0, 2);
720 Xapian::MSetIterator j;
721 j = mymset.begin();
722 Xapian::MSetIterator k = mymset.end();
723 Xapian::MSetIterator l(j);
724 Xapian::MSetIterator m(k);
725 Xapian::MSetIterator n = mymset.begin();
726 Xapian::MSetIterator o = mymset.begin();
727 TEST_NOT_EQUAL(j, k);
728 TEST_NOT_EQUAL(l, m);
729 TEST_EQUAL(k, m);
730 TEST_EQUAL(j, l);
731 TEST_EQUAL(j, j);
732 TEST_EQUAL(k, k);
734 k = j;
735 TEST_EQUAL(j, k);
736 TEST_EQUAL(j, o);
737 k++;
738 TEST_NOT_EQUAL(j, k);
739 TEST_NOT_EQUAL(k, l);
740 TEST_NOT_EQUAL(k, m);
741 TEST_NOT_EQUAL(k, o);
742 o++;
743 TEST_EQUAL(k, o);
744 k++;
745 TEST_NOT_EQUAL(j, k);
746 TEST_NOT_EQUAL(k, l);
747 TEST_EQUAL(k, m);
748 TEST_EQUAL(n, l);
750 n = m;
751 TEST_NOT_EQUAL(n, l);
752 TEST_EQUAL(n, m);
753 TEST_NOT_EQUAL(n, mymset.begin());
754 TEST_EQUAL(n, mymset.end());
756 return true;
759 // tests that mset iterators on empty msets compare equal.
760 DEFINE_TESTCASE(msetiterator2, backend) {
761 Xapian::Enquire enquire(get_database("apitest_simpledata"));
762 enquire.set_query(Xapian::Query("this"));
763 Xapian::MSet mymset = enquire.get_mset(0, 0);
765 Xapian::MSetIterator j = mymset.begin();
766 Xapian::MSetIterator k = mymset.end();
767 Xapian::MSetIterator l(j);
768 Xapian::MSetIterator m(k);
769 TEST_EQUAL(j, k);
770 TEST_EQUAL(l, m);
771 TEST_EQUAL(k, m);
772 TEST_EQUAL(j, l);
773 TEST_EQUAL(j, j);
774 TEST_EQUAL(k, k);
776 return true;
779 // tests that begin().get_document() works when first != 0
780 DEFINE_TESTCASE(msetiterator3, backend) {
781 Xapian::Database mydb(get_database("apitest_simpledata"));
782 Xapian::Enquire enquire(mydb);
783 enquire.set_query(Xapian::Query("this"));
785 Xapian::MSet mymset = enquire.get_mset(2, 10);
787 TEST(!mymset.empty());
788 Xapian::Document doc(mymset.begin().get_document());
789 TEST(!doc.get_data().empty());
791 return true;
794 // tests that eset iterators on empty esets compare equal.
795 DEFINE_TESTCASE(esetiterator1, backend) {
796 Xapian::Enquire enquire(get_database("apitest_simpledata"));
797 enquire.set_query(Xapian::Query("this"));
799 Xapian::MSet mymset = enquire.get_mset(0, 10);
800 TEST(mymset.size() >= 2);
802 Xapian::RSet myrset;
803 Xapian::MSetIterator i = mymset.begin();
804 myrset.add_document(*i);
805 myrset.add_document(*(++i));
807 Xapian::ESet myeset = enquire.get_eset(2, myrset);
808 Xapian::ESetIterator j;
809 j = myeset.begin();
810 Xapian::ESetIterator k = myeset.end();
811 Xapian::ESetIterator l(j);
812 Xapian::ESetIterator m(k);
813 Xapian::ESetIterator n = myeset.begin();
815 TEST_NOT_EQUAL(j, k);
816 TEST_NOT_EQUAL(l, m);
817 TEST_EQUAL(k, m);
818 TEST_EQUAL(j, l);
819 TEST_EQUAL(j, j);
820 TEST_EQUAL(k, k);
822 k = j;
823 TEST_EQUAL(j, k);
824 k++;
825 TEST_NOT_EQUAL(j, k);
826 TEST_NOT_EQUAL(k, l);
827 TEST_NOT_EQUAL(k, m);
828 k++;
829 TEST_NOT_EQUAL(j, k);
830 TEST_NOT_EQUAL(k, l);
831 TEST_EQUAL(k, m);
832 TEST_EQUAL(n, l);
834 n = m;
835 TEST_NOT_EQUAL(n, l);
836 TEST_EQUAL(n, m);
837 TEST_NOT_EQUAL(n, myeset.begin());
838 TEST_EQUAL(n, myeset.end());
840 return true;
843 // tests that eset iterators on empty esets compare equal.
844 DEFINE_TESTCASE(esetiterator2, backend) {
845 Xapian::Enquire enquire(get_database("apitest_simpledata"));
846 enquire.set_query(Xapian::Query("this"));
848 Xapian::MSet mymset = enquire.get_mset(0, 10);
849 TEST(mymset.size() >= 2);
851 Xapian::RSet myrset;
852 Xapian::MSetIterator i = mymset.begin();
853 myrset.add_document(*i);
854 myrset.add_document(*(++i));
856 Xapian::ESet myeset = enquire.get_eset(0, myrset);
857 Xapian::ESetIterator j = myeset.begin();
858 Xapian::ESetIterator k = myeset.end();
859 Xapian::ESetIterator l(j);
860 Xapian::ESetIterator m(k);
861 TEST_EQUAL(j, k);
862 TEST_EQUAL(l, m);
863 TEST_EQUAL(k, m);
864 TEST_EQUAL(j, l);
865 TEST_EQUAL(j, j);
866 TEST_EQUAL(k, k);
868 return true;
871 // tests the collapse-on-key
872 DEFINE_TESTCASE(collapsekey1, backend) {
873 Xapian::Enquire enquire(get_database("apitest_simpledata"));
874 enquire.set_query(Xapian::Query("this"));
876 Xapian::MSet mymset1 = enquire.get_mset(0, 100);
877 Xapian::doccount mymsize1 = mymset1.size();
879 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
880 enquire.set_collapse_key(value_no);
881 Xapian::MSet mymset = enquire.get_mset(0, 100);
883 TEST_AND_EXPLAIN(mymsize1 > mymset.size(),
884 "Had no fewer items when performing collapse: don't know whether it worked.");
886 map<string, Xapian::docid> values;
887 Xapian::MSetIterator i = mymset.begin();
888 for ( ; i != mymset.end(); ++i) {
889 string value = i.get_document().get_value(value_no);
890 TEST(values[value] == 0 || value.empty());
891 values[value] = *i;
895 return true;
898 // tests that collapse-on-key modifies the predicted bounds for the number of
899 // matches appropriately.
900 DEFINE_TESTCASE(collapsekey2, backend) {
901 SKIP_TEST("Don't have a suitable database currently");
902 // FIXME: this needs an appropriate database creating, but that's quite
903 // subtle to do it seems.
904 Xapian::Enquire enquire(get_database("apitest_simpledata2"));
905 enquire.set_query(Xapian::Query("this"));
907 Xapian::MSet mset1 = enquire.get_mset(0, 1);
909 // Test that if no duplicates are found, then the upper bound remains
910 // unchanged and the lower bound drops.
912 enquire.set_query(Xapian::Query("this"));
913 Xapian::valueno value_no = 3;
914 enquire.set_collapse_key(value_no);
915 Xapian::MSet mset = enquire.get_mset(0, 1);
917 TEST_REL(mset.get_matches_lower_bound(),<,mset1.get_matches_lower_bound());
918 TEST_EQUAL(mset.get_matches_upper_bound(), mset1.get_matches_upper_bound());
921 return true;
924 // tests that collapse-on-key modifies the predicted bounds for the number of
925 // matches appropriately.
926 DEFINE_TESTCASE(collapsekey3, backend) {
927 Xapian::Enquire enquire(get_database("apitest_simpledata"));
928 enquire.set_query(Xapian::Query("this"));
930 Xapian::MSet mymset1 = enquire.get_mset(0, 3);
932 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
933 enquire.set_collapse_key(value_no);
934 Xapian::MSet mymset = enquire.get_mset(0, 3);
936 TEST_AND_EXPLAIN(mymset1.get_matches_lower_bound() > mymset.get_matches_lower_bound(),
937 "Lower bound was not lower when performing collapse: don't know whether it worked.");
938 TEST_AND_EXPLAIN(mymset1.get_matches_upper_bound() > mymset.get_matches_upper_bound(),
939 "Upper bound was not lower when performing collapse: don't know whether it worked.");
941 map<string, Xapian::docid> values;
942 Xapian::MSetIterator i = mymset.begin();
943 for ( ; i != mymset.end(); ++i) {
944 string value = i.get_document().get_value(value_no);
945 TEST(values[value] == 0 || value.empty());
946 values[value] = *i;
950 // Test that if the collapse value is always empty, then the upper bound
951 // remains unchanged, and the lower bound is the same or lower (it can be
952 // lower because the matcher counts the number of documents with empty
953 // collapse keys, but may have rejected a document because its weight is
954 // too low for the proto-MSet before it even looks at its collapse key).
956 Xapian::valueno value_no = 1000;
957 enquire.set_collapse_key(value_no);
958 Xapian::MSet mymset = enquire.get_mset(0, 3);
960 TEST(mymset.get_matches_lower_bound() <= mymset1.get_matches_lower_bound());
961 TEST_EQUAL(mymset.get_matches_upper_bound(), mymset1.get_matches_upper_bound());
963 map<string, Xapian::docid> values;
964 Xapian::MSetIterator i = mymset.begin();
965 for ( ; i != mymset.end(); ++i) {
966 string value = i.get_document().get_value(value_no);
967 TEST(values[value] == 0 || value.empty());
968 values[value] = *i;
972 return true;
975 // tests that collapse-on-key modifies the predicted bounds for the number of
976 // matches appropriately even when no results are requested.
977 DEFINE_TESTCASE(collapsekey4, backend) {
978 Xapian::Enquire enquire(get_database("apitest_simpledata"));
979 enquire.set_query(Xapian::Query("this"));
981 Xapian::MSet mymset1 = enquire.get_mset(0, 0);
983 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
984 enquire.set_collapse_key(value_no);
985 Xapian::MSet mymset = enquire.get_mset(0, 0);
987 TEST_AND_EXPLAIN(mymset.get_matches_lower_bound() == 1,
988 "Lower bound was not 1 when performing collapse but not asking for any results.");
989 TEST_AND_EXPLAIN(mymset1.get_matches_upper_bound() == mymset.get_matches_upper_bound(),
990 "Upper bound was changed when performing collapse but not asking for any results.");
992 map<string, Xapian::docid> values;
993 Xapian::MSetIterator i = mymset.begin();
994 for ( ; i != mymset.end(); ++i) {
995 string value = i.get_document().get_value(value_no);
996 TEST(values[value] == 0 || value.empty());
997 values[value] = *i;
1001 return true;
1004 // test for keepalives
1005 DEFINE_TESTCASE(keepalive1, remote) {
1006 Xapian::Database db(get_remote_database("apitest_simpledata", 5000));
1008 /* Test that keep-alives work */
1009 for (int i = 0; i < 10; ++i) {
1010 sleep(2);
1011 db.keep_alive();
1013 Xapian::Enquire enquire(db);
1014 enquire.set_query(Xapian::Query("word"));
1015 enquire.get_mset(0, 10);
1017 /* Test that things break without keepalives */
1018 sleep(10);
1019 enquire.set_query(Xapian::Query("word"));
1020 TEST_EXCEPTION(Xapian::NetworkError,
1021 enquire.get_mset(0, 10));
1023 return true;
1026 // test that iterating through all terms in a database works.
1027 DEFINE_TESTCASE(allterms1, backend) {
1028 Xapian::Database db(get_database("apitest_allterms"));
1029 Xapian::TermIterator ati = db.allterms_begin();
1030 TEST(ati != db.allterms_end());
1031 TEST_EQUAL(*ati, "one");
1032 TEST_EQUAL(ati.get_termfreq(), 1);
1034 Xapian::TermIterator ati2 = ati;
1036 ati++;
1037 TEST(ati != db.allterms_end());
1038 if (verbose) {
1039 tout << "*ati = '" << *ati << "'\n";
1040 tout << "*ati.length = '" << (*ati).length() << "'\n";
1041 tout << "*ati == \"one\" = " << (*ati == "one") << "\n";
1042 tout << "*ati[3] = " << ((*ati)[3]) << "\n";
1043 tout << "*ati = '" << *ati << "'\n";
1045 TEST(*ati == "three");
1046 TEST(ati.get_termfreq() == 3);
1048 #if 0
1049 TEST(ati2 != db.allterms_end());
1050 TEST(*ati2 == "one");
1051 TEST(ati2.get_termfreq() == 1);
1052 #endif
1054 ++ati;
1055 #if 0
1056 ++ati2;
1057 #endif
1058 TEST(ati != db.allterms_end());
1059 TEST(*ati == "two");
1060 TEST(ati.get_termfreq() == 2);
1062 #if 0
1063 TEST(ati2 != db.allterms_end());
1064 TEST(*ati2 == "three");
1065 TEST(ati2.get_termfreq() == 3);
1066 #endif
1068 ati++;
1069 TEST(ati == db.allterms_end());
1071 return true;
1074 // test that iterating through all terms in two databases works.
1075 DEFINE_TESTCASE(allterms2, backend) {
1076 Xapian::Database db;
1077 db.add_database(get_database("apitest_allterms"));
1078 db.add_database(get_database("apitest_allterms2"));
1079 Xapian::TermIterator ati = db.allterms_begin();
1081 TEST(ati != db.allterms_end());
1082 TEST(*ati == "five");
1083 TEST(ati.get_termfreq() == 2);
1084 ati++;
1086 TEST(ati != db.allterms_end());
1087 TEST(*ati == "four");
1088 TEST(ati.get_termfreq() == 1);
1090 ati++;
1091 TEST(ati != db.allterms_end());
1092 TEST(*ati == "one");
1093 TEST(ati.get_termfreq() == 1);
1095 ++ati;
1096 TEST(ati != db.allterms_end());
1097 TEST(*ati == "six");
1098 TEST(ati.get_termfreq() == 3);
1100 ati++;
1101 TEST(ati != db.allterms_end());
1102 TEST(*ati == "three");
1103 TEST(ati.get_termfreq() == 3);
1105 ati++;
1106 TEST(ati != db.allterms_end());
1107 TEST(*ati == "two");
1108 TEST(ati.get_termfreq() == 2);
1110 ati++;
1111 TEST(ati == db.allterms_end());
1113 return true;
1116 // test that skip_to sets at_end (regression test)
1117 DEFINE_TESTCASE(allterms3, backend) {
1118 Xapian::Database db;
1119 db.add_database(get_database("apitest_allterms"));
1120 Xapian::TermIterator ati = db.allterms_begin();
1122 ati.skip_to(string("zzzzzz"));
1123 TEST(ati == db.allterms_end());
1125 return true;
1128 // test that next ignores extra entries due to long posting lists being
1129 // chunked (regression test for quartz)
1130 DEFINE_TESTCASE(allterms4, backend) {
1131 // apitest_allterms4 contains 682 documents each containing just the word
1132 // "foo". 682 was the magic number which started to cause Quartz problems.
1133 Xapian::Database db = get_database("apitest_allterms4");
1135 Xapian::TermIterator i = db.allterms_begin();
1136 TEST(i != db.allterms_end());
1137 TEST(*i == "foo");
1138 TEST(i.get_termfreq() == 682);
1139 ++i;
1140 TEST(i == db.allterms_end());
1142 return true;
1145 // test that skip_to with an exact match sets the current term (regression test
1146 // for quartz)
1147 DEFINE_TESTCASE(allterms5, backend) {
1148 Xapian::Database db;
1149 db.add_database(get_database("apitest_allterms"));
1150 Xapian::TermIterator ati = db.allterms_begin();
1151 ati.skip_to("three");
1152 TEST(ati != db.allterms_end());
1153 TEST_EQUAL(*ati, "three");
1155 return true;
1158 // test allterms iterators with prefixes
1159 DEFINE_TESTCASE(allterms6, backend) {
1160 Xapian::Database db;
1161 db.add_database(get_database("apitest_allterms"));
1162 db.add_database(get_database("apitest_allterms2"));
1164 Xapian::TermIterator ati = db.allterms_begin("three");
1165 TEST(ati != db.allterms_end("three"));
1166 TEST_EQUAL(*ati, "three");
1167 ati.skip_to("three");
1168 TEST(ati != db.allterms_end("three"));
1169 TEST_EQUAL(*ati, "three");
1170 ati++;
1171 TEST(ati == db.allterms_end("three"));
1173 ati = db.allterms_begin("thre");
1174 TEST(ati != db.allterms_end("thre"));
1175 TEST_EQUAL(*ati, "three");
1176 ati.skip_to("three");
1177 TEST(ati != db.allterms_end("thre"));
1178 TEST_EQUAL(*ati, "three");
1179 ati++;
1180 TEST(ati == db.allterms_end("thre"));
1182 ati = db.allterms_begin("f");
1183 TEST(ati != db.allterms_end("f"));
1184 TEST_EQUAL(*ati, "five");
1185 TEST(ati != db.allterms_end("f"));
1186 ati.skip_to("three");
1187 TEST(ati == db.allterms_end("f"));
1189 ati = db.allterms_begin("f");
1190 TEST(ati != db.allterms_end("f"));
1191 TEST_EQUAL(*ati, "five");
1192 ati++;
1193 TEST(ati != db.allterms_end("f"));
1194 TEST_EQUAL(*ati, "four");
1195 ati++;
1196 TEST(ati == db.allterms_end("f"));
1198 ati = db.allterms_begin("absent");
1199 TEST(ati == db.allterms_end("absent"));
1201 return true;
1204 // test that searching for a term with a special characters in it works
1205 DEFINE_TESTCASE(specialterms1, backend) {
1206 Xapian::Enquire enquire(get_database("apitest_space"));
1207 Xapian::MSet mymset;
1208 Xapian::doccount count;
1209 Xapian::MSetIterator m;
1210 Xapian::Stem stemmer("english");
1212 enquire.set_query(stemmer("new\nline"));
1213 mymset = enquire.get_mset(0, 10);
1214 TEST_MSET_SIZE(mymset, 1);
1215 count = 0;
1216 for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1217 TEST_EQUAL(count, 1);
1219 for (Xapian::valueno value_no = 0; value_no < 7; ++value_no) {
1220 string value = mymset.begin().get_document().get_value(value_no);
1221 TEST_NOT_EQUAL(value, "");
1222 if (value_no == 0) {
1223 TEST(value.size() > 263);
1224 TEST_EQUAL(static_cast<unsigned char>(value[262]), 255);
1225 for (int k = 0; k < 256; ++k) {
1226 TEST_EQUAL(static_cast<unsigned char>(value[k + 7]), k);
1231 enquire.set_query(stemmer(string("big\0zero", 8)));
1232 mymset = enquire.get_mset(0, 10);
1233 TEST_MSET_SIZE(mymset, 1);
1234 count = 0;
1235 for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1236 TEST_EQUAL(count, 1);
1238 return true;
1241 // test that terms with a special characters in appear correctly when iterating
1242 // allterms
1243 DEFINE_TESTCASE(specialterms2, backend) {
1244 Xapian::Database db(get_database("apitest_space"));
1246 // Check the terms are all as expected (after stemming) and that allterms
1247 // copes with iterating over them.
1248 Xapian::TermIterator t;
1249 t = db.allterms_begin();
1250 TEST_EQUAL(*t, "back\\slash"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1251 TEST_EQUAL(*t, string("big\0zero", 8)); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1252 TEST_EQUAL(*t, "new\nlin"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1253 TEST_EQUAL(*t, "one\x01on"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1254 TEST_EQUAL(*t, "space man"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1255 TEST_EQUAL(*t, "tab\tbi"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1256 TEST_EQUAL(*t, "tu\x02tu"); ++t; TEST_EQUAL(t, db.allterms_end());
1258 // Now check that skip_to exactly a term containing a zero byte works.
1259 // This is a regression test for flint and quartz - an Assert() used to
1260 // fire in debug builds (the Assert was wrong - the actual code handled
1261 // this OK).
1262 t = db.allterms_begin();
1263 t.skip_to(string("big\0zero", 8));
1264 TEST_NOT_EQUAL(t, db.allterms_end());
1265 TEST_EQUAL(*t, string("big\0zero", 8));
1267 return true;
1270 // test that rsets behave correctly with multiDBs
1271 DEFINE_TESTCASE(rsetmultidb2, backend && !multi) {
1272 Xapian::Database mydb1(get_database("apitest_rset", "apitest_simpledata2"));
1273 Xapian::Database mydb2(get_database("apitest_rset"));
1274 mydb2.add_database(get_database("apitest_simpledata2"));
1276 Xapian::Enquire enquire1(mydb1);
1277 Xapian::Enquire enquire2(mydb2);
1279 Xapian::Query myquery = query("is");
1281 enquire1.set_query(myquery);
1282 enquire2.set_query(myquery);
1284 Xapian::RSet myrset1;
1285 Xapian::RSet myrset2;
1286 myrset1.add_document(4);
1287 myrset2.add_document(2);
1289 Xapian::MSet mymset1a = enquire1.get_mset(0, 10);
1290 Xapian::MSet mymset1b = enquire1.get_mset(0, 10, &myrset1);
1291 Xapian::MSet mymset2a = enquire2.get_mset(0, 10);
1292 Xapian::MSet mymset2b = enquire2.get_mset(0, 10, &myrset2);
1294 mset_expect_order(mymset1a, 4, 3);
1295 mset_expect_order(mymset1b, 4, 3);
1296 mset_expect_order(mymset2a, 2, 5);
1297 mset_expect_order(mymset2b, 2, 5);
1299 TEST(mset_range_is_same_weights(mymset1a, 0, mymset2a, 0, 2));
1300 TEST(mset_range_is_same_weights(mymset1b, 0, mymset2b, 0, 2));
1301 TEST_NOT_EQUAL(mymset1a, mymset1b);
1302 TEST_NOT_EQUAL(mymset2a, mymset2b);
1304 return true;
1307 // tests an expand across multiple databases
1308 DEFINE_TESTCASE(multiexpand1, backend && !multi) {
1309 Xapian::Database mydb1(get_database("apitest_simpledata", "apitest_simpledata2"));
1310 Xapian::Enquire enquire1(mydb1);
1312 Xapian::Database mydb2(get_database("apitest_simpledata"));
1313 mydb2.add_database(get_database("apitest_simpledata2"));
1314 Xapian::Enquire enquire2(mydb2);
1316 // make simple equivalent rsets, with a document from each database in each.
1317 Xapian::RSet rset1;
1318 Xapian::RSet rset2;
1319 rset1.add_document(1);
1320 rset1.add_document(7);
1321 rset2.add_document(1);
1322 rset2.add_document(2);
1324 // Retrieve all the ESet results in each of the three setups:
1326 // This is the single database one.
1327 Xapian::ESet eset1 = enquire1.get_eset(1000, rset1);
1329 // This is the multi database with approximation
1330 Xapian::ESet eset2 = enquire2.get_eset(1000, rset2);
1332 // This is the multi database without approximation
1333 Xapian::ESet eset3 = enquire2.get_eset(1000, rset2, Xapian::Enquire::USE_EXACT_TERMFREQ);
1335 TEST_EQUAL(eset1.size(), eset3.size());
1337 Xapian::ESetIterator i = eset1.begin();
1338 Xapian::ESetIterator j = eset3.begin();
1339 while (i != eset1.end() && j != eset3.end()) {
1340 TEST_EQUAL(*i, *j);
1341 TEST_EQUAL(i.get_weight(), j.get_weight());
1342 ++i;
1343 ++j;
1345 TEST(i == eset1.end());
1346 TEST(j == eset3.end());
1348 bool eset1_eq_eset2 = true;
1349 i = eset1.begin();
1350 j = eset2.begin();
1351 while (i != eset1.end() && j != eset2.end()) {
1352 if (i.get_weight() != j.get_weight()) {
1353 eset1_eq_eset2 = false;
1354 break;
1356 ++i;
1357 ++j;
1359 TEST(!eset1_eq_eset2);
1361 return true;
1364 // tests that opening a non-existent postlist returns an empty list
1365 DEFINE_TESTCASE(postlist1, backend) {
1366 Xapian::Database db(get_database("apitest_simpledata"));
1368 TEST_EQUAL(db.postlist_begin("rosebud"), db.postlist_end("rosebud"));
1370 string s = "let_us_see_if_we_can_break_it_with_a_really_really_long_term.";
1371 for (int i = 0; i < 8; ++i) {
1372 s += s;
1373 TEST_EQUAL(db.postlist_begin(s), db.postlist_end(s));
1376 // A regression test (no, really!)
1377 TEST_NOT_EQUAL(db.postlist_begin("a"), db.postlist_end("a"));
1379 return true;
1382 // tests that a Xapian::PostingIterator works as an STL iterator
1383 DEFINE_TESTCASE(postlist2, backend) {
1384 Xapian::Database db(get_database("apitest_simpledata"));
1385 Xapian::PostingIterator p;
1386 p = db.postlist_begin("this");
1387 Xapian::PostingIterator pend = db.postlist_end("this");
1389 TEST(p.get_description() != "PostingIterator()");
1391 // test operator= creates a copy which compares equal
1392 Xapian::PostingIterator p_copy = p;
1393 TEST_EQUAL(p, p_copy);
1395 TEST(p_copy.get_description() != "PostingIterator()");
1397 // test copy constructor creates a copy which compares equal
1398 Xapian::PostingIterator p_clone(p);
1399 TEST_EQUAL(p, p_clone);
1401 TEST(p_clone.get_description() != "PostingIterator()");
1403 vector<Xapian::docid> v(p, pend);
1405 p = db.postlist_begin("this");
1406 pend = db.postlist_end("this");
1407 vector<Xapian::docid>::const_iterator i;
1408 for (i = v.begin(); i != v.end(); ++i) {
1409 TEST_NOT_EQUAL(p, pend);
1410 TEST_EQUAL(*i, *p);
1411 p++;
1413 TEST_EQUAL(p, pend);
1415 TEST_STRINGS_EQUAL(p.get_description(), "PostingIterator()");
1416 TEST_STRINGS_EQUAL(pend.get_description(), "PostingIterator()");
1418 return true;
1421 // tests that a Xapian::PostingIterator still works when the DB is deleted
1422 DEFINE_TESTCASE(postlist3, backend) {
1423 Xapian::PostingIterator u;
1425 Xapian::Database db_temp(get_database("apitest_simpledata"));
1426 u = db_temp.postlist_begin("this");
1429 Xapian::Database db(get_database("apitest_simpledata"));
1430 Xapian::PostingIterator p = db.postlist_begin("this");
1431 Xapian::PostingIterator pend = db.postlist_end("this");
1433 while (p != pend) {
1434 TEST_EQUAL(*p, *u);
1435 p++;
1436 u++;
1438 return true;
1441 // tests skip_to
1442 DEFINE_TESTCASE(postlist4, backend) {
1443 Xapian::Database db(get_database("apitest_simpledata"));
1444 Xapian::PostingIterator i = db.postlist_begin("this");
1445 i.skip_to(1);
1446 i.skip_to(999999999);
1447 TEST(i == db.postlist_end("this"));
1448 return true;
1451 // tests long postlists
1452 DEFINE_TESTCASE(postlist5, backend) {
1453 Xapian::Database db(get_database("apitest_manydocs"));
1454 TEST_EQUAL_DOUBLE(db.get_avlength(), 4);
1455 Xapian::PostingIterator i = db.postlist_begin("this");
1456 unsigned int j = 1;
1457 while (i != db.postlist_end("this")) {
1458 TEST_EQUAL(*i, j);
1459 i++;
1460 j++;
1462 TEST_EQUAL(j, 513);
1463 return true;
1466 // tests document length in postlists
1467 DEFINE_TESTCASE(postlist6, backend) {
1468 Xapian::Database db(get_database("apitest_simpledata"));
1469 Xapian::PostingIterator i = db.postlist_begin("this");
1470 TEST(i != db.postlist_end("this"));
1471 while (i != db.postlist_end("this")) {
1472 TEST_EQUAL(i.get_doclength(), db.get_doclength(*i));
1473 TEST_EQUAL(i.get_unique_terms(), db.get_unique_terms(*i));
1474 TEST_REL(i.get_wdf(),<=,i.get_doclength());
1475 TEST_REL(1,<=,i.get_unique_terms());
1476 // The next two aren't necessarily true if there are terms with wdf=0
1477 // in the document, but that isn't the case here.
1478 TEST_REL(i.get_unique_terms(),<=,i.get_doclength());
1479 TEST_REL(i.get_wdf() + i.get_unique_terms() - 1,<=,i.get_doclength());
1480 ++i;
1482 return true;
1485 // tests collection frequency
1486 DEFINE_TESTCASE(collfreq1, backend) {
1487 Xapian::Database db(get_database("apitest_simpledata"));
1489 TEST_EQUAL(db.get_collection_freq("this"), 11);
1490 TEST_EQUAL(db.get_collection_freq("first"), 1);
1491 TEST_EQUAL(db.get_collection_freq("last"), 0);
1492 TEST_EQUAL(db.get_collection_freq("word"), 9);
1494 Xapian::Database db1(get_database("apitest_simpledata", "apitest_simpledata2"));
1495 Xapian::Database db2(get_database("apitest_simpledata"));
1496 db2.add_database(get_database("apitest_simpledata2"));
1498 TEST_EQUAL(db1.get_collection_freq("this"), 15);
1499 TEST_EQUAL(db1.get_collection_freq("first"), 1);
1500 TEST_EQUAL(db1.get_collection_freq("last"), 0);
1501 TEST_EQUAL(db1.get_collection_freq("word"), 11);
1502 TEST_EQUAL(db2.get_collection_freq("this"), 15);
1503 TEST_EQUAL(db2.get_collection_freq("first"), 1);
1504 TEST_EQUAL(db2.get_collection_freq("last"), 0);
1505 TEST_EQUAL(db2.get_collection_freq("word"), 11);
1507 return true;
1510 // Regression test for split msets being incorrect when sorting
1511 DEFINE_TESTCASE(sortvalue1, backend) {
1512 Xapian::Enquire enquire(get_database("apitest_simpledata"));
1513 enquire.set_query(Xapian::Query("this"));
1515 for (int pass = 1; pass <= 2; ++pass) {
1516 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
1517 tout << "Sorting on value " << value_no << endl;
1518 enquire.set_sort_by_value(value_no, true);
1519 Xapian::MSet allbset = enquire.get_mset(0, 100);
1520 Xapian::MSet partbset1 = enquire.get_mset(0, 3);
1521 Xapian::MSet partbset2 = enquire.get_mset(3, 97);
1522 TEST_EQUAL(allbset.size(), partbset1.size() + partbset2.size());
1524 bool ok = true;
1525 int n = 0;
1526 Xapian::MSetIterator i, j;
1527 j = allbset.begin();
1528 for (i = partbset1.begin(); i != partbset1.end(); ++i) {
1529 tout << "Entry " << n << ": " << *i << " | " << *j << endl;
1530 TEST(j != allbset.end());
1531 if (*i != *j) ok = false;
1532 ++j;
1533 ++n;
1535 tout << "===\n";
1536 for (i = partbset2.begin(); i != partbset2.end(); ++i) {
1537 tout << "Entry " << n << ": " << *i << " | " << *j << endl;
1538 TEST(j != allbset.end());
1539 if (*i != *j) ok = false;
1540 ++j;
1541 ++n;
1543 TEST(j == allbset.end());
1544 if (!ok)
1545 FAIL_TEST("Split msets aren't consistent with unsplit");
1547 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1550 return true;
1553 // consistency check match - vary mset size and check results agree.
1554 // consistency1 will run on the remote backend, but it's particularly slow
1555 // with that, and testing it there doesn't actually improve the test
1556 // coverage really.
1557 DEFINE_TESTCASE(consistency1, backend && !remote) {
1558 Xapian::Database db(get_database("etext"));
1559 Xapian::Enquire enquire(db);
1560 enquire.set_query(Xapian::Query(Xapian::Query::OP_OR, Xapian::Query("the"), Xapian::Query("sky")));
1561 Xapian::doccount lots = 214;
1562 Xapian::MSet bigmset = enquire.get_mset(0, lots);
1563 TEST_EQUAL(bigmset.size(), lots);
1564 try {
1565 for (Xapian::doccount start = 0; start < lots; ++start) {
1566 for (Xapian::doccount size = 0; size < lots - start; ++size) {
1567 Xapian::MSet mset = enquire.get_mset(start, size);
1568 if (mset.size()) {
1569 TEST_EQUAL(start + mset.size(),
1570 min(start + size, bigmset.size()));
1571 } else if (size) {
1572 // tout << start << mset.size() << bigmset.size() << endl;
1573 TEST(start >= bigmset.size());
1575 for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1576 TEST_EQUAL(*mset[i], *bigmset[start + i]);
1577 TEST_EQUAL_DOUBLE(mset[i].get_weight(),
1578 bigmset[start + i].get_weight());
1582 } catch (const Xapian::NetworkTimeoutError &) {
1583 // consistency1 is a long test - may timeout with the remote backend...
1584 SKIP_TEST("Test taking too long");
1586 return true;
1589 // tests that specifying a nonexistent input file throws an exception.
1590 DEFINE_TESTCASE(chertdatabaseopeningerror1, chert) {
1591 #ifdef XAPIAN_HAS_CHERT_BACKEND
1592 mkdir(".chert", 0755);
1594 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
1595 Xapian::Database(".chert/nosuchdirectory",
1596 Xapian::DB_BACKEND_CHERT));
1597 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
1598 Xapian::WritableDatabase(".chert/nosuchdirectory",
1599 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT));
1601 mkdir(".chert/emptydirectory", 0700);
1602 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
1603 Xapian::Database(".chert/emptydirectory",
1604 Xapian::DB_BACKEND_CHERT));
1606 touch(".chert/somefile");
1607 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
1608 Xapian::Database(".chert/somefile",
1609 Xapian::DB_BACKEND_CHERT));
1610 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
1611 Xapian::WritableDatabase(".chert/somefile",
1612 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT));
1613 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1614 Xapian::WritableDatabase(".chert/somefile",
1615 Xapian::DB_CREATE|Xapian::DB_BACKEND_CHERT));
1616 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1617 Xapian::WritableDatabase(".chert/somefile",
1618 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT));
1619 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1620 Xapian::WritableDatabase(".chert/somefile",
1621 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT));
1622 #endif
1624 return true;
1627 /// Test opening of a chert database
1628 DEFINE_TESTCASE(chertdatabaseopen1, chert) {
1629 #ifdef XAPIAN_HAS_CHERT_BACKEND
1630 const string dbdir = ".chert/test_chertdatabaseopen1";
1631 mkdir(".chert", 0755);
1634 rm_rf(dbdir);
1635 Xapian::WritableDatabase wdb(dbdir,
1636 Xapian::DB_CREATE|Xapian::DB_BACKEND_CHERT);
1637 TEST_EXCEPTION(Xapian::DatabaseLockError,
1638 Xapian::WritableDatabase(dbdir,
1639 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT));
1640 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1644 rm_rf(dbdir);
1645 Xapian::WritableDatabase wdb(dbdir,
1646 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT);
1647 TEST_EXCEPTION(Xapian::DatabaseLockError,
1648 Xapian::WritableDatabase(dbdir,
1649 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT));
1650 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1654 rm_rf(dbdir);
1655 Xapian::WritableDatabase wdb(dbdir,
1656 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT);
1657 TEST_EXCEPTION(Xapian::DatabaseLockError,
1658 Xapian::WritableDatabase(dbdir,
1659 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT));
1660 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1664 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1665 Xapian::WritableDatabase(dbdir,
1666 Xapian::DB_CREATE|Xapian::DB_BACKEND_CHERT));
1667 Xapian::WritableDatabase wdb(dbdir,
1668 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT);
1669 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1673 Xapian::WritableDatabase wdb(dbdir,
1674 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT);
1675 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1679 Xapian::WritableDatabase wdb(dbdir,
1680 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT);
1681 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1683 #endif
1685 return true;
1688 // feature test for Enquire:
1689 // set_sort_by_value
1690 // set_sort_by_value_then_relevance
1691 // set_sort_by_relevance_then_value
1692 // Prior to 1.2.17 and 1.3.2, order8 and order9 were swapped, and
1693 // set_sort_by_relevance_then_value was buggy, so this testcase now serves as
1694 // a regression test for that bug.
1695 DEFINE_TESTCASE(sortrel1, backend) {
1696 Xapian::Enquire enquire(get_database("apitest_sortrel"));
1697 enquire.set_sort_by_value(1, true);
1698 enquire.set_query(Xapian::Query("woman"));
1700 static const Xapian::docid order1[] = { 1,2,3,4,5,6,7,8,9 };
1701 static const Xapian::docid order2[] = { 2,1,3,6,5,4,7,9,8 };
1702 static const Xapian::docid order3[] = { 3,2,1,6,5,4,9,8,7 };
1703 static const Xapian::docid order4[] = { 7,8,9,4,5,6,1,2,3 };
1704 static const Xapian::docid order5[] = { 9,8,7,6,5,4,3,2,1 };
1705 static const Xapian::docid order6[] = { 7,9,8,6,5,4,2,1,3 };
1706 static const Xapian::docid order7[] = { 7,9,8,6,5,4,2,1,3 };
1707 static const Xapian::docid order8[] = { 2,6,7,1,5,9,3,4,8 };
1708 static const Xapian::docid order9[] = { 7,6,2,9,5,1,8,4,3 };
1710 Xapian::MSet mset;
1711 size_t i;
1713 mset = enquire.get_mset(0, 10);
1714 TEST_EQUAL(mset.size(), sizeof(order1) / sizeof(Xapian::docid));
1715 for (i = 0; i < sizeof(order1) / sizeof(Xapian::docid); ++i) {
1716 TEST_EQUAL(*mset[i], order1[i]);
1719 enquire.set_sort_by_value_then_relevance(1, true);
1721 mset = enquire.get_mset(0, 10);
1722 TEST_EQUAL(mset.size(), sizeof(order2) / sizeof(Xapian::docid));
1723 for (i = 0; i < sizeof(order2) / sizeof(Xapian::docid); ++i) {
1724 TEST_EQUAL(*mset[i], order2[i]);
1727 enquire.set_sort_by_value(1, true);
1729 mset = enquire.get_mset(0, 10);
1730 TEST_EQUAL(mset.size(), sizeof(order1) / sizeof(Xapian::docid));
1731 for (i = 0; i < sizeof(order1) / sizeof(Xapian::docid); ++i) {
1732 TEST_EQUAL(*mset[i], order1[i]);
1735 enquire.set_sort_by_value_then_relevance(1, true);
1736 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1738 mset = enquire.get_mset(0, 10);
1739 TEST_EQUAL(mset.size(), sizeof(order2) / sizeof(Xapian::docid));
1740 for (i = 0; i < sizeof(order2) / sizeof(Xapian::docid); ++i) {
1741 TEST_EQUAL(*mset[i], order2[i]);
1744 enquire.set_sort_by_value(1, true);
1745 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1747 mset = enquire.get_mset(0, 10);
1748 TEST_EQUAL(mset.size(), sizeof(order3) / sizeof(Xapian::docid));
1749 for (i = 0; i < sizeof(order3) / sizeof(Xapian::docid); ++i) {
1750 TEST_EQUAL(*mset[i], order3[i]);
1753 enquire.set_sort_by_value(1, false);
1754 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1755 mset = enquire.get_mset(0, 10);
1756 TEST_EQUAL(mset.size(), sizeof(order4) / sizeof(Xapian::docid));
1757 for (i = 0; i < sizeof(order4) / sizeof(Xapian::docid); ++i) {
1758 TEST_EQUAL(*mset[i], order4[i]);
1761 enquire.set_sort_by_value(1, false);
1762 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1763 mset = enquire.get_mset(0, 10);
1764 TEST_EQUAL(mset.size(), sizeof(order5) / sizeof(Xapian::docid));
1765 for (i = 0; i < sizeof(order5) / sizeof(Xapian::docid); ++i) {
1766 TEST_EQUAL(*mset[i], order5[i]);
1769 enquire.set_sort_by_value_then_relevance(1, false);
1770 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1771 mset = enquire.get_mset(0, 10);
1772 TEST_EQUAL(mset.size(), sizeof(order6) / sizeof(Xapian::docid));
1773 for (i = 0; i < sizeof(order6) / sizeof(Xapian::docid); ++i) {
1774 TEST_EQUAL(*mset[i], order6[i]);
1777 enquire.set_sort_by_value_then_relevance(1, false);
1778 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1779 mset = enquire.get_mset(0, 10);
1780 TEST_EQUAL(mset.size(), sizeof(order7) / sizeof(Xapian::docid));
1781 for (i = 0; i < sizeof(order7) / sizeof(Xapian::docid); ++i) {
1782 TEST_EQUAL(*mset[i], order7[i]);
1785 enquire.set_sort_by_relevance_then_value(1, true);
1786 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1787 mset = enquire.get_mset(0, 10);
1788 TEST_EQUAL(mset.size(), sizeof(order8) / sizeof(Xapian::docid));
1789 for (i = 0; i < sizeof(order8) / sizeof(Xapian::docid); ++i) {
1790 TEST_EQUAL(*mset[i], order8[i]);
1793 enquire.set_sort_by_relevance_then_value(1, true);
1794 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1795 mset = enquire.get_mset(0, 10);
1796 TEST_EQUAL(mset.size(), sizeof(order8) / sizeof(Xapian::docid));
1797 for (i = 0; i < sizeof(order8) / sizeof(Xapian::docid); ++i) {
1798 TEST_EQUAL(*mset[i], order8[i]);
1801 enquire.set_sort_by_relevance_then_value(1, false);
1802 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1803 mset = enquire.get_mset(0, 10);
1804 TEST_EQUAL(mset.size(), sizeof(order9) / sizeof(Xapian::docid));
1805 for (i = 0; i < sizeof(order9) / sizeof(Xapian::docid); ++i) {
1806 TEST_EQUAL(*mset[i], order9[i]);
1809 enquire.set_sort_by_relevance_then_value(1, false);
1810 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1811 mset = enquire.get_mset(0, 10);
1812 TEST_EQUAL(mset.size(), sizeof(order9) / sizeof(Xapian::docid));
1813 for (i = 0; i < sizeof(order9) / sizeof(Xapian::docid); ++i) {
1814 TEST_EQUAL(*mset[i], order9[i]);
1817 return true;
1820 // Test network stats and local stats give the same results.
1821 DEFINE_TESTCASE(netstats1, remote) {
1822 BackendManagerLocal local_manager;
1823 local_manager.set_datadir(test_driver::get_srcdir() + "/testdata/");
1825 static const char * const words[] = { "paragraph", "word" };
1826 Xapian::Query query(Xapian::Query::OP_OR, words, words + 2);
1827 const size_t MSET_SIZE = 10;
1829 Xapian::RSet rset;
1830 rset.add_document(4);
1831 rset.add_document(9);
1833 Xapian::MSet mset_alllocal;
1835 Xapian::Database db;
1836 db.add_database(local_manager.get_database("apitest_simpledata"));
1837 db.add_database(local_manager.get_database("apitest_simpledata2"));
1839 Xapian::Enquire enq(db);
1840 enq.set_query(query);
1841 mset_alllocal = enq.get_mset(0, MSET_SIZE, &rset);
1845 Xapian::Database db;
1846 db.add_database(local_manager.get_database("apitest_simpledata"));
1847 db.add_database(get_database("apitest_simpledata2"));
1849 Xapian::Enquire enq(db);
1850 enq.set_query(query);
1851 Xapian::MSet mset = enq.get_mset(0, MSET_SIZE, &rset);
1852 TEST_EQUAL(mset.get_matches_lower_bound(), mset_alllocal.get_matches_lower_bound());
1853 TEST_EQUAL(mset.get_matches_upper_bound(), mset_alllocal.get_matches_upper_bound());
1854 TEST_EQUAL(mset.get_matches_estimated(), mset_alllocal.get_matches_estimated());
1855 TEST_EQUAL(mset.get_max_attained(), mset_alllocal.get_max_attained());
1856 TEST_EQUAL(mset.size(), mset_alllocal.size());
1857 TEST(mset_range_is_same(mset, 0, mset_alllocal, 0, mset.size()));
1861 Xapian::Database db;
1862 db.add_database(get_database("apitest_simpledata"));
1863 db.add_database(local_manager.get_database("apitest_simpledata2"));
1865 Xapian::Enquire enq(db);
1866 enq.set_query(query);
1867 Xapian::MSet mset = enq.get_mset(0, MSET_SIZE, &rset);
1868 TEST_EQUAL(mset.get_matches_lower_bound(), mset_alllocal.get_matches_lower_bound());
1869 TEST_EQUAL(mset.get_matches_upper_bound(), mset_alllocal.get_matches_upper_bound());
1870 TEST_EQUAL(mset.get_matches_estimated(), mset_alllocal.get_matches_estimated());
1871 TEST_EQUAL(mset.get_max_attained(), mset_alllocal.get_max_attained());
1872 TEST_EQUAL(mset.size(), mset_alllocal.size());
1873 TEST(mset_range_is_same(mset, 0, mset_alllocal, 0, mset.size()));
1877 Xapian::Database db;
1878 db.add_database(get_database("apitest_simpledata"));
1879 db.add_database(get_database("apitest_simpledata2"));
1881 Xapian::Enquire enq(db);
1882 enq.set_query(query);
1883 Xapian::MSet mset = enq.get_mset(0, MSET_SIZE, &rset);
1884 TEST_EQUAL(mset.get_matches_lower_bound(), mset_alllocal.get_matches_lower_bound());
1885 TEST_EQUAL(mset.get_matches_upper_bound(), mset_alllocal.get_matches_upper_bound());
1886 TEST_EQUAL(mset.get_matches_estimated(), mset_alllocal.get_matches_estimated());
1887 TEST_EQUAL(mset.get_max_attained(), mset_alllocal.get_max_attained());
1888 TEST_EQUAL(mset.size(), mset_alllocal.size());
1889 TEST(mset_range_is_same(mset, 0, mset_alllocal, 0, mset.size()));
1892 return true;
1895 // Coordinate matching - scores 1 for each matching term
1896 class MyWeight : public Xapian::Weight {
1897 double scale_factor;
1899 public:
1900 MyWeight * clone() const {
1901 return new MyWeight;
1903 void init(double factor) {
1904 scale_factor = factor;
1906 MyWeight() { }
1907 ~MyWeight() { }
1908 std::string name() const { return "MyWeight"; }
1909 string serialise() const { return string(); }
1910 MyWeight * unserialise(const string &) const { return new MyWeight; }
1911 double get_sumpart(Xapian::termcount, Xapian::termcount, Xapian::termcount) const {
1912 return scale_factor;
1914 double get_maxpart() const { return scale_factor; }
1916 double get_sumextra(Xapian::termcount, Xapian::termcount) const { return 0; }
1917 double get_maxextra() const { return 0; }
1920 // tests user weighting scheme.
1921 // Would work with remote if we registered the weighting scheme.
1922 // FIXME: do this so we also test that functionality...
1923 DEFINE_TESTCASE(userweight1, backend && !remote) {
1924 Xapian::Enquire enquire(get_database("apitest_simpledata"));
1925 enquire.set_weighting_scheme(MyWeight());
1926 static const char * const query[] = {
1927 "this", "line", "paragraph", "rubbish"
1929 enquire.set_query(Xapian::Query(Xapian::Query::OP_OR, query,
1930 query + sizeof(query) / sizeof(query[0])));
1931 Xapian::MSet mymset1 = enquire.get_mset(0, 100);
1932 // MyWeight scores 1 for each matching term, so the weight should equal
1933 // the number of matching terms.
1934 for (Xapian::MSetIterator i = mymset1.begin(); i != mymset1.end(); ++i) {
1935 Xapian::termcount matching_terms = 0;
1936 Xapian::TermIterator t = enquire.get_matching_terms_begin(i);
1937 while (t != enquire.get_matching_terms_end(i)) {
1938 ++matching_terms;
1939 ++t;
1941 TEST_EQUAL(i.get_weight(), matching_terms);
1944 return true;
1947 // tests MatchAll queries
1948 // This is a regression test, which failed with assertion failures in
1949 // revision 9094. Also check that the results aren't ranked by relevance
1950 // (regression test for bug fixed in 1.0.9).
1951 DEFINE_TESTCASE(matchall1, backend) {
1952 Xapian::Database db(get_database("apitest_simpledata"));
1953 Xapian::Enquire enquire(db);
1954 enquire.set_query(Xapian::Query::MatchAll);
1955 Xapian::MSet mset = enquire.get_mset(0, 10);
1956 TEST_EQUAL(mset.get_matches_lower_bound(), db.get_doccount());
1957 TEST_EQUAL(mset.get_uncollapsed_matches_lower_bound(), db.get_doccount());
1959 enquire.set_query(Xapian::Query(Xapian::Query::OP_OR,
1960 Xapian::Query("nosuchterm"),
1961 Xapian::Query::MatchAll));
1962 mset = enquire.get_mset(0, 10);
1963 TEST_EQUAL(mset.get_matches_lower_bound(), db.get_doccount());
1964 TEST_EQUAL(mset.get_uncollapsed_matches_lower_bound(), db.get_doccount());
1966 // Check that the results aren't ranked by relevance (fixed in 1.0.9).
1967 TEST(mset.size() > 1);
1968 TEST_EQUAL(mset[mset.size() - 1].get_weight(), 0);
1969 TEST_EQUAL(*mset[0], 1);
1970 TEST_EQUAL(*mset[mset.size() - 1], mset.size());
1972 return true;
1975 // Test using a ValueSetMatchDecider
1976 DEFINE_TESTCASE(valuesetmatchdecider2, backend && !remote) {
1977 Xapian::Database db(get_database("apitest_phrase"));
1978 Xapian::Enquire enq(db);
1979 enq.set_query(Xapian::Query("leav"));
1981 Xapian::ValueSetMatchDecider vsmd1(1, true);
1982 vsmd1.add_value("n");
1983 Xapian::ValueSetMatchDecider vsmd2(1, false);
1984 vsmd2.add_value("n");
1986 Xapian::MSet mymset = enq.get_mset(0, 20);
1987 mset_expect_order(mymset, 8, 6, 4, 5, 7, 10, 12, 11, 13, 9, 14);
1988 mymset = enq.get_mset(0, 20, 0, NULL, &vsmd1);
1989 mset_expect_order(mymset, 6, 12);
1990 mymset = enq.get_mset(0, 20, 0, NULL, &vsmd2);
1991 mset_expect_order(mymset, 8, 4, 5, 7, 10, 11, 13, 9, 14);
1993 return true;