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