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

Revision 11153, 55.6 kB (checked in by olly, 4 months ago)

Backport change from trunk:
tests/api_db.cc: Use TEST_EQUAL(a, b) rather than TEST(a == b).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/* api_db.cc: tests which need a backend
2 *
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2002,2003,2004,2005,2006,2007 Olly Betts
6 * Copyright 2006,2007 Lemur Consulting Ltd
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
21 * USA
22 */
23
24#include <config.h>
25
26#include "api_db.h"
27
28#include <algorithm>
29#include <fstream>
30#include <iomanip>
31#include <map>
32#include <string>
33#include <vector>
34
35// We have to use the deprecated Quartz::open() method.
36#define XAPIAN_DEPRECATED(D) D
37#include <xapian.h>
38
39#include "backendmanager.h"
40#include "backendmanager_local.h"
41#include "testsuite.h"
42#include "testutils.h"
43#include "unixcmds.h"
44#include "utils.h"
45
46#include "apitest.h"
47
48#include <list>
49
50using namespace std;
51
52static Xapian::Query
53query(const string &t)
54{
55    return Xapian::Query(Xapian::Stem("english")(t));
56}
57
58// #######################################################################
59// # Tests start here
60
61// tests Xapian::Database::get_termfreq() and Xapian::Database::term_exists()
62DEFINE_TESTCASE(termstats, backend) {
63    Xapian::Database db(get_database("apitest_simpledata"));
64
65    TEST(!db.term_exists("corn"));
66    TEST_EQUAL(db.get_termfreq("corn"), 0);
67    TEST(db.term_exists("banana"));
68    TEST_EQUAL(db.get_termfreq("banana"), 1);
69    TEST(db.term_exists("paragraph"));
70    TEST_EQUAL(db.get_termfreq("paragraph"), 5);
71
72    return true;
73}
74
75// check that stubdbs work
76DEFINE_TESTCASE(stubdb1, flint) {
77    {
78        // Create the database needed; this is why we require the flint backend.
79        (void) get_database("apitest_simpledata");
80    }
81    ofstream out("stubdb1");
82    TEST(out.is_open());
83    // FIXME: not very reliable...
84    out << "remote :" << BackendManager::get_xapian_progsrv_command()
85        << " .flint/db=apitest_simpledata\n";
86    out.close();
87
88    {
89        Xapian::Database db = Xapian::Auto::open_stub("stubdb1");
90        Xapian::Enquire enquire(db);
91        enquire.set_query(Xapian::Query("word"));
92        enquire.get_mset(0, 10);
93    }
94    {
95        Xapian::Database db("stubdb1");
96        Xapian::Enquire enquire(db);
97        enquire.set_query(Xapian::Query("word"));
98        enquire.get_mset(0, 10);
99    }
100
101    unlink("stubdb1");
102
103    return true;
104}
105
106#if 0 // the "force error" mechanism is no longer in place...
107class MyErrorHandler : public Xapian::ErrorHandler {
108    public:
109        int count;
110
111        bool handle_error(Xapian::Error & error) {
112            ++count;
113            tout << "Error handling caught: " << error.get_description()
114                 << ", count is now " << count << "\n";
115            return true;
116        }
117
118        MyErrorHandler() : count (0) {}
119};
120
121// tests error handler in multimatch().
122//DEFINE_TESTCASE(multierrhandler1, backend) {
123    MyErrorHandler myhandler;
124
125    Xapian::Database mydb2(get_database("apitest_simpledata"));
126    Xapian::Database mydb3(get_database("apitest_simpledata2"));
127    int errcount = 1;
128    for (int testcount = 0; testcount < 14; testcount ++) {
129        tout << "testcount=" << testcount << "\n";
130        Xapian::Database mydb4(get_database("-e", "apitest_termorder"));
131        Xapian::Database mydb5(get_network_database("apitest_termorder", 1));
132        Xapian::Database mydb6(get_database("-e2", "apitest_termorder"));
133        Xapian::Database mydb7(get_database("-e3", "apitest_simpledata"));
134
135        Xapian::Database dbs;
136        switch (testcount) {
137            case 0:
138                dbs.add_database(mydb2);
139                dbs.add_database(mydb3);
140                dbs.add_database(mydb4);
141                break;
142            case 1:
143                dbs.add_database(mydb4);
144                dbs.add_database(mydb2);
145                dbs.add_database(mydb3);
146                break;
147            case 2:
148                dbs.add_database(mydb3);
149                dbs.add_database(mydb4);
150                dbs.add_database(mydb2);
151                break;
152            case 3:
153                dbs.add_database(mydb2);
154                dbs.add_database(mydb3);
155                dbs.add_database(mydb5);
156                sleep(1);
157                break;
158            case 4:
159                dbs.add_database(mydb5);
160                dbs.add_database(mydb2);
161                dbs.add_database(mydb3);
162                sleep(1);
163                break;
164            case 5:
165                dbs.add_database(mydb3);
166                dbs.add_database(mydb5);
167                dbs.add_database(mydb2);
168                sleep(1);
169                break;
170            case 6:
171                dbs.add_database(mydb2);
172                dbs.add_database(mydb3);
173                dbs.add_database(mydb6);
174                break;
175            case 7:
176                dbs.add_database(mydb6);
177                dbs.add_database(mydb2);
178                dbs.add_database(mydb3);
179                break;
180            case 8:
181                dbs.add_database(mydb3);
182                dbs.add_database(mydb6);
183                dbs.add_database(mydb2);
184                break;
185            case 9:
186                dbs.add_database(mydb2);
187                dbs.add_database(mydb3);
188                dbs.add_database(mydb7);
189                break;
190            case 10:
191                dbs.add_database(mydb7);
192                dbs.add_database(mydb2);
193                dbs.add_database(mydb3);
194                break;
195            case 11:
196                dbs.add_database(mydb3);
197                dbs.add_database(mydb7);
198                dbs.add_database(mydb2);
199                break;
200            case 12:
201                dbs.add_database(mydb2);
202                dbs.add_database(mydb6);
203                dbs.add_database(mydb7);
204                break;
205            case 13:
206                dbs.add_database(mydb2);
207                dbs.add_database(mydb7);
208                dbs.add_database(mydb6);
209                break;
210        }
211        tout << "db=" << dbs << "\n";
212        Xapian::Enquire enquire(dbs, &myhandler);
213
214        // make a query
215        Xapian::Query myquery = query(Xapian::Query::OP_OR, "inmemory", "word");
216        enquire.set_weighting_scheme(Xapian::BoolWeight());
217        enquire.set_query(myquery);
218
219        tout << "query=" << myquery << "\n";
220        // retrieve the top ten results
221        Xapian::MSet mymset = enquire.get_mset(0, 10);
222
223        switch (testcount) {
224            case 0: case 3: case 6: case 9:
225                mset_expect_order(mymset, 2, 4, 10);
226                break;
227            case 1: case 4: case 7: case 10:
228                mset_expect_order(mymset, 3, 5, 11);
229                break;
230            case 2: case 5: case 8: case 11:
231                mset_expect_order(mymset, 1, 6, 12);
232                break;
233            case 12:
234            case 13:
235                mset_expect_order(mymset, 4, 10);
236                errcount += 1;
237                break;
238        }
239        TEST_EQUAL(myhandler.count, errcount);
240        errcount += 1;
241    }
242
243    return true;
244}
245#endif
246
247class myMatchDecider : public Xapian::MatchDecider {
248    public:
249        bool operator()(const Xapian::Document &doc) const {
250            // Note that this is not recommended usage of get_data()
251            return doc.get_data().find("This is") != string::npos;
252        }
253};
254
255// Test Xapian::MatchDecider functor.
256DEFINE_TESTCASE(matchfunctor1, backend && !remote) {
257    Xapian::Database db(get_database("apitest_simpledata"));
258    Xapian::Enquire enquire(db);
259    enquire.set_query(Xapian::Query("this"));
260
261    myMatchDecider myfunctor;
262
263    Xapian::MSet mymset = enquire.get_mset(0, 100, 0, &myfunctor);
264
265    vector<bool> docid_checked(db.get_lastdocid());
266
267    // Check that we get the expected number of matches, and that they
268    // satisfy the condition.
269    Xapian::MSetIterator i = mymset.begin();
270    TEST(i != mymset.end());
271    TEST_EQUAL(mymset.size(), 3);
272    TEST_EQUAL(mymset.get_matches_lower_bound(), 3);
273    TEST_EQUAL(mymset.get_matches_upper_bound(), 3);
274    TEST_EQUAL(mymset.get_matches_estimated(), 3);
275    for ( ; i != mymset.end(); ++i) {
276        const Xapian::Document doc(i.get_document());
277        TEST(myfunctor(doc));
278        docid_checked[*i] = true;
279    }
280
281    // Check that there are some documents which aren't accepted by the match
282    // decider.
283    mymset = enquire.get_mset(0, 100);
284    TEST(mymset.size() > 3);
285
286    // Check that the bounds are appropriate even if we don't ask for any
287    // actual matches.
288    mymset = enquire.get_mset(0, 0, 0, &myfunctor);
289    TEST_EQUAL(mymset.size(), 0);
290    TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
291    TEST_EQUAL(mymset.get_matches_upper_bound(), 6);
292    TEST(mymset.get_matches_estimated() > 0);
293    TEST(mymset.get_matches_estimated() <= 6);
294
295    // Check that the bounds are appropriate if we ask for only one hit.
296    // (Regression test - until SVN 10256, we didn't reduce the lower_bound
297    // appropriately, and returned 6 here.)
298    mymset = enquire.get_mset(0, 1, 0, &myfunctor);
299    TEST_EQUAL(mymset.size(), 1);
300    TEST(mymset.get_matches_lower_bound() >= 1);
301    TEST(mymset.get_matches_lower_bound() <= 3);
302    TEST(mymset.get_matches_upper_bound() >= 3);
303    TEST(mymset.get_matches_upper_bound() <= 6);
304    TEST(mymset.get_matches_estimated() > 0);
305    TEST(mymset.get_matches_estimated() <= 6);
306
307    // Check that the other documents don't satisfy the condition.
308    for (Xapian::docid did = 1; did < docid_checked.size(); ++did) {
309        if (!docid_checked[did]) {
310            TEST(!myfunctor(db.get_document(did)));
311        }
312    }
313
314    // Check that the bounds are appropriate if a collapse key is used.
315    // Use a value which is never set so we don't actually discard anything.
316    enquire.set_collapse_key(99);
317    mymset = enquire.get_mset(0, 1, 0, &myfunctor);
318    TEST_EQUAL(mymset.size(), 1);
319    TEST(mymset.get_matches_lower_bound() >= 1);
320    TEST(mymset.get_matches_lower_bound() <= 3);
321    TEST(mymset.get_matches_upper_bound() >= 3);
322    TEST(mymset.get_matches_upper_bound() <= 6);
323    TEST(mymset.get_matches_estimated() > 0);
324    TEST(mymset.get_matches_estimated() <= 6);
325
326    // Check that the bounds are appropriate if a percentage cutoff is in
327    // use.  Set a 1% threshold so we don't actually discard anything.
328    enquire.set_collapse_key(Xapian::BAD_VALUENO);
329    enquire.set_cutoff(1);
330    mymset = enquire.get_mset(0, 1, 0, &myfunctor);
331    TEST_EQUAL(mymset.size(), 1);
332    TEST(mymset.get_matches_lower_bound() >= 1);
333    TEST(mymset.get_matches_lower_bound() <= 3);
334    TEST(mymset.get_matches_upper_bound() >= 3);
335    TEST(mymset.get_matches_upper_bound() <= 6);
336    TEST(mymset.get_matches_estimated() > 0);
337    TEST(mymset.get_matches_estimated() <= 6);
338
339    // And now with both a collapse key and percentage cutoff.
340    enquire.set_collapse_key(99);
341    mymset = enquire.get_mset(0, 1, 0, &myfunctor);
342    TEST_EQUAL(mymset.size(), 1);
343    TEST(mymset.get_matches_lower_bound() >= 1);
344    TEST(mymset.get_matches_lower_bound() <= 3);
345    TEST(mymset.get_matches_upper_bound() >= 3);
346    TEST(mymset.get_matches_upper_bound() <= 6);
347    TEST(mymset.get_matches_estimated() > 0);
348    TEST(mymset.get_matches_estimated() <= 6);
349
350    return true;
351}
352
353// Test Xapian::MatchDecider functor used as a match spy.
354DEFINE_TESTCASE(matchfunctor2, backend && !remote) {
355    Xapian::Database db(get_database("apitest_simpledata"));
356    Xapian::Enquire enquire(db);
357    enquire.set_query(Xapian::Query("this"));
358
359    myMatchDecider myfunctor;
360
361    Xapian::MSet mymset = enquire.get_mset(0, 100, 0, NULL, &myfunctor);
362
363    vector<bool> docid_checked(db.get_lastdocid());
364
365    // Check that we get the expected number of matches, and that they
366    // satisfy the condition.
367    Xapian::MSetIterator i = mymset.begin();
368    TEST(i != mymset.end());
369    TEST_EQUAL(mymset.size(), 3);
370    for ( ; i != mymset.end(); ++i) {
371        const Xapian::Document doc(i.get_document());
372        TEST(myfunctor(doc));
373        docid_checked[*i] = true;
374    }
375
376    // Check that the other documents don't satisfy the condition.
377    for (Xapian::docid did = 1; did < docid_checked.size(); ++did) {
378        if (!docid_checked[did]) {
379            TEST(!myfunctor(db.get_document(did)));
380        }
381    }
382
383    return true;
384}
385
386class myMatchDecider2 : public Xapian::MatchDecider {
387    public:
388        bool operator()(const Xapian::Document &doc) const {
389            // Note that this is not recommended usage of get_data()
390            return doc.get_data().find("We produce") == string::npos;
391        }
392};
393
394
395// Regression test for lower bound using functor, sorting and collapsing.
396DEFINE_TESTCASE(matchfunctor3, backend) {
397    Xapian::Database db(get_database("etext"));
398    Xapian::Enquire enquire(db);
399    enquire.set_query(Xapian::Query(""));
400    enquire.set_collapse_key(12);
401    enquire.set_sort_by_value(11);
402
403    myMatchDecider2 myfunctor;
404
405    Xapian::MSet mymset1 = enquire.get_mset(0, 2, 0, NULL, &myfunctor);
406    Xapian::MSet mymset2 = enquire.get_mset(0, 1000, 0, NULL, &myfunctor);
407
408    // mymset2 should contain all the hits, so the statistics should be exact.
409    TEST_EQUAL(mymset2.get_matches_estimated(),
410               mymset2.size());
411    TEST_EQUAL(mymset2.get_matches_lower_bound(),
412               mymset2.get_matches_estimated());
413    TEST_EQUAL(mymset2.get_matches_estimated(),
414               mymset2.get_matches_upper_bound());
415
416    // Check that the lower bound in mymset1 is not greater than the known
417    // number of hits.  This failed until revision 10811.
418    TEST_LESSER_OR_EQUAL(mymset1.get_matches_lower_bound(),
419                         mymset2.size());
420
421    // Check that the bounds for mymset1 make sense
422    TEST_LESSER_OR_EQUAL(mymset1.get_matches_lower_bound(), mymset1.get_matches_estimated());
423    TEST_LESSER_OR_EQUAL(mymset1.size(), mymset1.get_matches_upper_bound());
424    TEST_LESSER_OR_EQUAL(mymset1.get_matches_estimated(), mymset1.get_matches_upper_bound());
425
426    return true;
427}
428
429// tests that mset iterators on msets compare correctly.
430DEFINE_TESTCASE(msetiterator1, backend) {
431    Xapian::Enquire enquire(get_database("apitest_simpledata"));
432    enquire.set_query(Xapian::Query("this"));
433    Xapian::MSet mymset = enquire.get_mset(0, 2);
434
435    Xapian::MSetIterator j;
436    j = mymset.begin();
437    Xapian::MSetIterator k = mymset.end();
438    Xapian::MSetIterator l(j);
439    Xapian::MSetIterator m(k);
440    Xapian::MSetIterator n = mymset.begin();
441    Xapian::MSetIterator o = mymset.begin();
442    TEST_NOT_EQUAL(j, k);
443    TEST_NOT_EQUAL(l, m);
444    TEST_EQUAL(k, m);
445    TEST_EQUAL(j, l);
446    TEST_EQUAL(j, j);
447    TEST_EQUAL(k, k);
448
449    k = j;
450    TEST_EQUAL(j, k);
451    TEST_EQUAL(j, o);
452    k++;
453    TEST_NOT_EQUAL(j, k);
454    TEST_NOT_EQUAL(k, l);
455    TEST_NOT_EQUAL(k, m);
456    TEST_NOT_EQUAL(k, o);
457    o++;
458    TEST_EQUAL(k, o);
459    k++;
460    TEST_NOT_EQUAL(j, k);
461    TEST_NOT_EQUAL(k, l);
462    TEST_EQUAL(k, m);
463    TEST_EQUAL(n, l);
464
465    n = m;
466    TEST_NOT_EQUAL(n, l);
467    TEST_EQUAL(n, m);
468    TEST_NOT_EQUAL(n, mymset.begin());
469    TEST_EQUAL(n, mymset.end());
470
471    return true;
472}
473
474// tests that mset iterators on empty msets compare equal.
475DEFINE_TESTCASE(msetiterator2, backend) {
476    Xapian::Enquire enquire(get_database("apitest_simpledata"));
477    enquire.set_query(Xapian::Query("this"));
478    Xapian::MSet mymset = enquire.get_mset(0, 0);
479
480    Xapian::MSetIterator j = mymset.begin();
481    Xapian::MSetIterator k = mymset.end();
482    Xapian::MSetIterator l(j);
483    Xapian::MSetIterator m(k);
484    TEST_EQUAL(j, k);
485    TEST_EQUAL(l, m);
486    TEST_EQUAL(k, m);
487    TEST_EQUAL(j, l);
488    TEST_EQUAL(j, j);
489    TEST_EQUAL(k, k);
490
491    return true;
492}
493
494// tests that begin().get_document() works when first != 0
495DEFINE_TESTCASE(msetiterator3, backend) {
496    Xapian::Database mydb(get_database("apitest_simpledata"));
497    Xapian::Enquire enquire(mydb);
498    enquire.set_query(Xapian::Query("this"));
499
500    Xapian::MSet mymset = enquire.get_mset(2, 10);
501
502    TEST(!mymset.empty());
503    Xapian::Document doc(mymset.begin().get_document());
504    TEST(!doc.get_data().empty());
505
506    return true;
507}
508
509// tests that eset iterators on empty esets compare equal.
510DEFINE_TESTCASE(esetiterator1, backend) {
511    Xapian::Enquire enquire(get_database("apitest_simpledata"));
512    enquire.set_query(Xapian::Query("this"));
513
514    Xapian::MSet mymset = enquire.get_mset(0, 10);
515    TEST(mymset.size() >= 2);
516
517    Xapian::RSet myrset;
518    Xapian::MSetIterator i = mymset.begin();
519    myrset.add_document(*i);
520    myrset.add_document(*(++i));
521
522    Xapian::ESet myeset = enquire.get_eset(2, myrset);
523    Xapian::ESetIterator j;
524    j = myeset.begin();
525    Xapian::ESetIterator k = myeset.end();
526    Xapian::ESetIterator l(j);
527    Xapian::ESetIterator m(k);
528    Xapian::ESetIterator n = myeset.begin();
529
530    TEST_NOT_EQUAL(j, k);
531    TEST_NOT_EQUAL(l, m);
532    TEST_EQUAL(k, m);
533    TEST_EQUAL(j, l);
534    TEST_EQUAL(j, j);
535    TEST_EQUAL(k, k);
536
537    k = j;
538    TEST_EQUAL(j, k);
539    k++;
540    TEST_NOT_EQUAL(j, k);
541    TEST_NOT_EQUAL(k, l);
542    TEST_NOT_EQUAL(k, m);
543    k++;
544    TEST_NOT_EQUAL(j, k);
545    TEST_NOT_EQUAL(k, l);
546    TEST_EQUAL(k, m);
547    TEST_EQUAL(n, l);
548
549    n = m;
550    TEST_NOT_EQUAL(n, l);
551    TEST_EQUAL(n, m);
552    TEST_NOT_EQUAL(n, myeset.begin());
553    TEST_EQUAL(n, myeset.end());
554
555    return true;
556}
557
558// tests that eset iterators on empty esets compare equal.
559DEFINE_TESTCASE(esetiterator2, backend) {
560    Xapian::Enquire enquire(get_database("apitest_simpledata"));
561    enquire.set_query(Xapian::Query("this"));
562
563    Xapian::MSet mymset = enquire.get_mset(0, 10);
564    TEST(mymset.size() >= 2);
565
566    Xapian::RSet myrset;
567    Xapian::MSetIterator i = mymset.begin();
568    myrset.add_document(*i);
569    myrset.add_document(*(++i));
570
571    Xapian::ESet myeset = enquire.get_eset(0, myrset);
572    Xapian::ESetIterator j = myeset.begin();
573    Xapian::ESetIterator k = myeset.end();
574    Xapian::ESetIterator l(j);
575    Xapian::ESetIterator m(k);
576    TEST_EQUAL(j, k);
577    TEST_EQUAL(l, m);
578    TEST_EQUAL(k, m);
579    TEST_EQUAL(j, l);
580    TEST_EQUAL(j, j);
581    TEST_EQUAL(k, k);
582
583    return true;
584}
585
586// tests the collapse-on-key
587DEFINE_TESTCASE(collapsekey1, backend) {
588    Xapian::Enquire enquire(get_database("apitest_simpledata"));
589    enquire.set_query(Xapian::Query("this"));
590
591    Xapian::MSet mymset1 = enquire.get_mset(0, 100);
592    Xapian::doccount mymsize1 = mymset1.size();
593
594    for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
595        enquire.set_collapse_key(value_no);
596        Xapian::MSet mymset = enquire.get_mset(0, 100);
597
598        TEST_AND_EXPLAIN(mymsize1 > mymset.size(),
599                         "Had no fewer items when performing collapse: don't know whether it worked.");
600
601        map<string, Xapian::docid> values;
602        Xapian::MSetIterator i = mymset.begin();
603        for ( ; i != mymset.end(); ++i) {
604            string value = i.get_document().get_value(value_no);
605            TEST(values[value] == 0 || value == "");
606            values[value] = *i;
607        }
608    }
609
610    return true;
611}
612
613// tests that collapse-on-key modifies the predicted bounds for the number of
614// matches appropriately.
615DEFINE_TESTCASE(collapsekey2, backend) {
616    SKIP_TEST("Don't have a suitable database currently");
617    // FIXME: this needs an appropriate database creating, but that's quite
618    // subtle to do it seems.
619    Xapian::Enquire enquire(get_database("apitest_simpledata2"));
620    enquire.set_query(Xapian::Query("this"));
621
622    Xapian::MSet mymset1 = enquire.get_mset(0, 1);
623
624    // Test that if no duplicates are found, then the upper bound remains
625    // unchanged and the lower bound drops.
626    {
627        enquire.set_query(Xapian::Query("this"));
628        Xapian::valueno value_no = 3;
629        enquire.set_collapse_key(value_no);
630        Xapian::MSet mymset = enquire.get_mset(0, 1);
631
632        TEST(mymset.get_matches_lower_bound() < mymset1.get_matches_lower_bound());
633        TEST_EQUAL(mymset.get_matches_upper_bound(), mymset1.get_matches_upper_bound());
634    }
635
636    return true;
637}
638
639// tests that collapse-on-key modifies the predicted bounds for the number of
640// matches appropriately.
641DEFINE_TESTCASE(collapsekey3, backend) {
642    Xapian::Enquire enquire(get_database("apitest_simpledata"));
643    enquire.set_query(Xapian::Query("this"));
644
645    Xapian::MSet mymset1 = enquire.get_mset(0, 3);
646
647    for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
648        enquire.set_collapse_key(value_no);
649        Xapian::MSet mymset = enquire.get_mset(0, 3);
650
651        TEST_AND_EXPLAIN(mymset1.get_matches_lower_bound() > mymset.get_matches_lower_bound(),
652                         "Lower bound was not lower when performing collapse: don't know whether it worked.");
653        TEST_AND_EXPLAIN(mymset1.get_matches_upper_bound() > mymset.get_matches_upper_bound(),
654                         "Upper bound was not lower when performing collapse: don't know whether it worked.");
655
656        map<string, Xapian::docid> values;
657        Xapian::MSetIterator i = mymset.begin();
658        for ( ; i != mymset.end(); ++i) {
659            string value = i.get_document().get_value(value_no);
660            TEST(values[value] == 0 || value == "");
661            values[value] = *i;
662        }
663    }
664
665    // Test that if the collapse value is always empty, then the upper bound
666    // remains unchanged, and the lower bound is the same or lower (it can be
667    // lower because the matcher counts the number of documents with empty
668    // collapse keys, but may have rejected a document because its weight is
669    // too low for the proto-MSet before it even looks at its collapse key).
670    {
671        Xapian::valueno value_no = 1000;
672        enquire.set_collapse_key(value_no);
673        Xapian::MSet mymset = enquire.get_mset(0, 3);
674
675        TEST(mymset.get_matches_lower_bound() <= mymset1.get_matches_lower_bound());
676        TEST_EQUAL(mymset.get_matches_upper_bound(), mymset1.get_matches_upper_bound());
677
678        map<string, Xapian::docid> values;
679        Xapian::MSetIterator i = mymset.begin();
680        for ( ; i != mymset.end(); ++i) {
681            string value = i.get_document().get_value(value_no);
682            TEST(values[value] == 0 || value == "");
683            values[value] = *i;
684        }
685    }
686
687    return true;
688}
689
690// tests that collapse-on-key modifies the predicted bounds for the number of
691// matches appropriately even when no results are requested.
692DEFINE_TESTCASE(collapsekey4, backend) {
693    Xapian::Enquire enquire(get_database("apitest_simpledata"));
694    enquire.set_query(Xapian::Query("this"));
695
696    Xapian::MSet mymset1 = enquire.get_mset(0, 0);
697
698    for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
699        enquire.set_collapse_key(value_no);
700        Xapian::MSet mymset = enquire.get_mset(0, 0);
701
702        TEST_AND_EXPLAIN(mymset.get_matches_lower_bound() == 1,
703                         "Lower bound was not 1 when performing collapse but not asking for any results.");
704        TEST_AND_EXPLAIN(mymset1.get_matches_upper_bound() == mymset.get_matches_upper_bound(),
705                         "Upper bound was changed when performing collapse but not asking for any results.");
706
707        map<string, Xapian::docid> values;
708        Xapian::MSetIterator i = mymset.begin();
709        for ( ; i != mymset.end(); ++i) {
710            string value = i.get_document().get_value(value_no);
711            TEST(values[value] == 0 || value == "");
712            values[value] = *i;
713        }
714    }
715
716    return true;
717}
718
719// test for keepalives
720DEFINE_TESTCASE(keepalive1, remote) {
721    Xapian::Database db(get_remote_database("apitest_simpledata", 5000));
722
723    /* Test that keep-alives work */
724    for (int i = 0; i < 10; ++i) {
725        sleep(2);
726        db.keep_alive();
727    }
728    Xapian::Enquire enquire(db);
729    enquire.set_query(Xapian::Query("word"));
730    enquire.get_mset(0, 10);
731
732    /* Test that things break without keepalives */
733    sleep(10);
734    enquire.set_query(Xapian::Query("word"));
735    TEST_EXCEPTION(Xapian::NetworkError,
736                   enquire.get_mset(0, 10));
737
738    return true;
739}
740
741// test that iterating through all terms in a database works.
742DEFINE_TESTCASE(allterms1, backend) {
743    Xapian::Database db(get_database("apitest_allterms"));
744    Xapian::TermIterator ati = db.allterms_begin();
745    TEST(ati != db.allterms_end());
746    TEST_EQUAL(*ati, "one");
747    TEST_EQUAL(ati.get_termfreq(), 1);
748
749    Xapian::TermIterator ati2 = ati;
750
751    ati++;
752    TEST(ati != db.allterms_end());
753    if (verbose) {
754        tout << "*ati = `" << *ati << "'\n";
755        tout << "*ati.length = `" << (*ati).length() << "'\n";
756        tout << "*ati == \"one\" = " << (*ati == "one") << "\n";
757        tout << "*ati[3] = " << ((*ati)[3]) << "\n";
758        tout << "*ati = `" << *ati << "'\n";
759    }
760    TEST(*ati == "three");
761    TEST(ati.get_termfreq() == 3);
762
763#if 0
764    TEST(ati2 != db.allterms_end());
765    TEST(*ati2 == "one");
766    TEST(ati2.get_termfreq() == 1);
767#endif
768
769    ++ati;
770#if 0
771    ++ati2;
772#endif
773    TEST(ati != db.allterms_end());
774    TEST(*ati == "two");
775    TEST(ati.get_termfreq() == 2);
776
777#if 0
778    TEST(ati2 != db.allterms_end());
779    TEST(*ati2 == "three");
780    TEST(ati2.get_termfreq() == 3);
781#endif
782
783    ati++;
784    TEST(ati == db.allterms_end());
785
786    return true;
787}
788
789// test that iterating through all terms in two databases works.
790DEFINE_TESTCASE(allterms2, backend) {
791    Xapian::Database db;
792    db.add_database(get_database("apitest_allterms"));
793    db.add_database(get_database("apitest_allterms2"));
794    Xapian::TermIterator ati = db.allterms_begin();
795
796    TEST(ati != db.allterms_end());
797    TEST(*ati == "five");
798    TEST(ati.get_termfreq() == 2);
799    ati++;
800
801    TEST(ati != db.allterms_end());
802    TEST(*ati == "four");
803    TEST(ati.get_termfreq() == 1);
804
805    ati++;
806    TEST(ati != db.allterms_end());
807    TEST(*ati == "one");
808    TEST(ati.get_termfreq() == 1);
809
810    ++ati;
811    TEST(ati != db.allterms_end());
812    TEST(*ati == "six");
813    TEST(ati.get_termfreq() == 3);
814
815    ati++;
816    TEST(ati != db.allterms_end());
817    TEST(*ati == "three");
818    TEST(ati.get_termfreq() == 3);
819
820    ati++;
821    TEST(ati != db.allterms_end());
822    TEST(*ati == "two");
823    TEST(ati.get_termfreq() == 2);
824
825    ati++;
826    TEST(ati == db.allterms_end());
827
828    return true;
829}
830
831// test that skip_to sets at_end (regression test)
832DEFINE_TESTCASE(allterms3, backend) {
833    Xapian::Database db;
834    db.add_database(get_database("apitest_allterms"));
835    Xapian::TermIterator ati = db.allterms_begin();
836
837    ati.skip_to(string("zzzzzz"));
838    TEST(ati == db.allterms_end());
839
840    return true;
841}
842
843// test that next ignores extra entries due to long posting lists being
844// chunked (regression test for quartz)
845DEFINE_TESTCASE(allterms4, backend) {
846    // apitest_allterms4 contains 682 documents each containing just the word
847    // "foo".  682 was the magic number which started to cause Quartz problems.
848    Xapian::Database db = get_database("apitest_allterms4");
849
850    Xapian::TermIterator i = db.allterms_begin();
851    TEST(i != db.allterms_end());
852    TEST(*i == "foo");
853    TEST(i.get_termfreq() == 682);
854    ++i;
855    TEST(i == db.allterms_end());
856
857    return true;
858}
859
860// test that skip_to with an exact match sets the current term (regression test
861// for quartz)
862DEFINE_TESTCASE(allterms5, backend) {
863    Xapian::Database db;
864    db.add_database(get_database("apitest_allterms"));
865    Xapian::TermIterator ati = db.allterms_begin();
866    ati.skip_to("three");
867    TEST(ati != db.allterms_end());
868    TEST_EQUAL(*ati, "three");
869
870    return true;
871}
872
873// test allterms iterators with prefixes
874DEFINE_TESTCASE(allterms6, backend) {
875    Xapian::Database db;
876    db.add_database(get_database("apitest_allterms"));
877    db.add_database(get_database("apitest_allterms2"));
878
879    Xapian::TermIterator ati = db.allterms_begin("three");
880    TEST(ati != db.allterms_end("three"));
881    TEST_EQUAL(*ati, "three");
882    ati.skip_to("three");
883    TEST(ati != db.allterms_end("three"));
884    TEST_EQUAL(*ati, "three");
885    ati++;
886    TEST(ati == db.allterms_end("three"));
887
888    ati = db.allterms_begin("thre");
889    TEST(ati != db.allterms_end("thre"));
890    TEST_EQUAL(*ati, "three");
891    ati.skip_to("three");
892    TEST(ati != db.allterms_end("thre"));
893    TEST_EQUAL(*ati, "three");
894    ati++;
895    TEST(ati == db.allterms_end("thre"));
896
897    ati = db.allterms_begin("f");
898    TEST(ati != db.allterms_end("f"));
899    TEST_EQUAL(*ati, "five");
900    TEST(ati != db.allterms_end("f"));
901    ati.skip_to("three");
902    TEST(ati == db.allterms_end("f"));
903
904    ati = db.allterms_begin("f");
905    TEST(ati != db.allterms_end("f"));
906    TEST_EQUAL(*ati, "five");
907    ati++;
908    TEST(ati != db.allterms_end("f"));
909    TEST_EQUAL(*ati, "four");
910    ati++;
911    TEST(ati == db.allterms_end("f"));
912
913    ati = db.allterms_begin("absent");
914    TEST(ati == db.allterms_end("absent"));
915
916    return true;
917}
918
919// test that searching for a term with a special characters in it works
920DEFINE_TESTCASE(specialterms1, backend) {
921    Xapian::Enquire enquire(get_database("apitest_space"));
922    Xapian::MSet mymset;
923    Xapian::doccount count;
924    Xapian::MSetIterator m;
925    Xapian::Stem stemmer("english");
926
927    enquire.set_query(stemmer("new\nline"));
928    mymset = enquire.get_mset(0, 10);
929    TEST_MSET_SIZE(mymset, 1);
930    count = 0;
931    for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
932    TEST_EQUAL(count, 1);
933
934    for (Xapian::valueno value_no = 0; value_no < 7; ++value_no) {
935        string value = mymset.begin().get_document().get_value(value_no);
936        TEST_NOT_EQUAL(value, "");
937        if (value_no == 0) {
938            TEST(value.size() > 263);
939            TEST_EQUAL(static_cast<unsigned char>(value[262]), 255);
940            for (int k = 0; k < 256; k++) {
941                TEST_EQUAL(static_cast<unsigned char>(value[k+7]), k);
942            }
943        }
944    }
945
946    enquire.set_query(stemmer(string("big\0zero", 8)));
947    mymset = enquire.get_mset(0, 10);
948    TEST_MSET_SIZE(mymset, 1);
949    count = 0;
950    for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
951    TEST_EQUAL(count, 1);
952
953    return true;
954}
955
956// test that terms with a special characters in appear correctly when iterating allterms
957