diff --git a/Lib/packaging/manifest.py b/Lib/packaging/manifest.py index a3798530a58..5eee174cc6e 100644 --- a/Lib/packaging/manifest.py +++ b/Lib/packaging/manifest.py @@ -147,7 +147,9 @@ class Manifest(object): def _parse_template_line(self, line): words = line.split() - if len(words) == 1: + if len(words) == 1 and words[0] not in ( + 'include', 'exclude', 'global-include', 'global-exclude', + 'recursive-include', 'recursive-exclude', 'graft', 'prune'): # no action given, let's use the default 'include' words.insert(0, 'include') diff --git a/Lib/packaging/tests/test_manifest.py b/Lib/packaging/tests/test_manifest.py index e0bcbbc3e9f..1c7aa934269 100644 --- a/Lib/packaging/tests/test_manifest.py +++ b/Lib/packaging/tests/test_manifest.py @@ -1,8 +1,10 @@ """Tests for packaging.manifest.""" import os +import re import logging from io import StringIO -from packaging.manifest import Manifest +from packaging.errors import PackagingTemplateError +from packaging.manifest import Manifest, _translate_pattern, _glob_to_re from packaging.tests import unittest, support @@ -34,6 +36,12 @@ class ManifestTestCase(support.TempdirManager, os.chdir(self.cwd) super(ManifestTestCase, self).tearDown() + def assertNoWarnings(self): + self.assertEqual(self.get_logs(logging.WARNING), []) + + def assertWarnings(self): + self.assertGreater(len(self.get_logs(logging.WARNING)), 0) + def test_manifest_reader(self): tmpdir = self.mkdtemp() MANIFEST = os.path.join(tmpdir, 'MANIFEST.in') @@ -69,6 +77,193 @@ class ManifestTestCase(support.TempdirManager, manifest.read_template(content) self.assertEqual(['README', 'file1'], manifest.files) + def test_glob_to_re(self): + # simple cases + self.assertEqual(_glob_to_re('foo*'), 'foo[^/]*\\Z(?ms)') + self.assertEqual(_glob_to_re('foo?'), 'foo[^/]\\Z(?ms)') + self.assertEqual(_glob_to_re('foo??'), 'foo[^/][^/]\\Z(?ms)') + + # special cases + self.assertEqual(_glob_to_re(r'foo\\*'), r'foo\\\\[^/]*\Z(?ms)') + self.assertEqual(_glob_to_re(r'foo\\\*'), r'foo\\\\\\[^/]*\Z(?ms)') + self.assertEqual(_glob_to_re('foo????'), r'foo[^/][^/][^/][^/]\Z(?ms)') + self.assertEqual(_glob_to_re(r'foo\\??'), r'foo\\\\[^/][^/]\Z(?ms)') + + def test_remove_duplicates(self): + manifest = Manifest() + manifest.files = ['a', 'b', 'a', 'g', 'c', 'g'] + # files must be sorted beforehand + manifest.sort() + manifest.remove_duplicates() + self.assertEqual(manifest.files, ['a', 'b', 'c', 'g']) + + def test_translate_pattern(self): + # blackbox test of a private function + + # not regex + pattern = _translate_pattern('a', anchor=True, is_regex=False) + self.assertTrue(hasattr(pattern, 'search')) + + # is a regex + regex = re.compile('a') + pattern = _translate_pattern(regex, anchor=True, is_regex=True) + self.assertEqual(pattern, regex) + + # plain string flagged as regex + pattern = _translate_pattern('a', anchor=True, is_regex=True) + self.assertTrue(hasattr(pattern, 'search')) + + # glob support + pattern = _translate_pattern('*.py', anchor=True, is_regex=False) + self.assertTrue(pattern.search('filelist.py')) + + def test_exclude_pattern(self): + # return False if no match + manifest = Manifest() + self.assertFalse(manifest.exclude_pattern('*.py')) + + # return True if files match + manifest = Manifest() + manifest.files = ['a.py', 'b.py'] + self.assertTrue(manifest.exclude_pattern('*.py')) + + # test excludes + manifest = Manifest() + manifest.files = ['a.py', 'a.txt'] + manifest.exclude_pattern('*.py') + self.assertEqual(manifest.files, ['a.txt']) + + def test_include_pattern(self): + # return False if no match + manifest = Manifest() + manifest.allfiles = [] + self.assertFalse(manifest._include_pattern('*.py')) + + # return True if files match + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt'] + self.assertTrue(manifest._include_pattern('*.py')) + + # test * matches all files + manifest = Manifest() + self.assertIsNone(manifest.allfiles) + manifest.allfiles = ['a.py', 'b.txt'] + manifest._include_pattern('*') + self.assertEqual(manifest.allfiles, ['a.py', 'b.txt']) + + def test_process_template(self): + # invalid lines + manifest = Manifest() + for action in ('include', 'exclude', 'global-include', + 'global-exclude', 'recursive-include', + 'recursive-exclude', 'graft', 'prune'): + self.assertRaises(PackagingTemplateError, + manifest._process_template_line, action) + + # implicit include + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('*.py') + self.assertEqual(manifest.files, ['a.py']) + self.assertNoWarnings() + + # include + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('include *.py') + self.assertEqual(manifest.files, ['a.py']) + self.assertNoWarnings() + + manifest._process_template_line('include *.rb') + self.assertEqual(manifest.files, ['a.py']) + self.assertWarnings() + + # exclude + manifest = Manifest() + manifest.files = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('exclude *.py') + self.assertEqual(manifest.files, ['b.txt', 'd/c.py']) + self.assertNoWarnings() + + manifest._process_template_line('exclude *.rb') + self.assertEqual(manifest.files, ['b.txt', 'd/c.py']) + self.assertWarnings() + + # global-include + manifest = Manifest() + manifest.allfiles = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('global-include *.py') + self.assertEqual(manifest.files, ['a.py', 'd/c.py']) + self.assertNoWarnings() + + manifest._process_template_line('global-include *.rb') + self.assertEqual(manifest.files, ['a.py', 'd/c.py']) + self.assertWarnings() + + # global-exclude + manifest = Manifest() + manifest.files = ['a.py', 'b.txt', 'd/c.py'] + + manifest._process_template_line('global-exclude *.py') + self.assertEqual(manifest.files, ['b.txt']) + self.assertNoWarnings() + + manifest._process_template_line('global-exclude *.rb') + self.assertEqual(manifest.files, ['b.txt']) + self.assertWarnings() + + # recursive-include + manifest = Manifest() + manifest.allfiles = ['a.py', 'd/b.py', 'd/c.txt', 'd/d/e.py'] + + manifest._process_template_line('recursive-include d *.py') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertNoWarnings() + + manifest._process_template_line('recursive-include e *.py') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertWarnings() + + # recursive-exclude + manifest = Manifest() + manifest.files = ['a.py', 'd/b.py', 'd/c.txt', 'd/d/e.py'] + + manifest._process_template_line('recursive-exclude d *.py') + self.assertEqual(manifest.files, ['a.py', 'd/c.txt']) + self.assertNoWarnings() + + manifest._process_template_line('recursive-exclude e *.py') + self.assertEqual(manifest.files, ['a.py', 'd/c.txt']) + self.assertWarnings() + + # graft + manifest = Manifest() + manifest.allfiles = ['a.py', 'd/b.py', 'd/d/e.py', 'f/f.py'] + + manifest._process_template_line('graft d') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertNoWarnings() + + manifest._process_template_line('graft e') + self.assertEqual(manifest.files, ['d/b.py', 'd/d/e.py']) + self.assertWarnings() + + # prune + manifest = Manifest() + manifest.files = ['a.py', 'd/b.py', 'd/d/e.py', 'f/f.py'] + + manifest._process_template_line('prune d') + self.assertEqual(manifest.files, ['a.py', 'f/f.py']) + self.assertNoWarnings() + + manifest._process_template_line('prune e') + self.assertEqual(manifest.files, ['a.py', 'f/f.py']) + self.assertWarnings() + def test_suite(): return unittest.makeSuite(ManifestTestCase)