1import contextlib 2import pytest 3from distutils.errors import DistutilsOptionError, DistutilsFileError 4from setuptools.dist import Distribution 5from setuptools.config import ConfigHandler, read_configuration 6 7 8class ErrConfigHandler(ConfigHandler): 9 """Erroneous handler. Fails to implement required methods.""" 10 11 12def make_package_dir(name, base_dir): 13 dir_package = base_dir.mkdir(name) 14 init_file = dir_package.join('__init__.py') 15 init_file.write('') 16 return dir_package, init_file 17 18 19def fake_env(tmpdir, setup_cfg, setup_py=None): 20 21 if setup_py is None: 22 setup_py = ( 23 'from setuptools import setup\n' 24 'setup()\n' 25 ) 26 27 tmpdir.join('setup.py').write(setup_py) 28 config = tmpdir.join('setup.cfg') 29 config.write(setup_cfg) 30 31 package_dir, init_file = make_package_dir('fake_package', tmpdir) 32 33 init_file.write( 34 'VERSION = (1, 2, 3)\n' 35 '\n' 36 'VERSION_MAJOR = 1' 37 '\n' 38 'def get_version():\n' 39 ' return [3, 4, 5, "dev"]\n' 40 '\n' 41 ) 42 return package_dir, config 43 44 45@contextlib.contextmanager 46def get_dist(tmpdir, kwargs_initial=None, parse=True): 47 kwargs_initial = kwargs_initial or {} 48 49 with tmpdir.as_cwd(): 50 dist = Distribution(kwargs_initial) 51 dist.script_name = 'setup.py' 52 parse and dist.parse_config_files() 53 54 yield dist 55 56 57def test_parsers_implemented(): 58 59 with pytest.raises(NotImplementedError): 60 handler = ErrConfigHandler(None, {}) 61 handler.parsers 62 63 64class TestConfigurationReader: 65 66 def test_basic(self, tmpdir): 67 _, config = fake_env( 68 tmpdir, 69 '[metadata]\n' 70 'version = 10.1.1\n' 71 'keywords = one, two\n' 72 '\n' 73 '[options]\n' 74 'scripts = bin/a.py, bin/b.py\n' 75 ) 76 config_dict = read_configuration('%s' % config) 77 assert config_dict['metadata']['version'] == '10.1.1' 78 assert config_dict['metadata']['keywords'] == ['one', 'two'] 79 assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py'] 80 81 def test_no_config(self, tmpdir): 82 with pytest.raises(DistutilsFileError): 83 read_configuration('%s' % tmpdir.join('setup.cfg')) 84 85 def test_ignore_errors(self, tmpdir): 86 _, config = fake_env( 87 tmpdir, 88 '[metadata]\n' 89 'version = attr: none.VERSION\n' 90 'keywords = one, two\n' 91 ) 92 with pytest.raises(ImportError): 93 read_configuration('%s' % config) 94 95 config_dict = read_configuration( 96 '%s' % config, ignore_option_errors=True) 97 98 assert config_dict['metadata']['keywords'] == ['one', 'two'] 99 assert 'version' not in config_dict['metadata'] 100 101 config.remove() 102 103 104class TestMetadata: 105 106 def test_basic(self, tmpdir): 107 108 fake_env( 109 tmpdir, 110 '[metadata]\n' 111 'version = 10.1.1\n' 112 'description = Some description\n' 113 'long_description_content_type = text/something\n' 114 'long_description = file: README\n' 115 'name = fake_name\n' 116 'keywords = one, two\n' 117 'provides = package, package.sub\n' 118 'license = otherlic\n' 119 'download_url = http://test.test.com/test/\n' 120 'maintainer_email = test@test.com\n' 121 ) 122 123 tmpdir.join('README').write('readme contents\nline2') 124 125 meta_initial = { 126 # This will be used so `otherlic` won't replace it. 127 'license': 'BSD 3-Clause License', 128 } 129 130 with get_dist(tmpdir, meta_initial) as dist: 131 metadata = dist.metadata 132 133 assert metadata.version == '10.1.1' 134 assert metadata.description == 'Some description' 135 assert metadata.long_description_content_type == 'text/something' 136 assert metadata.long_description == 'readme contents\nline2' 137 assert metadata.provides == ['package', 'package.sub'] 138 assert metadata.license == 'BSD 3-Clause License' 139 assert metadata.name == 'fake_name' 140 assert metadata.keywords == ['one', 'two'] 141 assert metadata.download_url == 'http://test.test.com/test/' 142 assert metadata.maintainer_email == 'test@test.com' 143 144 def test_file_mixed(self, tmpdir): 145 146 fake_env( 147 tmpdir, 148 '[metadata]\n' 149 'long_description = file: README.rst, CHANGES.rst\n' 150 '\n' 151 ) 152 153 tmpdir.join('README.rst').write('readme contents\nline2') 154 tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff') 155 156 with get_dist(tmpdir) as dist: 157 assert dist.metadata.long_description == ( 158 'readme contents\nline2\n' 159 'changelog contents\nand stuff' 160 ) 161 162 def test_file_sandboxed(self, tmpdir): 163 164 fake_env( 165 tmpdir, 166 '[metadata]\n' 167 'long_description = file: ../../README\n' 168 ) 169 170 with get_dist(tmpdir, parse=False) as dist: 171 with pytest.raises(DistutilsOptionError): 172 dist.parse_config_files() # file: out of sandbox 173 174 def test_aliases(self, tmpdir): 175 176 fake_env( 177 tmpdir, 178 '[metadata]\n' 179 'author-email = test@test.com\n' 180 'home-page = http://test.test.com/test/\n' 181 'summary = Short summary\n' 182 'platform = a, b\n' 183 'classifier =\n' 184 ' Framework :: Django\n' 185 ' Programming Language :: Python :: 3.5\n' 186 ) 187 188 with get_dist(tmpdir) as dist: 189 metadata = dist.metadata 190 assert metadata.author_email == 'test@test.com' 191 assert metadata.url == 'http://test.test.com/test/' 192 assert metadata.description == 'Short summary' 193 assert metadata.platforms == ['a', 'b'] 194 assert metadata.classifiers == [ 195 'Framework :: Django', 196 'Programming Language :: Python :: 3.5', 197 ] 198 199 def test_multiline(self, tmpdir): 200 201 fake_env( 202 tmpdir, 203 '[metadata]\n' 204 'name = fake_name\n' 205 'keywords =\n' 206 ' one\n' 207 ' two\n' 208 'classifiers =\n' 209 ' Framework :: Django\n' 210 ' Programming Language :: Python :: 3.5\n' 211 ) 212 with get_dist(tmpdir) as dist: 213 metadata = dist.metadata 214 assert metadata.keywords == ['one', 'two'] 215 assert metadata.classifiers == [ 216 'Framework :: Django', 217 'Programming Language :: Python :: 3.5', 218 ] 219 220 def test_dict(self, tmpdir): 221 222 fake_env( 223 tmpdir, 224 '[metadata]\n' 225 'project_urls =\n' 226 ' Link One = https://example.com/one/\n' 227 ' Link Two = https://example.com/two/\n' 228 ) 229 with get_dist(tmpdir) as dist: 230 metadata = dist.metadata 231 assert metadata.project_urls == { 232 'Link One': 'https://example.com/one/', 233 'Link Two': 'https://example.com/two/', 234 } 235 236 def test_version(self, tmpdir): 237 238 _, config = fake_env( 239 tmpdir, 240 '[metadata]\n' 241 'version = attr: fake_package.VERSION\n' 242 ) 243 with get_dist(tmpdir) as dist: 244 assert dist.metadata.version == '1.2.3' 245 246 config.write( 247 '[metadata]\n' 248 'version = attr: fake_package.get_version\n' 249 ) 250 with get_dist(tmpdir) as dist: 251 assert dist.metadata.version == '3.4.5.dev' 252 253 config.write( 254 '[metadata]\n' 255 'version = attr: fake_package.VERSION_MAJOR\n' 256 ) 257 with get_dist(tmpdir) as dist: 258 assert dist.metadata.version == '1' 259 260 subpack = tmpdir.join('fake_package').mkdir('subpackage') 261 subpack.join('__init__.py').write('') 262 subpack.join('submodule.py').write('VERSION = (2016, 11, 26)') 263 264 config.write( 265 '[metadata]\n' 266 'version = attr: fake_package.subpackage.submodule.VERSION\n' 267 ) 268 with get_dist(tmpdir) as dist: 269 assert dist.metadata.version == '2016.11.26' 270 271 def test_unknown_meta_item(self, tmpdir): 272 273 fake_env( 274 tmpdir, 275 '[metadata]\n' 276 'name = fake_name\n' 277 'unknown = some\n' 278 ) 279 with get_dist(tmpdir, parse=False) as dist: 280 dist.parse_config_files() # Skip unknown. 281 282 def test_usupported_section(self, tmpdir): 283 284 fake_env( 285 tmpdir, 286 '[metadata.some]\n' 287 'key = val\n' 288 ) 289 with get_dist(tmpdir, parse=False) as dist: 290 with pytest.raises(DistutilsOptionError): 291 dist.parse_config_files() 292 293 def test_classifiers(self, tmpdir): 294 expected = set([ 295 'Framework :: Django', 296 'Programming Language :: Python :: 3', 297 'Programming Language :: Python :: 3.5', 298 ]) 299 300 # From file. 301 _, config = fake_env( 302 tmpdir, 303 '[metadata]\n' 304 'classifiers = file: classifiers\n' 305 ) 306 307 tmpdir.join('classifiers').write( 308 'Framework :: Django\n' 309 'Programming Language :: Python :: 3\n' 310 'Programming Language :: Python :: 3.5\n' 311 ) 312 313 with get_dist(tmpdir) as dist: 314 assert set(dist.metadata.classifiers) == expected 315 316 # From list notation 317 config.write( 318 '[metadata]\n' 319 'classifiers =\n' 320 ' Framework :: Django\n' 321 ' Programming Language :: Python :: 3\n' 322 ' Programming Language :: Python :: 3.5\n' 323 ) 324 with get_dist(tmpdir) as dist: 325 assert set(dist.metadata.classifiers) == expected 326 327 328class TestOptions: 329 330 def test_basic(self, tmpdir): 331 332 fake_env( 333 tmpdir, 334 '[options]\n' 335 'zip_safe = True\n' 336 'use_2to3 = 1\n' 337 'include_package_data = yes\n' 338 'package_dir = b=c, =src\n' 339 'packages = pack_a, pack_b.subpack\n' 340 'namespace_packages = pack1, pack2\n' 341 'use_2to3_fixers = your.fixers, or.here\n' 342 'use_2to3_exclude_fixers = one.here, two.there\n' 343 'convert_2to3_doctests = src/tests/one.txt, src/two.txt\n' 344 'scripts = bin/one.py, bin/two.py\n' 345 'eager_resources = bin/one.py, bin/two.py\n' 346 'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n' 347 'tests_require = mock==0.7.2; pytest\n' 348 'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n' 349 'dependency_links = http://some.com/here/1, ' 350 'http://some.com/there/2\n' 351 'python_requires = >=1.0, !=2.8\n' 352 'py_modules = module1, module2\n' 353 ) 354 with get_dist(tmpdir) as dist: 355 assert dist.zip_safe 356 assert dist.use_2to3 357 assert dist.include_package_data 358 assert dist.package_dir == {'': 'src', 'b': 'c'} 359 assert dist.packages == ['pack_a', 'pack_b.subpack'] 360 assert dist.namespace_packages == ['pack1', 'pack2'] 361 assert dist.use_2to3_fixers == ['your.fixers', 'or.here'] 362 assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there'] 363 assert dist.convert_2to3_doctests == ([ 364 'src/tests/one.txt', 'src/two.txt']) 365 assert dist.scripts == ['bin/one.py', 'bin/two.py'] 366 assert dist.dependency_links == ([ 367 'http://some.com/here/1', 368 'http://some.com/there/2' 369 ]) 370 assert dist.install_requires == ([ 371 'docutils>=0.3', 372 'pack==1.1,==1.3', 373 'hey' 374 ]) 375 assert dist.setup_requires == ([ 376 'docutils>=0.3', 377 'spack ==1.1, ==1.3', 378 'there' 379 ]) 380 assert dist.tests_require == ['mock==0.7.2', 'pytest'] 381 assert dist.python_requires == '>=1.0, !=2.8' 382 assert dist.py_modules == ['module1', 'module2'] 383 384 def test_multiline(self, tmpdir): 385 fake_env( 386 tmpdir, 387 '[options]\n' 388 'package_dir = \n' 389 ' b=c\n' 390 ' =src\n' 391 'packages = \n' 392 ' pack_a\n' 393 ' pack_b.subpack\n' 394 'namespace_packages = \n' 395 ' pack1\n' 396 ' pack2\n' 397 'use_2to3_fixers = \n' 398 ' your.fixers\n' 399 ' or.here\n' 400 'use_2to3_exclude_fixers = \n' 401 ' one.here\n' 402 ' two.there\n' 403 'convert_2to3_doctests = \n' 404 ' src/tests/one.txt\n' 405 ' src/two.txt\n' 406 'scripts = \n' 407 ' bin/one.py\n' 408 ' bin/two.py\n' 409 'eager_resources = \n' 410 ' bin/one.py\n' 411 ' bin/two.py\n' 412 'install_requires = \n' 413 ' docutils>=0.3\n' 414 ' pack ==1.1, ==1.3\n' 415 ' hey\n' 416 'tests_require = \n' 417 ' mock==0.7.2\n' 418 ' pytest\n' 419 'setup_requires = \n' 420 ' docutils>=0.3\n' 421 ' spack ==1.1, ==1.3\n' 422 ' there\n' 423 'dependency_links = \n' 424 ' http://some.com/here/1\n' 425 ' http://some.com/there/2\n' 426 ) 427 with get_dist(tmpdir) as dist: 428 assert dist.package_dir == {'': 'src', 'b': 'c'} 429 assert dist.packages == ['pack_a', 'pack_b.subpack'] 430 assert dist.namespace_packages == ['pack1', 'pack2'] 431 assert dist.use_2to3_fixers == ['your.fixers', 'or.here'] 432 assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there'] 433 assert dist.convert_2to3_doctests == ( 434 ['src/tests/one.txt', 'src/two.txt']) 435 assert dist.scripts == ['bin/one.py', 'bin/two.py'] 436 assert dist.dependency_links == ([ 437 'http://some.com/here/1', 438 'http://some.com/there/2' 439 ]) 440 assert dist.install_requires == ([ 441 'docutils>=0.3', 442 'pack==1.1,==1.3', 443 'hey' 444 ]) 445 assert dist.setup_requires == ([ 446 'docutils>=0.3', 447 'spack ==1.1, ==1.3', 448 'there' 449 ]) 450 assert dist.tests_require == ['mock==0.7.2', 'pytest'] 451 452 def test_package_dir_fail(self, tmpdir): 453 fake_env( 454 tmpdir, 455 '[options]\n' 456 'package_dir = a b\n' 457 ) 458 with get_dist(tmpdir, parse=False) as dist: 459 with pytest.raises(DistutilsOptionError): 460 dist.parse_config_files() 461 462 def test_package_data(self, tmpdir): 463 fake_env( 464 tmpdir, 465 '[options.package_data]\n' 466 '* = *.txt, *.rst\n' 467 'hello = *.msg\n' 468 '\n' 469 '[options.exclude_package_data]\n' 470 '* = fake1.txt, fake2.txt\n' 471 'hello = *.dat\n' 472 ) 473 474 with get_dist(tmpdir) as dist: 475 assert dist.package_data == { 476 '': ['*.txt', '*.rst'], 477 'hello': ['*.msg'], 478 } 479 assert dist.exclude_package_data == { 480 '': ['fake1.txt', 'fake2.txt'], 481 'hello': ['*.dat'], 482 } 483 484 def test_packages(self, tmpdir): 485 fake_env( 486 tmpdir, 487 '[options]\n' 488 'packages = find:\n' 489 ) 490 491 with get_dist(tmpdir) as dist: 492 assert dist.packages == ['fake_package'] 493 494 def test_find_directive(self, tmpdir): 495 dir_package, config = fake_env( 496 tmpdir, 497 '[options]\n' 498 'packages = find:\n' 499 ) 500 501 dir_sub_one, _ = make_package_dir('sub_one', dir_package) 502 dir_sub_two, _ = make_package_dir('sub_two', dir_package) 503 504 with get_dist(tmpdir) as dist: 505 assert set(dist.packages) == set([ 506 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one' 507 ]) 508 509 config.write( 510 '[options]\n' 511 'packages = find:\n' 512 '\n' 513 '[options.packages.find]\n' 514 'where = .\n' 515 'include =\n' 516 ' fake_package.sub_one\n' 517 ' two\n' 518 ) 519 with get_dist(tmpdir) as dist: 520 assert dist.packages == ['fake_package.sub_one'] 521 522 config.write( 523 '[options]\n' 524 'packages = find:\n' 525 '\n' 526 '[options.packages.find]\n' 527 'exclude =\n' 528 ' fake_package.sub_one\n' 529 ) 530 with get_dist(tmpdir) as dist: 531 assert set(dist.packages) == set( 532 ['fake_package', 'fake_package.sub_two']) 533 534 def test_extras_require(self, tmpdir): 535 fake_env( 536 tmpdir, 537 '[options.extras_require]\n' 538 'pdf = ReportLab>=1.2; RXP\n' 539 'rest = \n' 540 ' docutils>=0.3\n' 541 ' pack ==1.1, ==1.3\n' 542 ) 543 544 with get_dist(tmpdir) as dist: 545 assert dist.extras_require == { 546 'pdf': ['ReportLab>=1.2', 'RXP'], 547 'rest': ['docutils>=0.3', 'pack==1.1,==1.3'] 548 } 549 assert dist.metadata.provides_extras == set(['pdf', 'rest']) 550 551 def test_entry_points(self, tmpdir): 552 _, config = fake_env( 553 tmpdir, 554 '[options.entry_points]\n' 555 'group1 = point1 = pack.module:func, ' 556 '.point2 = pack.module2:func_rest [rest]\n' 557 'group2 = point3 = pack.module:func2\n' 558 ) 559 560 with get_dist(tmpdir) as dist: 561 assert dist.entry_points == { 562 'group1': [ 563 'point1 = pack.module:func', 564 '.point2 = pack.module2:func_rest [rest]', 565 ], 566 'group2': ['point3 = pack.module:func2'] 567 } 568 569 expected = ( 570 '[blogtool.parsers]\n' 571 '.rst = some.nested.module:SomeClass.some_classmethod[reST]\n' 572 ) 573 574 tmpdir.join('entry_points').write(expected) 575 576 # From file. 577 config.write( 578 '[options]\n' 579 'entry_points = file: entry_points\n' 580 ) 581 582 with get_dist(tmpdir) as dist: 583 assert dist.entry_points == expected 584