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