1 /* api_anydb.cc: tests which work with any backend
2  *
3  * Copyright 1999,2000,2001 BrightStation PLC
4  * Copyright 2002 Ananova Ltd
5  * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012 Olly Betts
6  * Copyright 2006,2008 Lemur Consulting Ltd
7  * Copyright 2011 Action Without Borders
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_anydb.h"
28 
29 #include <algorithm>
30 #include <string>
31 
32 #include <xapian.h>
33 #include "backendmanager_local.h"
34 #include "testsuite.h"
35 #include "testutils.h"
36 #include "utils.h"
37 
38 #include "apitest.h"
39 
40 #include <list>
41 
42 using namespace std;
43 
44 static void
print_mset_weights(const Xapian::MSet & mset)45 print_mset_weights(const Xapian::MSet &mset)
46 {
47     Xapian::MSetIterator i = mset.begin();
48     for ( ; i != mset.end(); ++i) {
49         tout << " " << i.get_weight();
50     }
51 }
52 
53 static void
print_mset_percentages(const Xapian::MSet & mset)54 print_mset_percentages(const Xapian::MSet &mset)
55 {
56     Xapian::MSetIterator i = mset.begin();
57     for ( ; i != mset.end(); ++i) {
58         tout << " " << mset.convert_to_percent(i);
59     }
60 }
61 
62 static Xapian::Query
query(Xapian::Query::op op,const string & t1=string (),const string & t2=string (),const string & t3=string (),const string & t4=string (),const string & t5=string (),const string & t6=string (),const string & t7=string (),const string & t8=string (),const string & t9=string (),const string & t10=string ())63 query(Xapian::Query::op op,
64       const string & t1 = string(), const string & t2 = string(),
65       const string & t3 = string(), const string & t4 = string(),
66       const string & t5 = string(), const string & t6 = string(),
67       const string & t7 = string(), const string & t8 = string(),
68       const string & t9 = string(), const string & t10 = string())
69 {
70     vector<string> v;
71     Xapian::Stem stemmer("english");
72     if (!t1.empty()) v.push_back(stemmer(t1));
73     if (!t2.empty()) v.push_back(stemmer(t2));
74     if (!t3.empty()) v.push_back(stemmer(t3));
75     if (!t4.empty()) v.push_back(stemmer(t4));
76     if (!t5.empty()) v.push_back(stemmer(t5));
77     if (!t6.empty()) v.push_back(stemmer(t6));
78     if (!t7.empty()) v.push_back(stemmer(t7));
79     if (!t8.empty()) v.push_back(stemmer(t8));
80     if (!t9.empty()) v.push_back(stemmer(t9));
81     if (!t10.empty()) v.push_back(stemmer(t10));
82     return Xapian::Query(op, v.begin(), v.end());
83 }
84 
85 static Xapian::Query
query(Xapian::Query::op op,Xapian::termcount parameter,const string & t1=string (),const string & t2=string (),const string & t3=string (),const string & t4=string (),const string & t5=string (),const string & t6=string (),const string & t7=string (),const string & t8=string (),const string & t9=string (),const string & t10=string ())86 query(Xapian::Query::op op, Xapian::termcount parameter,
87       const string & t1 = string(), const string & t2 = string(),
88       const string & t3 = string(), const string & t4 = string(),
89       const string & t5 = string(), const string & t6 = string(),
90       const string & t7 = string(), const string & t8 = string(),
91       const string & t9 = string(), const string & t10 = string())
92 {
93     vector<string> v;
94     Xapian::Stem stemmer("english");
95     if (!t1.empty()) v.push_back(stemmer(t1));
96     if (!t2.empty()) v.push_back(stemmer(t2));
97     if (!t3.empty()) v.push_back(stemmer(t3));
98     if (!t4.empty()) v.push_back(stemmer(t4));
99     if (!t5.empty()) v.push_back(stemmer(t5));
100     if (!t6.empty()) v.push_back(stemmer(t6));
101     if (!t7.empty()) v.push_back(stemmer(t7));
102     if (!t8.empty()) v.push_back(stemmer(t8));
103     if (!t9.empty()) v.push_back(stemmer(t9));
104     if (!t10.empty()) v.push_back(stemmer(t10));
105     return Xapian::Query(op, v.begin(), v.end(), parameter);
106 }
107 
108 static Xapian::Query
query(const string & t)109 query(const string &t)
110 {
111     return Xapian::Query(Xapian::Stem("english")(t));
112 }
113 
114 // #######################################################################
115 // # Tests start here
116 
117 // tests that the backend doesn't return zero docids
DEFINE_TESTCASE(zerodocid1,backend)118 DEFINE_TESTCASE(zerodocid1, backend) {
119     // open the database (in this case a simple text file
120     // we prepared earlier)
121 
122     Xapian::Database mydb(get_database("apitest_onedoc"));
123 
124     Xapian::Enquire enquire(mydb);
125 
126     // make a simple query, with one word in it - "word".
127     enquire.set_query(Xapian::Query("word"));
128 
129     // retrieve the top ten results (we only expect one)
130     Xapian::MSet mymset = enquire.get_mset(0, 10);
131 
132     // We've done the query, now check that the result is what
133     // we expect (1 document, with non-zero docid)
134     TEST_MSET_SIZE(mymset, 1);
135 
136     TEST_AND_EXPLAIN(*(mymset.begin()) != 0,
137 		     "A query on a database returned a zero docid");
138 
139     return true;
140 }
141 
142 // tests that an empty query returns no matches
DEFINE_TESTCASE(emptyquery1,backend)143 DEFINE_TESTCASE(emptyquery1, backend) {
144     Xapian::Enquire enquire(get_database("apitest_simpledata"));
145 
146     enquire.set_query(Xapian::Query());
147     Xapian::MSet mymset = enquire.get_mset(0, 10);
148     TEST_MSET_SIZE(mymset, 0);
149     TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
150     TEST_EQUAL(mymset.get_matches_upper_bound(), 0);
151     TEST_EQUAL(mymset.get_matches_estimated(), 0);
152     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 0);
153     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 0);
154     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 0);
155 
156     vector<Xapian::Query> v;
157     enquire.set_query(Xapian::Query(Xapian::Query::OP_AND, v.begin(), v.end()));
158     mymset = enquire.get_mset(0, 10);
159     TEST_MSET_SIZE(mymset, 0);
160     TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
161     TEST_EQUAL(mymset.get_matches_upper_bound(), 0);
162     TEST_EQUAL(mymset.get_matches_estimated(), 0);
163     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 0);
164     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 0);
165     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 0);
166 
167     return true;
168 }
169 
170 // tests the document count for a simple query
DEFINE_TESTCASE(simplequery1,backend)171 DEFINE_TESTCASE(simplequery1, backend) {
172     Xapian::Enquire enquire(get_database("apitest_simpledata"));
173     enquire.set_query(Xapian::Query("word"));
174     Xapian::MSet mymset = enquire.get_mset(0, 10);
175     TEST_MSET_SIZE(mymset, 2);
176     return true;
177 }
178 
179 // tests for the right documents and weights returned with simple query
DEFINE_TESTCASE(simplequery2,backend)180 DEFINE_TESTCASE(simplequery2, backend) {
181     // open the database (in this case a simple text file
182     // we prepared earlier)
183     Xapian::Database db = get_database("apitest_simpledata");
184     Xapian::Enquire enquire(db);
185     enquire.set_query(Xapian::Query("word"));
186 
187     // retrieve the top results
188     Xapian::MSet mymset = enquire.get_mset(0, 10);
189 
190     // We've done the query, now check that the result is what
191     // we expect (documents 2 and 4)
192     mset_expect_order(mymset, 2, 4);
193 
194     // Check the weights
195     Xapian::MSetIterator i = mymset.begin();
196     // These weights are for BM25Weight(1,0,1,0.5,0.5)
197     TEST_EQUAL_DOUBLE(i.get_weight(), 1.04648168717725);
198     i++;
199     TEST_EQUAL_DOUBLE(i.get_weight(), 0.640987686595914);
200 
201     return true;
202 }
203 
204 // tests for the right document count for another simple query
DEFINE_TESTCASE(simplequery3,backend)205 DEFINE_TESTCASE(simplequery3, backend) {
206     Xapian::Enquire enquire(get_database("apitest_simpledata"));
207     enquire.set_query(query("this"));
208     Xapian::MSet mymset = enquire.get_mset(0, 10);
209 
210     // Check that 6 documents were returned.
211     TEST_MSET_SIZE(mymset, 6);
212 
213     return true;
214 }
215 
216 // tests for the right document count for a wildcard query
217 // FIXME: move this to querytest (and just use an InMemory DB).
DEFINE_TESTCASE(wildquery1,backend)218 DEFINE_TESTCASE(wildquery1, backend) {
219     Xapian::QueryParser queryparser;
220     unsigned flags = Xapian::QueryParser::FLAG_WILDCARD |
221 		     Xapian::QueryParser::FLAG_LOVEHATE;
222     queryparser.set_stemmer(Xapian::Stem("english"));
223     queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_ALL);
224     Xapian::Database db = get_database("apitest_simpledata");
225     queryparser.set_database(db);
226     Xapian::Enquire enquire(db);
227 
228     Xapian::Query qobj = queryparser.parse_query("th*", flags);
229     tout << qobj.get_description() << endl;
230     enquire.set_query(qobj);
231     Xapian::MSet mymset = enquire.get_mset(0, 10);
232     // Check that 6 documents were returned.
233     TEST_MSET_SIZE(mymset, 6);
234 
235     qobj = queryparser.parse_query("notindb* \"this\"", flags);
236     tout << qobj.get_description() << endl;
237     enquire.set_query(qobj);
238     mymset = enquire.get_mset(0, 10);
239     // Check that 6 documents were returned.
240     TEST_MSET_SIZE(mymset, 6);
241 
242     qobj = queryparser.parse_query("+notindb* \"this\"", flags);
243     tout << qobj.get_description() << endl;
244     enquire.set_query(qobj);
245     mymset = enquire.get_mset(0, 10);
246     // Check that 0 documents were returned.
247     TEST_MSET_SIZE(mymset, 0);
248 
249     return true;
250 }
251 
252 // multidb1 and multidb2 no longer exist.
253 
254 // test that a multidb with 2 dbs query returns correct docids
255 DEFINE_TESTCASE(multidb3, backend && !multi) {
256     Xapian::Database mydb2(get_database("apitest_simpledata"));
257     mydb2.add_database(get_database("apitest_simpledata2"));
258     Xapian::Enquire enquire(mydb2);
259 
260     // make a query
261     Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
262     enquire.set_weighting_scheme(Xapian::BoolWeight());
263     enquire.set_query(myquery);
264 
265     // retrieve the top ten results
266     Xapian::MSet mymset = enquire.get_mset(0, 10);
267     mset_expect_order(mymset, 2, 3, 7);
268 
269     return true;
270 }
271 
272 // test that a multidb with 3 dbs query returns correct docids
273 DEFINE_TESTCASE(multidb4, backend && !multi) {
274     Xapian::Database mydb2(get_database("apitest_simpledata"));
275     mydb2.add_database(get_database("apitest_simpledata2"));
276     mydb2.add_database(get_database("apitest_termorder"));
277     Xapian::Enquire enquire(mydb2);
278 
279     // make a query
280     Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
281     enquire.set_weighting_scheme(Xapian::BoolWeight());
282     enquire.set_query(myquery);
283 
284     // retrieve the top ten results
285     Xapian::MSet mymset = enquire.get_mset(0, 10);
286     mset_expect_order(mymset, 2, 3, 4, 10);
287 
288     return true;
289 }
290 
291 // tests MultiPostList::skip_to().
292 DEFINE_TESTCASE(multidb5, backend && !multi) {
293     Xapian::Database mydb2(get_database("apitest_simpledata"));
294     mydb2.add_database(get_database("apitest_simpledata2"));
295     Xapian::Enquire enquire(mydb2);
296 
297     // make a query
298     Xapian::Query myquery = query(Xapian::Query::OP_AND, "inmemory", "word");
299     enquire.set_weighting_scheme(Xapian::BoolWeight());
300     enquire.set_query(myquery);
301 
302     // retrieve the top ten results
303     Xapian::MSet mymset = enquire.get_mset(0, 10);
304     mset_expect_order(mymset, 2);
305 
306     return true;
307 }
308 
309 // tests that when specifying maxitems to get_mset, no more than
310 // that are returned.
DEFINE_TESTCASE(msetmaxitems1,backend)311 DEFINE_TESTCASE(msetmaxitems1, backend) {
312     Xapian::Enquire enquire(get_database("apitest_simpledata"));
313     enquire.set_query(query("this"));
314     Xapian::MSet mymset = enquire.get_mset(0, 1);
315     TEST_MSET_SIZE(mymset, 1);
316 
317     mymset = enquire.get_mset(0, 5);
318     TEST_MSET_SIZE(mymset, 5);
319 
320     return true;
321 }
322 
323 // tests the returned weights are as expected (regression test for remote
324 // backend which was using the average weight rather than the actual document
325 // weight for computing weights - fixed in 1.0.0).
DEFINE_TESTCASE(expandweights1,backend)326 DEFINE_TESTCASE(expandweights1, backend) {
327     Xapian::Enquire enquire(get_database("apitest_simpledata"));
328     enquire.set_query(Xapian::Query("this"));
329 
330     Xapian::MSet mymset = enquire.get_mset(0, 10);
331 
332     Xapian::RSet myrset;
333     Xapian::MSetIterator i = mymset.begin();
334     myrset.add_document(*i);
335     myrset.add_document(*(++i));
336 
337     Xapian::ESet eset = enquire.get_eset(3, myrset, enquire.USE_EXACT_TERMFREQ);
338     TEST_EQUAL(eset.size(), 3);
339     TEST_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
340     TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
341     TEST_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
342 
343     return true;
344 }
345 
346 // Just like test_expandweights1 but without USE_EXACT_TERMFREQ.
DEFINE_TESTCASE(expandweights2,backend)347 DEFINE_TESTCASE(expandweights2, backend) {
348     Xapian::Enquire enquire(get_database("apitest_simpledata"));
349     enquire.set_query(Xapian::Query("this"));
350 
351     Xapian::MSet mymset = enquire.get_mset(0, 10);
352 
353     Xapian::RSet myrset;
354     Xapian::MSetIterator i = mymset.begin();
355     myrset.add_document(*i);
356     myrset.add_document(*(++i));
357 
358     Xapian::ESet eset = enquire.get_eset(3, myrset);
359     TEST_EQUAL(eset.size(), 3);
360     if (!startswith(get_dbtype(), "multi")) {
361 	// For a single database, the weights should be the same with or
362 	// without USE_EXACT_TERMFREQ.
363 	TEST_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
364 	TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
365 	TEST_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
366     } else {
367 	// For multiple databases, we expect that using USE_EXACT_TERMFREQ
368 	// will result in different weights in some cases.
369 	TEST_NOT_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
370 	TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
371 	TEST_NOT_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
372     }
373 
374     return true;
375 }
376 
DEFINE_TESTCASE(expandweights3,backend)377 DEFINE_TESTCASE(expandweights3, backend) {
378     Xapian::Enquire enquire(get_database("apitest_simpledata"));
379     enquire.set_query(Xapian::Query("this"));
380 
381     Xapian::MSet mymset = enquire.get_mset(0, 10);
382 
383     Xapian::RSet myrset;
384     Xapian::MSetIterator i = mymset.begin();
385     myrset.add_document(*i);
386     myrset.add_document(*(++i));
387 
388     // Set min_wt to 0
389     Xapian::ESet eset = enquire.get_eset(50, myrset, 0, 1.0, 0, 0);
390     if (!startswith(get_dbtype(), "multi")) {
391 	// For a single database, the weights should be the same with or
392 	// without USE_EXACT_TERMFREQ.
393 	TEST_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
394 	TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
395 	TEST_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
396     } else {
397 	// For multiple databases, we expect that using USE_EXACT_TERMFREQ
398 	// will result in different weights in some cases.
399 	TEST_NOT_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
400 	TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
401 	TEST_NOT_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
402     }
403     TEST_REL(eset.back().get_weight(),>=,0);
404 
405     return true;
406 }
407 
408 
409 // tests that negative weights are returned
DEFINE_TESTCASE(expandweights4,backend)410 DEFINE_TESTCASE(expandweights4, backend) {
411     Xapian::Enquire enquire(get_database("apitest_simpledata"));
412     enquire.set_query(Xapian::Query("paragraph"));
413 
414     Xapian::MSet mymset = enquire.get_mset(0, 10);
415 
416     Xapian::RSet myrset;
417     Xapian::MSetIterator i = mymset.begin();
418     myrset.add_document(*i);
419     myrset.add_document(*(++i));
420 
421     Xapian::ESet eset = enquire.get_eset(37, myrset, 0, 1.0, 0, -100);
422     // Now include negative weights
423     TEST_EQUAL(eset.size(), 37);
424     TEST_REL(eset[36].get_weight(),<,0);
425     TEST_REL(eset[36].get_weight(),>=,-100);
426 
427     return true;
428 }
429 
430 
431 // tests that when specifying maxitems to get_eset, no more than
432 // that are returned.
DEFINE_TESTCASE(expandmaxitems1,backend)433 DEFINE_TESTCASE(expandmaxitems1, backend) {
434     Xapian::Enquire enquire(get_database("apitest_simpledata"));
435     enquire.set_query(Xapian::Query("this"));
436 
437     Xapian::MSet mymset = enquire.get_mset(0, 10);
438     tout << "mymset.size() = " << mymset.size() << endl;
439     TEST(mymset.size() >= 2);
440 
441     Xapian::RSet myrset;
442     Xapian::MSetIterator i = mymset.begin();
443     myrset.add_document(*i);
444     myrset.add_document(*(++i));
445 
446     Xapian::ESet myeset = enquire.get_eset(1, myrset);
447     TEST_EQUAL(myeset.size(), 1);
448 
449     return true;
450 }
451 
452 // tests that a pure boolean query has all weights set to 0
DEFINE_TESTCASE(boolquery1,backend)453 DEFINE_TESTCASE(boolquery1, backend) {
454     Xapian::Query myboolquery(query("this"));
455 
456     // open the database (in this case a simple text file
457     // we prepared earlier)
458     Xapian::Enquire enquire(get_database("apitest_simpledata"));
459     enquire.set_query(myboolquery);
460     enquire.set_weighting_scheme(Xapian::BoolWeight());
461 
462     // retrieve the top results
463     Xapian::MSet mymset = enquire.get_mset(0, 10);
464 
465     TEST_NOT_EQUAL(mymset.size(), 0);
466     TEST_EQUAL(mymset.get_max_possible(), 0);
467     for (Xapian::MSetIterator i = mymset.begin(); i != mymset.end(); ++i) {
468 	TEST_EQUAL(i.get_weight(), 0);
469     }
470     return true;
471 }
472 
473 // tests that get_mset() specifying "this" works as expected
DEFINE_TESTCASE(msetfirst1,backend)474 DEFINE_TESTCASE(msetfirst1, backend) {
475     Xapian::Enquire enquire(get_database("apitest_simpledata"));
476     enquire.set_query(query("this"));
477     Xapian::MSet mymset1 = enquire.get_mset(0, 6);
478     Xapian::MSet mymset2 = enquire.get_mset(3, 3);
479     TEST(mset_range_is_same(mymset1, 3, mymset2, 0, 3));
480 
481     // Regression test - we weren't adjusting the index into items[] by
482     // firstitem in api/omenquire.cc.
483     TEST_EQUAL(mymset1[5].get_document().get_data(),
484 	       mymset2[2].get_document().get_data());
485     return true;
486 }
487 
488 // tests the converting-to-percent functions
DEFINE_TESTCASE(topercent1,backend)489 DEFINE_TESTCASE(topercent1, backend) {
490     Xapian::Enquire enquire(get_database("apitest_simpledata"));
491     enquire.set_query(query("this"));
492     Xapian::MSet mymset = enquire.get_mset(0, 20);
493 
494     int last_pct = 100;
495     Xapian::MSetIterator i = mymset.begin();
496     for ( ; i != mymset.end(); ++i) {
497 	int pct = mymset.convert_to_percent(i);
498 	TEST_AND_EXPLAIN(pct == i.get_percent(),
499 			 "convert_to_%(msetitor) != convert_to_%(wt)");
500 	TEST_AND_EXPLAIN(pct == mymset.convert_to_percent(i.get_weight()),
501 			 "convert_to_%(msetitor) != convert_to_%(wt)");
502         TEST_AND_EXPLAIN(pct >= 0 && pct <= 100,
503 			 "percentage out of range: " << pct);
504         TEST_AND_EXPLAIN(pct <= last_pct, "percentage increased down mset");
505 	last_pct = pct;
506     }
507     return true;
508 }
509 
510 // tests the percentage values returned
DEFINE_TESTCASE(topercent2,backend)511 DEFINE_TESTCASE(topercent2, backend) {
512     BackendManagerLocal local_manager;
513     local_manager.set_datadir(test_driver::get_srcdir() + "/testdata/");
514     Xapian::Enquire localenq(local_manager.get_database("apitest_simpledata"));
515     Xapian::Enquire enquire(get_database("apitest_simpledata"));
516 
517     int pct;
518 
519     // First, test a search in which the top document scores 100%.
520     enquire.set_query(query("this"));
521     localenq.set_query(query("this"));
522     Xapian::MSet mymset = enquire.get_mset(0, 20);
523     Xapian::MSet localmset = localenq.get_mset(0, 20);
524 
525     Xapian::MSetIterator i = mymset.begin();
526     TEST(i != mymset.end());
527     pct = mymset.convert_to_percent(i);
528     TEST_EQUAL(pct, 100);
529 
530     TEST_EQUAL(mymset.get_matches_lower_bound(), localmset.get_matches_lower_bound());
531     TEST_EQUAL(mymset.get_matches_upper_bound(), localmset.get_matches_upper_bound());
532     TEST_EQUAL(mymset.get_matches_estimated(), localmset.get_matches_estimated());
533     TEST_EQUAL_DOUBLE(mymset.get_max_attained(), localmset.get_max_attained());
534     TEST_EQUAL(mymset.size(), localmset.size());
535     TEST(mset_range_is_same(mymset, 0, localmset, 0, mymset.size()));
536 
537     // A search in which the top document doesn't have 100%
538     Xapian::Query q = query(Xapian::Query::OP_OR,
539 			    "this", "line", "paragraph", "rubbish");
540     enquire.set_query(q);
541     localenq.set_query(q);
542     mymset = enquire.get_mset(0, 20);
543     localmset = localenq.get_mset(0, 20);
544 
545     i = mymset.begin();
546     TEST(i != mymset.end());
547     pct = mymset.convert_to_percent(i);
548     TEST_REL(pct,>,60);
549     TEST_REL(pct,<,76);
550 
551     ++i;
552 
553     TEST(i != mymset.end());
554     pct = mymset.convert_to_percent(i);
555     TEST_REL(pct,>,40);
556     TEST_REL(pct,<,50);
557 
558     TEST_EQUAL(mymset.get_matches_lower_bound(), localmset.get_matches_lower_bound());
559     TEST_EQUAL(mymset.get_matches_upper_bound(), localmset.get_matches_upper_bound());
560     TEST_EQUAL(mymset.get_matches_estimated(), localmset.get_matches_estimated());
561     TEST_EQUAL_DOUBLE(mymset.get_max_attained(), localmset.get_max_attained());
562     TEST_EQUAL(mymset.size(), localmset.size());
563     TEST(mset_range_is_same(mymset, 0, localmset, 0, mymset.size()));
564 
565     return true;
566 }
567 
568 class myExpandFunctor : public Xapian::ExpandDecider {
569     public:
operator ()(const string & tname) const570 	bool operator()(const string & tname) const {
571 	    unsigned long sum = 0;
572 	    for (string::const_iterator i=tname.begin(); i!=tname.end(); ++i) {
573 		sum += *i;
574 	    }
575 //	    if (verbose) {
576 //		tout << tname << "==> " << sum << "\n";
577 //	    }
578 	    return (sum % 2) == 0;
579 	}
580 };
581 
582 // tests the expand decision functor
DEFINE_TESTCASE(expandfunctor1,backend)583 DEFINE_TESTCASE(expandfunctor1, backend) {
584     Xapian::Enquire enquire(get_database("apitest_simpledata"));
585     enquire.set_query(Xapian::Query("this"));
586 
587     Xapian::MSet mymset = enquire.get_mset(0, 10);
588     TEST(mymset.size() >= 2);
589 
590     Xapian::RSet myrset;
591     Xapian::MSetIterator i = mymset.begin();
592     myrset.add_document(*i);
593     myrset.add_document(*(++i));
594 
595     myExpandFunctor myfunctor;
596 
597     Xapian::ESet myeset_orig = enquire.get_eset(1000, myrset);
598     unsigned int neweset_size = 0;
599     Xapian::ESetIterator j = myeset_orig.begin();
600     for ( ; j != myeset_orig.end(); ++j) {
601         if (myfunctor(*j)) neweset_size++;
602     }
603     Xapian::ESet myeset = enquire.get_eset(neweset_size, myrset, &myfunctor);
604 
605 #if 0
606     // Compare myeset with the hand-filtered version of myeset_orig.
607     if (verbose) {
608 	tout << "orig_eset: ";
609 	copy(myeset_orig.begin(), myeset_orig.end(),
610 	     ostream_iterator<Xapian::ESetItem>(tout, " "));
611 	tout << "\n";
612 
613 	tout << "new_eset: ";
614 	copy(myeset.begin(), myeset.end(),
615 	     ostream_iterator<Xapian::ESetItem>(tout, " "));
616 	tout << "\n";
617     }
618 #endif
619     Xapian::ESetIterator orig = myeset_orig.begin();
620     Xapian::ESetIterator filt = myeset.begin();
621     for (; orig != myeset_orig.end() && filt != myeset.end(); ++orig, ++filt) {
622 	// skip over items that shouldn't be in myeset
623 	while (orig != myeset_orig.end() && !myfunctor(*orig)) {
624 	    ++orig;
625 	}
626 
627 	TEST_AND_EXPLAIN(*orig == *filt &&
628 			 orig.get_weight() == filt.get_weight(),
629 			 "Mismatch in items " << *orig << " vs. " << *filt
630 			 << " after filtering");
631     }
632 
633     while (orig != myeset_orig.end() && !myfunctor(*orig)) {
634 	++orig;
635     }
636 
637     TEST_EQUAL(orig, myeset_orig.end());
638     TEST_AND_EXPLAIN(filt == myeset.end(),
639 		     "Extra items in the filtered eset.");
640     return true;
641 }
642 
643 // tests the percent cutoff option
DEFINE_TESTCASE(pctcutoff1,backend)644 DEFINE_TESTCASE(pctcutoff1, backend) {
645     Xapian::Enquire enquire(get_database("apitest_simpledata"));
646     enquire.set_query(query(Xapian::Query::OP_OR,
647 			    "this", "line", "paragraph", "rubbish"));
648     Xapian::MSet mymset1 = enquire.get_mset(0, 100);
649 
650     if (verbose) {
651 	tout << "Original mset pcts:";
652 	print_mset_percentages(mymset1);
653 	tout << "\n";
654     }
655 
656     unsigned int num_items = 0;
657     int my_pct = 100;
658     int changes = 0;
659     Xapian::MSetIterator i = mymset1.begin();
660     int c = 0;
661     for ( ; i != mymset1.end(); ++i, ++c) {
662         int new_pct = mymset1.convert_to_percent(i);
663         if (new_pct != my_pct) {
664 	    changes++;
665 	    if (changes > 3) break;
666 	    num_items = c;
667 	    my_pct = new_pct;
668 	}
669     }
670 
671     TEST_AND_EXPLAIN(changes > 3, "MSet not varied enough to test");
672     if (verbose) {
673         tout << "Cutoff percent: " << my_pct << "\n";
674     }
675 
676     enquire.set_cutoff(my_pct);
677     Xapian::MSet mymset2 = enquire.get_mset(0, 100);
678 
679     if (verbose) {
680         tout << "Percentages after cutoff:";
681 	print_mset_percentages(mymset2);
682         tout << "\n";
683     }
684 
685     TEST_AND_EXPLAIN(mymset2.size() >= num_items,
686 		     "Match with % cutoff lost too many items");
687 
688     TEST_AND_EXPLAIN(mymset2.size() == num_items ||
689 		     (mymset2.convert_to_percent(mymset2[num_items]) == my_pct &&
690 		      mymset2.convert_to_percent(mymset2.back()) == my_pct),
691 		     "Match with % cutoff returned too many items");
692 
693     return true;
694 }
695 
696 // Tests the percent cutoff option combined with collapsing
DEFINE_TESTCASE(pctcutoff2,backend)697 DEFINE_TESTCASE(pctcutoff2, backend) {
698     Xapian::Enquire enquire(get_database("apitest_simpledata"));
699     enquire.set_query(Xapian::Query("this"));
700     enquire.set_query(Xapian::Query(Xapian::Query::OP_AND_NOT, Xapian::Query("this"), Xapian::Query("banana")));
701     Xapian::MSet mset = enquire.get_mset(0, 100);
702 
703     if (verbose) {
704 	tout << "Original mset pcts:";
705 	print_mset_percentages(mset);
706 	tout << "\n";
707     }
708 
709     TEST(mset.size() >= 2);
710     TEST(mset[0].get_percent() - mset[1].get_percent() >= 2);
711 
712     Xapian::percent cutoff = mset[0].get_percent() + mset[1].get_percent();
713     cutoff /= 2;
714 
715     enquire.set_cutoff(cutoff);
716     enquire.set_collapse_key(1234); // Value which is always empty.
717 
718     Xapian::MSet mset2 = enquire.get_mset(0, 1);
719     TEST_EQUAL(mset2.size(), 1);
720     TEST_EQUAL(mset2.get_matches_lower_bound(), 1);
721     TEST_REL(mset2.get_uncollapsed_matches_lower_bound(),>=,1);
722     TEST_REL(mset2.get_uncollapsed_matches_lower_bound(),<=,mset.size());
723     TEST_REL(mset2.get_uncollapsed_matches_upper_bound(),>=,mset.size());
724     TEST_REL(mset2.get_uncollapsed_matches_lower_bound(),<=,mset2.get_uncollapsed_matches_estimated());
725     TEST_REL(mset2.get_uncollapsed_matches_upper_bound(),>=,mset2.get_uncollapsed_matches_estimated());
726 
727     return true;
728 }
729 
730 // Test that the percent cutoff option returns all the answers it should.
DEFINE_TESTCASE(pctcutoff3,backend)731 DEFINE_TESTCASE(pctcutoff3, backend) {
732     Xapian::Enquire enquire(get_database("apitest_simpledata"));
733     enquire.set_query(Xapian::Query("this"));
734     Xapian::MSet mset1 = enquire.get_mset(0, 10);
735 
736     if (verbose) {
737 	tout << "Original mset pcts:";
738 	print_mset_percentages(mset1);
739 	tout << "\n";
740     }
741 
742     int percent = 100;
743     for (Xapian::MSetIterator i = mset1.begin(); i != mset1.end(); ++i) {
744 	int new_percent = mset1.convert_to_percent(i);
745 	if (new_percent != percent) {
746 	    enquire.set_cutoff(percent);
747 	    Xapian::MSet mset2 = enquire.get_mset(0, 10);
748 	    TEST_EQUAL(mset2.size(), i.get_rank());
749 	    percent = new_percent;
750 	}
751     }
752 
753     return true;
754 }
755 
756 // tests the cutoff option
DEFINE_TESTCASE(cutoff1,backend)757 DEFINE_TESTCASE(cutoff1, backend) {
758     Xapian::Enquire enquire(get_database("apitest_simpledata"));
759     enquire.set_query(query(Xapian::Query::OP_OR,
760 			    "this", "line", "paragraph", "rubbish"));
761     Xapian::MSet mymset1 = enquire.get_mset(0, 100);
762 
763     if (verbose) {
764 	tout << "Original mset weights:";
765 	print_mset_weights(mymset1);
766 	tout << "\n";
767     }
768 
769     unsigned int num_items = 0;
770     Xapian::weight my_wt = -100;
771     int changes = 0;
772     Xapian::MSetIterator i = mymset1.begin();
773     int c = 0;
774     for ( ; i != mymset1.end(); ++i, ++c) {
775         Xapian::weight new_wt = i.get_weight();
776         if (new_wt != my_wt) {
777 	    changes++;
778 	    if (changes > 3) break;
779 	    num_items = c;
780 	    my_wt = new_wt;
781 	}
782     }
783 
784     TEST_AND_EXPLAIN(changes > 3, "MSet not varied enough to test");
785     if (verbose) {
786         tout << "Cutoff weight: " << my_wt << "\n";
787     }
788 
789     enquire.set_cutoff(0, my_wt);
790     Xapian::MSet mymset2 = enquire.get_mset(0, 100);
791 
792     if (verbose) {
793         tout << "Weights after cutoff:";
794 	print_mset_weights(mymset2);
795         tout << "\n";
796     }
797 
798     TEST_AND_EXPLAIN(mymset2.size() >= num_items,
799 		     "Match with cutoff lost too many items");
800 
801     TEST_AND_EXPLAIN(mymset2.size() == num_items ||
802 		     (mymset2[num_items].get_weight() == my_wt &&
803 		      mymset2.back().get_weight() == my_wt),
804 		     "Match with cutoff returned too many items");
805 
806     return true;
807 }
808 
809 // tests the allow query terms expand option
DEFINE_TESTCASE(allowqterms1,backend)810 DEFINE_TESTCASE(allowqterms1, backend) {
811     Xapian::Enquire enquire(get_database("apitest_simpledata"));
812     string term = "paragraph";
813     enquire.set_query(Xapian::Query(term));
814 
815     Xapian::MSet mymset = enquire.get_mset(0, 10);
816     TEST(mymset.size() >= 2);
817 
818     Xapian::RSet myrset;
819     Xapian::MSetIterator i = mymset.begin();
820     myrset.add_document(*i);
821     myrset.add_document(*(++i));
822 
823     Xapian::ESet myeset = enquire.get_eset(1000, myrset);
824     Xapian::ESetIterator j = myeset.begin();
825     for ( ; j != myeset.end(); ++j) {
826 	TEST_NOT_EQUAL(*j, term);
827     }
828 
829     Xapian::ESet myeset2 = enquire.get_eset(1000, myrset, Xapian::Enquire::INCLUDE_QUERY_TERMS);
830     j = myeset2.begin();
831     for ( ; j != myeset2.end(); ++j) {
832 	if (*j == term) break;
833     }
834     TEST(j != myeset2.end());
835     return true;
836 }
837 
838 // tests that the MSet max_attained works
DEFINE_TESTCASE(maxattain1,backend)839 DEFINE_TESTCASE(maxattain1, backend) {
840     Xapian::Enquire enquire(get_database("apitest_simpledata"));
841     enquire.set_query(query("this"));
842     Xapian::MSet mymset = enquire.get_mset(0, 100);
843 
844     Xapian::weight mymax = 0;
845     Xapian::MSetIterator i = mymset.begin();
846     for ( ; i != mymset.end(); ++i) {
847         if (i.get_weight() > mymax) mymax = i.get_weight();
848     }
849     TEST_EQUAL(mymax, mymset.get_max_attained());
850 
851     return true;
852 }
853 
854 // tests a reversed boolean query
DEFINE_TESTCASE(reversebool1,backend)855 DEFINE_TESTCASE(reversebool1, backend) {
856     Xapian::Enquire enquire(get_database("apitest_simpledata"));
857     enquire.set_query(Xapian::Query("this"));
858     enquire.set_weighting_scheme(Xapian::BoolWeight());
859 
860     Xapian::MSet mymset1 = enquire.get_mset(0, 100);
861     TEST_AND_EXPLAIN(mymset1.size() > 1,
862 		     "Mset was too small to test properly");
863 
864     enquire.set_docid_order(Xapian::Enquire::ASCENDING);
865     Xapian::MSet mymset2 = enquire.get_mset(0, 100);
866     enquire.set_docid_order(Xapian::Enquire::DESCENDING);
867     Xapian::MSet mymset3 = enquire.get_mset(0, 100);
868 
869     // mymset1 and mymset2 should be identical
870     TEST_EQUAL(mymset1.size(), mymset2.size());
871 
872     {
873 	Xapian::MSetIterator i = mymset1.begin();
874 	Xapian::MSetIterator j = mymset2.begin();
875 	for ( ; i != mymset1.end(); ++i, j++) {
876 	    TEST(j != mymset2.end());
877 	    // if this fails, then setting match_sort_forward=true was not
878 	    // the same as the default.
879 	    TEST_EQUAL(*i, *j);
880 	}
881 	TEST(j == mymset2.end());
882     }
883 
884     // mymset1 and mymset3 should be same but reversed
885     TEST_EQUAL(mymset1.size(), mymset3.size());
886 
887     {
888 	Xapian::MSetIterator i = mymset1.begin();
889 	Xapian::MSetIterator j = mymset3.end();
890 	for ( ; i != mymset1.end(); ++i) {
891 	    // if this fails, then setting match_sort_forward=false didn't
892 	    // reverse the results.
893 	    TEST_EQUAL(*i, *--j);
894 	}
895     }
896 
897     return true;
898 }
899 
900 // tests a reversed boolean query, where the full mset isn't returned
DEFINE_TESTCASE(reversebool2,backend)901 DEFINE_TESTCASE(reversebool2, backend) {
902     Xapian::Enquire enquire(get_database("apitest_simpledata"));
903     enquire.set_query(Xapian::Query("this"));
904     enquire.set_weighting_scheme(Xapian::BoolWeight());
905 
906     Xapian::MSet mymset1 = enquire.get_mset(0, 100);
907 
908     TEST_AND_EXPLAIN(mymset1.size() > 1,
909 		     "Mset was too small to test properly");
910 
911     enquire.set_docid_order(Xapian::Enquire::ASCENDING);
912     Xapian::doccount msize = mymset1.size() / 2;
913     Xapian::MSet mymset2 = enquire.get_mset(0, msize);
914     enquire.set_docid_order(Xapian::Enquire::DESCENDING);
915     Xapian::MSet mymset3 = enquire.get_mset(0, msize);
916 
917     // mymset2 should be first msize items of mymset1
918     TEST_EQUAL(msize, mymset2.size());
919     {
920 	Xapian::MSetIterator i = mymset1.begin();
921 	Xapian::MSetIterator j = mymset2.begin();
922 	for ( ; j != mymset2.end(); ++i, j++) {
923 	    TEST(i != mymset1.end());
924 	    // if this fails, then setting match_sort_forward=true was not
925 	    // the same as the default.
926 	    TEST_EQUAL(*i, *j);
927 	}
928 	// mymset1 should be larger.
929 	TEST(i != mymset1.end());
930     }
931 
932     // mymset3 should be last msize items of mymset1, in reverse order
933     TEST_EQUAL(msize, mymset3.size());
934     {
935 	Xapian::MSetIterator i = mymset1.end();
936 	Xapian::MSetIterator j;
937 	for (j = mymset3.begin(); j != mymset3.end(); j++) {
938 	    // if this fails, then setting match_sort_forward=false didn't
939 	    // reverse the results.
940 	    TEST_EQUAL(*--i, *j);
941 	}
942     }
943 
944     return true;
945 }
946 
947 // tests that get_matching_terms() returns the terms in the right order
DEFINE_TESTCASE(getmterms1,backend)948 DEFINE_TESTCASE(getmterms1, backend) {
949     list<string> answers_list;
950     answers_list.push_back("one");
951     answers_list.push_back("two");
952     answers_list.push_back("three");
953     answers_list.push_back("four");
954 
955     Xapian::Database mydb(get_database("apitest_termorder"));
956     Xapian::Enquire enquire(mydb);
957 
958     Xapian::Query myquery(Xapian::Query::OP_OR,
959 	    Xapian::Query(Xapian::Query::OP_AND,
960 		    Xapian::Query("one", 1, 1),
961 		    Xapian::Query("three", 1, 3)),
962 	    Xapian::Query(Xapian::Query::OP_OR,
963 		    Xapian::Query("four", 1, 4),
964 		    Xapian::Query("two", 1, 2)));
965 
966     enquire.set_query(myquery);
967 
968     Xapian::MSet mymset = enquire.get_mset(0, 10);
969 
970     TEST_MSET_SIZE(mymset, 1);
971     list<string> list(enquire.get_matching_terms_begin(mymset.begin()),
972 			  enquire.get_matching_terms_end(mymset.begin()));
973     TEST(list == answers_list);
974 
975     return true;
976 }
977 
978 // tests that get_matching_terms() returns the terms only once
DEFINE_TESTCASE(getmterms2,backend)979 DEFINE_TESTCASE(getmterms2, backend) {
980     list<string> answers_list;
981     answers_list.push_back("one");
982     answers_list.push_back("two");
983     answers_list.push_back("three");
984 
985     Xapian::Database mydb(get_database("apitest_termorder"));
986     Xapian::Enquire enquire(mydb);
987 
988     Xapian::Query myquery(Xapian::Query::OP_OR,
989 	    Xapian::Query(Xapian::Query::OP_AND,
990 		    Xapian::Query("one", 1, 1),
991 		    Xapian::Query("three", 1, 3)),
992 	    Xapian::Query(Xapian::Query::OP_OR,
993 		    Xapian::Query("one", 1, 4),
994 		    Xapian::Query("two", 1, 2)));
995 
996     enquire.set_query(myquery);
997 
998     Xapian::MSet mymset = enquire.get_mset(0, 10);
999 
1000     TEST_MSET_SIZE(mymset, 1);
1001     list<string> list(enquire.get_matching_terms_begin(mymset.begin()),
1002 			  enquire.get_matching_terms_end(mymset.begin()));
1003     TEST(list == answers_list);
1004 
1005     return true;
1006 }
1007 
1008 // tests that the collapsing on termpos optimisation works
DEFINE_TESTCASE(poscollapse1,backend)1009 DEFINE_TESTCASE(poscollapse1, backend) {
1010     Xapian::Query myquery1(Xapian::Query::OP_OR,
1011 		     Xapian::Query("this", 1, 1),
1012 		     Xapian::Query("this", 1, 1));
1013     Xapian::Query myquery2("this", 2, 1);
1014 
1015     if (verbose) {
1016 	tout << myquery1.get_description() << "\n";
1017 	tout << myquery2.get_description() << "\n";
1018     }
1019 
1020     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1021     enquire.set_query(myquery1);
1022     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
1023 
1024     enquire.set_query(myquery2);
1025     Xapian::MSet mymset2 = enquire.get_mset(0, 10);
1026 
1027     TEST_EQUAL(mymset1, mymset2);
1028 
1029     return true;
1030 }
1031 
1032 // test that running a query twice returns the same results
DEFINE_TESTCASE(repeatquery1,backend)1033 DEFINE_TESTCASE(repeatquery1, backend) {
1034     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1035     enquire.set_query(Xapian::Query("this"));
1036 
1037     enquire.set_query(query(Xapian::Query::OP_OR, "this", "word"));
1038 
1039     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
1040     Xapian::MSet mymset2 = enquire.get_mset(0, 10);
1041     TEST_EQUAL(mymset1, mymset2);
1042 
1043     return true;
1044 }
1045 
1046 // test that prefetching documents works (at least, gives same results)
DEFINE_TESTCASE(fetchdocs1,backend)1047 DEFINE_TESTCASE(fetchdocs1, backend) {
1048     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1049     enquire.set_query(Xapian::Query("this"));
1050 
1051     enquire.set_query(query(Xapian::Query::OP_OR, "this", "word"));
1052 
1053     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
1054     Xapian::MSet mymset2 = enquire.get_mset(0, 10);
1055     TEST_EQUAL(mymset1, mymset2);
1056     mymset2.fetch(mymset2[0], mymset2[mymset2.size() - 1]);
1057     mymset2.fetch(mymset2.begin(), mymset2.end());
1058     mymset2.fetch(mymset2.begin());
1059     mymset2.fetch();
1060 
1061     Xapian::MSetIterator it1 = mymset1.begin();
1062     Xapian::MSetIterator it2 = mymset2.begin();
1063 
1064     while (it1 != mymset1.end() && it2 != mymset2.end()) {
1065 	TEST_EQUAL(it1.get_document().get_data(),
1066 		   it2.get_document().get_data());
1067 	TEST_NOT_EQUAL(it1.get_document().get_data(), "");
1068 	TEST_NOT_EQUAL(it2.get_document().get_data(), "");
1069 	it1++;
1070 	it2++;
1071     }
1072     TEST_EQUAL(it1, mymset1.end());
1073     TEST_EQUAL(it1, mymset2.end());
1074 
1075     return true;
1076 }
1077 
1078 // test that searching for a term not in the database fails nicely
DEFINE_TESTCASE(absentterm1,backend)1079 DEFINE_TESTCASE(absentterm1, backend) {
1080     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1081     enquire.set_weighting_scheme(Xapian::BoolWeight());
1082     enquire.set_query(Xapian::Query("frink"));
1083 
1084     Xapian::MSet mymset = enquire.get_mset(0, 10);
1085     mset_expect_order(mymset);
1086 
1087     return true;
1088 }
1089 
1090 // as absentterm1, but setting query from a vector of terms
DEFINE_TESTCASE(absentterm2,backend)1091 DEFINE_TESTCASE(absentterm2, backend) {
1092     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1093     vector<string> terms;
1094     terms.push_back("frink");
1095 
1096     Xapian::Query query(Xapian::Query::OP_OR, terms.begin(), terms.end());
1097     enquire.set_query(query);
1098 
1099     Xapian::MSet mymset = enquire.get_mset(0, 10);
1100     mset_expect_order(mymset);
1101 
1102     return true;
1103 }
1104 
1105 // test that rsets do sensible things
DEFINE_TESTCASE(rset1,backend)1106 DEFINE_TESTCASE(rset1, backend) {
1107     Xapian::Database mydb(get_database("apitest_rset"));
1108     Xapian::Enquire enquire(mydb);
1109     Xapian::Query myquery = query(Xapian::Query::OP_OR, "giraffe", "tiger");
1110     enquire.set_query(myquery);
1111 
1112     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
1113 
1114     Xapian::RSet myrset;
1115     myrset.add_document(1);
1116 
1117     Xapian::MSet mymset2 = enquire.get_mset(0, 10, &myrset);
1118 
1119     // We should have the same documents turn up, but 1 and 3 should
1120     // have higher weights with the RSet.
1121     TEST_MSET_SIZE(mymset1, 3);
1122     TEST_MSET_SIZE(mymset2, 3);
1123 
1124     return true;
1125 }
1126 
1127 // test that rsets do more sensible things
DEFINE_TESTCASE(rset2,backend)1128 DEFINE_TESTCASE(rset2, backend) {
1129     Xapian::Database mydb(get_database("apitest_rset"));
1130     Xapian::Enquire enquire(mydb);
1131     Xapian::Query myquery = query(Xapian::Query::OP_OR, "cuddly", "people");
1132     enquire.set_query(myquery);
1133 
1134     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
1135 
1136     Xapian::RSet myrset;
1137     myrset.add_document(2);
1138 
1139     Xapian::MSet mymset2 = enquire.get_mset(0, 10, &myrset);
1140 
1141     mset_expect_order(mymset1, 1, 2);
1142     mset_expect_order(mymset2, 2, 1);
1143 
1144     return true;
1145 }
1146 
1147 // test that rsets behave correctly with multiDBs
1148 DEFINE_TESTCASE(rsetmultidb1, backend && !multi) {
1149     Xapian::Database mydb1(get_database("apitest_rset", "apitest_simpledata2"));
1150     Xapian::Database mydb2(get_database("apitest_rset"));
1151     mydb2.add_database(get_database("apitest_simpledata2"));
1152 
1153     Xapian::Enquire enquire1(mydb1);
1154     Xapian::Enquire enquire2(mydb2);
1155 
1156     Xapian::Query myquery = query(Xapian::Query::OP_OR, "cuddly", "multiple");
1157 
1158     enquire1.set_query(myquery);
1159     enquire2.set_query(myquery);
1160 
1161     Xapian::RSet myrset1;
1162     Xapian::RSet myrset2;
1163     myrset1.add_document(4);
1164     myrset2.add_document(2);
1165 
1166     Xapian::MSet mymset1a = enquire1.get_mset(0, 10);
1167     Xapian::MSet mymset1b = enquire1.get_mset(0, 10, &myrset1);
1168     Xapian::MSet mymset2a = enquire2.get_mset(0, 10);
1169     Xapian::MSet mymset2b = enquire2.get_mset(0, 10, &myrset2);
1170 
1171     mset_expect_order(mymset1a, 1, 4);
1172     mset_expect_order(mymset1b, 4, 1);
1173     mset_expect_order(mymset2a, 1, 2);
1174     mset_expect_order(mymset2b, 2, 1);
1175 
1176     TEST(mset_range_is_same_weights(mymset1a, 0, mymset2a, 0, 2));
1177     TEST(mset_range_is_same_weights(mymset1b, 0, mymset2b, 0, 2));
1178     TEST_NOT_EQUAL(mymset1a, mymset1b);
1179     TEST_NOT_EQUAL(mymset2a, mymset2b);
1180 
1181     return true;
1182 }
1183 
1184 // regression tests - used to cause assertion in stats.h to fail
1185 // Doesn't actually fail for multi but it doesn't make sense to run there.
1186 DEFINE_TESTCASE(rsetmultidb3, backend && !multi) {
1187     Xapian::Enquire enquire(get_database("apitest_simpledata2"));
1188     enquire.set_query(query(Xapian::Query::OP_OR, "cuddly", "people"));
1189     Xapian::MSet mset = enquire.get_mset(0, 10); // used to fail assertion
1190     return true;
1191 }
1192 
1193 /// Simple test of the elite set operator.
DEFINE_TESTCASE(eliteset1,backend)1194 DEFINE_TESTCASE(eliteset1, backend) {
1195     // FIXME: OP_ELITE_SET erroneously picks the best N terms separately in
1196     // each sub-database!
1197     SKIP_TEST_FOR_BACKEND("multi");
1198 
1199     Xapian::Database mydb(get_database("apitest_simpledata"));
1200     Xapian::Enquire enquire(mydb);
1201 
1202     Xapian::Query myquery1 = query(Xapian::Query::OP_OR, "word");
1203 
1204     Xapian::Query myquery2 = query(Xapian::Query::OP_ELITE_SET, 1,
1205 				   "simple", "word");
1206 
1207     enquire.set_query(myquery1, 2); // So the query lengths are the same.
1208     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
1209 
1210     enquire.set_query(myquery2);
1211     Xapian::MSet mymset2 = enquire.get_mset(0, 10);
1212 
1213     TEST_EQUAL(mymset1, mymset2);
1214     return true;
1215 }
1216 
1217 /// Test that the elite set operator works if the set contains
1218 /// sub-expressions (regression test)
DEFINE_TESTCASE(eliteset2,backend)1219 DEFINE_TESTCASE(eliteset2, backend) {
1220     // FIXME: OP_ELITE_SET erroneously picks the best N terms separately in
1221     // each sub-database!
1222     SKIP_TEST_FOR_BACKEND("multi");
1223 
1224     Xapian::Database mydb(get_database("apitest_simpledata"));
1225     Xapian::Enquire enquire(mydb);
1226 
1227     Xapian::Query myquery1 = query(Xapian::Query::OP_AND, "word", "search");
1228 
1229     vector<Xapian::Query> qs;
1230     qs.push_back(query("this"));
1231     qs.push_back(query(Xapian::Query::OP_AND, "word", "search"));
1232     Xapian::Query myquery2(Xapian::Query::OP_ELITE_SET,
1233 			   qs.begin(), qs.end(), 1);
1234 
1235     enquire.set_query(myquery1);
1236     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
1237 
1238     enquire.set_query(myquery2);
1239     Xapian::MSet mymset2 = enquire.get_mset(0, 10);
1240 
1241     TEST_EQUAL(mymset1, mymset2);
1242     // query lengths differ so mset weights not the same (with some weighting
1243     // parameters)
1244     //test_mset_order_equal(mymset1, mymset2);
1245 
1246     return true;
1247 }
1248 
1249 /// Test that elite set doesn't affect query results if we have fewer
1250 /// terms than the threshold
DEFINE_TESTCASE(eliteset3,backend)1251 DEFINE_TESTCASE(eliteset3, backend) {
1252     Xapian::Database mydb1(get_database("apitest_simpledata"));
1253     Xapian::Enquire enquire1(mydb1);
1254 
1255     Xapian::Database mydb2(get_database("apitest_simpledata"));
1256     Xapian::Enquire enquire2(mydb2);
1257 
1258     // make a query
1259     Xapian::Stem stemmer("english");
1260 
1261     string term1 = stemmer("word");
1262     string term2 = stemmer("rubbish");
1263     string term3 = stemmer("banana");
1264 
1265     vector<string> terms;
1266     terms.push_back(term1);
1267     terms.push_back(term2);
1268     terms.push_back(term3);
1269 
1270     Xapian::Query myquery1(Xapian::Query::OP_OR, terms.begin(), terms.end());
1271     enquire1.set_query(myquery1);
1272 
1273     Xapian::Query myquery2(Xapian::Query::OP_ELITE_SET, terms.begin(), terms.end(), 3);
1274     enquire2.set_query(myquery2);
1275 
1276     // retrieve the results
1277     Xapian::MSet mymset1 = enquire1.get_mset(0, 10);
1278     Xapian::MSet mymset2 = enquire2.get_mset(0, 10);
1279 
1280     TEST_EQUAL(mymset1.get_termfreq(term1),
1281 	       mymset2.get_termfreq(term1));
1282     TEST_EQUAL(mymset1.get_termweight(term1),
1283 	       mymset2.get_termweight(term1));
1284     TEST_EQUAL(mymset1.get_termfreq(term2),
1285 	       mymset2.get_termfreq(term2));
1286     TEST_EQUAL(mymset1.get_termweight(term2),
1287 	       mymset2.get_termweight(term2));
1288     TEST_EQUAL(mymset1.get_termfreq(term3),
1289 	       mymset2.get_termfreq(term3));
1290     TEST_EQUAL(mymset1.get_termweight(term3),
1291 	       mymset2.get_termweight(term3));
1292 //    TEST_EQUAL(mymset1, mymset2);
1293 
1294     return true;
1295 }
1296 
1297 /// Test that elite set doesn't pick terms with 0 frequency
DEFINE_TESTCASE(eliteset4,backend)1298 DEFINE_TESTCASE(eliteset4, backend) {
1299     // FIXME: OP_ELITE_SET erroneously picks the best N terms separately in
1300     // each sub-database!
1301     SKIP_TEST_FOR_BACKEND("multi");
1302 
1303     Xapian::Database mydb1(get_database("apitest_simpledata"));
1304     Xapian::Enquire enquire1(mydb1);
1305 
1306     Xapian::Database mydb2(get_database("apitest_simpledata"));
1307     Xapian::Enquire enquire2(mydb2);
1308 
1309     Xapian::Query myquery1 = query("rubbish");
1310     Xapian::Query myquery2 = query(Xapian::Query::OP_ELITE_SET, 1,
1311 				   "word", "rubbish", "fibble");
1312     enquire1.set_query(myquery1);
1313     enquire2.set_query(myquery2);
1314 
1315     // retrieve the results
1316     Xapian::MSet mymset1 = enquire1.get_mset(0, 10);
1317     Xapian::MSet mymset2 = enquire2.get_mset(0, 10);
1318 
1319     TEST_NOT_EQUAL(mymset2.size(), 0);
1320     TEST_EQUAL(mymset1, mymset2);
1321 //    TEST_EQUAL(mymset1, mymset2);
1322 
1323     return true;
1324 }
1325 
1326 /// Regression test for problem with excess precision.
DEFINE_TESTCASE(eliteset5,backend)1327 DEFINE_TESTCASE(eliteset5, backend) {
1328     SKIP_TEST_FOR_BACKEND("multi");
1329 
1330     Xapian::Database mydb1(get_database("apitest_simpledata"));
1331     Xapian::Enquire enquire1(mydb1);
1332 
1333     vector<string> v;
1334     for (int i = 0; i != 3; ++i) {
1335 	v.push_back("simpl");
1336 	v.push_back("queri");
1337 
1338 	v.push_back("rubbish");
1339 	v.push_back("rubbish");
1340 	v.push_back("rubbish");
1341 	v.push_back("word");
1342 	v.push_back("word");
1343 	v.push_back("word");
1344     }
1345 
1346     Xapian::Query myquery1 = Xapian::Query(Xapian::Query::OP_ELITE_SET,
1347 					   v.begin(), v.end(), 1);
1348     myquery1 = Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT,
1349 			     myquery1,
1350 			     0.004);
1351 
1352     enquire1.set_query(myquery1);
1353     // On architectures with excess precision (or, at least, on x86), the
1354     // following call used to result in a segfault.
1355     enquire1.get_mset(0, 10);
1356 
1357     return true;
1358 }
1359 
1360 /// Test that the termfreq returned by termlists is correct.
DEFINE_TESTCASE(termlisttermfreq1,backend)1361 DEFINE_TESTCASE(termlisttermfreq1, backend) {
1362     Xapian::Database mydb(get_database("apitest_simpledata"));
1363     Xapian::Enquire enquire(mydb);
1364     Xapian::Stem stemmer("english");
1365     Xapian::RSet rset1;
1366     Xapian::RSet rset2;
1367     rset1.add_document(5);
1368     rset2.add_document(6);
1369 
1370     Xapian::ESet eset1 = enquire.get_eset(1000, rset1);
1371     Xapian::ESet eset2 = enquire.get_eset(1000, rset2);
1372 
1373     // search for weight of term 'another'
1374     string theterm = stemmer("another");
1375 
1376     Xapian::weight wt1 = 0;
1377     Xapian::weight wt2 = 0;
1378     {
1379 	Xapian::ESetIterator i = eset1.begin();
1380 	for ( ; i != eset1.end(); i++) {
1381 	    if (*i == theterm) {
1382 		wt1 = i.get_weight();
1383 		break;
1384 	    }
1385 	}
1386     }
1387     {
1388 	Xapian::ESetIterator i = eset2.begin();
1389 	for ( ; i != eset2.end(); i++) {
1390 	    if (*i == theterm) {
1391 		wt2 = i.get_weight();
1392 		break;
1393 	    }
1394 	}
1395     }
1396 
1397     TEST_NOT_EQUAL(wt1, 0);
1398     TEST_NOT_EQUAL(wt2, 0);
1399     TEST_EQUAL(wt1, wt2);
1400 
1401     return true;
1402 }
1403 
1404 /// Test the termfrequency and termweight info returned for query terms
DEFINE_TESTCASE(qterminfo1,backend)1405 DEFINE_TESTCASE(qterminfo1, backend) {
1406     Xapian::Database mydb1(get_database("apitest_simpledata", "apitest_simpledata2"));
1407     Xapian::Enquire enquire1(mydb1);
1408 
1409     Xapian::Database mydb2(get_database("apitest_simpledata"));
1410     mydb2.add_database(get_database("apitest_simpledata2"));
1411     Xapian::Enquire enquire2(mydb2);
1412 
1413     // make a query
1414     Xapian::Stem stemmer("english");
1415 
1416     string term1 = stemmer("word");
1417     string term2 = stemmer("inmemory");
1418     string term3 = stemmer("flibble");
1419 
1420     Xapian::Query myquery(Xapian::Query::OP_OR,
1421 		    Xapian::Query(term1),
1422 		    Xapian::Query(Xapian::Query::OP_OR,
1423 			    Xapian::Query(term2),
1424 			    Xapian::Query(term3)));
1425     enquire1.set_query(myquery);
1426     enquire2.set_query(myquery);
1427 
1428     // retrieve the results
1429     Xapian::MSet mymset1a = enquire1.get_mset(0, 0);
1430     Xapian::MSet mymset2a = enquire2.get_mset(0, 0);
1431 
1432     TEST_EQUAL(mymset1a.get_termfreq(term1),
1433 	       mymset2a.get_termfreq(term1));
1434     TEST_EQUAL(mymset1a.get_termfreq(term2),
1435 	       mymset2a.get_termfreq(term2));
1436     TEST_EQUAL(mymset1a.get_termfreq(term3),
1437 	       mymset2a.get_termfreq(term3));
1438 
1439     TEST_EQUAL(mymset1a.get_termfreq(term1), 3);
1440     TEST_EQUAL(mymset1a.get_termfreq(term2), 1);
1441     TEST_EQUAL(mymset1a.get_termfreq(term3), 0);
1442 
1443     TEST_NOT_EQUAL(mymset1a.get_termweight(term1), 0);
1444     TEST_NOT_EQUAL(mymset1a.get_termweight(term2), 0);
1445     // non-existent terms should have 0 weight.
1446     TEST_EQUAL(mymset1a.get_termweight(term3), 0);
1447 
1448     TEST_EQUAL(mymset1a.get_termfreq(stemmer("banana")), 1);
1449     TEST_EXCEPTION(Xapian::InvalidArgumentError,
1450 		   mymset1a.get_termweight(stemmer("banana")));
1451 
1452     TEST_EQUAL(mymset1a.get_termfreq("sponge"), 0);
1453     TEST_EXCEPTION(Xapian::InvalidArgumentError,
1454 		   mymset1a.get_termweight("sponge"));
1455 
1456     return true;
1457 }
1458 
1459 /// Regression test for bug #37.
DEFINE_TESTCASE(qterminfo2,backend)1460 DEFINE_TESTCASE(qterminfo2, backend) {
1461     Xapian::Database db(get_database("apitest_simpledata"));
1462     Xapian::Enquire enquire(db);
1463 
1464     // make a query
1465     Xapian::Stem stemmer("english");
1466 
1467     string term1 = stemmer("paragraph");
1468     string term2 = stemmer("another");
1469 
1470     Xapian::Query query(Xapian::Query::OP_AND_NOT, term1,
1471 	    Xapian::Query(Xapian::Query::OP_AND, term1, term2));
1472     enquire.set_query(query);
1473 
1474     // retrieve the results
1475     // Note: get_mset() used to throw "AssertionError" in debug builds
1476     Xapian::MSet mset = enquire.get_mset(0, 10);
1477 
1478     TEST_NOT_EQUAL(mset.get_termweight("paragraph"), 0);
1479 
1480     return true;
1481 }
1482 
1483 // tests that when specifying that no items are to be returned, those
1484 // statistics which should be the same are.
DEFINE_TESTCASE(msetzeroitems1,backend)1485 DEFINE_TESTCASE(msetzeroitems1, backend) {
1486     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1487     enquire.set_query(query("this"));
1488     Xapian::MSet mymset1 = enquire.get_mset(0, 0);
1489 
1490     Xapian::MSet mymset2 = enquire.get_mset(0, 1);
1491 
1492     TEST_EQUAL(mymset1.get_max_possible(), mymset2.get_max_possible());
1493 
1494     return true;
1495 }
1496 
1497 // test that the matches_* of a simple query are as expected
DEFINE_TESTCASE(matches1,backend)1498 DEFINE_TESTCASE(matches1, backend) {
1499     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1500     Xapian::Query myquery;
1501     Xapian::MSet mymset;
1502 
1503     myquery = query("word");
1504     enquire.set_query(myquery);
1505     mymset = enquire.get_mset(0, 10);
1506     TEST_EQUAL(mymset.get_matches_lower_bound(), 2);
1507     TEST_EQUAL(mymset.get_matches_estimated(), 2);
1508     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1509     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 2);
1510     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 2);
1511     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1512 
1513     myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
1514     enquire.set_query(myquery);
1515     mymset = enquire.get_mset(0, 10);
1516     TEST_EQUAL(mymset.get_matches_lower_bound(), 2);
1517     TEST_EQUAL(mymset.get_matches_estimated(), 2);
1518     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1519     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 2);
1520     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 2);
1521     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1522 
1523     myquery = query(Xapian::Query::OP_AND, "inmemory", "word");
1524     enquire.set_query(myquery);
1525     mymset = enquire.get_mset(0, 10);
1526     TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
1527     TEST_EQUAL(mymset.get_matches_estimated(), 0);
1528     TEST_EQUAL(mymset.get_matches_upper_bound(), 0);
1529     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 0);
1530     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 0);
1531     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 0);
1532 
1533     myquery = query(Xapian::Query::OP_AND, "simple", "word");
1534     enquire.set_query(myquery);
1535     mymset = enquire.get_mset(0, 10);
1536     TEST_EQUAL(mymset.get_matches_lower_bound(), 2);
1537     TEST_EQUAL(mymset.get_matches_estimated(), 2);
1538     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1539     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 2);
1540     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 2);
1541     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1542 
1543     myquery = query(Xapian::Query::OP_AND, "simple", "word");
1544     enquire.set_query(myquery);
1545     mymset = enquire.get_mset(0, 0);
1546     // For a single database, this is true, but not for "multi" (since there
1547     // one sub-database has 3 documents and simple and word both have termfreq
1548     // of 2, so the matcher can tell at least one document must match!)
1549     // TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
1550     TEST_REL(mymset.get_matches_lower_bound(),<=,mymset.get_matches_estimated());
1551     TEST_EQUAL(mymset.get_matches_estimated(), 1);
1552     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1553     TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),<=,mymset.get_uncollapsed_matches_estimated());
1554     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 1);
1555     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1556 
1557     mymset = enquire.get_mset(0, 1);
1558     TEST_EQUAL(mymset.get_matches_lower_bound(), 2);
1559     TEST_EQUAL(mymset.get_matches_estimated(), 2);
1560     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1561     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 2);
1562     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 2);
1563     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1564 
1565     mymset = enquire.get_mset(0, 2);
1566     TEST_EQUAL(mymset.get_matches_lower_bound(), 2);
1567     TEST_EQUAL(mymset.get_matches_estimated(), 2);
1568     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1569     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 2);
1570     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 2);
1571     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1572 
1573     myquery = query(Xapian::Query::OP_AND, "paragraph", "another");
1574     enquire.set_query(myquery);
1575     mymset = enquire.get_mset(0, 0);
1576     TEST_EQUAL(mymset.get_matches_lower_bound(), 1);
1577     TEST_EQUAL(mymset.get_matches_estimated(), 2);
1578     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1579     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 1);
1580     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 2);
1581     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1582 
1583     mymset = enquire.get_mset(0, 1);
1584     TEST_EQUAL(mymset.get_matches_lower_bound(), 1);
1585     TEST_EQUAL(mymset.get_matches_estimated(), 2);
1586     TEST_EQUAL(mymset.get_matches_upper_bound(), 2);
1587     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 1);
1588     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 2);
1589     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 2);
1590 
1591     mymset = enquire.get_mset(0, 2);
1592     TEST_EQUAL(mymset.get_matches_lower_bound(), 1);
1593     TEST_EQUAL(mymset.get_matches_estimated(), 1);
1594     TEST_EQUAL(mymset.get_matches_upper_bound(), 1);
1595     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 1);
1596     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 1);
1597     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 1);
1598 
1599     mymset = enquire.get_mset(1, 20);
1600     TEST_EQUAL(mymset.get_matches_lower_bound(), 1);
1601     TEST_EQUAL(mymset.get_matches_estimated(), 1);
1602     TEST_EQUAL(mymset.get_matches_upper_bound(), 1);
1603     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 1);
1604     TEST_EQUAL(mymset.get_uncollapsed_matches_estimated(), 1);
1605     TEST_EQUAL(mymset.get_uncollapsed_matches_upper_bound(), 1);
1606 
1607     return true;
1608 }
1609 
1610 // tests that wqf affects the document weights
DEFINE_TESTCASE(wqf1,backend)1611 DEFINE_TESTCASE(wqf1, backend) {
1612     // Both queries have length 2; in q1 word has wqf=2, in q2 word has wqf=1
1613     Xapian::Query q1("word", 2);
1614     Xapian::Query q2("word");
1615     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1616     enquire.set_query(q1);
1617     Xapian::MSet mset1 = enquire.get_mset(0, 10);
1618     enquire.set_query(q2);
1619     Xapian::MSet mset2 = enquire.get_mset(0, 2);
1620     // Check the weights
1621     TEST(mset1.begin().get_weight() > mset2.begin().get_weight());
1622     return true;
1623 }
1624 
1625 // tests that query length affects the document weights
DEFINE_TESTCASE(qlen1,backend)1626 DEFINE_TESTCASE(qlen1, backend) {
1627     Xapian::Query q1("word");
1628     Xapian::Query q2("word");
1629     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1630     enquire.set_query(q1);
1631     Xapian::MSet mset1 = enquire.get_mset(0, 10);
1632     enquire.set_query(q2);
1633     Xapian::MSet mset2 = enquire.get_mset(0, 2);
1634     // Check the weights
1635     //TEST(mset1.begin().get_weight() < mset2.begin().get_weight());
1636     TEST(mset1.begin().get_weight() == mset2.begin().get_weight());
1637     return true;
1638 }
1639 
1640 // tests that opening a non-existent termlist throws the correct exception
DEFINE_TESTCASE(termlist1,backend)1641 DEFINE_TESTCASE(termlist1, backend) {
1642     Xapian::Database db(get_database("apitest_onedoc"));
1643     TEST_EXCEPTION(Xapian::InvalidArgumentError,
1644 		   Xapian::TermIterator t = db.termlist_begin(0));
1645     TEST_EXCEPTION(Xapian::DocNotFoundError,
1646 		   Xapian::TermIterator t = db.termlist_begin(2));
1647     /* Cause the database to be used properly, showing up problems
1648      * with the link being in a bad state.  CME */
1649     Xapian::TermIterator temp = db.termlist_begin(1);
1650     TEST_EXCEPTION(Xapian::DocNotFoundError,
1651 		   Xapian::TermIterator t = db.termlist_begin(999999999));
1652     return true;
1653 }
1654 
1655 // tests that a Xapian::TermIterator works as an STL iterator
DEFINE_TESTCASE(termlist2,backend)1656 DEFINE_TESTCASE(termlist2, backend) {
1657     Xapian::Database db(get_database("apitest_onedoc"));
1658     Xapian::TermIterator t = db.termlist_begin(1);
1659     Xapian::TermIterator tend = db.termlist_end(1);
1660 
1661     // test operator= creates a copy which compares equal
1662     Xapian::TermIterator t_copy = t;
1663     TEST_EQUAL(t, t_copy);
1664 
1665     // test copy constructor creates a copy which compares equal
1666     Xapian::TermIterator t_clone(t);
1667     TEST_EQUAL(t, t_clone);
1668 
1669     vector<string> v(t, tend);
1670 
1671     t = db.termlist_begin(1);
1672     tend = db.termlist_end(1);
1673     vector<string>::const_iterator i;
1674     for (i = v.begin(); i != v.end(); ++i) {
1675 	TEST_NOT_EQUAL(t, tend);
1676 	TEST_EQUAL(*i, *t);
1677 	t++;
1678     }
1679     TEST_EQUAL(t, tend);
1680     return true;
1681 }
1682 
1683 static Xapian::TermIterator
test_termlist3_helper()1684 test_termlist3_helper()
1685 {
1686     Xapian::Database db(get_database("apitest_onedoc"));
1687     return db.termlist_begin(1);
1688 }
1689 
1690 // tests that a Xapian::TermIterator still works when the DB is deleted
DEFINE_TESTCASE(termlist3,backend)1691 DEFINE_TESTCASE(termlist3, backend) {
1692     Xapian::TermIterator u = test_termlist3_helper();
1693     Xapian::Database db(get_database("apitest_onedoc"));
1694     Xapian::TermIterator t = db.termlist_begin(1);
1695     Xapian::TermIterator tend = db.termlist_end(1);
1696 
1697     while (t != tend) {
1698 	TEST_EQUAL(*t, *u);
1699 	t++;
1700 	u++;
1701     }
1702     return true;
1703 }
1704 
1705 // tests skip_to
DEFINE_TESTCASE(termlist4,backend)1706 DEFINE_TESTCASE(termlist4, backend) {
1707     Xapian::Database db(get_database("apitest_onedoc"));
1708     Xapian::TermIterator i = db.termlist_begin(1);
1709     i.skip_to("");
1710     i.skip_to("\xff");
1711     return true;
1712 }
1713 
1714 // tests punctuation is OK in terms (particularly in remote queries)
DEFINE_TESTCASE(puncterms1,backend)1715 DEFINE_TESTCASE(puncterms1, backend) {
1716     Xapian::Database db(get_database("apitest_punc"));
1717     Xapian::Enquire enquire(db);
1718 
1719     Xapian::Query q1("semi;colon");
1720     enquire.set_query(q1);
1721     Xapian::MSet m1 = enquire.get_mset(0, 10);
1722 
1723     Xapian::Query q2("col:on");
1724     enquire.set_query(q2);
1725     Xapian::MSet m2 = enquire.get_mset(0, 10);
1726 
1727     Xapian::Query q3("com,ma");
1728     enquire.set_query(q3);
1729     Xapian::MSet m3 = enquire.get_mset(0, 10);
1730 
1731     return true;
1732 }
1733 
1734 // test that searching for a term with a space or backslash in it works
DEFINE_TESTCASE(spaceterms1,backend)1735 DEFINE_TESTCASE(spaceterms1, backend) {
1736     Xapian::Enquire enquire(get_database("apitest_space"));
1737     Xapian::MSet mymset;
1738     Xapian::doccount count;
1739     Xapian::MSetIterator m;
1740     Xapian::Stem stemmer("english");
1741 
1742     enquire.set_query(stemmer("space man"));
1743     mymset = enquire.get_mset(0, 10);
1744     TEST_MSET_SIZE(mymset, 1);
1745     count = 0;
1746     for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1747     TEST_EQUAL(count, 1);
1748 
1749     for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
1750 	TEST_NOT_EQUAL(mymset.begin().get_document().get_data(), "");
1751 	TEST_NOT_EQUAL(mymset.begin().get_document().get_value(value_no), "");
1752     }
1753 
1754     enquire.set_query(stemmer("tab\tby"));
1755     mymset = enquire.get_mset(0, 10);
1756     TEST_MSET_SIZE(mymset, 1);
1757     count = 0;
1758     for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1759     TEST_EQUAL(count, 1);
1760 
1761     for (Xapian::valueno value_no = 0; value_no < 7; ++value_no) {
1762 	string value = mymset.begin().get_document().get_value(value_no);
1763 	TEST_NOT_EQUAL(value, "");
1764 	if (value_no == 0) {
1765 	    TEST(value.size() > 262);
1766 	    TEST_EQUAL(static_cast<unsigned char>(value[262]), 255);
1767 	}
1768     }
1769 
1770     enquire.set_query(stemmer("back\\slash"));
1771     mymset = enquire.get_mset(0, 10);
1772     TEST_MSET_SIZE(mymset, 1);
1773     count = 0;
1774     for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1775     TEST_EQUAL(count, 1);
1776 
1777     return true;
1778 }
1779 
1780 // test that XOR queries work
DEFINE_TESTCASE(xor1,backend)1781 DEFINE_TESTCASE(xor1, backend) {
1782     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1783     Xapian::Stem stemmer("english");
1784 
1785     vector<string> terms;
1786     terms.push_back(stemmer("this"));
1787     terms.push_back(stemmer("word"));
1788     terms.push_back(stemmer("of"));
1789 
1790     Xapian::Query query(Xapian::Query::OP_XOR, terms.begin(), terms.end());
1791     enquire.set_weighting_scheme(Xapian::BoolWeight());
1792     enquire.set_query(query);
1793 
1794     Xapian::MSet mymset = enquire.get_mset(0, 10);
1795     //	Docid	this	word	of	Match?
1796     //	1	*			*
1797     //	2	*	*	*	*
1798     //	3	*		*
1799     //	4	*	*
1800     //	5	*			*
1801     //	6	*			*
1802     mset_expect_order(mymset, 1, 2, 5, 6);
1803 
1804     return true;
1805 }
1806 
1807 /// Test that weighted XOR queries work (bug fixed in 1.2.1 and 1.0.21).
DEFINE_TESTCASE(xor2,backend)1808 DEFINE_TESTCASE(xor2, backend) {
1809     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1810     Xapian::Stem stemmer("english");
1811 
1812     vector<string> terms;
1813     terms.push_back(stemmer("this"));
1814     terms.push_back(stemmer("word"));
1815     terms.push_back(stemmer("of"));
1816 
1817     Xapian::Query query(Xapian::Query::OP_XOR, terms.begin(), terms.end());
1818     enquire.set_query(query);
1819 
1820     Xapian::MSet mymset = enquire.get_mset(0, 10);
1821     //	Docid	LEN	this	word	of	Match?
1822     //	1	28	2			*
1823     //	2	81	5	8	1	*
1824     //	3	15	1		2
1825     //	4	31	1	1
1826     //	5	15	1			*
1827     //	6	15	1			*
1828     mset_expect_order(mymset, 2, 1, 5, 6);
1829 
1830     return true;
1831 }
1832 
1833 // test Xapian::Database::get_document()
DEFINE_TESTCASE(getdoc1,backend)1834 DEFINE_TESTCASE(getdoc1, backend) {
1835     Xapian::Database db(get_database("apitest_onedoc"));
1836     Xapian::Document doc(db.get_document(1));
1837     TEST_EXCEPTION(Xapian::InvalidArgumentError, db.get_document(0));
1838     TEST_EXCEPTION(Xapian::DocNotFoundError, db.get_document(999999999));
1839     TEST_EXCEPTION(Xapian::DocNotFoundError, db.get_document(123456789));
1840     TEST_EXCEPTION(Xapian::DocNotFoundError, db.get_document(3));
1841     TEST_EXCEPTION(Xapian::DocNotFoundError, db.get_document(2));
1842     // Check that Document works as a handle on modification
1843     // (this was broken for the first try at Xapian::Document prior to 0.7).
1844     Xapian::Document doc2 = doc;
1845     doc.set_data("modified!");
1846     TEST_EQUAL(doc.get_data(), "modified!");
1847     TEST_EQUAL(doc.get_data(), doc2.get_data());
1848     return true;
1849 }
1850 
1851 // test whether operators with no elements work as a null query
DEFINE_TESTCASE(emptyop1,backend)1852 DEFINE_TESTCASE(emptyop1, backend) {
1853     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1854     vector<Xapian::Query> nullvec;
1855 
1856     Xapian::Query query1(Xapian::Query::OP_XOR, nullvec.begin(), nullvec.end());
1857 
1858     enquire.set_query(query1);
1859     Xapian::MSet mymset = enquire.get_mset(0, 10);
1860     TEST_MSET_SIZE(mymset, 0);
1861     TEST_EXCEPTION(Xapian::InvalidArgumentError, enquire.get_matching_terms_begin(1));
1862 
1863     return true;
1864 }
1865 
1866 // Regression test for check_at_least SEGV when there are no matches.
DEFINE_TESTCASE(checkatleast1,backend)1867 DEFINE_TESTCASE(checkatleast1, backend) {
1868     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1869     enquire.set_query(Xapian::Query("thom"));
1870     Xapian::MSet mymset = enquire.get_mset(0, 10, 11);
1871     TEST_EQUAL(0, mymset.size());
1872 
1873     return true;
1874 }
1875 
1876 // Regression test - if check_at_least was set we returned (check_at_least - 1)
1877 // results, rather than the requested msize.  Fixed in 1.0.2.
DEFINE_TESTCASE(checkatleast2,backend)1878 DEFINE_TESTCASE(checkatleast2, backend) {
1879     Xapian::Enquire enquire(get_database("apitest_simpledata"));
1880     enquire.set_query(Xapian::Query("paragraph"));
1881 
1882     Xapian::MSet mymset = enquire.get_mset(0, 3, 10);
1883     TEST_MSET_SIZE(mymset, 3);
1884     TEST_EQUAL(mymset.get_matches_lower_bound(), 5);
1885     TEST_EQUAL(mymset.get_uncollapsed_matches_lower_bound(), 5);
1886 
1887     mymset = enquire.get_mset(0, 2, 4);
1888     TEST_MSET_SIZE(mymset, 2);
1889     TEST_REL(mymset.get_matches_lower_bound(),>=,4);
1890     TEST_REL(mymset.get_matches_lower_bound(),>=,4);
1891     TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),>=,4);
1892     TEST_REL(mymset.get_uncollapsed_matches_lower_bound(),>=,4);
1893 
1894     return true;
1895 }
1896 
1897 // Feature tests - check_at_least with various sorting options.
DEFINE_TESTCASE(checkatleast3,backend)1898 DEFINE_TESTCASE(checkatleast3, backend) {
1899     Xapian::Enquire enquire(get_database("etext"));
1900     enquire.set_query(Xapian::Query("prussian")); // 60 matches.
1901 
1902     for (int order = 0; order < 3; ++order) {
1903 	switch (order) {
1904 	    case 0:
1905 		enquire.set_docid_order(Xapian::Enquire::ASCENDING);
1906 		break;
1907 	    case 1:
1908 		enquire.set_docid_order(Xapian::Enquire::DESCENDING);
1909 		break;
1910 	    case 2:
1911 		enquire.set_docid_order(Xapian::Enquire::DONT_CARE);
1912 		break;
1913 	}
1914 
1915 	for (int sort = 0; sort < 7; ++sort) {
1916 	    bool reverse = (sort & 1);
1917 	    switch (sort) {
1918 		case 0:
1919 		    enquire.set_sort_by_relevance();
1920 		    break;
1921 		case 1: case 2:
1922 		    enquire.set_sort_by_value(0, reverse);
1923 		    break;
1924 		case 3: case 4:
1925 		    enquire.set_sort_by_value_then_relevance(0, reverse);
1926 		    break;
1927 		case 5: case 6:
1928 		    enquire.set_sort_by_relevance_then_value(0, reverse);
1929 		    break;
1930 	    }
1931 
1932 	    Xapian::MSet mset = enquire.get_mset(0, 100, 500);
1933 	    TEST_MSET_SIZE(mset, 60);
1934 	    TEST_EQUAL(mset.get_matches_lower_bound(), 60);
1935 	    TEST_EQUAL(mset.get_matches_estimated(), 60);
1936 	    TEST_EQUAL(mset.get_matches_upper_bound(), 60);
1937 	    TEST_EQUAL(mset.get_uncollapsed_matches_lower_bound(), 60);
1938 	    TEST_EQUAL(mset.get_uncollapsed_matches_estimated(), 60);
1939 	    TEST_EQUAL(mset.get_uncollapsed_matches_upper_bound(), 60);
1940 
1941 	    mset = enquire.get_mset(0, 50, 100);
1942 	    TEST_MSET_SIZE(mset, 50);
1943 	    TEST_EQUAL(mset.get_matches_lower_bound(), 60);
1944 	    TEST_EQUAL(mset.get_matches_estimated(), 60);
1945 	    TEST_EQUAL(mset.get_matches_upper_bound(), 60);
1946 	    TEST_EQUAL(mset.get_uncollapsed_matches_lower_bound(), 60);
1947 	    TEST_EQUAL(mset.get_uncollapsed_matches_estimated(), 60);
1948 	    TEST_EQUAL(mset.get_uncollapsed_matches_upper_bound(), 60);
1949 
1950 	    mset = enquire.get_mset(0, 10, 50);
1951 	    TEST_MSET_SIZE(mset, 10);
1952 	    TEST_REL(mset.get_matches_lower_bound(),>=,50);
1953 	    TEST_REL(mset.get_uncollapsed_matches_lower_bound(),>=,50);
1954 	}
1955     }
1956 
1957     return true;
1958 }
1959 
1960 // tests all document postlists
DEFINE_TESTCASE(allpostlist1,backend)1961 DEFINE_TESTCASE(allpostlist1, backend) {
1962     Xapian::Database db(get_database("apitest_manydocs"));
1963     Xapian::PostingIterator i = db.postlist_begin("");
1964     unsigned int j = 1;
1965     while (i != db.postlist_end("")) {
1966 	TEST_EQUAL(*i, j);
1967 	i++;
1968 	j++;
1969     }
1970     TEST_EQUAL(j, 513);
1971 
1972     i = db.postlist_begin("");
1973     j = 1;
1974     while (i != db.postlist_end("")) {
1975 	TEST_EQUAL(*i, j);
1976 	i++;
1977 	j++;
1978 	if (j == 50) {
1979 	    j += 10;
1980 	    i.skip_to(j);
1981 	}
1982     }
1983     TEST_EQUAL(j, 513);
1984 
1985     return true;
1986 }
1987 
test_emptyterm1_helper(Xapian::Database & db)1988 static void test_emptyterm1_helper(Xapian::Database & db)
1989 {
1990     // Don't bother with postlist_begin() because allpostlist tests cover that.
1991     TEST_EXCEPTION(Xapian::InvalidArgumentError, db.positionlist_begin(1, ""));
1992     TEST_EQUAL(db.get_doccount(), db.get_termfreq(""));
1993     TEST_EQUAL(db.get_doccount() != 0, db.term_exists(""));
1994     TEST_EQUAL(db.get_doccount(), db.get_collection_freq(""));
1995 }
1996 
1997 // tests results of passing an empty term to various methods
DEFINE_TESTCASE(emptyterm1,backend)1998 DEFINE_TESTCASE(emptyterm1, backend) {
1999     Xapian::Database db(get_database("apitest_manydocs"));
2000     TEST_EQUAL(db.get_doccount(), 512);
2001     test_emptyterm1_helper(db);
2002 
2003     db = get_database("apitest_onedoc");
2004     TEST_EQUAL(db.get_doccount(), 1);
2005     test_emptyterm1_helper(db);
2006 
2007     db = get_database("");
2008     TEST_EQUAL(db.get_doccount(), 0);
2009     test_emptyterm1_helper(db);
2010 
2011     return true;
2012 }
2013 
2014 // Test for alldocs postlist with a sparse database.
DEFINE_TESTCASE(alldocspl1,writable)2015 DEFINE_TESTCASE(alldocspl1, writable) {
2016     Xapian::WritableDatabase db = get_writable_database();
2017     Xapian::Document doc;
2018     doc.set_data("5");
2019     doc.add_value(0, "5");
2020     db.replace_document(5, doc);
2021 
2022     Xapian::PostingIterator i = db.postlist_begin("");
2023     TEST(i != db.postlist_end(""));
2024     TEST_EQUAL(*i, 5);
2025     TEST_EQUAL(i.get_doclength(), 0);
2026     TEST_EQUAL(i.get_wdf(), 1);
2027     ++i;
2028     TEST(i == db.postlist_end(""));
2029 
2030     return true;
2031 }
2032 
2033 // Test reading and writing a modified alldocspostlist.
DEFINE_TESTCASE(alldocspl2,writable)2034 DEFINE_TESTCASE(alldocspl2, writable) {
2035     Xapian::PostingIterator i, end;
2036     {
2037 	Xapian::WritableDatabase db = get_writable_database();
2038 	Xapian::Document doc;
2039 	doc.set_data("5");
2040 	doc.add_value(0, "5");
2041 	db.replace_document(5, doc);
2042 
2043 	// Test iterating before committing the changes.
2044 	i = db.postlist_begin("");
2045 	end = db.postlist_end("");
2046 	TEST(i != end);
2047 	TEST_EQUAL(*i, 5);
2048 	TEST_EQUAL(i.get_doclength(), 0);
2049 	TEST_EQUAL(i.get_wdf(), 1);
2050 	++i;
2051 	TEST(i == end);
2052 
2053 	db.commit();
2054 
2055 	// Test iterating after committing the changes.
2056 	i = db.postlist_begin("");
2057 	end = db.postlist_end("");
2058 	TEST(i != end);
2059 	TEST_EQUAL(*i, 5);
2060 	TEST_EQUAL(i.get_doclength(), 0);
2061 	TEST_EQUAL(i.get_wdf(), 1);
2062 	++i;
2063 	TEST(i == end);
2064 
2065 	// Add another document.
2066 	doc = Xapian::Document();
2067 	doc.set_data("5");
2068 	doc.add_value(0, "7");
2069 	db.replace_document(7, doc);
2070 
2071 	// Test iterating through before committing the changes.
2072 	i = db.postlist_begin("");
2073 	end = db.postlist_end("");
2074 	TEST(i != end);
2075 	TEST_EQUAL(*i, 5);
2076 	TEST_EQUAL(i.get_doclength(), 0);
2077 	TEST_EQUAL(i.get_wdf(), 1);
2078 	++i;
2079 	TEST(i != end);
2080 	TEST_EQUAL(*i, 7);
2081 	TEST_EQUAL(i.get_doclength(), 0);
2082 	TEST_EQUAL(i.get_wdf(), 1);
2083 	++i;
2084 	TEST(i == end);
2085 
2086 	// Delete the first document.
2087 	db.delete_document(5);
2088 
2089 	// Test iterating through before committing the changes.
2090 	i = db.postlist_begin("");
2091 	end = db.postlist_end("");
2092 	TEST(i != end);
2093 	TEST_EQUAL(*i, 7);
2094 	TEST_EQUAL(i.get_doclength(), 0);
2095 	TEST_EQUAL(i.get_wdf(), 1);
2096 	++i;
2097 	TEST(i == end);
2098 
2099 	// Test iterating through after committing the changes, and dropping the
2100 	// reference to the main DB.
2101 	db.commit();
2102 	i = db.postlist_begin("");
2103 	end = db.postlist_end("");
2104     }
2105 
2106     TEST(i != end);
2107     TEST_EQUAL(*i, 7);
2108     TEST_EQUAL(i.get_doclength(), 0);
2109     TEST_EQUAL(i.get_wdf(), 1);
2110     ++i;
2111     TEST(i == end);
2112 
2113     return true;
2114 }
2115 
2116 // Feature test for Query::OP_SCALE_WEIGHT.
DEFINE_TESTCASE(scaleweight1,backend)2117 DEFINE_TESTCASE(scaleweight1, backend) {
2118     Xapian::Database db(get_database("apitest_phrase"));
2119     Xapian::Enquire enq(db);
2120     Xapian::QueryParser qp;
2121 
2122     static const char * queries[] = {
2123 	"pad",
2124 	"milk fridge",
2125 	"leave milk on fridge",
2126 	"ordered milk operator",
2127 	"ordered phrase operator",
2128 	"leave \"milk on fridge\"",
2129 	"notpresent",
2130 	"leave \"milk notpresent\"",
2131 	NULL
2132     };
2133     static const double multipliers[] = {
2134 	-1000000, -2.5, -1, -0.5, 0, 0.5, 1, 2.5, 1000000,
2135 	0, 0
2136     };
2137 
2138     for (const char **qstr = queries; *qstr; ++qstr) {
2139 	tout.str(string());
2140 	Xapian::Query query1 = qp.parse_query(*qstr);
2141 	tout << "query1: " << query1.get_description() << endl;
2142 	for (const double *multp = multipliers; multp[0] != multp[1]; ++multp) {
2143 	    double mult = *multp;
2144 	    if (mult < 0) {
2145 		TEST_EXCEPTION(Xapian::InvalidArgumentError,
2146 			       Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT,
2147 					     query1, mult));
2148 		continue;
2149 	    }
2150 	    Xapian::Query query2(Xapian::Query::OP_SCALE_WEIGHT, query1, mult);
2151 	    tout << "query2: " << query2.get_description() << endl;
2152 
2153 	    enq.set_query(query1);
2154 	    Xapian::MSet mset1 = enq.get_mset(0, 20);
2155 	    enq.set_query(query2);
2156 	    Xapian::MSet mset2 = enq.get_mset(0, 20);
2157 
2158 	    TEST_EQUAL(mset1.size(), mset2.size());
2159 
2160 	    Xapian::MSetIterator i1, i2;
2161 	    if (mult > 0) {
2162 		for (i1 = mset1.begin(), i2 = mset2.begin();
2163 		     i1 != mset1.end() && i2 != mset2.end(); ++i1, ++i2) {
2164 		    TEST_EQUAL_DOUBLE(i1.get_weight() * mult, i2.get_weight());
2165 		    TEST_EQUAL(*i1, *i2);
2166 		}
2167 	    } else {
2168 		// Weights in mset2 are 0; so it should be sorted by docid.
2169 		vector<Xapian::docid> ids1;
2170 		vector<Xapian::docid> ids2;
2171 		for (i1 = mset1.begin(), i2 = mset2.begin();
2172 		     i1 != mset1.end() && i2 != mset2.end(); ++i1, ++i2) {
2173 		    TEST_NOT_EQUAL_DOUBLE(i1.get_weight(), 0);
2174 		    TEST_EQUAL_DOUBLE(i2.get_weight(), 0);
2175 		    ids1.push_back(*i1);
2176 		    ids2.push_back(*i2);
2177 		}
2178 		sort(ids1.begin(), ids1.end());
2179 		TEST_EQUAL(ids1, ids2);
2180 	    }
2181 	}
2182     }
2183     return true;
2184 }
2185 
2186 // Test Query::OP_SCALE_WEIGHT being used to multiply some of the weights of a
2187 // search by zero.
DEFINE_TESTCASE(scaleweight2,backend)2188 DEFINE_TESTCASE(scaleweight2, backend) {
2189     Xapian::Database db(get_database("apitest_phrase"));
2190     Xapian::Enquire enq(db);
2191     Xapian::MSetIterator i;
2192 
2193     Xapian::Query query1("fridg");
2194     Xapian::Query query2(Xapian::Query::OP_SCALE_WEIGHT, query1, 2.5);
2195     Xapian::Query query3("milk");
2196     Xapian::Query query4(Xapian::Query::OP_SCALE_WEIGHT, query3, 0);
2197     Xapian::Query query5(Xapian::Query::OP_OR, query2, query4);
2198 
2199     // query5 should first return the same results as query1, in the same
2200     // order, and then return the results of query3 which aren't also results
2201     // of query1, in ascending docid order.  We test that this happens.
2202 
2203     // First, build a vector of docids matching the first part of the query,
2204     // and append the non-duplicate docids matching the second part of the
2205     // query.
2206     vector<Xapian::docid> ids1;
2207     set<Xapian::docid> idsin1;
2208     vector<Xapian::docid> ids3;
2209 
2210     enq.set_query(query1);
2211     Xapian::MSet mset1 = enq.get_mset(0, 20);
2212     enq.set_query(query3);
2213     Xapian::MSet mset3 = enq.get_mset(0, 20);
2214     TEST_NOT_EQUAL(mset1.size(), 0);
2215     for (i = mset1.begin(); i != mset1.end(); ++i) {
2216 	ids1.push_back(*i);
2217 	idsin1.insert(*i);
2218     }
2219     TEST_NOT_EQUAL(mset3.size(), 0);
2220     for (i = mset3.begin(); i != mset3.end(); ++i) {
2221 	if (idsin1.find(*i) != idsin1.end())
2222 	    continue;
2223 	ids3.push_back(*i);
2224     }
2225     sort(ids3.begin(), ids3.end());
2226     ids1.insert(ids1.end(), ids3.begin(), ids3.end());
2227 
2228     // Now, run the combined query and build a vector of the matching docids.
2229     vector<Xapian::docid> ids5;
2230     enq.set_query(query5);
2231     Xapian::MSet mset5 = enq.get_mset(0, 20);
2232     for (i = mset5.begin(); i != mset5.end(); ++i) {
2233 	ids5.push_back(*i);
2234     }
2235 
2236     TEST_EQUAL(ids1, ids5);
2237     return true;
2238 }
2239 
2240 // Regression test for bug fixed in 1.0.5 - this test would failed under
2241 // valgrind because it used an uninitialised value.
DEFINE_TESTCASE(bm25weight1,backend)2242 DEFINE_TESTCASE(bm25weight1, backend) {
2243     Xapian::Enquire enquire(get_database("apitest_simpledata"));
2244     enquire.set_weighting_scheme(Xapian::BM25Weight(1, 25, 1, 0.01, 0.5));
2245     enquire.set_query(Xapian::Query("word") );
2246 
2247     Xapian::MSet mset = enquire.get_mset(0, 25);
2248 
2249     return true;
2250 }
2251 
2252 // Feature test for TradWeight.
DEFINE_TESTCASE(tradweight1,backend)2253 DEFINE_TESTCASE(tradweight1, backend) {
2254     Xapian::Enquire enquire(get_database("apitest_simpledata"));
2255     enquire.set_weighting_scheme(Xapian::TradWeight());
2256     enquire.set_query(Xapian::Query("word") );
2257 
2258     Xapian::MSet mset = enquire.get_mset(0, 25);
2259     TEST_EQUAL(mset.size(), 2);
2260 
2261     enquire.set_weighting_scheme(Xapian::TradWeight(0));
2262     enquire.set_query(Xapian::Query("this") );
2263 
2264     mset = enquire.get_mset(0, 25);
2265     TEST_EQUAL(mset.size(), 6);
2266 
2267     // Check that TradWeight(0) means wdf and doc length really don't affect
2268     // the weights as stated in the documentation.
2269     TEST_EQUAL(mset[0].get_weight(), mset[5].get_weight());
2270 
2271     return true;
2272 }
2273 
2274 // Test TradWeight when weighting documents using an RSet.
2275 // Simply changed the weighting scheme used by rset2 testcase.
DEFINE_TESTCASE(tradweight4,backend)2276 DEFINE_TESTCASE(tradweight4, backend) {
2277     Xapian::Database mydb(get_database("apitest_rset"));
2278     Xapian::Enquire enquire(mydb);
2279     Xapian::Query myquery = query(Xapian::Query::OP_OR, "cuddly", "people");
2280 
2281     enquire.set_query(myquery);
2282     enquire.set_weighting_scheme(Xapian::TradWeight());
2283 
2284     Xapian::MSet mymset1 = enquire.get_mset(0, 10);
2285 
2286     Xapian::RSet myrset;
2287     myrset.add_document(2);
2288 
2289     Xapian::MSet mymset2 = enquire.get_mset(0, 10, &myrset);
2290 
2291     mset_expect_order(mymset1, 1, 2);
2292     // Document 2 should have higher weight than document 1 despite the wdf of
2293     // "people" being 1 because "people" indexes a document in the RSet whereas
2294     // "cuddly" (wdf=2) does not.
2295     mset_expect_order(mymset2, 2, 1);
2296 
2297     return true;
2298 }
2299 
2300 // Feature test for Database::get_uuid().
2301 DEFINE_TESTCASE(uuid1, backend && !multi) {
2302     SKIP_TEST_FOR_BACKEND("inmemory");
2303     Xapian::Database db = get_database("apitest_simpledata");
2304     string uuid1 = db.get_uuid();
2305     TEST_EQUAL(uuid1.size(), 36);
2306 
2307     // A database with no sub-databases has an empty UUID.
2308     Xapian::Database db2;
2309     TEST(db2.get_uuid().empty());
2310 
2311     db2.add_database(db);
2312     TEST_EQUAL(uuid1, db2.get_uuid());
2313 
2314     // Multi-database has multiple UUIDs (we don't define the format exactly
2315     // so this assumes something about the implementation).
2316     db2.add_database(db);
2317     TEST_EQUAL(uuid1 + ":" + uuid1, db2.get_uuid());
2318 
2319 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
2320     // This relies on InMemory databases not supporting uuids.
2321     // A multi-database containing a database with no uuid has no uuid.
2322     db2.add_database(Xapian::InMemory::open());
2323     TEST(db2.get_uuid().empty());
2324 #endif
2325 
2326     return true;
2327 }
2328