test.py 10 KB

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