Home | History | Annotate | Download | only in tests
      1 """Tests for distutils.command.sdist."""
      2 import os
      3 import tarfile
      4 import unittest
      5 import warnings
      6 import zipfile
      7 from os.path import join
      8 from textwrap import dedent
      9 from test.support import captured_stdout, check_warnings, run_unittest
     10 
     11 try:
     12     import zlib
     13     ZLIB_SUPPORT = True
     14 except ImportError:
     15     ZLIB_SUPPORT = False
     16 
     17 try:
     18     import grp
     19     import pwd
     20     UID_GID_SUPPORT = True
     21 except ImportError:
     22     UID_GID_SUPPORT = False
     23 
     24 from distutils.command.sdist import sdist, show_formats
     25 from distutils.core import Distribution
     26 from distutils.tests.test_config import BasePyPIRCCommandTestCase
     27 from distutils.errors import DistutilsOptionError
     28 from distutils.spawn import find_executable
     29 from distutils.log import WARN
     30 from distutils.filelist import FileList
     31 from distutils.archive_util import ARCHIVE_FORMATS
     32 
     33 SETUP_PY = """
     34 from distutils.core import setup
     35 import somecode
     36 
     37 setup(name='fake')
     38 """
     39 
     40 MANIFEST = """\
     41 # file GENERATED by distutils, do NOT edit
     42 README
     43 buildout.cfg
     44 inroot.txt
     45 setup.py
     46 data%(sep)sdata.dt
     47 scripts%(sep)sscript.py
     48 some%(sep)sfile.txt
     49 some%(sep)sother_file.txt
     50 somecode%(sep)s__init__.py
     51 somecode%(sep)sdoc.dat
     52 somecode%(sep)sdoc.txt
     53 """
     54 
     55 class 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 
    480 def test_suite():
    481     return unittest.makeSuite(SDistTestCase)
    482 
    483 if __name__ == "__main__":
    484     run_unittest(test_suite())
    485