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