OLD | NEW |
(Empty) | |
| 1 # -*- coding: utf-8 -*- |
| 2 """sdist tests""" |
| 3 |
| 4 import locale |
| 5 import os |
| 6 import shutil |
| 7 import sys |
| 8 import tempfile |
| 9 import unittest |
| 10 import unicodedata |
| 11 import re |
| 12 import contextlib |
| 13 from setuptools.tests import environment, test_svn |
| 14 from setuptools.tests.py26compat import skipIf |
| 15 |
| 16 from setuptools.compat import StringIO, unicode, PY3, PY2 |
| 17 from setuptools.command.sdist import sdist, walk_revctrl |
| 18 from setuptools.command.egg_info import manifest_maker |
| 19 from setuptools.dist import Distribution |
| 20 from setuptools import svn_utils |
| 21 |
| 22 SETUP_ATTRS = { |
| 23 'name': 'sdist_test', |
| 24 'version': '0.0', |
| 25 'packages': ['sdist_test'], |
| 26 'package_data': {'sdist_test': ['*.txt']} |
| 27 } |
| 28 |
| 29 |
| 30 SETUP_PY = """\ |
| 31 from setuptools import setup |
| 32 |
| 33 setup(**%r) |
| 34 """ % SETUP_ATTRS |
| 35 |
| 36 |
| 37 if PY3: |
| 38 LATIN1_FILENAME = 'smörbröd.py'.encode('latin-1') |
| 39 else: |
| 40 LATIN1_FILENAME = 'sm\xf6rbr\xf6d.py' |
| 41 |
| 42 |
| 43 # Cannot use context manager because of Python 2.4 |
| 44 @contextlib.contextmanager |
| 45 def quiet(): |
| 46 old_stdout, old_stderr = sys.stdout, sys.stderr |
| 47 sys.stdout, sys.stderr = StringIO(), StringIO() |
| 48 try: |
| 49 yield |
| 50 finally: |
| 51 sys.stdout, sys.stderr = old_stdout, old_stderr |
| 52 |
| 53 |
| 54 # Fake byte literals for Python <= 2.5 |
| 55 def b(s, encoding='utf-8'): |
| 56 if PY3: |
| 57 return s.encode(encoding) |
| 58 return s |
| 59 |
| 60 |
| 61 # Convert to POSIX path |
| 62 def posix(path): |
| 63 if PY3 and not isinstance(path, str): |
| 64 return path.replace(os.sep.encode('ascii'), b('/')) |
| 65 else: |
| 66 return path.replace(os.sep, '/') |
| 67 |
| 68 |
| 69 # HFS Plus uses decomposed UTF-8 |
| 70 def decompose(path): |
| 71 if isinstance(path, unicode): |
| 72 return unicodedata.normalize('NFD', path) |
| 73 try: |
| 74 path = path.decode('utf-8') |
| 75 path = unicodedata.normalize('NFD', path) |
| 76 path = path.encode('utf-8') |
| 77 except UnicodeError: |
| 78 pass # Not UTF-8 |
| 79 return path |
| 80 |
| 81 |
| 82 class TestSdistTest(unittest.TestCase): |
| 83 |
| 84 def setUp(self): |
| 85 self.temp_dir = tempfile.mkdtemp() |
| 86 f = open(os.path.join(self.temp_dir, 'setup.py'), 'w') |
| 87 f.write(SETUP_PY) |
| 88 f.close() |
| 89 |
| 90 # Set up the rest of the test package |
| 91 test_pkg = os.path.join(self.temp_dir, 'sdist_test') |
| 92 os.mkdir(test_pkg) |
| 93 # *.rst was not included in package_data, so c.rst should not be |
| 94 # automatically added to the manifest when not under version control |
| 95 for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']: |
| 96 # Just touch the files; their contents are irrelevant |
| 97 open(os.path.join(test_pkg, fname), 'w').close() |
| 98 |
| 99 self.old_cwd = os.getcwd() |
| 100 os.chdir(self.temp_dir) |
| 101 |
| 102 def tearDown(self): |
| 103 os.chdir(self.old_cwd) |
| 104 shutil.rmtree(self.temp_dir) |
| 105 |
| 106 def test_package_data_in_sdist(self): |
| 107 """Regression test for pull request #4: ensures that files listed in |
| 108 package_data are included in the manifest even if they're not added to |
| 109 version control. |
| 110 """ |
| 111 |
| 112 dist = Distribution(SETUP_ATTRS) |
| 113 dist.script_name = 'setup.py' |
| 114 cmd = sdist(dist) |
| 115 cmd.ensure_finalized() |
| 116 |
| 117 with quiet(): |
| 118 cmd.run() |
| 119 |
| 120 manifest = cmd.filelist.files |
| 121 self.assertTrue(os.path.join('sdist_test', 'a.txt') in manifest) |
| 122 self.assertTrue(os.path.join('sdist_test', 'b.txt') in manifest) |
| 123 self.assertTrue(os.path.join('sdist_test', 'c.rst') not in manifest) |
| 124 |
| 125 |
| 126 def test_defaults_case_sensitivity(self): |
| 127 """ |
| 128 Make sure default files (README.*, etc.) are added in a case-sensiti
ve |
| 129 way to avoid problems with packages built on Windows. |
| 130 """ |
| 131 |
| 132 open(os.path.join(self.temp_dir, 'readme.rst'), 'w').close() |
| 133 open(os.path.join(self.temp_dir, 'SETUP.cfg'), 'w').close() |
| 134 |
| 135 dist = Distribution(SETUP_ATTRS) |
| 136 # the extension deliberately capitalized for this test |
| 137 # to make sure the actual filename (not capitalized) gets added |
| 138 # to the manifest |
| 139 dist.script_name = 'setup.PY' |
| 140 cmd = sdist(dist) |
| 141 cmd.ensure_finalized() |
| 142 |
| 143 with quiet(): |
| 144 cmd.run() |
| 145 |
| 146 # lowercase all names so we can test in a case-insensitive way to make s
ure the files are not included |
| 147 manifest = map(lambda x: x.lower(), cmd.filelist.files) |
| 148 self.assertFalse('readme.rst' in manifest, manifest) |
| 149 self.assertFalse('setup.py' in manifest, manifest) |
| 150 self.assertFalse('setup.cfg' in manifest, manifest) |
| 151 |
| 152 def test_manifest_is_written_with_utf8_encoding(self): |
| 153 # Test for #303. |
| 154 dist = Distribution(SETUP_ATTRS) |
| 155 dist.script_name = 'setup.py' |
| 156 mm = manifest_maker(dist) |
| 157 mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
| 158 os.mkdir('sdist_test.egg-info') |
| 159 |
| 160 # UTF-8 filename |
| 161 filename = os.path.join('sdist_test', 'smörbröd.py') |
| 162 |
| 163 # Must create the file or it will get stripped. |
| 164 open(filename, 'w').close() |
| 165 |
| 166 # Add UTF-8 filename and write manifest |
| 167 with quiet(): |
| 168 mm.run() |
| 169 mm.filelist.append(filename) |
| 170 mm.write_manifest() |
| 171 |
| 172 manifest = open(mm.manifest, 'rbU') |
| 173 contents = manifest.read() |
| 174 manifest.close() |
| 175 |
| 176 # The manifest should be UTF-8 encoded |
| 177 try: |
| 178 u_contents = contents.decode('UTF-8') |
| 179 except UnicodeDecodeError: |
| 180 e = sys.exc_info()[1] |
| 181 self.fail(e) |
| 182 |
| 183 # The manifest should contain the UTF-8 filename |
| 184 if PY2: |
| 185 fs_enc = sys.getfilesystemencoding() |
| 186 filename = filename.decode(fs_enc) |
| 187 |
| 188 self.assertTrue(posix(filename) in u_contents) |
| 189 |
| 190 # Python 3 only |
| 191 if PY3: |
| 192 |
| 193 def test_write_manifest_allows_utf8_filenames(self): |
| 194 # Test for #303. |
| 195 dist = Distribution(SETUP_ATTRS) |
| 196 dist.script_name = 'setup.py' |
| 197 mm = manifest_maker(dist) |
| 198 mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
| 199 os.mkdir('sdist_test.egg-info') |
| 200 |
| 201 # UTF-8 filename |
| 202 filename = os.path.join(b('sdist_test'), b('smörbröd.py')) |
| 203 |
| 204 # Must touch the file or risk removal |
| 205 open(filename, "w").close() |
| 206 |
| 207 # Add filename and write manifest |
| 208 with quiet(): |
| 209 mm.run() |
| 210 u_filename = filename.decode('utf-8') |
| 211 mm.filelist.files.append(u_filename) |
| 212 # Re-write manifest |
| 213 mm.write_manifest() |
| 214 |
| 215 manifest = open(mm.manifest, 'rbU') |
| 216 contents = manifest.read() |
| 217 manifest.close() |
| 218 |
| 219 # The manifest should be UTF-8 encoded |
| 220 try: |
| 221 contents.decode('UTF-8') |
| 222 except UnicodeDecodeError: |
| 223 e = sys.exc_info()[1] |
| 224 self.fail(e) |
| 225 |
| 226 # The manifest should contain the UTF-8 filename |
| 227 self.assertTrue(posix(filename) in contents) |
| 228 |
| 229 # The filelist should have been updated as well |
| 230 self.assertTrue(u_filename in mm.filelist.files) |
| 231 |
| 232 def test_write_manifest_skips_non_utf8_filenames(self): |
| 233 """ |
| 234 Files that cannot be encoded to UTF-8 (specifically, those that |
| 235 weren't originally successfully decoded and have surrogate |
| 236 escapes) should be omitted from the manifest. |
| 237 See https://bitbucket.org/tarek/distribute/issue/303 for history. |
| 238 """ |
| 239 dist = Distribution(SETUP_ATTRS) |
| 240 dist.script_name = 'setup.py' |
| 241 mm = manifest_maker(dist) |
| 242 mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
| 243 os.mkdir('sdist_test.egg-info') |
| 244 |
| 245 # Latin-1 filename |
| 246 filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) |
| 247 |
| 248 # Add filename with surrogates and write manifest |
| 249 with quiet(): |
| 250 mm.run() |
| 251 u_filename = filename.decode('utf-8', 'surrogateescape') |
| 252 mm.filelist.append(u_filename) |
| 253 # Re-write manifest |
| 254 mm.write_manifest() |
| 255 |
| 256 manifest = open(mm.manifest, 'rbU') |
| 257 contents = manifest.read() |
| 258 manifest.close() |
| 259 |
| 260 # The manifest should be UTF-8 encoded |
| 261 try: |
| 262 contents.decode('UTF-8') |
| 263 except UnicodeDecodeError: |
| 264 e = sys.exc_info()[1] |
| 265 self.fail(e) |
| 266 |
| 267 # The Latin-1 filename should have been skipped |
| 268 self.assertFalse(posix(filename) in contents) |
| 269 |
| 270 # The filelist should have been updated as well |
| 271 self.assertFalse(u_filename in mm.filelist.files) |
| 272 |
| 273 def test_manifest_is_read_with_utf8_encoding(self): |
| 274 # Test for #303. |
| 275 dist = Distribution(SETUP_ATTRS) |
| 276 dist.script_name = 'setup.py' |
| 277 cmd = sdist(dist) |
| 278 cmd.ensure_finalized() |
| 279 |
| 280 # Create manifest |
| 281 with quiet(): |
| 282 cmd.run() |
| 283 |
| 284 # Add UTF-8 filename to manifest |
| 285 filename = os.path.join(b('sdist_test'), b('smörbröd.py')) |
| 286 cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
| 287 manifest = open(cmd.manifest, 'ab') |
| 288 manifest.write(b('\n') + filename) |
| 289 manifest.close() |
| 290 |
| 291 # The file must exist to be included in the filelist |
| 292 open(filename, 'w').close() |
| 293 |
| 294 # Re-read manifest |
| 295 cmd.filelist.files = [] |
| 296 with quiet(): |
| 297 cmd.read_manifest() |
| 298 |
| 299 # The filelist should contain the UTF-8 filename |
| 300 if PY3: |
| 301 filename = filename.decode('utf-8') |
| 302 self.assertTrue(filename in cmd.filelist.files) |
| 303 |
| 304 # Python 3 only |
| 305 if PY3: |
| 306 |
| 307 def test_read_manifest_skips_non_utf8_filenames(self): |
| 308 # Test for #303. |
| 309 dist = Distribution(SETUP_ATTRS) |
| 310 dist.script_name = 'setup.py' |
| 311 cmd = sdist(dist) |
| 312 cmd.ensure_finalized() |
| 313 |
| 314 # Create manifest |
| 315 with quiet(): |
| 316 cmd.run() |
| 317 |
| 318 # Add Latin-1 filename to manifest |
| 319 filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) |
| 320 cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
| 321 manifest = open(cmd.manifest, 'ab') |
| 322 manifest.write(b('\n') + filename) |
| 323 manifest.close() |
| 324 |
| 325 # The file must exist to be included in the filelist |
| 326 open(filename, 'w').close() |
| 327 |
| 328 # Re-read manifest |
| 329 cmd.filelist.files = [] |
| 330 with quiet(): |
| 331 try: |
| 332 cmd.read_manifest() |
| 333 except UnicodeDecodeError: |
| 334 e = sys.exc_info()[1] |
| 335 self.fail(e) |
| 336 |
| 337 # The Latin-1 filename should have been skipped |
| 338 filename = filename.decode('latin-1') |
| 339 self.assertFalse(filename in cmd.filelist.files) |
| 340 |
| 341 @skipIf(PY3 and locale.getpreferredencoding() != 'UTF-8', |
| 342 'Unittest fails if locale is not utf-8 but the manifests is recorded
correctly') |
| 343 def test_sdist_with_utf8_encoded_filename(self): |
| 344 # Test for #303. |
| 345 dist = Distribution(SETUP_ATTRS) |
| 346 dist.script_name = 'setup.py' |
| 347 cmd = sdist(dist) |
| 348 cmd.ensure_finalized() |
| 349 |
| 350 # UTF-8 filename |
| 351 filename = os.path.join(b('sdist_test'), b('smörbröd.py')) |
| 352 open(filename, 'w').close() |
| 353 |
| 354 with quiet(): |
| 355 cmd.run() |
| 356 |
| 357 if sys.platform == 'darwin': |
| 358 filename = decompose(filename) |
| 359 |
| 360 if PY3: |
| 361 fs_enc = sys.getfilesystemencoding() |
| 362 |
| 363 if sys.platform == 'win32': |
| 364 if fs_enc == 'cp1252': |
| 365 # Python 3 mangles the UTF-8 filename |
| 366 filename = filename.decode('cp1252') |
| 367 self.assertTrue(filename in cmd.filelist.files) |
| 368 else: |
| 369 filename = filename.decode('mbcs') |
| 370 self.assertTrue(filename in cmd.filelist.files) |
| 371 else: |
| 372 filename = filename.decode('utf-8') |
| 373 self.assertTrue(filename in cmd.filelist.files) |
| 374 else: |
| 375 self.assertTrue(filename in cmd.filelist.files) |
| 376 |
| 377 def test_sdist_with_latin1_encoded_filename(self): |
| 378 # Test for #303. |
| 379 dist = Distribution(SETUP_ATTRS) |
| 380 dist.script_name = 'setup.py' |
| 381 cmd = sdist(dist) |
| 382 cmd.ensure_finalized() |
| 383 |
| 384 # Latin-1 filename |
| 385 filename = os.path.join(b('sdist_test'), LATIN1_FILENAME) |
| 386 open(filename, 'w').close() |
| 387 self.assertTrue(os.path.isfile(filename)) |
| 388 |
| 389 with quiet(): |
| 390 cmd.run() |
| 391 |
| 392 if PY3: |
| 393 # not all windows systems have a default FS encoding of cp1252 |
| 394 if sys.platform == 'win32': |
| 395 # Latin-1 is similar to Windows-1252 however |
| 396 # on mbcs filesys it is not in latin-1 encoding |
| 397 fs_enc = sys.getfilesystemencoding() |
| 398 if fs_enc == 'mbcs': |
| 399 filename = filename.decode('mbcs') |
| 400 else: |
| 401 filename = filename.decode('latin-1') |
| 402 |
| 403 self.assertTrue(filename in cmd.filelist.files) |
| 404 else: |
| 405 # The Latin-1 filename should have been skipped |
| 406 filename = filename.decode('latin-1') |
| 407 self.assertFalse(filename in cmd.filelist.files) |
| 408 else: |
| 409 # Under Python 2 there seems to be no decoded string in the |
| 410 # filelist. However, due to decode and encoding of the |
| 411 # file name to get utf-8 Manifest the latin1 maybe excluded |
| 412 try: |
| 413 # fs_enc should match how one is expect the decoding to |
| 414 # be proformed for the manifest output. |
| 415 fs_enc = sys.getfilesystemencoding() |
| 416 filename.decode(fs_enc) |
| 417 self.assertTrue(filename in cmd.filelist.files) |
| 418 except UnicodeDecodeError: |
| 419 self.assertFalse(filename in cmd.filelist.files) |
| 420 |
| 421 class TestDummyOutput(environment.ZippedEnvironment): |
| 422 |
| 423 def setUp(self): |
| 424 self.datafile = os.path.join('setuptools', 'tests', |
| 425 'svn_data', "dummy.zip") |
| 426 self.dataname = "dummy" |
| 427 super(TestDummyOutput, self).setUp() |
| 428 |
| 429 def _run(self): |
| 430 code, data = environment.run_setup_py(["sdist"], |
| 431 pypath=self.old_cwd, |
| 432 data_stream=0) |
| 433 if code: |
| 434 info = "DIR: " + os.path.abspath('.') |
| 435 info += "\n SDIST RETURNED: %i\n\n" % code |
| 436 info += data |
| 437 raise AssertionError(info) |
| 438 |
| 439 datalines = data.splitlines() |
| 440 |
| 441 possible = ( |
| 442 "running sdist", |
| 443 "running egg_info", |
| 444 "creating dummy\.egg-info", |
| 445 "writing dummy\.egg-info", |
| 446 "writing top-level names to dummy\.egg-info", |
| 447 "writing dependency_links to dummy\.egg-info", |
| 448 "writing manifest file 'dummy\.egg-info", |
| 449 "reading manifest file 'dummy\.egg-info", |
| 450 "reading manifest template 'MANIFEST\.in'", |
| 451 "writing manifest file 'dummy\.egg-info", |
| 452 "creating dummy-0.1.1", |
| 453 "making hard links in dummy-0\.1\.1", |
| 454 "copying files to dummy-0\.1\.1", |
| 455 "copying \S+ -> dummy-0\.1\.1", |
| 456 "copying dummy", |
| 457 "copying dummy\.egg-info", |
| 458 "hard linking \S+ -> dummy-0\.1\.1", |
| 459 "hard linking dummy", |
| 460 "hard linking dummy\.egg-info", |
| 461 "Writing dummy-0\.1\.1", |
| 462 "creating dist", |
| 463 "creating 'dist", |
| 464 "Creating tar archive", |
| 465 "running check", |
| 466 "adding 'dummy-0\.1\.1", |
| 467 "tar .+ dist/dummy-0\.1\.1\.tar dummy-0\.1\.1", |
| 468 "gzip .+ dist/dummy-0\.1\.1\.tar", |
| 469 "removing 'dummy-0\.1\.1' \\(and everything under it\\)", |
| 470 ) |
| 471 |
| 472 print(" DIR: " + os.path.abspath('.')) |
| 473 for line in datalines: |
| 474 found = False |
| 475 for pattern in possible: |
| 476 if re.match(pattern, line): |
| 477 print(" READ: " + line) |
| 478 found = True |
| 479 break |
| 480 if not found: |
| 481 raise AssertionError("Unexpexected: %s\n-in-\n%s" |
| 482 % (line, data)) |
| 483 |
| 484 return data |
| 485 |
| 486 def test_sources(self): |
| 487 self._run() |
| 488 |
| 489 |
| 490 class TestSvn(environment.ZippedEnvironment): |
| 491 |
| 492 def setUp(self): |
| 493 version = svn_utils.SvnInfo.get_svn_version() |
| 494 if not version: # None or Empty |
| 495 return |
| 496 |
| 497 self.base_version = tuple([int(x) for x in version.split('.')][:2]) |
| 498 |
| 499 if not self.base_version: |
| 500 raise ValueError('No SVN tools installed') |
| 501 elif self.base_version < (1, 3): |
| 502 raise ValueError('Insufficient SVN Version %s' % version) |
| 503 elif self.base_version >= (1, 9): |
| 504 # trying the latest version |
| 505 self.base_version = (1, 8) |
| 506 |
| 507 self.dataname = "svn%i%i_example" % self.base_version |
| 508 self.datafile = os.path.join('setuptools', 'tests', |
| 509 'svn_data', self.dataname + ".zip") |
| 510 super(TestSvn, self).setUp() |
| 511 |
| 512 @skipIf(not test_svn._svn_check, "No SVN to text, in the first place") |
| 513 def test_walksvn(self): |
| 514 if self.base_version >= (1, 6): |
| 515 folder2 = 'third party2' |
| 516 folder3 = 'third party3' |
| 517 else: |
| 518 folder2 = 'third_party2' |
| 519 folder3 = 'third_party3' |
| 520 |
| 521 # TODO is this right |
| 522 expected = set([ |
| 523 os.path.join('a file'), |
| 524 os.path.join(folder2, 'Changes.txt'), |
| 525 os.path.join(folder2, 'MD5SUMS'), |
| 526 os.path.join(folder2, 'README.txt'), |
| 527 os.path.join(folder3, 'Changes.txt'), |
| 528 os.path.join(folder3, 'MD5SUMS'), |
| 529 os.path.join(folder3, 'README.txt'), |
| 530 os.path.join(folder3, 'TODO.txt'), |
| 531 os.path.join(folder3, 'fin'), |
| 532 os.path.join('third_party', 'README.txt'), |
| 533 os.path.join('folder', folder2, 'Changes.txt'), |
| 534 os.path.join('folder', folder2, 'MD5SUMS'), |
| 535 os.path.join('folder', folder2, 'WatashiNiYomimasu.txt'), |
| 536 os.path.join('folder', folder3, 'Changes.txt'), |
| 537 os.path.join('folder', folder3, 'fin'), |
| 538 os.path.join('folder', folder3, 'MD5SUMS'), |
| 539 os.path.join('folder', folder3, 'oops'), |
| 540 os.path.join('folder', folder3, 'WatashiNiYomimasu.txt'), |
| 541 os.path.join('folder', folder3, 'ZuMachen.txt'), |
| 542 os.path.join('folder', 'third_party', 'WatashiNiYomimasu.txt'), |
| 543 os.path.join('folder', 'lalala.txt'), |
| 544 os.path.join('folder', 'quest.txt'), |
| 545 # The example will have a deleted file |
| 546 # (or should) but shouldn't return it |
| 547 ]) |
| 548 self.assertEqual(set(x for x in walk_revctrl()), expected) |
| 549 |
| 550 |
| 551 def test_suite(): |
| 552 return unittest.defaultTestLoader.loadTestsFromName(__name__) |
OLD | NEW |