1 /* api_replicate.cc: tests of replication functionality
3 * Copyright 2008 Lemur Consulting Ltd
4 * Copyright 2009,2010,2011,2012,2013,2014,2015,2016,2017 Olly Betts
5 * Copyright 2010 Richard Boulton
6 * Copyright 2011 Dan Colish
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.
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.
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
26 #include "api_replicate.h"
29 #include "api/replication.h"
34 #include "filetests.h"
35 #include "safedirent.h"
36 #include "safefcntl.h"
37 #include "safesysstat.h"
38 #include "safeunistd.h"
39 #include "testsuite.h"
40 #include "testutils.h"
43 #include <sys/types.h>
49 #include <stdlib.h> // For setenv() or putenv() or _putenv_s()
53 #ifdef XAPIAN_HAS_REMOTE_BACKEND
55 static void rmtmpdir(const string
& path
) {
59 static void mktmpdir(const string
& path
) {
61 if (mkdir(path
.c_str(), 0700) == -1 && errno
!= EEXIST
) {
62 FAIL_TEST("Can't make temporary directory");
66 static off_t
get_file_size(const string
& path
) {
67 off_t size
= file_size(path
);
69 FAIL_TEST("Can't stat '" << path
<< "'");
74 static size_t do_read(int fd
, char * p
, size_t desired
)
78 ssize_t c
= read(fd
, p
, desired
);
79 if (c
== 0) return total
;
81 if (errno
== EINTR
) continue;
82 FAIL_TEST("Error reading from file");
91 static void do_write(int fd
, const char * p
, size_t n
)
94 ssize_t c
= write(fd
, p
, n
);
96 if (errno
== EINTR
) continue;
97 FAIL_TEST("Error writing to file");
104 // Make a truncated copy of a file.
106 truncated_copy(const string
& srcpath
, const string
& destpath
, off_t tocopy
)
108 FD
fdin(open(srcpath
.c_str(), O_RDONLY
| O_BINARY
));
110 FAIL_TEST("Open failed (when opening '" << srcpath
<< "')");
113 FD
fdout(open(destpath
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| O_BINARY
, 0666));
115 FAIL_TEST("Open failed (when creating '" << destpath
<< "')");
118 const int BUFSIZE
= 1024;
120 size_t total_bytes
= 0;
122 size_t thiscopy
= tocopy
> BUFSIZE
? BUFSIZE
: tocopy
;
123 size_t bytes
= do_read(fdin
, buf
, thiscopy
);
124 if (thiscopy
!= bytes
) {
125 FAIL_TEST("Couldn't read desired number of bytes from changeset");
128 total_bytes
+= bytes
;
129 do_write(fdout
, buf
, bytes
);
132 if (close(fdout
) == -1)
133 FAIL_TEST("Error closing file");
139 get_changeset(const string
& changesetpath
,
140 Xapian::DatabaseMaster
& master
,
141 Xapian::DatabaseReplica
& replica
,
142 int expected_changesets
,
143 int expected_fullcopies
,
144 bool expected_changed
,
145 bool full_copy
= false)
147 FD
fd(open(changesetpath
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| O_BINARY
, 0666));
149 FAIL_TEST("Open failed (when creating a new changeset file at '"
150 << changesetpath
<< "')");
152 Xapian::ReplicationInfo info1
;
153 master
.write_changesets_to_fd(fd
,
154 full_copy
? "" : replica
.get_revision_info(),
157 TEST_EQUAL(info1
.changeset_count
, expected_changesets
);
158 TEST_EQUAL(info1
.fullcopy_count
, expected_fullcopies
);
159 TEST_EQUAL(info1
.changed
, expected_changed
);
163 apply_changeset(const string
& changesetpath
,
164 Xapian::DatabaseReplica
& replica
,
165 int expected_changesets
,
166 int expected_fullcopies
,
167 bool expected_changed
)
169 FD
fd(open(changesetpath
.c_str(), O_RDONLY
| O_BINARY
));
171 FAIL_TEST("Open failed (when reading changeset file at '"
172 << changesetpath
<< "')");
176 replica
.set_read_fd(fd
);
177 Xapian::ReplicationInfo info1
;
178 Xapian::ReplicationInfo info2
;
179 bool client_changed
= false;
180 while (replica
.apply_next_changeset(&info2
, 0)) {
182 info1
.changeset_count
+= info2
.changeset_count
;
183 info1
.fullcopy_count
+= info2
.fullcopy_count
;
185 client_changed
= true;
187 info1
.changeset_count
+= info2
.changeset_count
;
188 info1
.fullcopy_count
+= info2
.fullcopy_count
;
190 client_changed
= true;
192 TEST_EQUAL(info1
.changeset_count
, expected_changesets
);
193 TEST_EQUAL(info1
.fullcopy_count
, expected_fullcopies
);
194 TEST_EQUAL(client_changed
, expected_changed
);
198 // Replicate from the master to the replica.
199 // Returns the number of changesets which were applied.
201 replicate(Xapian::DatabaseMaster
& master
,
202 Xapian::DatabaseReplica
& replica
,
203 const string
& tempdir
,
204 int expected_changesets
,
205 int expected_fullcopies
,
206 bool expected_changed
,
207 bool full_copy
= false)
209 string changesetpath
= tempdir
+ "/changeset";
210 get_changeset(changesetpath
, master
, replica
,
215 return apply_changeset(changesetpath
, replica
,
221 // Check that the databases held at the given path are identical.
223 check_equal_dbs(const string
& masterpath
, const string
& replicapath
)
225 Xapian::Database
master(masterpath
);
226 Xapian::Database
replica(replicapath
);
228 TEST_EQUAL(master
.get_uuid(), master
.get_uuid());
229 dbcheck(replica
, master
.get_doccount(), master
.get_lastdocid());
231 for (Xapian::TermIterator t
= master
.allterms_begin();
232 t
!= master
.allterms_end(); ++t
) {
233 TEST_EQUAL(postlist_to_string(master
, *t
),
234 postlist_to_string(replica
, *t
));
238 #if 0 // Dynamic version which we don't currently need.
240 set_max_changesets(int count
) {
241 #if HAVE_DECL__PUTENV_S
242 _putenv_s("XAPIAN_MAX_CHANGESETS", str(count
).c_str());
243 #elif defined HAVE_SETENV
244 setenv("XAPIAN_MAX_CHANGESETS", str(count
).c_str(), 1);
246 static char buf
[64] = "XAPIAN_MAX_CHANGESETS=";
247 sprintf(buf
+ CONST_STRLEN("XAPIAN_MAX_CHANGESETS="), "%d", count
);
253 #if HAVE_DECL__PUTENV_S
254 # define set_max_changesets(N) _putenv_s("XAPIAN_MAX_CHANGESETS", #N)
255 #elif defined HAVE_SETENV
256 # define set_max_changesets(N) setenv("XAPIAN_MAX_CHANGESETS", #N, 1)
258 # define set_max_changesets(N) putenv(const_cast<char*>("XAPIAN_MAX_CHANGESETS="#N))
261 struct unset_max_changesets_helper_
{
262 unset_max_changesets_helper_() { }
263 ~unset_max_changesets_helper_() { set_max_changesets(0); }
266 // Ensure that we don't leave generation of changesets on for the next
267 // testcase, even if this one exits with an exception.
268 #define UNSET_MAX_CHANGESETS_AFTERWARDS unset_max_changesets_helper_ ezlxq
272 // #######################################################################
273 // # Tests start here
275 // Basic test of replication functionality.
276 DEFINE_TESTCASE(replicate1
, replicas
) {
277 #ifdef XAPIAN_HAS_REMOTE_BACKEND
278 UNSET_MAX_CHANGESETS_AFTERWARDS
;
279 string tempdir
= ".replicatmp";
281 string masterpath
= get_named_writable_database_path("master");
283 set_max_changesets(10);
285 Xapian::Document doc1
;
286 doc1
.set_data(string("doc1"));
287 doc1
.add_posting("doc", 1);
288 doc1
.add_posting("one", 1);
290 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
291 Xapian::DatabaseMaster
master(masterpath
);
292 string replicapath
= tempdir
+ "/replica";
294 Xapian::DatabaseReplica
replica(replicapath
);
296 // Add a document to the original database.
297 orig
.add_document(doc1
);
300 // Apply the replication - we don't have changesets stored, so this
301 // should just do a database copy, and return a count of 1.
302 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
303 TEST_EQUAL(count
, 1);
305 Xapian::Database
dbcopy(replicapath
);
306 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
309 // Repeating the replication should return a count of 1, since no
310 // further changes should need to be applied.
311 count
= replicate(master
, replica
, tempdir
, 0, 0, false);
312 TEST_EQUAL(count
, 1);
314 Xapian::Database
dbcopy(replicapath
);
315 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
319 // Regression test - if the replica was reopened, a full copy always
320 // used to occur, whether it was needed or not. Fixed in revision
322 Xapian::DatabaseReplica
replica(replicapath
);
323 int count
= replicate(master
, replica
, tempdir
, 0, 0, false);
324 TEST_EQUAL(count
, 1);
326 Xapian::Database
dbcopy(replicapath
);
327 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
330 orig
.add_document(doc1
);
332 orig
.add_document(doc1
);
335 count
= replicate(master
, replica
, tempdir
, 2, 0, true);
336 TEST_EQUAL(count
, 3);
338 Xapian::Database
dbcopy(replicapath
);
339 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
342 check_equal_dbs(masterpath
, replicapath
);
344 // We need this inner scope to we close the replica before we remove
345 // the temporary directory on Windows.
353 // Test replication from a replicated copy.
354 DEFINE_TESTCASE(replicate2
, replicas
) {
355 #ifdef XAPIAN_HAS_REMOTE_BACKEND
356 SKIP_TEST_FOR_BACKEND("glass"); // Glass doesn't currently support this.
357 UNSET_MAX_CHANGESETS_AFTERWARDS
;
359 string tempdir
= ".replicatmp";
361 string masterpath
= get_named_writable_database_path("master");
363 set_max_changesets(10);
366 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
367 Xapian::DatabaseMaster
master(masterpath
);
368 string replicapath
= tempdir
+ "/replica";
369 Xapian::DatabaseReplica
replica(replicapath
);
371 Xapian::DatabaseMaster
master2(replicapath
);
372 string replica2path
= tempdir
+ "/replica2";
373 Xapian::DatabaseReplica
replica2(replica2path
);
375 // Add a document to the original database.
376 Xapian::Document doc1
;
377 doc1
.set_data(string("doc1"));
378 doc1
.add_posting("doc", 1);
379 doc1
.add_posting("one", 1);
380 orig
.add_document(doc1
);
383 // Apply the replication - we don't have changesets stored, so this
384 // should just do a database copy, and return a count of 1.
385 TEST_EQUAL(replicate(master
, replica
, tempdir
, 0, 1, true), 1);
386 check_equal_dbs(masterpath
, replicapath
);
388 // Replicate from the replica.
389 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 0, 1, true), 1);
390 check_equal_dbs(masterpath
, replica2path
);
392 orig
.add_document(doc1
);
394 orig
.add_document(doc1
);
397 // Replicate from the replica - should have no changes.
398 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 0, 0, false), 1);
399 check_equal_dbs(replicapath
, replica2path
);
401 // Replicate, and replicate from the replica - should have 2 changes.
402 TEST_EQUAL(replicate(master
, replica
, tempdir
, 2, 0, 1), 3);
403 check_equal_dbs(masterpath
, replicapath
);
404 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 2, 0, 1), 3);
405 check_equal_dbs(masterpath
, replica2path
);
407 // Stop writing changesets, and make a modification
408 set_max_changesets(0);
410 orig
= get_writable_database_again();
411 orig
.add_document(doc1
);
414 // Replication should do a full copy.
415 TEST_EQUAL(replicate(master
, replica
, tempdir
, 0, 1, true), 1);
416 check_equal_dbs(masterpath
, replicapath
);
417 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 0, 1, true), 1);
418 check_equal_dbs(masterpath
, replica2path
);
420 // Start writing changesets, but only keep 1 in history, and make a
422 set_max_changesets(1);
424 orig
= get_writable_database_again();
425 orig
.add_document(doc1
);
428 // Replicate, and replicate from the replica - should have 1 changes.
429 TEST_EQUAL(replicate(master
, replica
, tempdir
, 1, 0, 1), 2);
430 check_equal_dbs(masterpath
, replicapath
);
431 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 1, 0, 1), 2);
432 check_equal_dbs(masterpath
, replica2path
);
434 // Make two changes - only one changeset should be preserved.
435 orig
.add_document(doc1
);
438 // Replication should do a full copy, since one of the needed
439 // changesets is missing.
441 // FIXME - the following tests are commented out because the backends
442 // don't currently tidy up old changesets correctly.
443 // TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, true), 1);
444 // check_equal_dbs(masterpath, replicapath);
445 // TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 1, true), 1);
446 // check_equal_dbs(masterpath, replica2path);
448 // We need this inner scope to we close the replicas before we remove
449 // the temporary directory on Windows.
457 #ifdef XAPIAN_HAS_REMOTE_BACKEND
459 replicate_with_brokenness(Xapian::DatabaseMaster
& master
,
460 Xapian::DatabaseReplica
& replica
,
461 const string
& tempdir
,
462 int expected_changesets
,
463 int expected_fullcopies
,
464 bool expected_changed
)
466 string changesetpath
= tempdir
+ "/changeset";
467 get_changeset(changesetpath
, master
, replica
,
470 // Try applying truncated changesets of various different lengths.
471 string brokenchangesetpath
= tempdir
+ "/changeset_broken";
472 off_t filesize
= get_file_size(changesetpath
);
475 while (len
< filesize
) {
476 copylen
= truncated_copy(changesetpath
, brokenchangesetpath
, len
);
477 TEST_EQUAL(copylen
, len
);
478 tout
<< "Trying replication with a changeset truncated to " << len
<<
479 " bytes, from " << filesize
<< " bytes\n";
480 TEST_EXCEPTION(Xapian::NetworkError
,
481 apply_changeset(brokenchangesetpath
, replica
,
482 expected_changesets
, expected_fullcopies
,
484 if (len
< 30 || len
>= filesize
- 10) {
485 // For lengths near the beginning and end, increment size by 1
488 // Don't bother incrementing by small amounts in the middle of
491 if (len
>= filesize
- 10) {
499 // Test changesets which are truncated (and therefore invalid).
500 DEFINE_TESTCASE(replicate3
, replicas
) {
501 #ifdef XAPIAN_HAS_REMOTE_BACKEND
502 UNSET_MAX_CHANGESETS_AFTERWARDS
;
503 string tempdir
= ".replicatmp";
505 string masterpath
= get_named_writable_database_path("master");
507 set_max_changesets(10);
510 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
511 Xapian::DatabaseMaster
master(masterpath
);
512 string replicapath
= tempdir
+ "/replica";
513 Xapian::DatabaseReplica
replica(replicapath
);
515 // Add a document to the original database.
516 Xapian::Document doc1
;
517 doc1
.set_data(string("doc1"));
518 doc1
.add_posting("doc", 1);
519 doc1
.add_posting("one", 1);
520 orig
.add_document(doc1
);
523 TEST_EQUAL(replicate(master
, replica
, tempdir
, 0, 1, true), 1);
524 check_equal_dbs(masterpath
, replicapath
);
527 orig
.add_document(doc1
);
530 replicate_with_brokenness(master
, replica
, tempdir
, 1, 0, true);
531 // Although it throws an error, the final replication in
532 // replicate_with_brokenness() updates the database, since it's just
533 // the end-of-replication message which is missing its body.
534 check_equal_dbs(masterpath
, replicapath
);
536 // Check that the earlier broken replications didn't cause any problems
537 // for the next replication.
538 orig
.add_document(doc1
);
540 TEST_EQUAL(replicate(master
, replica
, tempdir
, 1, 0, true), 2);
542 // We need this inner scope to we close the replica before we remove
543 // the temporary directory on Windows.
551 // Tests for max_changesets
552 DEFINE_TESTCASE(replicate4
, replicas
) {
553 #ifdef XAPIAN_HAS_REMOTE_BACKEND
554 UNSET_MAX_CHANGESETS_AFTERWARDS
;
555 string tempdir
= ".replicatmp";
557 string masterpath
= get_named_writable_database_path("master");
559 set_max_changesets(1);
562 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
563 Xapian::DatabaseMaster
master(masterpath
);
564 string replicapath
= tempdir
+ "/replica";
565 Xapian::DatabaseReplica
replica(replicapath
);
567 // Add a document with no positions to the original database.
568 Xapian::Document doc1
;
569 doc1
.set_data(string("doc1"));
570 doc1
.add_term("nopos");
571 orig
.add_document(doc1
);
574 // Apply the replication - we don't have changesets stored, so this
575 // should just do a database copy, and return a count of 1.
576 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
577 TEST_EQUAL(count
, 1);
579 Xapian::Database
dbcopy(replicapath
);
580 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
583 // Add a document with positional information to the original database.
584 doc1
.add_posting("pos", 1);
585 orig
.add_document(doc1
);
588 // Replicate, and check that we have the positional information.
589 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
590 TEST_EQUAL(count
, 2);
592 Xapian::Database
dbcopy(replicapath
);
593 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
595 check_equal_dbs(masterpath
, replicapath
);
597 // Add a document with no positions to the original database.
598 Xapian::Document doc2
;
599 doc2
.set_data(string("doc2"));
600 doc2
.add_term("nopos");
601 orig
.add_document(doc2
);
602 if (get_dbtype() == "glass") {
603 // FIXME: Needs to be pre-commit for glass
604 set_max_changesets(0);
608 // Replicate, and check that we have the positional information.
609 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
610 TEST_EQUAL(count
, 2);
612 Xapian::Database
dbcopy(replicapath
);
613 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
615 check_equal_dbs(masterpath
, replicapath
);
616 TEST(!file_exists(masterpath
+ "/changes1"));
618 // Turn off replication, make sure we dont write anything
619 if (get_dbtype() != "glass") {
620 set_max_changesets(0);
623 // Add a document with no positions to the original database.
624 Xapian::Document doc3
;
625 doc3
.set_data(string("doc3"));
626 doc3
.add_term("nonopos");
627 orig
.add_document(doc3
);
630 // Replicate, and check that we have the positional information.
631 count
= replicate(master
, replica
, tempdir
, 0, 1, true);
632 TEST_EQUAL(count
, 1);
634 Xapian::Database
dbcopy(replicapath
);
635 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
637 // Should have pulled a full copy
638 check_equal_dbs(masterpath
, replicapath
);
639 TEST(!file_exists(masterpath
+ "/changes3"));
641 // We need this inner scope to we close the replica before we remove
642 // the temporary directory on Windows.
650 // Tests for max_changesets
651 DEFINE_TESTCASE(replicate5
, replicas
) {
652 #ifdef XAPIAN_HAS_REMOTE_BACKEND
653 UNSET_MAX_CHANGESETS_AFTERWARDS
;
654 string tempdir
= ".replicatmp";
656 string masterpath
= get_named_writable_database_path("master");
658 set_max_changesets(2);
661 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
662 Xapian::DatabaseMaster
master(masterpath
);
663 string replicapath
= tempdir
+ "/replica";
664 Xapian::DatabaseReplica
replica(replicapath
);
666 // Add a document with no positions to the original database.
667 Xapian::Document doc1
;
668 doc1
.set_data(string("doc1"));
669 doc1
.add_term("nopos");
670 orig
.add_document(doc1
);
673 // Apply the replication - we don't have changesets stored, so this
674 // should just do a database copy, and return a count of 1.
675 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
676 TEST_EQUAL(count
, 1);
678 Xapian::Database
dbcopy(replicapath
);
679 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
682 // Add a document with positional information to the original database.
683 doc1
.add_posting("pos", 1);
684 orig
.add_document(doc1
);
687 // Replicate, and check that we have the positional information.
688 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
689 TEST_EQUAL(count
, 2);
691 Xapian::Database
dbcopy(replicapath
);
692 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
694 check_equal_dbs(masterpath
, replicapath
);
696 // Add a document with no positions to the original database.
697 Xapian::Document doc2
;
698 doc2
.set_data(string("doc2"));
699 doc2
.add_term("nopos");
700 orig
.add_document(doc2
);
703 // Replicate, and check that we have the positional information.
704 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
705 TEST_EQUAL(count
, 2);
707 Xapian::Database
dbcopy(replicapath
);
708 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
710 check_equal_dbs(masterpath
, replicapath
);
712 // Add a document with no positions to the original database.
713 Xapian::Document doc3
;
714 doc3
.set_data(string("doc3"));
715 doc3
.add_term("nonopos");
716 orig
.add_document(doc3
);
719 // Replicate, and check that we have the positional information.
720 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
721 TEST_EQUAL(count
, 2);
723 Xapian::Database
dbcopy(replicapath
);
724 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
726 check_equal_dbs(masterpath
, replicapath
);
728 // Ensure that only these changesets exists
729 TEST(!file_exists(masterpath
+ "/changes1"));
730 TEST(file_exists(masterpath
+ "/changes2"));
731 TEST(file_exists(masterpath
+ "/changes3"));
733 set_max_changesets(3);
734 masterpath
= get_named_writable_database_path("master");
736 // Add a document with no positions to the original database.
737 Xapian::Document doc4
;
738 doc4
.set_data(string("doc4"));
739 doc4
.add_term("nononopos");
740 orig
.add_document(doc4
);
743 // Replicate, and check that we have the positional information.
744 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
745 TEST_EQUAL(count
, 2);
747 Xapian::Database
dbcopy(replicapath
);
748 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
750 check_equal_dbs(masterpath
, replicapath
);
752 // Add a document with no positions to the original database.
753 Xapian::Document doc5
;
754 doc5
.set_data(string("doc5"));
755 doc5
.add_term("nonononopos");
756 orig
.add_document(doc5
);
759 // Replicate, and check that we have the positional information.
760 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
761 TEST_EQUAL(count
, 2);
763 Xapian::Database
dbcopy(replicapath
);
764 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
766 check_equal_dbs(masterpath
, replicapath
);
768 TEST(!file_exists(masterpath
+ "/changes2"));
769 TEST(file_exists(masterpath
+ "/changes3"));
770 TEST(file_exists(masterpath
+ "/changes4"));
771 TEST(file_exists(masterpath
+ "/changes5"));
773 // We need this inner scope to we close the replica before we remove
774 // the temporary directory on Windows.
782 /// Test --full-copy option.
783 DEFINE_TESTCASE(replicate6
, replicas
) {
784 #ifdef XAPIAN_HAS_REMOTE_BACKEND
785 UNSET_MAX_CHANGESETS_AFTERWARDS
;
786 string tempdir
= ".replicatmp";
788 string masterpath
= get_named_writable_database_path("master");
790 set_max_changesets(10);
793 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
794 Xapian::DatabaseMaster
master(masterpath
);
795 string replicapath
= tempdir
+ "/replica";
796 Xapian::DatabaseReplica
replica(replicapath
);
798 // Add a document to the original database.
799 Xapian::Document doc1
;
800 doc1
.set_data(string("doc1"));
801 doc1
.add_posting("doc", 1);
802 doc1
.add_posting("one", 1);
803 orig
.add_document(doc1
);
806 rm_rf(masterpath
+ "1");
807 cp_R(masterpath
, masterpath
+ "1");
809 orig
.add_document(doc1
);
812 // Apply the replication - we don't have changesets stored, so this
813 // should just do a database copy, and return a count of 1.
814 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
815 TEST_EQUAL(count
, 1);
817 Xapian::Database
dbcopy(replicapath
);
818 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
821 Xapian::DatabaseMaster
master1(masterpath
+ "1");
823 // Try to replicate an older version of the master.
824 count
= replicate(master1
, replica
, tempdir
, 0, 0, false);
825 TEST_EQUAL(count
, 1);
827 // Force a full copy.
828 count
= replicate(master1
, replica
, tempdir
, 0, 1, true, true);
829 TEST_EQUAL(count
, 1);
831 // Test we can still replicate.
832 orig
.add_document(doc1
);
835 count
= replicate(master
, replica
, tempdir
, 2, 0, true);
836 TEST_EQUAL(count
, 3);
838 check_equal_dbs(masterpath
, replicapath
);
840 // We need this inner scope to we close the replica before we remove
841 // the temporary directory on Windows.
849 /// Test healing a corrupt replica (new in 1.3.5).
850 DEFINE_TESTCASE(replicate7
, replicas
) {
851 #ifdef XAPIAN_HAS_REMOTE_BACKEND
852 UNSET_MAX_CHANGESETS_AFTERWARDS
;
853 string tempdir
= ".replicatmp";
855 string masterpath
= get_named_writable_database_path("master");
857 set_max_changesets(10);
859 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
860 Xapian::DatabaseMaster
master(masterpath
);
861 string replicapath
= tempdir
+ "/replica";
863 Xapian::DatabaseReplica
replica(replicapath
);
865 // Add a document to the original database.
866 Xapian::Document doc1
;
867 doc1
.set_data(string("doc1"));
868 doc1
.add_posting("doc", 1);
869 doc1
.add_posting("one", 1);
870 orig
.add_document(doc1
);
873 orig
.add_document(doc1
);
876 // Apply the replication - we don't have changesets stored, so this
877 // should just do a database copy, and return a count of 1.
878 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
879 TEST_EQUAL(count
, 1);
881 Xapian::Database
dbcopy(replicapath
);
882 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
887 // Corrupt replica by truncating all the files to size 0.
888 string d
= replicapath
;
890 DIR * dir
= opendir(d
.c_str());
894 struct dirent
* entry
= readdir(dir
);
898 FAIL_TEST("readdir failed: " << strerror(errno
));
901 // Skip '.' and '..'.
902 if (entry
->d_name
[0] == '.') continue;
906 file
+= entry
->d_name
;
907 int fd
= open(file
.c_str(), O_WRONLY
|O_TRUNC
, 0666);
909 TEST(close(fd
) == 0);
915 Xapian::DatabaseReplica
replica(replicapath
);
917 // Replication should succeed and perform a full copy.
918 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
919 TEST_EQUAL(count
, 1);
921 check_equal_dbs(masterpath
, replicapath
);
923 // We need this inner scope to we close the replica before we remove
924 // the temporary directory on Windows.