test.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import doctest
  2. import filecmp
  3. import os
  4. from io import BytesIO, StringIO
  5. import stat
  6. import sys
  7. import shutil
  8. import tempfile
  9. import unittest
  10. import xattr
  11. from . import helpers, lrucache, crypto
  12. from .chunker import chunkify, buzhash, buzhash_update
  13. from .archiver import Archiver
  14. from .key import suite as KeySuite
  15. from .store import Store, suite as StoreSuite
  16. from .remote import Store, suite as RemoteStoreSuite
  17. has_mtime_ns = sys.version >= '3.3'
  18. utime_supports_fd = os.utime in getattr(os, 'supports_fd', {})
  19. class Test(unittest.TestCase):
  20. prefix = ''
  21. def setUp(self):
  22. self.archiver = Archiver()
  23. self.tmpdir = tempfile.mkdtemp()
  24. self.store_path = os.path.join(self.tmpdir, 'store')
  25. self.store_location = self.prefix + self.store_path
  26. self.input_path = os.path.join(self.tmpdir, 'input')
  27. self.output_path = os.path.join(self.tmpdir, 'output')
  28. self.keys_path = os.path.join(self.tmpdir, 'keys')
  29. self.cache_path = os.path.join(self.tmpdir, 'cache')
  30. os.environ['DARC_KEYS_DIR'] = self.keys_path
  31. os.environ['DARC_CACHE_DIR'] = self.cache_path
  32. os.mkdir(self.input_path)
  33. os.mkdir(self.output_path)
  34. os.mkdir(self.keys_path)
  35. os.mkdir(self.cache_path)
  36. os.chdir(self.tmpdir)
  37. def tearDown(self):
  38. shutil.rmtree(self.tmpdir)
  39. def darc(self, *args, **kwargs):
  40. exit_code = kwargs.get('exit_code', 0)
  41. args = list(args)
  42. try:
  43. stdout, stderr = sys.stdout, sys.stderr
  44. output = StringIO()
  45. sys.stdout = sys.stderr = output
  46. ret = self.archiver.run(args)
  47. sys.stdout, sys.stderr = stdout, stderr
  48. if ret != exit_code:
  49. print(output.getvalue())
  50. self.assertEqual(exit_code, ret)
  51. return output.getvalue()
  52. finally:
  53. sys.stdout, sys.stderr = stdout, stderr
  54. def create_src_archive(self, name):
  55. src_dir = os.path.join(os.getcwd(), os.path.dirname(__file__), '..')
  56. self.darc('init', self.store_location)
  57. self.darc('create', self.store_location + '::' + name, src_dir)
  58. def create_regual_file(self, name, size=0):
  59. filename = os.path.join(self.input_path, name)
  60. if not os.path.exists(os.path.dirname(filename)):
  61. os.makedirs(os.path.dirname(filename))
  62. with open(filename, 'wb') as fd:
  63. fd.write(b'X' * size)
  64. def get_xattrs(self, path):
  65. try:
  66. return xattr.get_all(path, True)
  67. except EnvironmentError:
  68. return {}
  69. def diff_dirs(self, dir1, dir2):
  70. diff = filecmp.dircmp(dir1, dir2)
  71. self.assertEqual(diff.left_only, [])
  72. self.assertEqual(diff.right_only, [])
  73. self.assertEqual(diff.diff_files, [])
  74. for filename in diff.common:
  75. path1 = os.path.join(dir1, filename)
  76. path2 = os.path.join(dir2, filename)
  77. s1 = os.lstat(path1)
  78. s2 = os.lstat(path2)
  79. attrs = ['st_mode', 'st_uid', 'st_gid', 'st_rdev']
  80. if not os.path.islink(path1) or utime_supports_fd:
  81. attrs.append('st_mtime_ns' if has_mtime_ns else 'st_mtime')
  82. d1 = [filename] + [getattr(s1, a) for a in attrs]
  83. d2 = [filename] + [getattr(s2, a) for a in attrs]
  84. # 'st_mtime precision is limited'
  85. if attrs[-1] == 'st_mtime':
  86. d1[-1] = round(d1[-1], 4)
  87. d2[-1] = round(d2[-1], 4)
  88. d1.append(self.get_xattrs(path1))
  89. d2.append(self.get_xattrs(path2))
  90. self.assertEqual(d1, d2)
  91. def test_basic_functionality(self):
  92. # File
  93. self.create_regual_file('file1', size=1024 * 80)
  94. # Directory
  95. self.create_regual_file('dir2/file2', size=1024 * 80)
  96. # File owner
  97. os.chown('input/file1', 100, 200)
  98. # File mode
  99. os.chmod('input/file1', 0o7755)
  100. os.chmod('input/dir2', 0o700)
  101. # Block device
  102. os.mknod('input/bdev', 0o600 | stat.S_IFBLK, os.makedev(10, 20))
  103. # Char device
  104. os.mknod('input/cdev', 0o600 | stat.S_IFCHR, os.makedev(30, 40))
  105. xattr.set(os.path.join(self.input_path, 'file1'), 'user.foo', 'bar')
  106. # Hard link
  107. os.link(os.path.join(self.input_path, 'file1'),
  108. os.path.join(self.input_path, 'hardlink'))
  109. # Symlink
  110. os.symlink('somewhere', os.path.join(self.input_path, 'link1'))
  111. # FIFO node
  112. os.mkfifo(os.path.join(self.input_path, 'fifo1'))
  113. self.darc('init', self.store_location)
  114. self.darc('create', self.store_location + '::test', 'input')
  115. self.darc('create', self.store_location + '::test.2', 'input')
  116. self.darc('extract', self.store_location + '::test', 'output')
  117. self.assertEqual(len(self.darc('list', self.store_location).splitlines()), 2)
  118. self.assertEqual(len(self.darc('list', self.store_location + '::test').splitlines()), 9)
  119. self.diff_dirs('input', 'output/input')
  120. info_output = self.darc('info', self.store_location + '::test')
  121. shutil.rmtree(self.cache_path)
  122. info_output2 = self.darc('info', self.store_location + '::test')
  123. # info_output2 starts with some "initializing cache" text but should
  124. # end the same way as info_output
  125. assert info_output2.endswith(info_output)
  126. def test_overwrite(self):
  127. self.create_regual_file('file1', size=1024 * 80)
  128. self.create_regual_file('dir2/file2', size=1024 * 80)
  129. self.darc('init', self.store_location)
  130. self.darc('create', self.store_location + '::test', 'input')
  131. # Overwriting regular files and directories should be supported
  132. os.mkdir('output/input')
  133. os.mkdir('output/input/file1')
  134. os.mkdir('output/input/dir2')
  135. self.darc('extract', self.store_location + '::test', 'output')
  136. self.diff_dirs('input', 'output/input')
  137. # But non-empty dirs should fail
  138. os.unlink('output/input/file1')
  139. os.mkdir('output/input/file1')
  140. os.mkdir('output/input/file1/dir')
  141. self.darc('extract', self.store_location + '::test', 'output', exit_code=1)
  142. def test_delete(self):
  143. self.create_regual_file('file1', size=1024 * 80)
  144. self.create_regual_file('dir2/file2', size=1024 * 80)
  145. self.darc('init', self.store_location)
  146. self.darc('create', self.store_location + '::test', 'input')
  147. self.darc('create', self.store_location + '::test.2', 'input')
  148. self.darc('verify', self.store_location + '::test')
  149. self.darc('verify', self.store_location + '::test.2')
  150. self.darc('delete', self.store_location + '::test')
  151. self.darc('verify', self.store_location + '::test.2')
  152. self.darc('delete', self.store_location + '::test.2')
  153. # Make sure all data except the manifest has been deleted
  154. store = Store(self.store_path)
  155. self.assertEqual(store._len(), 1)
  156. def test_corrupted_store(self):
  157. self.create_src_archive('test')
  158. self.darc('verify', self.store_location + '::test')
  159. name = sorted(os.listdir(os.path.join(self.tmpdir, 'store', 'data', '0')), reverse=True)[0]
  160. fd = open(os.path.join(self.tmpdir, 'store', 'data', '0', name), 'r+')
  161. fd.seek(100)
  162. fd.write('X')
  163. fd.close()
  164. self.darc('verify', self.store_location + '::test', exit_code=1)
  165. def test_prune_store(self):
  166. src_dir = os.path.join(os.getcwd(), os.path.dirname(__file__))
  167. self.darc('init', self.store_location)
  168. self.darc('create', self.store_location + '::test1', src_dir)
  169. self.darc('create', self.store_location + '::test2', src_dir)
  170. self.darc('prune', self.store_location, '--daily=2')
  171. output = self.darc('list', self.store_location)
  172. assert 'test1' not in output
  173. assert 'test2' in output
  174. class ChunkTest(unittest.TestCase):
  175. def test_chunkify(self):
  176. data = b'0' * 1024 * 1024 * 15 + b'Y'
  177. parts = [bytes(c) for c in chunkify(BytesIO(data), 2, 0x3, 2, 0)]
  178. self.assertEqual(len(parts), 2)
  179. self.assertEqual(b''.join(parts), data)
  180. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b''), 2, 0x3, 2, 0)], [])
  181. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 0)], [b'fooba', b'rboobaz', b'fooba', b'rboobaz', b'fooba', b'rboobaz'])
  182. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 1)], [b'fo', b'obarb', b'oob', b'azf', b'oobarb', b'oob', b'azf', b'oobarb', b'oobaz'])
  183. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 2)], [b'foob', b'ar', b'boobazfoob', b'ar', b'boobazfoob', b'ar', b'boobaz'])
  184. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 0)], [b'foobarboobaz' * 3])
  185. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 1)], [b'foobar', b'boo', b'bazfo', b'obar', b'boo', b'bazfo', b'obar', b'boobaz'])
  186. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 2)], [b'foo', b'barboobaz', b'foo', b'barboobaz', b'foo', b'barboobaz'])
  187. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 0)], [b'foobarboobaz' * 3])
  188. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 1)], [b'foobar', b'boobazfo', b'obar', b'boobazfo', b'obar', b'boobaz'])
  189. self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 2)], [b'foob', b'arboobaz', b'foob', b'arboobaz', b'foob', b'arboobaz'])
  190. def test_buzhash(self):
  191. self.assertEqual(buzhash(b'abcdefghijklmnop', 0), 3795437769)
  192. self.assertEqual(buzhash(b'abcdefghijklmnop', 1), 3795400502)
  193. self.assertEqual(buzhash(b'abcdefghijklmnop', 1), buzhash_update(buzhash(b'Xabcdefghijklmno', 1), ord('X'), ord('p'), 16, 1))
  194. class RemoteTest(Test):
  195. prefix = 'localhost:'
  196. def suite():
  197. suite = unittest.TestSuite()
  198. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(ChunkTest))
  199. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(Test))
  200. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(RemoteTest))
  201. suite.addTest(KeySuite())
  202. suite.addTest(StoreSuite())
  203. suite.addTest(RemoteStoreSuite())
  204. suite.addTest(doctest.DocTestSuite(helpers))
  205. suite.addTest(lrucache.suite())
  206. suite.addTest(crypto.suite())
  207. return suite
  208. if __name__ == '__main__':
  209. unittest.TextTestRunner(verbosity=2).run(suite())