test_archive_util.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. """Tests for distutils.archive_util."""
  2. import functools
  3. import operator
  4. import os
  5. import pathlib
  6. import sys
  7. import tarfile
  8. from distutils import archive_util
  9. from distutils.archive_util import (
  10. ARCHIVE_FORMATS,
  11. check_archive_formats,
  12. make_archive,
  13. make_tarball,
  14. make_zipfile,
  15. )
  16. from distutils.spawn import spawn
  17. from distutils.tests import support
  18. from os.path import splitdrive
  19. import path
  20. import pytest
  21. from test.support import patch
  22. from .unix_compat import UID_0_SUPPORT, grp, pwd, require_uid_0, require_unix_id
  23. def can_fs_encode(filename):
  24. """
  25. Return True if the filename can be saved in the file system.
  26. """
  27. if os.path.supports_unicode_filenames:
  28. return True
  29. try:
  30. filename.encode(sys.getfilesystemencoding())
  31. except UnicodeEncodeError:
  32. return False
  33. return True
  34. def all_equal(values):
  35. return functools.reduce(operator.eq, values)
  36. def same_drive(*paths):
  37. return all_equal(pathlib.Path(path).drive for path in paths)
  38. class ArchiveUtilTestCase(support.TempdirManager):
  39. @pytest.mark.usefixtures('needs_zlib')
  40. def test_make_tarball(self, name='archive'):
  41. # creating something to tar
  42. tmpdir = self._create_files()
  43. self._make_tarball(tmpdir, name, '.tar.gz')
  44. # trying an uncompressed one
  45. self._make_tarball(tmpdir, name, '.tar', compress=None)
  46. @pytest.mark.usefixtures('needs_zlib')
  47. def test_make_tarball_gzip(self):
  48. tmpdir = self._create_files()
  49. self._make_tarball(tmpdir, 'archive', '.tar.gz', compress='gzip')
  50. def test_make_tarball_bzip2(self):
  51. pytest.importorskip('bz2')
  52. tmpdir = self._create_files()
  53. self._make_tarball(tmpdir, 'archive', '.tar.bz2', compress='bzip2')
  54. def test_make_tarball_xz(self):
  55. pytest.importorskip('lzma')
  56. tmpdir = self._create_files()
  57. self._make_tarball(tmpdir, 'archive', '.tar.xz', compress='xz')
  58. @pytest.mark.skipif("not can_fs_encode('årchiv')")
  59. def test_make_tarball_latin1(self):
  60. """
  61. Mirror test_make_tarball, except filename contains latin characters.
  62. """
  63. self.test_make_tarball('årchiv') # note this isn't a real word
  64. @pytest.mark.skipif("not can_fs_encode('のアーカイブ')")
  65. def test_make_tarball_extended(self):
  66. """
  67. Mirror test_make_tarball, except filename contains extended
  68. characters outside the latin charset.
  69. """
  70. self.test_make_tarball('のアーカイブ') # japanese for archive
  71. def _make_tarball(self, tmpdir, target_name, suffix, **kwargs):
  72. tmpdir2 = self.mkdtemp()
  73. if same_drive(tmpdir, tmpdir2):
  74. pytest.skip("source and target should be on same drive")
  75. base_name = os.path.join(tmpdir2, target_name)
  76. # working with relative paths to avoid tar warnings
  77. with path.Path(tmpdir):
  78. make_tarball(splitdrive(base_name)[1], 'dist', **kwargs)
  79. # check if the compressed tarball was created
  80. tarball = base_name + suffix
  81. assert os.path.exists(tarball)
  82. assert self._tarinfo(tarball) == self._created_files
  83. def _tarinfo(self, path):
  84. tar = tarfile.open(path)
  85. try:
  86. names = tar.getnames()
  87. names.sort()
  88. return names
  89. finally:
  90. tar.close()
  91. _zip_created_files = [
  92. 'dist/',
  93. 'dist/file1',
  94. 'dist/file2',
  95. 'dist/sub/',
  96. 'dist/sub/file3',
  97. 'dist/sub2/',
  98. ]
  99. _created_files = [p.rstrip('/') for p in _zip_created_files]
  100. def _create_files(self):
  101. # creating something to tar
  102. tmpdir = self.mkdtemp()
  103. dist = os.path.join(tmpdir, 'dist')
  104. os.mkdir(dist)
  105. self.write_file([dist, 'file1'], 'xxx')
  106. self.write_file([dist, 'file2'], 'xxx')
  107. os.mkdir(os.path.join(dist, 'sub'))
  108. self.write_file([dist, 'sub', 'file3'], 'xxx')
  109. os.mkdir(os.path.join(dist, 'sub2'))
  110. return tmpdir
  111. @pytest.mark.usefixtures('needs_zlib')
  112. @pytest.mark.skipif("not (shutil.which('tar') and shutil.which('gzip'))")
  113. def test_tarfile_vs_tar(self):
  114. tmpdir = self._create_files()
  115. tmpdir2 = self.mkdtemp()
  116. base_name = os.path.join(tmpdir2, 'archive')
  117. old_dir = os.getcwd()
  118. os.chdir(tmpdir)
  119. try:
  120. make_tarball(base_name, 'dist')
  121. finally:
  122. os.chdir(old_dir)
  123. # check if the compressed tarball was created
  124. tarball = base_name + '.tar.gz'
  125. assert os.path.exists(tarball)
  126. # now create another tarball using `tar`
  127. tarball2 = os.path.join(tmpdir, 'archive2.tar.gz')
  128. tar_cmd = ['tar', '-cf', 'archive2.tar', 'dist']
  129. gzip_cmd = ['gzip', '-f', '-9', 'archive2.tar']
  130. old_dir = os.getcwd()
  131. os.chdir(tmpdir)
  132. try:
  133. spawn(tar_cmd)
  134. spawn(gzip_cmd)
  135. finally:
  136. os.chdir(old_dir)
  137. assert os.path.exists(tarball2)
  138. # let's compare both tarballs
  139. assert self._tarinfo(tarball) == self._created_files
  140. assert self._tarinfo(tarball2) == self._created_files
  141. # trying an uncompressed one
  142. base_name = os.path.join(tmpdir2, 'archive')
  143. old_dir = os.getcwd()
  144. os.chdir(tmpdir)
  145. try:
  146. make_tarball(base_name, 'dist', compress=None)
  147. finally:
  148. os.chdir(old_dir)
  149. tarball = base_name + '.tar'
  150. assert os.path.exists(tarball)
  151. @pytest.mark.usefixtures('needs_zlib')
  152. def test_make_zipfile(self):
  153. zipfile = pytest.importorskip('zipfile')
  154. # creating something to tar
  155. tmpdir = self._create_files()
  156. base_name = os.path.join(self.mkdtemp(), 'archive')
  157. with path.Path(tmpdir):
  158. make_zipfile(base_name, 'dist')
  159. # check if the compressed tarball was created
  160. tarball = base_name + '.zip'
  161. assert os.path.exists(tarball)
  162. with zipfile.ZipFile(tarball) as zf:
  163. assert sorted(zf.namelist()) == self._zip_created_files
  164. def test_make_zipfile_no_zlib(self):
  165. zipfile = pytest.importorskip('zipfile')
  166. patch(self, archive_util.zipfile, 'zlib', None) # force zlib ImportError
  167. called = []
  168. zipfile_class = zipfile.ZipFile
  169. def fake_zipfile(*a, **kw):
  170. if kw.get('compression', None) == zipfile.ZIP_STORED:
  171. called.append((a, kw))
  172. return zipfile_class(*a, **kw)
  173. patch(self, archive_util.zipfile, 'ZipFile', fake_zipfile)
  174. # create something to tar and compress
  175. tmpdir = self._create_files()
  176. base_name = os.path.join(self.mkdtemp(), 'archive')
  177. with path.Path(tmpdir):
  178. make_zipfile(base_name, 'dist')
  179. tarball = base_name + '.zip'
  180. assert called == [((tarball, "w"), {'compression': zipfile.ZIP_STORED})]
  181. assert os.path.exists(tarball)
  182. with zipfile.ZipFile(tarball) as zf:
  183. assert sorted(zf.namelist()) == self._zip_created_files
  184. def test_check_archive_formats(self):
  185. assert check_archive_formats(['gztar', 'xxx', 'zip']) == 'xxx'
  186. assert (
  187. check_archive_formats(['gztar', 'bztar', 'xztar', 'ztar', 'tar', 'zip'])
  188. is None
  189. )
  190. def test_make_archive(self):
  191. tmpdir = self.mkdtemp()
  192. base_name = os.path.join(tmpdir, 'archive')
  193. with pytest.raises(ValueError):
  194. make_archive(base_name, 'xxx')
  195. def test_make_archive_cwd(self):
  196. current_dir = os.getcwd()
  197. def _breaks(*args, **kw):
  198. raise RuntimeError()
  199. ARCHIVE_FORMATS['xxx'] = (_breaks, [], 'xxx file')
  200. try:
  201. try:
  202. make_archive('xxx', 'xxx', root_dir=self.mkdtemp())
  203. except Exception:
  204. pass
  205. assert os.getcwd() == current_dir
  206. finally:
  207. ARCHIVE_FORMATS.pop('xxx')
  208. def test_make_archive_tar(self):
  209. base_dir = self._create_files()
  210. base_name = os.path.join(self.mkdtemp(), 'archive')
  211. res = make_archive(base_name, 'tar', base_dir, 'dist')
  212. assert os.path.exists(res)
  213. assert os.path.basename(res) == 'archive.tar'
  214. assert self._tarinfo(res) == self._created_files
  215. @pytest.mark.usefixtures('needs_zlib')
  216. def test_make_archive_gztar(self):
  217. base_dir = self._create_files()
  218. base_name = os.path.join(self.mkdtemp(), 'archive')
  219. res = make_archive(base_name, 'gztar', base_dir, 'dist')
  220. assert os.path.exists(res)
  221. assert os.path.basename(res) == 'archive.tar.gz'
  222. assert self._tarinfo(res) == self._created_files
  223. def test_make_archive_bztar(self):
  224. pytest.importorskip('bz2')
  225. base_dir = self._create_files()
  226. base_name = os.path.join(self.mkdtemp(), 'archive')
  227. res = make_archive(base_name, 'bztar', base_dir, 'dist')
  228. assert os.path.exists(res)
  229. assert os.path.basename(res) == 'archive.tar.bz2'
  230. assert self._tarinfo(res) == self._created_files
  231. def test_make_archive_xztar(self):
  232. pytest.importorskip('lzma')
  233. base_dir = self._create_files()
  234. base_name = os.path.join(self.mkdtemp(), 'archive')
  235. res = make_archive(base_name, 'xztar', base_dir, 'dist')
  236. assert os.path.exists(res)
  237. assert os.path.basename(res) == 'archive.tar.xz'
  238. assert self._tarinfo(res) == self._created_files
  239. def test_make_archive_owner_group(self):
  240. # testing make_archive with owner and group, with various combinations
  241. # this works even if there's not gid/uid support
  242. if UID_0_SUPPORT:
  243. group = grp.getgrgid(0)[0]
  244. owner = pwd.getpwuid(0)[0]
  245. else:
  246. group = owner = 'root'
  247. base_dir = self._create_files()
  248. root_dir = self.mkdtemp()
  249. base_name = os.path.join(self.mkdtemp(), 'archive')
  250. res = make_archive(
  251. base_name, 'zip', root_dir, base_dir, owner=owner, group=group
  252. )
  253. assert os.path.exists(res)
  254. res = make_archive(base_name, 'zip', root_dir, base_dir)
  255. assert os.path.exists(res)
  256. res = make_archive(
  257. base_name, 'tar', root_dir, base_dir, owner=owner, group=group
  258. )
  259. assert os.path.exists(res)
  260. res = make_archive(
  261. base_name, 'tar', root_dir, base_dir, owner='kjhkjhkjg', group='oihohoh'
  262. )
  263. assert os.path.exists(res)
  264. @pytest.mark.usefixtures('needs_zlib')
  265. @require_unix_id
  266. @require_uid_0
  267. def test_tarfile_root_owner(self):
  268. tmpdir = self._create_files()
  269. base_name = os.path.join(self.mkdtemp(), 'archive')
  270. old_dir = os.getcwd()
  271. os.chdir(tmpdir)
  272. group = grp.getgrgid(0)[0]
  273. owner = pwd.getpwuid(0)[0]
  274. try:
  275. archive_name = make_tarball(
  276. base_name, 'dist', compress=None, owner=owner, group=group
  277. )
  278. finally:
  279. os.chdir(old_dir)
  280. # check if the compressed tarball was created
  281. assert os.path.exists(archive_name)
  282. # now checks the rights
  283. archive = tarfile.open(archive_name)
  284. try:
  285. for member in archive.getmembers():
  286. assert member.uid == 0
  287. assert member.gid == 0
  288. finally:
  289. archive.close()