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 "safeerrno.h"
37 #include "safefcntl.h"
38 #include "safesysstat.h"
39 #include "safeunistd.h"
40 #include "testsuite.h"
41 #include "testutils.h"
44 #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.
651 // Tests for max_changesets
652 DEFINE_TESTCASE(replicate5
, replicas
) {
653 #ifdef XAPIAN_HAS_REMOTE_BACKEND
654 UNSET_MAX_CHANGESETS_AFTERWARDS
;
655 string tempdir
= ".replicatmp";
657 string masterpath
= get_named_writable_database_path("master");
659 set_max_changesets(2);
662 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
663 Xapian::DatabaseMaster
master(masterpath
);
664 string replicapath
= tempdir
+ "/replica";
665 Xapian::DatabaseReplica
replica(replicapath
);
667 // Add a document with no positions to the original database.
668 Xapian::Document doc1
;
669 doc1
.set_data(string("doc1"));
670 doc1
.add_term("nopos");
671 orig
.add_document(doc1
);
674 // Apply the replication - we don't have changesets stored, so this
675 // should just do a database copy, and return a count of 1.
676 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
677 TEST_EQUAL(count
, 1);
679 Xapian::Database
dbcopy(replicapath
);
680 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
683 // Add a document with positional information to the original database.
684 doc1
.add_posting("pos", 1);
685 orig
.add_document(doc1
);
688 // Replicate, and check that we have the positional information.
689 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
690 TEST_EQUAL(count
, 2);
692 Xapian::Database
dbcopy(replicapath
);
693 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
695 check_equal_dbs(masterpath
, replicapath
);
697 // Add a document with no positions to the original database.
698 Xapian::Document doc2
;
699 doc2
.set_data(string("doc2"));
700 doc2
.add_term("nopos");
701 orig
.add_document(doc2
);
704 // Replicate, and check that we have the positional information.
705 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
706 TEST_EQUAL(count
, 2);
708 Xapian::Database
dbcopy(replicapath
);
709 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
711 check_equal_dbs(masterpath
, replicapath
);
713 // Add a document with no positions to the original database.
714 Xapian::Document doc3
;
715 doc3
.set_data(string("doc3"));
716 doc3
.add_term("nonopos");
717 orig
.add_document(doc3
);
720 // Replicate, and check that we have the positional information.
721 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
722 TEST_EQUAL(count
, 2);
724 Xapian::Database
dbcopy(replicapath
);
725 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
727 check_equal_dbs(masterpath
, replicapath
);
729 // Ensure that only these changesets exists
730 TEST(!file_exists(masterpath
+ "/changes1"));
731 TEST(file_exists(masterpath
+ "/changes2"));
732 TEST(file_exists(masterpath
+ "/changes3"));
734 set_max_changesets(3);
735 masterpath
= get_named_writable_database_path("master");
737 // Add a document with no positions to the original database.
738 Xapian::Document doc4
;
739 doc4
.set_data(string("doc4"));
740 doc4
.add_term("nononopos");
741 orig
.add_document(doc4
);
744 // Replicate, and check that we have the positional information.
745 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
746 TEST_EQUAL(count
, 2);
748 Xapian::Database
dbcopy(replicapath
);
749 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
751 check_equal_dbs(masterpath
, replicapath
);
753 // Add a document with no positions to the original database.
754 Xapian::Document doc5
;
755 doc5
.set_data(string("doc5"));
756 doc5
.add_term("nonononopos");
757 orig
.add_document(doc5
);
760 // Replicate, and check that we have the positional information.
761 count
= replicate(master
, replica
, tempdir
, 1, 0, true);
762 TEST_EQUAL(count
, 2);
764 Xapian::Database
dbcopy(replicapath
);
765 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
767 check_equal_dbs(masterpath
, replicapath
);
769 TEST(!file_exists(masterpath
+ "/changes2"));
770 TEST(file_exists(masterpath
+ "/changes3"));
771 TEST(file_exists(masterpath
+ "/changes4"));
772 TEST(file_exists(masterpath
+ "/changes5"));
774 // We need this inner scope to we close the replica before we remove
775 // the temporary directory on Windows.
783 /// Test --full-copy option.
784 DEFINE_TESTCASE(replicate6
, replicas
) {
785 #ifdef XAPIAN_HAS_REMOTE_BACKEND
786 UNSET_MAX_CHANGESETS_AFTERWARDS
;
787 string tempdir
= ".replicatmp";
789 string masterpath
= get_named_writable_database_path("master");
791 set_max_changesets(10);
794 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
795 Xapian::DatabaseMaster
master(masterpath
);
796 string replicapath
= tempdir
+ "/replica";
797 Xapian::DatabaseReplica
replica(replicapath
);
799 // Add a document to the original database.
800 Xapian::Document doc1
;
801 doc1
.set_data(string("doc1"));
802 doc1
.add_posting("doc", 1);
803 doc1
.add_posting("one", 1);
804 orig
.add_document(doc1
);
807 rm_rf(masterpath
+ "1");
808 cp_R(masterpath
, masterpath
+ "1");
810 orig
.add_document(doc1
);
813 // Apply the replication - we don't have changesets stored, so this
814 // should just do a database copy, and return a count of 1.
815 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
816 TEST_EQUAL(count
, 1);
818 Xapian::Database
dbcopy(replicapath
);
819 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
822 Xapian::DatabaseMaster
master1(masterpath
+ "1");
824 // Try to replicate an older version of the master.
825 count
= replicate(master1
, replica
, tempdir
, 0, 0, false);
826 TEST_EQUAL(count
, 1);
828 // Force a full copy.
829 count
= replicate(master1
, replica
, tempdir
, 0, 1, true, true);
830 TEST_EQUAL(count
, 1);
832 // Test we can still replicate.
833 orig
.add_document(doc1
);
836 count
= replicate(master
, replica
, tempdir
, 2, 0, true);
837 TEST_EQUAL(count
, 3);
839 check_equal_dbs(masterpath
, replicapath
);
841 // We need this inner scope to we close the replica before we remove
842 // the temporary directory on Windows.
850 /// Test healing a corrupt replica (new in 1.3.5).
851 DEFINE_TESTCASE(replicate7
, replicas
) {
852 #ifdef XAPIAN_HAS_REMOTE_BACKEND
853 UNSET_MAX_CHANGESETS_AFTERWARDS
;
854 string tempdir
= ".replicatmp";
856 string masterpath
= get_named_writable_database_path("master");
858 set_max_changesets(10);
860 Xapian::WritableDatabase
orig(get_named_writable_database("master"));
861 Xapian::DatabaseMaster
master(masterpath
);
862 string replicapath
= tempdir
+ "/replica";
864 Xapian::DatabaseReplica
replica(replicapath
);
866 // Add a document to the original database.
867 Xapian::Document doc1
;
868 doc1
.set_data(string("doc1"));
869 doc1
.add_posting("doc", 1);
870 doc1
.add_posting("one", 1);
871 orig
.add_document(doc1
);
874 orig
.add_document(doc1
);
877 // Apply the replication - we don't have changesets stored, so this
878 // should just do a database copy, and return a count of 1.
879 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
880 TEST_EQUAL(count
, 1);
882 Xapian::Database
dbcopy(replicapath
);
883 TEST_EQUAL(orig
.get_uuid(), dbcopy
.get_uuid());
888 // Corrupt replica by truncating all the files to size 0.
889 string d
= replicapath
;
891 DIR * dir
= opendir(d
.c_str());
895 struct dirent
* entry
= readdir(dir
);
899 FAIL_TEST("readdir failed: " << strerror(errno
));
902 // Skip '.' and '..'.
903 if (entry
->d_name
[0] == '.') continue;
907 file
+= entry
->d_name
;
908 int fd
= open(file
.c_str(), O_WRONLY
|O_TRUNC
, 0666);
910 TEST(close(fd
) == 0);
916 Xapian::DatabaseReplica
replica(replicapath
);
918 // Replication should succeed and perform a full copy.
919 int count
= replicate(master
, replica
, tempdir
, 0, 1, true);
920 TEST_EQUAL(count
, 1);
922 check_equal_dbs(masterpath
, replicapath
);
924 // We need this inner scope to we close the replica before we remove
925 // the temporary directory on Windows.