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