1"""Tests for distutils.command.sdist."""
2import os
3import tarfile
4import unittest
5import warnings
6import zipfile
7from os.path import join
8from textwrap import dedent
9from test.support import captured_stdout, check_warnings, run_unittest
10
11try:
12    import zlib
13    ZLIB_SUPPORT = True
14except ImportError:
15    ZLIB_SUPPORT = False
16
17try:
18    import grp
19    import pwd
20    UID_GID_SUPPORT = True
21except ImportError:
22    UID_GID_SUPPORT = False
23
24from distutils.command.sdist import sdist, show_formats
25from distutils.core import Distribution
26from distutils.tests.test_config import BasePyPIRCCommandTestCase
27from distutils.errors import DistutilsOptionError
28from distutils.spawn import find_executable
29from distutils.log import WARN
30from distutils.filelist import FileList
31from distutils.archive_util import ARCHIVE_FORMATS
32
33SETUP_PY = """
34from distutils.core import setup
35import somecode
36
37setup(name='fake')
38"""
39
40MANIFEST = """\
41# file GENERATED by distutils, do NOT edit
42README
43buildout.cfg
44inroot.txt
45setup.py
46data%(sep)sdata.dt
47scripts%(sep)sscript.py
48some%(sep)sfile.txt
49some%(sep)sother_file.txt
50somecode%(sep)s__init__.py
51somecode%(sep)sdoc.dat
52somecode%(sep)sdoc.txt
53"""
54
55class SDistTestCase(BasePyPIRCCommandTestCase):
56
57    def setUp(self):
58        # PyPIRCCommandTestCase creates a temp dir already
59        # and put it in self.tmp_dir
60        super(SDistTestCase, self).setUp()
61        # setting up an environment
62        self.old_path = os.getcwd()
63        os.mkdir(join(self.tmp_dir, 'somecode'))
64        os.mkdir(join(self.tmp_dir, 'dist'))
65        # a package, and a README
66        self.write_file((self.tmp_dir, 'README'), 'xxx')
67        self.write_file((self.tmp_dir, 'somecode', '__init__.py'), '#')
68        self.write_file((self.tmp_dir, 'setup.py'), SETUP_PY)
69        os.chdir(self.tmp_dir)
70
71    def tearDown(self):
72        # back to normal
73        os.chdir(self.old_path)
74        super(SDistTestCase, self).tearDown()
75
76    def get_cmd(self, metadata=None):
77        """Returns a cmd"""
78        if metadata is None:
79            metadata = {'name': 'fake', 'version': '1.0',
80                        'url': 'xxx', 'author': 'xxx',
81                        'author_email': 'xxx'}
82        dist = Distribution(metadata)
83        dist.script_name = 'setup.py'
84        dist.packages = ['somecode']
85        dist.include_package_data = True
86        cmd = sdist(dist)
87        cmd.dist_dir = 'dist'
88        return dist, cmd
89
90    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
91    def test_prune_file_list(self):
92        # this test creates a project with some VCS dirs and an NFS rename
93        # file, then launches sdist to check they get pruned on all systems
94
95        # creating VCS directories with some files in them
96        os.mkdir(join(self.tmp_dir, 'somecode', '.svn'))
97        self.write_file((self.tmp_dir, 'somecode', '.svn', 'ok.py'), 'xxx')
98
99        os.mkdir(join(self.tmp_dir, 'somecode', '.hg'))
100        self.write_file((self.tmp_dir, 'somecode', '.hg',
101                         'ok'), 'xxx')
102
103        os.mkdir(join(self.tmp_dir, 'somecode', '.git'))
104        self.write_file((self.tmp_dir, 'somecode', '.git',
105                         'ok'), 'xxx')
106
107        self.write_file((self.tmp_dir, 'somecode', '.nfs0001'), 'xxx')
108
109        # now building a sdist
110        dist, cmd = self.get_cmd()
111
112        # zip is available universally
113        # (tar might not be installed under win32)
114        cmd.formats = ['zip']
115
116        cmd.ensure_finalized()
117        cmd.run()
118
119        # now let's check what we have
120        dist_folder = join(self.tmp_dir, 'dist')
121        files = os.listdir(dist_folder)
122        self.assertEqual(files, ['fake-1.0.zip'])
123
124        zip_file = zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip'))
125        try:
126            content = zip_file.namelist()
127        finally:
128            zip_file.close()
129
130        # making sure everything has been pruned correctly
131        self.assertEqual(len(content), 4)
132
133    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
134    @unittest.skipIf(find_executable('tar') is None,
135                     "The tar command is not found")
136    @unittest.skipIf(find_executable('gzip') is None,
137                     "The gzip command is not found")
138    def test_make_distribution(self):
139        # now building a sdist
140        dist, cmd = self.get_cmd()
141
142        # creating a gztar then a tar
143        cmd.formats = ['gztar', 'tar']
144        cmd.ensure_finalized()
145        cmd.run()
146
147        # making sure we have two files
148        dist_folder = join(self.tmp_dir, 'dist')
149        result = os.listdir(dist_folder)
150        result.sort()
151        self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz'])
152
153        os.remove(join(dist_folder, 'fake-1.0.tar'))
154        os.remove(join(dist_folder, 'fake-1.0.tar.gz'))
155
156        # now trying a tar then a gztar
157        cmd.formats = ['tar', 'gztar']
158
159        cmd.ensure_finalized()
160        cmd.run()
161
162        result = os.listdir(dist_folder)
163        result.sort()
164        self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz'])
165
166    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
167    def test_add_defaults(self):
168
169        # http://bugs.python.org/issue2279
170
171        # add_default should also include
172        # data_files and package_data
173        dist, cmd = self.get_cmd()
174
175        # filling data_files by pointing files
176        # in package_data
177        dist.package_data = {'': ['*.cfg', '*.dat'],
178                             'somecode': ['*.txt']}
179        self.write_file((self.tmp_dir, 'somecode', 'doc.txt'), '#')
180        self.write_file((self.tmp_dir, 'somecode', 'doc.dat'), '#')
181
182        # adding some data in data_files
183        data_dir = join(self.tmp_dir, 'data')
184        os.mkdir(data_dir)
185        self.write_file((data_dir, 'data.dt'), '#')
186        some_dir = join(self.tmp_dir, 'some')
187        os.mkdir(some_dir)
188        # make sure VCS directories are pruned (#14004)
189        hg_dir = join(self.tmp_dir, '.hg')
190        os.mkdir(hg_dir)
191        self.write_file((hg_dir, 'last-message.txt'), '#')
192        # a buggy regex used to prevent this from working on windows (#6884)
193        self.write_file((self.tmp_dir, 'buildout.cfg'), '#')
194        self.write_file((self.tmp_dir, 'inroot.txt'), '#')
195        self.write_file((some_dir, 'file.txt'), '#')
196        self.write_file((some_dir, 'other_file.txt'), '#')
197
198        dist.data_files = [('data', ['data/data.dt',
199                                     'buildout.cfg',
200                                     'inroot.txt',
201                                     'notexisting']),
202                           'some/file.txt',
203                           'some/other_file.txt']
204
205        # adding a script
206        script_dir = join(self.tmp_dir, 'scripts')
207        os.mkdir(script_dir)
208        self.write_file((script_dir, 'script.py'), '#')
209        dist.scripts = [join('scripts', 'script.py')]
210
211        cmd.formats = ['zip']
212        cmd.use_defaults = True
213
214        cmd.ensure_finalized()
215        cmd.run()
216
217        # now let's check what we have
218        dist_folder = join(self.tmp_dir, 'dist')
219        files = os.listdir(dist_folder)
220        self.assertEqual(files, ['fake-1.0.zip'])
221
222        zip_file = zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip'))
223        try:
224            content = zip_file.namelist()
225        finally:
226            zip_file.close()
227
228        # making sure everything was added
229        self.assertEqual(len(content), 12)
230
231        # checking the MANIFEST
232        f = open(join(self.tmp_dir, 'MANIFEST'))
233        try:
234            manifest = f.read()
235        finally:
236            f.close()
237        self.assertEqual(manifest, MANIFEST % {'sep': os.sep})
238
239    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
240    def test_metadata_check_option(self):
241        # testing the `medata-check` option
242        dist, cmd = self.get_cmd(metadata={})
243
244        # this should raise some warnings !
245        # with the `check` subcommand
246        cmd.ensure_finalized()
247        cmd.run()
248        warnings = [msg for msg in self.get_logs(WARN) if
249                    msg.startswith('warning: check:')]
250        self.assertEqual(len(warnings), 2)
251
252        # trying with a complete set of metadata
253        self.clear_logs()
254        dist, cmd = self.get_cmd()
255        cmd.ensure_finalized()
256        cmd.metadata_check = 0
257        cmd.run()
258        warnings = [msg for msg in self.get_logs(WARN) if
259                    msg.startswith('warning: check:')]
260        self.assertEqual(len(warnings), 0)
261
262    def test_check_metadata_deprecated(self):
263        # makes sure make_metadata is deprecated
264        dist, cmd = self.get_cmd()
265        with check_warnings() as w:
266            warnings.simplefilter("always")
267            cmd.check_metadata()
268            self.assertEqual(len(w.warnings), 1)
269
270    def test_show_formats(self):
271        with captured_stdout() as stdout:
272            show_formats()
273
274        # the output should be a header line + one line per format
275        num_formats = len(ARCHIVE_FORMATS.keys())
276        output = [line for line in stdout.getvalue().split('\n')
277                  if line.strip().startswith('--formats=')]
278        self.assertEqual(len(output), num_formats)
279
280    def test_finalize_options(self):
281        dist, cmd = self.get_cmd()
282        cmd.finalize_options()
283
284        # default options set by finalize
285        self.assertEqual(cmd.manifest, 'MANIFEST')
286        self.assertEqual(cmd.template, 'MANIFEST.in')
287        self.assertEqual(cmd.dist_dir, 'dist')
288
289        # formats has to be a string splitable on (' ', ',') or
290        # a stringlist
291        cmd.formats = 1
292        self.assertRaises(DistutilsOptionError, cmd.finalize_options)
293        cmd.formats = ['zip']
294        cmd.finalize_options()
295
296        # formats has to be known
297        cmd.formats = 'supazipa'
298        self.assertRaises(DistutilsOptionError, cmd.finalize_options)
299
300    # the following tests make sure there is a nice error message instead
301    # of a traceback when parsing an invalid manifest template
302
303    def _check_template(self, content):
304        dist, cmd = self.get_cmd()
305        os.chdir(self.tmp_dir)
306        self.write_file('MANIFEST.in', content)
307        cmd.ensure_finalized()
308        cmd.filelist = FileList()
309        cmd.read_template()
310        warnings = self.get_logs(WARN)
311        self.assertEqual(len(warnings), 1)
312
313    def test_invalid_template_unknown_command(self):
314        self._check_template('taunt knights *')
315
316    def test_invalid_template_wrong_arguments(self):
317        # this manifest command takes one argument
318        self._check_template('prune')
319
320    @unittest.skipIf(os.name != 'nt', 'test relevant for Windows only')
321    def test_invalid_template_wrong_path(self):
322        # on Windows, trailing slashes are not allowed
323        # this used to crash instead of raising a warning: #8286
324        self._check_template('include examples/')
325
326    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
327    def test_get_file_list(self):
328        # make sure MANIFEST is recalculated
329        dist, cmd = self.get_cmd()
330
331        # filling data_files by pointing files in package_data
332        dist.package_data = {'somecode': ['*.txt']}
333        self.write_file((self.tmp_dir, 'somecode', 'doc.txt'), '#')
334        cmd.formats = ['gztar']
335        cmd.ensure_finalized()
336        cmd.run()
337
338        f = open(cmd.manifest)
339        try:
340            manifest = [line.strip() for line in f.read().split('\n')
341                        if line.strip() != '']
342        finally:
343            f.close()
344
345        self.assertEqual(len(manifest), 5)
346
347        # adding a file
348        self.write_file((self.tmp_dir, 'somecode', 'doc2.txt'), '#')
349
350        # make sure build_py is reinitialized, like a fresh run
351        build_py = dist.get_command_obj('build_py')
352        build_py.finalized = False
353        build_py.ensure_finalized()
354
355        cmd.run()
356
357        f = open(cmd.manifest)
358        try:
359            manifest2 = [line.strip() for line in f.read().split('\n')
360                         if line.strip() != '']
361        finally:
362            f.close()
363
364        # do we have the new file in MANIFEST ?
365        self.assertEqual(len(manifest2), 6)
366        self.assertIn('doc2.txt', manifest2[-1])
367
368    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
369    def test_manifest_marker(self):
370        # check that autogenerated MANIFESTs have a marker
371        dist, cmd = self.get_cmd()
372        cmd.ensure_finalized()
373        cmd.run()
374
375        f = open(cmd.manifest)
376        try:
377            manifest = [line.strip() for line in f.read().split('\n')
378                        if line.strip() != '']
379        finally:
380            f.close()
381
382        self.assertEqual(manifest[0],
383                         '# file GENERATED by distutils, do NOT edit')
384
385    @unittest.skipUnless(ZLIB_SUPPORT, "Need zlib support to run")
386    def test_manifest_comments(self):
387        # make sure comments don't cause exceptions or wrong includes
388        contents = dedent("""\
389            # bad.py
390            #bad.py
391            good.py
392            """)
393        dist, cmd = self.get_cmd()
394        cmd.ensure_finalized()
395        self.write_file((self.tmp_dir, cmd.manifest), contents)
396        self.write_file((self.tmp_dir, 'good.py'), '# pick me!')
397        self.write_file((self.tmp_dir, 'bad.py'), "# don't pick me!")
398        self.write_file((self.tmp_dir, '#bad.py'), "# don't pick me!")
399        cmd.run()
400        self.assertEqual(cmd.filelist.files, ['good.py'])
401
402    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
403    def test_manual_manifest(self):
404        # check that a MANIFEST without a marker is left alone
405        dist, cmd = self.get_cmd()
406        cmd.formats = ['gztar']
407        cmd.ensure_finalized()
408        self.write_file((self.tmp_dir, cmd.manifest), 'README.manual')
409        self.write_file((self.tmp_dir, 'README.manual'),
410                         'This project maintains its MANIFEST file itself.')
411        cmd.run()
412        self.assertEqual(cmd.filelist.files, ['README.manual'])
413
414        f = open(cmd.manifest)
415        try:
416            manifest = [line.strip() for line in f.read().split('\n')
417                        if line.strip() != '']
418        finally:
419            f.close()
420
421        self.assertEqual(manifest, ['README.manual'])
422
423        archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
424        archive = tarfile.open(archive_name)
425        try:
426            filenames = [tarinfo.name for tarinfo in archive]
427        finally:
428            archive.close()
429        self.assertEqual(sorted(filenames), ['fake-1.0', 'fake-1.0/PKG-INFO',
430                                             'fake-1.0/README.manual'])
431
432    @unittest.skipUnless(ZLIB_SUPPORT, "requires zlib")
433    @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
434    @unittest.skipIf(find_executable('tar') is None,
435                     "The tar command is not found")
436    @unittest.skipIf(find_executable('gzip') is None,
437                     "The gzip command is not found")
438    def test_make_distribution_owner_group(self):
439        # now building a sdist
440        dist, cmd = self.get_cmd()
441
442        # creating a gztar and specifying the owner+group
443        cmd.formats = ['gztar']
444        cmd.owner = pwd.getpwuid(0)[0]
445        cmd.group = grp.getgrgid(0)[0]
446        cmd.ensure_finalized()
447        cmd.run()
448
449        # making sure we have the good rights
450        archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
451        archive = tarfile.open(archive_name)
452        try:
453            for member in archive.getmembers():
454                self.assertEqual(member.uid, 0)
455                self.assertEqual(member.gid, 0)
456        finally:
457            archive.close()
458
459        # building a sdist again
460        dist, cmd = self.get_cmd()
461
462        # creating a gztar
463        cmd.formats = ['gztar']
464        cmd.ensure_finalized()
465        cmd.run()
466
467        # making sure we have the good rights
468        archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
469        archive = tarfile.open(archive_name)
470
471        # note that we are not testing the group ownership here
472        # because, depending on the platforms and the container
473        # rights (see #7408)
474        try:
475            for member in archive.getmembers():
476                self.assertEqual(member.uid, os.getuid())
477        finally:
478            archive.close()
479
480def test_suite():
481    return unittest.makeSuite(SDistTestCase)
482
483if __name__ == "__main__":
484    run_unittest(test_suite())
485