use real new volume handler during rescue
[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
458 assert len (failed) == self.FAILURES
459
460 # with one file missing
461 missing = []
462 mismatch = []
463 for key, value in self.hash.items ():
464 kkey = "%s/%s" % (self.dst_path, key)
465 if os.path.exists (kkey):
466 if value != self.md5sum (kkey):
467 mismatch.append (key)
468 else:
469 missing.append (key)
470
2fe5f6e7
PG
471 assert len (missing) == (self.MISSING if self.MISSING is not None
472 else self.FAILURES)
0c6682ce
PG
473 assert len (mismatch) == self.MISMATCHES
474
475 shutil.rmtree (self.dst_path)
476
477
2fe5f6e7
PG
478class GenIndexTest (DefectiveTest):
479 """
480 Deducing an index for a backup with tarfile.
481 """
482
483 def test_gen_index (self):
484 """
485 Create backup, leave it unharmed, then generate an index.
486 """
487 mode = self.COMPRESSION or "#"
488 bak_path, backup_file, backup_full, index_file = \
489 self.gen_file_names (self.COMPRESSION, self.PASSWORD)
490
491 vname = partial (self.default_volume_name, backup_file)
492 dtar = deltatar.DeltaTar (mode=mode,
493 logger=None,
494 password=self.PASSWORD,
495 index_name_func=lambda _: index_file,
496 volume_name_func=vname)
497
498 dtar.create_full_backup \
499 (source_path=self.src_path, backup_path=bak_path,
500 max_volume_size=1)
501
502 psidx = tarfile.gen_rescue_index (backup_full, mode, password=self.PASSWORD)
503
504 assert len (psidx) == len (self.hash)
505
506
507###############################################################################
508# rescue
509###############################################################################
510
e25f31ac 511class RecoverCorruptPayloadTestBase (RecoverTest):
00b8c150
PG
512 COMPRESSION = None
513 PASSWORD = None
9d89c237
PG
514 FAILURES = 0 # tarfile will restore but corrupted, as
515 MISMATCHES = 1 # revealed by the hash
00b8c150 516
e25f31ac
PG
517class RecoverCorruptPayloadSingleTest (RecoverCorruptPayloadTestBase):
518 VOLUMES = 1
519
520class RecoverCorruptPayloadMultiTest (RecoverCorruptPayloadTestBase):
521 VOLUMES = 3
522
00b8c150 523
e25f31ac 524class RecoverCorruptPayloadGZTestBase (RecoverTest):
00b8c150
PG
525 COMPRESSION = "#gz"
526 PASSWORD = None
527 FAILURES = 1
9d89c237 528 MISMATCHES = 0
00b8c150 529
e25f31ac
PG
530class RecoverCorruptPayloadGZSingleTest (RecoverCorruptPayloadGZTestBase):
531 VOLUMES = 1
00b8c150 532
e25f31ac
PG
533class RecoverCorruptPayloadGZMultiTest (RecoverCorruptPayloadGZTestBase):
534 VOLUMES = 3
535
536
537class RecoverCorruptPayloadGZAESTestBase (RecoverTest):
00b8c150
PG
538 COMPRESSION = "#gz"
539 PASSWORD = TEST_PASSWORD
540 FAILURES = 1
9d89c237 541 MISMATCHES = 0
00b8c150 542
e25f31ac
PG
543class RecoverCorruptPayloadGZAESSingleTest (RecoverCorruptPayloadGZAESTestBase):
544 VOLUMES = 1
545
546class RecoverCorruptPayloadGZAESMultiTest (RecoverCorruptPayloadGZAESTestBase):
547 VOLUMES = 3
00b8c150 548
e25f31ac
PG
549
550class RecoverCorruptHeaderTestBase (RecoverTest):
0349168a
PG
551 COMPRESSION = None
552 PASSWORD = None
553 FAILURES = 1
554 CORRUPT = corrupt_header
9d89c237 555 MISMATCHES = 0
0349168a 556
e25f31ac
PG
557class RecoverCorruptHeaderSingleTest (RecoverCorruptHeaderTestBase):
558 VOLUMES = 1
559
560class RecoverCorruptHeaderMultiTest (RecoverCorruptHeaderTestBase):
561 VOLUMES = 3
562
0349168a 563
e25f31ac 564class RecoverCorruptHeaderGZTestBase (RecoverTest):
96fe6399
PG
565 COMPRESSION = "#gz"
566 PASSWORD = None
567 FAILURES = 1
00b8c150 568 CORRUPT = corrupt_header
9d89c237 569 MISMATCHES = 0
96fe6399 570
e25f31ac
PG
571class RecoverCorruptHeaderGZSingleTest (RecoverCorruptHeaderGZTestBase):
572 VOLUMES = 1
3267933a 573
e25f31ac
PG
574class RecoverCorruptHeaderGZMultiTest (RecoverCorruptHeaderGZTestBase):
575 VOLUMES = 3
576
577
578class RecoverCorruptHeaderGZAESTestBase (RecoverTest):
96fe6399
PG
579 COMPRESSION = "#gz"
580 PASSWORD = TEST_PASSWORD
581 FAILURES = 1
00b8c150 582 CORRUPT = corrupt_header
9d89c237 583 MISMATCHES = 0
fbdc9f4a 584
e25f31ac
PG
585class RecoverCorruptHeaderGZAESSingleTest (RecoverCorruptHeaderGZAESTestBase):
586 VOLUMES = 1
587
588class RecoverCorruptHeaderGZAESMultiTest (RecoverCorruptHeaderGZAESTestBase):
589 VOLUMES = 3
da8996f0 590
e25f31ac
PG
591
592class RecoverCorruptEntireHeaderTestBase (RecoverTest):
da8996f0
PG
593 COMPRESSION = None
594 PASSWORD = None
595 FAILURES = 1
596 CORRUPT = corrupt_entire_header
9d89c237 597 MISMATCHES = 0
da8996f0 598
e25f31ac
PG
599class RecoverCorruptEntireHeaderSingleTest (RecoverCorruptEntireHeaderTestBase):
600 VOLUMES = 1
601
602class RecoverCorruptEntireHeaderMultiTest (RecoverCorruptEntireHeaderTestBase):
603 VOLUMES = 3
604
da8996f0 605
e25f31ac 606class RecoverCorruptEntireHeaderGZTestBase (RecoverTest):
da8996f0
PG
607 COMPRESSION = "#gz"
608 PASSWORD = None
609 FAILURES = 1
610 CORRUPT = corrupt_entire_header
9d89c237 611 MISMATCHES = 0
da8996f0 612
e25f31ac
PG
613class RecoverCorruptEntireHeaderGZSingleTest (RecoverCorruptEntireHeaderGZTestBase):
614 VOLUMES = 1
da8996f0 615
e25f31ac
PG
616class RecoverCorruptEntireHeaderGZMultiTest (RecoverCorruptEntireHeaderGZTestBase):
617 VOLUMES = 3
618
619
620class RecoverCorruptEntireHeaderGZAESTestBase (RecoverTest):
da8996f0
PG
621 COMPRESSION = "#gz"
622 PASSWORD = TEST_PASSWORD
623 FAILURES = 1
624 CORRUPT = corrupt_entire_header
9d89c237 625 MISMATCHES = 0
da8996f0 626
e25f31ac
PG
627class RecoverCorruptEntireHeaderGZAESSingleTest (RecoverCorruptEntireHeaderGZAESTestBase):
628 VOLUMES = 1
629
630class RecoverCorruptEntireHeaderGZAESMultiTest (RecoverCorruptEntireHeaderGZAESTestBase):
631 VOLUMES = 3
517d35b7 632
e25f31ac
PG
633
634class RecoverCorruptTrailingDataTestBase (RecoverTest):
517d35b7
PG
635 # plain Tar is indifferent against traling data and the results
636 # are consistent
637 COMPRESSION = None
638 PASSWORD = None
639 FAILURES = 0
640 CORRUPT = corrupt_trailing_data
641 MISMATCHES = 0
642
e25f31ac
PG
643class RecoverCorruptTrailingDataSingleTest (RecoverCorruptTrailingDataTestBase):
644 VOLUMES = 1
645
646class RecoverCorruptTrailingDataMultiTest (RecoverCorruptTrailingDataTestBase):
14895f4b
PG
647 # the last object in first archive has extra bytes somewhere in the
648 # middle because tar itself performs no data checksumming.
649 MISMATCHES = 1
e25f31ac
PG
650 VOLUMES = 3
651
517d35b7 652
e25f31ac 653class RecoverCorruptTrailingDataGZTestBase (RecoverTest):
517d35b7
PG
654 # reading past the final object will cause decompression failure;
655 # all objects except for the last survive unharmed though
656 COMPRESSION = "#gz"
657 PASSWORD = None
658 FAILURES = 1
659 CORRUPT = corrupt_trailing_data
660 MISMATCHES = 0
661
e25f31ac
PG
662class RecoverCorruptTrailingDataGZSingleTest (RecoverCorruptTrailingDataGZTestBase):
663 VOLUMES = 1
517d35b7 664
e25f31ac
PG
665class RecoverCorruptTrailingDataGZMultiTest (RecoverCorruptTrailingDataGZTestBase):
666 VOLUMES = 3
14895f4b
PG
667 # the last file of the first volume will only contain the data of the
668 # second part which is contained in the second volume. this happens
669 # because the CRC32 is wrong for the first part so it gets discarded, then
670 # the object is recreated from the first header of the second volume,
671 # containing only the remainder of the data.
672 MISMATCHES = 1
4d4925de 673 MISSING = 0
e25f31ac
PG
674
675
676class RecoverCorruptTrailingDataGZAESTestBase (RecoverTest):
517d35b7
PG
677 COMPRESSION = "#gz"
678 PASSWORD = TEST_PASSWORD
679 FAILURES = 0
680 CORRUPT = corrupt_trailing_data
681 MISMATCHES = 0
682
e25f31ac
PG
683class RecoverCorruptTrailingDataGZAESSingleTest (RecoverCorruptTrailingDataGZAESTestBase):
684 VOLUMES = 1
685
686class RecoverCorruptTrailingDataGZAESMultiTest (RecoverCorruptTrailingDataGZAESTestBase):
687 VOLUMES = 3
517d35b7 688
20e1d773
PG
689
690class RecoverCorruptVolumeBaseTest (RecoverTest):
691 COMPRESSION = None
692 PASSWORD = None
693 FAILURES = 8
694 CORRUPT = corrupt_volume
695 VOLUMES = 3
696
697class RecoverCorruptVolumeTest (RecoverCorruptVolumeBaseTest):
698 pass
699
3692fd82
PG
700class RecoverCorruptVolumeGZTest (RecoverCorruptVolumeBaseTest):
701 COMPRESSION = "#gz"
702
703class RecoverCorruptVolumeGZAESTest (RecoverCorruptVolumeBaseTest):
20e1d773 704 COMPRESSION = "#gz"
3692fd82
PG
705 PASSWORD = TEST_PASSWORD
706
707
708class RecoverCorruptHoleBaseTest (RecoverTest):
709 """
710 Cut bytes from the middle of a volume.
711
712 Index-based recovery works only up to the hole.
713 """
714 COMPRESSION = None
20e1d773 715 PASSWORD = None
3692fd82
PG
716 FAILURES = 3
717 CORRUPT = corrupt_hole
718 VOLUMES = 2 # request two vols to swell up the first one
719 MISMATCHES = 1
720
721class RecoverCorruptHoleTest (RecoverCorruptHoleBaseTest):
722 FAILURES = 2
723
724class RecoverCorruptHoleGZTest (RecoverCorruptHoleBaseTest):
725 COMPRESSION = "#gz"
726 MISSING = 2
20e1d773 727
3692fd82 728class RecoverCorruptHoleGZAESTest (RecoverCorruptHoleBaseTest):
20e1d773
PG
729 COMPRESSION = "#gz"
730 PASSWORD = TEST_PASSWORD
3692fd82 731 MISSING = 2
20e1d773 732
2fe5f6e7
PG
733###############################################################################
734# rescue
735###############################################################################
736
737class RescueCorruptHoleBaseTest (RescueTest):
738 """
739 Cut bytes from the middle of a volume.
740 """
741 COMPRESSION = None
742 PASSWORD = None
743 FAILURES = 3
744 CORRUPT = corrupt_hole
745 VOLUMES = 2 # request two vols to swell up the first one
746 MISMATCHES = 1
747
748class RescueCorruptHoleTest (RescueCorruptHoleBaseTest):
001bd488
PG
749 FAILURES = 0
750 MISMATCHES = 1 # intersected by hole
751 MISSING = 1 # excised by hole
2fe5f6e7
PG
752
753class RescueCorruptHoleGZTest (RescueCorruptHoleBaseTest):
754 COMPRESSION = "#gz"
755 MISSING = 2
756
757class RescueCorruptHoleGZAESTest (RescueCorruptHoleBaseTest):
758 COMPRESSION = "#gz"
759 PASSWORD = TEST_PASSWORD
760 MISSING = 2
761
762###############################################################################
763# index
764###############################################################################
765
766class GenIndexIntactBaseTest (GenIndexTest):
767 """
768 """
769 COMPRESSION = None
770 PASSWORD = None
771 FAILURES = 0
772 CORRUPT = immaculate
773 VOLUMES = 1
774 MISMATCHES = 1
775
776
777class GenIndexIntactTest (GenIndexIntactBaseTest):
778 pass
779
780class GenIndexIntactGZTest (GenIndexIntactBaseTest):
781 COMPRESSION = "#gz"
782 MISSING = 2
783
784class GenIndexIntactGZAESTest (GenIndexIntactBaseTest):
785 COMPRESSION = "#gz"
786 PASSWORD = TEST_PASSWORD
787 MISSING = 2
788