"Fossies" - the Fresh Open Source Software Archive

Member "xapian-core-1.4.14/tests/api_compact.cc" (23 Nov 2019, 21142 Bytes) of package /linux/www/xapian-core-1.4.14.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "api_compact.cc": 1.4.12_vs_1.4.13.

    1 /** @file api_compact.cc
    2  * @brief Tests of Database::compact()
    3  */
    4 /* Copyright (C) 2009,2010,2011,2012,2013,2015,2016,2017,2018,2019 Olly Betts
    5  * Copyright (C) 2010 Richard Boulton
    6  *
    7  * This program is free software; you can redistribute it and/or
    8  * modify it under the terms of the GNU General Public License as
    9  * published by the Free Software Foundation; either version 2 of the
   10  * License, or (at your option) any later version.
   11  *
   12  * This program is distributed in the hope that it will be useful,
   13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15  * GNU General Public License for more details.
   16  *
   17  * You should have received a copy of the GNU General Public License
   18  * along with this program; if not, write to the Free Software
   19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
   20  * USA
   21  */
   22 
   23 #include <config.h>
   24 
   25 #include "api_compact.h"
   26 
   27 #include <xapian.h>
   28 
   29 #include "apitest.h"
   30 #include "dbcheck.h"
   31 #include "filetests.h"
   32 #include "msvcignoreinvalidparam.h"
   33 #include "str.h"
   34 #include "testsuite.h"
   35 #include "testutils.h"
   36 
   37 #include <cerrno>
   38 #include <cstdlib>
   39 #include <fstream>
   40 
   41 #include <sys/types.h>
   42 #include "safesysstat.h"
   43 #include "safefcntl.h"
   44 #include "safeunistd.h"
   45 
   46 #include "unixcmds.h"
   47 
   48 using namespace std;
   49 
   50 static void
   51 make_sparse_db(Xapian::WritableDatabase &db, const string & s)
   52 {
   53     // Need non-const pointer for strtoul(), but data isn't modified.
   54     char * p = const_cast<char *>(s.c_str());
   55 
   56     while (*p) {
   57     bool del = (*p == '!');
   58     if (del) ++p;
   59     Xapian::docid first = strtoul(p, &p, 10);
   60     Xapian::docid last = first;
   61     if (*p == '-') {
   62         last = strtoul(p + 1, &p, 10);
   63     }
   64     if (*p && *p != ' ') {
   65         tout << p - s.c_str() << endl;
   66         FAIL_TEST("Bad sparse db spec (expected space): " << s);
   67     }
   68     if (first > last) {
   69         FAIL_TEST("Bad sparse db spec (first > last): " << s);
   70     }
   71 
   72     do {
   73         if (del) {
   74         db.delete_document(first);
   75         } else {
   76         Xapian::Document doc;
   77         string id = str(first);
   78         doc.set_data(id);
   79         doc.add_term("Q" + str(first));
   80         doc.add_term(string(first % 7 + 1, char((first % 26) + 'a')));
   81         db.replace_document(first, doc);
   82         }
   83     } while (first++ < last);
   84 
   85     if (*p == '\0') break;
   86     ++p;
   87     }
   88 
   89     db.commit();
   90 }
   91 
   92 static void
   93 check_sparse_uid_terms(const string & path)
   94 {
   95     Xapian::Database db(path);
   96     Xapian::TermIterator t;
   97     for (t = db.allterms_begin("Q"); t != db.allterms_end("Q"); ++t) {
   98     Xapian::docid did = atoi((*t).c_str() + 1);
   99     Xapian::PostingIterator p = db.postlist_begin(*t);
  100     TEST_EQUAL(*p, did);
  101     }
  102 }
  103 
  104 // With multi the docids in the shards change the behaviour.
  105 DEFINE_TESTCASE(compactnorenumber1, compact && generated && !multi) {
  106     string a = get_database_path("compactnorenumber1a", make_sparse_db,
  107                  "5-7 24 76 987 1023-1027 9999 !9999");
  108     string a_uuid;
  109     {
  110     Xapian::Database db(a);
  111     a_uuid = db.get_uuid();
  112     }
  113     string b = get_database_path("compactnorenumber1b", make_sparse_db,
  114                  "1027-1030");
  115     string c = get_database_path("compactnorenumber1c", make_sparse_db,
  116                  "1028-1040");
  117     string d = get_database_path("compactnorenumber1d", make_sparse_db,
  118                  "3000 999999 !999999");
  119 
  120     string out = get_compaction_output_path("compactnorenumber1out");
  121 
  122     rm_rf(out);
  123     {
  124     Xapian::Database db(a);
  125     db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER);
  126     }
  127 
  128     check_sparse_uid_terms(out);
  129 
  130     {
  131     TEST(!dir_exists(out + "/donor"));
  132     Xapian::Database db(out);
  133     // xapian-compact should change the UUID of the database, but didn't
  134     // prior to 1.0.18/1.1.4.
  135     string out_uuid = db.get_uuid();
  136     TEST_NOT_EQUAL(a_uuid, out_uuid);
  137     TEST_EQUAL(out_uuid.size(), 36);
  138     TEST_NOT_EQUAL(out_uuid, "00000000-0000-0000-0000-000000000000");
  139 
  140     // White box test - ensure that the donor database is removed.
  141     TEST(!dir_exists(out + "/donor"));
  142     }
  143 
  144     rm_rf(out);
  145     {
  146     Xapian::Database db;
  147     db.add_database(Xapian::Database(a));
  148     db.add_database(Xapian::Database(c));
  149     db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER);
  150     }
  151     check_sparse_uid_terms(out);
  152     {
  153     // Check that xapian-compact is producing a consistent database.  Also,
  154     // regression test - xapian 1.1.4 set lastdocid to 0 in the output
  155     // database.
  156     Xapian::Database outdb(out);
  157     dbcheck(outdb, 24, 9999);
  158     }
  159 
  160     rm_rf(out);
  161     {
  162     Xapian::Database db;
  163     db.add_database(Xapian::Database(d));
  164     db.add_database(Xapian::Database(a));
  165     db.add_database(Xapian::Database(c));
  166     db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER);
  167     }
  168     check_sparse_uid_terms(out);
  169 
  170     rm_rf(out);
  171     {
  172     Xapian::Database db;
  173     db.add_database(Xapian::Database(c));
  174     db.add_database(Xapian::Database(a));
  175     db.add_database(Xapian::Database(d));
  176     db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER);
  177     }
  178     check_sparse_uid_terms(out);
  179 
  180     // Should fail.
  181     rm_rf(out);
  182     {
  183     Xapian::Database db;
  184     db.add_database(Xapian::Database(a));
  185     db.add_database(Xapian::Database(b));
  186     TEST_EXCEPTION(Xapian::InvalidOperationError,
  187         db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER)
  188     );
  189     }
  190 
  191     // Should fail.
  192     rm_rf(out);
  193     {
  194     Xapian::Database db;
  195     db.add_database(Xapian::Database(b));
  196     db.add_database(Xapian::Database(a));
  197     TEST_EXCEPTION(Xapian::InvalidOperationError,
  198         db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER)
  199     );
  200     }
  201 
  202     // Should fail.
  203     rm_rf(out);
  204     {
  205     Xapian::Database db;
  206     db.add_database(Xapian::Database(a));
  207     db.add_database(Xapian::Database(b));
  208     db.add_database(Xapian::Database(d));
  209     TEST_EXCEPTION(Xapian::InvalidOperationError,
  210         db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER)
  211     );
  212     }
  213 
  214     // Should fail.
  215     rm_rf(out);
  216     {
  217     Xapian::Database db;
  218     db.add_database(Xapian::Database(d));
  219     db.add_database(Xapian::Database(b));
  220     db.add_database(Xapian::Database(a));
  221     TEST_EXCEPTION(Xapian::InvalidOperationError,
  222         db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER)
  223     );
  224     }
  225 
  226     // Should fail.
  227     rm_rf(out);
  228     {
  229     Xapian::Database db;
  230     db.add_database(Xapian::Database(b));
  231     db.add_database(Xapian::Database(a));
  232     db.add_database(Xapian::Database(d));
  233     TEST_EXCEPTION(Xapian::InvalidOperationError,
  234         db.compact(out, Xapian::DBCOMPACT_NO_RENUMBER)
  235     );
  236     }
  237 
  238     return true;
  239 }
  240 
  241 // Test use of compact to merge two databases.
  242 DEFINE_TESTCASE(compactmerge1, compact) {
  243     string indbpath = get_database_path("apitest_simpledata");
  244     string outdbpath = get_compaction_output_path("compactmerge1out");
  245     rm_rf(outdbpath);
  246 
  247     const string& dbtype = get_dbtype();
  248     bool singlefile = startswith(dbtype, "singlefile_");
  249     {
  250     Xapian::Database db;
  251     db.add_database(Xapian::Database(indbpath));
  252     db.add_database(Xapian::Database(indbpath));
  253     if (singlefile) {
  254         db.compact(outdbpath, Xapian::DBCOMPACT_SINGLE_FILE);
  255     } else {
  256         db.compact(outdbpath);
  257     }
  258     }
  259 
  260     Xapian::Database indb(get_database("apitest_simpledata"));
  261     Xapian::Database outdb(outdbpath);
  262 
  263     TEST_EQUAL(indb.get_doccount() * 2, outdb.get_doccount());
  264     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  265 
  266     if (singlefile) {
  267     // Check we actually got a single file out.
  268     TEST(file_exists(outdbpath));
  269     TEST_EQUAL(Xapian::Database::check(outdbpath, 0, &tout), 0);
  270     } else if (startswith(dbtype, "multi_")) {
  271     // Can't check a sharded DB.
  272     } else {
  273     // Check we got a directory out, not a file.
  274     TEST(dir_exists(outdbpath));
  275     static const char* const suffixes[] = {
  276         "", "/postlist", "/termlist.", nullptr
  277     };
  278     for (auto s : suffixes) {
  279         string suffix;
  280         if (s) {
  281         suffix = s;
  282         } else {
  283         if (get_dbtype() == "chert") {
  284             suffix = "/record.DB";
  285         } else {
  286             suffix = "/docdata." + dbtype;
  287         }
  288         }
  289         tout.str(string());
  290         tout << "Trying suffix '" << suffix << "'" << endl;
  291         string arg = outdbpath;
  292         arg += suffix;
  293         TEST_EQUAL(Xapian::Database::check(arg, 0, &tout), 0);
  294     }
  295     }
  296 
  297     return true;
  298 }
  299 
  300 static void
  301 make_multichunk_db(Xapian::WritableDatabase &db, const string &)
  302 {
  303     int count = 10000;
  304 
  305     Xapian::Document doc;
  306     doc.add_term("a");
  307     while (count) {
  308     db.add_document(doc);
  309     --count;
  310     }
  311 
  312     db.commit();
  313 }
  314 
  315 // Test use of compact on a database which has multiple chunks for a term.
  316 // This is a regression test for ticket #427
  317 DEFINE_TESTCASE(compactmultichunks1, compact && generated) {
  318     string indbpath = get_database_path("compactmultichunks1in",
  319                     make_multichunk_db, "");
  320     string outdbpath = get_compaction_output_path("compactmultichunks1out");
  321     rm_rf(outdbpath);
  322 
  323     {
  324     Xapian::Database db(indbpath);
  325     db.compact(outdbpath);
  326     }
  327 
  328     Xapian::Database indb(indbpath);
  329     Xapian::Database outdb(outdbpath);
  330 
  331     TEST_EQUAL(indb.get_doccount(), outdb.get_doccount());
  332     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  333 
  334     return true;
  335 }
  336 
  337 // Test compacting from a stub database directory.
  338 DEFINE_TESTCASE(compactstub1, compact) {
  339     const char * stubpath = ".stub/compactstub1";
  340     const char * stubpathfile = ".stub/compactstub1/XAPIANDB";
  341     mkdir(".stub", 0755);
  342     mkdir(stubpath, 0755);
  343     ofstream stub(stubpathfile);
  344     TEST(stub.is_open());
  345     stub << "auto ../../" << get_database_path("apitest_simpledata") << endl;
  346     stub << "auto ../../" << get_database_path("apitest_simpledata2") << endl;
  347     stub.close();
  348 
  349     string outdbpath = get_compaction_output_path("compactstub1out");
  350     rm_rf(outdbpath);
  351 
  352     {
  353     Xapian::Database db(stubpath);
  354     db.compact(outdbpath);
  355     }
  356 
  357     Xapian::Database indb(stubpath);
  358     Xapian::Database outdb(outdbpath);
  359 
  360     TEST_EQUAL(indb.get_doccount(), outdb.get_doccount());
  361     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  362 
  363     return true;
  364 }
  365 
  366 // Test compacting from a stub database file.
  367 DEFINE_TESTCASE(compactstub2, compact) {
  368     const char * stubpath = ".stub/compactstub2";
  369     mkdir(".stub", 0755);
  370     ofstream stub(stubpath);
  371     TEST(stub.is_open());
  372     stub << "auto ../" << get_database_path("apitest_simpledata") << endl;
  373     stub << "auto ../" << get_database_path("apitest_simpledata2") << endl;
  374     stub.close();
  375 
  376     string outdbpath = get_compaction_output_path("compactstub2out");
  377     rm_rf(outdbpath);
  378 
  379     {
  380     Xapian::Database db(stubpath);
  381     db.compact(outdbpath);
  382     }
  383 
  384     Xapian::Database indb(stubpath);
  385     Xapian::Database outdb(outdbpath);
  386 
  387     TEST_EQUAL(indb.get_doccount(), outdb.get_doccount());
  388     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  389 
  390     return true;
  391 }
  392 
  393 // Test compacting a stub database file to itself.
  394 DEFINE_TESTCASE(compactstub3, compact) {
  395     const char * stubpath = ".stub/compactstub3";
  396     mkdir(".stub", 0755);
  397     ofstream stub(stubpath);
  398     TEST(stub.is_open());
  399     stub << "auto ../" << get_database_path("apitest_simpledata") << endl;
  400     stub << "auto ../" << get_database_path("apitest_simpledata2") << endl;
  401     stub.close();
  402 
  403     Xapian::doccount in_docs;
  404     {
  405     Xapian::Database indb(stubpath);
  406     in_docs = indb.get_doccount();
  407     indb.compact(stubpath);
  408     }
  409 
  410     Xapian::Database outdb(stubpath);
  411 
  412     TEST_EQUAL(in_docs, outdb.get_doccount());
  413     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  414 
  415     return true;
  416 }
  417 
  418 // Test compacting a stub database directory to itself.
  419 DEFINE_TESTCASE(compactstub4, compact) {
  420     const char * stubpath = ".stub/compactstub4";
  421     const char * stubpathfile = ".stub/compactstub4/XAPIANDB";
  422     mkdir(".stub", 0755);
  423     mkdir(stubpath, 0755);
  424     ofstream stub(stubpathfile);
  425     TEST(stub.is_open());
  426     stub << "auto ../../" << get_database_path("apitest_simpledata") << endl;
  427     stub << "auto ../../" << get_database_path("apitest_simpledata2") << endl;
  428     stub.close();
  429 
  430     Xapian::doccount in_docs;
  431     {
  432     Xapian::Database indb(stubpath);
  433     in_docs = indb.get_doccount();
  434     indb.compact(stubpath);
  435     }
  436 
  437     Xapian::Database outdb(stubpath);
  438 
  439     TEST_EQUAL(in_docs, outdb.get_doccount());
  440     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  441 
  442     return true;
  443 }
  444 
  445 static void
  446 make_all_tables(Xapian::WritableDatabase &db, const string &)
  447 {
  448     Xapian::Document doc;
  449     doc.add_term("foo");
  450     db.add_document(doc);
  451     db.add_spelling("foo");
  452     db.add_synonym("bar", "pub");
  453     db.add_synonym("foobar", "foo");
  454 
  455     db.commit();
  456 }
  457 
  458 static void
  459 make_missing_tables(Xapian::WritableDatabase &db, const string &)
  460 {
  461     Xapian::Document doc;
  462     doc.add_term("foo");
  463     db.add_document(doc);
  464 
  465     db.commit();
  466 }
  467 
  468 DEFINE_TESTCASE(compactmissingtables1, compact && generated) {
  469     string a = get_database_path("compactmissingtables1a",
  470                  make_all_tables);
  471     string b = get_database_path("compactmissingtables1b",
  472                  make_missing_tables);
  473 
  474     string out = get_compaction_output_path("compactmissingtables1out");
  475     rm_rf(out);
  476 
  477     {
  478     Xapian::Database db;
  479     db.add_database(Xapian::Database(a));
  480     db.add_database(Xapian::Database(b));
  481     db.compact(out);
  482     }
  483 
  484     {
  485     Xapian::Database db(out);
  486     TEST_NOT_EQUAL(db.spellings_begin(), db.spellings_end());
  487     TEST_NOT_EQUAL(db.synonym_keys_begin(), db.synonym_keys_end());
  488     // FIXME: arrange for input b to not have a termlist table.
  489 //  TEST_EXCEPTION(Xapian::FeatureUnavailableError, db.termlist_begin(1));
  490     }
  491 
  492     return true;
  493 }
  494 
  495 static void
  496 make_all_tables2(Xapian::WritableDatabase &db, const string &)
  497 {
  498     Xapian::Document doc;
  499     doc.add_term("bar");
  500     db.add_document(doc);
  501     db.add_spelling("bar");
  502     db.add_synonym("bar", "baa");
  503     db.add_synonym("barfoo", "barbar");
  504     db.add_synonym("foofoo", "barfoo");
  505 
  506     db.commit();
  507 }
  508 
  509 /// Adds coverage for merging synonym table.
  510 DEFINE_TESTCASE(compactmergesynonym1, compact && generated) {
  511     string a = get_database_path("compactmergesynonym1a",
  512                  make_all_tables);
  513     string b = get_database_path("compactmergesynonym1b",
  514                  make_all_tables2);
  515 
  516     string out = get_compaction_output_path("compactmergesynonym1out");
  517     rm_rf(out);
  518 
  519     {
  520     Xapian::Database db;
  521     db.add_database(Xapian::Database(a));
  522     db.add_database(Xapian::Database(b));
  523     db.compact(out);
  524     }
  525 
  526     {
  527     Xapian::Database db(out);
  528 
  529     Xapian::TermIterator i = db.spellings_begin();
  530     TEST_NOT_EQUAL(i, db.spellings_end());
  531     TEST_EQUAL(*i, "bar");
  532     ++i;
  533     TEST_NOT_EQUAL(i, db.spellings_end());
  534     TEST_EQUAL(*i, "foo");
  535     ++i;
  536     TEST_EQUAL(i, db.spellings_end());
  537 
  538     i = db.synonym_keys_begin();
  539     TEST_NOT_EQUAL(i, db.synonym_keys_end());
  540     TEST_EQUAL(*i, "bar");
  541     ++i;
  542     TEST_NOT_EQUAL(i, db.synonym_keys_end());
  543     TEST_EQUAL(*i, "barfoo");
  544     ++i;
  545     TEST_NOT_EQUAL(i, db.synonym_keys_end());
  546     TEST_EQUAL(*i, "foobar");
  547     ++i;
  548     TEST_NOT_EQUAL(i, db.synonym_keys_end());
  549     TEST_EQUAL(*i, "foofoo");
  550     ++i;
  551     TEST_EQUAL(i, db.synonym_keys_end());
  552     }
  553 
  554     return true;
  555 }
  556 
  557 DEFINE_TESTCASE(compactempty1, compact) {
  558     string empty_dbpath = get_database_path(string());
  559     string outdbpath = get_compaction_output_path("compactempty1out");
  560     rm_rf(outdbpath);
  561 
  562     {
  563     // Compacting an empty database tried to divide by zero in 1.3.0.
  564     Xapian::Database db;
  565     db.add_database(Xapian::Database(empty_dbpath));
  566     db.compact(outdbpath);
  567 
  568     Xapian::Database outdb(outdbpath);
  569     TEST_EQUAL(outdb.get_doccount(), 0);
  570     dbcheck(outdb, 0, 0);
  571     }
  572 
  573     {
  574     // Check compacting two empty databases together.
  575     Xapian::Database db;
  576     db.add_database(Xapian::Database(empty_dbpath));
  577     db.add_database(Xapian::Database(empty_dbpath));
  578     db.compact(outdbpath);
  579 
  580     Xapian::Database outdb(outdbpath);
  581     TEST_EQUAL(outdb.get_doccount(), 0);
  582     dbcheck(outdb, 0, 0);
  583     }
  584 
  585     return true;
  586 }
  587 
  588 DEFINE_TESTCASE(compactmultipass1, compact && generated) {
  589     string outdbpath = get_compaction_output_path("compactmultipass1");
  590     rm_rf(outdbpath);
  591 
  592     string a = get_database_path("compactnorenumber1a", make_sparse_db,
  593                  "5-7 24 76 987 1023-1027 9999 !9999");
  594     string b = get_database_path("compactnorenumber1b", make_sparse_db,
  595                  "1027-1030");
  596     string c = get_database_path("compactnorenumber1c", make_sparse_db,
  597                  "1028-1040");
  598     string d = get_database_path("compactnorenumber1d", make_sparse_db,
  599                  "3000 999999 !999999");
  600 
  601     {
  602     Xapian::Database db;
  603     db.add_database(Xapian::Database(a));
  604     db.add_database(Xapian::Database(b));
  605     db.add_database(Xapian::Database(c));
  606     db.add_database(Xapian::Database(d));
  607     db.compact(outdbpath, Xapian::DBCOMPACT_MULTIPASS);
  608     }
  609 
  610     Xapian::Database outdb(outdbpath);
  611     dbcheck(outdb, 29, 1041);
  612 
  613     return true;
  614 }
  615 
  616 // Test compacting to an fd.
  617 // Chert doesn't support single file databases.
  618 DEFINE_TESTCASE(compacttofd1, compact && !chert) {
  619     Xapian::Database indb(get_database("apitest_simpledata"));
  620     string outdbpath = get_compaction_output_path("compacttofd1out");
  621     rm_rf(outdbpath);
  622 
  623     int fd = open(outdbpath.c_str(), O_CREAT|O_RDWR|O_BINARY, 0666);
  624     TEST(fd != -1);
  625     indb.compact(fd);
  626 
  627     // Confirm that the fd was closed by Xapian.  Set errno first to workaround
  628     // a bug in Wine's msvcrt.dll which fails to set errno in this case:
  629     // https://bugs.winehq.org/show_bug.cgi?id=43902
  630     errno = EBADF;
  631     {
  632     MSVCIgnoreInvalidParameter invalid_fd_in_close_is_expected;
  633     TEST(close(fd) == -1);
  634     TEST_EQUAL(errno, EBADF);
  635     }
  636 
  637     Xapian::Database outdb(outdbpath);
  638 
  639     TEST_EQUAL(indb.get_doccount(), outdb.get_doccount());
  640     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  641 
  642     return true;
  643 }
  644 
  645 // Test compacting to an fd at at offset.
  646 // Chert doesn't support single file databases.
  647 DEFINE_TESTCASE(compacttofd2, compact && !chert) {
  648     Xapian::Database indb(get_database("apitest_simpledata"));
  649     string outdbpath = get_compaction_output_path("compacttofd2out");
  650     rm_rf(outdbpath);
  651 
  652     int fd = open(outdbpath.c_str(), O_CREAT|O_RDWR|O_BINARY, 0666);
  653     TEST(fd != -1);
  654     TEST(lseek(fd, 8192, SEEK_SET) == 8192);
  655     indb.compact(fd);
  656 
  657     // Confirm that the fd was closed by Xapian.  Set errno first to workaround
  658     // a bug in Wine's msvcrt.dll which fails to set errno in this case:
  659     // https://bugs.winehq.org/show_bug.cgi?id=43902
  660     errno = EBADF;
  661     {
  662     MSVCIgnoreInvalidParameter invalid_fd_in_close_is_expected;
  663     TEST(close(fd) == -1);
  664     TEST_EQUAL(errno, EBADF);
  665     }
  666 
  667     fd = open(outdbpath.c_str(), O_RDONLY|O_BINARY, 0666);
  668     TEST(fd != -1);
  669 
  670     // Test that the database wasn't just written to the start of the file.
  671     char buf[8192];
  672     size_t n = sizeof(buf);
  673     while (n) {
  674     ssize_t c = read(fd, buf, n);
  675     TEST(c > 0);
  676     for (const char * p = buf; p != buf + c; ++p) {
  677         TEST(*p == 0);
  678     }
  679     n -= c;
  680     }
  681 
  682     TEST(lseek(fd, 8192, SEEK_SET) == 8192);
  683     Xapian::Database outdb(fd);
  684 
  685     TEST_EQUAL(indb.get_doccount(), outdb.get_doccount());
  686     dbcheck(outdb, outdb.get_doccount(), outdb.get_doccount());
  687 
  688     return true;
  689 }
  690 
  691 // Regression test for bug fixed in 1.3.5.  If you compact a WritableDatabase
  692 // with uncommitted changes, you get an inconsistent output.
  693 //
  694 // Chert doesn't support single file databases.
  695 DEFINE_TESTCASE(compactsingle1, compact && writable && !chert) {
  696     Xapian::WritableDatabase db = get_writable_database();
  697     Xapian::Document doc;
  698     doc.add_term("foo");
  699     doc.add_term("bar");
  700     doc.add_term("baz");
  701     db.add_document(doc);
  702     // Include a zero-length document as a regression test for a
  703     // Database::check() bug fixed in 1.4.7 (and introduced in 1.4.6).  Test it
  704     // here so we also have test coverage for compaction for such a document.
  705     Xapian::Document doc2;
  706     doc2.add_boolean_term("Kfoo");
  707     db.add_document(doc2);
  708     // Also test a completely empty document.
  709     db.add_document(Xapian::Document());
  710 
  711     string output = get_compaction_output_path("compactsingle1-out");
  712     // In 1.3.4, we would hang if the output file already existed, so check
  713     // that works.
  714     touch(output);
  715 
  716     TEST_EXCEPTION(Xapian::InvalidOperationError,
  717     db.compact(output, Xapian::DBCOMPACT_SINGLE_FILE));
  718 
  719     // Check the file wasn't removed by the failed attempt.
  720     TEST(file_exists(output));
  721 
  722     db.commit();
  723     db.compact(output, Xapian::DBCOMPACT_SINGLE_FILE);
  724     db.close();
  725 
  726     TEST_EQUAL(Xapian::Database::check(output, 0, &tout), 0);
  727 
  728     TEST_EQUAL(Xapian::Database(output).get_doccount(), 3);
  729 
  730     return true;
  731 }
  732 
  733 // Regression test for bug fixed in 1.4.6.  Same as above, except not with
  734 // a single file database!
  735 DEFINE_TESTCASE(compact1, compact && writable) {
  736     Xapian::WritableDatabase db = get_writable_database();
  737     Xapian::Document doc;
  738     doc.add_term("foo");
  739     doc.add_term("bar");
  740     doc.add_term("baz");
  741     db.add_document(doc);
  742     // Include a zero-length document as a regression test for a
  743     // Database::check() bug fixed in 1.4.7 (and introduced in 1.4.6).  Test it
  744     // here so we also have test coverage for compaction for such a document.
  745     Xapian::Document doc2;
  746     doc2.add_boolean_term("Kfoo");
  747     db.add_document(doc2);
  748     // Also test a completely empty document.
  749     db.add_document(Xapian::Document());
  750 
  751     string output = get_compaction_output_path("compact1-out");
  752     rm_rf(output);
  753 
  754     TEST_EXCEPTION(Xapian::InvalidOperationError,
  755     db.compact(output));
  756 
  757     db.commit();
  758     db.compact(output);
  759     db.close();
  760 
  761     TEST_EQUAL(Xapian::Database::check(output, 0, &tout), 0);
  762 
  763     TEST_EQUAL(Xapian::Database(output).get_doccount(), 3);
  764 
  765     return true;
  766 }