Branch data Line data Source code
1 : : /* api_replicate.cc: tests of replication functionality
2 : : *
3 : : * Copyright 2008 Lemur Consulting Ltd
4 : : * Copyright 2009,2010 Olly Betts
5 : : * Copyright 2010 Richard Boulton
6 : : * Copyright 2011 Dan Colish
7 : : *
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.
12 : : *
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.
17 : : *
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
21 : : * USA
22 : : */
23 : :
24 : : #include <config.h>
25 : :
26 : : #include "api_replicate.h"
27 : :
28 : : #include <xapian.h>
29 : :
30 : : #include "apitest.h"
31 : : #include "dbcheck.h"
32 : : #include "safeerrno.h"
33 : : #include "safefcntl.h"
34 : : #include "safesysstat.h"
35 : : #include "safeunistd.h"
36 : : #include "str.h"
37 : : #include "testsuite.h"
38 : : #include "testutils.h"
39 : : #include "utils.h"
40 : : #include "unixcmds.h"
41 : :
42 : : #include <sys/types.h>
43 : :
44 : : #include <cstdlib>
45 : : #include <string>
46 : :
47 : : #include <stdlib.h> // For setenv() or putenv()
48 : :
49 : : using namespace std;
50 : :
51 : 24 : static void rmtmpdir(const string & path) {
52 : 24 : rm_rf(path);
53 : 24 : }
54 : :
55 : 12 : static void mktmpdir(const string & path) {
56 : 12 : rmtmpdir(path);
57 [ - + # # ]: 12 : if (mkdir(path, 0700) == -1 && errno != EEXIST) {
[ - + ]
58 [ # # ]: 0 : FAIL_TEST("Can't make temporary directory");
59 : : }
60 : 12 : }
61 : :
62 : 3 : static off_t file_size(const string & path) {
63 : : struct stat sb;
64 [ - + ]: 3 : if (stat(path.c_str(), &sb)) {
65 [ # # ]: 0 : FAIL_TEST("Can't stat '" + path + "'");
66 : : }
67 : 3 : return sb.st_size;
68 : : }
69 : :
70 : 483 : static size_t do_read(int fd, char * p, size_t desired)
71 : : {
72 : 483 : size_t total = 0;
73 [ + + ]: 966 : while (desired) {
74 : 483 : ssize_t c = read(fd, p, desired);
75 [ - + ]: 483 : if (c == 0) return total;
76 [ - + ]: 483 : if (c < 0) {
77 [ # # ]: 0 : if (errno == EINTR) continue;
78 [ # # ]: 0 : FAIL_TEST("Error reading from file");
79 : : }
80 : 483 : p += c;
81 : 483 : total += c;
82 : 483 : desired -= c;
83 : : }
84 : 483 : return total;
85 : : }
86 : :
87 : 483 : static void do_write(int fd, const char * p, size_t n)
88 : : {
89 [ + + ]: 966 : while (n) {
90 : 483 : ssize_t c = write(fd, p, n);
91 [ - + ]: 483 : if (c < 0) {
92 [ # # ]: 0 : if (errno == EINTR) continue;
93 [ # # ]: 0 : FAIL_TEST("Error writing to file");
94 : : }
95 : 483 : p += c;
96 : 483 : n -= c;
97 : : }
98 : 483 : }
99 : :
100 : : // Make a truncated copy of a file.
101 : : static off_t
102 : 119 : truncated_copy(const string & srcpath, const string & destpath, off_t tocopy)
103 : : {
104 : 119 : int fdin = open(srcpath.c_str(), O_RDONLY);
105 [ - + ]: 119 : if (fdin == -1) {
106 [ # # ]: 0 : FAIL_TEST("Open failed (when opening '" + srcpath + "')");
107 : : }
108 : :
109 : 119 : int fdout = open(destpath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
110 [ - + ]: 119 : if (fdout == -1) {
111 [ # # ]: 0 : FAIL_TEST("Open failed (when creating '" + destpath + "')");
112 : : }
113 : :
114 : : #define BUFSIZE 1024
115 : : char buf[BUFSIZE];
116 : 119 : size_t total_bytes = 0;
117 [ + + ]: 602 : while (tocopy > 0) {
118 : 483 : size_t thiscopy = tocopy > BUFSIZE ? BUFSIZE : tocopy;
119 : 483 : size_t bytes = do_read(fdin, buf, thiscopy);
120 [ - + ]: 483 : if (thiscopy != bytes) {
121 [ # # ]: 0 : FAIL_TEST("Couldn't read desired number of bytes from changeset");
122 : : }
123 : 483 : tocopy -= bytes;
124 : 483 : total_bytes += bytes;
125 : 483 : do_write(fdout, buf, bytes);
126 : : }
127 : : #undef BUFSIZE
128 : :
129 : 119 : close(fdin);
130 : 119 : close(fdout);
131 : :
132 : 119 : return total_bytes;
133 : : }
134 : :
135 : : // Replicate from the master to the replica.
136 : : // Returns the number of changsets which were applied.
137 : : static void
138 : 57 : get_changeset(const string & changesetpath,
139 : : Xapian::DatabaseMaster & master,
140 : : Xapian::DatabaseReplica & replica,
141 : : int expected_changesets,
142 : : int expected_fullcopies,
143 : : bool expected_changed)
144 : : {
145 : 57 : int fd = open(changesetpath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
146 [ - + ]: 57 : if (fd == -1) {
147 [ # # ]: 0 : FAIL_TEST("Open failed (when creating a new changeset file at '"
148 : : + changesetpath + "')");
149 : : }
150 : 57 : fdcloser fdc(fd);
151 : 57 : Xapian::ReplicationInfo info1;
152 : 57 : master.write_changesets_to_fd(fd, replica.get_revision_info(), &info1);
153 : :
154 [ - + # # ]: 57 : TEST_EQUAL(info1.changeset_count, expected_changesets);
155 [ - + ][ # # ]: 57 : TEST_EQUAL(info1.fullcopy_count, expected_fullcopies);
156 [ - + ][ # # ]: 57 : TEST_EQUAL(info1.changed, expected_changed);
157 : 57 : }
158 : :
159 : : static int
160 : 173 : apply_changeset(const string & changesetpath,
161 : : Xapian::DatabaseReplica & replica,
162 : : int expected_changesets,
163 : : int expected_fullcopies,
164 : : bool expected_changed)
165 : : {
166 : 173 : int fd = open(changesetpath.c_str(), O_RDONLY);
167 [ - + ]: 173 : if (fd == -1) {
168 [ # # ]: 0 : FAIL_TEST("Open failed (when reading changeset file at '"
169 : : + changesetpath + "')");
170 : : }
171 : 173 : fdcloser fdc(fd);
172 : :
173 : 173 : int count = 1;
174 : 173 : replica.set_read_fd(fd);
175 : 173 : Xapian::ReplicationInfo info1;
176 : 173 : Xapian::ReplicationInfo info2;
177 : 173 : bool client_changed = false;
178 [ + + ]: 208 : while (replica.apply_next_changeset(&info2, 0)) {
179 : 35 : ++count;
180 : 35 : info1.changeset_count += info2.changeset_count;
181 : 35 : info1.fullcopy_count += info2.fullcopy_count;
182 [ + - ]: 35 : if (info2.changed)
183 : 35 : client_changed = true;
184 : : }
185 : 54 : info1.changeset_count += info2.changeset_count;
186 : 54 : info1.fullcopy_count += info2.fullcopy_count;
187 [ + + ]: 54 : if (info2.changed)
188 : 21 : client_changed = true;
189 : :
190 [ - + ][ # # ]: 54 : TEST_EQUAL(info1.changeset_count, expected_changesets);
191 [ - + ][ # # ]: 54 : TEST_EQUAL(info1.fullcopy_count, expected_fullcopies);
192 [ - + ][ # # ]: 54 : TEST_EQUAL(client_changed, expected_changed);
193 : 173 : return count;
194 : : }
195 : :
196 : : static int
197 : 54 : replicate(Xapian::DatabaseMaster & master,
198 : : Xapian::DatabaseReplica & replica,
199 : : const string & tempdir,
200 : : int expected_changesets,
201 : : int expected_fullcopies,
202 : : bool expected_changed)
203 : : {
204 : 54 : string changesetpath = tempdir + "/changeset";
205 : : get_changeset(changesetpath, master, replica,
206 : : expected_changesets,
207 : : expected_fullcopies,
208 : 54 : expected_changed);
209 : : return apply_changeset(changesetpath, replica,
210 : : expected_changesets,
211 : : expected_fullcopies,
212 : 54 : expected_changed);
213 : : }
214 : :
215 : : // Check that the databases held at the given path are identical.
216 : : static void
217 : 41 : check_equal_dbs(const string & masterpath, const string & replicapath)
218 : : {
219 : 41 : Xapian::Database master(masterpath);
220 : 41 : Xapian::Database replica(replicapath);
221 : :
222 [ - + ][ # # ]: 41 : TEST_EQUAL(master.get_uuid(), master.get_uuid());
223 : 41 : dbcheck(replica, master.get_doccount(), master.get_lastdocid());
224 : :
225 [ + + ]: 132 : for (Xapian::TermIterator t = master.allterms_begin();
226 : : t != master.allterms_end(); ++t) {
227 [ - + ][ # # ]: 91 : TEST_EQUAL(postlist_to_string(master, *t),
228 : : postlist_to_string(replica, *t));
229 : 41 : }
230 : 41 : }
231 : :
232 : : #if 0 // Dynamic version which we don't currently need.
233 : : static void
234 : : set_max_changesets(int count) {
235 : : #ifdef __WIN32__
236 : : _putenv_s("XAPIAN_MAX_CHANGESETS", str(count).c_str());
237 : : #elif defined HAVE_SETENV
238 : : setenv("XAPIAN_MAX_CHANGESETS", str(count).c_str(), 1);
239 : : #else
240 : : static char buf[64] = "XAPIAN_MAX_CHANGESETS=";
241 : : sprintf(buf + CONST_STRLEN("XAPIAN_MAX_CHANGESETS="), "%d", count);
242 : : putenv(buf);
243 : : #endif
244 : : }
245 : : #endif
246 : :
247 : : #ifdef __WIN32__
248 : : # define set_max_changesets(N) _putenv_s("XAPIAN_MAX_CHANGESETS", #N)
249 : : #elif defined HAVE_SETENV
250 : : # define set_max_changesets(N) setenv("XAPIAN_MAX_CHANGESETS", #N, 1)
251 : : #else
252 : : # define set_max_changesets(N) putenv(const_cast<char*>("XAPIAN_MAX_CHANGESETS="#N))
253 : : #endif
254 : :
255 : : // #######################################################################
256 : : // # Tests start here
257 : :
258 : : // Basic test of replication functionality.
259 : 3 : DEFINE_TESTCASE(replicate1, replicas) {
260 : 3 : string tempdir = ".replicatmp";
261 : 3 : mktmpdir(tempdir);
262 : 3 : string masterpath = get_named_writable_database_path("master");
263 : :
264 : 3 : set_max_changesets(10);
265 : :
266 : 3 : Xapian::WritableDatabase orig(get_named_writable_database("master"));
267 : 3 : Xapian::DatabaseMaster master(masterpath);
268 : 3 : string replicapath = tempdir + "/replica";
269 : 3 : Xapian::DatabaseReplica replica(replicapath);
270 : :
271 : : // Add a document to the original database.
272 : 3 : Xapian::Document doc1;
273 : 3 : doc1.set_data(string("doc1"));
274 : 3 : doc1.add_posting("doc", 1);
275 : 3 : doc1.add_posting("one", 1);
276 : 3 : orig.add_document(doc1);
277 : 3 : orig.commit();
278 : :
279 : : // Apply the replication - we don't have changesets stored, so this should
280 : : // just do a database copy, and return a count of 1.
281 : 3 : int count = replicate(master, replica, tempdir, 0, 1, 1);
282 [ - + # # ]: 3 : TEST_EQUAL(count, 1);
283 : : {
284 : 3 : Xapian::Database dbcopy(replicapath);
285 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
286 : : }
287 : :
288 : : // Repeating the replication should return a count of 1, since no further
289 : : // changes should need to be applied.
290 : 3 : count = replicate(master, replica, tempdir, 0, 0, 0);
291 [ - + # # ]: 3 : TEST_EQUAL(count, 1);
292 : : {
293 : 3 : Xapian::Database dbcopy(replicapath);
294 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
295 : : }
296 : :
297 : : // Regression test - if the replica was reopened, a full copy always used
298 : : // to occur, whether it was needed or not. Fixed in revision #10117.
299 : 3 : replica.close();
300 : 3 : replica = Xapian::DatabaseReplica(replicapath);
301 : 3 : count = replicate(master, replica, tempdir, 0, 0, 0);
302 [ - + # # ]: 3 : TEST_EQUAL(count, 1);
303 : : {
304 : 3 : Xapian::Database dbcopy(replicapath);
305 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
306 : : }
307 : :
308 : 3 : orig.add_document(doc1);
309 : 3 : orig.commit();
310 : 3 : orig.add_document(doc1);
311 : 3 : orig.commit();
312 : :
313 : 3 : count = replicate(master, replica, tempdir, 2, 0, 1);
314 [ - + # # ]: 3 : TEST_EQUAL(count, 3);
315 : : {
316 : 3 : Xapian::Database dbcopy(replicapath);
317 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
318 : : }
319 : :
320 : 3 : check_equal_dbs(masterpath, replicapath);
321 : :
322 : : // Need to close the replica before we remove the temporary directory on
323 : : // Windows.
324 : 3 : replica.close();
325 : 3 : rmtmpdir(tempdir);
326 : 3 : return true;
327 : : }
328 : :
329 : : // Test replication from a replicated copy.
330 : 3 : DEFINE_TESTCASE(replicate2, replicas) {
331 : 5 : SKIP_TEST_FOR_BACKEND("brass"); // Brass doesn't currently support this.
332 : :
333 : 2 : string tempdir = ".replicatmp";
334 : 2 : mktmpdir(tempdir);
335 : 2 : string masterpath = get_named_writable_database_path("master");
336 : :
337 : 2 : set_max_changesets(10);
338 : :
339 : 2 : Xapian::WritableDatabase orig(get_named_writable_database("master"));
340 : 2 : Xapian::DatabaseMaster master(masterpath);
341 : 2 : string replicapath = tempdir + "/replica";
342 : 2 : Xapian::DatabaseReplica replica(replicapath);
343 : :
344 : 2 : Xapian::DatabaseMaster master2(replicapath);
345 : 2 : string replica2path = tempdir + "/replica2";
346 : 2 : Xapian::DatabaseReplica replica2(replica2path);
347 : :
348 : : // Add a document to the original database.
349 : 2 : Xapian::Document doc1;
350 : 2 : doc1.set_data(string("doc1"));
351 : 2 : doc1.add_posting("doc", 1);
352 : 2 : doc1.add_posting("one", 1);
353 : 2 : orig.add_document(doc1);
354 : 2 : orig.commit();
355 : :
356 : : // Apply the replication - we don't have changesets stored, so this should
357 : : // just do a database copy, and return a count of 1.
358 [ - + # # ]: 2 : TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, 1), 1);
359 : 2 : check_equal_dbs(masterpath, replicapath);
360 : :
361 : : // Replicate from the replica.
362 [ - + # # ]: 2 : TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 1, 1), 1);
363 : 2 : check_equal_dbs(masterpath, replica2path);
364 : :
365 : 2 : orig.add_document(doc1);
366 : 2 : orig.commit();
367 : 2 : orig.add_document(doc1);
368 : 2 : orig.commit();
369 : :
370 : : // Replicate from the replica - should have no changes.
371 [ - + # # ]: 2 : TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 0, 0), 1);
372 : 2 : check_equal_dbs(replicapath, replica2path);
373 : :
374 : : // Replicate, and replicate from the replica - should have 2 changes.
375 [ - + # # ]: 2 : TEST_EQUAL(replicate(master, replica, tempdir, 2, 0, 1), 3);
376 : 2 : check_equal_dbs(masterpath, replicapath);
377 [ - + # # ]: 2 : TEST_EQUAL(replicate(master2, replica2, tempdir, 2, 0, 1), 3);
378 : 2 : check_equal_dbs(masterpath, replica2path);
379 : :
380 : : // Stop writing changesets, and make a modification
381 : 2 : set_max_changesets(0);
382 : 2 : orig.close();
383 : 2 : orig = get_writable_database_again();
384 : 2 : orig.add_document(doc1);
385 : 2 : orig.commit();
386 : :
387 : : // Replication should do a full copy.
388 [ - + # # ]: 2 : TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, 1), 1);
389 : 2 : check_equal_dbs(masterpath, replicapath);
390 [ - + # # ]: 2 : TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 1, 1), 1);
391 : 2 : check_equal_dbs(masterpath, replica2path);
392 : :
393 : : // Start writing changesets, but only keep 1 in history, and make a
394 : : // modification.
395 : 2 : set_max_changesets(1);
396 : 2 : orig.close();
397 : 2 : orig = get_writable_database_again();
398 : 2 : orig.add_document(doc1);
399 : 2 : orig.commit();
400 : :
401 : : // Replicate, and replicate from the replica - should have 1 changes.
402 [ - + # # ]: 2 : TEST_EQUAL(replicate(master, replica, tempdir, 1, 0, 1), 2);
403 : 2 : check_equal_dbs(masterpath, replicapath);
404 [ - + # # ]: 2 : TEST_EQUAL(replicate(master2, replica2, tempdir, 1, 0, 1), 2);
405 : 2 : check_equal_dbs(masterpath, replica2path);
406 : :
407 : : // Make two changes - only one changeset should be preserved.
408 : 2 : orig.add_document(doc1);
409 : 2 : orig.commit();
410 : :
411 : : // Replication should do a full copy, since one of the needed changesets
412 : : // is missing.
413 : :
414 : : //FIXME - the following tests are commented out because the backends don't currently tidy up old changesets correctly.
415 : : //TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, 1), 1);
416 : : //check_equal_dbs(masterpath, replicapath);
417 : : //TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 1, 1), 1);
418 : : //check_equal_dbs(masterpath, replica2path);
419 : :
420 : : // Need to close the replicas before we remove the temporary directory on
421 : : // Windows.
422 : 2 : replica.close();
423 : 2 : replica2.close();
424 : 2 : rmtmpdir(tempdir);
425 : 2 : return true;
426 : : }
427 : :
428 : : static void
429 : 3 : replicate_with_brokenness(Xapian::DatabaseMaster & master,
430 : : Xapian::DatabaseReplica & replica,
431 : : const string & tempdir,
432 : : int expected_changesets,
433 : : int expected_fullcopies,
434 : : bool expected_changed)
435 : : {
436 : 3 : string changesetpath = tempdir + "/changeset";
437 : : get_changeset(changesetpath, master, replica,
438 : 3 : 1, 0, 1);
439 : :
440 : : // Try applying truncated changesets of various different lengths.
441 : 3 : string brokenchangesetpath = tempdir + "/changeset_broken";
442 : 3 : off_t filesize = file_size(changesetpath);
443 : 3 : off_t len = 10;
444 : : off_t copylen;
445 [ + + ]: 122 : while (len < filesize) {
446 : 119 : copylen = truncated_copy(changesetpath, brokenchangesetpath, len);
447 [ - + # # ]: 119 : TEST_EQUAL(copylen, len);
448 : : tout << "Trying replication with a changeset truncated to " << len <<
449 : 119 : " bytes, from " << filesize << " bytes\n";
450 [ + - ][ - + ]: 238 : TEST_EXCEPTION(Xapian::NetworkError,
[ # # ][ - + ]
451 : : apply_changeset(brokenchangesetpath, replica,
452 : : expected_changesets, expected_fullcopies,
453 : : expected_changed));
454 [ + + + + ]: 119 : if (len < 30 || len >= filesize - 10) {
455 : : // For lengths near the beginning and end, increment size by 1
456 : 90 : len += 1;
457 : : } else {
458 : : // Don't bother incrementing by small amounts in the middle of
459 : : // the changeset.
460 : 29 : len += 1000;
461 [ + + ]: 29 : if (len >= filesize - 10) {
462 : 3 : len = filesize - 10;
463 : : }
464 : : }
465 : : }
466 : 3 : return;
467 : : }
468 : :
469 : : // Test changesets which are truncated (and therefore invalid).
470 : 3 : DEFINE_TESTCASE(replicate3, replicas) {
471 : 3 : string tempdir = ".replicatmp";
472 : 3 : mktmpdir(tempdir);
473 : 3 : string masterpath = get_named_writable_database_path("master");
474 : :
475 : 3 : set_max_changesets(10);
476 : :
477 : 3 : Xapian::WritableDatabase orig(get_named_writable_database("master"));
478 : 3 : Xapian::DatabaseMaster master(masterpath);
479 : 3 : string replicapath = tempdir + "/replica";
480 : 3 : Xapian::DatabaseReplica replica(replicapath);
481 : :
482 : : // Add a document to the original database.
483 : 3 : Xapian::Document doc1;
484 : 3 : doc1.set_data(string("doc1"));
485 : 3 : doc1.add_posting("doc", 1);
486 : 3 : doc1.add_posting("one", 1);
487 : 3 : orig.add_document(doc1);
488 : 3 : orig.commit();
489 : :
490 [ - + # # ]: 3 : TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, 1), 1);
491 : 3 : check_equal_dbs(masterpath, replicapath);
492 : :
493 : : // Make a changeset.
494 : 3 : orig.add_document(doc1);
495 : 3 : orig.commit();
496 : :
497 : 3 : replicate_with_brokenness(master, replica, tempdir, 1, 0, 1);
498 : : // Although it throws an error, the final replication in
499 : : // replicate_with_brokenness() updates the database, since it's just the
500 : : // end-of-replication message which is missing its body.
501 : 3 : check_equal_dbs(masterpath, replicapath);
502 : :
503 : : // Check that the earlier broken replications didn't cause any problems for the
504 : : // next replication.
505 : 3 : orig.add_document(doc1);
506 : 3 : orig.commit();
507 [ - + # # ]: 3 : TEST_EQUAL(replicate(master, replica, tempdir, 1, 0, 1), 2);
508 : :
509 : : // Need to close the replicas before we remove the temporary directory on
510 : : // Windows.
511 : 3 : replica.close();
512 : 3 : rmtmpdir(tempdir);
513 : 3 : return true;
514 : : }
515 : :
516 : : // Tests for max_changesets
517 : 3 : DEFINE_TESTCASE(replicate4, replicas) {
518 : 3 : string tempdir = ".replicatmp";
519 : 3 : mktmpdir(tempdir);
520 : 3 : string masterpath = get_named_writable_database_path("master");
521 : :
522 : 3 : set_max_changesets(1);
523 : :
524 : 3 : Xapian::WritableDatabase orig(get_named_writable_database("master"));
525 : 3 : Xapian::DatabaseMaster master(masterpath);
526 : 3 : string replicapath = tempdir + "/replica";
527 : 3 : Xapian::DatabaseReplica replica(replicapath);
528 : :
529 : : // Add a document with no positions to the original database.
530 : 3 : Xapian::Document doc1;
531 : 3 : doc1.set_data(string("doc1"));
532 : 3 : doc1.add_term("nopos");
533 : 3 : orig.add_document(doc1);
534 : 3 : orig.commit();
535 : :
536 : : // Apply the replication - we don't have changesets stored, so this should
537 : : // just do a database copy, and return a count of 1.
538 : 3 : int count = replicate(master, replica, tempdir, 0, 1, 1);
539 [ - + # # ]: 3 : TEST_EQUAL(count, 1);
540 : : {
541 : 3 : Xapian::Database dbcopy(replicapath);
542 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
543 : : }
544 : :
545 : : // Add a document with positional information to the original database.
546 : 3 : doc1.add_posting("pos", 1);
547 : 3 : orig.add_document(doc1);
548 : 3 : orig.commit();
549 : :
550 : : // Replicate, and check that we have the positional information.
551 : 3 : count = replicate(master, replica, tempdir, 1, 0, 1);
552 [ - + # # ]: 3 : TEST_EQUAL(count, 2);
553 : : {
554 : 3 : Xapian::Database dbcopy(replicapath);
555 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
556 : : }
557 : 3 : check_equal_dbs(masterpath, replicapath);
558 : :
559 : : // Add a document with no positions to the original database.
560 : 3 : Xapian::Document doc2;
561 : 3 : doc2.set_data(string("doc2"));
562 : 3 : doc2.add_term("nopos");
563 : 3 : orig.add_document(doc2);
564 : 3 : orig.commit();
565 : :
566 : : // Replicate, and check that we have the positional information.
567 : 3 : count = replicate(master, replica, tempdir, 1, 0, 1);
568 [ - + # # ]: 3 : TEST_EQUAL(count, 2);
569 : : {
570 : 3 : Xapian::Database dbcopy(replicapath);
571 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
572 : : }
573 : 3 : check_equal_dbs(masterpath, replicapath);
574 [ - + ][ # # ]: 3 : TEST(!file_exists(masterpath + "/changes1"));
575 : :
576 : : // Turn off replication, make sure we dont write anything
577 : 3 : set_max_changesets(0);
578 : :
579 : : // Add a document with no positions to the original database.
580 : 3 : Xapian::Document doc3;
581 : 3 : doc3.set_data(string("doc3"));
582 : 3 : doc3.add_term("nonopos");
583 : 3 : orig.add_document(doc3);
584 : 3 : orig.commit();
585 : :
586 : : // Replicate, and check that we have the positional information.
587 : 3 : count = replicate(master, replica, tempdir, 0, 1, 1);
588 [ - + # # ]: 3 : TEST_EQUAL(count, 1);
589 : : {
590 : 3 : Xapian::Database dbcopy(replicapath);
591 [ - + ][ # # ]: 3 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
592 : : }
593 : : // Should have pulled a full copy
594 : 3 : check_equal_dbs(masterpath, replicapath);
595 [ - + ][ # # ]: 3 : TEST(!file_exists(masterpath + "/changes3"));
596 : :
597 : :
598 : : // Need to close the replica before we remove the temporary directory on
599 : : // Windows.
600 : 3 : replica.close();
601 : 3 : rmtmpdir(tempdir);
602 : 3 : return true;
603 : : }
604 : :
605 : :
606 : : // Tests for max_changesets
607 : 3 : DEFINE_TESTCASE(replicate5, replicas) {
608 : 5 : SKIP_TEST_FOR_BACKEND("chert");
609 : 4 : SKIP_TEST_FOR_BACKEND("flint");
610 : 1 : string tempdir = ".replicatmp";
611 : 1 : mktmpdir(tempdir);
612 : 1 : string masterpath = get_named_writable_database_path("master");
613 : :
614 : 1 : set_max_changesets(2);
615 : :
616 : 1 : Xapian::WritableDatabase orig(get_named_writable_database("master"));
617 : 1 : Xapian::DatabaseMaster master(masterpath);
618 : 1 : string replicapath = tempdir + "/replica";
619 : 1 : Xapian::DatabaseReplica replica(replicapath);
620 : :
621 : : // Add a document with no positions to the original database.
622 : 1 : Xapian::Document doc1;
623 : 1 : doc1.set_data(string("doc1"));
624 : 1 : doc1.add_term("nopos");
625 : 1 : orig.add_document(doc1);
626 : 1 : orig.commit();
627 : :
628 : : // Apply the replication - we don't have changesets stored, so this should
629 : : // just do a database copy, and return a count of 1.
630 : 1 : int count = replicate(master, replica, tempdir, 0, 1, 1);
631 [ - + # # ]: 1 : TEST_EQUAL(count, 1);
632 : : {
633 : 1 : Xapian::Database dbcopy(replicapath);
634 [ - + ][ # # ]: 1 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
635 : : }
636 : :
637 : : // Add a document with positional information to the original database.
638 : 1 : doc1.add_posting("pos", 1);
639 : 1 : orig.add_document(doc1);
640 : 1 : orig.commit();
641 : :
642 : : // Replicate, and check that we have the positional information.
643 : 1 : count = replicate(master, replica, tempdir, 1, 0, 1);
644 [ - + # # ]: 1 : TEST_EQUAL(count, 2);
645 : : {
646 : 1 : Xapian::Database dbcopy(replicapath);
647 [ - + ][ # # ]: 1 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
648 : : }
649 : 1 : check_equal_dbs(masterpath, replicapath);
650 : :
651 : : // Add a document with no positions to the original database.
652 : 1 : Xapian::Document doc2;
653 : 1 : doc2.set_data(string("doc2"));
654 : 1 : doc2.add_term("nopos");
655 : 1 : orig.add_document(doc2);
656 : 1 : orig.commit();
657 : :
658 : : // Replicate, and check that we have the positional information.
659 : 1 : count = replicate(master, replica, tempdir, 1, 0, 1);
660 [ - + # # ]: 1 : TEST_EQUAL(count, 2);
661 : : {
662 : 1 : Xapian::Database dbcopy(replicapath);
663 [ - + ][ # # ]: 1 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
664 : : }
665 : 1 : check_equal_dbs(masterpath, replicapath);
666 : :
667 : : // Add a document with no positions to the original database.
668 : 1 : Xapian::Document doc3;
669 : 1 : doc3.set_data(string("doc3"));
670 : 1 : doc3.add_term("nonopos");
671 : 1 : orig.add_document(doc3);
672 : 1 : orig.commit();
673 : :
674 : : // Replicate, and check that we have the positional information.
675 : 1 : count = replicate(master, replica, tempdir, 1, 0, 1);
676 [ - + # # ]: 1 : TEST_EQUAL(count, 2);
677 : : {
678 : 1 : Xapian::Database dbcopy(replicapath);
679 [ - + ][ # # ]: 1 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
680 : : }
681 : 1 : check_equal_dbs(masterpath, replicapath);
682 : :
683 : : // Ensure that only these changesets exists
684 [ - + ][ # # ]: 1 : TEST(!file_exists(masterpath + "/changes1"));
685 [ - + ][ # # ]: 1 : TEST(file_exists(masterpath + "/changes2"));
686 [ - + ][ # # ]: 1 : TEST(file_exists(masterpath + "/changes3"));
687 : :
688 : 1 : set_max_changesets(3);
689 : 1 : masterpath = get_named_writable_database_path("master");
690 : :
691 : : // Add a document with no positions to the original database.
692 : 1 : Xapian::Document doc4;
693 : 1 : doc4.set_data(string("doc4"));
694 : 1 : doc4.add_term("nononopos");
695 : 1 : orig.add_document(doc4);
696 : 1 : orig.commit();
697 : :
698 : : // Replicate, and check that we have the positional information.
699 : 1 : count = replicate(master, replica, tempdir, 1, 0, 1);
700 [ - + # # ]: 1 : TEST_EQUAL(count, 2);
701 : : {
702 : 1 : Xapian::Database dbcopy(replicapath);
703 [ - + ][ # # ]: 1 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
704 : : }
705 : 1 : check_equal_dbs(masterpath, replicapath);
706 : :
707 : : // Add a document with no positions to the original database.
708 : 1 : Xapian::Document doc5;
709 : 1 : doc5.set_data(string("doc5"));
710 : 1 : doc5.add_term("nonononopos");
711 : 1 : orig.add_document(doc5);
712 : 1 : orig.commit();
713 : :
714 : : // Replicate, and check that we have the positional information.
715 : 1 : count = replicate(master, replica, tempdir, 1, 0, 1);
716 [ - + # # ]: 1 : TEST_EQUAL(count, 2);
717 : : {
718 : 1 : Xapian::Database dbcopy(replicapath);
719 [ - + ][ # # ]: 1 : TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
720 : : }
721 : 1 : check_equal_dbs(masterpath, replicapath);
722 : :
723 [ - + ][ # # ]: 1 : TEST(!file_exists(masterpath + "/changes2"));
724 [ - + ][ # # ]: 1 : TEST(file_exists(masterpath + "/changes3"));
725 [ - + ][ # # ]: 1 : TEST(file_exists(masterpath + "/changes4"));
726 [ - + ][ # # ]: 1 : TEST(file_exists(masterpath + "/changes5"));
727 : :
728 : : // Need to close the replica before we remove the temporary directory on
729 : : // Windows.
730 : 1 : replica.close();
731 : 1 : rmtmpdir(tempdir);
732 : 1 : return true;
733 : : }
|