adjust post-conditions for GZ[,AES]/rescue unit test
[python-delta-tar] / testing / test_recover.py
CommitLineData
fbdc9f4a
PG
1import logging
2import os
3import shutil
3692fd82 4import stat
fbdc9f4a 5
2fe5f6e7
PG
6from functools import partial
7
fbdc9f4a 8import deltatar.deltatar as deltatar
3267933a 9import deltatar.crypto as crypto
203cb25e 10import deltatar.tarfile as tarfile
fbdc9f4a
PG
11
12from . import BaseTest
13
e25f31ac 14TEST_PASSWORD = "test1234"
85e7013f 15TEST_VOLSIZ = 2 # MB
e25f31ac 16TEST_FILESPERVOL = 3
85e7013f
PG
17VOLUME_OVERHEAD = 1.4 # account for tar overhead when fitting files into
18 # volumes; this is black magic
20e1d773 19TEST_BLOCKSIZE = 4096
96fe6399
PG
20
21###############################################################################
22## helpers ##
23###############################################################################
24
3267933a
PG
25def flip_bits (fname, off, b=0x01, n=1):
26 """
27 Open file *fname* at offset *off*, replacing the next *n* bytes with
28 their values xor’ed with *b*.
29 """
30 fd = os.open (fname, os.O_RDWR)
203cb25e 31
3267933a
PG
32 try:
33 pos = os.lseek (fd, off, os.SEEK_SET)
34 assert pos == off
35 chunk = os.read (fd, n)
36 chunk = bytes (map (lambda v: v ^ b, chunk))
da8996f0
PG
37 pos = os.lseek (fd, off, os.SEEK_SET)
38 assert pos == off
3267933a
PG
39 os.write (fd, chunk)
40 finally:
41 os.close (fd)
42
203cb25e
PG
43
44def gz_header_size (fname, off=0):
45 """
46 Determine the length of the gzip header starting at *off* in file fname.
47
48 The header is variable length because it may contain the filename as NUL
49 terminated bytes.
50 """
51 # length so we need to determine where the actual payload starts
52 off = tarfile.GZ_HEADER_SIZE
53 fd = os.open (fname, os.O_RDONLY)
54
55 try:
56 pos = os.lseek (fd, off, os.SEEK_SET)
57 assert pos == off
58 while os.read (fd, 1)[0] != 0:
59 off += 1
60 pos = os.lseek (fd, off, os.SEEK_SET)
61 assert pos == off
62 finally:
63 os.close (fd)
64
65 return off
66
da8996f0 67
96fe6399
PG
68def is_pdt_encrypted (fname):
69 """
70 Returns true if the file contains at least one PDT header plus enough
71 space for the object.
72 """
73 try:
74 with open (fname, "rb") as st:
75 hdr = crypto.hdr_read_stream (st)
76 siz = hdr ["ctsize"]
77 assert (len (st.read (siz)) == siz)
78 except Exception as exn:
79 return False
80 return True
81
82
3692fd82
PG
83###############################################################################
84## corruption simulators ##
85###############################################################################
86
00b8c150
PG
87def corrupt_header (_, fname, compress, encrypt):
88 """
89 Modify a significant byte in the object header of the format.
90 """
91 if encrypt is True: # damage GCM tag
92 flip_bits (fname, crypto.HDR_OFF_TAG + 1)
93 elif compress is True: # invalidate magic
94 flip_bits (fname, 1)
95 else: # Fudge checksum. From tar(5):
96 #
97 # struct header_gnu_tar {
98 # char name[100];
99 # char mode[8];
100 # char uid[8];
101 # char gid[8];
102 # char size[12];
103 # char mtime[12];
104 # char checksum[8];
105 # …
106 flip_bits (fname, 100 + 8 + 8 + 8 + 12 + 12 + 1)
107
108
da8996f0
PG
109def corrupt_entire_header (_, fname, compress, encrypt):
110 """
111 Flip all bits in the first object header.
112 """
113 if encrypt is True:
114 flip_bits (fname, 0, 0xff, crypto.PDTCRYPT_HDR_SIZE)
115 elif compress is True: # invalidate magic
116 flip_bits (fname, 0, 0xff, gz_header_size (fname))
117 else:
118 flip_bits (fname, 0, 0xff, tarfile.BLOCKSIZE)
119
120
00b8c150
PG
121def corrupt_payload_start (_, fname, compress, encrypt):
122 """
123 Modify the byte following the object header structure of the format.
124 """
125 if encrypt is True:
126 flip_bits (fname, crypto.PDTCRYPT_HDR_SIZE + 1)
127 elif compress is True:
128 flip_bits (fname, gz_header_size (fname) + 1)
129 else:
130 flip_bits (fname, tarfile.BLOCKSIZE + 1)
131
132
517d35b7
PG
133def corrupt_trailing_data (_, fname, compress, encrypt):
134 """
135 Modify the byte following the object header structure of the format.
136 """
137 junk = os.urandom (42)
138 fd = os.open (fname, os.O_WRONLY | os.O_APPEND)
139 os.write (fd, junk)
140 os.close (fd)
141
00b8c150 142
20e1d773
PG
143def corrupt_volume (_, fname, compress, encrypt):
144 """
145 Zero out an entire volume.
146 """
147 fd = os.open (fname, os.O_WRONLY)
148 size = os.lseek (fd, 0, os.SEEK_END)
149 assert os.lseek (fd, 0, os.SEEK_SET) == 0
150 zeros = bytes (b'\x00' * TEST_BLOCKSIZE)
151 while size > 0:
152 todo = min (size, TEST_BLOCKSIZE)
153 os.write (fd, zeros [:todo])
154 size -= todo
155 os.close (fd)
156
157
3692fd82
PG
158def corrupt_hole (_, fname, compress, encrypt):
159 """
160 Cut file in three pieces, reassemble without the middle one.
161 """
162 aname = os.path.abspath (fname)
163 infd = os.open (fname, os.O_RDONLY)
164 size = os.lseek (infd, 0, os.SEEK_END)
165 assert os.lseek (infd, 0, os.SEEK_SET) == 0
166 assert size > 3 * TEST_BLOCKSIZE
167 hole = (size / 3, size * 2 / 3)
168 outfd = os.open (os.path.dirname (aname), os.O_WRONLY | os.O_TMPFILE,
169 stat.S_IRUSR | stat.S_IWUSR)
170
171 zeros = bytes (b'\x00' * TEST_BLOCKSIZE)
172 done = 0
173 while done < size:
174 data = os.read (infd, TEST_BLOCKSIZE)
175 if done < hole [0] or hole [1] < done:
176 # only copy from outside hole
177 os.write (outfd, data)
178 done += len (data)
179
180 os.close (infd)
181 os.unlink (fname)
182
183 path = "/proc/self/fd/%d" % outfd
184 os.link (path, aname, src_dir_fd=0, follow_symlinks=True)
185 os.close (outfd)
186
2fe5f6e7
PG
187def immaculate (_, _fname, _compress, _encrypt):
188 """
189 No-op dummy.
190 """
191 pass
3692fd82 192
96fe6399
PG
193###############################################################################
194## tests ##
195###############################################################################
203cb25e 196
0c6682ce 197class DefectiveTest (BaseTest):
fbdc9f4a
PG
198 """
199 Disaster recovery: restore corrupt backups.
200 """
201
96fe6399
PG
202 COMPRESSION = None
203 PASSWORD = None
9d89c237
PG
204 FAILURES = 0 # files that could not be restored
205 MISMATCHES = 0 # files that were restored but corrupted
00b8c150 206 CORRUPT = corrupt_payload_start
e25f31ac 207 VOLUMES = 1
4d4925de 208 MISSING = None # normally the number of failures
96fe6399 209
fbdc9f4a
PG
210
211 def setUp(self):
212 '''
213 Create base test data
214 '''
96fe6399
PG
215 self.pwd = os.getcwd()
216 self.dst_path = "source_dir"
217 self.src_path = "%s2" % self.dst_path
218 self.hash = dict()
219
fbdc9f4a 220 os.system('rm -rf target_dir source_dir* backup_dir* huge')
96fe6399 221 os.makedirs (self.src_path)
fbdc9f4a 222
96fe6399 223 for i in range (5):
85e7013f 224 f = "dummy_%d" % i
96fe6399
PG
225 self.hash [f] = self.create_file ("%s/%s"
226 % (self.src_path, f), 5 + i)
fbdc9f4a 227
96fe6399
PG
228
229 def tearDown(self):
230 '''
231 Remove temporal files created by unit tests and reset globals.
232 '''
233 os.chdir(self.pwd)
234 os.system("rm -rf source_dir source_dir2 backup_dir*")
fbdc9f4a
PG
235
236
2fe5f6e7
PG
237 @staticmethod
238 def default_volume_name (backup_file, _x, _y, n, *a, **kwa):
239 return backup_file % n
0c6682ce 240
2fe5f6e7 241 def gen_file_names (self, comp, pw):
203cb25e 242 bak_path = "backup_dir"
e25f31ac
PG
243 backup_file = "the_full_backup_%0.2d.tar"
244 backup_full = ("%s/%s" % (bak_path, backup_file)) % 0
96fe6399
PG
245 index_file = "the_full_index"
246
247 if self.COMPRESSION is not None:
248 backup_file += ".gz"
249 backup_full += ".gz"
250 index_file += ".gz"
251
252 if self.PASSWORD is not None:
e25f31ac
PG
253 backup_file = "%s.%s" % (backup_file, deltatar.PDTCRYPT_EXTENSION)
254 backup_full = "%s.%s" % (backup_full, deltatar.PDTCRYPT_EXTENSION)
255 index_file = "%s.%s" % (index_file , deltatar.PDTCRYPT_EXTENSION)
256
2fe5f6e7
PG
257 return bak_path, backup_file, backup_full, index_file
258
259
260class RecoverTest (DefectiveTest):
261 """
262 Recover: restore corrupt backups from index file information.
263 """
264
265 def test_recover_corrupt (self):
266 """
267 Perform various damaging actions that cause unreadable objects.
268
269 Expects the extraction to fail in normal mode. With disaster recovery,
270 extraction must succeed, and exactly one file must be missing.
271 """
272 mode = self.COMPRESSION or "#"
273 bak_path, backup_file, backup_full, index_file = \
274 self.gen_file_names (self.COMPRESSION, self.PASSWORD)
275
e25f31ac 276 if self.VOLUMES > 1:
85e7013f
PG
277 # add n files for one nth the volume size each, corrected
278 # for metadata and tar block overhead
279 fsiz = int ( ( TEST_VOLSIZ
280 / (TEST_FILESPERVOL * VOLUME_OVERHEAD))
281 * 1024 * 1024)
282 fcnt = (self.VOLUMES - 1) * TEST_FILESPERVOL
e25f31ac
PG
283 for i in range (fcnt):
284 nvol, invol = divmod(i, TEST_FILESPERVOL)
285 f = "dummy_vol_%d_n_%0.2d" % (nvol, invol)
286 self.hash [f] = self.create_file ("%s/%s"
287 % (self.src_path, f),
85e7013f
PG
288 fsiz,
289 random=True)
e25f31ac 290
2fe5f6e7 291 vname = partial (self.default_volume_name, backup_file)
96fe6399
PG
292 dtar = deltatar.DeltaTar (mode=mode,
293 logger=None,
294 password=self.PASSWORD,
203cb25e 295 index_name_func=lambda _: index_file,
3267933a 296 volume_name_func=vname)
fbdc9f4a
PG
297
298 dtar.create_full_backup \
e25f31ac
PG
299 (source_path=self.src_path, backup_path=bak_path,
300 max_volume_size=1)
96fe6399
PG
301
302 if self.PASSWORD is not None:
303 # ensure all files are at least superficially in PDT format
304 for f in os.listdir (bak_path):
305 assert is_pdt_encrypted ("%s/%s" % (bak_path, f))
203cb25e
PG
306
307 # first restore must succeed
96fe6399 308 dtar.restore_backup(target_path=self.dst_path,
f090d35a
PG
309 backup_indexes_paths=[
310 "%s/%s" % (bak_path, index_file)
311 ])
203cb25e 312 for key, value in self.hash.items ():
96fe6399 313 f = "%s/%s" % (self.dst_path, key)
b15e549b
PG
314 assert os.path.exists (f)
315 assert value == self.md5sum (f)
96fe6399
PG
316 shutil.rmtree (self.dst_path)
317 shutil.rmtree (self.src_path)
203cb25e 318
00b8c150
PG
319 self.CORRUPT (backup_full,
320 self.COMPRESSION is not None,
321 self.PASSWORD is not None)
203cb25e
PG
322
323 # normal restore must fail
96fe6399
PG
324 try:
325 dtar.restore_backup(target_path=self.dst_path,
203cb25e 326 backup_tar_path=backup_full)
96fe6399
PG
327 except tarfile.CompressionError:
328 if self.PASSWORD is not None or self.COMPRESSION is not None:
329 pass
00b8c150
PG
330 else:
331 raise
96fe6399 332 except tarfile.ReadError:
00b8c150
PG
333 # can happen with all three modes
334 pass
335 except tarfile.DecryptionError:
336 if self.PASSWORD is not None:
96fe6399 337 pass
00b8c150
PG
338 else:
339 raise
96fe6399
PG
340
341 os.chdir (self.pwd) # not restored due to the error above
203cb25e 342 # but recover will succeed
96fe6399 343 failed = dtar.recover_backup(target_path=self.dst_path,
b15e549b
PG
344 backup_indexes_paths=[
345 "%s/%s" % (bak_path, index_file)
346 ])
96fe6399
PG
347
348 assert len (failed) == self.FAILURES
203cb25e
PG
349
350 # with one file missing
9d89c237
PG
351 missing = []
352 mismatch = []
203cb25e 353 for key, value in self.hash.items ():
96fe6399 354 kkey = "%s/%s" % (self.dst_path, key)
b15e549b 355 if os.path.exists (kkey):
9d89c237
PG
356 if value != self.md5sum (kkey):
357 mismatch.append (key)
203cb25e 358 else:
757319dd 359 missing.append (key)
4d4925de
PG
360
361 # usually, an object whose extraction fails will not be found on
362 # disk afterwards so the number of failures equals that of missing
363 # files. however, some modes will create partial files for objects
364 # spanning multiple volumes that contain the parts whose checksums
365 # were valid.
366 assert len (missing) == (self.MISSING if self.MISSING is not None
367 else self.FAILURES)
9d89c237 368 assert len (mismatch) == self.MISMATCHES
96fe6399
PG
369
370 shutil.rmtree (self.dst_path)
371
372
0c6682ce
PG
373class RescueTest (DefectiveTest):
374 """
375 Rescue: restore corrupt backups from backup set that is damaged to a degree
376 that the index file is worthless.
377 """
378
379 def test_rescue_corrupt (self):
380 """
381 Perform various damaging actions that cause unreadable objects, then
382 attempt to extract objects regardless.
383 """
2fe5f6e7
PG
384 mode = self.COMPRESSION or "#"
385 bak_path, backup_file, backup_full, index_file = \
386 self.gen_file_names (self.COMPRESSION, self.PASSWORD)
0c6682ce
PG
387
388 if self.VOLUMES > 1:
389 # add n files for one nth the volume size each, corrected
390 # for metadata and tar block overhead
391 fsiz = int ( ( TEST_VOLSIZ
392 / (TEST_FILESPERVOL * VOLUME_OVERHEAD))
393 * 1024 * 1024)
394 fcnt = (self.VOLUMES - 1) * TEST_FILESPERVOL
395 for i in range (fcnt):
396 nvol, invol = divmod(i, TEST_FILESPERVOL)
397 f = "dummy_vol_%d_n_%0.2d" % (nvol, invol)
398 self.hash [f] = self.create_file ("%s/%s"
399 % (self.src_path, f),
400 fsiz,
401 random=True)
402
2fe5f6e7 403 vname = partial (self.default_volume_name, backup_file)
0c6682ce
PG
404 dtar = deltatar.DeltaTar (mode=mode,
405 logger=None,
406 password=self.PASSWORD,
407 index_name_func=lambda _: index_file,
408 volume_name_func=vname)
409
410 dtar.create_full_backup \
411 (source_path=self.src_path, backup_path=bak_path,
412 max_volume_size=1)
413
414 if self.PASSWORD is not None:
415 # ensure all files are at least superficially in PDT format
416 for f in os.listdir (bak_path):
417 assert is_pdt_encrypted ("%s/%s" % (bak_path, f))
418
419 # first restore must succeed
420 dtar.restore_backup(target_path=self.dst_path,
421 backup_indexes_paths=[
422 "%s/%s" % (bak_path, index_file)
423 ])
424 for key, value in self.hash.items ():
425 f = "%s/%s" % (self.dst_path, key)
426 assert os.path.exists (f)
427 assert value == self.md5sum (f)
428 shutil.rmtree (self.dst_path)
429 shutil.rmtree (self.src_path)
430
431 self.CORRUPT (backup_full,
432 self.COMPRESSION is not None,
433 self.PASSWORD is not None)
434
435 # normal restore must fail
436 try:
437 dtar.restore_backup(target_path=self.dst_path,
438 backup_tar_path=backup_full)
439 except tarfile.CompressionError:
440 if self.PASSWORD is not None or self.COMPRESSION is not None:
441 pass
442 else:
443 raise
444 except tarfile.ReadError:
445 # can happen with all three modes
446 pass
447 except tarfile.DecryptionError:
448 if self.PASSWORD is not None:
449 pass
450 else:
451 raise
452
453 os.chdir (self.pwd) # not restored due to the error above
454 # but recover will succeed
455 failed = dtar.rescue_backup(target_path=self.dst_path,
2fe5f6e7 456 backup_tar_path=backup_full)
0c6682ce
PG
457 # with one file missing
458 missing = []
459 mismatch = []
460 for key, value in self.hash.items ():
461 kkey = "%s/%s" % (self.dst_path, key)
462 if os.path.exists (kkey):
463 if value != self.md5sum (kkey):
464 mismatch.append (key)
465 else:
466 missing.append (key)
467
79bc14cf 468 assert len (failed) == self.FAILURES
2fe5f6e7
PG
469 assert len (missing) == (self.MISSING if self.MISSING is not None
470 else self.FAILURES)
0c6682ce
PG
471 assert len (mismatch) == self.MISMATCHES
472
473 shutil.rmtree (self.dst_path)
474
475
2fe5f6e7
PG
476class GenIndexTest (DefectiveTest):
477 """
478 Deducing an index for a backup with tarfile.
479 """
480
481 def test_gen_index (self):
482 """
483 Create backup, leave it unharmed, then generate an index.
484 """
485 mode = self.COMPRESSION or "#"
486 bak_path, backup_file, backup_full, index_file = \
487 self.gen_file_names (self.COMPRESSION, self.PASSWORD)
488
489 vname = partial (self.default_volume_name, backup_file)
490 dtar = deltatar.DeltaTar (mode=mode,
491 logger=None,
492 password=self.PASSWORD,
493 index_name_func=lambda _: index_file,
494 volume_name_func=vname)
495
496 dtar.create_full_backup \
497 (source_path=self.src_path, backup_path=bak_path,
498 max_volume_size=1)
499
500 psidx = tarfile.gen_rescue_index (backup_full, mode, password=self.PASSWORD)
501
502 assert len (psidx) == len (self.hash)
503
504
505###############################################################################
506# rescue
507###############################################################################
508
e25f31ac 509class RecoverCorruptPayloadTestBase (RecoverTest):
00b8c150
PG
510 COMPRESSION = None
511 PASSWORD = None
9d89c237
PG
512 FAILURES = 0 # tarfile will restore but corrupted, as
513 MISMATCHES = 1 # revealed by the hash
00b8c150 514
e25f31ac
PG
515class RecoverCorruptPayloadSingleTest (RecoverCorruptPayloadTestBase):
516 VOLUMES = 1
517
518class RecoverCorruptPayloadMultiTest (RecoverCorruptPayloadTestBase):
519 VOLUMES = 3
520
00b8c150 521
e25f31ac 522class RecoverCorruptPayloadGZTestBase (RecoverTest):
00b8c150
PG
523 COMPRESSION = "#gz"
524 PASSWORD = None
525 FAILURES = 1
9d89c237 526 MISMATCHES = 0
00b8c150 527
e25f31ac
PG
528class RecoverCorruptPayloadGZSingleTest (RecoverCorruptPayloadGZTestBase):
529 VOLUMES = 1
00b8c150 530
e25f31ac
PG
531class RecoverCorruptPayloadGZMultiTest (RecoverCorruptPayloadGZTestBase):
532 VOLUMES = 3
533
534
535class RecoverCorruptPayloadGZAESTestBase (RecoverTest):
00b8c150
PG
536 COMPRESSION = "#gz"
537 PASSWORD = TEST_PASSWORD
538 FAILURES = 1
9d89c237 539 MISMATCHES = 0
00b8c150 540
e25f31ac
PG
541class RecoverCorruptPayloadGZAESSingleTest (RecoverCorruptPayloadGZAESTestBase):
542 VOLUMES = 1
543
544class RecoverCorruptPayloadGZAESMultiTest (RecoverCorruptPayloadGZAESTestBase):
545 VOLUMES = 3
00b8c150 546
e25f31ac
PG
547
548class RecoverCorruptHeaderTestBase (RecoverTest):
0349168a
PG
549 COMPRESSION = None
550 PASSWORD = None
551 FAILURES = 1
552 CORRUPT = corrupt_header
9d89c237 553 MISMATCHES = 0
0349168a 554
e25f31ac
PG
555class RecoverCorruptHeaderSingleTest (RecoverCorruptHeaderTestBase):
556 VOLUMES = 1
557
558class RecoverCorruptHeaderMultiTest (RecoverCorruptHeaderTestBase):
559 VOLUMES = 3
560
0349168a 561
e25f31ac 562class RecoverCorruptHeaderGZTestBase (RecoverTest):
96fe6399
PG
563 COMPRESSION = "#gz"
564 PASSWORD = None
565 FAILURES = 1
00b8c150 566 CORRUPT = corrupt_header
9d89c237 567 MISMATCHES = 0
96fe6399 568
e25f31ac
PG
569class RecoverCorruptHeaderGZSingleTest (RecoverCorruptHeaderGZTestBase):
570 VOLUMES = 1
3267933a 571
e25f31ac
PG
572class RecoverCorruptHeaderGZMultiTest (RecoverCorruptHeaderGZTestBase):
573 VOLUMES = 3
574
575
576class RecoverCorruptHeaderGZAESTestBase (RecoverTest):
96fe6399
PG
577 COMPRESSION = "#gz"
578 PASSWORD = TEST_PASSWORD
579 FAILURES = 1
00b8c150 580 CORRUPT = corrupt_header
9d89c237 581 MISMATCHES = 0
fbdc9f4a 582
e25f31ac
PG
583class RecoverCorruptHeaderGZAESSingleTest (RecoverCorruptHeaderGZAESTestBase):
584 VOLUMES = 1
585
586class RecoverCorruptHeaderGZAESMultiTest (RecoverCorruptHeaderGZAESTestBase):
587 VOLUMES = 3
da8996f0 588
e25f31ac
PG
589
590class RecoverCorruptEntireHeaderTestBase (RecoverTest):
da8996f0
PG
591 COMPRESSION = None
592 PASSWORD = None
593 FAILURES = 1
594 CORRUPT = corrupt_entire_header
9d89c237 595 MISMATCHES = 0
da8996f0 596
e25f31ac
PG
597class RecoverCorruptEntireHeaderSingleTest (RecoverCorruptEntireHeaderTestBase):
598 VOLUMES = 1
599
600class RecoverCorruptEntireHeaderMultiTest (RecoverCorruptEntireHeaderTestBase):
601 VOLUMES = 3
602
da8996f0 603
e25f31ac 604class RecoverCorruptEntireHeaderGZTestBase (RecoverTest):
da8996f0
PG
605 COMPRESSION = "#gz"
606 PASSWORD = None
607 FAILURES = 1
608 CORRUPT = corrupt_entire_header
9d89c237 609 MISMATCHES = 0
da8996f0 610
e25f31ac
PG
611class RecoverCorruptEntireHeaderGZSingleTest (RecoverCorruptEntireHeaderGZTestBase):
612 VOLUMES = 1
da8996f0 613
e25f31ac
PG
614class RecoverCorruptEntireHeaderGZMultiTest (RecoverCorruptEntireHeaderGZTestBase):
615 VOLUMES = 3
616
617
618class RecoverCorruptEntireHeaderGZAESTestBase (RecoverTest):
da8996f0
PG
619 COMPRESSION = "#gz"
620 PASSWORD = TEST_PASSWORD
621 FAILURES = 1
622 CORRUPT = corrupt_entire_header
9d89c237 623 MISMATCHES = 0
da8996f0 624
e25f31ac
PG
625class RecoverCorruptEntireHeaderGZAESSingleTest (RecoverCorruptEntireHeaderGZAESTestBase):
626 VOLUMES = 1
627
628class RecoverCorruptEntireHeaderGZAESMultiTest (RecoverCorruptEntireHeaderGZAESTestBase):
629 VOLUMES = 3
517d35b7 630
e25f31ac
PG
631
632class RecoverCorruptTrailingDataTestBase (RecoverTest):
517d35b7
PG
633 # plain Tar is indifferent against traling data and the results
634 # are consistent
635 COMPRESSION = None
636 PASSWORD = None
637 FAILURES = 0
638 CORRUPT = corrupt_trailing_data
639 MISMATCHES = 0
640
e25f31ac
PG
641class RecoverCorruptTrailingDataSingleTest (RecoverCorruptTrailingDataTestBase):
642 VOLUMES = 1
643
644class RecoverCorruptTrailingDataMultiTest (RecoverCorruptTrailingDataTestBase):
14895f4b
PG
645 # the last object in first archive has extra bytes somewhere in the
646 # middle because tar itself performs no data checksumming.
647 MISMATCHES = 1
e25f31ac
PG
648 VOLUMES = 3
649
517d35b7 650
e25f31ac 651class RecoverCorruptTrailingDataGZTestBase (RecoverTest):
517d35b7
PG
652 # reading past the final object will cause decompression failure;
653 # all objects except for the last survive unharmed though
654 COMPRESSION = "#gz"
655 PASSWORD = None
656 FAILURES = 1
657 CORRUPT = corrupt_trailing_data
658 MISMATCHES = 0
659
e25f31ac
PG
660class RecoverCorruptTrailingDataGZSingleTest (RecoverCorruptTrailingDataGZTestBase):
661 VOLUMES = 1
517d35b7 662
e25f31ac
PG
663class RecoverCorruptTrailingDataGZMultiTest (RecoverCorruptTrailingDataGZTestBase):
664 VOLUMES = 3
14895f4b
PG
665 # the last file of the first volume will only contain the data of the
666 # second part which is contained in the second volume. this happens
667 # because the CRC32 is wrong for the first part so it gets discarded, then
668 # the object is recreated from the first header of the second volume,
669 # containing only the remainder of the data.
670 MISMATCHES = 1
4d4925de 671 MISSING = 0
e25f31ac
PG
672
673
674class RecoverCorruptTrailingDataGZAESTestBase (RecoverTest):
517d35b7
PG
675 COMPRESSION = "#gz"
676 PASSWORD = TEST_PASSWORD
677 FAILURES = 0
678 CORRUPT = corrupt_trailing_data
679 MISMATCHES = 0
680
e25f31ac
PG
681class RecoverCorruptTrailingDataGZAESSingleTest (RecoverCorruptTrailingDataGZAESTestBase):
682 VOLUMES = 1
683
684class RecoverCorruptTrailingDataGZAESMultiTest (RecoverCorruptTrailingDataGZAESTestBase):
685 VOLUMES = 3
517d35b7 686
20e1d773
PG
687
688class RecoverCorruptVolumeBaseTest (RecoverTest):
689 COMPRESSION = None
690 PASSWORD = None
691 FAILURES = 8
692 CORRUPT = corrupt_volume
693 VOLUMES = 3
694
695class RecoverCorruptVolumeTest (RecoverCorruptVolumeBaseTest):
696 pass
697
3692fd82
PG
698class RecoverCorruptVolumeGZTest (RecoverCorruptVolumeBaseTest):
699 COMPRESSION = "#gz"
700
701class RecoverCorruptVolumeGZAESTest (RecoverCorruptVolumeBaseTest):
20e1d773 702 COMPRESSION = "#gz"
3692fd82
PG
703 PASSWORD = TEST_PASSWORD
704
705
706class RecoverCorruptHoleBaseTest (RecoverTest):
707 """
708 Cut bytes from the middle of a volume.
709
710 Index-based recovery works only up to the hole.
711 """
712 COMPRESSION = None
20e1d773 713 PASSWORD = None
3692fd82
PG
714 FAILURES = 3
715 CORRUPT = corrupt_hole
716 VOLUMES = 2 # request two vols to swell up the first one
717 MISMATCHES = 1
718
719class RecoverCorruptHoleTest (RecoverCorruptHoleBaseTest):
720 FAILURES = 2
721
722class RecoverCorruptHoleGZTest (RecoverCorruptHoleBaseTest):
723 COMPRESSION = "#gz"
724 MISSING = 2
20e1d773 725
3692fd82 726class RecoverCorruptHoleGZAESTest (RecoverCorruptHoleBaseTest):
20e1d773
PG
727 COMPRESSION = "#gz"
728 PASSWORD = TEST_PASSWORD
3692fd82 729 MISSING = 2
20e1d773 730
2fe5f6e7
PG
731###############################################################################
732# rescue
733###############################################################################
734
735class RescueCorruptHoleBaseTest (RescueTest):
736 """
737 Cut bytes from the middle of a volume.
738 """
739 COMPRESSION = None
740 PASSWORD = None
79bc14cf 741 FAILURES = 0
2fe5f6e7
PG
742 CORRUPT = corrupt_hole
743 VOLUMES = 2 # request two vols to swell up the first one
79bc14cf
PG
744 MISMATCHES = 2 # intersected by hole
745 MISSING = 1 # excised by hole
2fe5f6e7
PG
746
747class RescueCorruptHoleTest (RescueCorruptHoleBaseTest):
79bc14cf 748 pass
2fe5f6e7
PG
749
750class RescueCorruptHoleGZTest (RescueCorruptHoleBaseTest):
751 COMPRESSION = "#gz"
79bc14cf
PG
752 # the decompressor explodes in our face processing the first dummy, nothing
753 # we can do to recover
754 FAILURES = 1
2fe5f6e7
PG
755
756class RescueCorruptHoleGZAESTest (RescueCorruptHoleBaseTest):
757 COMPRESSION = "#gz"
758 PASSWORD = TEST_PASSWORD
79bc14cf
PG
759 # again, ignoring the crypto errors yields a bad zlib stream causing the
760 # decompressor to abort where the hole begins; the file is extracted up
761 # to this point though
762 FAILURES = 1
2fe5f6e7
PG
763
764###############################################################################
765# index
766###############################################################################
767
768class GenIndexIntactBaseTest (GenIndexTest):
769 """
770 """
771 COMPRESSION = None
772 PASSWORD = None
773 FAILURES = 0
774 CORRUPT = immaculate
775 VOLUMES = 1
776 MISMATCHES = 1
777
778
779class GenIndexIntactTest (GenIndexIntactBaseTest):
780 pass
781
782class GenIndexIntactGZTest (GenIndexIntactBaseTest):
783 COMPRESSION = "#gz"
784 MISSING = 2
785
786class GenIndexIntactGZAESTest (GenIndexIntactBaseTest):
787 COMPRESSION = "#gz"
788 PASSWORD = TEST_PASSWORD
789 MISSING = 2
790