blob: 43ab2c85b4cc3e4b54fe4a7fd8646e1b8767449c [file] [log] [blame]
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001"""Tests for packaging.config."""
2import os
3import sys
4import logging
5from io import StringIO
6
7from packaging import command
8from packaging.dist import Distribution
9from packaging.errors import PackagingFileError
10from packaging.compiler import new_compiler, _COMPILERS
11from packaging.command.sdist import sdist
12
13from packaging.tests import unittest, support
Ezio Melotticad648c2011-05-19 21:25:10 +030014from packaging.tests.support import requires_zlib
Tarek Ziade1231a4e2011-05-19 13:07:25 +020015
16
17SETUP_CFG = """
18[metadata]
19name = RestingParrot
20version = 0.6.4
21author = Carl Meyer
22author_email = carl@oddbird.net
23maintainer = Éric Araujo
24maintainer_email = merwok@netwok.org
25summary = A sample project demonstrating packaging
26description-file = %(description-file)s
27keywords = packaging, sample project
28
29classifier =
30 Development Status :: 4 - Beta
31 Environment :: Console (Text Based)
32 Environment :: X11 Applications :: GTK; python_version < '3'
33 License :: OSI Approved :: MIT License
34 Programming Language :: Python
35 Programming Language :: Python :: 2
36 Programming Language :: Python :: 3
37
38requires_python = >=2.4, <3.2
39
40requires_dist =
41 PetShoppe
42 MichaelPalin (> 1.1)
43 pywin32; sys.platform == 'win32'
44 pysqlite2; python_version < '2.5'
45 inotify (0.0.1); sys.platform == 'linux2'
46
47requires_external = libxml2
48
49provides_dist = packaging-sample-project (0.2)
50 unittest2-sample-project
51
52project_url =
53 Main repository, http://bitbucket.org/carljm/sample-distutils2-project
54 Fork in progress, http://bitbucket.org/Merwok/sample-distutils2-project
55
56[files]
57packages_root = src
58
59packages = one
60 two
61 three
62
63modules = haven
64
65scripts =
66 script1.py
67 scripts/find-coconuts
68 bin/taunt
69
70package_data =
71 cheese = data/templates/*
72
73extra_files = %(extra-files)s
74
75# Replaces MANIFEST.in
76sdist_extra =
77 include THANKS HACKING
78 recursive-include examples *.txt *.py
79 prune examples/sample?/build
80
81resources=
82 bm/ {b1,b2}.gif = {icon}
83 Cf*/ *.CFG = {config}/baBar/
84 init_script = {script}/JunGle/
85
86[global]
87commands =
88 packaging.tests.test_config.FooBarBazTest
89
90compilers =
91 packaging.tests.test_config.DCompiler
92
Éric Araujo643cb732011-06-11 00:33:38 +020093setup_hooks = %(setup-hooks)s
Tarek Ziade1231a4e2011-05-19 13:07:25 +020094
95
96
97[install_dist]
98sub_commands = foo
99"""
100
101# Can not be merged with SETUP_CFG else install_dist
102# command will fail when trying to compile C sources
103EXT_SETUP_CFG = """
104[files]
105packages = one
106 two
107
108[extension=speed_coconuts]
109name = one.speed_coconuts
110sources = c_src/speed_coconuts.c
111extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared
112define_macros = HAVE_CAIRO HAVE_GTK2
113libraries = gecodeint gecodekernel -- sys.platform != 'win32'
114 GecodeInt GecodeKernel -- sys.platform == 'win32'
115
116[extension=fast_taunt]
Éric Araujo0a975f92011-06-09 07:47:25 +0200117name = two.fast_taunt
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200118sources = cxx_src/utils_taunt.cxx
119 cxx_src/python_module.cxx
120include_dirs = /usr/include/gecode
121 /usr/include/blitz
122extra_compile_args = -fPIC -O2
123 -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32'
124 /DGECODE_VERSION='win32' -- sys.platform == 'win32'
125language = cxx
126
127"""
128
Éric Araujo3e425ac2011-06-19 21:23:43 +0200129HOOKS_MODULE = """
130import logging
131
132logger = logging.getLogger('packaging')
133
134def logging_hook(config):
135 logger.warning('logging_hook called')
136"""
137
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200138
139class DCompiler:
140 name = 'd'
141 description = 'D Compiler'
142
143 def __init__(self, *args):
144 pass
145
146
Éric Araujo643cb732011-06-11 00:33:38 +0200147def version_hook(config):
148 config['metadata']['version'] += '.dev1'
149
150
151def first_hook(config):
152 config['files']['modules'] += '\n first'
153
154
155def third_hook(config):
156 config['files']['modules'] += '\n third'
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200157
158
159class FooBarBazTest:
160
161 def __init__(self, dist):
162 self.distribution = dist
163
164 @classmethod
165 def get_command_name(cls):
166 return 'foo'
167
168 def run(self):
169 self.distribution.foo_was_here = True
170
171 def nothing(self):
172 pass
173
174 def get_source_files(self):
175 return []
176
177 ensure_finalized = finalize_options = initialize_options = nothing
178
179
180class ConfigTestCase(support.TempdirManager,
181 support.EnvironRestorer,
182 support.LoggingCatcher,
183 unittest.TestCase):
184
185 restore_environ = ['PLAT']
186
187 def setUp(self):
188 super(ConfigTestCase, self).setUp()
189 self.addCleanup(setattr, sys, 'stdout', sys.stdout)
190 self.addCleanup(setattr, sys, 'stderr', sys.stderr)
191 sys.stdout = StringIO()
192 sys.stderr = StringIO()
193
194 self.addCleanup(os.chdir, os.getcwd())
195 tempdir = self.mkdtemp()
Tarek Ziadeeb64b612011-05-23 18:31:20 +0200196 self.working_dir = os.getcwd()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200197 os.chdir(tempdir)
198 self.tempdir = tempdir
199
Tarek Ziadeeb64b612011-05-23 18:31:20 +0200200 def tearDown(self):
201 os.chdir(self.working_dir)
202 super(ConfigTestCase, self).tearDown()
203
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200204 def write_setup(self, kwargs=None):
205 opts = {'description-file': 'README', 'extra-files': '',
Éric Araujo643cb732011-06-11 00:33:38 +0200206 'setup-hooks': 'packaging.tests.test_config.version_hook'}
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200207 if kwargs:
208 opts.update(kwargs)
Victor Stinnerdd13dd42011-05-19 18:45:32 +0200209 self.write_file('setup.cfg', SETUP_CFG % opts, encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200210
211 def get_dist(self):
212 dist = Distribution()
213 dist.parse_config_files()
214 return dist
215
216 def test_config(self):
217 self.write_setup()
218 self.write_file('README', 'yeah')
219 os.mkdir('bm')
220 self.write_file(('bm', 'b1.gif'), '')
221 self.write_file(('bm', 'b2.gif'), '')
222 os.mkdir('Cfg')
223 self.write_file(('Cfg', 'data.CFG'), '')
224 self.write_file('init_script', '')
225
226 # try to load the metadata now
227 dist = self.get_dist()
228
229 # check what was done
230 self.assertEqual(dist.metadata['Author'], 'Carl Meyer')
231 self.assertEqual(dist.metadata['Author-Email'], 'carl@oddbird.net')
232
233 # the hook adds .dev1
234 self.assertEqual(dist.metadata['Version'], '0.6.4.dev1')
235
236 wanted = [
237 'Development Status :: 4 - Beta',
238 'Environment :: Console (Text Based)',
239 "Environment :: X11 Applications :: GTK; python_version < '3'",
240 'License :: OSI Approved :: MIT License',
241 'Programming Language :: Python',
242 'Programming Language :: Python :: 2',
243 'Programming Language :: Python :: 3']
244 self.assertEqual(dist.metadata['Classifier'], wanted)
245
246 wanted = ['packaging', 'sample project']
247 self.assertEqual(dist.metadata['Keywords'], wanted)
248
249 self.assertEqual(dist.metadata['Requires-Python'], '>=2.4, <3.2')
250
251 wanted = ['PetShoppe',
252 'MichaelPalin (> 1.1)',
253 "pywin32; sys.platform == 'win32'",
254 "pysqlite2; python_version < '2.5'",
255 "inotify (0.0.1); sys.platform == 'linux2'"]
256
257 self.assertEqual(dist.metadata['Requires-Dist'], wanted)
258 urls = [('Main repository',
259 'http://bitbucket.org/carljm/sample-distutils2-project'),
260 ('Fork in progress',
261 'http://bitbucket.org/Merwok/sample-distutils2-project')]
262 self.assertEqual(dist.metadata['Project-Url'], urls)
263
264 self.assertEqual(dist.packages, ['one', 'two', 'three'])
265 self.assertEqual(dist.py_modules, ['haven'])
266 self.assertEqual(dist.package_data, {'cheese': 'data/templates/*'})
267 self.assertEqual(
268 {'bm/b1.gif': '{icon}/b1.gif',
269 'bm/b2.gif': '{icon}/b2.gif',
270 'Cfg/data.CFG': '{config}/baBar/data.CFG',
271 'init_script': '{script}/JunGle/init_script'},
272 dist.data_files)
273
274 self.assertEqual(dist.package_dir, 'src')
275
276 # Make sure we get the foo command loaded. We use a string comparison
277 # instead of assertIsInstance because the class is not the same when
278 # this test is run directly: foo is packaging.tests.test_config.Foo
279 # because get_command_class uses the full name, but a bare "Foo" in
280 # this file would be __main__.Foo when run as "python test_config.py".
281 # The name FooBarBazTest should be unique enough to prevent
282 # collisions.
283 self.assertEqual('FooBarBazTest',
284 dist.get_command_obj('foo').__class__.__name__)
285
286 # did the README got loaded ?
287 self.assertEqual(dist.metadata['description'], 'yeah')
288
289 # do we have the D Compiler enabled ?
290 self.assertIn('d', _COMPILERS)
291 d = new_compiler(compiler='d')
292 self.assertEqual(d.description, 'D Compiler')
293
294 def test_multiple_description_file(self):
295 self.write_setup({'description-file': 'README CHANGES'})
296 self.write_file('README', 'yeah')
297 self.write_file('CHANGES', 'changelog2')
298 dist = self.get_dist()
299 self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES'])
300
301 def test_multiline_description_file(self):
302 self.write_setup({'description-file': 'README\n CHANGES'})
303 self.write_file('README', 'yeah')
304 self.write_file('CHANGES', 'changelog')
305 dist = self.get_dist()
306 self.assertEqual(dist.metadata['description'], 'yeah\nchangelog')
307 self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES'])
308
309 def test_parse_extensions_in_config(self):
310 self.write_file('setup.cfg', EXT_SETUP_CFG)
311 dist = self.get_dist()
312
313 ext_modules = dict((mod.name, mod) for mod in dist.ext_modules)
314 self.assertEqual(len(ext_modules), 2)
315 ext = ext_modules.get('one.speed_coconuts')
316 self.assertEqual(ext.sources, ['c_src/speed_coconuts.c'])
317 self.assertEqual(ext.define_macros, ['HAVE_CAIRO', 'HAVE_GTK2'])
318 libs = ['gecodeint', 'gecodekernel']
319 if sys.platform == 'win32':
320 libs = ['GecodeInt', 'GecodeKernel']
321 self.assertEqual(ext.libraries, libs)
322 self.assertEqual(ext.extra_link_args,
323 ['`gcc -print-file-name=libgcc.a`', '-shared'])
324
Éric Araujo0a975f92011-06-09 07:47:25 +0200325 ext = ext_modules.get('two.fast_taunt')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200326 self.assertEqual(ext.sources,
327 ['cxx_src/utils_taunt.cxx', 'cxx_src/python_module.cxx'])
328 self.assertEqual(ext.include_dirs,
329 ['/usr/include/gecode', '/usr/include/blitz'])
330 cargs = ['-fPIC', '-O2']
331 if sys.platform == 'win32':
Tarek Ziade91f0e342011-05-21 12:00:10 +0200332 cargs.append("/DGECODE_VERSION=win32")
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200333 else:
334 cargs.append('-DGECODE_VERSION=$(./gecode_version)')
335 self.assertEqual(ext.extra_compile_args, cargs)
336 self.assertEqual(ext.language, 'cxx')
337
Éric Araujo3e425ac2011-06-19 21:23:43 +0200338 def test_project_setup_hook_works(self):
339 # Bug #11637: ensure the project directory is on sys.path to allow
340 # project-specific hooks
341 self.write_setup({'setup-hooks': 'hooks.logging_hook'})
342 self.write_file('README', 'yeah')
343 self.write_file('hooks.py', HOOKS_MODULE)
344 self.get_dist()
345 logs = self.get_logs(logging.WARNING)
346 self.assertEqual(['logging_hook called'], logs)
347 self.assertIn('hooks', sys.modules)
348
Éric Araujo643cb732011-06-11 00:33:38 +0200349 def test_missing_setup_hook_warns(self):
350 self.write_setup({'setup-hooks': 'this.does._not.exist'})
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200351 self.write_file('README', 'yeah')
Éric Araujo3e425ac2011-06-19 21:23:43 +0200352 self.get_dist()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200353 logs = self.get_logs(logging.WARNING)
354 self.assertEqual(1, len(logs))
Éric Araujo643cb732011-06-11 00:33:38 +0200355 self.assertIn('cannot find setup hook', logs[0])
356
357 def test_multiple_setup_hooks(self):
358 self.write_setup({
359 'setup-hooks': '\n packaging.tests.test_config.first_hook'
360 '\n packaging.tests.test_config.missing_hook'
361 '\n packaging.tests.test_config.third_hook'
362 })
363 self.write_file('README', 'yeah')
364 dist = self.get_dist()
365
366 self.assertEqual(['haven', 'first', 'third'], dist.py_modules)
367 logs = self.get_logs(logging.WARNING)
368 self.assertEqual(1, len(logs))
369 self.assertIn('cannot find setup hook', logs[0])
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200370
371 def test_metadata_requires_description_files_missing(self):
Éric Araujo8474f292011-06-11 00:21:18 +0200372 self.write_setup({'description-file': 'README README2'})
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200373 self.write_file('README', 'yeah')
374 self.write_file('README2', 'yeah')
375 os.mkdir('src')
376 self.write_file(('src', 'haven.py'), '#')
377 self.write_file('script1.py', '#')
378 os.mkdir('scripts')
379 self.write_file(('scripts', 'find-coconuts'), '#')
380 os.mkdir('bin')
381 self.write_file(('bin', 'taunt'), '#')
382
383 for pkg in ('one', 'two', 'three'):
384 pkg = os.path.join('src', pkg)
385 os.mkdir(pkg)
386 self.write_file((pkg, '__init__.py'), '#')
387
388 dist = self.get_dist()
389 cmd = sdist(dist)
390 cmd.finalize_options()
391 cmd.get_file_list()
392 self.assertRaises(PackagingFileError, cmd.make_distribution)
393
Ezio Melotticad648c2011-05-19 21:25:10 +0300394 @requires_zlib
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200395 def test_metadata_requires_description_files(self):
396 # Create the following file structure:
397 # README
398 # README2
399 # script1.py
400 # scripts/
401 # find-coconuts
402 # bin/
403 # taunt
404 # src/
405 # haven.py
406 # one/__init__.py
407 # two/__init__.py
408 # three/__init__.py
409
410 self.write_setup({'description-file': 'README\n README2',
411 'extra-files': '\n README3'})
412 self.write_file('README', 'yeah 1')
413 self.write_file('README2', 'yeah 2')
414 self.write_file('README3', 'yeah 3')
415 os.mkdir('src')
416 self.write_file(('src', 'haven.py'), '#')
417 self.write_file('script1.py', '#')
418 os.mkdir('scripts')
419 self.write_file(('scripts', 'find-coconuts'), '#')
420 os.mkdir('bin')
421 self.write_file(('bin', 'taunt'), '#')
422
423 for pkg in ('one', 'two', 'three'):
424 pkg = os.path.join('src', pkg)
425 os.mkdir(pkg)
426 self.write_file((pkg, '__init__.py'), '#')
427
428 dist = self.get_dist()
429 self.assertIn('yeah 1\nyeah 2', dist.metadata['description'])
430
431 cmd = sdist(dist)
432 cmd.finalize_options()
433 cmd.get_file_list()
434 self.assertRaises(PackagingFileError, cmd.make_distribution)
435
436 self.write_setup({'description-file': 'README\n README2',
437 'extra-files': '\n README2\n README'})
438 dist = self.get_dist()
439 cmd = sdist(dist)
440 cmd.finalize_options()
441 cmd.get_file_list()
442 cmd.make_distribution()
443 with open('MANIFEST') as fp:
444 self.assertIn('README\nREADME2\n', fp.read())
445
446 def test_sub_commands(self):
447 self.write_setup()
448 self.write_file('README', 'yeah')
449 os.mkdir('src')
450 self.write_file(('src', 'haven.py'), '#')
451 self.write_file('script1.py', '#')
452 os.mkdir('scripts')
453 self.write_file(('scripts', 'find-coconuts'), '#')
454 os.mkdir('bin')
455 self.write_file(('bin', 'taunt'), '#')
456
457 for pkg in ('one', 'two', 'three'):
458 pkg = os.path.join('src', pkg)
459 os.mkdir(pkg)
460 self.write_file((pkg, '__init__.py'), '#')
461
462 # try to run the install command to see if foo is called
463 dist = self.get_dist()
464 self.assertIn('foo', command.get_command_names())
465 self.assertEqual('FooBarBazTest',
466 dist.get_command_obj('foo').__class__.__name__)
467
468
469def test_suite():
470 return unittest.makeSuite(ConfigTestCase)
471
472if __name__ == '__main__':
473 unittest.main(defaultTest='test_suite')