test.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. from __future__ import with_statement
  2. import doctest
  3. import filecmp
  4. import os
  5. from StringIO import StringIO
  6. import stat
  7. import sys
  8. import shutil
  9. import tempfile
  10. import unittest
  11. from xattr import xattr, XATTR_NOFOLLOW
  12. from . import helpers, lrucache
  13. from .chunker import chunkify, buzhash, buzhash_update
  14. from .archiver import Archiver
  15. from .key import suite as KeySuite
  16. from .store import Store, suite as StoreSuite
  17. from .remote import Store, suite as RemoteStoreSuite
  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, 'wbx') as fd:
  62. fd.write('X' * size)
  63. def get_xattrs(self, path):
  64. try:
  65. return dict(xattr(path, XATTR_NOFOLLOW))
  66. except IOError:
  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. # We can't restore symlink atime/mtime right now
  80. if not os.path.islink(path1):
  81. attrs.append('st_mtime')
  82. d1 = [filename] + [getattr(s1, a) for a in attrs]
  83. d2 = [filename] + [getattr(s2, a) for a in attrs]
  84. if(len(d1) == 6):
  85. d1[-1] = int(d1[-1])
  86. d2[-1] = int(d2[-1])
  87. d1.append(self.get_xattrs(path1))
  88. d2.append(self.get_xattrs(path2))
  89. self.assertEqual(d1, d2)
  90. def test_basic_functionality(self):
  91. # File
  92. self.create_regual_file('file1', size=1024 * 80)
  93. # Directory
  94. self.create_regual_file('dir2/file2', size=1024 * 80)
  95. # File owner
  96. os.chown('input/file1', 100, 200)
  97. # File mode
  98. os.chmod('input/file1', 7755)
  99. os.chmod('input/dir2', 0700)
  100. # Block device
  101. os.mknod('input/bdev', 0600 | stat.S_IFBLK, os.makedev(10, 20))
  102. # Char device
  103. os.mknod('input/cdev', 0600 | stat.S_IFCHR, os.makedev(30, 40))
  104. # xattr
  105. x = xattr(os.path.join(self.input_path, 'file1'))
  106. x.set('user.foo', 'bar')
  107. # Hard link
  108. os.link(os.path.join(self.input_path, 'file1'),
  109. os.path.join(self.input_path, 'hardlink'))
  110. # Symlink
  111. os.symlink('somewhere', os.path.join(self.input_path, 'link1'))
  112. # FIFO node
  113. os.mkfifo(os.path.join(self.input_path, 'fifo1'))
  114. self.darc('init', self.store_location)
  115. self.darc('create', self.store_location + '::test', 'input')
  116. self.darc('create', self.store_location + '::test.2', 'input')
  117. self.darc('extract', self.store_location + '::test', 'output')
  118. self.assertEqual(len(self.darc('list', self.store_location).splitlines()), 2)
  119. self.assertEqual(len(self.darc('list', self.store_location + '::test').splitlines()), 9)
  120. self.diff_dirs('input', 'output/input')
  121. info_output = self.darc('info', self.store_location + '::test')
  122. shutil.rmtree(self.cache_path)
  123. info_output2 = self.darc('info', self.store_location + '::test')
  124. # info_output2 starts with some "initializing cache" text but should
  125. # end the same way as info_output
  126. assert info_output2.endswith(info_output)
  127. def test_overwrite(self):
  128. self.create_regual_file('file1', size=1024 * 80)
  129. self.create_regual_file('dir2/file2', size=1024 * 80)
  130. self.darc('init', self.store_location)
  131. self.darc('create', self.store_location + '::test', 'input')
  132. # Overwriting regular files and directories should be supported
  133. os.mkdir('output/input')
  134. os.mkdir('output/input/file1')
  135. os.mkdir('output/input/dir2')
  136. self.darc('extract', self.store_location + '::test', 'output')
  137. self.diff_dirs('input', 'output/input')
  138. # But non-empty dirs should fail
  139. os.unlink('output/input/file1')
  140. os.mkdir('output/input/file1')
  141. os.mkdir('output/input/file1/dir')
  142. self.darc('extract', self.store_location + '::test', 'output', exit_code=1)
  143. def test_delete(self):
  144. self.create_regual_file('file1', size=1024 * 80)
  145. self.create_regual_file('dir2/file2', size=1024 * 80)
  146. self.darc('init', self.store_location)
  147. self.darc('create', self.store_location + '::test', 'input')
  148. self.darc('create', self.store_location + '::test.2', 'input')
  149. self.darc('verify', self.store_location + '::test')
  150. self.darc('verify', self.store_location + '::test.2')
  151. self.darc('delete', self.store_location + '::test')
  152. self.darc('verify', self.store_location + '::test.2')
  153. self.darc('delete', self.store_location + '::test.2')
  154. # Make sure all data except the manifest has been deleted
  155. store = Store(self.store_path)
  156. self.assertEqual(store._len(), 1)
  157. def test_corrupted_store(self):
  158. self.create_src_archive('test')
  159. self.darc('verify', self.store_location + '::test')
  160. name = sorted(os.listdir(os.path.join(self.tmpdir, 'store', 'data', '0')), reverse=True)[0]
  161. fd = open(os.path.join(self.tmpdir, 'store', 'data', '0', name), 'r+')
  162. fd.seek(100)
  163. fd.write('X')
  164. fd.close()
  165. self.darc('verify', self.store_location + '::test', exit_code=1)
  166. def test_prune_store(self):
  167. src_dir = os.path.join(os.getcwd(), os.path.dirname(__file__))
  168. self.darc('init', self.store_location)
  169. self.darc('create', self.store_location + '::test1', src_dir)
  170. self.darc('create', self.store_location + '::test2', src_dir)
  171. self.darc('prune', self.store_location, '--daily=2')
  172. output = self.darc('list', self.store_location)
  173. assert 'test1' not in output
  174. assert 'test2' in output
  175. class ChunkTest(unittest.TestCase):
  176. def test_chunkify(self):
  177. data = '0' * 1024 * 1024 * 15 + 'Y'
  178. parts = [str(c) for c in chunkify(StringIO(data), 2, 0x3, 2, 0)]
  179. self.assertEqual(len(parts), 2)
  180. self.assertEqual(''.join(parts), data)
  181. self.assertEqual([str(c) for c in chunkify(StringIO(''), 2, 0x3, 2, 0)], [])
  182. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 2, 0x3, 2, 0)], ['fooba', 'rboobaz', 'fooba', 'rboobaz', 'fooba', 'rboobaz'])
  183. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 2, 0x3, 2, 1)], ['fo', 'obarb', 'oob', 'azf', 'oobarb', 'oob', 'azf', 'oobarb', 'oobaz'])
  184. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 2, 0x3, 2, 2)], ['foob', 'ar', 'boobazfoob', 'ar', 'boobazfoob', 'ar', 'boobaz'])
  185. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 3, 0x3, 3, 0)], ['foobarboobaz' * 3])
  186. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 3, 0x3, 3, 1)], ['foobar', 'boo', 'bazfo', 'obar', 'boo', 'bazfo', 'obar', 'boobaz'])
  187. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 3, 0x3, 3, 2)], ['foo', 'barboobaz', 'foo', 'barboobaz', 'foo', 'barboobaz'])
  188. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 3, 0x3, 4, 0)], ['foobarboobaz' * 3])
  189. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 3, 0x3, 4, 1)], ['foobar', 'boobazfo', 'obar', 'boobazfo', 'obar', 'boobaz'])
  190. self.assertEqual([str(c) for c in chunkify(StringIO('foobarboobaz' * 3), 3, 0x3, 4, 2)], ['foob', 'arboobaz', 'foob', 'arboobaz', 'foob', 'arboobaz'])
  191. def test_buzhash(self):
  192. self.assertEqual(buzhash('abcdefghijklmnop', 0), 3795437769L)
  193. self.assertEqual(buzhash('abcdefghijklmnop', 1), 3795400502L)
  194. self.assertEqual(buzhash('abcdefghijklmnop', 1), buzhash_update(buzhash('Xabcdefghijklmno', 1), ord('X'), ord('p'), 16, 1))
  195. class RemoteTest(Test):
  196. prefix = 'localhost:'
  197. def suite():
  198. suite = unittest.TestSuite()
  199. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(ChunkTest))
  200. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(Test))
  201. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(RemoteTest))
  202. suite.addTest(KeySuite())
  203. suite.addTest(StoreSuite())
  204. suite.addTest(RemoteStoreSuite())
  205. suite.addTest(doctest.DocTestSuite(helpers))
  206. suite.addTest(lrucache.suite())
  207. return suite
  208. if __name__ == '__main__':
  209. unittest.TextTestRunner(verbosity=2).run(suite())