Merge lp:~mterry/duplicity/static-corruption into lp:duplicity/0.6
- static-corruption
- Merge into 0.6-series
Status: | Merged |
---|---|
Merged at revision: | 903 |
Proposed branch: | lp:~mterry/duplicity/static-corruption |
Merge into: | lp:duplicity/0.6 |
Diff against target: |
498 lines (+188/-47) 6 files modified
bin/duplicity (+13/-5) duplicity/diffdir.py (+21/-19) duplicity/dup_temp.py (+4/-2) duplicity/gpg.py (+4/-6) testing/tests/gpgtest.py (+10/-5) testing/tests/restarttest.py (+136/-10) |
To merge this branch: | bzr merge lp:~mterry/duplicity/static-corruption |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
duplicity-team | Pending | ||
Review via email: mp+142044@code.launchpad.net |
Commit message
Description of the change
This branch fixes three possible ways a backup could get data-corrupted. Inspired by bug 1091269.
A) If resuming after a volume that ended in a one-block file, we would skip the first block of the next file.
B) If resuming after a volume that ended in a multi-block file, we would skip the first block of the next file.
C) If resuming after a volume that spanned a multi-block file, we would skip some data inside the file.
A and B are because when finding the right place in the source files to restart the backup, the iteration loop didn't handle None block numbers very well (which are used to indicate the end of a file).
C is what bug 1091269 talks about. This was because data block sizes would get smaller as the difftar file got closer and closer to the volsize. Standard block sizes were 64 * 1024. But say we were close to the end of the difftar... When resuming, duplicity doesn't know the custom block sizes used by the previous run, so it uses standard block sizes. And it doesn't always match up, as you can imagine. So we would leave chunks of data out of the backed up file.
Tests added for these cases.
This branch is called 'static-corruption' because all these issues occur even when the source data doesn't change. I still think there are some corruption issues when a file changes in between duplicity runs. I haven't started looking into that yet, but that's next on my list.
Michael Terry (mterry) wrote : | # |
- 909. By Michael Terry
-
whoops, fix a test I broke with last commit because I didn't add get_read_size to the mock writer
- 910. By Michael Terry
-
add test to confirm expected behavior if new files appear before we restart a backup
Michael Terry (mterry) wrote : | # |
Eh, I mentioned looking into fixing issues around when a file changes in between duplicity runs. I don't think there's corruption, just the expected "first half is different than second half" issue. Not sure what we want to do about that if anything.
This branch is ready for review.
- 911. By Michael Terry
-
use a copy of file1 rather than writing out a new small file so that it is clearer the first block is skipped rather than the whole file
Preview Diff
1 | === modified file 'bin/duplicity' |
2 | --- bin/duplicity 2012-11-21 01:27:35 +0000 |
3 | +++ bin/duplicity 2013-01-07 16:15:25 +0000 |
4 | @@ -223,7 +223,13 @@ |
5 | # Just spin our wheels |
6 | while tarblock_iter.next(): |
7 | if (tarblock_iter.previous_index == last_index): |
8 | - if (tarblock_iter.previous_block > last_block): |
9 | + # If both the previous index and this index are done, exit now |
10 | + # before we hit the next index, to prevent skipping its first |
11 | + # block. |
12 | + if not last_block and not tarblock_iter.previous_block: |
13 | + break |
14 | + # Only check block number if last_block is also a number |
15 | + if last_block and tarblock_iter.previous_block > last_block: |
16 | break |
17 | if tarblock_iter.previous_index > last_index: |
18 | log.Warn(_("File %s complete in backup set.\n" |
19 | @@ -937,11 +943,10 @@ |
20 | """ |
21 | Copy data from src_iter to file at fn |
22 | """ |
23 | - block_size = 128 * 1024 |
24 | file = open(filename, "wb") |
25 | while True: |
26 | try: |
27 | - data = src_iter.next(block_size).data |
28 | + data = src_iter.next().data |
29 | except StopIteration: |
30 | break |
31 | file.write(data) |
32 | @@ -989,9 +994,9 @@ |
33 | def __init__(self, fileobj): |
34 | self.fileobj = fileobj |
35 | |
36 | - def next(self, size): |
37 | + def next(self): |
38 | try: |
39 | - res = Block(self.fileobj.read(size)) |
40 | + res = Block(self.fileobj.read(self.get_read_size())) |
41 | except Exception: |
42 | if hasattr(self.fileobj, 'name'): |
43 | name = self.fileobj.name |
44 | @@ -1005,6 +1010,9 @@ |
45 | raise StopIteration |
46 | return res |
47 | |
48 | + def get_read_size(self): |
49 | + return 128 * 1024 |
50 | + |
51 | def get_footer(self): |
52 | return "" |
53 | |
54 | |
55 | === modified file 'duplicity/diffdir.py' |
56 | --- duplicity/diffdir.py 2012-09-28 15:48:21 +0000 |
57 | +++ duplicity/diffdir.py 2013-01-07 16:15:25 +0000 |
58 | @@ -481,14 +481,14 @@ |
59 | filler_data = "" |
60 | return TarBlock(index, "%s%s%s" % (headers, file_data, filler_data)) |
61 | |
62 | - def process(self, val, size): |
63 | + def process(self, val): |
64 | """ |
65 | Turn next value of input_iter into a TarBlock |
66 | """ |
67 | assert not self.process_waiting |
68 | XXX # Override in subclass @UndefinedVariable |
69 | |
70 | - def process_continued(self, size): |
71 | + def process_continued(self): |
72 | """ |
73 | Get more tarblocks |
74 | |
75 | @@ -498,15 +498,15 @@ |
76 | assert self.process_waiting |
77 | XXX # Override in subclass @UndefinedVariable |
78 | |
79 | - def next(self, size = 1024 * 1024): |
80 | + def next(self): |
81 | """ |
82 | - Return next block, no bigger than size, and update offset |
83 | + Return next block and update offset |
84 | """ |
85 | if self.process_waiting: |
86 | - result = self.process_continued(size) |
87 | + result = self.process_continued() |
88 | else: |
89 | # Below a StopIteration exception will just be passed upwards |
90 | - result = self.process(self.input_iter.next(), size) |
91 | + result = self.process(self.input_iter.next()) |
92 | block_number = self.process_next_vol_number |
93 | self.offset += len(result.data) |
94 | self.previous_index = result.index |
95 | @@ -517,6 +517,13 @@ |
96 | self.remember_next = False |
97 | return result |
98 | |
99 | + def get_read_size(self): |
100 | + # read size must always be the same, because if we are restarting a |
101 | + # backup volume where the previous volume ended in a data block, we |
102 | + # have to be able to assume it's length in order to continue reading |
103 | + # the file from the right place. |
104 | + return 64 * 1024 |
105 | + |
106 | def get_previous_index(self): |
107 | """ |
108 | Return index of last tarblock, or None if no previous index |
109 | @@ -553,7 +560,7 @@ |
110 | """ |
111 | TarBlockIter that does no file reading |
112 | """ |
113 | - def process(self, delta_ropath, size): |
114 | + def process(self, delta_ropath): |
115 | """ |
116 | Get a fake tarblock from delta_ropath |
117 | """ |
118 | @@ -577,13 +584,9 @@ |
119 | """ |
120 | TarBlockIter that yields blocks of a signature tar from path_iter |
121 | """ |
122 | - def process(self, path, size): |
123 | + def process(self, path): |
124 | """ |
125 | Return associated signature TarBlock from path |
126 | - |
127 | - Here size is just ignored --- let's hope a signature isn't too |
128 | - big. Also signatures are stored in multiple volumes so it |
129 | - doesn't matter. |
130 | """ |
131 | ti = path.get_tarinfo() |
132 | if path.isreg(): |
133 | @@ -606,7 +609,7 @@ |
134 | delta_path_iter, so the delta information has already been |
135 | calculated. |
136 | """ |
137 | - def process(self, delta_ropath, size): |
138 | + def process(self, delta_ropath): |
139 | """ |
140 | Get a tarblock from delta_ropath |
141 | """ |
142 | @@ -631,8 +634,7 @@ |
143 | |
144 | # Now handle single volume block case |
145 | fp = delta_ropath.open("rb") |
146 | - # Below the 512 is the usual length of a tar header |
147 | - data, last_block = self.get_data_block(fp, size - 512) |
148 | + data, last_block = self.get_data_block(fp) |
149 | if stats: |
150 | stats.RawDeltaSize += len(data) |
151 | if last_block: |
152 | @@ -654,11 +656,11 @@ |
153 | self.process_next_vol_number = 2 |
154 | return self.tarinfo2tarblock(index, ti, data) |
155 | |
156 | - def get_data_block(self, fp, max_size): |
157 | + def get_data_block(self, fp): |
158 | """ |
159 | Return pair (next data block, boolean last data block) |
160 | """ |
161 | - read_size = min(64*1024, max(max_size, 512)) |
162 | + read_size = self.get_read_size() |
163 | buf = fp.read(read_size) |
164 | if len(buf) < read_size: |
165 | if fp.close(): |
166 | @@ -667,7 +669,7 @@ |
167 | else: |
168 | return (buf, False) |
169 | |
170 | - def process_continued(self, size): |
171 | + def process_continued(self): |
172 | """ |
173 | Return next volume in multivol diff or snapshot |
174 | """ |
175 | @@ -675,7 +677,7 @@ |
176 | ropath = self.process_ropath |
177 | ti, index = ropath.get_tarinfo(), ropath.index |
178 | ti.name = "%s/%d" % (self.process_prefix, self.process_next_vol_number) |
179 | - data, last_block = self.get_data_block(self.process_fp, size - 512) |
180 | + data, last_block = self.get_data_block(self.process_fp) |
181 | if stats: |
182 | stats.RawDeltaSize += len(data) |
183 | if last_block: |
184 | |
185 | === modified file 'duplicity/dup_temp.py' |
186 | --- duplicity/dup_temp.py 2012-09-28 15:48:21 +0000 |
187 | +++ duplicity/dup_temp.py 2013-01-07 16:15:25 +0000 |
188 | @@ -256,9 +256,9 @@ |
189 | def __init__(self, src): |
190 | self.src = src |
191 | self.fp = src.open("rb") |
192 | - def next(self, size): |
193 | + def next(self): |
194 | try: |
195 | - res = Block(self.fp.read(size)) |
196 | + res = Block(self.fp.read(self.get_read_size())) |
197 | except Exception: |
198 | log.FatalError(_("Failed to read %s: %s") % |
199 | (self.src.name, sys.exc_info()), |
200 | @@ -267,5 +267,7 @@ |
201 | self.fp.close() |
202 | raise StopIteration |
203 | return res |
204 | + def get_read_size(self): |
205 | + return 128 * 1024 |
206 | def get_footer(self): |
207 | return "" |
208 | |
209 | === modified file 'duplicity/gpg.py' |
210 | --- duplicity/gpg.py 2013-01-02 14:02:35 +0000 |
211 | +++ duplicity/gpg.py 2013-01-07 16:15:25 +0000 |
212 | @@ -319,17 +319,16 @@ |
213 | def get_current_size(): |
214 | return os.stat(filename).st_size |
215 | |
216 | - block_size = 128 * 1024 # don't bother requesting blocks smaller, but also don't ask for bigger |
217 | target_size = size - 50 * 1024 # fudge factor, compensate for gpg buffering |
218 | data_size = target_size - max_footer_size |
219 | file = GPGFile(True, path.Path(filename), profile) |
220 | at_end_of_blockiter = 0 |
221 | while True: |
222 | bytes_to_go = data_size - get_current_size() |
223 | - if bytes_to_go < block_size: |
224 | + if bytes_to_go < block_iter.get_read_size(): |
225 | break |
226 | try: |
227 | - data = block_iter.next(min(block_size, bytes_to_go)).data |
228 | + data = block_iter.next().data |
229 | except StopIteration: |
230 | at_end_of_blockiter = 1 |
231 | break |
232 | @@ -373,16 +372,15 @@ |
233 | def close(self): |
234 | return self.fileobj.close() |
235 | |
236 | - block_size = 64 * 1024 |
237 | file_counted = FileCounted(open(filename, "wb")) |
238 | gzip_file = gzip.GzipFile(None, "wb", 6, file_counted) |
239 | at_end_of_blockiter = 0 |
240 | while True: |
241 | bytes_to_go = size - file_counted.byte_count |
242 | - if bytes_to_go < block_size: |
243 | + if bytes_to_go < block_iter.get_read_size(): |
244 | break |
245 | try: |
246 | - new_block = block_iter.next(min(block_size, bytes_to_go)) |
247 | + new_block = block_iter.next() |
248 | except StopIteration: |
249 | at_end_of_blockiter = 1 |
250 | break |
251 | |
252 | === modified file 'testing/tests/gpgtest.py' |
253 | --- testing/tests/gpgtest.py 2012-11-24 19:45:09 +0000 |
254 | +++ testing/tests/gpgtest.py 2013-01-07 16:15:25 +0000 |
255 | @@ -165,7 +165,7 @@ |
256 | #print os.stat("testfiles/output/gzwrite.gz").st_size-size |
257 | assert size - 64 * 1024 <= os.stat("testfiles/output/gzwrite.gz").st_size <= size + 64 * 1024 |
258 | gwfh.set_at_end() |
259 | - gpg.GzipWriteFile(gwfh, "testfiles/output/gzwrite.gpg", size = size) |
260 | + gpg.GzipWriteFile(gwfh, "testfiles/output/gzwrite.gz", size = size) |
261 | #print os.stat("testfiles/output/gzwrite.gz").st_size |
262 | |
263 | |
264 | @@ -188,13 +188,18 @@ |
265 | s2 = size - s1 |
266 | return "a"*s1 + self.from_random_fp.read(s2) |
267 | |
268 | - def next(self, size): |
269 | + def next(self): |
270 | if self.at_end: raise StopIteration |
271 | - if random.randrange(2): real_size = size |
272 | - else: real_size = random.randrange(0, size) |
273 | - block_data = self.get_buffer(real_size) |
274 | + block_data = self.get_buffer(self.get_read_size()) |
275 | return GPGWriteHelper2(block_data) |
276 | |
277 | + def get_read_size(self): |
278 | + size = 64 * 1024 |
279 | + if random.randrange(2): |
280 | + return size |
281 | + else: |
282 | + return random.randrange(0, size) |
283 | + |
284 | def get_footer(self): |
285 | return "e" * random.randrange(0, 15000) |
286 | |
287 | |
288 | === modified file 'testing/tests/restarttest.py' |
289 | --- testing/tests/restarttest.py 2012-11-24 19:45:09 +0000 |
290 | +++ testing/tests/restarttest.py 2013-01-07 16:15:25 +0000 |
291 | @@ -36,7 +36,6 @@ |
292 | other_args = ["-v0", "--no-print-statistics"] |
293 | #other_args = ["--short-filenames"] |
294 | #other_args = ["--ssh-command 'ssh -v'", "--scp-command 'scp -C'"] |
295 | -#other_args = ['--no-encryption'] |
296 | |
297 | # If this is set to true, after each backup, verify contents |
298 | verify = 1 |
299 | @@ -52,8 +51,9 @@ |
300 | Test checkpoint/restart using duplicity binary |
301 | """ |
302 | def setUp(self): |
303 | + self.class_args = [] |
304 | assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1") |
305 | - assert not os.system("rm -rf testfiles/output " |
306 | + assert not os.system("rm -rf testfiles/output testfiles/largefiles " |
307 | "testfiles/restore_out testfiles/cache") |
308 | assert not os.system("mkdir testfiles/output testfiles/cache") |
309 | backend = duplicity.backend.get_backend(backend_url) |
310 | @@ -80,6 +80,7 @@ |
311 | cmd_list.append("--current-time %s" % (current_time,)) |
312 | if other_args: |
313 | cmd_list.extend(other_args) |
314 | + cmd_list.extend(self.class_args) |
315 | cmd_list.extend(arglist) |
316 | cmd_list.extend(["<", "/dev/null"]) |
317 | cmdline = " ".join(cmd_list) |
318 | @@ -144,11 +145,14 @@ |
319 | self.verify(dirname, |
320 | time = current_time, options = restore_options) |
321 | |
322 | - def make_largefiles(self): |
323 | - # create 3 2M files |
324 | + def make_largefiles(self, count=3, size=2): |
325 | + """ |
326 | + Makes a number of large files in testfiles/largefiles that each are |
327 | + the specified number of megabytes. |
328 | + """ |
329 | assert not os.system("mkdir testfiles/largefiles") |
330 | - for n in (1,2,3): |
331 | - assert not os.system("dd if=/dev/urandom of=testfiles/largefiles/file%d bs=1024 count=2048 > /dev/null 2>&1" % n) |
332 | + for n in range(count): |
333 | + assert not os.system("dd if=/dev/urandom of=testfiles/largefiles/file%d bs=1024 count=%d > /dev/null 2>&1" % (n + 1, size * 1024)) |
334 | |
335 | def check_same(self, filename1, filename2): |
336 | """ |
337 | @@ -327,6 +331,128 @@ |
338 | self.backup("inc", "testfiles/largefiles") |
339 | self.verify("testfiles/largefiles") |
340 | |
341 | + def make_fake_second_volume(self, name): |
342 | + """ |
343 | + Takes a successful backup and pretend that we interrupted a backup |
344 | + after two-volumes. (This is because we want to be able to model |
345 | + restarting the second volume and duplicity deletes the last volume |
346 | + found because it may have not finished uploading.) |
347 | + """ |
348 | + # First, confirm that we have signs of a successful backup |
349 | + self.assertEqual(len(glob.glob("testfiles/output/*.manifest*")), 1) |
350 | + self.assertEqual(len(glob.glob("testfiles/output/*.sigtar*")), 1) |
351 | + self.assertEqual(len(glob.glob("testfiles/cache/%s/*" % name)), 2) |
352 | + self.assertEqual(len(glob.glob( |
353 | + "testfiles/cache/%s/*.manifest*" % name)), 1) |
354 | + self.assertEqual(len(glob.glob( |
355 | + "testfiles/cache/%s/*.sigtar*" % name)), 1) |
356 | + # Alright, everything is in order; fake a second interrupted volume |
357 | + assert not os.system("rm testfiles/output/*.manifest*") |
358 | + assert not os.system("rm testfiles/output/*.sigtar*") |
359 | + assert not os.system("rm -f testfiles/output/*.vol[23456789].*") |
360 | + assert not os.system("rm -f testfiles/output/*.vol1[^.]+.*") |
361 | + self.assertEqual(len(glob.glob("testfiles/output/*.difftar*")), 1) |
362 | + assert not os.system("rm testfiles/cache/%s/*.sigtar*" % name) |
363 | + assert not os.system("cp testfiles/output/*.difftar* " |
364 | + "`ls testfiles/output/*.difftar* | " |
365 | + " sed 's|vol1|vol2|'`") |
366 | + assert not os.system("head -n6 testfiles/cache/%s/*.manifest > " |
367 | + "testfiles/cache/%s/" |
368 | + "`basename testfiles/cache/%s/*.manifest`" |
369 | + ".part" % (name, name, name)) |
370 | + assert not os.system("rm testfiles/cache/%s/*.manifest" % name) |
371 | + assert not os.system("""echo 'Volume 2: |
372 | + StartingPath foo |
373 | + EndingPath bar |
374 | + Hash SHA1 sha1' >> testfiles/cache/%s/*.manifest.part""" % name) |
375 | + |
376 | + def test_split_after_small(self): |
377 | + """ |
378 | + If we restart right after a volume that ended with a small |
379 | + (one-block) file, make sure we restart in the right place. |
380 | + """ |
381 | + source = 'testfiles/largefiles' |
382 | + assert not os.system("mkdir -p %s" % source) |
383 | + assert not os.system("echo hello > %s/file1" % source) |
384 | + self.backup("full", source, options=["--name=backup1"]) |
385 | + # Fake an interruption |
386 | + self.make_fake_second_volume("backup1") |
387 | + # Add new file |
388 | + assert not os.system("cp %s/file1 %s/newfile" % (source, source)) |
389 | + # 'restart' the backup |
390 | + self.backup("full", source, options=["--name=backup1"]) |
391 | + # Confirm we actually resumed the previous backup |
392 | + self.assertEqual(len(os.listdir("testfiles/output")), 4) |
393 | + # Now make sure everything is byte-for-byte the same once restored |
394 | + self.restore() |
395 | + assert not os.system("diff -r %s testfiles/restore_out" % source) |
396 | + |
397 | + def test_split_after_large(self): |
398 | + """ |
399 | + If we restart right after a volume that ended with a large |
400 | + (multi-block) file, make sure we restart in the right place. |
401 | + """ |
402 | + source = 'testfiles/largefiles' |
403 | + self.make_largefiles(count=1, size=1) |
404 | + self.backup("full", source, options=["--name=backup1"]) |
405 | + # Fake an interruption |
406 | + self.make_fake_second_volume("backup1") |
407 | + # Add new file |
408 | + assert not os.system("cp %s/file1 %s/newfile" % (source, source)) |
409 | + # 'restart' the backup |
410 | + self.backup("full", source, options=["--name=backup1"]) |
411 | + # Confirm we actually resumed the previous backup |
412 | + self.assertEqual(len(os.listdir("testfiles/output")), 4) |
413 | + # Now make sure everything is byte-for-byte the same once restored |
414 | + self.restore() |
415 | + assert not os.system("diff -r %s testfiles/restore_out" % source) |
416 | + |
417 | + def test_split_inside_large(self): |
418 | + """ |
419 | + If we restart right after a volume that ended inside of a large |
420 | + (multi-block) file, make sure we restart in the right place. |
421 | + """ |
422 | + source = 'testfiles/largefiles' |
423 | + self.make_largefiles(count=1, size=3) |
424 | + self.backup("full", source, options=["--vols 1", "--name=backup1"]) |
425 | + # Fake an interruption |
426 | + self.make_fake_second_volume("backup1") |
427 | + # 'restart' the backup |
428 | + self.backup("full", source, options=["--vols 1", "--name=backup1"]) |
429 | + # Now make sure everything is byte-for-byte the same once restored |
430 | + self.restore() |
431 | + assert not os.system("diff -r %s testfiles/restore_out" % source) |
432 | + |
433 | + def test_new_file(self): |
434 | + """ |
435 | + If we restart right after a volume, but there are new files that would |
436 | + have been backed up earlier in the volume, make sure we don't wig out. |
437 | + (Expected result is to ignore new, ealier files, but pick up later |
438 | + ones.) |
439 | + """ |
440 | + source = 'testfiles/largefiles' |
441 | + self.make_largefiles(count=1, size=1) |
442 | + self.backup("full", source, options=["--name=backup1"]) |
443 | + # Fake an interruption |
444 | + self.make_fake_second_volume("backup1") |
445 | + # Add new files, earlier and later in filename sort order |
446 | + assert not os.system("echo hello > %s/a" % source) |
447 | + assert not os.system("echo hello > %s/z" % source) |
448 | + # 'restart' the backup |
449 | + self.backup("full", source, options=["--name=backup1"]) |
450 | + # Now make sure everything is the same once restored, except 'a' |
451 | + self.restore() |
452 | + assert not os.system("test ! -e testfiles/restore_out/a") |
453 | + assert not os.system("diff %s/file1 testfiles/restore_out/file1" % source) |
454 | + assert not os.system("diff %s/z testfiles/restore_out/z" % source) |
455 | + |
456 | + |
457 | +# Note that this class duplicates all the tests in RestartTest |
458 | +class RestartTestWithoutEncryption(RestartTest): |
459 | + def setUp(self): |
460 | + RestartTest.setUp(self) |
461 | + self.class_args.extend(["--no-encryption"]) |
462 | + |
463 | def test_no_write_double_snapshot(self): |
464 | """ |
465 | Test that restarting a full backup does not write duplicate entries |
466 | @@ -337,12 +463,12 @@ |
467 | self.make_largefiles() |
468 | # Start backup |
469 | try: |
470 | - self.backup("full", "testfiles/largefiles", options = ["--fail 2", "--vols 1", "--no-encryption"]) |
471 | + self.backup("full", "testfiles/largefiles", options = ["--fail 2", "--vols 1"]) |
472 | self.fail() |
473 | except CmdError, e: |
474 | self.assertEqual(30, e.exit_status) |
475 | # Finish it |
476 | - self.backup("full", "testfiles/largefiles", options = ["--no-encryption"]) |
477 | + self.backup("full", "testfiles/largefiles") |
478 | # Now check sigtar |
479 | sigtars = glob.glob("testfiles/output/duplicity-full*.sigtar.gz") |
480 | self.assertEqual(1, len(sigtars)) |
481 | @@ -359,7 +485,7 @@ |
482 | https://launchpad.net/bugs/929067 |
483 | """ |
484 | # Intial normal backup |
485 | - self.backup("full", "testfiles/blocktartest", options = ["--no-encryption"]) |
486 | + self.backup("full", "testfiles/blocktartest") |
487 | # Create an exact clone of the snapshot folder in the sigtar already. |
488 | # Permissions and mtime must match. |
489 | os.mkdir("testfiles/snapshot", 0755) |
490 | @@ -379,7 +505,7 @@ |
491 | self.assertEqual(0, os.system("rm -r testfiles/cache")) |
492 | # Try a follow on incremental (which in buggy versions, would create |
493 | # a deleted entry for the base dir) |
494 | - self.backup("inc", "testfiles/blocktartest", options = ["--no-encryption"]) |
495 | + self.backup("inc", "testfiles/blocktartest") |
496 | self.assertEqual(1, len(glob.glob("testfiles/output/duplicity-new*.sigtar.gz"))) |
497 | # Confirm we can restore it (which in buggy versions, would fail) |
498 | self.restore() |
I forgot to mention, C only happened without encryption (because the gpg writer function already happened to force a constant data block size). A and B happened with or without encryption.