archiver.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import filecmp
  2. import os
  3. from io import StringIO
  4. import stat
  5. import sys
  6. import shutil
  7. import tempfile
  8. from darc import xattr
  9. from darc.archiver import Archiver
  10. from darc.repository import Repository
  11. from darc.testsuite import DarcTestCase
  12. has_mtime_ns = sys.version >= '3.3'
  13. utime_supports_fd = os.utime in getattr(os, 'supports_fd', {})
  14. src_dir = os.path.join(os.getcwd(), os.path.dirname(__file__), '..', '..')
  15. class ArchiverTestCase(DarcTestCase):
  16. prefix = ''
  17. def setUp(self):
  18. self.archiver = Archiver()
  19. self.tmpdir = tempfile.mkdtemp(dir=os.getcwd())
  20. self.repository_path = os.path.join(self.tmpdir, 'repository')
  21. self.repository_location = self.prefix + self.repository_path
  22. self.input_path = os.path.join(self.tmpdir, 'input')
  23. self.output_path = os.path.join(self.tmpdir, 'output')
  24. self.keys_path = os.path.join(self.tmpdir, 'keys')
  25. self.cache_path = os.path.join(self.tmpdir, 'cache')
  26. os.environ['DARC_KEYS_DIR'] = self.keys_path
  27. os.environ['DARC_CACHE_DIR'] = self.cache_path
  28. os.mkdir(self.input_path)
  29. os.mkdir(self.output_path)
  30. os.mkdir(self.keys_path)
  31. os.mkdir(self.cache_path)
  32. self._old_wd = os.getcwd()
  33. os.chdir(self.tmpdir)
  34. def tearDown(self):
  35. shutil.rmtree(self.tmpdir)
  36. os.chdir(self._old_wd)
  37. def darc(self, *args, **kwargs):
  38. exit_code = kwargs.get('exit_code', 0)
  39. args = list(args)
  40. try:
  41. stdout, stderr = sys.stdout, sys.stderr
  42. output = StringIO()
  43. sys.stdout = sys.stderr = output
  44. ret = self.archiver.run(args)
  45. sys.stdout, sys.stderr = stdout, stderr
  46. if ret != exit_code:
  47. print(output.getvalue())
  48. self.assert_equal(exit_code, ret)
  49. return output.getvalue()
  50. finally:
  51. sys.stdout, sys.stderr = stdout, stderr
  52. def create_src_archive(self, name):
  53. self.darc('init', self.repository_location)
  54. self.darc('create', self.repository_location + '::' + name, src_dir)
  55. def create_regual_file(self, name, size=0):
  56. filename = os.path.join(self.input_path, name)
  57. if not os.path.exists(os.path.dirname(filename)):
  58. os.makedirs(os.path.dirname(filename))
  59. with open(filename, 'wb') as fd:
  60. fd.write(b'X' * size)
  61. def get_xattrs(self, path):
  62. try:
  63. return xattr.get_all(path)
  64. except EnvironmentError:
  65. return {}
  66. def diff_dirs(self, dir1, dir2):
  67. diff = filecmp.dircmp(dir1, dir2)
  68. self.assert_equal(diff.left_only, [])
  69. self.assert_equal(diff.right_only, [])
  70. self.assert_equal(diff.diff_files, [])
  71. for filename in diff.common:
  72. path1 = os.path.join(dir1, filename)
  73. path2 = os.path.join(dir2, filename)
  74. s1 = os.lstat(path1)
  75. s2 = os.lstat(path2)
  76. attrs = ['st_mode', 'st_uid', 'st_gid', 'st_rdev']
  77. if not os.path.islink(path1) or utime_supports_fd:
  78. attrs.append('st_mtime_ns' if has_mtime_ns else 'st_mtime')
  79. d1 = [filename] + [getattr(s1, a) for a in attrs]
  80. d2 = [filename] + [getattr(s2, a) for a in attrs]
  81. # 'st_mtime precision is limited'
  82. if attrs[-1] == 'st_mtime':
  83. d1[-1] = round(d1[-1], 4)
  84. d2[-1] = round(d2[-1], 4)
  85. d1.append(self.get_xattrs(path1))
  86. d2.append(self.get_xattrs(path2))
  87. self.assert_equal(d1, d2)
  88. def test_basic_functionality(self):
  89. # File
  90. self.create_regual_file('file1', size=1024 * 80)
  91. # Directory
  92. self.create_regual_file('dir2/file2', size=1024 * 80)
  93. # File owner
  94. os.chown('input/file1', 100, 200)
  95. # File mode
  96. os.chmod('input/file1', 0o7755)
  97. os.chmod('input/dir2', 0o700)
  98. # Block device
  99. os.mknod('input/bdev', 0o600 | stat.S_IFBLK, os.makedev(10, 20))
  100. # Char device
  101. os.mknod('input/cdev', 0o600 | stat.S_IFCHR, os.makedev(30, 40))
  102. xattr.set(os.path.join(self.input_path, 'file1'), b'foo', b'bar')
  103. # Hard link
  104. os.link(os.path.join(self.input_path, 'file1'),
  105. os.path.join(self.input_path, 'hardlink'))
  106. # Symlink
  107. os.symlink('somewhere', os.path.join(self.input_path, 'link1'))
  108. # FIFO node
  109. os.mkfifo(os.path.join(self.input_path, 'fifo1'))
  110. self.darc('init', self.repository_location)
  111. self.darc('create', self.repository_location + '::test', 'input')
  112. self.darc('create', self.repository_location + '::test.2', 'input')
  113. self.darc('extract', self.repository_location + '::test', 'output')
  114. self.assert_equal(len(self.darc('list', self.repository_location).splitlines()), 2)
  115. self.assert_equal(len(self.darc('list', self.repository_location + '::test').splitlines()), 9)
  116. self.diff_dirs('input', 'output/input')
  117. info_output = self.darc('info', self.repository_location + '::test')
  118. shutil.rmtree(self.cache_path)
  119. info_output2 = self.darc('info', self.repository_location + '::test')
  120. # info_output2 starts with some "initializing cache" text but should
  121. # end the same way as info_output
  122. assert info_output2.endswith(info_output)
  123. def test_extract_include_exclude(self):
  124. self.darc('init', self.repository_location)
  125. self.create_regual_file('file1', size=1024 * 80)
  126. self.create_regual_file('file2', size=1024 * 80)
  127. self.create_regual_file('file3', size=1024 * 80)
  128. self.create_regual_file('file4', size=1024 * 80)
  129. self.darc('create', '--exclude=input/file4', self.repository_location + '::test', 'input')
  130. self.darc('extract', '--include=file1', self.repository_location + '::test', 'output')
  131. self.assert_equal(sorted(os.listdir('output/input')), ['file1'])
  132. self.darc('extract', '--exclude=file2', self.repository_location + '::test', 'output')
  133. self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file3'])
  134. def test_overwrite(self):
  135. self.create_regual_file('file1', size=1024 * 80)
  136. self.create_regual_file('dir2/file2', size=1024 * 80)
  137. self.darc('init', self.repository_location)
  138. self.darc('create', self.repository_location + '::test', 'input')
  139. # Overwriting regular files and directories should be supported
  140. os.mkdir('output/input')
  141. os.mkdir('output/input/file1')
  142. os.mkdir('output/input/dir2')
  143. self.darc('extract', self.repository_location + '::test', 'output')
  144. self.diff_dirs('input', 'output/input')
  145. # But non-empty dirs should fail
  146. os.unlink('output/input/file1')
  147. os.mkdir('output/input/file1')
  148. os.mkdir('output/input/file1/dir')
  149. self.darc('extract', self.repository_location + '::test', 'output', exit_code=1)
  150. def test_delete(self):
  151. self.create_regual_file('file1', size=1024 * 80)
  152. self.create_regual_file('dir2/file2', size=1024 * 80)
  153. self.darc('init', self.repository_location)
  154. self.darc('create', self.repository_location + '::test', 'input')
  155. self.darc('create', self.repository_location + '::test.2', 'input')
  156. self.darc('verify', self.repository_location + '::test')
  157. self.darc('verify', self.repository_location + '::test.2')
  158. self.darc('delete', self.repository_location + '::test')
  159. self.darc('verify', self.repository_location + '::test.2')
  160. self.darc('delete', self.repository_location + '::test.2')
  161. # Make sure all data except the manifest has been deleted
  162. repository = Repository(self.repository_path)
  163. self.assert_equal(repository._len(), 1)
  164. def test_corrupted_repository(self):
  165. self.create_src_archive('test')
  166. self.darc('verify', self.repository_location + '::test')
  167. name = sorted(os.listdir(os.path.join(self.tmpdir, 'repository', 'data', '0')), reverse=True)[0]
  168. fd = open(os.path.join(self.tmpdir, 'repository', 'data', '0', name), 'r+')
  169. fd.seek(100)
  170. fd.write('X')
  171. fd.close()
  172. self.darc('verify', self.repository_location + '::test', exit_code=1)
  173. def test_prune_repository(self):
  174. self.darc('init', self.repository_location)
  175. self.darc('create', self.repository_location + '::test1', src_dir)
  176. self.darc('create', self.repository_location + '::test2', src_dir)
  177. self.darc('prune', self.repository_location, '--daily=2')
  178. output = self.darc('list', self.repository_location)
  179. assert 'test1' not in output
  180. assert 'test2' in output
  181. def test_usage(self):
  182. self.assert_raises(SystemExit, lambda: self.darc())
  183. self.assert_raises(SystemExit, lambda: self.darc('-h'))
  184. class RemoteArchiverTestCase(ArchiverTestCase):
  185. prefix = 'localhost:'