• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# -*- coding: utf-8 -*-
2"""Tests for distutils.archive_util."""
3import unittest
4import os
5import sys
6import tarfile
7from os.path import splitdrive
8import warnings
9
10from distutils import archive_util
11from distutils.archive_util import (check_archive_formats, make_tarball,
12                                    make_zipfile, make_archive,
13                                    ARCHIVE_FORMATS)
14from distutils.spawn import find_executable, spawn
15from distutils.tests import support
16from test.support import run_unittest, patch
17from .unix_compat import require_unix_id, require_uid_0, grp, pwd, UID_0_SUPPORT
18
19from .py38compat import change_cwd
20from .py38compat import check_warnings
21
22
23try:
24    import zipfile
25    ZIP_SUPPORT = True
26except ImportError:
27    ZIP_SUPPORT = find_executable('zip')
28
29try:
30    import zlib
31    ZLIB_SUPPORT = True
32except ImportError:
33    ZLIB_SUPPORT = False
34
35try:
36    import bz2
37except ImportError:
38    bz2 = None
39
40try:
41    import lzma
42except ImportError:
43    lzma = None
44
45def can_fs_encode(filename):
46    """
47    Return True if the filename can be saved in the file system.
48    """
49    if os.path.supports_unicode_filenames:
50        return True
51    try:
52        filename.encode(sys.getfilesystemencoding())
53    except UnicodeEncodeError:
54        return False
55    return True
56
57
58class ArchiveUtilTestCase(support.TempdirManager,
59                          support.LoggingSilencer,
60                          unittest.TestCase):
61
62    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
63    def test_make_tarball(self, name='archive'):
64        # creating something to tar
65        tmpdir = self._create_files()
66        self._make_tarball(tmpdir, name, '.tar.gz')
67        # trying an uncompressed one
68        self._make_tarball(tmpdir, name, '.tar', compress=None)
69
70    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
71    def test_make_tarball_gzip(self):
72        tmpdir = self._create_files()
73        self._make_tarball(tmpdir, 'archive', '.tar.gz', compress='gzip')
74
75    @unittest.skipUnless(bz2, 'Need bz2 support to run')
76    def test_make_tarball_bzip2(self):
77        tmpdir = self._create_files()
78        self._make_tarball(tmpdir, 'archive', '.tar.bz2', compress='bzip2')
79
80    @unittest.skipUnless(lzma, 'Need lzma support to run')
81    def test_make_tarball_xz(self):
82        tmpdir = self._create_files()
83        self._make_tarball(tmpdir, 'archive', '.tar.xz', compress='xz')
84
85    @unittest.skipUnless(can_fs_encode('årchiv'),
86        'File system cannot handle this filename')
87    def test_make_tarball_latin1(self):
88        """
89        Mirror test_make_tarball, except filename contains latin characters.
90        """
91        self.test_make_tarball('årchiv') # note this isn't a real word
92
93    @unittest.skipUnless(can_fs_encode('のアーカイブ'),
94        'File system cannot handle this filename')
95    def test_make_tarball_extended(self):
96        """
97        Mirror test_make_tarball, except filename contains extended
98        characters outside the latin charset.
99        """
100        self.test_make_tarball('のアーカイブ') # japanese for archive
101
102    def _make_tarball(self, tmpdir, target_name, suffix, **kwargs):
103        tmpdir2 = self.mkdtemp()
104        unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0],
105                            "source and target should be on same drive")
106
107        base_name = os.path.join(tmpdir2, target_name)
108
109        # working with relative paths to avoid tar warnings
110        with change_cwd(tmpdir):
111            make_tarball(splitdrive(base_name)[1], 'dist', **kwargs)
112
113        # check if the compressed tarball was created
114        tarball = base_name + suffix
115        self.assertTrue(os.path.exists(tarball))
116        self.assertEqual(self._tarinfo(tarball), self._created_files)
117
118    def _tarinfo(self, path):
119        tar = tarfile.open(path)
120        try:
121            names = tar.getnames()
122            names.sort()
123            return names
124        finally:
125            tar.close()
126
127    _zip_created_files = ['dist/', 'dist/file1', 'dist/file2',
128                          'dist/sub/', 'dist/sub/file3', 'dist/sub2/']
129    _created_files = [p.rstrip('/') for p in _zip_created_files]
130
131    def _create_files(self):
132        # creating something to tar
133        tmpdir = self.mkdtemp()
134        dist = os.path.join(tmpdir, 'dist')
135        os.mkdir(dist)
136        self.write_file([dist, 'file1'], 'xxx')
137        self.write_file([dist, 'file2'], 'xxx')
138        os.mkdir(os.path.join(dist, 'sub'))
139        self.write_file([dist, 'sub', 'file3'], 'xxx')
140        os.mkdir(os.path.join(dist, 'sub2'))
141        return tmpdir
142
143    @unittest.skipUnless(find_executable('tar') and find_executable('gzip')
144                         and ZLIB_SUPPORT,
145                         'Need the tar, gzip and zlib command to run')
146    def test_tarfile_vs_tar(self):
147        tmpdir =  self._create_files()
148        tmpdir2 = self.mkdtemp()
149        base_name = os.path.join(tmpdir2, 'archive')
150        old_dir = os.getcwd()
151        os.chdir(tmpdir)
152        try:
153            make_tarball(base_name, 'dist')
154        finally:
155            os.chdir(old_dir)
156
157        # check if the compressed tarball was created
158        tarball = base_name + '.tar.gz'
159        self.assertTrue(os.path.exists(tarball))
160
161        # now create another tarball using `tar`
162        tarball2 = os.path.join(tmpdir, 'archive2.tar.gz')
163        tar_cmd = ['tar', '-cf', 'archive2.tar', 'dist']
164        gzip_cmd = ['gzip', '-f', '-9', 'archive2.tar']
165        old_dir = os.getcwd()
166        os.chdir(tmpdir)
167        try:
168            spawn(tar_cmd)
169            spawn(gzip_cmd)
170        finally:
171            os.chdir(old_dir)
172
173        self.assertTrue(os.path.exists(tarball2))
174        # let's compare both tarballs
175        self.assertEqual(self._tarinfo(tarball), self._created_files)
176        self.assertEqual(self._tarinfo(tarball2), self._created_files)
177
178        # trying an uncompressed one
179        base_name = os.path.join(tmpdir2, 'archive')
180        old_dir = os.getcwd()
181        os.chdir(tmpdir)
182        try:
183            make_tarball(base_name, 'dist', compress=None)
184        finally:
185            os.chdir(old_dir)
186        tarball = base_name + '.tar'
187        self.assertTrue(os.path.exists(tarball))
188
189        # now for a dry_run
190        base_name = os.path.join(tmpdir2, 'archive')
191        old_dir = os.getcwd()
192        os.chdir(tmpdir)
193        try:
194            make_tarball(base_name, 'dist', compress=None, dry_run=True)
195        finally:
196            os.chdir(old_dir)
197        tarball = base_name + '.tar'
198        self.assertTrue(os.path.exists(tarball))
199
200    @unittest.skipUnless(find_executable('compress'),
201                         'The compress program is required')
202    def test_compress_deprecated(self):
203        tmpdir =  self._create_files()
204        base_name = os.path.join(self.mkdtemp(), 'archive')
205
206        # using compress and testing the PendingDeprecationWarning
207        old_dir = os.getcwd()
208        os.chdir(tmpdir)
209        try:
210            with check_warnings() as w:
211                warnings.simplefilter("always")
212                make_tarball(base_name, 'dist', compress='compress')
213        finally:
214            os.chdir(old_dir)
215        tarball = base_name + '.tar.Z'
216        self.assertTrue(os.path.exists(tarball))
217        self.assertEqual(len(w.warnings), 1)
218
219        # same test with dry_run
220        os.remove(tarball)
221        old_dir = os.getcwd()
222        os.chdir(tmpdir)
223        try:
224            with check_warnings() as w:
225                warnings.simplefilter("always")
226                make_tarball(base_name, 'dist', compress='compress',
227                             dry_run=True)
228        finally:
229            os.chdir(old_dir)
230        self.assertFalse(os.path.exists(tarball))
231        self.assertEqual(len(w.warnings), 1)
232
233    @unittest.skipUnless(ZIP_SUPPORT and ZLIB_SUPPORT,
234                         'Need zip and zlib support to run')
235    def test_make_zipfile(self):
236        # creating something to tar
237        tmpdir = self._create_files()
238        base_name = os.path.join(self.mkdtemp(), 'archive')
239        with change_cwd(tmpdir):
240            make_zipfile(base_name, 'dist')
241
242        # check if the compressed tarball was created
243        tarball = base_name + '.zip'
244        self.assertTrue(os.path.exists(tarball))
245        with zipfile.ZipFile(tarball) as zf:
246            self.assertEqual(sorted(zf.namelist()), self._zip_created_files)
247
248    @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run')
249    def test_make_zipfile_no_zlib(self):
250        patch(self, archive_util.zipfile, 'zlib', None)  # force zlib ImportError
251
252        called = []
253        zipfile_class = zipfile.ZipFile
254        def fake_zipfile(*a, **kw):
255            if kw.get('compression', None) == zipfile.ZIP_STORED:
256                called.append((a, kw))
257            return zipfile_class(*a, **kw)
258
259        patch(self, archive_util.zipfile, 'ZipFile', fake_zipfile)
260
261        # create something to tar and compress
262        tmpdir = self._create_files()
263        base_name = os.path.join(self.mkdtemp(), 'archive')
264        with change_cwd(tmpdir):
265            make_zipfile(base_name, 'dist')
266
267        tarball = base_name + '.zip'
268        self.assertEqual(called,
269                         [((tarball, "w"), {'compression': zipfile.ZIP_STORED})])
270        self.assertTrue(os.path.exists(tarball))
271        with zipfile.ZipFile(tarball) as zf:
272            self.assertEqual(sorted(zf.namelist()), self._zip_created_files)
273
274    def test_check_archive_formats(self):
275        self.assertEqual(check_archive_formats(['gztar', 'xxx', 'zip']),
276                         'xxx')
277        self.assertIsNone(check_archive_formats(['gztar', 'bztar', 'xztar',
278                                                 'ztar', 'tar', 'zip']))
279
280    def test_make_archive(self):
281        tmpdir = self.mkdtemp()
282        base_name = os.path.join(tmpdir, 'archive')
283        self.assertRaises(ValueError, make_archive, base_name, 'xxx')
284
285    def test_make_archive_cwd(self):
286        current_dir = os.getcwd()
287        def _breaks(*args, **kw):
288            raise RuntimeError()
289        ARCHIVE_FORMATS['xxx'] = (_breaks, [], 'xxx file')
290        try:
291            try:
292                make_archive('xxx', 'xxx', root_dir=self.mkdtemp())
293            except:
294                pass
295            self.assertEqual(os.getcwd(), current_dir)
296        finally:
297            del ARCHIVE_FORMATS['xxx']
298
299    def test_make_archive_tar(self):
300        base_dir =  self._create_files()
301        base_name = os.path.join(self.mkdtemp() , 'archive')
302        res = make_archive(base_name, 'tar', base_dir, 'dist')
303        self.assertTrue(os.path.exists(res))
304        self.assertEqual(os.path.basename(res), 'archive.tar')
305        self.assertEqual(self._tarinfo(res), self._created_files)
306
307    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
308    def test_make_archive_gztar(self):
309        base_dir =  self._create_files()
310        base_name = os.path.join(self.mkdtemp() , 'archive')
311        res = make_archive(base_name, 'gztar', base_dir, 'dist')
312        self.assertTrue(os.path.exists(res))
313        self.assertEqual(os.path.basename(res), 'archive.tar.gz')
314        self.assertEqual(self._tarinfo(res), self._created_files)
315
316    @unittest.skipUnless(bz2, 'Need bz2 support to run')
317    def test_make_archive_bztar(self):
318        base_dir =  self._create_files()
319        base_name = os.path.join(self.mkdtemp() , 'archive')
320        res = make_archive(base_name, 'bztar', base_dir, 'dist')
321        self.assertTrue(os.path.exists(res))
322        self.assertEqual(os.path.basename(res), 'archive.tar.bz2')
323        self.assertEqual(self._tarinfo(res), self._created_files)
324
325    @unittest.skipUnless(lzma, 'Need xz support to run')
326    def test_make_archive_xztar(self):
327        base_dir =  self._create_files()
328        base_name = os.path.join(self.mkdtemp() , 'archive')
329        res = make_archive(base_name, 'xztar', base_dir, 'dist')
330        self.assertTrue(os.path.exists(res))
331        self.assertEqual(os.path.basename(res), 'archive.tar.xz')
332        self.assertEqual(self._tarinfo(res), self._created_files)
333
334    def test_make_archive_owner_group(self):
335        # testing make_archive with owner and group, with various combinations
336        # this works even if there's not gid/uid support
337        if UID_0_SUPPORT:
338            group = grp.getgrgid(0)[0]
339            owner = pwd.getpwuid(0)[0]
340        else:
341            group = owner = 'root'
342
343        base_dir =  self._create_files()
344        root_dir = self.mkdtemp()
345        base_name = os.path.join(self.mkdtemp() , 'archive')
346        res = make_archive(base_name, 'zip', root_dir, base_dir, owner=owner,
347                           group=group)
348        self.assertTrue(os.path.exists(res))
349
350        res = make_archive(base_name, 'zip', root_dir, base_dir)
351        self.assertTrue(os.path.exists(res))
352
353        res = make_archive(base_name, 'tar', root_dir, base_dir,
354                           owner=owner, group=group)
355        self.assertTrue(os.path.exists(res))
356
357        res = make_archive(base_name, 'tar', root_dir, base_dir,
358                           owner='kjhkjhkjg', group='oihohoh')
359        self.assertTrue(os.path.exists(res))
360
361    @unittest.skipUnless(ZLIB_SUPPORT, "Requires zlib")
362    @require_unix_id
363    @require_uid_0
364    def test_tarfile_root_owner(self):
365        tmpdir =  self._create_files()
366        base_name = os.path.join(self.mkdtemp(), 'archive')
367        old_dir = os.getcwd()
368        os.chdir(tmpdir)
369        group = grp.getgrgid(0)[0]
370        owner = pwd.getpwuid(0)[0]
371        try:
372            archive_name = make_tarball(base_name, 'dist', compress=None,
373                                        owner=owner, group=group)
374        finally:
375            os.chdir(old_dir)
376
377        # check if the compressed tarball was created
378        self.assertTrue(os.path.exists(archive_name))
379
380        # now checks the rights
381        archive = tarfile.open(archive_name)
382        try:
383            for member in archive.getmembers():
384                self.assertEqual(member.uid, 0)
385                self.assertEqual(member.gid, 0)
386        finally:
387            archive.close()
388
389def test_suite():
390    return unittest.TestLoader().loadTestsFromTestCase(ArchiveUtilTestCase)
391
392if __name__ == "__main__":
393    run_unittest(test_suite())
394