blob: fb6d524fdc5f1304166553a438c85c10452b526f [file] [log] [blame]
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001"""Tests for packaging.dist."""
2import os
3import io
4import sys
5import logging
6import textwrap
7import packaging.dist
8
9from packaging.dist import Distribution
10from packaging.command import set_command
11from packaging.command.cmd import Command
12from packaging.errors import PackagingModuleError, PackagingOptionError
13from packaging.tests import TESTFN, captured_stdout
14from packaging.tests import support, unittest
15from packaging.tests.support import create_distribution
Victor Stinner9bcfacd2011-05-24 14:01:39 +020016from test.support import unload
Tarek Ziade1231a4e2011-05-19 13:07:25 +020017
18
19class test_dist(Command):
20 """Sample packaging extension command."""
21
22 user_options = [
23 ("sample-option=", "S", "help text"),
24 ]
25
26 def initialize_options(self):
27 self.sample_option = None
28
29 def finalize_options(self):
30 pass
31
32
33class DistributionTestCase(support.TempdirManager,
34 support.LoggingCatcher,
35 support.EnvironRestorer,
36 unittest.TestCase):
37
38 restore_environ = ['HOME']
39
40 def setUp(self):
41 super(DistributionTestCase, self).setUp()
42 self.argv = sys.argv, sys.argv[:]
43 del sys.argv[1:]
44
45 def tearDown(self):
46 sys.argv = self.argv[0]
47 sys.argv[:] = self.argv[1]
48 super(DistributionTestCase, self).tearDown()
49
50 def test_debug_mode(self):
51 self.addCleanup(os.unlink, TESTFN)
52 with open(TESTFN, "w") as f:
53 f.write("[global]\n")
54 f.write("command_packages = foo.bar, splat")
55
56 files = [TESTFN]
57 sys.argv.append("build")
58 __, stdout = captured_stdout(create_distribution, files)
59 self.assertEqual(stdout, '')
60 packaging.dist.DEBUG = True
61 try:
62 __, stdout = captured_stdout(create_distribution, files)
63 self.assertEqual(stdout, '')
64 finally:
65 packaging.dist.DEBUG = False
66
67 def test_write_pkg_file(self):
68 # Check Metadata handling of Unicode fields
69 tmp_dir = self.mkdtemp()
70 my_file = os.path.join(tmp_dir, 'f')
71 cls = Distribution
72
73 dist = cls(attrs={'author': 'Mister Café',
74 'name': 'my.package',
75 'maintainer': 'Café Junior',
76 'summary': 'Café torréfié',
77 'description': 'Héhéhé'})
78
79 # let's make sure the file can be written
80 # with Unicode fields. they are encoded with
81 # PKG_INFO_ENCODING
Victor Stinnerc3364522011-05-19 18:49:56 +020082 with open(my_file, 'w', encoding='utf-8') as fp:
Tarek Ziade1231a4e2011-05-19 13:07:25 +020083 dist.metadata.write_file(fp)
84
85 # regular ascii is of course always usable
86 dist = cls(attrs={'author': 'Mister Cafe',
87 'name': 'my.package',
88 'maintainer': 'Cafe Junior',
89 'summary': 'Cafe torrefie',
90 'description': 'Hehehe'})
91
92 with open(my_file, 'w') as fp:
93 dist.metadata.write_file(fp)
94
95 def test_bad_attr(self):
96 Distribution(attrs={'author': 'xxx',
97 'name': 'xxx',
98 'version': '1.2',
99 'url': 'xxxx',
100 'badoptname': 'xxx'})
101 logs = self.get_logs(logging.WARNING)
102 self.assertEqual(1, len(logs))
103 self.assertIn('unknown argument', logs[0])
104
105 def test_bad_version(self):
106 Distribution(attrs={'author': 'xxx',
107 'name': 'xxx',
108 'version': 'xxx',
109 'url': 'xxxx'})
110 logs = self.get_logs(logging.WARNING)
111 self.assertEqual(1, len(logs))
112 self.assertIn('not a valid version', logs[0])
113
114 def test_empty_options(self):
115 # an empty options dictionary should not stay in the
116 # list of attributes
117 Distribution(attrs={'author': 'xxx',
118 'name': 'xxx',
119 'version': '1.2',
120 'url': 'xxxx',
121 'options': {}})
122
123 self.assertEqual([], self.get_logs(logging.WARNING))
124
125 def test_non_empty_options(self):
126 # TODO: how to actually use options is not documented except
127 # for a few cryptic comments in dist.py. If this is to stay
128 # in the public API, it deserves some better documentation.
129
130 # Here is an example of how it's used out there:
131 # http://svn.pythonmac.org/py2app/py2app/trunk/doc/
132 # index.html#specifying-customizations
133 dist = Distribution(attrs={'author': 'xxx',
134 'name': 'xxx',
135 'version': 'xxx',
136 'url': 'xxxx',
137 'options': {'sdist': {'owner': 'root'}}})
138
139 self.assertIn('owner', dist.get_option_dict('sdist'))
140
141 def test_finalize_options(self):
142
143 attrs = {'keywords': 'one,two',
144 'platform': 'one,two'}
145
146 dist = Distribution(attrs=attrs)
147 dist.finalize_options()
148
149 # finalize_option splits platforms and keywords
150 self.assertEqual(dist.metadata['platform'], ['one', 'two'])
151 self.assertEqual(dist.metadata['keywords'], ['one', 'two'])
152
153 def test_find_config_files_disable(self):
154 # Bug #1180: Allow users to disable their own config file.
155 temp_home = self.mkdtemp()
156 if os.name == 'posix':
157 user_filename = os.path.join(temp_home, ".pydistutils.cfg")
158 else:
159 user_filename = os.path.join(temp_home, "pydistutils.cfg")
160
161 with open(user_filename, 'w') as f:
162 f.write('[distutils2]\n')
163
164 def _expander(path):
165 return temp_home
166
167 old_expander = os.path.expanduser
168 os.path.expanduser = _expander
169 try:
170 d = packaging.dist.Distribution()
171 all_files = d.find_config_files()
172
173 d = packaging.dist.Distribution(attrs={'script_args':
174 ['--no-user-cfg']})
175 files = d.find_config_files()
176 finally:
177 os.path.expanduser = old_expander
178
179 # make sure --no-user-cfg disables the user cfg file
180 self.assertEqual((len(all_files) - 1), len(files))
181
182 def test_special_hooks_parsing(self):
183 temp_home = self.mkdtemp()
184 config_files = [os.path.join(temp_home, "config1.cfg"),
185 os.path.join(temp_home, "config2.cfg")]
186
187 # Store two aliased hooks in config files
188 self.write_file((temp_home, "config1.cfg"),
189 '[test_dist]\npre-hook.a = type')
190 self.write_file((temp_home, "config2.cfg"),
191 '[test_dist]\npre-hook.b = type')
192
193 set_command('packaging.tests.test_dist.test_dist')
194 dist = create_distribution(config_files)
195 cmd = dist.get_command_obj("test_dist")
196 self.assertEqual(cmd.pre_hook, {"a": 'type', "b": 'type'})
197
198 def test_hooks_get_run(self):
199 temp_home = self.mkdtemp()
Tarek Ziadecd0f7bf2011-05-19 14:46:10 +0200200 module_name = os.path.split(temp_home)[-1]
201 pyname = '%s.py' % module_name
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200202 config_file = os.path.join(temp_home, "config1.cfg")
Tarek Ziadecd0f7bf2011-05-19 14:46:10 +0200203 hooks_module = os.path.join(temp_home, pyname)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200204
205 self.write_file(config_file, textwrap.dedent('''
206 [test_dist]
Tarek Ziadecd0f7bf2011-05-19 14:46:10 +0200207 pre-hook.test = %(modname)s.log_pre_call
208 post-hook.test = %(modname)s.log_post_call'''
209 % {'modname': module_name}))
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200210
211 self.write_file(hooks_module, textwrap.dedent('''
212 record = []
213
214 def log_pre_call(cmd):
215 record.append('pre-%s' % cmd.get_command_name())
216
217 def log_post_call(cmd):
218 record.append('post-%s' % cmd.get_command_name())
219 '''))
220
221 set_command('packaging.tests.test_dist.test_dist')
222 d = create_distribution([config_file])
223 cmd = d.get_command_obj("test_dist")
224
225 # prepare the call recorders
226 sys.path.append(temp_home)
227 self.addCleanup(sys.path.remove, temp_home)
Victor Stinner9bcfacd2011-05-24 14:01:39 +0200228 self.addCleanup(unload, module_name)
Tarek Ziadecd0f7bf2011-05-19 14:46:10 +0200229 record = __import__(module_name).record
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200230
Tarek Ziadecd0f7bf2011-05-19 14:46:10 +0200231 old_run = cmd.run
232 old_finalize = cmd.finalize_options
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200233 cmd.run = lambda: record.append('run')
234 cmd.finalize_options = lambda: record.append('finalize')
Tarek Ziadecd0f7bf2011-05-19 14:46:10 +0200235 try:
236 d.run_command('test_dist')
237 finally:
238 cmd.run = old_run
239 cmd.finalize_options = old_finalize
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200240
241 self.assertEqual(record, ['finalize',
242 'pre-test_dist',
243 'run',
244 'post-test_dist'])
245
246 def test_hooks_importable(self):
247 temp_home = self.mkdtemp()
248 config_file = os.path.join(temp_home, "config1.cfg")
249
250 self.write_file(config_file, textwrap.dedent('''
251 [test_dist]
252 pre-hook.test = nonexistent.dotted.name'''))
253
254 set_command('packaging.tests.test_dist.test_dist')
255 d = create_distribution([config_file])
256 cmd = d.get_command_obj("test_dist")
257 cmd.ensure_finalized()
258
259 self.assertRaises(PackagingModuleError, d.run_command, 'test_dist')
260
261 def test_hooks_callable(self):
262 temp_home = self.mkdtemp()
263 config_file = os.path.join(temp_home, "config1.cfg")
264
265 self.write_file(config_file, textwrap.dedent('''
266 [test_dist]
267 pre-hook.test = packaging.tests.test_dist.__doc__'''))
268
269 set_command('packaging.tests.test_dist.test_dist')
270 d = create_distribution([config_file])
271 cmd = d.get_command_obj("test_dist")
272 cmd.ensure_finalized()
273
274 self.assertRaises(PackagingOptionError, d.run_command, 'test_dist')
275
276
277class MetadataTestCase(support.TempdirManager,
278 support.LoggingCatcher,
279 unittest.TestCase):
280
281 def setUp(self):
282 super(MetadataTestCase, self).setUp()
283 self.argv = sys.argv, sys.argv[:]
284
285 def tearDown(self):
286 sys.argv = self.argv[0]
287 sys.argv[:] = self.argv[1]
288 super(MetadataTestCase, self).tearDown()
289
290 def test_simple_metadata(self):
291 attrs = {"name": "package",
292 "version": "1.0"}
293 dist = Distribution(attrs)
294 meta = self.format_metadata(dist)
295 self.assertIn("Metadata-Version: 1.0", meta)
296 self.assertNotIn("provides:", meta.lower())
297 self.assertNotIn("requires:", meta.lower())
298 self.assertNotIn("obsoletes:", meta.lower())
299
300 def test_provides_dist(self):
301 attrs = {"name": "package",
302 "version": "1.0",
303 "provides_dist": ["package", "package.sub"]}
304 dist = Distribution(attrs)
305 self.assertEqual(dist.metadata['Provides-Dist'],
306 ["package", "package.sub"])
307 meta = self.format_metadata(dist)
308 self.assertIn("Metadata-Version: 1.2", meta)
309 self.assertNotIn("requires:", meta.lower())
310 self.assertNotIn("obsoletes:", meta.lower())
311
312 def _test_provides_illegal(self):
313 # XXX to do: check the versions
314 self.assertRaises(ValueError, Distribution,
315 {"name": "package",
316 "version": "1.0",
317 "provides_dist": ["my.pkg (splat)"]})
318
319 def test_requires_dist(self):
320 attrs = {"name": "package",
321 "version": "1.0",
322 "requires_dist": ["other", "another (==1.0)"]}
323 dist = Distribution(attrs)
324 self.assertEqual(dist.metadata['Requires-Dist'],
325 ["other", "another (==1.0)"])
326 meta = self.format_metadata(dist)
327 self.assertIn("Metadata-Version: 1.2", meta)
328 self.assertNotIn("provides:", meta.lower())
329 self.assertIn("Requires-Dist: other", meta)
330 self.assertIn("Requires-Dist: another (==1.0)", meta)
331 self.assertNotIn("obsoletes:", meta.lower())
332
333 def _test_requires_illegal(self):
334 # XXX
335 self.assertRaises(ValueError, Distribution,
336 {"name": "package",
337 "version": "1.0",
338 "requires": ["my.pkg (splat)"]})
339
340 def test_obsoletes_dist(self):
341 attrs = {"name": "package",
342 "version": "1.0",
343 "obsoletes_dist": ["other", "another (<1.0)"]}
344 dist = Distribution(attrs)
345 self.assertEqual(dist.metadata['Obsoletes-Dist'],
346 ["other", "another (<1.0)"])
347 meta = self.format_metadata(dist)
348 self.assertIn("Metadata-Version: 1.2", meta)
349 self.assertNotIn("provides:", meta.lower())
350 self.assertNotIn("requires:", meta.lower())
351 self.assertIn("Obsoletes-Dist: other", meta)
352 self.assertIn("Obsoletes-Dist: another (<1.0)", meta)
353
354 def _test_obsoletes_illegal(self):
355 # XXX
356 self.assertRaises(ValueError, Distribution,
357 {"name": "package",
358 "version": "1.0",
359 "obsoletes": ["my.pkg (splat)"]})
360
361 def format_metadata(self, dist):
362 sio = io.StringIO()
363 dist.metadata.write_file(sio)
364 return sio.getvalue()
365
366 def test_custom_pydistutils(self):
367 # fixes #2166
368 # make sure pydistutils.cfg is found
369 if os.name == 'posix':
370 user_filename = ".pydistutils.cfg"
371 else:
372 user_filename = "pydistutils.cfg"
373
374 temp_dir = self.mkdtemp()
375 user_filename = os.path.join(temp_dir, user_filename)
376 with open(user_filename, 'w') as f:
377 f.write('.')
378
379 dist = Distribution()
380
381 # linux-style
382 if sys.platform in ('linux', 'darwin'):
383 os.environ['HOME'] = temp_dir
384 files = dist.find_config_files()
385 self.assertIn(user_filename, files)
386
387 # win32-style
388 if sys.platform == 'win32':
389 # home drive should be found
390 os.environ['HOME'] = temp_dir
391 files = dist.find_config_files()
392 self.assertIn(user_filename, files)
393
394 def test_show_help(self):
395 # smoke test, just makes sure some help is displayed
396 dist = Distribution()
397 sys.argv = []
398 dist.help = True
399 dist.script_name = 'setup.py'
400 __, stdout = captured_stdout(dist.parse_command_line)
401 output = [line for line in stdout.split('\n')
402 if line.strip() != '']
403 self.assertGreater(len(output), 0)
404
405 def test_description(self):
406 desc = textwrap.dedent("""\
407 example::
408 We start here
409 and continue here
410 and end here.""")
411 attrs = {"name": "package",
412 "version": "1.0",
413 "description": desc}
414
415 dist = packaging.dist.Distribution(attrs)
416 meta = self.format_metadata(dist)
417 meta = meta.replace('\n' + 7 * ' ' + '|', '\n')
418 self.assertIn(desc, meta)
419
420 def test_read_metadata(self):
421 attrs = {"name": "package",
422 "version": "1.0",
423 "description": "desc",
424 "summary": "xxx",
425 "download_url": "http://example.com",
426 "keywords": ['one', 'two'],
427 "requires_dist": ['foo']}
428
429 dist = Distribution(attrs)
430 metadata = dist.metadata
431
432 # write it then reloads it
433 PKG_INFO = io.StringIO()
434 metadata.write_file(PKG_INFO)
435 PKG_INFO.seek(0)
436
437 metadata.read_file(PKG_INFO)
438 self.assertEqual(metadata['name'], "package")
439 self.assertEqual(metadata['version'], "1.0")
440 self.assertEqual(metadata['summary'], "xxx")
441 self.assertEqual(metadata['download_url'], 'http://example.com')
442 self.assertEqual(metadata['keywords'], ['one', 'two'])
443 self.assertEqual(metadata['platform'], [])
444 self.assertEqual(metadata['obsoletes'], [])
445 self.assertEqual(metadata['requires-dist'], ['foo'])
446
447
448def test_suite():
449 suite = unittest.TestSuite()
450 suite.addTest(unittest.makeSuite(DistributionTestCase))
451 suite.addTest(unittest.makeSuite(MetadataTestCase))
452 return suite
453
454if __name__ == "__main__":
455 unittest.main(defaultTest="test_suite")