archiver.py 8.7 KB

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