Fix testcase unsupportedcheck1 for --disable-backend-remote
[xapian.git] / xapian-core / tests / api_replicate.cc
blob55f33dd84d268a1d5e85edb70a223870e9c3a033
1 /** @file
2 * @brief tests of replication functionality
3 */
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
22 * USA
25 #include <config.h>
27 #include "api_replicate.h"
29 #include <xapian.h>
30 #include "api/replication.h"
32 #include "apitest.h"
33 #include "dbcheck.h"
34 #include "errno_to_string.h"
35 #include "fd.h"
36 #include "filetests.h"
37 #include "safedirent.h"
38 #include "safefcntl.h"
39 #include "safesysstat.h"
40 #include "safeunistd.h"
41 #include "setenv.h"
42 #include "testsuite.h"
43 #include "testutils.h"
44 #include "unixcmds.h"
46 #include <sys/types.h>
48 #include <cerrno>
49 #include <cstdlib>
50 #include <string>
52 using namespace std;
54 #ifdef XAPIAN_HAS_REMOTE_BACKEND
56 static void rmtmpdir(const string & path) {
57 rm_rf(path);
60 static void mktmpdir(const string & path) {
61 rmtmpdir(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)
69 size_t total = 0;
70 while (desired) {
71 ssize_t c = read(fd, p, desired);
72 if (c == 0) return total;
73 if (c < 0) {
74 if (errno == EINTR) continue;
75 FAIL_TEST("Error reading from file");
77 p += c;
78 total += c;
79 desired -= c;
81 return total;
84 static void do_write(int fd, const char * p, size_t n)
86 while (n) {
87 ssize_t c = write(fd, p, n);
88 if (c < 0) {
89 if (errno == EINTR) continue;
90 FAIL_TEST("Error writing to file");
92 p += c;
93 n -= c;
97 // Make a truncated copy of a file.
98 static file_size_type
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));
104 if (fdin == -1) {
105 FAIL_TEST("Open failed (when opening '" << srcpath << "')");
108 FD fdout(open(destpath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
109 if (fdout == -1) {
110 FAIL_TEST("Open failed (when creating '" << destpath << "')");
113 const int BUFSIZE = 1024;
114 char buf[BUFSIZE];
115 size_t total_bytes = 0;
116 while (tocopy > 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");
122 tocopy -= bytes;
123 total_bytes += bytes;
124 do_write(fdout, buf, bytes);
127 if (close(fdout) == -1)
128 FAIL_TEST("Error closing file");
130 return total_bytes;
133 static void
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));
143 if (fd == -1) {
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(),
150 &info1);
152 TEST_EQUAL(info1.changeset_count, expected_changesets);
153 TEST_EQUAL(info1.fullcopy_count, expected_fullcopies);
154 TEST_EQUAL(info1.changed, expected_changed);
157 static int
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));
165 if (fd == -1) {
166 FAIL_TEST("Open failed (when reading changeset file at '"
167 << changesetpath << "')");
170 int count = 1;
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)) {
176 ++count;
177 info1.changeset_count += info2.changeset_count;
178 info1.fullcopy_count += info2.fullcopy_count;
179 if (info2.changed)
180 client_changed = true;
182 info1.changeset_count += info2.changeset_count;
183 info1.fullcopy_count += info2.fullcopy_count;
184 if (info2.changed)
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);
190 return count;
193 // Replicate from the master to the replica.
194 // Returns the number of changesets which were applied.
195 static int
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,
206 expected_changesets,
207 expected_fullcopies,
208 expected_changed,
209 full_copy);
210 return apply_changeset(changesetpath, replica,
211 expected_changesets,
212 expected_fullcopies,
213 expected_changed);
216 // Check that the databases held at the given path are identical.
217 static void
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
244 #endif
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";
254 mktmpdir(tempdir);
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);
272 orig.commit();
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
295 // #10117.
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);
305 orig.commit();
306 orig.add_document(doc1);
307 orig.commit();
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);
324 rmtmpdir(tempdir);
325 #endif
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";
335 mktmpdir(tempdir);
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);
356 orig.commit();
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);
368 orig.commit();
369 orig.add_document(doc1);
370 orig.commit();
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);
384 orig.close();
385 orig = get_writable_database_again();
386 orig.add_document(doc1);
387 orig.commit();
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
396 // modification.
397 set_max_changesets(1);
398 orig.close();
399 orig = get_writable_database_again();
400 orig.add_document(doc1);
401 orig.commit();
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);
411 orig.commit();
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.
427 rmtmpdir(tempdir);
428 #endif
431 #ifdef XAPIAN_HAS_REMOTE_BACKEND
432 static void
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,
442 1, 0, 1);
444 // Try applying truncated changesets of various different lengths.
445 string brokenchangesetpath = tempdir + "/changeset_broken";
446 auto filesize = file_size(changesetpath);
447 if (errno) {
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,
460 expected_changed));
461 if (len < 30 || len >= filesize - 10) {
462 // For lengths near the beginning and end, increment size by 1
463 ++len;
464 } else {
465 // Don't bother incrementing by small amounts in the middle of
466 // the changeset.
467 len += 1000;
468 if (len >= filesize - 10) {
469 len = filesize - 10;
474 #endif
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";
481 mktmpdir(tempdir);
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);
498 orig.commit();
500 TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, true), 1);
501 check_equal_dbs(masterpath, replicapath);
503 // Make a changeset.
504 orig.add_document(doc1);
505 orig.commit();
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);
516 orig.commit();
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.
523 rmtmpdir(tempdir);
524 #endif
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";
532 mktmpdir(tempdir);
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);
548 orig.commit();
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);
562 orig.commit();
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);
582 orig.commit();
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);
604 orig.commit();
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.
621 rmtmpdir(tempdir);
622 #endif
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";
630 mktmpdir(tempdir);
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);
646 orig.commit();
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);
660 orig.commit();
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);
676 orig.commit();
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);
692 orig.commit();
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);
716 orig.commit();
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);
732 orig.commit();
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.
752 rmtmpdir(tempdir);
753 #endif
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";
761 mktmpdir(tempdir);
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);
778 orig.commit();
780 rm_rf(masterpath + "1");
781 cp_R(masterpath, masterpath + "1");
783 orig.add_document(doc1);
784 orig.commit();
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);
807 orig.commit();
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.
818 rmtmpdir(tempdir);
819 #endif
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";
827 mktmpdir(tempdir);
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);
844 orig.commit();
846 orig.add_document(doc1);
847 orig.commit();
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;
862 d += "/replica_1";
863 DIR * dir = opendir(d.c_str());
864 TEST(dir != NULL);
865 while (true) {
866 errno = 0;
867 struct dirent * entry = readdir(dir);
868 if (!entry) {
869 if (errno == 0)
870 break;
871 FAIL_TEST("readdir failed: " << errno_to_string(errno));
874 // Skip '.' and '..'.
875 if (entry->d_name[0] == '.') continue;
877 string file = d;
878 file += '/';
879 file += entry->d_name;
880 int fd = open(file.c_str(), O_WRONLY|O_TRUNC);
881 TEST(fd != -1);
882 TEST(close(fd) == 0);
884 closedir(dir);
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.
900 rmtmpdir(tempdir);
901 #endif