1 /** @file
2 * @brief tests which need a backend
3 */
4 /* Copyright 1999,2000,2001 BrightStation PLC
5 * Copyright 2002 Ananova Ltd
6 * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2011,2012,2013,2015,2016,2017,2019 Olly Betts
7 * Copyright 2006,2007,2008,2009 Lemur Consulting Ltd
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 * USA
23 */
24
25 #include <config.h>
26
27 #include "api_db.h"
28
29 #include <algorithm>
30 #include <fstream>
31 #include <map>
32 #include <string>
33 #include <vector>
34 #include "safenetdb.h" // For gai_strerror().
35 #include "safesysstat.h" // For mkdir().
36 #include "safeunistd.h" // For sleep().
37
38 #include <xapian.h>
39
40 #include "backendmanager.h"
41 #include "testsuite.h"
42 #include "testutils.h"
43 #include "unixcmds.h"
44
45 #include "apitest.h"
46
47 using namespace std;
48
49 static Xapian::Query
query(const string & t)50 query(const string &t)
51 {
52 return Xapian::Query(Xapian::Stem("english")(t));
53 }
54
55 // #######################################################################
56 // # Tests start here
57
58 // tests Xapian::Database::get_termfreq() and Xapian::Database::term_exists()
DEFINE_TESTCASE(termstats,backend)59 DEFINE_TESTCASE(termstats, backend) {
60 Xapian::Database db(get_database("apitest_simpledata"));
61
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);
68 }
69
70 // Check that stub databases work.
DEFINE_TESTCASE(stubdb1,path)71 DEFINE_TESTCASE(stubdb1, path) {
72 mkdir(".stub", 0755);
73 const char * dbpath = ".stub/stubdb1";
74 ofstream out(dbpath);
75 TEST(out.is_open());
76 out << "auto ../" << get_database_path("apitest_simpledata") << endl;
77 out.close();
78
79 {
80 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
81 Xapian::Enquire enquire(db);
82 enquire.set_query(Xapian::Query("word"));
83 enquire.get_mset(0, 10);
84 }
85 {
86 Xapian::Database db(dbpath);
87 Xapian::Enquire enquire(db);
88 enquire.set_query(Xapian::Query("word"));
89 enquire.get_mset(0, 10);
90 }
91
92 TEST_EQUAL(Xapian::Database::check(dbpath), 0);
93 }
94
95 // Check that stub databases work remotely.
DEFINE_TESTCASE(stubdb2,path)96 DEFINE_TESTCASE(stubdb2, path) {
97 mkdir(".stub", 0755);
98 const char * dbpath = ".stub/stubdb2";
99 ofstream out(dbpath);
100 TEST(out.is_open());
101 out << "remote :" << BackendManager::get_xapian_progsrv_command()
102 << ' ' << get_database_path("apitest_simpledata") << endl;
103 out.close();
104
105 try {
106 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
107 Xapian::Enquire enquire(db);
108 enquire.set_query(Xapian::Query("word"));
109 enquire.get_mset(0, 10);
110 } catch (Xapian::FeatureUnavailableError&) {
111 #ifdef XAPIAN_HAS_REMOTE_BACKEND
112 throw;
113 #endif
114 }
115
116 try {
117 Xapian::Database db(dbpath);
118 Xapian::Enquire enquire(db);
119 enquire.set_query(Xapian::Query("word"));
120 enquire.get_mset(0, 10);
121 } catch (Xapian::FeatureUnavailableError&) {
122 #ifdef XAPIAN_HAS_REMOTE_BACKEND
123 throw;
124 #endif
125 }
126
127 out.open(dbpath);
128 TEST(out.is_open());
129 out << "remote" << endl;
130 out.close();
131
132 // Quietly ignored prior to 1.4.1.
133 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
134 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB)
135 );
136
137 // Quietly ignored prior to 1.4.1.
138 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
139 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB)
140 );
141
142 #ifdef XAPIAN_HAS_REMOTE_BACKEND
143 # define EXPECTED_EXCEPTION Xapian::DatabaseOpeningError
144 #else
145 # define EXPECTED_EXCEPTION Xapian::FeatureUnavailableError
146 #endif
147
148 out.open(dbpath);
149 TEST(out.is_open());
150 out << "remote foo" << endl;
151 out.close();
152
153 // Quietly ignored prior to 1.4.1.
154 TEST_EXCEPTION(EXPECTED_EXCEPTION,
155 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB)
156 );
157
158 // Quietly ignored prior to 1.4.1.
159 TEST_EXCEPTION(EXPECTED_EXCEPTION,
160 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB)
161 );
162
163 #ifdef XAPIAN_HAS_REMOTE_BACKEND
164 out.open(dbpath);
165 TEST(out.is_open());
166 out << "remote [::1]:65535" << endl;
167 out.close();
168
169 try {
170 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
171 } catch (const Xapian::NetworkError& e) {
172 // 1.4.0 threw (on Linux) the confusing message:
173 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
174 // 1.4.1 throws (because we don't actually support IPv6 yet) on Linux (EAI_ADDRFAMILY):
175 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (nodename nor servname provided, or not known)
176 // or on OS X (EAI_NONAME):
177 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (Address family for hostname not supported)
178 //
179 // But NetBSD seems to resolve ::1 to an IPv4 address and then tries
180 // to connect to it (which hopefully fails), so just test the message
181 // doesn't match the bad 1.4.0 result.
182 TEST(e.get_msg().find("host [") == string::npos);
183 }
184
185 try {
186 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB);
187 } catch (const Xapian::NetworkError& e) {
188 // 1.4.0 threw (Linux):
189 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
190 // 1.4.1 throws (because we don't actually support IPv6 yet) on Linux (EAI_ADDRFAMILY):
191 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (nodename nor servname provided, or not known)
192 // or on OS X (EAI_NONAME):
193 // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (Address family for hostname not supported)
194 // So we test the message instead of the error string for portability.
195 //
196 // But NetBSD seems to resolve ::1 to an IPv4 address and then tries
197 // to connect to it (which hopefully fails), so just test the message
198 // doesn't match the bad 1.4.0 result.
199 TEST(e.get_msg().find("host [") == string::npos);
200 }
201 #endif
202
203 out.open(dbpath);
204 TEST(out.is_open());
205 // Invalid - the port number is required.
206 out << "remote [::1]" << endl;
207 out.close();
208
209 // 1.4.0 threw:
210 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
211 TEST_EXCEPTION(EXPECTED_EXCEPTION,
212 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
213 );
214
215 // 1.4.0 threw:
216 // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
217 TEST_EXCEPTION(EXPECTED_EXCEPTION,
218 Xapian::WritableDatabase db(dbpath, Xapian::DB_BACKEND_STUB);
219 );
220 }
221
222 // Regression test - bad entries were ignored after a good entry prior to 1.0.8.
DEFINE_TESTCASE(stubdb3,path)223 DEFINE_TESTCASE(stubdb3, path) {
224 mkdir(".stub", 0755);
225 const char * dbpath = ".stub/stubdb3";
226 ofstream out(dbpath);
227 TEST(out.is_open());
228 out << "auto ../" << get_database_path("apitest_simpledata") << "\n"
229 "bad line here\n";
230 out.close();
231
232 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
233 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB));
234
235 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
236 Xapian::Database db(dbpath));
237 }
238
239 // Test a stub database with just a bad entry.
240 DEFINE_TESTCASE(stubdb4, !backend) {
241 mkdir(".stub", 0755);
242 const char * dbpath = ".stub/stubdb4";
243 ofstream out(dbpath);
244 TEST(out.is_open());
245 out << "bad line here\n";
246 out.close();
247
248 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
249 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB));
250
251 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
252 Xapian::Database db(dbpath));
253 }
254
255 // Test a stub database with a bad entry with no spaces (prior to 1.1.0 this
256 // was deliberately allowed, though not documented.
DEFINE_TESTCASE(stubdb5,path)257 DEFINE_TESTCASE(stubdb5, path) {
258 mkdir(".stub", 0755);
259 const char * dbpath = ".stub/stubdb5";
260 ofstream out(dbpath);
261 TEST(out.is_open());
262 out << "bad\n"
263 "auto ../" << get_database_path("apitest_simpledata") << endl;
264 out.close();
265
266 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
267 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB));
268
269 TEST_EXCEPTION(Xapian::DatabaseOpeningError,
270 Xapian::Database db(dbpath));
271 }
272
273 // Test a stub database with an inmemory database (new feature in 1.1.0).
DEFINE_TESTCASE(stubdb6,inmemory)274 DEFINE_TESTCASE(stubdb6, inmemory) {
275 mkdir(".stub", 0755);
276 const char * dbpath = ".stub/stubdb6";
277 ofstream out(dbpath);
278 TEST(out.is_open());
279 out << "inmemory\n";
280 out.close();
281
282 // Read-only tests:
283 {
284 Xapian::Database db(dbpath, Xapian::DB_BACKEND_STUB);
285 TEST_EQUAL(db.get_doccount(), 0);
286 Xapian::Enquire enquire(db);
287 enquire.set_query(Xapian::Query("word"));
288 Xapian::MSet mset = enquire.get_mset(0, 10);
289 TEST(mset.empty());
290 }
291 {
292 Xapian::Database db(dbpath);
293 TEST_EQUAL(db.get_doccount(), 0);
294 Xapian::Enquire enquire(db);
295 enquire.set_query(Xapian::Query("word"));
296 Xapian::MSet mset = enquire.get_mset(0, 10);
297 TEST(mset.empty());
298 }
299
300 // Writable tests:
301 {
302 Xapian::WritableDatabase db(dbpath,
303 Xapian::DB_OPEN|Xapian::DB_BACKEND_STUB);
304 TEST_EQUAL(db.get_doccount(), 0);
305 db.add_document(Xapian::Document());
306 TEST_EQUAL(db.get_doccount(), 1);
307 }
308 {
309 Xapian::WritableDatabase db(dbpath,
310 Xapian::DB_OPEN|Xapian::DB_BACKEND_STUB);
311 TEST_EQUAL(db.get_doccount(), 0);
312 db.add_document(Xapian::Document());
313 TEST_EQUAL(db.get_doccount(), 1);
314 }
315 }
316
317 /// Test error running Database::check() on a stub database.
318 // Regression test - in 1.4.3 and earlier this threw
319 // Xapian::DatabaseError.
DEFINE_TESTCASE(stubdb8,inmemory)320 DEFINE_TESTCASE(stubdb8, inmemory) {
321 mkdir(".stub", 0755);
322 const char * dbpath = ".stub/stubdb8";
323 ofstream out(dbpath);
324 TEST(out.is_open());
325 out << "inmemory\n";
326 out.close();
327
328 try {
329 Xapian::Database::check(dbpath);
330 FAIL_TEST("Managed to check inmemory stub");
331 } catch (const Xapian::UnimplementedError& e) {
332 // Check the message is appropriate.
333 TEST_STRINGS_EQUAL(e.get_msg(),
334 "InMemory database checking not implemented");
335 }
336 }
337
338 /// Test error running Database::check() on a remote stub database.
DEFINE_TESTCASE(stubdb9,path)339 DEFINE_TESTCASE(stubdb9, path) {
340 mkdir(".stub", 0755);
341 const char * dbpath = ".stub/stubdb9";
342 ofstream out(dbpath);
343 TEST(out.is_open());
344 out << "remote :" << BackendManager::get_xapian_progsrv_command()
345 << ' ' << get_database_path("apitest_simpledata") << endl;
346 out.close();
347
348 try {
349 Xapian::Database::check(dbpath);
350 FAIL_TEST("Managed to check remote stub");
351 } catch (const Xapian::UnimplementedError& e) {
352 // Check the message is appropriate.
353 TEST_STRINGS_EQUAL(e.get_msg(),
354 "Remote database checking not implemented");
355 }
356 }
357
358 #if 0 // the "force error" mechanism is no longer in place...
359 class MyErrorHandler : public Xapian::ErrorHandler {
360 public:
361 int count;
362
363 bool handle_error(Xapian::Error & error) {
364 ++count;
365 tout << "Error handling caught: " << error.get_description()
366 << ", count is now " << count << "\n";
367 return true;
368 }
369
370 MyErrorHandler() : count (0) {}
371 };
372
373 // tests error handler in multimatch().
374 DEFINE_TESTCASE(multierrhandler1, backend) {
375 MyErrorHandler myhandler;
376
377 Xapian::Database mydb2(get_database("apitest_simpledata"));
378 Xapian::Database mydb3(get_database("apitest_simpledata2"));
379 int errcount = 1;
380 for (int testcount = 0; testcount < 14; testcount ++) {
381 tout << "testcount=" << testcount << "\n";
382 Xapian::Database mydb4(get_database("-e", "apitest_termorder"));
383 Xapian::Database mydb5(get_network_database("apitest_termorder", 1));
384 Xapian::Database mydb6(get_database("-e2", "apitest_termorder"));
385 Xapian::Database mydb7(get_database("-e3", "apitest_simpledata"));
386
387 Xapian::Database dbs;
388 switch (testcount) {
389 case 0:
390 dbs.add_database(mydb2);
391 dbs.add_database(mydb3);
392 dbs.add_database(mydb4);
393 break;
394 case 1:
395 dbs.add_database(mydb4);
396 dbs.add_database(mydb2);
397 dbs.add_database(mydb3);
398 break;
399 case 2:
400 dbs.add_database(mydb3);
401 dbs.add_database(mydb4);
402 dbs.add_database(mydb2);
403 break;
404 case 3:
405 dbs.add_database(mydb2);
406 dbs.add_database(mydb3);
407 dbs.add_database(mydb5);
408 sleep(1);
409 break;
410 case 4:
411 dbs.add_database(mydb5);
412 dbs.add_database(mydb2);
413 dbs.add_database(mydb3);
414 sleep(1);
415 break;
416 case 5:
417 dbs.add_database(mydb3);
418 dbs.add_database(mydb5);
419 dbs.add_database(mydb2);
420 sleep(1);
421 break;
422 case 6:
423 dbs.add_database(mydb2);
424 dbs.add_database(mydb3);
425 dbs.add_database(mydb6);
426 break;
427 case 7:
428 dbs.add_database(mydb6);
429 dbs.add_database(mydb2);
430 dbs.add_database(mydb3);
431 break;
432 case 8:
433 dbs.add_database(mydb3);
434 dbs.add_database(mydb6);
435 dbs.add_database(mydb2);
436 break;
437 case 9:
438 dbs.add_database(mydb2);
439 dbs.add_database(mydb3);
440 dbs.add_database(mydb7);
441 break;
442 case 10:
443 dbs.add_database(mydb7);
444 dbs.add_database(mydb2);
445 dbs.add_database(mydb3);
446 break;
447 case 11:
448 dbs.add_database(mydb3);
449 dbs.add_database(mydb7);
450 dbs.add_database(mydb2);
451 break;
452 case 12:
453 dbs.add_database(mydb2);
454 dbs.add_database(mydb6);
455 dbs.add_database(mydb7);
456 break;
457 case 13:
458 dbs.add_database(mydb2);
459 dbs.add_database(mydb7);
460 dbs.add_database(mydb6);
461 break;
462 }
463 tout << "db=" << dbs << "\n";
464 Xapian::Enquire enquire(dbs, &myhandler);
465
466 // make a query
467 Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
468 enquire.set_weighting_scheme(Xapian::BoolWeight());
469 enquire.set_query(myquery);
470
471 tout << "query=" << myquery << "\n";
472 // retrieve the top ten results
473 Xapian::MSet mymset = enquire.get_mset(0, 10);
474
475 switch (testcount) {
476 case 0: case 3: case 6: case 9:
477 mset_expect_order(mymset, 2, 4, 10);
478 break;
479 case 1: case 4: case 7: case 10:
480 mset_expect_order(mymset, 3, 5, 11);
481 break;
482 case 2: case 5: case 8: case 11:
483 mset_expect_order(mymset, 1, 6, 12);
484 break;
485 case 12:
486 case 13:
487 mset_expect_order(mymset, 4, 10);
488 errcount += 1;
489 break;
490 }
491 TEST_EQUAL(myhandler.count, errcount);
492 errcount += 1;
493 }
494 }
495 #endif
496
497 class GrepMatchDecider : public Xapian::MatchDecider {
498 string needle;
499 public:
GrepMatchDecider(const string & needle_)500 explicit GrepMatchDecider(const string& needle_)
501 : needle(needle_) {}
502
operator ()(const Xapian::Document & doc) const503 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;
506 }
507 };
508
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"));
514
515 GrepMatchDecider myfunctor("This is");
516
517 Xapian::MSet mymset = enquire.get_mset(0, 100, 0, &myfunctor);
518
519 vector<bool> docid_checked(db.get_lastdocid());
520
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;
536 }
537
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);
542
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);
555
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);
573
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)));
578 }
579 }
580
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);
598
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);
617
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);
634 }
635
636 // Test Xapian::MatchDecider functor used as a match spy.
637 DEFINE_TESTCASE(matchdecider2, backend && !remote) {
638 Xapian::Database db(get_database("apitest_simpledata"));
639 Xapian::Enquire enquire(db);
640 enquire.set_query(Xapian::Query("this"));
641
642 GrepMatchDecider myfunctor("This is");
643
644 Xapian::MSet mymset = enquire.get_mset(0, 100, 0, NULL, &myfunctor);
645
646 vector<bool> docid_checked(db.get_lastdocid());
647
648 // Check that we get the expected number of matches, and that they
649 // satisfy the condition.
650 Xapian::MSetIterator i = mymset.begin();
651 TEST(i != mymset.end());
652 TEST_EQUAL(mymset.size(), 3);
653 for ( ; i != mymset.end(); ++i) {
654 const Xapian::Document doc(i.get_document());
655 TEST(myfunctor(doc));
656 docid_checked[*i] = true;
657 }
658
659 // Check that the other documents don't satisfy the condition.
660 for (Xapian::docid did = 1; did < docid_checked.size(); ++did) {
661 if (!docid_checked[did]) {
662 TEST(!myfunctor(db.get_document(did)));
663 }
664 }
665 }
666
667 // Regression test for lower bound using functor, sorting and collapsing.
668 DEFINE_TESTCASE(matchdecider3, backend && !remote) {
669 Xapian::Database db(get_database("etext"));
670 Xapian::Enquire enquire(db);
671 enquire.set_query(Xapian::Query(""));
672 enquire.set_collapse_key(12);
673 enquire.set_sort_by_value(11, true);
674
675 GrepMatchDecider myfunctor("We produce");
676
677 Xapian::MSet mset1 = enquire.get_mset(0, 2, 0, NULL, &myfunctor);
678 Xapian::MSet mset2 = enquire.get_mset(0, 1000, 0, NULL, &myfunctor);
679
680 // mset2 should contain all the hits, so the statistics should be exact.
681 TEST_EQUAL(mset2.get_matches_estimated(), mset2.size());
682 TEST_EQUAL(mset2.get_matches_lower_bound(), mset2.get_matches_estimated());
683 TEST_EQUAL(mset2.get_matches_estimated(), mset2.get_matches_upper_bound());
684
685 TEST_REL(mset2.get_uncollapsed_matches_lower_bound(),<=,mset2.get_uncollapsed_matches_estimated());
686 TEST_REL(mset2.get_uncollapsed_matches_estimated(),<=,mset2.get_uncollapsed_matches_upper_bound());
687
688 // Check that the lower bound in mset1 is not greater than the known
689 // number of hits. This failed until revision 10811.
690 TEST_REL(mset1.get_matches_lower_bound(),<=,mset2.size());
691
692 // Check that the bounds for mset1 make sense.
693 TEST_REL(mset1.get_matches_lower_bound(),<=,mset1.get_matches_estimated());
694 TEST_REL(mset1.get_matches_estimated(),<=,mset1.get_matches_upper_bound());
695 TEST_REL(mset1.size(),<=,mset1.get_matches_upper_bound());
696
697 TEST_REL(mset1.get_uncollapsed_matches_lower_bound(),<=,mset1.get_uncollapsed_matches_estimated());
698 TEST_REL(mset1.get_uncollapsed_matches_estimated(),<=,mset1.get_uncollapsed_matches_upper_bound());
699
700 // The uncollapsed match would match all documents but the one the
701 // matchdecider rejects.
702 TEST_REL(mset1.get_uncollapsed_matches_upper_bound(),>=,db.get_doccount() - 1);
703 TEST_REL(mset1.get_uncollapsed_matches_upper_bound(),<=,db.get_doccount());
704 TEST_REL(mset2.get_uncollapsed_matches_upper_bound(),>=,db.get_doccount() - 1);
705 TEST_REL(mset2.get_uncollapsed_matches_upper_bound(),<=,db.get_doccount());
706 }
707
708 // tests that mset iterators on msets compare correctly.
DEFINE_TESTCASE(msetiterator1,backend)709 DEFINE_TESTCASE(msetiterator1, backend) {
710 Xapian::Enquire enquire(get_database("apitest_simpledata"));
711 enquire.set_query(Xapian::Query("this"));
712 Xapian::MSet mymset = enquire.get_mset(0, 2);
713
714 Xapian::MSetIterator j;
715 j = mymset.begin();
716 Xapian::MSetIterator k = mymset.end();
717 Xapian::MSetIterator l(j);
718 Xapian::MSetIterator m(k);
719 Xapian::MSetIterator n = mymset.begin();
720 Xapian::MSetIterator o = mymset.begin();
721 TEST_NOT_EQUAL(j, k);
722 TEST_NOT_EQUAL(l, m);
723 TEST_EQUAL(k, m);
724 TEST_EQUAL(j, l);
725 TEST_EQUAL(j, j);
726 TEST_EQUAL(k, k);
727
728 k = j;
729 TEST_EQUAL(j, k);
730 TEST_EQUAL(j, o);
731 k++;
732 TEST_NOT_EQUAL(j, k);
733 TEST_NOT_EQUAL(k, l);
734 TEST_NOT_EQUAL(k, m);
735 TEST_NOT_EQUAL(k, o);
736 o++;
737 TEST_EQUAL(k, o);
738 k++;
739 TEST_NOT_EQUAL(j, k);
740 TEST_NOT_EQUAL(k, l);
741 TEST_EQUAL(k, m);
742 TEST_EQUAL(n, l);
743
744 n = m;
745 TEST_NOT_EQUAL(n, l);
746 TEST_EQUAL(n, m);
747 TEST_NOT_EQUAL(n, mymset.begin());
748 TEST_EQUAL(n, mymset.end());
749 }
750
751 // tests that mset iterators on empty msets compare equal.
DEFINE_TESTCASE(msetiterator2,backend)752 DEFINE_TESTCASE(msetiterator2, backend) {
753 Xapian::Enquire enquire(get_database("apitest_simpledata"));
754 enquire.set_query(Xapian::Query("this"));
755 Xapian::MSet mymset = enquire.get_mset(0, 0);
756
757 Xapian::MSetIterator j = mymset.begin();
758 Xapian::MSetIterator k = mymset.end();
759 Xapian::MSetIterator l(j);
760 Xapian::MSetIterator m(k);
761 TEST_EQUAL(j, k);
762 TEST_EQUAL(l, m);
763 TEST_EQUAL(k, m);
764 TEST_EQUAL(j, l);
765 TEST_EQUAL(j, j);
766 TEST_EQUAL(k, k);
767 }
768
769 // tests that begin().get_document() works when first != 0
DEFINE_TESTCASE(msetiterator3,backend)770 DEFINE_TESTCASE(msetiterator3, backend) {
771 Xapian::Database mydb(get_database("apitest_simpledata"));
772 Xapian::Enquire enquire(mydb);
773 enquire.set_query(Xapian::Query("this"));
774
775 Xapian::MSet mymset = enquire.get_mset(2, 10);
776
777 TEST(!mymset.empty());
778 Xapian::Document doc(mymset.begin().get_document());
779 TEST(!doc.get_data().empty());
780 }
781
782 // tests that eset iterators on empty esets compare equal.
DEFINE_TESTCASE(esetiterator1,backend)783 DEFINE_TESTCASE(esetiterator1, backend) {
784 Xapian::Enquire enquire(get_database("apitest_simpledata"));
785 enquire.set_query(Xapian::Query("this"));
786
787 Xapian::MSet mymset = enquire.get_mset(0, 10);
788 TEST(mymset.size() >= 2);
789
790 Xapian::RSet myrset;
791 Xapian::MSetIterator i = mymset.begin();
792 myrset.add_document(*i);
793 myrset.add_document(*(++i));
794
795 Xapian::ESet myeset = enquire.get_eset(2, myrset);
796 Xapian::ESetIterator j;
797 j = myeset.begin();
798 Xapian::ESetIterator k = myeset.end();
799 Xapian::ESetIterator l(j);
800 Xapian::ESetIterator m(k);
801 Xapian::ESetIterator n = myeset.begin();
802
803 TEST_NOT_EQUAL(j, k);
804 TEST_NOT_EQUAL(l, m);
805 TEST_EQUAL(k, m);
806 TEST_EQUAL(j, l);
807 TEST_EQUAL(j, j);
808 TEST_EQUAL(k, k);
809
810 k = j;
811 TEST_EQUAL(j, k);
812 k++;
813 TEST_NOT_EQUAL(j, k);
814 TEST_NOT_EQUAL(k, l);
815 TEST_NOT_EQUAL(k, m);
816 k++;
817 TEST_NOT_EQUAL(j, k);
818 TEST_NOT_EQUAL(k, l);
819 TEST_EQUAL(k, m);
820 TEST_EQUAL(n, l);
821
822 n = m;
823 TEST_NOT_EQUAL(n, l);
824 TEST_EQUAL(n, m);
825 TEST_NOT_EQUAL(n, myeset.begin());
826 TEST_EQUAL(n, myeset.end());
827 }
828
829 // tests that eset iterators on empty esets compare equal.
DEFINE_TESTCASE(esetiterator2,backend)830 DEFINE_TESTCASE(esetiterator2, backend) {
831 Xapian::Enquire enquire(get_database("apitest_simpledata"));
832 enquire.set_query(Xapian::Query("this"));
833
834 Xapian::MSet mymset = enquire.get_mset(0, 10);
835 TEST(mymset.size() >= 2);
836
837 Xapian::RSet myrset;
838 Xapian::MSetIterator i = mymset.begin();
839 myrset.add_document(*i);
840 myrset.add_document(*(++i));
841
842 Xapian::ESet myeset = enquire.get_eset(0, myrset);
843 Xapian::ESetIterator j = myeset.begin();
844 Xapian::ESetIterator k = myeset.end();
845 Xapian::ESetIterator l(j);
846 Xapian::ESetIterator m(k);
847 TEST_EQUAL(j, k);
848 TEST_EQUAL(l, m);
849 TEST_EQUAL(k, m);
850 TEST_EQUAL(j, l);
851 TEST_EQUAL(j, j);
852 TEST_EQUAL(k, k);
853 }
854
855 // tests the collapse-on-key
DEFINE_TESTCASE(collapsekey1,backend)856 DEFINE_TESTCASE(collapsekey1, backend) {
857 Xapian::Enquire enquire(get_database("apitest_simpledata"));
858 enquire.set_query(Xapian::Query("this"));
859
860 Xapian::MSet mymset1 = enquire.get_mset(0, 100);
861 Xapian::doccount mymsize1 = mymset1.size();
862
863 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
864 enquire.set_collapse_key(value_no);
865 Xapian::MSet mymset = enquire.get_mset(0, 100);
866
867 TEST_AND_EXPLAIN(mymsize1 > mymset.size(),
868 "Had no fewer items when performing collapse: don't know whether it worked.");
869
870 map<string, Xapian::docid> values;
871 Xapian::MSetIterator i = mymset.begin();
872 for ( ; i != mymset.end(); ++i) {
873 string value = i.get_document().get_value(value_no);
874 TEST(values[value] == 0 || value.empty());
875 values[value] = *i;
876 }
877 }
878 }
879
880 // tests that collapse-on-key modifies the predicted bounds for the number of
881 // matches appropriately.
DEFINE_TESTCASE(collapsekey2,backend)882 DEFINE_TESTCASE(collapsekey2, backend) {
883 SKIP_TEST("Don't have a suitable database currently");
884 // FIXME: this needs an appropriate database creating, but that's quite
885 // subtle to do it seems.
886 Xapian::Enquire enquire(get_database("apitest_simpledata2"));
887 enquire.set_query(Xapian::Query("this"));
888
889 Xapian::MSet mset1 = enquire.get_mset(0, 1);
890
891 // Test that if no duplicates are found, then the upper bound remains
892 // unchanged and the lower bound drops.
893 {
894 enquire.set_query(Xapian::Query("this"));
895 Xapian::valueno value_no = 3;
896 enquire.set_collapse_key(value_no);
897 Xapian::MSet mset = enquire.get_mset(0, 1);
898
899 TEST_REL(mset.get_matches_lower_bound(),<,mset1.get_matches_lower_bound());
900 TEST_EQUAL(mset.get_matches_upper_bound(), mset1.get_matches_upper_bound());
901 }
902 }
903
904 // tests that collapse-on-key modifies the predicted bounds for the number of
905 // matches appropriately.
DEFINE_TESTCASE(collapsekey3,backend)906 DEFINE_TESTCASE(collapsekey3, backend) {
907 Xapian::Enquire enquire(get_database("apitest_simpledata"));
908 enquire.set_query(Xapian::Query("this"));
909
910 Xapian::MSet mymset1 = enquire.get_mset(0, 3);
911
912 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
913 enquire.set_collapse_key(value_no);
914 Xapian::MSet mymset = enquire.get_mset(0, 3);
915
916 TEST_AND_EXPLAIN(mymset1.get_matches_lower_bound() > mymset.get_matches_lower_bound(),
917 "Lower bound was not lower when performing collapse: don't know whether it worked.");
918 TEST_AND_EXPLAIN(mymset1.get_matches_upper_bound() > mymset.get_matches_upper_bound(),
919 "Upper bound was not lower when performing collapse: don't know whether it worked.");
920
921 map<string, Xapian::docid> values;
922 Xapian::MSetIterator i = mymset.begin();
923 for ( ; i != mymset.end(); ++i) {
924 string value = i.get_document().get_value(value_no);
925 TEST(values[value] == 0 || value.empty());
926 values[value] = *i;
927 }
928 }
929
930 // Test that if the collapse value is always empty, then the upper bound
931 // remains unchanged, and the lower bound is the same or lower (it can be
932 // lower because the matcher counts the number of documents with empty
933 // collapse keys, but may have rejected a document because its weight is
934 // too low for the proto-MSet before it even looks at its collapse key).
935 {
936 Xapian::valueno value_no = 1000;
937 enquire.set_collapse_key(value_no);
938 Xapian::MSet mymset = enquire.get_mset(0, 3);
939
940 TEST(mymset.get_matches_lower_bound() <= mymset1.get_matches_lower_bound());
941 TEST_EQUAL(mymset.get_matches_upper_bound(), mymset1.get_matches_upper_bound());
942
943 map<string, Xapian::docid> values;
944 Xapian::MSetIterator i = mymset.begin();
945 for ( ; i != mymset.end(); ++i) {
946 string value = i.get_document().get_value(value_no);
947 TEST(values[value] == 0 || value.empty());
948 values[value] = *i;
949 }
950 }
951 }
952
953 // tests that collapse-on-key modifies the predicted bounds for the number of
954 // matches appropriately even when no results are requested.
DEFINE_TESTCASE(collapsekey4,backend)955 DEFINE_TESTCASE(collapsekey4, backend) {
956 Xapian::Enquire enquire(get_database("apitest_simpledata"));
957 enquire.set_query(Xapian::Query("this"));
958
959 Xapian::MSet mymset1 = enquire.get_mset(0, 0);
960
961 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
962 enquire.set_collapse_key(value_no);
963 Xapian::MSet mymset = enquire.get_mset(0, 0);
964
965 TEST_AND_EXPLAIN(mymset.get_matches_lower_bound() == 1,
966 "Lower bound was not 1 when performing collapse but not asking for any results.");
967 TEST_AND_EXPLAIN(mymset1.get_matches_upper_bound() == mymset.get_matches_upper_bound(),
968 "Upper bound was changed when performing collapse but not asking for any results.");
969
970 map<string, Xapian::docid> values;
971 Xapian::MSetIterator i = mymset.begin();
972 for ( ; i != mymset.end(); ++i) {
973 string value = i.get_document().get_value(value_no);
974 TEST(values[value] == 0 || value.empty());
975 values[value] = *i;
976 }
977 }
978 }
979
980 // test for keepalives
DEFINE_TESTCASE(keepalive1,remote)981 DEFINE_TESTCASE(keepalive1, remote) {
982 Xapian::Database db(get_remote_database("apitest_simpledata", 5000));
983
984 /* Test that keep-alives work */
985 for (int i = 0; i < 10; ++i) {
986 sleep(2);
987 db.keep_alive();
988 }
989 Xapian::Enquire enquire(db);
990 enquire.set_query(Xapian::Query("word"));
991 enquire.get_mset(0, 10);
992
993 /* Test that things break without keepalives */
994 sleep(10);
995 enquire.set_query(Xapian::Query("word"));
996 /* Currently this can throw NetworkError or NetworkTimeoutError (which is
997 * a subclass of NetworkError).
998 */
999 TEST_EXCEPTION_BASE_CLASS(Xapian::NetworkError,
1000 enquire.get_mset(0, 10));
1001 }
1002
1003 // test that iterating through all terms in a database works.
DEFINE_TESTCASE(allterms1,backend)1004 DEFINE_TESTCASE(allterms1, backend) {
1005 Xapian::Database db(get_database("apitest_allterms"));
1006 Xapian::TermIterator ati = db.allterms_begin();
1007 TEST(ati != db.allterms_end());
1008 TEST_EQUAL(*ati, "one");
1009 TEST_EQUAL(ati.get_termfreq(), 1);
1010
1011 Xapian::TermIterator ati2 = ati;
1012
1013 ati++;
1014 TEST(ati != db.allterms_end());
1015 if (verbose) {
1016 tout << "*ati = '" << *ati << "'\n";
1017 tout << "*ati.length = '" << (*ati).length() << "'\n";
1018 tout << "*ati == \"one\" = " << (*ati == "one") << "\n";
1019 tout << "*ati[3] = " << ((*ati)[3]) << "\n";
1020 tout << "*ati = '" << *ati << "'\n";
1021 }
1022 TEST(*ati == "three");
1023 TEST(ati.get_termfreq() == 3);
1024
1025 #if 0
1026 TEST(ati2 != db.allterms_end());
1027 TEST(*ati2 == "one");
1028 TEST(ati2.get_termfreq() == 1);
1029 #endif
1030
1031 ++ati;
1032 #if 0
1033 ++ati2;
1034 #endif
1035 TEST(ati != db.allterms_end());
1036 TEST(*ati == "two");
1037 TEST(ati.get_termfreq() == 2);
1038
1039 #if 0
1040 TEST(ati2 != db.allterms_end());
1041 TEST(*ati2 == "three");
1042 TEST(ati2.get_termfreq() == 3);
1043 #endif
1044
1045 ati++;
1046 TEST(ati == db.allterms_end());
1047 }
1048
1049 // test that iterating through all terms in two databases works.
DEFINE_TESTCASE(allterms2,backend)1050 DEFINE_TESTCASE(allterms2, backend) {
1051 Xapian::Database db;
1052 db.add_database(get_database("apitest_allterms"));
1053 db.add_database(get_database("apitest_allterms2"));
1054 Xapian::TermIterator ati = db.allterms_begin();
1055
1056 TEST(ati != db.allterms_end());
1057 TEST(*ati == "five");
1058 TEST(ati.get_termfreq() == 2);
1059 ati++;
1060
1061 TEST(ati != db.allterms_end());
1062 TEST(*ati == "four");
1063 TEST(ati.get_termfreq() == 1);
1064
1065 ati++;
1066 TEST(ati != db.allterms_end());
1067 TEST(*ati == "one");
1068 TEST(ati.get_termfreq() == 1);
1069
1070 ++ati;
1071 TEST(ati != db.allterms_end());
1072 TEST(*ati == "six");
1073 TEST(ati.get_termfreq() == 3);
1074
1075 ati++;
1076 TEST(ati != db.allterms_end());
1077 TEST(*ati == "three");
1078 TEST(ati.get_termfreq() == 3);
1079
1080 ati++;
1081 TEST(ati != db.allterms_end());
1082 TEST(*ati == "two");
1083 TEST(ati.get_termfreq() == 2);
1084
1085 ati++;
1086 TEST(ati == db.allterms_end());
1087 }
1088
1089 // test that skip_to sets at_end (regression test)
DEFINE_TESTCASE(allterms3,backend)1090 DEFINE_TESTCASE(allterms3, backend) {
1091 Xapian::Database db;
1092 db.add_database(get_database("apitest_allterms"));
1093 Xapian::TermIterator ati = db.allterms_begin();
1094
1095 ati.skip_to(string("zzzzzz"));
1096 TEST(ati == db.allterms_end());
1097 }
1098
1099 // test that next ignores extra entries due to long posting lists being
1100 // chunked (regression test for quartz)
DEFINE_TESTCASE(allterms4,backend)1101 DEFINE_TESTCASE(allterms4, backend) {
1102 // apitest_allterms4 contains 682 documents each containing just the word
1103 // "foo". 682 was the magic number which started to cause Quartz problems.
1104 Xapian::Database db = get_database("apitest_allterms4");
1105
1106 Xapian::TermIterator i = db.allterms_begin();
1107 TEST(i != db.allterms_end());
1108 TEST(*i == "foo");
1109 TEST(i.get_termfreq() == 682);
1110 ++i;
1111 TEST(i == db.allterms_end());
1112 }
1113
1114 // test that skip_to with an exact match sets the current term (regression test
1115 // for quartz)
DEFINE_TESTCASE(allterms5,backend)1116 DEFINE_TESTCASE(allterms5, backend) {
1117 Xapian::Database db;
1118 db.add_database(get_database("apitest_allterms"));
1119 Xapian::TermIterator ati = db.allterms_begin();
1120 ati.skip_to("three");
1121 TEST(ati != db.allterms_end());
1122 TEST_EQUAL(*ati, "three");
1123 }
1124
1125 // test allterms iterators with prefixes
DEFINE_TESTCASE(allterms6,backend)1126 DEFINE_TESTCASE(allterms6, backend) {
1127 Xapian::Database db;
1128 db.add_database(get_database("apitest_allterms"));
1129 db.add_database(get_database("apitest_allterms2"));
1130
1131 Xapian::TermIterator ati = db.allterms_begin("three");
1132 TEST(ati != db.allterms_end("three"));
1133 TEST_EQUAL(*ati, "three");
1134 ati.skip_to("three");
1135 TEST(ati != db.allterms_end("three"));
1136 TEST_EQUAL(*ati, "three");
1137 ati++;
1138 TEST(ati == db.allterms_end("three"));
1139
1140 ati = db.allterms_begin("thre");
1141 TEST(ati != db.allterms_end("thre"));
1142 TEST_EQUAL(*ati, "three");
1143 ati.skip_to("three");
1144 TEST(ati != db.allterms_end("thre"));
1145 TEST_EQUAL(*ati, "three");
1146 ati++;
1147 TEST(ati == db.allterms_end("thre"));
1148
1149 ati = db.allterms_begin("f");
1150 TEST(ati != db.allterms_end("f"));
1151 TEST_EQUAL(*ati, "five");
1152 TEST(ati != db.allterms_end("f"));
1153 ati.skip_to("three");
1154 TEST(ati == db.allterms_end("f"));
1155
1156 ati = db.allterms_begin("f");
1157 TEST(ati != db.allterms_end("f"));
1158 TEST_EQUAL(*ati, "five");
1159 ati++;
1160 TEST(ati != db.allterms_end("f"));
1161 TEST_EQUAL(*ati, "four");
1162 ati++;
1163 TEST(ati == db.allterms_end("f"));
1164
1165 ati = db.allterms_begin("absent");
1166 TEST(ati == db.allterms_end("absent"));
1167 }
1168
1169 // test that searching for a term with a special characters in it works
DEFINE_TESTCASE(specialterms1,backend)1170 DEFINE_TESTCASE(specialterms1, backend) {
1171 Xapian::Enquire enquire(get_database("apitest_space"));
1172 Xapian::MSet mymset;
1173 Xapian::doccount count;
1174 Xapian::MSetIterator m;
1175 Xapian::Stem stemmer("english");
1176
1177 enquire.set_query(stemmer("new\nline"));
1178 mymset = enquire.get_mset(0, 10);
1179 TEST_MSET_SIZE(mymset, 1);
1180 count = 0;
1181 for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1182 TEST_EQUAL(count, 1);
1183
1184 for (Xapian::valueno value_no = 0; value_no < 7; ++value_no) {
1185 string value = mymset.begin().get_document().get_value(value_no);
1186 TEST_NOT_EQUAL(value, "");
1187 if (value_no == 0) {
1188 TEST(value.size() > 263);
1189 TEST_EQUAL(static_cast<unsigned char>(value[262]), 255);
1190 for (int k = 0; k < 256; ++k) {
1191 TEST_EQUAL(static_cast<unsigned char>(value[k + 7]), k);
1192 }
1193 }
1194 }
1195
1196 enquire.set_query(stemmer(string("big\0zero", 8)));
1197 mymset = enquire.get_mset(0, 10);
1198 TEST_MSET_SIZE(mymset, 1);
1199 count = 0;
1200 for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1201 TEST_EQUAL(count, 1);
1202 }
1203
1204 // test that terms with a special characters in appear correctly when iterating
1205 // allterms
DEFINE_TESTCASE(specialterms2,backend)1206 DEFINE_TESTCASE(specialterms2, backend) {
1207 Xapian::Database db(get_database("apitest_space"));
1208
1209 // Check the terms are all as expected (after stemming) and that allterms
1210 // copes with iterating over them.
1211 Xapian::TermIterator t;
1212 t = db.allterms_begin();
1213 TEST_EQUAL(*t, "back\\slash"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1214 TEST_EQUAL(*t, string("big\0zero", 8)); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1215 TEST_EQUAL(*t, "new\nlin"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1216 TEST_EQUAL(*t, "one\x01on"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1217 TEST_EQUAL(*t, "space man"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1218 TEST_EQUAL(*t, "tab\tbi"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1219 TEST_EQUAL(*t, "tu\x02tu"); ++t; TEST_EQUAL(t, db.allterms_end());
1220
1221 // Now check that skip_to exactly a term containing a zero byte works.
1222 // This is a regression test for flint and quartz - an Assert() used to
1223 // fire in debug builds (the Assert was wrong - the actual code handled
1224 // this OK).
1225 t = db.allterms_begin();
1226 t.skip_to(string("big\0zero", 8));
1227 TEST_NOT_EQUAL(t, db.allterms_end());
1228 TEST_EQUAL(*t, string("big\0zero", 8));
1229 }
1230
1231 // test that rsets behave correctly with multiDBs
1232 DEFINE_TESTCASE(rsetmultidb2, backend && !multi) {
1233 Xapian::Database mydb1(get_database("apitest_rset", "apitest_simpledata2"));
1234 Xapian::Database mydb2(get_database("apitest_rset"));
1235 mydb2.add_database(get_database("apitest_simpledata2"));
1236
1237 Xapian::Enquire enquire1(mydb1);
1238 Xapian::Enquire enquire2(mydb2);
1239
1240 Xapian::Query myquery = query("is");
1241
1242 enquire1.set_query(myquery);
1243 enquire2.set_query(myquery);
1244
1245 Xapian::RSet myrset1;
1246 Xapian::RSet myrset2;
1247 myrset1.add_document(4);
1248 myrset2.add_document(2);
1249
1250 Xapian::MSet mymset1a = enquire1.get_mset(0, 10);
1251 Xapian::MSet mymset1b = enquire1.get_mset(0, 10, &myrset1);
1252 Xapian::MSet mymset2a = enquire2.get_mset(0, 10);
1253 Xapian::MSet mymset2b = enquire2.get_mset(0, 10, &myrset2);
1254
1255 mset_expect_order(mymset1a, 4, 3);
1256 mset_expect_order(mymset1b, 4, 3);
1257 mset_expect_order(mymset2a, 2, 5);
1258 mset_expect_order(mymset2b, 2, 5);
1259
1260 TEST(mset_range_is_same_weights(mymset1a, 0, mymset2a, 0, 2));
1261 TEST(mset_range_is_same_weights(mymset1b, 0, mymset2b, 0, 2));
1262 TEST_NOT_EQUAL(mymset1a, mymset1b);
1263 TEST_NOT_EQUAL(mymset2a, mymset2b);
1264 }
1265
1266 // tests an expand across multiple databases
1267 DEFINE_TESTCASE(multiexpand1, backend && !multi) {
1268 Xapian::Database mydb1(get_database("apitest_simpledata", "apitest_simpledata2"));
1269 Xapian::Enquire enquire1(mydb1);
1270
1271 Xapian::Database mydb2(get_database("apitest_simpledata"));
1272 mydb2.add_database(get_database("apitest_simpledata2"));
1273 Xapian::Enquire enquire2(mydb2);
1274
1275 // make simple equivalent rsets, with a document from each database in each.
1276 Xapian::RSet rset1;
1277 Xapian::RSet rset2;
1278 rset1.add_document(1);
1279 rset1.add_document(7);
1280 rset2.add_document(1);
1281 rset2.add_document(2);
1282
1283 // Retrieve all the ESet results in each of the three setups:
1284
1285 // This is the single database one.
1286 Xapian::ESet eset1 = enquire1.get_eset(1000, rset1);
1287
1288 // This is the multi database with approximation
1289 Xapian::ESet eset2 = enquire2.get_eset(1000, rset2);
1290
1291 // This is the multi database without approximation
1292 Xapian::ESet eset3 = enquire2.get_eset(1000, rset2, Xapian::Enquire::USE_EXACT_TERMFREQ);
1293
1294 TEST_EQUAL(eset1.size(), eset3.size());
1295
1296 Xapian::ESetIterator i = eset1.begin();
1297 Xapian::ESetIterator j = eset3.begin();
1298 while (i != eset1.end() && j != eset3.end()) {
1299 TEST_EQUAL(*i, *j);
1300 TEST_EQUAL(i.get_weight(), j.get_weight());
1301 ++i;
1302 ++j;
1303 }
1304 TEST(i == eset1.end());
1305 TEST(j == eset3.end());
1306
1307 bool eset1_eq_eset2 = true;
1308 i = eset1.begin();
1309 j = eset2.begin();
1310 while (i != eset1.end() && j != eset2.end()) {
1311 if (i.get_weight() != j.get_weight()) {
1312 eset1_eq_eset2 = false;
1313 break;
1314 }
1315 ++i;
1316 ++j;
1317 }
1318 TEST(!eset1_eq_eset2);
1319 }
1320
1321 // tests that opening a non-existent postlist returns an empty list
DEFINE_TESTCASE(postlist1,backend)1322 DEFINE_TESTCASE(postlist1, backend) {
1323 Xapian::Database db(get_database("apitest_simpledata"));
1324
1325 TEST_EQUAL(db.postlist_begin("rosebud"), db.postlist_end("rosebud"));
1326
1327 string s = "let_us_see_if_we_can_break_it_with_a_really_really_long_term.";
1328 for (int i = 0; i < 8; ++i) {
1329 s += s;
1330 TEST_EQUAL(db.postlist_begin(s), db.postlist_end(s));
1331 }
1332
1333 // A regression test (no, really!)
1334 TEST_NOT_EQUAL(db.postlist_begin("a"), db.postlist_end("a"));
1335 }
1336
1337 // tests that a Xapian::PostingIterator works as an STL iterator
DEFINE_TESTCASE(postlist2,backend)1338 DEFINE_TESTCASE(postlist2, backend) {
1339 Xapian::Database db(get_database("apitest_simpledata"));
1340 Xapian::PostingIterator p;
1341 p = db.postlist_begin("this");
1342 Xapian::PostingIterator pend = db.postlist_end("this");
1343
1344 TEST(p.get_description() != "PostingIterator()");
1345
1346 // test operator= creates a copy which compares equal
1347 Xapian::PostingIterator p_copy = p;
1348 TEST_EQUAL(p, p_copy);
1349
1350 TEST(p_copy.get_description() != "PostingIterator()");
1351
1352 // test copy constructor creates a copy which compares equal
1353 Xapian::PostingIterator p_clone(p);
1354 TEST_EQUAL(p, p_clone);
1355
1356 TEST(p_clone.get_description() != "PostingIterator()");
1357
1358 vector<Xapian::docid> v(p, pend);
1359
1360 p = db.postlist_begin("this");
1361 pend = db.postlist_end("this");
1362 vector<Xapian::docid>::const_iterator i;
1363 for (i = v.begin(); i != v.end(); ++i) {
1364 TEST_NOT_EQUAL(p, pend);
1365 TEST_EQUAL(*i, *p);
1366 p++;
1367 }
1368 TEST_EQUAL(p, pend);
1369
1370 TEST_STRINGS_EQUAL(p.get_description(), "PostingIterator()");
1371 TEST_STRINGS_EQUAL(pend.get_description(), "PostingIterator()");
1372 }
1373
1374 // tests that a Xapian::PostingIterator still works when the DB is deleted
DEFINE_TESTCASE(postlist3,backend)1375 DEFINE_TESTCASE(postlist3, backend) {
1376 Xapian::PostingIterator u;
1377 {
1378 Xapian::Database db_temp(get_database("apitest_simpledata"));
1379 u = db_temp.postlist_begin("this");
1380 }
1381
1382 Xapian::Database db(get_database("apitest_simpledata"));
1383 Xapian::PostingIterator p = db.postlist_begin("this");
1384 Xapian::PostingIterator pend = db.postlist_end("this");
1385
1386 while (p != pend) {
1387 TEST_EQUAL(*p, *u);
1388 p++;
1389 u++;
1390 }
1391 }
1392
1393 // tests skip_to
DEFINE_TESTCASE(postlist4,backend)1394 DEFINE_TESTCASE(postlist4, backend) {
1395 Xapian::Database db(get_database("apitest_simpledata"));
1396 Xapian::PostingIterator i = db.postlist_begin("this");
1397 i.skip_to(1);
1398 i.skip_to(999999999);
1399 TEST(i == db.postlist_end("this"));
1400 }
1401
1402 // tests long postlists
DEFINE_TESTCASE(postlist5,backend)1403 DEFINE_TESTCASE(postlist5, backend) {
1404 Xapian::Database db(get_database("apitest_manydocs"));
1405 TEST_EQUAL_DOUBLE(db.get_avlength(), 4);
1406 Xapian::PostingIterator i = db.postlist_begin("this");
1407 unsigned int j = 1;
1408 while (i != db.postlist_end("this")) {
1409 TEST_EQUAL(*i, j);
1410 i++;
1411 j++;
1412 }
1413 TEST_EQUAL(j, 513);
1414 }
1415
1416 // tests document length in postlists
DEFINE_TESTCASE(postlist6,backend)1417 DEFINE_TESTCASE(postlist6, backend) {
1418 Xapian::Database db(get_database("apitest_simpledata"));
1419 Xapian::PostingIterator i = db.postlist_begin("this");
1420 TEST(i != db.postlist_end("this"));
1421 while (i != db.postlist_end("this")) {
1422 TEST_EQUAL(i.get_doclength(), db.get_doclength(*i));
1423 TEST_EQUAL(i.get_unique_terms(), db.get_unique_terms(*i));
1424 TEST_REL(i.get_wdf(),<=,i.get_doclength());
1425 TEST_REL(1,<=,i.get_unique_terms());
1426 // The next two aren't necessarily true if there are terms with wdf=0
1427 // in the document, but that isn't the case here.
1428 TEST_REL(i.get_unique_terms(),<=,i.get_doclength());
1429 TEST_REL(i.get_wdf() + i.get_unique_terms() - 1,<=,i.get_doclength());
1430 ++i;
1431 }
1432 }
1433
1434 // tests collection frequency
DEFINE_TESTCASE(collfreq1,backend)1435 DEFINE_TESTCASE(collfreq1, backend) {
1436 Xapian::Database db(get_database("apitest_simpledata"));
1437
1438 TEST_EQUAL(db.get_collection_freq("this"), 11);
1439 TEST_EQUAL(db.get_collection_freq("first"), 1);
1440 TEST_EQUAL(db.get_collection_freq("last"), 0);
1441 TEST_EQUAL(db.get_collection_freq("word"), 9);
1442
1443 Xapian::Database db1(get_database("apitest_simpledata", "apitest_simpledata2"));
1444 Xapian::Database db2(get_database("apitest_simpledata"));
1445 db2.add_database(get_database("apitest_simpledata2"));
1446
1447 TEST_EQUAL(db1.get_collection_freq("this"), 15);
1448 TEST_EQUAL(db1.get_collection_freq("first"), 1);
1449 TEST_EQUAL(db1.get_collection_freq("last"), 0);
1450 TEST_EQUAL(db1.get_collection_freq("word"), 11);
1451 TEST_EQUAL(db2.get_collection_freq("this"), 15);
1452 TEST_EQUAL(db2.get_collection_freq("first"), 1);
1453 TEST_EQUAL(db2.get_collection_freq("last"), 0);
1454 TEST_EQUAL(db2.get_collection_freq("word"), 11);
1455 }
1456
1457 // Regression test for split msets being incorrect when sorting
DEFINE_TESTCASE(sortvalue1,backend)1458 DEFINE_TESTCASE(sortvalue1, backend) {
1459 Xapian::Enquire enquire(get_database("apitest_simpledata"));
1460 enquire.set_query(Xapian::Query("this"));
1461
1462 for (int pass = 1; pass <= 2; ++pass) {
1463 for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
1464 tout << "Sorting on value " << value_no << endl;
1465 enquire.set_sort_by_value(value_no, true);
1466 Xapian::MSet allbset = enquire.get_mset(0, 100);
1467 Xapian::MSet partbset1 = enquire.get_mset(0, 3);
1468 Xapian::MSet partbset2 = enquire.get_mset(3, 97);
1469 TEST_EQUAL(allbset.size(), partbset1.size() + partbset2.size());
1470
1471 bool ok = true;
1472 int n = 0;
1473 Xapian::MSetIterator i, j;
1474 j = allbset.begin();
1475 for (i = partbset1.begin(); i != partbset1.end(); ++i) {
1476 tout << "Entry " << n << ": " << *i << " | " << *j << endl;
1477 TEST(j != allbset.end());
1478 if (*i != *j) ok = false;
1479 ++j;
1480 ++n;
1481 }
1482 tout << "===\n";
1483 for (i = partbset2.begin(); i != partbset2.end(); ++i) {
1484 tout << "Entry " << n << ": " << *i << " | " << *j << endl;
1485 TEST(j != allbset.end());
1486 if (*i != *j) ok = false;
1487 ++j;
1488 ++n;
1489 }
1490 TEST(j == allbset.end());
1491 if (!ok)
1492 FAIL_TEST("Split msets aren't consistent with unsplit");
1493 }
1494 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1495 }
1496 }
1497
1498 // consistency check match - vary mset size and check results agree.
1499 // consistency1 will run on the remote backend, but it's particularly slow
1500 // with that, and testing it there doesn't actually improve the test
1501 // coverage really.
1502 DEFINE_TESTCASE(consistency1, backend && !remote) {
1503 Xapian::Database db(get_database("etext"));
1504 Xapian::Enquire enquire(db);
1505 enquire.set_query(Xapian::Query(Xapian::Query::OP_OR, Xapian::Query("the"), Xapian::Query("sky")));
1506 Xapian::doccount lots = 214;
1507 Xapian::MSet bigmset = enquire.get_mset(0, lots);
1508 TEST_EQUAL(bigmset.size(), lots);
1509 try {
1510 for (Xapian::doccount start = 0; start < lots; ++start) {
1511 for (Xapian::doccount size = 0; size < lots - start; ++size) {
1512 Xapian::MSet mset = enquire.get_mset(start, size);
1513 if (mset.size()) {
1514 TEST_EQUAL(start + mset.size(),
1515 min(start + size, bigmset.size()));
1516 } else if (size) {
1517 // tout << start << mset.size() << bigmset.size() << endl;
1518 TEST(start >= bigmset.size());
1519 }
1520 for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1521 TEST_EQUAL(*mset[i], *bigmset[start + i]);
1522 TEST_EQUAL_DOUBLE(mset[i].get_weight(),
1523 bigmset[start + i].get_weight());
1524 }
1525 }
1526 }
1527 } catch (const Xapian::NetworkTimeoutError &) {
1528 // consistency1 is a long test - may timeout with the remote backend...
1529 SKIP_TEST("Test taking too long");
1530 }
1531 }
1532
1533 // tests that specifying a nonexistent input file throws an exception.
DEFINE_TESTCASE(chertdatabasenotfounderror1,chert)1534 DEFINE_TESTCASE(chertdatabasenotfounderror1, chert) {
1535 #ifdef XAPIAN_HAS_CHERT_BACKEND
1536 mkdir(".chert", 0755);
1537
1538 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1539 Xapian::Database(".chert/nosuchdirectory",
1540 Xapian::DB_BACKEND_CHERT));
1541 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1542 Xapian::WritableDatabase(".chert/nosuchdirectory",
1543 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT));
1544
1545 mkdir(".chert/emptydirectory", 0700);
1546 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1547 Xapian::Database(".chert/emptydirectory",
1548 Xapian::DB_BACKEND_CHERT));
1549
1550 touch(".chert/somefile");
1551 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1552 Xapian::Database(".chert/somefile",
1553 Xapian::DB_BACKEND_CHERT));
1554 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1555 Xapian::WritableDatabase(".chert/somefile",
1556 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT));
1557 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1558 Xapian::WritableDatabase(".chert/somefile",
1559 Xapian::DB_CREATE|Xapian::DB_BACKEND_CHERT));
1560 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1561 Xapian::WritableDatabase(".chert/somefile",
1562 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT));
1563 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1564 Xapian::WritableDatabase(".chert/somefile",
1565 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT));
1566 #endif
1567
1568 }
1569
DEFINE_TESTCASE(glassdatabasenotfounderror1,glass)1570 DEFINE_TESTCASE(glassdatabasenotfounderror1, glass) {
1571 #ifdef XAPIAN_HAS_GLASS_BACKEND
1572 mkdir(".glass", 0755);
1573
1574 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1575 Xapian::Database(".glass/nosuchdirectory",
1576 Xapian::DB_BACKEND_GLASS));
1577 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1578 Xapian::WritableDatabase(".glass/nosuchdirectory",
1579 Xapian::DB_OPEN|Xapian::DB_BACKEND_GLASS));
1580
1581 mkdir(".glass/emptydirectory", 0700);
1582 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1583 Xapian::Database(".glass/emptydirectory",
1584 Xapian::DB_BACKEND_GLASS));
1585
1586 touch(".glass/somefile");
1587 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1588 Xapian::Database(".glass/somefile",
1589 Xapian::DB_BACKEND_GLASS));
1590 TEST_EXCEPTION(Xapian::DatabaseNotFoundError,
1591 Xapian::WritableDatabase(".glass/somefile",
1592 Xapian::DB_OPEN|Xapian::DB_BACKEND_GLASS));
1593 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1594 Xapian::WritableDatabase(".glass/somefile",
1595 Xapian::DB_CREATE|Xapian::DB_BACKEND_GLASS));
1596 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1597 Xapian::WritableDatabase(".glass/somefile",
1598 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_GLASS));
1599 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1600 Xapian::WritableDatabase(".glass/somefile",
1601 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_GLASS));
1602 #endif
1603 }
1604
1605 /// Test opening of a chert database
DEFINE_TESTCASE(chertdatabaseopen1,chert)1606 DEFINE_TESTCASE(chertdatabaseopen1, chert) {
1607 #ifdef XAPIAN_HAS_CHERT_BACKEND
1608 const string dbdir = ".chert/test_chertdatabaseopen1";
1609 mkdir(".chert", 0755);
1610
1611 {
1612 rm_rf(dbdir);
1613 Xapian::WritableDatabase wdb(dbdir,
1614 Xapian::DB_CREATE|Xapian::DB_BACKEND_CHERT);
1615 TEST_EXCEPTION(Xapian::DatabaseLockError,
1616 Xapian::WritableDatabase(dbdir,
1617 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT));
1618 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1619 }
1620
1621 {
1622 rm_rf(dbdir);
1623 Xapian::WritableDatabase wdb(dbdir,
1624 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT);
1625 TEST_EXCEPTION(Xapian::DatabaseLockError,
1626 Xapian::WritableDatabase(dbdir,
1627 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT));
1628 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1629 }
1630
1631 {
1632 rm_rf(dbdir);
1633 Xapian::WritableDatabase wdb(dbdir,
1634 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT);
1635 TEST_EXCEPTION(Xapian::DatabaseLockError,
1636 Xapian::WritableDatabase(dbdir,
1637 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT));
1638 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1639 }
1640
1641 {
1642 TEST_EXCEPTION(Xapian::DatabaseCreateError,
1643 Xapian::WritableDatabase(dbdir,
1644 Xapian::DB_CREATE|Xapian::DB_BACKEND_CHERT));
1645 Xapian::WritableDatabase wdb(dbdir,
1646 Xapian::DB_CREATE_OR_OVERWRITE|Xapian::DB_BACKEND_CHERT);
1647 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1648 }
1649
1650 {
1651 Xapian::WritableDatabase wdb(dbdir,
1652 Xapian::DB_CREATE_OR_OPEN|Xapian::DB_BACKEND_CHERT);
1653 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1654 }
1655
1656 {
1657 Xapian::WritableDatabase wdb(dbdir,
1658 Xapian::DB_OPEN|Xapian::DB_BACKEND_CHERT);
1659 Xapian::Database(dbdir, Xapian::DB_BACKEND_CHERT);
1660 }
1661 #endif
1662 }
1663
1664 // feature test for Enquire:
1665 // set_sort_by_value
1666 // set_sort_by_value_then_relevance
1667 // set_sort_by_relevance_then_value
1668 // Prior to 1.2.17 and 1.3.2, order8 and order9 were swapped, and
1669 // set_sort_by_relevance_then_value was buggy, so this testcase now serves as
1670 // a regression test for that bug.
DEFINE_TESTCASE(sortrel1,backend)1671 DEFINE_TESTCASE(sortrel1, backend) {
1672 Xapian::Enquire enquire(get_database("apitest_sortrel"));
1673 enquire.set_sort_by_value(1, true);
1674 enquire.set_query(Xapian::Query("woman"));
1675
1676 static const Xapian::docid order1[] = { 1,2,3,4,5,6,7,8,9 };
1677 static const Xapian::docid order2[] = { 2,1,3,6,5,4,7,9,8 };
1678 static const Xapian::docid order3[] = { 3,2,1,6,5,4,9,8,7 };
1679 static const Xapian::docid order4[] = { 7,8,9,4,5,6,1,2,3 };
1680 static const Xapian::docid order5[] = { 9,8,7,6,5,4,3,2,1 };
1681 static const Xapian::docid order6[] = { 7,9,8,6,5,4,2,1,3 };
1682 static const Xapian::docid order7[] = { 7,9,8,6,5,4,2,1,3 };
1683 static const Xapian::docid order8[] = { 2,6,7,1,5,9,3,4,8 };
1684 static const Xapian::docid order9[] = { 7,6,2,9,5,1,8,4,3 };
1685
1686 Xapian::MSet mset;
1687 size_t i;
1688
1689 mset = enquire.get_mset(0, 10);
1690 TEST_EQUAL(mset.size(), sizeof(order1) / sizeof(Xapian::docid));
1691 for (i = 0; i < sizeof(order1) / sizeof(Xapian::docid); ++i) {
1692 TEST_EQUAL(*mset[i], order1[i]);
1693 }
1694
1695 enquire.set_sort_by_value_then_relevance(1, true);
1696
1697 mset = enquire.get_mset(0, 10);
1698 TEST_EQUAL(mset.size(), sizeof(order2) / sizeof(Xapian::docid));
1699 for (i = 0; i < sizeof(order2) / sizeof(Xapian::docid); ++i) {
1700 TEST_EQUAL(*mset[i], order2[i]);
1701 }
1702
1703 enquire.set_sort_by_value(1, true);
1704
1705 mset = enquire.get_mset(0, 10);
1706 TEST_EQUAL(mset.size(), sizeof(order1) / sizeof(Xapian::docid));
1707 for (i = 0; i < sizeof(order1) / sizeof(Xapian::docid); ++i) {
1708 TEST_EQUAL(*mset[i], order1[i]);
1709 }
1710
1711 enquire.set_sort_by_value_then_relevance(1, true);
1712 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1713
1714 mset = enquire.get_mset(0, 10);
1715 TEST_EQUAL(mset.size(), sizeof(order2) / sizeof(Xapian::docid));
1716 for (i = 0; i < sizeof(order2) / sizeof(Xapian::docid); ++i) {
1717 TEST_EQUAL(*mset[i], order2[i]);
1718 }
1719
1720 enquire.set_sort_by_value(1, true);
1721 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1722
1723 mset = enquire.get_mset(0, 10);
1724 TEST_EQUAL(mset.size(), sizeof(order3) / sizeof(Xapian::docid));
1725 for (i = 0; i < sizeof(order3) / sizeof(Xapian::docid); ++i) {
1726 TEST_EQUAL(*mset[i], order3[i]);
1727 }
1728
1729 enquire.set_sort_by_value(1, false);
1730 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1731 mset = enquire.get_mset(0, 10);
1732 TEST_EQUAL(mset.size(), sizeof(order4) / sizeof(Xapian::docid));
1733 for (i = 0; i < sizeof(order4) / sizeof(Xapian::docid); ++i) {
1734 TEST_EQUAL(*mset[i], order4[i]);
1735 }
1736
1737 enquire.set_sort_by_value(1, false);
1738 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1739 mset = enquire.get_mset(0, 10);
1740 TEST_EQUAL(mset.size(), sizeof(order5) / sizeof(Xapian::docid));
1741 for (i = 0; i < sizeof(order5) / sizeof(Xapian::docid); ++i) {
1742 TEST_EQUAL(*mset[i], order5[i]);
1743 }
1744
1745 enquire.set_sort_by_value_then_relevance(1, false);
1746 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1747 mset = enquire.get_mset(0, 10);
1748 TEST_EQUAL(mset.size(), sizeof(order6) / sizeof(Xapian::docid));
1749 for (i = 0; i < sizeof(order6) / sizeof(Xapian::docid); ++i) {
1750 TEST_EQUAL(*mset[i], order6[i]);
1751 }
1752
1753 enquire.set_sort_by_value_then_relevance(1, false);
1754 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1755 mset = enquire.get_mset(0, 10);
1756 TEST_EQUAL(mset.size(), sizeof(order7) / sizeof(Xapian::docid));
1757 for (i = 0; i < sizeof(order7) / sizeof(Xapian::docid); ++i) {
1758 TEST_EQUAL(*mset[i], order7[i]);
1759 }
1760
1761 enquire.set_sort_by_relevance_then_value(1, true);
1762 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1763 mset = enquire.get_mset(0, 10);
1764 TEST_EQUAL(mset.size(), sizeof(order8) / sizeof(Xapian::docid));
1765 for (i = 0; i < sizeof(order8) / sizeof(Xapian::docid); ++i) {
1766 TEST_EQUAL(*mset[i], order8[i]);
1767 }
1768
1769 enquire.set_sort_by_relevance_then_value(1, true);
1770 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1771 mset = enquire.get_mset(0, 10);
1772 TEST_EQUAL(mset.size(), sizeof(order8) / sizeof(Xapian::docid));
1773 for (i = 0; i < sizeof(order8) / sizeof(Xapian::docid); ++i) {
1774 TEST_EQUAL(*mset[i], order8[i]);
1775 }
1776
1777 enquire.set_sort_by_relevance_then_value(1, false);
1778 enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1779 mset = enquire.get_mset(0, 10);
1780 TEST_EQUAL(mset.size(), sizeof(order9) / sizeof(Xapian::docid));
1781 for (i = 0; i < sizeof(order9) / sizeof(Xapian::docid); ++i) {
1782 TEST_EQUAL(*mset[i], order9[i]);
1783 }
1784
1785 enquire.set_sort_by_relevance_then_value(1, false);
1786 enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1787 mset = enquire.get_mset(0, 10);
1788 TEST_EQUAL(mset.size(), sizeof(order9) / sizeof(Xapian::docid));
1789 for (i = 0; i < sizeof(order9) / sizeof(Xapian::docid); ++i) {
1790 TEST_EQUAL(*mset[i], order9[i]);
1791 }
1792 }
1793
1794 static void
make_netstats1_db(Xapian::WritableDatabase & db,const string &)1795 make_netstats1_db(Xapian::WritableDatabase& db, const string&)
1796 {
1797 static const struct { Xapian::docid did; const char* text; } content[] = {
1798 {1, "This is a test document used with the API test. This paragraph "
1799 "must be at least three lines (including the blank line) to be "
1800 "counted as a \"paragraph\"."},
1801 {2, "This is a second simple data test, used to test multiple "
1802 "(inmemory anyway) databases. The text in this file is "
1803 "unimportant, although I suppose it ought to include the "
1804 "standard word \"word\" in a few places."},
1805 {3, "This file will be indexed by paragraph, and the simple query will "
1806 "search for the word \"word\". Well expect the mset to contain "
1807 "two documents, including this paragraph and the fourth, below. "
1808 "Since this paragraph uses the word \"word\" so much, this "
1809 "should be the first one in the match set. Ill just say the word "
1810 "a few more times (word!) to make sure of that. If this doesnt "
1811 "word (typo, I meant work), then there may be fourletter words "
1812 "spoken."},
1813 {4, "Ill leave this at two paragraphs. This one hasnt got any useful "
1814 "information in it either."},
1815 {5, "This paragraph only has a load of absolute rubbish, and nothing "
1816 "of any use whatsoever."},
1817 {7, "This is the other paragraph with the word in the simple query "
1818 "in it. For simplicity, all paragraphs are at least two lines, "
1819 "due to how the hacked up indexer works."},
1820 {9, "This is another paragraph which wont be returned. Well, not "
1821 "with the simple query, anyway."},
1822 {11, "And yet another. This one does mention banana splits, though, "
1823 "so cant be that bad."}
1824 };
1825
1826 Xapian::TermGenerator indexer;
1827 indexer.set_stemmer(Xapian::Stem("english"));
1828 indexer.set_stemming_strategy(indexer.STEM_ALL);
1829
1830 for (auto& i : content) {
1831 Xapian::Document doc;
1832 indexer.set_document(doc);
1833 indexer.index_text(i.text);
1834 db.replace_document(i.did, doc);
1835 }
1836
1837 db.commit();
1838 }
1839
1840 // Test network stats and local stats give the same results.
DEFINE_TESTCASE(netstats1,generated)1841 DEFINE_TESTCASE(netstats1, generated) {
1842 static const char * const words[] = { "paragraph", "word" };
1843 Xapian::Query query(Xapian::Query::OP_OR, words, words + 2);
1844 const size_t MSET_SIZE = 10;
1845
1846 Xapian::RSet rset;
1847 rset.add_document(4);
1848 rset.add_document(9);
1849
1850 {
1851 Xapian::Database db = get_database("netstats1", make_netstats1_db);
1852
1853 Xapian::Enquire enq(db);
1854 enq.set_query(query);
1855 Xapian::MSet mset = enq.get_mset(0, MSET_SIZE, &rset);
1856 TEST_EQUAL(mset.get_matches_lower_bound(), 7);
1857 TEST_EQUAL(mset.get_matches_upper_bound(), 7);
1858 TEST_EQUAL(mset.get_matches_estimated(), 7);
1859 TEST_EQUAL(mset.get_max_attained(), 1.445962071042388164);
1860 TEST_EQUAL(mset.size(), 7);
1861
1862 static const pair<Xapian::docid, double> to_compare[] = {
1863 {7, 1.445962071042388164},
1864 {3, 1.4140112748017070743},
1865 {1, 1.3747698831232337824},
1866 {5, 1.1654938419498412916},
1867 {9, 1.1654938419498412916},
1868 {4, 1.1543806706320836053},
1869 {2, 0.12268031290495594321}
1870 };
1871
1872 TEST(mset_range_is_same(mset, 0, to_compare, mset.size()));
1873 }
1874 }
1875
1876 // Coordinate matching - scores 1 for each matching term
1877 class MyWeight : public Xapian::Weight {
1878 double scale_factor;
1879
1880 public:
clone() const1881 MyWeight * clone() const {
1882 return new MyWeight;
1883 }
init(double factor)1884 void init(double factor) {
1885 scale_factor = factor;
1886 }
MyWeight()1887 MyWeight() { }
~MyWeight()1888 ~MyWeight() { }
name() const1889 std::string name() const { return "MyWeight"; }
serialise() const1890 string serialise() const { return string(); }
unserialise(const string &) const1891 MyWeight * unserialise(const string &) const { return new MyWeight; }
get_sumpart(Xapian::termcount,Xapian::termcount,Xapian::termcount) const1892 double get_sumpart(Xapian::termcount, Xapian::termcount, Xapian::termcount) const {
1893 return scale_factor;
1894 }
get_maxpart() const1895 double get_maxpart() const { return scale_factor; }
1896
get_sumextra(Xapian::termcount,Xapian::termcount) const1897 double get_sumextra(Xapian::termcount, Xapian::termcount) const { return 0; }
get_maxextra() const1898 double get_maxextra() const { return 0; }
1899 };
1900
1901 // tests user weighting scheme.
1902 // Would work with remote if we registered the weighting scheme.
1903 // FIXME: do this so we also test that functionality...
1904 DEFINE_TESTCASE(userweight1, backend && !remote) {
1905 Xapian::Enquire enquire(get_database("apitest_simpledata"));
1906 enquire.set_weighting_scheme(MyWeight());
1907 static const char * const query[] = {
1908 "this", "line", "paragraph", "rubbish"
1909 };
1910 enquire.set_query(Xapian::Query(Xapian::Query::OP_OR, query,
1911 query + sizeof(query) / sizeof(query[0])));
1912 Xapian::MSet mymset1 = enquire.get_mset(0, 100);
1913 // MyWeight scores 1 for each matching term, so the weight should equal
1914 // the number of matching terms.
1915 for (Xapian::MSetIterator i = mymset1.begin(); i != mymset1.end(); ++i) {
1916 Xapian::termcount matching_terms = 0;
1917 Xapian::TermIterator t = enquire.get_matching_terms_begin(i);
1918 while (t != enquire.get_matching_terms_end(i)) {
1919 ++matching_terms;
1920 ++t;
1921 }
1922 TEST_EQUAL(i.get_weight(), matching_terms);
1923 }
1924 }
1925
1926 // tests MatchAll queries
1927 // This is a regression test, which failed with assertion failures in
1928 // revision 9094. Also check that the results aren't ranked by relevance
1929 // (regression test for bug fixed in 1.0.9).
DEFINE_TESTCASE(matchall1,backend)1930 DEFINE_TESTCASE(matchall1, backend) {
1931 Xapian::Database db(get_database("apitest_simpledata"));
1932 Xapian::Enquire enquire(db);
1933 enquire.set_query(Xapian::Query::MatchAll);
1934 Xapian::MSet mset = enquire.get_mset(0, 10);
1935 TEST_EQUAL(mset.get_matches_lower_bound(), db.get_doccount());
1936 TEST_EQUAL(mset.get_uncollapsed_matches_lower_bound(), db.get_doccount());
1937
1938 enquire.set_query(Xapian::Query(Xapian::Query::OP_OR,
1939 Xapian::Query("nosuchterm"),
1940 Xapian::Query::MatchAll));
1941 mset = enquire.get_mset(0, 10);
1942 TEST_EQUAL(mset.get_matches_lower_bound(), db.get_doccount());
1943 TEST_EQUAL(mset.get_uncollapsed_matches_lower_bound(), db.get_doccount());
1944
1945 // Check that the results aren't ranked by relevance (fixed in 1.0.9).
1946 TEST(mset.size() > 1);
1947 TEST_EQUAL(mset[mset.size() - 1].get_weight(), 0);
1948 TEST_EQUAL(*mset[0], 1);
1949 TEST_EQUAL(*mset[mset.size() - 1], mset.size());
1950 }
1951
1952 // Test using a ValueSetMatchDecider
1953 DEFINE_TESTCASE(valuesetmatchdecider2, backend && !remote) {
1954 Xapian::Database db(get_database("apitest_phrase"));
1955 Xapian::Enquire enq(db);
1956 enq.set_query(Xapian::Query("leav"));
1957
1958 Xapian::ValueSetMatchDecider vsmd1(1, true);
1959 vsmd1.add_value("n");
1960 Xapian::ValueSetMatchDecider vsmd2(1, false);
1961 vsmd2.add_value("n");
1962
1963 Xapian::MSet mymset = enq.get_mset(0, 20);
1964 mset_expect_order(mymset, 8, 6, 4, 5, 7, 10, 12, 11, 13, 9, 14);
1965 mymset = enq.get_mset(0, 20, 0, NULL, &vsmd1);
1966 mset_expect_order(mymset, 6, 12);
1967 mymset = enq.get_mset(0, 20, 0, NULL, &vsmd2);
1968 mset_expect_order(mymset, 8, 4, 5, 7, 10, 11, 13, 9, 14);
1969 }
1970