2 * @brief tests of replication functionality
4 /* Copyright 2008 Lemur Consulting Ltd
5 * Copyright 2009-2022 Olly Betts
6 * Copyright 2010 Richard Boulton
7 * Copyright 2011 Dan Colish
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
27 #include "api_replicate.h"
30 #include "api/replication.h"
34 #include "errno_to_string.h"
36 #include "filetests.h"
37 #include "safedirent.h"
38 #include "safefcntl.h"
39 #include "safesysstat.h"
40 #include "safeunistd.h"
42 #include "testsuite.h"
43 #include "testutils.h"
46 #include <sys/types.h>
54 #ifdef XAPIAN_HAS_REMOTE_BACKEND
56 static void rmtmpdir(const string
& path
) {
60 static void mktmpdir(const string
& path
) {
62 if (mkdir(path
.c_str(), 0700) == -1 && errno
!= EEXIST
) {
63 FAIL_TEST("Can't make temporary directory");
67 static size_t do_read(int fd
, char * p
, size_t desired
)
71 ssize_t c
= read(fd
, p
, desired
);
72 if (c
== 0) return total
;
74 if (errno
== EINTR
) continue;
75 FAIL_TEST("Error reading from file");
84 static void do_write(int fd
, const char * p
, size_t n
)
87 ssize_t c
= write(fd
, p
, n
);
89 if (errno
== EINTR
) continue;
90 FAIL_TEST("Error writing to file");
97 // Make a truncated copy of a file.
99 truncated_copy(const string
& srcpath
,
100 const string
& destpath
,
101 file_size_type tocopy
)
103 FD
fdin(open(srcpath
.c_str(), O_RDONLY
| O_BINARY
));
105 FAIL_TEST("Open failed (when opening '" << srcpath
<< "')");
108 FD
fdout(open(destpath
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| O_BINARY
, 0666));
110 FAIL_TEST("Open failed (when creating '" << destpath
<< "')");
113 const int BUFSIZE
= 1024;
115 size_t total_bytes
= 0;
117 size_t thiscopy
= tocopy
> BUFSIZE
? BUFSIZE
: tocopy
;
118 size_t bytes
= do_read(fdin
, buf
, thiscopy
);
119 if (thiscopy
!= bytes
) {
120 FAIL_TEST("Couldn't read desired number of bytes from changeset");
123 total_bytes
+= bytes
;
124 do_write(fdout
, buf
, bytes
);
127 if (close(fdout
) == -1)
128 FAIL_TEST("Error closing file");
134 get_changeset(const string
& changesetpath
,
135 Xapian::DatabaseMaster
& master
,
136 Xapian::DatabaseReplica
& replica
,
137 int expected_changesets
,
138 int expected_fullcopies
,
139 bool expected_changed
,
140 bool full_copy
= false)
142 FD
fd(open(changesetpath
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| O_BINARY
, 0666));
144 FAIL_TEST("Open failed (when creating a new changeset file at '"
145 << changesetpath
<< "')");
147 Xapian::ReplicationInfo info1
;
148 master
.write_changesets_to_fd(fd
,
149 full_copy
? "" : replica
.get_revision_info(),
152 TEST_EQUAL(info1
.changeset_count
, expected_changesets
);
153 TEST_EQUAL(info1
.fullcopy_count
, expected_fullcopies
);
154 TEST_EQUAL(info1
.changed
, expected_changed
);
158 apply_changeset(const string
& changesetpath
,
159 Xapian::DatabaseReplica
& replica
,
160 int expected_changesets
,
161 int expected_fullcopies
,
162 bool expected_changed
)
164 FD
fd(open(changesetpath
.c_str(), O_RDONLY
| O_BINARY
));
166 FAIL_TEST("Open failed (when reading changeset file at '"
167 << changesetpath
<< "')");
171 replica
.set_read_fd(fd
);
172 Xapian::ReplicationInfo info1
;
173 Xapian::ReplicationInfo info2
;
174 bool client_changed
= false;
175 while (replica
.apply_next_changeset(&info2
, 0)) {
177 info1
.changeset_count
+= info2
.changeset_count
;
178 info1
.fullcopy_count
+= info2
.fullcopy_count
;
180 client_changed
= true;
182 info1
.changeset_count
+= info2
.changeset_count
;
183 info1
.fullcopy_count
+= info2
.fullcopy_count
;
185 client_changed
= true;
187 TEST_EQUAL(info1
.changeset_count
, expected_changesets
);
188 TEST_EQUAL(info1
.fullcopy_count
, expected_fullcopies
);
189 TEST_EQUAL(client_changed
, expected_changed
);
193 // Replicate from the master to the replica.
194 // Returns the number of changesets which were applied.
196 replicate(Xapian::DatabaseMaster
& master
,
197 Xapian::DatabaseReplica
& replica
,
198 const string
& tempdir
,
199 int expected_changesets
,
200 int expected_fullcopies
,
201 bool expected_changed
,
202 bool full_copy
= false)
204 string changesetpath
= tempdir
+ "/changeset";
205 get_changeset(changesetpath
, master
, replica
,
210 return apply_changeset(changesetpath
, replica
,
216 // Check that the databases held at the given path are identical.
218 check_equal_dbs(const string
& masterpath
, const string
& replicapath
)
220 Xapian::Database
master(masterpath
);
221 Xapian::Database
replica(replicapath
);
223 TEST_EQUAL(master
.get_uuid(), master
.get_uuid());
224 dbcheck(replica
, master
.get_doccount(), master
.get_lastdocid());
226 for (Xapian::TermIterator t
= master
.allterms_begin();
227 t
!= master
.allterms_end(); ++t
) {
228 TEST_EQUAL(postlist_to_string(master
, *t
),
229 postlist_to_string(replica
, *t
));
233 #define set_max_changesets(N) setenv("XAPIAN_MAX_CHANGESETS", #N, 1)
235 struct unset_max_changesets_helper_
{
236 unset_max_changesets_helper_() { }
237 ~unset_max_changesets_helper_() { set_max_changesets(0); }
240 // Ensure that we don't leave generation of changesets on for the next
241 // testcase, even if this one exits with an exception.
242 #define UNSET_MAX_CHANGESETS_AFTERWARDS unset_max_changesets_helper_ ezlxq
246 // #######################################################################
247 // # Tests start here
249 // Basic test of replication functionality.
250 DEFINE_TESTCASE(replicate1
, replicas
) {
251 #ifdef XAPIAN_HAS_REMOTE_BACKEND
252 UNSET_MAX_CHANGESETS_AFTERWARDS
;
253 string tempdir
= ".replicatmp";
255 string masterpath
= get_named_writable_database_path("master");
257 set_max_changesets(10);
259 Xapian::Document doc1
;
260 doc1
.set_data(string("doc1"));
261 doc1
.add_posting("doc", 1);
262 doc1
.add_posting("one", 1);
264 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
265 Xapian::DatabaseMaster
master(masterpath
);
266 string replicapath
= tempdir
+ "/replica";
268 Xapian::DatabaseReplica
replica(replicapath
);
270 // Add a document to the original database.
271 orig
.add_document(doc1
);
274 // Apply the replication - we don't have changesets stored, so this
275 // should just do a database copy, and return a count of 1.
276 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
277 TEST_EQUAL(count
, 1);
279 Xapian::Database
dbcopy(replicapath
);
280 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
283 // Repeating the replication should return a count of 1, since no
284 // further changes should need to be applied.
285 count
= replicate(master
, replica
, tempdir
, 0, 0, false);
286 TEST_EQUAL(count
, 1);
288 Xapian::Database
dbcopy(replicapath
);
289 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
293 // Regression test - if the replica was reopened, a full copy always
294 // used to occur, whether it was needed or not. Fixed in revision
296 Xapian::DatabaseReplica
replica(replicapath
);
297 int count
= replicate(master
, replica
, tempdir
, 0, 0, false);
298 TEST_EQUAL(count
, 1);
300 Xapian::Database
dbcopy(replicapath
);
301 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
304 orig
.add_document(doc1
);
306 orig
.add_document(doc1
);
309 count
= replicate(master
, replica
, tempdir
, 2, 0, true);
310 TEST_EQUAL(count
, 3);
312 Xapian::Database
dbcopy(replicapath
);
313 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
316 check_equal_dbs(masterpath
, replicapath
);
318 // We need this inner scope to we close the replica before we remove
319 // the temporary directory on Windows.
322 TEST_EQUAL(Xapian::Database::check(masterpath
), 0);
328 // Test replication from a replicated copy.
329 DEFINE_TESTCASE(replicate2
, replicas
) {
330 #ifdef XAPIAN_HAS_REMOTE_BACKEND
331 SKIP_TEST_FOR_BACKEND("glass"); // Glass doesn't currently support this.
332 UNSET_MAX_CHANGESETS_AFTERWARDS
;
334 string tempdir
= ".replicatmp";
336 string masterpath
= get_named_writable_database_path("master");
338 set_max_changesets(10);
341 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
342 Xapian::DatabaseMaster
master(masterpath
);
343 string replicapath
= tempdir
+ "/replica";
344 Xapian::DatabaseReplica
replica(replicapath
);
346 Xapian::DatabaseMaster
master2(replicapath
);
347 string replica2path
= tempdir
+ "/replica2";
348 Xapian::DatabaseReplica
replica2(replica2path
);
350 // Add a document to the original database.
351 Xapian::Document doc1
;
352 doc1
.set_data(string("doc1"));
353 doc1
.add_posting("doc", 1);
354 doc1
.add_posting("one", 1);
355 orig
.add_document(doc1
);
358 // Apply the replication - we don't have changesets stored, so this
359 // should just do a database copy, and return a count of 1.
360 TEST_EQUAL(replicate(master
, replica
, tempdir
, 0, 1, true), 1);
361 check_equal_dbs(masterpath
, replicapath
);
363 // Replicate from the replica.
364 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 0, 1, true), 1);
365 check_equal_dbs(masterpath
, replica2path
);
367 orig
.add_document(doc1
);
369 orig
.add_document(doc1
);
372 // Replicate from the replica - should have no changes.
373 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 0, 0, false), 1);
374 check_equal_dbs(replicapath
, replica2path
);
376 // Replicate, and replicate from the replica - should have 2 changes.
377 TEST_EQUAL(replicate(master
, replica
, tempdir
, 2, 0, 1), 3);
378 check_equal_dbs(masterpath
, replicapath
);
379 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 2, 0, 1), 3);
380 check_equal_dbs(masterpath
, replica2path
);
382 // Stop writing changesets, and make a modification
383 set_max_changesets(0);
385 orig
= get_writable_database_again();
386 orig
.add_document(doc1
);
389 // Replication should do a full copy.
390 TEST_EQUAL(replicate(master
, replica
, tempdir
, 0, 1, true), 1);
391 check_equal_dbs(masterpath
, replicapath
);
392 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 0, 1, true), 1);
393 check_equal_dbs(masterpath
, replica2path
);
395 // Start writing changesets, but only keep 1 in history, and make a
397 set_max_changesets(1);
399 orig
= get_writable_database_again();
400 orig
.add_document(doc1
);
403 // Replicate, and replicate from the replica - should have 1 changes.
404 TEST_EQUAL(replicate(master
, replica
, tempdir
, 1, 0, 1), 2);
405 check_equal_dbs(masterpath
, replicapath
);
406 TEST_EQUAL(replicate(master2
, replica2
, tempdir
, 1, 0, 1), 2);
407 check_equal_dbs(masterpath
, replica2path
);
409 // Make two changes - only one changeset should be preserved.
410 orig
.add_document(doc1
);
413 // Replication should do a full copy, since one of the needed
414 // changesets is missing.
416 // FIXME - the following tests are commented out because the backends
417 // don't currently tidy up old changesets correctly.
418 // TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, true), 1);
419 // check_equal_dbs(masterpath, replicapath);
420 // TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 1, true), 1);
421 // check_equal_dbs(masterpath, replica2path);
423 // We need this inner scope to we close the replicas before we remove
424 // the temporary directory on Windows.
431 #ifdef XAPIAN_HAS_REMOTE_BACKEND
433 replicate_with_brokenness(Xapian::DatabaseMaster
& master
,
434 Xapian::DatabaseReplica
& replica
,
435 const string
& tempdir
,
436 int expected_changesets
,
437 int expected_fullcopies
,
438 bool expected_changed
)
440 string changesetpath
= tempdir
+ "/changeset";
441 get_changeset(changesetpath
, master
, replica
,
444 // Try applying truncated changesets of various different lengths.
445 string brokenchangesetpath
= tempdir
+ "/changeset_broken";
446 auto filesize
= file_size(changesetpath
);
448 FAIL_TEST("Can't stat '" << changesetpath
<< "'");
450 file_size_type len
= 10;
451 file_size_type copylen
;
452 while (len
< filesize
) {
453 copylen
= truncated_copy(changesetpath
, brokenchangesetpath
, len
);
454 TEST_EQUAL(copylen
, len
);
455 tout
<< "Trying replication with a changeset truncated to " << len
<<
456 " bytes, from " << filesize
<< " bytes\n";
457 TEST_EXCEPTION(Xapian::NetworkError
,
458 apply_changeset(brokenchangesetpath
, replica
,
459 expected_changesets
, expected_fullcopies
,
461 if (len
< 30 || len
>= filesize
- 10) {
462 // For lengths near the beginning and end, increment size by 1
465 // Don't bother incrementing by small amounts in the middle of
468 if (len
>= filesize
- 10) {
476 // Test changesets which are truncated (and therefore invalid).
477 DEFINE_TESTCASE(replicate3
, replicas
) {
478 #ifdef XAPIAN_HAS_REMOTE_BACKEND
479 UNSET_MAX_CHANGESETS_AFTERWARDS
;
480 string tempdir
= ".replicatmp";
482 string masterpath
= get_named_writable_database_path("master");
484 set_max_changesets(10);
487 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
488 Xapian::DatabaseMaster
master(masterpath
);
489 string replicapath
= tempdir
+ "/replica";
490 Xapian::DatabaseReplica
replica(replicapath
);
492 // Add a document to the original database.
493 Xapian::Document doc1
;
494 doc1
.set_data(string("doc1"));
495 doc1
.add_posting("doc", 1);
496 doc1
.add_posting("one", 1);
497 orig
.add_document(doc1
);
500 TEST_EQUAL(replicate(master
, replica
, tempdir
, 0, 1, true), 1);
501 check_equal_dbs(masterpath
, replicapath
);
504 orig
.add_document(doc1
);
507 replicate_with_brokenness(master
, replica
, tempdir
, 1, 0, true);
508 // Although it throws an error, the final replication in
509 // replicate_with_brokenness() updates the database, since it's just
510 // the end-of-replication message which is missing its body.
511 check_equal_dbs(masterpath
, replicapath
);
513 // Check that the earlier broken replications didn't cause any problems
514 // for the next replication.
515 orig
.add_document(doc1
);
517 TEST_EQUAL(replicate(master
, replica
, tempdir
, 1, 0, true), 2);
519 // We need this inner scope to we close the replica before we remove
520 // the temporary directory on Windows.
527 // Tests for max_changesets
528 DEFINE_TESTCASE(replicate4
, replicas
) {
529 #ifdef XAPIAN_HAS_REMOTE_BACKEND
530 UNSET_MAX_CHANGESETS_AFTERWARDS
;
531 string tempdir
= ".replicatmp";
533 string masterpath
= get_named_writable_database_path("master");
535 set_max_changesets(1);
538 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
539 Xapian::DatabaseMaster
master(masterpath
);
540 string replicapath
= tempdir
+ "/replica";
541 Xapian::DatabaseReplica
replica(replicapath
);
543 // Add a document with no positions to the original database.
544 Xapian::Document doc1
;
545 doc1
.set_data(string("doc1"));
546 doc1
.add_term("nopos");
547 orig
.add_document(doc1
);
550 // Apply the replication - we don't have changesets stored, so this
551 // should just do a database copy, and return a count of 1.
552 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
553 TEST_EQUAL(count
, 1);
555 Xapian::Database
dbcopy(replicapath
);
556 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
559 // Add a document with positional information to the original database.
560 doc1
.add_posting("pos", 1);
561 orig
.add_document(doc1
);
564 // Replicate, and check that we have the positional information.
565 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
566 TEST_EQUAL(count
, 2);
568 Xapian::Database
dbcopy(replicapath
);
569 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
571 check_equal_dbs(masterpath
, replicapath
);
573 // Add a document with no positions to the original database.
574 Xapian::Document doc2
;
575 doc2
.set_data(string("doc2"));
576 doc2
.add_term("nopos");
577 orig
.add_document(doc2
);
578 if (get_dbtype() == "glass") {
579 // FIXME: Needs to be pre-commit for glass
580 set_max_changesets(0);
584 // Replicate, and check that we have the positional information.
585 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
586 TEST_EQUAL(count
, 2);
588 Xapian::Database
dbcopy(replicapath
);
589 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
591 check_equal_dbs(masterpath
, replicapath
);
592 TEST(!file_exists(masterpath
+ "/changes1"));
594 // Turn off replication, make sure we don't write anything.
595 if (get_dbtype() != "glass") {
596 set_max_changesets(0);
599 // Add a document with no positions to the original database.
600 Xapian::Document doc3
;
601 doc3
.set_data(string("doc3"));
602 doc3
.add_term("nonopos");
603 orig
.add_document(doc3
);
606 // Replicate, and check that we have the positional information.
607 count
= replicate(master
, replica
, tempdir
, 0, 1, true);
608 TEST_EQUAL(count
, 1);
610 Xapian::Database
dbcopy(replicapath
);
611 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
613 // Should have pulled a full copy
614 check_equal_dbs(masterpath
, replicapath
);
615 TEST(!file_exists(masterpath
+ "/changes3"));
617 // We need this inner scope to we close the replica before we remove
618 // the temporary directory on Windows.
625 // Tests for max_changesets
626 DEFINE_TESTCASE(replicate5
, replicas
) {
627 #ifdef XAPIAN_HAS_REMOTE_BACKEND
628 UNSET_MAX_CHANGESETS_AFTERWARDS
;
629 string tempdir
= ".replicatmp";
631 string masterpath
= get_named_writable_database_path("master");
633 set_max_changesets(2);
636 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
637 Xapian::DatabaseMaster
master(masterpath
);
638 string replicapath
= tempdir
+ "/replica";
639 Xapian::DatabaseReplica
replica(replicapath
);
641 // Add a document with no positions to the original database.
642 Xapian::Document doc1
;
643 doc1
.set_data(string("doc1"));
644 doc1
.add_term("nopos");
645 orig
.add_document(doc1
);
648 // Apply the replication - we don't have changesets stored, so this
649 // should just do a database copy, and return a count of 1.
650 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
651 TEST_EQUAL(count
, 1);
653 Xapian::Database
dbcopy(replicapath
);
654 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
657 // Add a document with positional information to the original database.
658 doc1
.add_posting("pos", 1);
659 orig
.add_document(doc1
);
662 // Replicate, and check that we have the positional information.
663 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
664 TEST_EQUAL(count
, 2);
666 Xapian::Database
dbcopy(replicapath
);
667 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
669 check_equal_dbs(masterpath
, replicapath
);
671 // Add a document with no positions to the original database.
672 Xapian::Document doc2
;
673 doc2
.set_data(string("doc2"));
674 doc2
.add_term("nopos");
675 orig
.add_document(doc2
);
678 // Replicate, and check that we have the positional information.
679 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
680 TEST_EQUAL(count
, 2);
682 Xapian::Database
dbcopy(replicapath
);
683 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
685 check_equal_dbs(masterpath
, replicapath
);
687 // Add a document with no positions to the original database.
688 Xapian::Document doc3
;
689 doc3
.set_data(string("doc3"));
690 doc3
.add_term("nonopos");
691 orig
.add_document(doc3
);
694 // Replicate, and check that we have the positional information.
695 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
696 TEST_EQUAL(count
, 2);
698 Xapian::Database
dbcopy(replicapath
);
699 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
701 check_equal_dbs(masterpath
, replicapath
);
703 // Ensure that only these changesets exists
704 TEST(!file_exists(masterpath
+ "/changes1"));
705 TEST(file_exists(masterpath
+ "/changes2"));
706 TEST(file_exists(masterpath
+ "/changes3"));
708 set_max_changesets(3);
709 masterpath
= get_named_writable_database_path("master");
711 // Add a document with no positions to the original database.
712 Xapian::Document doc4
;
713 doc4
.set_data(string("doc4"));
714 doc4
.add_term("nononopos");
715 orig
.add_document(doc4
);
718 // Replicate, and check that we have the positional information.
719 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
720 TEST_EQUAL(count
, 2);
722 Xapian::Database
dbcopy(replicapath
);
723 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
725 check_equal_dbs(masterpath
, replicapath
);
727 // Add a document with no positions to the original database.
728 Xapian::Document doc5
;
729 doc5
.set_data(string("doc5"));
730 doc5
.add_term("nonononopos");
731 orig
.add_document(doc5
);
734 // Replicate, and check that we have the positional information.
735 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
736 TEST_EQUAL(count
, 2);
738 Xapian::Database
dbcopy(replicapath
);
739 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
741 check_equal_dbs(masterpath
, replicapath
);
743 TEST(!file_exists(masterpath
+ "/changes2"));
744 TEST(file_exists(masterpath
+ "/changes3"));
745 TEST(file_exists(masterpath
+ "/changes4"));
746 TEST(file_exists(masterpath
+ "/changes5"));
748 // We need this inner scope to we close the replica before we remove
749 // the temporary directory on Windows.
756 /// Test --full-copy option.
757 DEFINE_TESTCASE(replicate6
, replicas
) {
758 #ifdef XAPIAN_HAS_REMOTE_BACKEND
759 UNSET_MAX_CHANGESETS_AFTERWARDS
;
760 string tempdir
= ".replicatmp";
762 string masterpath
= get_named_writable_database_path("master");
764 set_max_changesets(10);
767 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
768 Xapian::DatabaseMaster
master(masterpath
);
769 string replicapath
= tempdir
+ "/replica";
770 Xapian::DatabaseReplica
replica(replicapath
);
772 // Add a document to the original database.
773 Xapian::Document doc1
;
774 doc1
.set_data(string("doc1"));
775 doc1
.add_posting("doc", 1);
776 doc1
.add_posting("one", 1);
777 orig
.add_document(doc1
);
780 rm_rf(masterpath
+ "1");
781 cp_R(masterpath
, masterpath
+ "1");
783 orig
.add_document(doc1
);
786 // Apply the replication - we don't have changesets stored, so this
787 // should just do a database copy, and return a count of 1.
788 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
789 TEST_EQUAL(count
, 1);
791 Xapian::Database
dbcopy(replicapath
);
792 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
795 Xapian::DatabaseMaster
master1(masterpath
+ "1");
797 // Try to replicate an older version of the master.
798 count
= replicate(master1
, replica
, tempdir
, 0, 0, false);
799 TEST_EQUAL(count
, 1);
801 // Force a full copy.
802 count
= replicate(master1
, replica
, tempdir
, 0, 1, true, true);
803 TEST_EQUAL(count
, 1);
805 // Test we can still replicate.
806 orig
.add_document(doc1
);
809 count
= replicate(master
, replica
, tempdir
, 2, 0, true);
810 TEST_EQUAL(count
, 3);
812 check_equal_dbs(masterpath
, replicapath
);
814 // We need this inner scope to we close the replica before we remove
815 // the temporary directory on Windows.
822 /// Test healing a corrupt replica (new in 1.3.5).
823 DEFINE_TESTCASE(replicate7
, replicas
) {
824 #ifdef XAPIAN_HAS_REMOTE_BACKEND
825 UNSET_MAX_CHANGESETS_AFTERWARDS
;
826 string tempdir
= ".replicatmp";
828 string masterpath
= get_named_writable_database_path("master");
830 set_max_changesets(10);
832 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
833 Xapian::DatabaseMaster
master(masterpath
);
834 string replicapath
= tempdir
+ "/replica";
836 Xapian::DatabaseReplica
replica(replicapath
);
838 // Add a document to the original database.
839 Xapian::Document doc1
;
840 doc1
.set_data(string("doc1"));
841 doc1
.add_posting("doc", 1);
842 doc1
.add_posting("one", 1);
843 orig
.add_document(doc1
);
846 orig
.add_document(doc1
);
849 // Apply the replication - we don't have changesets stored, so this
850 // should just do a database copy, and return a count of 1.
851 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
852 TEST_EQUAL(count
, 1);
854 Xapian::Database
dbcopy(replicapath
);
855 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
860 // Corrupt replica by truncating all the files to size 0.
861 string d
= replicapath
;
863 DIR * dir
= opendir(d
.c_str());
867 struct dirent
* entry
= readdir(dir
);
871 FAIL_TEST("readdir failed: " << errno_to_string(errno
));
874 // Skip '.' and '..'.
875 if (entry
->d_name
[0] == '.') continue;
879 file
+= entry
->d_name
;
880 int fd
= open(file
.c_str(), O_WRONLY
|O_TRUNC
);
882 TEST(close(fd
) == 0);
888 Xapian::DatabaseReplica
replica(replicapath
);
890 // Replication should succeed and perform a full copy.
891 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
892 TEST_EQUAL(count
, 1);
894 check_equal_dbs(masterpath
, replicapath
);
896 // We need this inner scope to we close the replica before we remove
897 // the temporary directory on Windows.