"""Tests for packaging.config.""" import os import sys from packaging import command from packaging.dist import Distribution from packaging.errors import PackagingFileError, PackagingOptionError from packaging.compiler import new_compiler, _COMPILERS from packaging.command.sdist import sdist from packaging.tests import unittest, support from packaging.tests.support import requires_zlib SETUP_CFG = """ [metadata] name = RestingParrot version = 0.6.4 author = Carl Meyer author_email = carl@oddbird.net maintainer = Éric Araujo maintainer_email = merwok@netwok.org summary = A sample project demonstrating packaging description-file = %(description-file)s keywords = packaging, sample project classifier = Development Status :: 4 - Beta Environment :: Console (Text Based) Environment :: X11 Applications :: GTK; python_version < '3' License :: OSI Approved :: MIT License Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 3 requires_python = >=2.4, <3.2 requires_dist = PetShoppe MichaelPalin (> 1.1) pywin32; sys.platform == 'win32' pysqlite2; python_version < '2.5' inotify (0.0.1); sys.platform == 'linux2' requires_external = libxml2 provides_dist = packaging-sample-project (0.2) unittest2-sample-project project_url = Main repository, http://bitbucket.org/carljm/sample-distutils2-project Fork in progress, http://bitbucket.org/Merwok/sample-distutils2-project [files] packages_root = src packages = one two three modules = haven scripts = script1.py scripts/find-coconuts bin/taunt package_data = cheese = data/templates/* doc/* doc/images/*.png extra_files = %(extra-files)s # Replaces MANIFEST.in # FIXME no, it's extra_files # (but sdist_extra is a better name, should use it) sdist_extra = include THANKS HACKING recursive-include examples *.txt *.py prune examples/sample?/build resources= bm/ {b1,b2}.gif = {icon} Cf*/ *.CFG = {config}/baBar/ init_script = {script}/JunGle/ [global] commands = packaging.tests.test_config.FooBarBazTest compilers = packaging.tests.test_config.DCompiler setup_hooks = %(setup-hooks)s [install_dist] sub_commands = foo """ SETUP_CFG_PKGDATA_BUGGY_1 = """ [files] package_data = foo.* """ SETUP_CFG_PKGDATA_BUGGY_2 = """ [files] package_data = foo.* """ # Can not be merged with SETUP_CFG else install_dist # command will fail when trying to compile C sources # TODO use a DummyCommand to mock build_ext EXT_SETUP_CFG = """ [files] packages = one two parent.undeclared [extension:one.speed_coconuts] sources = c_src/speed_coconuts.c extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared define_macros = HAVE_CAIRO HAVE_GTK2 libraries = gecodeint gecodekernel -- sys.platform != 'win32' GecodeInt GecodeKernel -- sys.platform == 'win32' [extension: two.fast_taunt] sources = cxx_src/utils_taunt.cxx cxx_src/python_module.cxx include_dirs = /usr/include/gecode /usr/include/blitz extra_compile_args = -fPIC -O2 -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32' /DGECODE_VERSION=win32 -- sys.platform == 'win32' language = cxx # corner case: if the parent package of an extension is declared but # not its grandparent, it's legal [extension: parent.undeclared._speed] sources = parent/undeclared/_speed.c """ EXT_SETUP_CFG_BUGGY_1 = """ [extension: realname] name = crash_here """ EXT_SETUP_CFG_BUGGY_2 = """ [files] packages = ham [extension: spam.eggs] """ EXT_SETUP_CFG_BUGGY_3 = """ [files] packages = ok ok.works [extension: ok.works.breaks._ext] """ HOOKS_MODULE = """ import logging logger = logging.getLogger('packaging') def logging_hook(config): logger.warning('logging_hook called') """ class DCompiler: name = 'd' description = 'D Compiler' def __init__(self, *args): pass def version_hook(config): config['metadata']['version'] += '.dev1' def first_hook(config): config['files']['modules'] += '\n first' def third_hook(config): config['files']['modules'] += '\n third' class FooBarBazTest: def __init__(self, dist): self.distribution = dist self._record = [] @classmethod def get_command_name(cls): return 'foo' def run(self): self._record.append('foo has run') def nothing(self): pass def get_source_files(self): return [] ensure_finalized = finalize_options = initialize_options = nothing class ConfigTestCase(support.TempdirManager, support.EnvironRestorer, support.LoggingCatcher, unittest.TestCase): restore_environ = ['PLAT'] def setUp(self): super(ConfigTestCase, self).setUp() tempdir = self.mkdtemp() self.working_dir = os.getcwd() os.chdir(tempdir) self.tempdir = tempdir def write_setup(self, kwargs=None): opts = {'description-file': 'README', 'extra-files': '', 'setup-hooks': 'packaging.tests.test_config.version_hook'} if kwargs: opts.update(kwargs) self.write_file('setup.cfg', SETUP_CFG % opts, encoding='utf-8') def get_dist(self): dist = Distribution() dist.parse_config_files() return dist def test_config(self): self.write_setup() self.write_file('README', 'yeah') os.mkdir('bm') self.write_file(('bm', 'b1.gif'), '') self.write_file(('bm', 'b2.gif'), '') os.mkdir('Cfg') self.write_file(('Cfg', 'data.CFG'), '') self.write_file('init_script', '') # try to load the metadata now dist = self.get_dist() # check what was done self.assertEqual(dist.metadata['Author'], 'Carl Meyer') self.assertEqual(dist.metadata['Author-Email'], 'carl@oddbird.net') # the hook adds .dev1 self.assertEqual(dist.metadata['Version'], '0.6.4.dev1') wanted = [ 'Development Status :: 4 - Beta', 'Environment :: Console (Text Based)', "Environment :: X11 Applications :: GTK; python_version < '3'", 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3'] self.assertEqual(dist.metadata['Classifier'], wanted) wanted = ['packaging', 'sample project'] self.assertEqual(dist.metadata['Keywords'], wanted) self.assertEqual(dist.metadata['Requires-Python'], '>=2.4, <3.2') wanted = ['PetShoppe', 'MichaelPalin (> 1.1)', "pywin32; sys.platform == 'win32'", "pysqlite2; python_version < '2.5'", "inotify (0.0.1); sys.platform == 'linux2'"] self.assertEqual(dist.metadata['Requires-Dist'], wanted) urls = [('Main repository', 'http://bitbucket.org/carljm/sample-distutils2-project'), ('Fork in progress', 'http://bitbucket.org/Merwok/sample-distutils2-project')] self.assertEqual(dist.metadata['Project-Url'], urls) self.assertEqual(dist.packages, ['one', 'two', 'three']) self.assertEqual(dist.py_modules, ['haven']) self.assertEqual(dist.package_data, {'cheese': ['data/templates/*', 'doc/*', 'doc/images/*.png']}) self.assertEqual(dist.data_files, {'bm/b1.gif': '{icon}/b1.gif', 'bm/b2.gif': '{icon}/b2.gif', 'Cfg/data.CFG': '{config}/baBar/data.CFG', 'init_script': '{script}/JunGle/init_script'}) self.assertEqual(dist.package_dir, 'src') # Make sure we get the foo command loaded. We use a string comparison # instead of assertIsInstance because the class is not the same when # this test is run directly: foo is packaging.tests.test_config.Foo # because get_command_class uses the full name, but a bare "Foo" in # this file would be __main__.Foo when run as "python test_config.py". # The name FooBarBazTest should be unique enough to prevent # collisions. self.assertEqual(dist.get_command_obj('foo').__class__.__name__, 'FooBarBazTest') # did the README got loaded ? self.assertEqual(dist.metadata['description'], 'yeah') # do we have the D Compiler enabled ? self.assertIn('d', _COMPILERS) d = new_compiler(compiler='d') self.assertEqual(d.description, 'D Compiler') # check error reporting for invalid package_data value self.write_file('setup.cfg', SETUP_CFG_PKGDATA_BUGGY_1) self.assertRaises(PackagingOptionError, self.get_dist) self.write_file('setup.cfg', SETUP_CFG_PKGDATA_BUGGY_2) self.assertRaises(PackagingOptionError, self.get_dist) def test_multiple_description_file(self): self.write_setup({'description-file': 'README CHANGES'}) self.write_file('README', 'yeah') self.write_file('CHANGES', 'changelog2') dist = self.get_dist() self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES']) def test_multiline_description_file(self): self.write_setup({'description-file': 'README\n CHANGES'}) self.write_file('README', 'yeah') self.write_file('CHANGES', 'changelog') dist = self.get_dist() self.assertEqual(dist.metadata['description'], 'yeah\nchangelog') self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES']) def test_parse_extensions_in_config(self): self.write_file('setup.cfg', EXT_SETUP_CFG) dist = self.get_dist() ext_modules = dict((mod.name, mod) for mod in dist.ext_modules) self.assertEqual(len(ext_modules), 3) ext = ext_modules.get('one.speed_coconuts') self.assertEqual(ext.sources, ['c_src/speed_coconuts.c']) self.assertEqual(ext.define_macros, ['HAVE_CAIRO', 'HAVE_GTK2']) libs = ['gecodeint', 'gecodekernel'] if sys.platform == 'win32': libs = ['GecodeInt', 'GecodeKernel'] self.assertEqual(ext.libraries, libs) self.assertEqual(ext.extra_link_args, ['`gcc -print-file-name=libgcc.a`', '-shared']) ext = ext_modules.get('two.fast_taunt') self.assertEqual(ext.sources, ['cxx_src/utils_taunt.cxx', 'cxx_src/python_module.cxx']) self.assertEqual(ext.include_dirs, ['/usr/include/gecode', '/usr/include/blitz']) cargs = ['-fPIC', '-O2'] if sys.platform == 'win32': cargs.append("/DGECODE_VERSION=win32") else: cargs.append('-DGECODE_VERSION=$(./gecode_version)') self.assertEqual(ext.extra_compile_args, cargs) self.assertEqual(ext.language, 'cxx') self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_1) self.assertRaises(PackagingOptionError, self.get_dist) self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_2) self.assertRaises(PackagingOptionError, self.get_dist) self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_3) self.assertRaises(PackagingOptionError, self.get_dist) def test_project_setup_hook_works(self): # Bug #11637: ensure the project directory is on sys.path to allow # project-specific hooks self.write_setup({'setup-hooks': 'hooks.logging_hook'}) self.write_file('README', 'yeah') self.write_file('hooks.py', HOOKS_MODULE) self.get_dist() self.assertEqual(['logging_hook called'], self.get_logs()) self.assertIn('hooks', sys.modules) def test_missing_setup_hook_warns(self): self.write_setup({'setup-hooks': 'does._not.exist'}) self.write_file('README', 'yeah') self.get_dist() logs = self.get_logs() self.assertEqual(1, len(logs)) self.assertIn('cannot find setup hook', logs[0]) def test_multiple_setup_hooks(self): self.write_setup({ 'setup-hooks': '\n packaging.tests.test_config.first_hook' '\n packaging.tests.test_config.missing_hook' '\n packaging.tests.test_config.third_hook', }) self.write_file('README', 'yeah') dist = self.get_dist() self.assertEqual(['haven', 'first', 'third'], dist.py_modules) logs = self.get_logs() self.assertEqual(1, len(logs)) self.assertIn('cannot find setup hook', logs[0]) def test_metadata_requires_description_files_missing(self): self.write_setup({'description-file': 'README README2'}) self.write_file('README', 'yeah') self.write_file('README2', 'yeah') os.mkdir('src') self.write_file(('src', 'haven.py'), '#') self.write_file('script1.py', '#') os.mkdir('scripts') self.write_file(('scripts', 'find-coconuts'), '#') os.mkdir('bin') self.write_file(('bin', 'taunt'), '#') for pkg in ('one', 'two', 'three'): pkg = os.path.join('src', pkg) os.mkdir(pkg) self.write_file((pkg, '__init__.py'), '#') dist = self.get_dist() cmd = sdist(dist) cmd.finalize_options() cmd.get_file_list() self.assertRaises(PackagingFileError, cmd.make_distribution) @requires_zlib def test_metadata_requires_description_files(self): # Create the following file structure: # README # README2 # script1.py # scripts/ # find-coconuts # bin/ # taunt # src/ # haven.py # one/__init__.py # two/__init__.py # three/__init__.py self.write_setup({'description-file': 'README\n README2', 'extra-files': '\n README3'}) self.write_file('README', 'yeah 1') self.write_file('README2', 'yeah 2') self.write_file('README3', 'yeah 3') os.mkdir('src') self.write_file(('src', 'haven.py'), '#') self.write_file('script1.py', '#') os.mkdir('scripts') self.write_file(('scripts', 'find-coconuts'), '#') os.mkdir('bin') self.write_file(('bin', 'taunt'), '#') for pkg in ('one', 'two', 'three'): pkg = os.path.join('src', pkg) os.mkdir(pkg) self.write_file((pkg, '__init__.py'), '#') dist = self.get_dist() self.assertIn('yeah 1\nyeah 2', dist.metadata['description']) cmd = sdist(dist) cmd.finalize_options() cmd.get_file_list() self.assertRaises(PackagingFileError, cmd.make_distribution) self.write_setup({'description-file': 'README\n README2', 'extra-files': '\n README2\n README'}) dist = self.get_dist() cmd = sdist(dist) cmd.finalize_options() cmd.get_file_list() cmd.make_distribution() with open('MANIFEST') as fp: self.assertIn('README\nREADME2\n', fp.read()) def test_sub_commands(self): self.write_setup() self.write_file('README', 'yeah') os.mkdir('src') self.write_file(('src', 'haven.py'), '#') self.write_file('script1.py', '#') os.mkdir('scripts') self.write_file(('scripts', 'find-coconuts'), '#') os.mkdir('bin') self.write_file(('bin', 'taunt'), '#') for pkg in ('one', 'two', 'three'): pkg = os.path.join('src', pkg) os.mkdir(pkg) self.write_file((pkg, '__init__.py'), '#') # try to run the install command to see if foo is called self.addCleanup(command._COMMANDS.__delitem__, 'foo') dist = self.get_dist() dist.run_command('install_dist') cmd = dist.get_command_obj('foo') self.assertEqual(cmd.__class__.__name__, 'FooBarBazTest') self.assertEqual(cmd._record, ['foo has run']) def test_suite(): return unittest.makeSuite(ConfigTestCase) if __name__ == '__main__': unittest.main(defaultTest='test_suite')