root / tags / 1.0.8 / xapian-core / tests / api_anydb.cc

Revision 10904, 69.0 kB (checked in by olly, 6 months ago)

Backport change from trunk:
tests/api_anydb.cc: Add testcase for percent cutoff plus collapsing
which most likely would have failed before Richard's recent fix
for the lower bound with collapsing and a matchdecider.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
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 Olly Betts
6 * Copyright 2006,2008 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_anydb.h"
27
28#include <algorithm>
29#include <iomanip>
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
42using namespace std;
43
44static void
45print_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
53static void
54print_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
62static Xapian::Query
63query(Xapian::Query::op op, string t1 = "", string t2 = "",
64      string t3 = "", string t4 = "", string t5 = "",
65      string t6 = "", string t7 = "", string t8 = "",
66      string t9 = "", string t10 = "")
67{
68    vector<string> v;
69    Xapian::Stem stemmer("english");
70    if (!t1.empty()) v.push_back(stemmer(t1));
71    if (!t2.empty()) v.push_back(stemmer(t2));
72    if (!t3.empty()) v.push_back(stemmer(t3));
73    if (!t4.empty()) v.push_back(stemmer(t4));
74    if (!t5.empty()) v.push_back(stemmer(t5));
75    if (!t6.empty()) v.push_back(stemmer(t6));
76    if (!t7.empty()) v.push_back(stemmer(t7));
77    if (!t8.empty()) v.push_back(stemmer(t8));
78    if (!t9.empty()) v.push_back(stemmer(t9));
79    if (!t10.empty()) v.push_back(stemmer(t10));
80    return Xapian::Query(op, v.begin(), v.end());
81}
82
83static Xapian::Query
84query(Xapian::Query::op op, Xapian::termcount parameter,
85      string t1 = "", string t2 = "",
86      string t3 = "", string t4 = "", string t5 = "",
87      string t6 = "", string t7 = "", string t8 = "",
88      string t9 = "", string t10 = "")
89{
90    vector<string> v;
91    Xapian::Stem stemmer("english");
92    if (!t1.empty()) v.push_back(stemmer(t1));
93    if (!t2.empty()) v.push_back(stemmer(t2));
94    if (!t3.empty()) v.push_back(stemmer(t3));
95    if (!t4.empty()) v.push_back(stemmer(t4));
96    if (!t5.empty()) v.push_back(stemmer(t5));
97    if (!t6.empty()) v.push_back(stemmer(t6));
98    if (!t7.empty()) v.push_back(stemmer(t7));
99    if (!t8.empty()) v.push_back(stemmer(t8));
100    if (!t9.empty()) v.push_back(stemmer(t9));
101    if (!t10.empty()) v.push_back(stemmer(t10));
102    return Xapian::Query(op, v.begin(), v.end(), parameter);
103}
104
105static Xapian::Query
106query(const string &t)
107{
108    return Xapian::Query(Xapian::Stem("english")(t));
109}
110
111// #######################################################################
112// # Tests start here
113
114// tests that the backend doesn't return zero docids
115DEFINE_TESTCASE(zerodocid1, backend) {
116    // open the database (in this case a simple text file
117    // we prepared earlier)
118
119    Xapian::Database mydb(get_database("apitest_onedoc"));
120
121    Xapian::Enquire enquire(mydb);
122
123    // make a simple query, with one word in it - "word".
124    enquire.set_query(Xapian::Query("word"));
125
126    // retrieve the top ten results (we only expect one)
127    Xapian::MSet mymset = enquire.get_mset(0, 10);
128
129    // We've done the query, now check that the result is what
130    // we expect (1 document, with non-zero docid)
131    TEST_MSET_SIZE(mymset, 1);
132
133    TEST_AND_EXPLAIN(*(mymset.begin()) != 0,
134                     "A query on a database returned a zero docid");
135
136    return true;
137}
138
139// tests that an empty query returns no matches
140DEFINE_TESTCASE(emptyquery1, backend) {
141    Xapian::Enquire enquire(get_database("apitest_simpledata"));
142
143    enquire.set_query(Xapian::Query());
144    Xapian::MSet mymset = enquire.get_mset(0, 10);
145    TEST_MSET_SIZE(mymset, 0);
146    TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
147    TEST_EQUAL(mymset.get_matches_upper_bound(), 0);
148    TEST_EQUAL(mymset.get_matches_estimated(), 0);
149
150    vector<Xapian::Query> v;
151    enquire.set_query(Xapian::Query(Xapian::Query::OP_AND, v.begin(), v.end()));
152    mymset = enquire.get_mset(0, 10);
153    TEST_MSET_SIZE(mymset, 0);
154    TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
155    TEST_EQUAL(mymset.get_matches_upper_bound(), 0);
156    TEST_EQUAL(mymset.get_matches_estimated(), 0);
157
158    return true;
159}
160
161// tests the document count for a simple query
162DEFINE_TESTCASE(simplequery1, backend) {
163    Xapian::Enquire enquire(get_database("apitest_simpledata"));
164    enquire.set_query(Xapian::Query("word"));
165    Xapian::MSet mymset = enquire.get_mset(0, 10);
166    TEST_MSET_SIZE(mymset, 2);
167    return true;
168}
169
170// tests for the right documents and weights returned with simple query
171DEFINE_TESTCASE(simplequery2, backend) {
172    // open the database (in this case a simple text file
173    // we prepared earlier)
174    Xapian::Database db = get_database("apitest_simpledata");
175    Xapian::Enquire enquire(db);
176    enquire.set_query(Xapian::Query("word"));
177
178    // retrieve the top results
179    Xapian::MSet mymset = enquire.get_mset(0, 10);
180
181    // We've done the query, now check that the result is what
182    // we expect (documents 2 and 4)
183    mset_expect_order(mymset, 2, 4);
184
185    // Check the weights
186    Xapian::MSetIterator i = mymset.begin();
187    // These weights are for BM25Weight(1,0,1,0.5,0.5)
188    TEST_EQUAL_DOUBLE(i.get_weight(), 1.04648168717725);
189    i++;
190    TEST_EQUAL_DOUBLE(i.get_weight(), 0.640987686595914);
191
192    return true;
193}
194
195// tests for the right document count for another simple query
196DEFINE_TESTCASE(simplequery3, backend) {
197    Xapian::Enquire enquire(get_database("apitest_simpledata"));
198    enquire.set_query(query("this"));
199    Xapian::MSet mymset = enquire.get_mset(0, 10);
200
201    // Check that 6 documents were returned.
202    TEST_MSET_SIZE(mymset, 6);
203
204    return true;
205}
206
207// tests for the right document count for a wildcard query
208// FIXME: move this to querytest (and just use an InMemory DB).
209DEFINE_TESTCASE(wildquery1, backend) {
210    Xapian::QueryParser queryparser;
211    unsigned flags = Xapian::QueryParser::FLAG_WILDCARD |
212                     Xapian::QueryParser::FLAG_LOVEHATE;
213    queryparser.set_stemmer(Xapian::Stem("english"));
214    queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_ALL);
215    Xapian::Database db = get_database("apitest_simpledata");
216    queryparser.set_database(db);
217    Xapian::Enquire enquire(db);
218
219    Xapian::Query qobj = queryparser.parse_query("th*", flags);
220    tout << qobj.get_description() << endl;
221    enquire.set_query(qobj);
222    Xapian::MSet mymset = enquire.get_mset(0, 10);
223    // Check that 6 documents were returned.
224    TEST_MSET_SIZE(mymset, 6);
225
226    qobj = queryparser.parse_query("notindb* \"this\"", flags);
227    tout << qobj.get_description() << endl;
228    enquire.set_query(qobj);
229    mymset = enquire.get_mset(0, 10);
230    // Check that 6 documents were returned.
231    TEST_MSET_SIZE(mymset, 6);
232
233    qobj = queryparser.parse_query("+notindb* \"this\"", flags);
234    tout << qobj.get_description() << endl;
235    enquire.set_query(qobj);
236    mymset = enquire.get_mset(0, 10);
237    // Check that 0 documents were returned.
238    TEST_MSET_SIZE(mymset, 0);
239
240    return true;
241}
242
243// tests a query across multiple databases
244DEFINE_TESTCASE(multidb1, backend) {
245    Xapian::Database mydb1(get_database("apitest_simpledata", "apitest_simpledata2"));
246    Xapian::Enquire enquire1(mydb1);
247
248    Xapian::Database mydb2(get_database("apitest_simpledata"));
249    mydb2.add_database(get_database("apitest_simpledata2"));
250    Xapian::Enquire enquire2(mydb2);
251
252    // make a simple query, with one word in it - "word".
253    Xapian::Query myquery("word");
254    enquire1.set_query(myquery);
255    enquire2.set_query(myquery);
256
257    // retrieve the top ten results from each method of accessing
258    // multiple text files
259    Xapian::MSet mymset1 = enquire1.get_mset(0, 10);
260    Xapian::MSet mymset2 = enquire2.get_mset(0, 10);
261
262    TEST_EQUAL(mymset1.size(), mymset2.size());
263    TEST(mset_range_is_same_weights(mymset1, 0, mymset2, 0, mymset1.size()));
264    return true;
265}
266
267// tests a query across multiple databases with terms only
268// in one of the two databases
269DEFINE_TESTCASE(multidb2, backend && !multi) {
270    Xapian::Database mydb1(get_database("apitest_simpledata",
271                                  "apitest_simpledata2"));
272    Xapian::Enquire enquire1(mydb1);
273
274    Xapian::Database mydb2(get_database("apitest_simpledata"));
275    mydb2.add_database(get_database("apitest_simpledata2"));
276    Xapian::Enquire enquire2(mydb2);
277
278    // make a simple query
279    Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
280    enquire1.set_query(myquery);
281    enquire2.set_query(myquery);
282
283    // retrieve the top ten results from each method of accessing
284    // multiple text files
285    Xapian::MSet mymset1 = enquire1.get_mset(0, 10);
286    Xapian::MSet mymset2 = enquire2.get_mset(0, 10);
287
288    TEST_EQUAL(mymset1.size(), mymset2.size());
289    TEST(mset_range_is_same_weights(mymset1, 0, mymset2, 0, mymset1.size()));
290    return true;
291}
292
293// test that a multidb with 2 dbs query returns correct docids
294DEFINE_TESTCASE(multidb3, backend && !multi) {
295    Xapian::Database mydb2(get_database("apitest_simpledata"));
296    mydb2.add_database(get_database("apitest_simpledata2"));
297    Xapian::Enquire enquire(mydb2);
298
299    // make a query
300    Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
301    enquire.set_weighting_scheme(Xapian::BoolWeight());
302    enquire.set_query(myquery);
303
304    // retrieve the top ten results
305    Xapian::MSet mymset = enquire.get_mset(0, 10);
306    mset_expect_order(mymset, 2, 3, 7);
307
308    return true;
309}
310
311// test that a multidb with 3 dbs query returns correct docids
312DEFINE_TESTCASE(multidb4, backend && !multi) {
313    Xapian::Database mydb2(get_database("apitest_simpledata"));
314    mydb2.add_database(get_database("apitest_simpledata2"));
315    mydb2.add_database(get_database("apitest_termorder"));
316    Xapian::Enquire enquire(mydb2);
317
318    // make a query
319    Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
320    enquire.set_weighting_scheme(Xapian::BoolWeight());
321    enquire.set_query(myquery);
322
323    // retrieve the top ten results
324    Xapian::MSet mymset = enquire.get_mset(0, 10);
325    mset_expect_order(mymset, 2, 3, 4, 10);
326
327    return true;
328}
329
330// tests MultiPostList::skip_to().
331DEFINE_TESTCASE(multidb5, backend && !multi) {
332    Xapian::Database mydb2(get_database("apitest_simpledata"));
333    mydb2.add_database(get_database("apitest_simpledata2"));
334    Xapian::Enquire enquire(mydb2);
335
336    // make a query
337    Xapian::Query myquery = query(Xapian::Query::OP_AND, "inmemory", "word");
338    enquire.set_weighting_scheme(Xapian::BoolWeight());
339    enquire.set_query(myquery);
340
341    // retrieve the top ten results
342    Xapian::MSet mymset = enquire.get_mset(0, 10);
343    mset_expect_order(mymset, 2);
344
345    return true;
346}
347
348// tests that when specifying maxitems to get_mset, no more than
349// that are returned.
350DEFINE_TESTCASE(msetmaxitems1, backend) {
351    Xapian::Enquire enquire(get_database("apitest_simpledata"));
352    enquire.set_query(query("this"));
353    Xapian::MSet mymset = enquire.get_mset(0, 1);
354    TEST_MSET_SIZE(mymset, 1);
355
356    mymset = enquire.get_mset(0, 5);
357    TEST_MSET_SIZE(mymset, 5);
358
359    return true;
360}
361
362// tests the returned weights are as expected (regression test for remote
363// backend which was using the average weight rather than the actual document
364// weight for computing weights - fixed in 1.0.0).
365DEFINE_TESTCASE(expandweights1, backend) {
366    Xapian::Enquire enquire(get_database("apitest_simpledata"));
367    enquire.set_query(Xapian::Query("this"));
368
369    Xapian::MSet mymset = enquire.get_mset(0, 10);
370
371    Xapian::RSet myrset;
372    Xapian::MSetIterator i = mymset.begin();
373    myrset.add_document(*i);
374    myrset.add_document(*(++i));
375
376    Xapian::ESet eset = enquire.get_eset(3, myrset, enquire.USE_EXACT_TERMFREQ);
377    TEST_EQUAL(eset.size(), 3);
378    TEST_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
379    TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
380    TEST_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
381
382    return true;
383}
384
385// Just like test_expandweights1 but without USE_EXACT_TERMFREQ.
386DEFINE_TESTCASE(expandweights2, backend) {
387    Xapian::Enquire enquire(get_database("apitest_simpledata"));
388    enquire.set_query(Xapian::Query("this"));
389
390    Xapian::MSet mymset = enquire.get_mset(0, 10);
391
392    Xapian::RSet myrset;
393    Xapian::MSetIterator i = mymset.begin();
394    myrset.add_document(*i);
395    myrset.add_document(*(++i));
396
397    Xapian::ESet eset = enquire.get_eset(3, myrset);
398    TEST_EQUAL(eset.size(), 3);
399    if (strcmp(get_dbtype(), "multi") != 0) {
400        // For a single database, the weights should be the same with or
401        // without USE_EXACT_TERMFREQ.
402        TEST_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
403        TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
404        TEST_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
405    } else {
406        // For multiple databases, we expect that using USE_EXACT_TERMFREQ
407        // will result in different weights in some cases.
408        TEST_NOT_EQUAL_DOUBLE(eset[0].get_weight(), 6.08904001099445);
409        TEST_EQUAL_DOUBLE(eset[1].get_weight(), 6.08904001099445);
410        TEST_NOT_EQUAL_DOUBLE(eset[2].get_weight(), 4.73383620844021);
411    }
412
413    return true;
414}
415
416// tests that when specifying maxitems to get_eset, no more than
417// that are returned.
418DEFINE_TESTCASE(expandmaxitems1, backend) {
419    Xapian::Enquire enquire(get_database("apitest_simpledata"));
420    enquire.set_query(Xapian::Query("this"));
421
422    Xapian::MSet mymset = enquire.get_mset(0, 10);
423    tout << "mymset.size() = " << mymset.size() << endl;
424    TEST(mymset.size() >= 2);
425
426    Xapian::RSet myrset;
427    Xapian::MSetIterator i = mymset.begin();
428    myrset.add_document(*i);
429    myrset.add_document(*(++i));
430
431    Xapian::ESet myeset = enquire.get_eset(1, myrset);
432    TEST_EQUAL(myeset.size(), 1);
433
434    return true;
435}
436
437// tests that a pure boolean query has all weights set to 0
438DEFINE_TESTCASE(boolquery1, backend) {
439    Xapian::Query myboolquery(query("this"));
440
441    // open the database (in this case a simple text file
442    // we prepared earlier)
443    Xapian::Enquire enquire(get_database("apitest_simpledata"));
444    enquire.set_query(myboolquery);
445    enquire.set_weighting_scheme(Xapian::BoolWeight());
446
447    // retrieve the top results
448    Xapian::MSet mymset = enquire.get_mset(0, 10);
449
450    TEST_NOT_EQUAL(mymset.size(), 0);
451    TEST_EQUAL(mymset.get_max_possible(), 0);
452    for (Xapian::MSetIterator i = mymset.begin(); i != mymset.end(); ++i) {
453        TEST_EQUAL(i.get_weight(), 0);
454    }
455    return true;
456}
457
458// tests that get_mset() specifying "this" works as expected
459DEFINE_TESTCASE(msetfirst1, backend) {
460    Xapian::Enquire enquire(get_database("apitest_simpledata"));
461    enquire.set_query(query("this"));
462    Xapian::MSet mymset1 = enquire.get_mset(0, 6);
463    Xapian::MSet mymset2 = enquire.get_mset(3, 3);
464    TEST(mset_range_is_same(mymset1, 3, mymset2, 0, 3));
465
466    // Regression test - we weren't adjusting the index into items[] by
467    // firstitem in api/omenquire.cc.
468    TEST_EQUAL(mymset1[5].get_document().get_data(),
469               mymset2[2].get_document().get_data());
470    return true;
471}
472
473// tests the converting-to-percent functions
474DEFINE_TESTCASE(topercent1, backend) {
475    Xapian::Enquire enquire(get_database("apitest_simpledata"));
476    enquire.set_query(query("this"));
477    Xapian::MSet mymset = enquire.get_mset(0, 20);
478
479    int last_pct = 100;
480    Xapian::MSetIterator i = mymset.begin();
481    for ( ; i != mymset.end(); ++i) {
482        int pct = mymset.convert_to_percent(i);
483        TEST_AND_EXPLAIN(pct == i.get_percent(),
484                         "convert_to_%(msetitor) != convert_to_%(wt)");
485        TEST_AND_EXPLAIN(pct == mymset.convert_to_percent(i.get_weight()),
486                         "convert_to_%(msetitor) != convert_to_%(wt)");
487        TEST_AND_EXPLAIN(pct >= 0 && pct <= 100,
488                         "percentage out of range: " << pct);
489        TEST_AND_EXPLAIN(pct <= last_pct, "percentage increased down mset");
490        last_pct = pct;
491    }
492    return true;
493}
494
495// tests the percentage values returned
496DEFINE_TESTCASE(topercent2, backend) {
497    BackendManagerLocal local_manager;
498    local_manager.set_datadir(test_driver::get_srcdir() + "/testdata/");
499    Xapian::Enquire localenq(local_manager.get_database("apitest_simpledata"));
500    Xapian::Enquire enquire(get_database("apitest_simpledata"));
501
502    int pct;
503
504    // First, test a search in which the top document scores 100%.
505    enquire.set_query(query("this"));
506    localenq.set_query(query("this"));
507    Xapian::MSet mymset = enquire.get_mset(0, 20);
508    Xapian::MSet localmset = localenq.get_mset(0, 20);
509
510    Xapian::MSetIterator i = mymset.begin();
511    TEST(i != mymset.end());
512    pct = mymset.convert_to_percent(i);
513    TEST_EQUAL(pct, 100);
514
515    TEST_EQUAL(mymset, localmset);
516    TEST(mset_range_is_same_percents(mymset, 0, localmset, 0, mymset.size()));
517
518    // A search in which the top document doesn't have 100%
519    Xapian::Query q = query(Xapian::Query::OP_OR,
520                            "this", "line", "paragraph", "rubbish");
521    enquire.set_query(q);
522    localenq.set_query(q);
523    mymset = enquire.get_mset(0, 20);
524    localmset = localenq.get_mset(0, 20);
525
526    i = mymset.begin();
527    TEST(i != mymset.end());
528    pct = mymset.convert_to_percent(i);
529    TEST_GREATER(pct, 65);
530    TEST_LESSER(pct, 75);
531
532    ++i;
533
534    TEST(i != mymset.end());
535    pct = mymset.convert_to_percent(i);
536    TEST_GREATER(pct, 40);
537    TEST_LESSER(pct, 50);
538
539    TEST_EQUAL(mymset, localmset);
540    TEST(mset_range_is_same_percents(mymset, 0, localmset, 0, mymset.size()));
541
542    return true;
543}
544
545class myExpandFunctor : public Xapian::ExpandDecider {
546    public:
547        bool operator()(const string & tname) const {
548            unsigned long sum = 0;
549            for (string::const_iterator i=tname.begin(); i!=tname.end(); ++i) {
550                sum += *i;
551            }
552//          if (verbose) {
553//              tout << tname << "==> " << sum << "\n";
554//          }
555            return (sum % 2) == 0;
556        }
557};
558
559// tests the expand decision functor
560DEFINE_TESTCASE(expandfunctor1, backend) {
561    Xapian::Enquire enquire(get_database("apitest_simpledata"));
562    enquire.set_query(Xapian::Query("this"));
563
564    Xapian::MSet mymset = enquire.get_mset(0, 10);
565    TEST(mymset.size() >= 2);
566
567    Xapian::RSet myrset;
568    Xapian::MSetIterator i = mymset.begin();
569    myrset.add_document(*i);
570    myrset.add_document(*(++i));
571
572    myExpandFunctor myfunctor;
573
574    Xapian::ESet myeset_orig = enquire.get_eset(1000, myrset);
575    unsigned int neweset_size = 0;
576    Xapian::ESetIterator j = myeset_orig.begin();
577    for ( ; j != myeset_orig.end(); ++j) {
578        if (myfunctor(*j)) neweset_size++;
579    }
580    Xapian::ESet myeset = enquire.get_eset(neweset_size, myrset, &myfunctor);
581
582#if 0
583    // Compare myeset with the hand-filtered version of myeset_orig.
584    if (verbose) {
585        tout << "orig_eset: ";
586        copy(myeset_orig.begin(), myeset_orig.end(),
587             ostream_iterator<Xapian::ESetItem>(tout, " "));
588        tout << "\n";
589
590        tout << "new_eset: ";
591        copy(myeset.begin(), myeset.end(),
592             ostream_iterator<Xapian::ESetItem>(tout, " "));
593        tout << "\n";
594    }
595#endif
596    Xapian::ESetIterator orig = myeset_orig.begin();
597    Xapian::ESetIterator filt = myeset.begin();
598    for (; orig != myeset_orig.end() && filt != myeset.end(); ++orig, ++filt) {
599        // skip over items that shouldn't be in myeset
600        while (orig != myeset_orig.end() && !myfunctor(*orig)) {
601            ++orig;
602        }
603
604        TEST_AND_EXPLAIN(*orig == *filt &&
605                         orig.get_weight() == filt.get_weight(),
606                         "Mismatch in items " << *orig << " vs. " << *filt
607                         << " after filtering");
608    }
609
610    while (orig != myeset_orig.end() && !myfunctor(*orig)) {
611        ++orig;
612    }
613
614    TEST_EQUAL(orig, myeset_orig.end());
615    TEST_AND_EXPLAIN(filt == myeset.end(),
616                     "Extra items in the filtered eset.");
617    return true;
618}
619
620// tests the percent cutoff option
621DEFINE_TESTCASE(pctcutoff1, backend) {
622    Xapian::Enquire enquire(get_database("apitest_simpledata"));
623    enquire.set_query(query(Xapian::Query::OP_OR,
624                            "this", "line", "paragraph", "rubbish"));
625    Xapian::MSet mymset1 = enquire.get_mset(0, 100);
626
627    if (verbose) {
628        tout << "Original mset pcts:";
629        print_mset_percentages(mymset1);
630        tout << "\n";
631    }
632
633    unsigned int num_items = 0;
634    int my_pct = 100;
635    int changes = 0;
636    Xapian::MSetIterator i = mymset1.begin();
637    int c = 0;
638    for ( ; i != mymset1.end(); ++i, ++c) {
639        int new_pct = mymset1.convert_to_percent(i);
640        if (new_pct != my_pct) {
641            changes++;
642            if (changes > 3) break;
643            num_items = c;
644            my_pct = new_pct;
645        }
646    }
647
648    TEST_AND_EXPLAIN(changes > 3, "MSet not varied enough to test");
649    if (verbose) {
650        tout << "Cutoff percent: " << my_pct << "\n";
651    }
652
653    enquire.set_cutoff(my_pct);
654    Xapian::MSet mymset2 = enquire.get_mset(0, 100);
655
656    if (verbose) {
657        tout << "Percentages after cutoff:";
658        print_mset_percentages(mymset2);
659        tout << "\n";
660    }
661
662    TEST_AND_EXPLAIN(mymset2.size() >= num_items,
663                     "Match with % cutoff lost too many items");
664
665    TEST_AND_EXPLAIN(mymset2.size() == num_items ||
666                     (mymset2.convert_to_percent(mymset2[num_items]) == my_pct &&
667                      mymset2.convert_to_percent(mymset2.back()) == my_pct),
668                     "Match with % cutoff returned too many items");
669
670    return true;
671}
672
673// Tests the percent cutoff option combined with collapsing
674DEFINE_TESTCASE(pctcutoff2, backend) {
675    Xapian::Enquire enquire(get_database("apitest_simpledata"));
676    enquire.set_query(Xapian::Query("this"));
677    enquire.set_query(Xapian::Query(Xapian::Query::OP_AND_NOT, Xapian::Query("this"), Xapian::Query("banana")));
678    Xapian::MSet mset = enquire.get_mset(0, 100);
679
680    if (verbose) {
681        tout << "Original mset pcts:";
682        print_mset_percentages(mset);
683        tout << "\n";
684    }
685
686    TEST(mset.size() >= 2);
687    TEST(mset[0].get_percent() - mset[1].get_percent() >= 2);
688
689    Xapian::percent cutoff = mset[0].get_percent() + mset[1].get_percent();
690    cutoff /= 2;
691
692    enquire.set_cutoff(cutoff);
693    enquire.set_collapse_key(1234); // Value which is always empty.
694
695    mset = enquire.get_mset(0, 1);
696    TEST_EQUAL(mset.size(), 1);
697    TEST_EQUAL(mset.get_matches_lower_bound(), 1);
698
699    return true;
700}
701
702// tests the cutoff option
703DEFINE_TESTCASE(cutoff1, backend) {
704    Xapian::Enquire enquire(get_database("apitest_simpledata"));
705    enquire.set_query(query(Xapian::Query::OP_OR,
706                            "this", "line", "paragraph", "rubbish"));
707    Xapian::MSet mymset1 = enquire.get_mset(0, 100);
708
709    if (verbose) {
710        tout << "Original mset weights:";
711        print_mset_weights(mymset1);
712        tout << "\n";
713    }
714
715    unsigned int num_items = 0;
716    Xapian::weight my_wt = -100;
717    int changes = 0;
718    Xapian::MSetIterator i = mymset1.begin();
719    int c = 0;
720    for ( ; i != mymset1.end(); ++i, ++c) {
721        Xapian::weight new_wt = i.get_weight();
722        if (new_wt != my_wt) {
723            changes++;
724            if (changes > 3) break;
725            num_items = c;
726            my_wt = new_wt;
727        }
728    }
729
730    TEST_AND_EXPLAIN(changes > 3, "MSet not varied enough to test");
731    if (verbose) {
732        tout << "Cutoff weight: " << my_wt << "\n";
733    }
734
735    enquire.set_cutoff(0, my_wt);
736    Xapian::MSet mymset2 = enquire.get_mset(0, 100);
737
738    if (verbose) {
739        tout << "Weights after cutoff:";
740        print_mset_weights(mymset2);
741        tout << "\n";
742    }
743
744    TEST_AND_EXPLAIN(mymset2.size() >= num_items,
745                     "Match with cutoff lost too many items");
746
747    TEST_AND_EXPLAIN(mymset2.size() == num_items ||
748                     (mymset2[num_items].get_weight() == my_wt &&
749                      mymset2.back().get_weight() == my_wt),
750                     "Match with cutoff returned too many items");
751
752    return true;
753}
754
755// tests the allow query terms expand option
756DEFINE_TESTCASE(allowqterms1, backend) {
757    Xapian::Enquire enquire(get_database("apitest_simpledata"));
758    enquire.set_query(Xapian::Query("this"));
759
760    Xapian::MSet mymset = enquire.get_mset(0, 10);
761    TEST(mymset.size() >= 2);
762
763    Xapian::RSet myrset;
764    Xapian::MSetIterator i = mymset.begin();
765    myrset.add_document(*i);
766    myrset.add_document(*(++i));
767
768    Xapian::ESet myeset = enquire.get_eset(1000, myrset);
769    Xapian::ESetIterator j = myeset.begin();
770    for ( ; j != myeset.end(); ++j) {
771        TEST_NOT_EQUAL(*j, "this");
772    }
773
774    Xapian::ESet myeset2 = enquire.get_eset(1000, myrset, Xapian::Enquire::INCLUDE_QUERY_TERMS);
775    j = myeset2.begin();
776    for ( ; j != myeset2.end(); ++j) {
777        if (*j == "this") break;
778    }
779    TEST(j != myeset2.end());
780    return true;
781}
782
783// tests that the MSet max_attained works
784DEFINE_TESTCASE(maxattain1, backend) {
785    Xapian::Enquire enquire(get_database("apitest_simpledata"));
786    enquire.set_query(query("this"));
787    Xapian::MSet mymset = enquire.get_mset(0, 100);
788
789    Xapian::weight mymax = 0;
790    Xapian::MSetIterator i = mymset.begin();
791    for ( ; i != mymset.end(); ++i) {
792        if (i.get_weight() > mymax) mymax = i.get_weight();
793    }
794    TEST_EQUAL(mymax, mymset.get_max_attained());
795
796    return true;
797}
798
799// tests a reversed boolean query
800DEFINE_TESTCASE(reversebool1, backend) {
801    Xapian::Enquire enquire(get_database("apitest_simpledata"));
802    enquire.set_query(Xapian::Query("this"));
803    enquire.set_weighting_scheme(Xapian::BoolWeight());
804
805    Xapian::MSet mymset1 = enquire.get_mset(0, 100);
806    TEST_AND_EXPLAIN(mymset1.size() > 1,
807                     "Mset was too small to test properly");
808
809    enquire.set_docid_order(Xapian::Enquire::ASCENDING);
810    Xapian::MSet mymset2 = enquire.get_mset(0, 100);
811    enquire.set_docid_order(Xapian::Enquire::DESCENDING);
812    Xapian::MSet mymset3 = enquire.get_mset(0, 100);
813
814    // mymset1 and mymset2 should be identical
815    TEST_EQUAL(mymset1.size(), mymset2.size());
816
817    {
818        Xapian::MSetIterator i = mymset1.begin();
819        Xapian::MSetIterator j = mymset2.begin();
820        for ( ; i != mymset1.end(), j != mymset2.end(); ++i, j++) {
821            // if this fails, then setting match_sort_forward=true was not
822            // the same as the default.
823            TEST_EQUAL(*i, *j);
824        }
825    }
826
827    // mymset1 and mymset3 should be same but reversed
828    TEST_EQUAL(mymset1.size(), mymset3.size());
829
830    {
831        Xapian::MSetIterator i = mymset1.begin();
832        vector<Xapian::docid> rev(mymset3.begin(), mymset3.end());
833        // Next iterator not const because of compiler brokenness (egcs 1.1.2)
834        vector<Xapian::docid>::reverse_iterator j = rev.rbegin();
835        for ( ; i != mymset1.end(); ++i, j++) {
836            // if this fails, then setting match_sort_forward=false didn't
837            // reverse the results.
838            TEST_EQUAL(*i, *j);
839        }
840    }
841
842    return true;
843}
844
845// tests a reversed boolean query, where the full mset isn't returned
846DEFINE_TESTCASE(reversebool2, backend) {
847    Xapian::Enquire enquire(get_database("apitest_simpledata"));
848    enquire.set_query(Xapian::Query("this"));
849    enquire.set_weighting_scheme(Xapian::BoolWeight());
850
851    Xapian::MSet mymset1 = enquire.get_mset(0, 100);
852
853    TEST_AND_EXPLAIN(mymset1.size() > 1,
854                     "Mset was too small to test properly");
855
856    enquire.set_docid_order(Xapian::Enquire::ASCENDING);
857    Xapian::doccount msize = mymset1.size() / 2;
858    Xapian::MSet mymset2 = enquire.get_mset(0, msize);
859    enquire.set_docid_order(Xapian::Enquire::DESCENDING);
860    Xapian::MSet mymset3 = enquire.get_mset(0, msize);
861
862    // mymset2 should be first msize items of mymset1
863    TEST_EQUAL(msize, mymset2.size());
864    {
865        Xapian::MSetIterator i = mymset1.begin();
866        Xapian::MSetIterator j = mymset2.begin();
867        for ( ; i != mymset1.end(), j != mymset2.end(); ++i, j++) {
868            // if this fails, then setting match_sort_forward=true was not
869            // the same as the default.
870            TEST_EQUAL(*i, *j);
871        }
872    }
873
874    // mymset3 should be last msize items of mymset1, in reverse order
875    TEST_EQUAL(msize, mymset3.size());
876    {
877        vector<Xapian::docid> rev(mymset1.begin(), mymset1.end());
878        // Next iterator not const because of compiler brokenness (egcs 1.1.2)
879        vector<Xapian::docid>::reverse_iterator i = rev.rbegin();
880        Xapian::MSetIterator j = mymset3.begin();
881        for ( ; j != mymset3.end(); ++i, j++) {
882            // if this fails, then setting match_sort_forward=false didn't
883            // reverse the results.
884            TEST_EQUAL(*i, *j);
885        }
886    }
887
888    return true;
889}
890
891// tests that get_matching_terms() returns the terms in the right order
892DEFINE_TESTCASE(getmterms1, backend) {
893    list<string> answers_list;
894    answers_list.push_back("one");
895    answers_list.push_back("two");
896    answers_list.push_back("three");
897    answers_list.push_back("four");
898
899    Xapian::Database mydb(get_database("apitest_termorder"));
900    Xapian::Enquire enquire(mydb);
901
902    Xapian::Query myquery(Xapian::Query::OP_OR,
903            Xapian::Query(Xapian::Query::OP_AND,
904                    Xapian::Query("one", 1, 1),
905                    Xapian::Query("three", 1, 3)),
906            Xapian::Query(Xapian::Query::OP_OR,
907                    Xapian::Query("four", 1, 4),
908                    Xapian::Query("two", 1, 2)));
909
910    enquire.set_query(myquery);
911
912    Xapian::MSet mymset = enquire.get_mset(0, 10);
913
914    TEST_MSET_SIZE(mymset, 1);
915    list<string> list(enquire.get_matching_terms_begin(mymset.begin()),
916                          enquire.get_matching_terms_end(mymset.begin()));
917    TEST(list == answers_list);
918
919    return true;
920}
921
922// tests that get_matchi