»Core Development>Code coverage>Lib/packaging/tests/test_metadata.py

Python code coverage for Lib/packaging/tests/test_metadata.py

#countcontent
1n/a"""Tests for packaging.metadata."""
2n/aimport os
3n/aimport sys
4n/afrom textwrap import dedent
5n/afrom io import StringIO
6n/a
7n/afrom packaging.errors import (MetadataConflictError, MetadataMissingError,
8n/a MetadataUnrecognizedVersionError)
9n/afrom packaging.metadata import Metadata, PKG_INFO_PREFERRED_VERSION
10n/a
11n/afrom packaging.tests import unittest
12n/afrom packaging.tests.support import (LoggingCatcher, TempdirManager,
13n/a EnvironRestorer)
14n/a
15n/a
16n/aclass MetadataTestCase(LoggingCatcher,
17n/a TempdirManager,
18n/a EnvironRestorer,
19n/a unittest.TestCase):
20n/a
21n/a maxDiff = None
22n/a restore_environ = ['HOME']
23n/a
24n/a def setUp(self):
25n/a super(MetadataTestCase, self).setUp()
26n/a self.argv = sys.argv, sys.argv[:]
27n/a
28n/a def tearDown(self):
29n/a sys.argv = self.argv[0]
30n/a sys.argv[:] = self.argv[1]
31n/a super(MetadataTestCase, self).tearDown()
32n/a
33n/a #### Test various methods of the Metadata class
34n/a
35n/a def test_instantiation(self):
36n/a PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
37n/a with open(PKG_INFO, 'r', encoding='utf-8') as f:
38n/a contents = f.read()
39n/a fp = StringIO(contents)
40n/a
41n/a m = Metadata()
42n/a self.assertRaises(MetadataUnrecognizedVersionError, m.items)
43n/a
44n/a m = Metadata(PKG_INFO)
45n/a self.assertEqual(len(m.items()), 22)
46n/a
47n/a m = Metadata(fileobj=fp)
48n/a self.assertEqual(len(m.items()), 22)
49n/a
50n/a m = Metadata(mapping=dict(name='Test', version='1.0'))
51n/a self.assertEqual(len(m.items()), 11)
52n/a
53n/a d = dict(m.items())
54n/a self.assertRaises(TypeError, Metadata,
55n/a PKG_INFO, fileobj=fp)
56n/a self.assertRaises(TypeError, Metadata,
57n/a PKG_INFO, mapping=d)
58n/a self.assertRaises(TypeError, Metadata,
59n/a fileobj=fp, mapping=d)
60n/a self.assertRaises(TypeError, Metadata,
61n/a PKG_INFO, mapping=m, fileobj=fp)
62n/a
63n/a def test_metadata_markers(self):
64n/a # see if we can be platform-aware
65n/a PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
66n/a with open(PKG_INFO, 'r', encoding='utf-8') as f:
67n/a content = f.read() % sys.platform
68n/a metadata = Metadata(platform_dependent=True)
69n/a
70n/a metadata.read_file(StringIO(content))
71n/a self.assertEqual(metadata['Requires-Dist'], ['bar'])
72n/a metadata['Name'] = "baz; sys.platform == 'blah'"
73n/a # FIXME is None or 'UNKNOWN' correct here?
74n/a # where is that documented?
75n/a self.assertEqual(metadata['Name'], None)
76n/a
77n/a # test with context
78n/a context = {'sys.platform': 'okook'}
79n/a metadata = Metadata(platform_dependent=True, execution_context=context)
80n/a metadata.read_file(StringIO(content))
81n/a self.assertEqual(metadata['Requires-Dist'], ['foo'])
82n/a
83n/a def test_mapping_api(self):
84n/a PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
85n/a with open(PKG_INFO, 'r', encoding='utf-8') as f:
86n/a content = f.read() % sys.platform
87n/a metadata = Metadata(fileobj=StringIO(content))
88n/a self.assertIn('Version', metadata.keys())
89n/a self.assertIn('0.5', metadata.values())
90n/a self.assertIn(('Version', '0.5'), metadata.items())
91n/a
92n/a metadata.update({'version': '0.6'})
93n/a self.assertEqual(metadata['Version'], '0.6')
94n/a metadata.update([('version', '0.7')])
95n/a self.assertEqual(metadata['Version'], '0.7')
96n/a
97n/a # make sure update method checks values like the set method does
98n/a metadata.update({'version': '1--2'})
99n/a self.assertEqual(len(self.get_logs()), 1)
100n/a
101n/a # XXX caveat: the keys method and friends are not 3.x-style views
102n/a # should be changed or documented
103n/a self.assertEqual(list(metadata), metadata.keys())
104n/a
105n/a def test_read_metadata(self):
106n/a fields = {'name': 'project',
107n/a 'version': '1.0',
108n/a 'description': 'desc',
109n/a 'summary': 'xxx',
110n/a 'download_url': 'http://example.com',
111n/a 'keywords': ['one', 'two'],
112n/a 'requires_dist': ['foo']}
113n/a
114n/a metadata = Metadata(mapping=fields)
115n/a PKG_INFO = StringIO()
116n/a metadata.write_file(PKG_INFO)
117n/a PKG_INFO.seek(0)
118n/a
119n/a metadata = Metadata(fileobj=PKG_INFO)
120n/a
121n/a self.assertEqual(metadata['name'], 'project')
122n/a self.assertEqual(metadata['version'], '1.0')
123n/a self.assertEqual(metadata['summary'], 'xxx')
124n/a self.assertEqual(metadata['download_url'], 'http://example.com')
125n/a self.assertEqual(metadata['keywords'], ['one', 'two'])
126n/a self.assertEqual(metadata['platform'], [])
127n/a self.assertEqual(metadata['obsoletes'], [])
128n/a self.assertEqual(metadata['requires-dist'], ['foo'])
129n/a
130n/a def test_write_metadata(self):
131n/a # check support of non-ASCII values
132n/a tmp_dir = self.mkdtemp()
133n/a my_file = os.path.join(tmp_dir, 'f')
134n/a
135n/a metadata = Metadata(mapping={'author': 'Mister Café',
136n/a 'name': 'my.project',
137n/a 'author': 'Café Junior',
138n/a 'summary': 'Café torréfié',
139n/a 'description': 'Héhéhé',
140n/a 'keywords': ['café', 'coffee']})
141n/a metadata.write(my_file)
142n/a
143n/a # the file should use UTF-8
144n/a metadata2 = Metadata()
145n/a with open(my_file, encoding='utf-8') as fp:
146n/a metadata2.read_file(fp)
147n/a
148n/a # XXX when keywords are not defined, metadata will have
149n/a # 'Keywords': [] but metadata2 will have 'Keywords': ['']
150n/a # because of a value.split(',') in Metadata.get
151n/a self.assertEqual(metadata.items(), metadata2.items())
152n/a
153n/a # ASCII also works, it's a subset of UTF-8
154n/a metadata = Metadata(mapping={'author': 'Mister Cafe',
155n/a 'name': 'my.project',
156n/a 'author': 'Cafe Junior',
157n/a 'summary': 'Cafe torrefie',
158n/a 'description': 'Hehehe'})
159n/a metadata.write(my_file)
160n/a
161n/a metadata2 = Metadata()
162n/a with open(my_file, encoding='utf-8') as fp:
163n/a metadata2.read_file(fp)
164n/a
165n/a def test_metadata_read_write(self):
166n/a PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
167n/a metadata = Metadata(PKG_INFO)
168n/a out = StringIO()
169n/a metadata.write_file(out)
170n/a
171n/a out.seek(0)
172n/a res = Metadata()
173n/a res.read_file(out)
174n/a self.assertEqual(metadata.values(), res.values())
175n/a
176n/a #### Test checks
177n/a
178n/a def test_check_version(self):
179n/a metadata = Metadata()
180n/a metadata['Name'] = 'vimpdb'
181n/a metadata['Home-page'] = 'http://pypi.python.org'
182n/a metadata['Author'] = 'Monty Python'
183n/a metadata.docutils_support = False
184n/a missing, warnings = metadata.check()
185n/a self.assertEqual(missing, ['Version'])
186n/a
187n/a def test_check_version_strict(self):
188n/a metadata = Metadata()
189n/a metadata['Name'] = 'vimpdb'
190n/a metadata['Home-page'] = 'http://pypi.python.org'
191n/a metadata['Author'] = 'Monty Python'
192n/a metadata.docutils_support = False
193n/a self.assertRaises(MetadataMissingError, metadata.check, strict=True)
194n/a
195n/a def test_check_name(self):
196n/a metadata = Metadata()
197n/a metadata['Version'] = '1.0'
198n/a metadata['Home-page'] = 'http://pypi.python.org'
199n/a metadata['Author'] = 'Monty Python'
200n/a metadata.docutils_support = False
201n/a missing, warnings = metadata.check()
202n/a self.assertEqual(missing, ['Name'])
203n/a
204n/a def test_check_name_strict(self):
205n/a metadata = Metadata()
206n/a metadata['Version'] = '1.0'
207n/a metadata['Home-page'] = 'http://pypi.python.org'
208n/a metadata['Author'] = 'Monty Python'
209n/a metadata.docutils_support = False
210n/a self.assertRaises(MetadataMissingError, metadata.check, strict=True)
211n/a
212n/a def test_check_author(self):
213n/a metadata = Metadata()
214n/a metadata['Version'] = '1.0'
215n/a metadata['Name'] = 'vimpdb'
216n/a metadata['Home-page'] = 'http://pypi.python.org'
217n/a metadata.docutils_support = False
218n/a missing, warnings = metadata.check()
219n/a self.assertEqual(missing, ['Author'])
220n/a
221n/a def test_check_homepage(self):
222n/a metadata = Metadata()
223n/a metadata['Version'] = '1.0'
224n/a metadata['Name'] = 'vimpdb'
225n/a metadata['Author'] = 'Monty Python'
226n/a metadata.docutils_support = False
227n/a missing, warnings = metadata.check()
228n/a self.assertEqual(missing, ['Home-page'])
229n/a
230n/a def test_check_predicates(self):
231n/a metadata = Metadata()
232n/a metadata['Version'] = 'rr'
233n/a metadata['Name'] = 'vimpdb'
234n/a metadata['Home-page'] = 'http://pypi.python.org'
235n/a metadata['Author'] = 'Monty Python'
236n/a metadata['Requires-dist'] = ['Foo (a)']
237n/a metadata['Obsoletes-dist'] = ['Foo (a)']
238n/a metadata['Provides-dist'] = ['Foo (a)']
239n/a missing, warnings = metadata.check()
240n/a self.assertEqual(len(warnings), 4)
241n/a
242n/a #### Test fields and metadata versions
243n/a
244n/a def test_metadata_versions(self):
245n/a metadata = Metadata(mapping={'name': 'project', 'version': '1.0'})
246n/a self.assertEqual(metadata['Metadata-Version'],
247n/a PKG_INFO_PREFERRED_VERSION)
248n/a self.assertNotIn('Provides', metadata)
249n/a self.assertNotIn('Requires', metadata)
250n/a self.assertNotIn('Obsoletes', metadata)
251n/a
252n/a metadata['Classifier'] = ['ok']
253n/a self.assertEqual(metadata['Metadata-Version'], '1.1')
254n/a
255n/a metadata = Metadata()
256n/a metadata['Download-URL'] = 'ok'
257n/a self.assertEqual(metadata['Metadata-Version'], '1.1')
258n/a
259n/a metadata = Metadata()
260n/a metadata['Obsoletes'] = 'ok'
261n/a self.assertEqual(metadata['Metadata-Version'], '1.1')
262n/a
263n/a del metadata['Obsoletes']
264n/a metadata['Obsoletes-Dist'] = 'ok'
265n/a self.assertEqual(metadata['Metadata-Version'], '1.2')
266n/a
267n/a self.assertRaises(MetadataConflictError, metadata.set,
268n/a 'Obsoletes', 'ok')
269n/a
270n/a del metadata['Obsoletes']
271n/a del metadata['Obsoletes-Dist']
272n/a metadata['Version'] = '1'
273n/a self.assertEqual(metadata['Metadata-Version'], '1.0')
274n/a
275n/a # make sure the _best_version function works okay with
276n/a # non-conflicting fields from 1.1 and 1.2 (i.e. we want only the
277n/a # requires/requires-dist and co. pairs to cause a conflict, not all
278n/a # fields in _314_MARKERS)
279n/a metadata = Metadata()
280n/a metadata['Requires-Python'] = '3'
281n/a metadata['Classifier'] = ['Programming language :: Python :: 3']
282n/a self.assertEqual(metadata['Metadata-Version'], '1.2')
283n/a
284n/a PKG_INFO = os.path.join(os.path.dirname(__file__),
285n/a 'SETUPTOOLS-PKG-INFO')
286n/a metadata = Metadata(PKG_INFO)
287n/a self.assertEqual(metadata['Metadata-Version'], '1.0')
288n/a
289n/a PKG_INFO = os.path.join(os.path.dirname(__file__),
290n/a 'SETUPTOOLS-PKG-INFO2')
291n/a metadata = Metadata(PKG_INFO)
292n/a self.assertEqual(metadata['Metadata-Version'], '1.1')
293n/a
294n/a # Update the _fields dict directly to prevent 'Metadata-Version'
295n/a # from being updated by the _set_best_version() method.
296n/a metadata._fields['Metadata-Version'] = '1.618'
297n/a self.assertRaises(MetadataUnrecognizedVersionError, metadata.keys)
298n/a
299n/a def test_version(self):
300n/a Metadata(mapping={'author': 'xxx',
301n/a 'name': 'xxx',
302n/a 'version': 'xxx',
303n/a 'home_page': 'xxxx'})
304n/a logs = self.get_logs()
305n/a self.assertEqual(1, len(logs))
306n/a self.assertIn('not a valid version', logs[0])
307n/a
308n/a def test_description(self):
309n/a PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
310n/a with open(PKG_INFO, 'r', encoding='utf-8') as f:
311n/a content = f.read() % sys.platform
312n/a metadata = Metadata()
313n/a metadata.read_file(StringIO(content))
314n/a
315n/a # see if we can read the description now
316n/a DESC = os.path.join(os.path.dirname(__file__), 'LONG_DESC.txt')
317n/a with open(DESC) as f:
318n/a wanted = f.read()
319n/a self.assertEqual(wanted, metadata['Description'])
320n/a
321n/a # save the file somewhere and make sure we can read it back
322n/a out = StringIO()
323n/a metadata.write_file(out)
324n/a out.seek(0)
325n/a
326n/a out.seek(0)
327n/a metadata = Metadata()
328n/a metadata.read_file(out)
329n/a self.assertEqual(wanted, metadata['Description'])
330n/a
331n/a def test_description_folding(self):
332n/a # make sure the indentation is preserved
333n/a out = StringIO()
334n/a desc = dedent("""\
335n/a example::
336n/a We start here
337n/a and continue here
338n/a and end here.
339n/a """)
340n/a
341n/a metadata = Metadata()
342n/a metadata['description'] = desc
343n/a metadata.write_file(out)
344n/a
345n/a folded_desc = desc.replace('\n', '\n' + (7 * ' ') + '|')
346n/a self.assertIn(folded_desc, out.getvalue())
347n/a
348n/a def test_project_url(self):
349n/a metadata = Metadata()
350n/a metadata['Project-URL'] = [('one', 'http://ok')]
351n/a self.assertEqual(metadata['Project-URL'], [('one', 'http://ok')])
352n/a self.assertEqual(metadata['Metadata-Version'], '1.2')
353n/a
354n/a # make sure this particular field is handled properly when written
355n/a fp = StringIO()
356n/a metadata.write_file(fp)
357n/a self.assertIn('Project-URL: one,http://ok', fp.getvalue().split('\n'))
358n/a
359n/a fp.seek(0)
360n/a metadata = Metadata()
361n/a metadata.read_file(fp)
362n/a self.assertEqual(metadata['Project-Url'], [('one', 'http://ok')])
363n/a
364n/a # TODO copy tests for v1.1 requires, obsoletes and provides from distutils
365n/a # (they're useless but we support them so we should test them anyway)
366n/a
367n/a def test_provides_dist(self):
368n/a fields = {'name': 'project',
369n/a 'version': '1.0',
370n/a 'provides_dist': ['project', 'my.project']}
371n/a metadata = Metadata(mapping=fields)
372n/a self.assertEqual(metadata['Provides-Dist'],
373n/a ['project', 'my.project'])
374n/a self.assertEqual(metadata['Metadata-Version'], '1.2', metadata)
375n/a self.assertNotIn('Requires', metadata)
376n/a self.assertNotIn('Obsoletes', metadata)
377n/a
378n/a @unittest.skip('needs to be implemented')
379n/a def test_provides_illegal(self):
380n/a # TODO check the versions (like distutils does for old provides field)
381n/a self.assertRaises(ValueError, Metadata,
382n/a mapping={'name': 'project',
383n/a 'version': '1.0',
384n/a 'provides_dist': ['my.pkg (splat)']})
385n/a
386n/a def test_requires_dist(self):
387n/a fields = {'name': 'project',
388n/a 'version': '1.0',
389n/a 'requires_dist': ['other', 'another (==1.0)']}
390n/a metadata = Metadata(mapping=fields)
391n/a self.assertEqual(metadata['Requires-Dist'],
392n/a ['other', 'another (==1.0)'])
393n/a self.assertEqual(metadata['Metadata-Version'], '1.2')
394n/a self.assertNotIn('Provides', metadata)
395n/a self.assertEqual(metadata['Requires-Dist'],
396n/a ['other', 'another (==1.0)'])
397n/a self.assertNotIn('Obsoletes', metadata)
398n/a
399n/a # make sure write_file uses one RFC 822 header per item
400n/a fp = StringIO()
401n/a metadata.write_file(fp)
402n/a lines = fp.getvalue().split('\n')
403n/a self.assertIn('Requires-Dist: other', lines)
404n/a self.assertIn('Requires-Dist: another (==1.0)', lines)
405n/a
406n/a # test warnings for invalid version predicates
407n/a # XXX this would cause no warnings if we used update (or the mapping
408n/a # argument of the constructor), see comment in Metadata.update
409n/a metadata = Metadata()
410n/a metadata['Requires-Dist'] = 'Funky (Groovie)'
411n/a metadata['Requires-Python'] = '1-4'
412n/a self.assertEqual(len(self.get_logs()), 2)
413n/a
414n/a # test multiple version predicates
415n/a metadata = Metadata()
416n/a
417n/a # XXX check PEP and see if 3 == 3.0
418n/a metadata['Requires-Python'] = '>=2.6, <3.0'
419n/a metadata['Requires-Dist'] = ['Foo (>=2.6, <3.0)']
420n/a self.assertEqual(self.get_logs(), [])
421n/a
422n/a @unittest.skip('needs to be implemented')
423n/a def test_requires_illegal(self):
424n/a self.assertRaises(ValueError, Metadata,
425n/a mapping={'name': 'project',
426n/a 'version': '1.0',
427n/a 'requires': ['my.pkg (splat)']})
428n/a
429n/a def test_obsoletes_dist(self):
430n/a fields = {'name': 'project',
431n/a 'version': '1.0',
432n/a 'obsoletes_dist': ['other', 'another (<1.0)']}
433n/a metadata = Metadata(mapping=fields)
434n/a self.assertEqual(metadata['Obsoletes-Dist'],
435n/a ['other', 'another (<1.0)'])
436n/a self.assertEqual(metadata['Metadata-Version'], '1.2')
437n/a self.assertNotIn('Provides', metadata)
438n/a self.assertNotIn('Requires', metadata)
439n/a self.assertEqual(metadata['Obsoletes-Dist'],
440n/a ['other', 'another (<1.0)'])
441n/a
442n/a @unittest.skip('needs to be implemented')
443n/a def test_obsoletes_illegal(self):
444n/a self.assertRaises(ValueError, Metadata,
445n/a mapping={'name': 'project',
446n/a 'version': '1.0',
447n/a 'obsoletes': ['my.pkg (splat)']})
448n/a
449n/a
450n/adef test_suite():
451n/a return unittest.makeSuite(MetadataTestCase)
452n/a
453n/aif __name__ == '__main__':
454n/a unittest.main(defaultTest='test_suite')