check-manifest/tests.py

1636 lines
63 KiB
Python

from __future__ import print_function
import codecs
import doctest
import locale
import os
import shutil
import subprocess
import sys
import tarfile
import tempfile
import textwrap
import unittest
import zipfile
from contextlib import closing
from io import BytesIO
from xml.etree import cElementTree as ET
try:
from cStringIO import StringIO # Python 2.x
except ImportError:
from io import StringIO # Python 3.x
import mock
from check_manifest import rmtree
CAN_SKIP_TESTS = os.getenv('SKIP_NO_TESTS', '') == ''
try:
codecs.lookup('oem')
except LookupError:
HAS_OEM_CODEC = False
else:
# Python >= 3.6 on Windows
HAS_OEM_CODEC = True
class Tests(unittest.TestCase):
def setUp(self):
import check_manifest
self.warnings = []
self._real_warning = check_manifest.warning
check_manifest.warning = self.warnings.append
def tearDown(self):
import check_manifest
check_manifest.warning = self._real_warning
def make_temp_dir(self):
tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest')
self.addCleanup(rmtree, tmpdir)
return tmpdir
def create_file(self, filename, contents):
with open(filename, 'w') as f:
f.write(contents)
def create_zip_file(self, filename, filenames):
with closing(zipfile.ZipFile(filename, 'w')) as zf:
for fn in filenames:
zf.writestr(fn, '')
def create_tar_file(self, filename, filenames):
with closing(tarfile.TarFile(filename, 'w')) as tf:
for fn in filenames:
tf.addfile(tarfile.TarInfo(fn), BytesIO())
def test_run_success(self):
from check_manifest import run
self.assertEqual(run(["true"]), "")
def test_run_failure(self):
from check_manifest import run, CommandFailed
with self.assertRaises(CommandFailed) as cm:
run(["false"])
self.assertEqual(str(cm.exception),
"['false'] failed (status 1):\n")
def test_run_no_such_program(self):
from check_manifest import run, Failure
with self.assertRaises(Failure) as cm:
run(["there-is-really-no-such-program"])
# Linux says "[Errno 2] No such file or directory"
# Windows says "[Error 2] The system cannot find the file specified"
# but on 3.x it's "[WinErr 2] The system cannot find the file specified"
should_start_with = "could not run ['there-is-really-no-such-program']:"
self.assertTrue(
str(cm.exception).startswith(should_start_with),
'\n%r does not start with\n%r' % (str(cm.exception),
should_start_with))
def test_mkdtemp_readonly_files(self):
from check_manifest import mkdtemp
with mkdtemp(hint='-test-readonly') as d:
fn = os.path.join(d, 'file.txt')
with open(fn, 'w'):
pass
os.chmod(fn, 0o444) # readonly
assert not os.path.exists(d)
@unittest.skipIf(sys.platform == 'win32',
"No POSIX-like unreadable directories on Windows")
def test_rmtree_unreadable_directories(self):
d = self.make_temp_dir()
sd = os.path.join(d, 'subdir')
os.mkdir(sd)
os.chmod(sd, 0) # a bad mode for a directory, oops
# The onerror API of shutil.rmtree doesn't let us recover from
# os.listdir() failures.
with self.assertRaises(OSError):
rmtree(sd)
os.chmod(sd, 0o755) # so we can clean up
def test_rmtree_readonly_directories(self):
d = self.make_temp_dir()
sd = os.path.join(d, 'subdir')
fn = os.path.join(sd, 'file.txt')
os.mkdir(sd)
open(fn, 'w').close()
os.chmod(sd, 0o444) # a bad mode for a directory, oops
rmtree(sd)
assert not os.path.exists(sd)
def test_rmtree_readonly_directories_and_files(self):
d = self.make_temp_dir()
sd = os.path.join(d, 'subdir')
fn = os.path.join(sd, 'file.txt')
os.mkdir(sd)
open(fn, 'w').close()
os.chmod(fn, 0o444) # readonly
os.chmod(sd, 0o444) # a bad mode for a directory, oops
rmtree(sd)
assert not os.path.exists(sd)
def test_copy_files(self):
from check_manifest import copy_files
actions = []
n = os.path.normpath
with mock.patch('os.path.isdir', lambda d: d in ('b', n('/dest/dir'))):
with mock.patch('os.makedirs',
lambda d: actions.append('makedirs %s' % d)):
with mock.patch('os.mkdir',
lambda d: actions.append('mkdir %s' % d)):
with mock.patch('shutil.copy2',
lambda s, d: actions.append('cp %s %s' % (s, d))):
copy_files(['a', 'b', n('c/d/e')], n('/dest/dir'))
self.assertEqual(
actions,
[
'cp a %s' % n('/dest/dir/a'),
'mkdir %s' % n('/dest/dir/b'),
'makedirs %s' % n('/dest/dir/c/d'),
'cp %s %s' % (n('c/d/e'), n('/dest/dir/c/d/e')),
])
def test_get_one_file_in(self):
from check_manifest import get_one_file_in
with mock.patch('os.listdir', lambda dir: ['a']):
self.assertEqual(get_one_file_in(os.path.normpath('/some/dir')),
os.path.normpath('/some/dir/a'))
def test_get_one_file_in_empty_directory(self):
from check_manifest import get_one_file_in, Failure
with mock.patch('os.listdir', lambda dir: []):
with self.assertRaises(Failure) as cm:
get_one_file_in('/some/dir')
self.assertEqual(str(cm.exception),
"No files found in /some/dir")
def test_get_one_file_in_too_many(self):
from check_manifest import get_one_file_in, Failure
with mock.patch('os.listdir', lambda dir: ['b', 'a']):
with self.assertRaises(Failure) as cm:
get_one_file_in('/some/dir')
self.assertEqual(str(cm.exception),
"More than one file exists in /some/dir:\na\nb")
def test_unicodify(self):
from check_manifest import unicodify
nonascii = b'\xc3\xa9.txt'.decode('UTF-8') # because Py3.2 lacks u''
self.assertEqual(unicodify(nonascii), nonascii)
self.assertEqual(
unicodify(nonascii.encode(locale.getpreferredencoding())),
nonascii)
def test_get_archive_file_list_unrecognized_archive(self):
from check_manifest import get_archive_file_list, Failure
with self.assertRaises(Failure) as cm:
get_archive_file_list('archive.rar')
self.assertEqual(str(cm.exception), 'Unrecognized archive type: .rar')
def test_get_archive_file_list_zip(self):
from check_manifest import get_archive_file_list
filename = os.path.join(self.make_temp_dir(), 'archive.zip')
self.create_zip_file(filename, ['a', 'b/c'])
self.assertEqual(get_archive_file_list(filename),
['a', 'b', 'b/c'])
def test_get_archive_file_list_zip_nonascii(self):
from check_manifest import get_archive_file_list
filename = os.path.join(self.make_temp_dir(), 'archive.zip')
nonascii = b'\xc3\xa9.txt'.decode('UTF-8') # because Py3.2 lacks u''
self.create_zip_file(filename, [nonascii])
self.assertEqual(get_archive_file_list(filename),
[nonascii])
def test_get_archive_file_list_tar(self):
from check_manifest import get_archive_file_list
filename = os.path.join(self.make_temp_dir(), 'archive.tar')
self.create_tar_file(filename, ['a', 'b/c'])
self.assertEqual(get_archive_file_list(filename),
['a', 'b', 'b/c'])
def test_get_archive_file_list_tar_nonascii(self):
from check_manifest import get_archive_file_list
filename = os.path.join(self.make_temp_dir(), 'archive.tar')
nonascii = b'\xc3\xa9.txt'.decode('UTF-8') # because Py3.2 lacks u''
self.create_tar_file(filename, [nonascii])
self.assertEqual(get_archive_file_list(filename),
[nonascii])
def test_format_list(self):
from check_manifest import format_list
self.assertEqual(format_list([]), "")
self.assertEqual(format_list(['a']), " a")
self.assertEqual(format_list(['a', 'b']), " a\n b")
def test_format_missing(self):
from check_manifest import format_missing
self.assertEqual(
format_missing(set(), set(), "1st", "2nd"),
"")
self.assertEqual(
format_missing(set(["c"]), set(["a"]), "1st", "2nd"),
"missing from 1st:\n"
" c\n"
"missing from 2nd:\n"
" a")
def test_strip_toplevel_name_empty_list(self):
from check_manifest import strip_toplevel_name
self.assertEqual(strip_toplevel_name([]), [])
def test_strip_toplevel_name_no_common_prefix(self):
from check_manifest import strip_toplevel_name, Failure
self.assertRaises(Failure, strip_toplevel_name, ["a/b", "c/d"])
def test_detect_vcs_no_vcs(self):
from check_manifest import detect_vcs, Failure
with mock.patch('check_manifest.VCS.detect', staticmethod(lambda *a: False)):
with mock.patch('check_manifest.Git.detect', staticmethod(lambda *a: False)):
with self.assertRaises(Failure) as cm:
detect_vcs()
self.assertEqual(str(cm.exception),
"Couldn't find version control data"
" (git/hg/bzr/svn supported)")
def test_normalize_names(self):
from check_manifest import normalize_names
j = os.path.join
self.assertEqual(normalize_names(["a", j("b", ""), j("c", "d"),
j("e", "f", ""),
j("g", "h", "..", "i")]),
["a", "b", j("c", "d"), j("e", "f"), j("g", "i")])
def test_add_directories(self):
from check_manifest import add_directories
j = os.path.join
self.assertEqual(add_directories(['a', 'b', j('c', 'd'), j('e', 'f')]),
['a', 'b', 'c', j('c', 'd'), 'e', j('e', 'f')])
def test_file_matches(self):
from check_manifest import file_matches
# On Windows we might get the pattern list from setup.cfg using / as
# the directory separator, but the filenames we're matching against
# will use os.path.sep
patterns = ['setup.cfg', '*.egg-info', '*.egg-info/*']
j = os.path.join
self.assertFalse(file_matches('setup.py', patterns))
self.assertTrue(file_matches('setup.cfg', patterns))
self.assertTrue(file_matches(j('src', 'zope.foo.egg-info'), patterns))
self.assertTrue(
file_matches(j('src', 'zope.foo.egg-info', 'SOURCES.txt'),
patterns))
def test_strip_sdist_extras(self):
from check_manifest import strip_sdist_extras
filelist = list(map(os.path.normpath, [
'.github',
'.github/ISSUE_TEMPLATE',
'.github/ISSUE_TEMPLATE/bug_report.md',
'.gitignore',
'.travis.yml',
'setup.py',
'setup.cfg',
'README.txt',
'src',
'src/.gitignore',
'src/zope',
'src/zope/__init__.py',
'src/zope/foo',
'src/zope/foo/__init__.py',
'src/zope/foo/language.po',
'src/zope/foo/language.mo',
'src/zope.foo.egg-info',
'src/zope.foo.egg-info/SOURCES.txt',
]))
expected = list(map(os.path.normpath, [
'setup.py',
'README.txt',
'src',
'src/zope',
'src/zope/__init__.py',
'src/zope/foo',
'src/zope/foo/__init__.py',
'src/zope/foo/language.po',
]))
self.assertEqual(strip_sdist_extras(filelist), expected)
def test_strip_sdist_extras_with_manifest(self):
import check_manifest
from check_manifest import strip_sdist_extras
from check_manifest import _get_ignore_from_manifest_lines as parse
orig_ignore = check_manifest.IGNORE[:]
orig_ignore_regexps = check_manifest.IGNORE_REGEXPS[:]
manifest_in = textwrap.dedent("""
graft src
exclude *.cfg
global-exclude *.mo
prune src/dump
recursive-exclude src/zope *.sh
""")
filelist = list(map(os.path.normpath, [
'.github/ISSUE_TEMPLATE/bug_report.md',
'.gitignore',
'setup.py',
'setup.cfg',
'MANIFEST.in',
'README.txt',
'src',
'src/helper.sh',
'src/dump',
'src/dump/__init__.py',
'src/zope',
'src/zope/__init__.py',
'src/zope/zopehelper.sh',
'src/zope/foo',
'src/zope/foo/__init__.py',
'src/zope/foo/language.po',
'src/zope/foo/language.mo',
'src/zope/foo/config.cfg',
'src/zope/foo/foohelper.sh',
'src/zope.foo.egg-info',
'src/zope.foo.egg-info/SOURCES.txt',
]))
expected = list(map(os.path.normpath, [
'setup.py',
'MANIFEST.in',
'README.txt',
'src',
'src/helper.sh',
'src/zope',
'src/zope/__init__.py',
'src/zope/foo',
'src/zope/foo/__init__.py',
'src/zope/foo/language.po',
'src/zope/foo/config.cfg',
]))
# This will change the definitions.
try:
# This is normally done in read_manifest:
ignore, ignore_regexps = parse(manifest_in.splitlines())
check_manifest.IGNORE.extend(ignore)
check_manifest.IGNORE_REGEXPS.extend(ignore_regexps)
# Filter the file list.
result = strip_sdist_extras(filelist)
finally:
# Restore the original definitions
check_manifest.IGNORE[:] = orig_ignore
check_manifest.IGNORE_REGEXPS[:] = orig_ignore_regexps
self.assertEqual(result, expected)
def test_find_bad_ideas(self):
from check_manifest import find_bad_ideas
filelist = list(map(os.path.normpath, [
'.gitignore',
'setup.py',
'setup.cfg',
'README.txt',
'src',
'src/zope',
'src/zope/__init__.py',
'src/zope/foo',
'src/zope/foo/__init__.py',
'src/zope/foo/language.po',
'src/zope/foo/language.mo',
'src/zope.foo.egg-info',
'src/zope.foo.egg-info/SOURCES.txt',
]))
expected = list(map(os.path.normpath, [
'src/zope/foo/language.mo',
'src/zope.foo.egg-info',
]))
self.assertEqual(find_bad_ideas(filelist), expected)
def test_find_suggestions(self):
from check_manifest import find_suggestions
self.assertEqual(find_suggestions(['buildout.cfg']),
(['include buildout.cfg'], []))
self.assertEqual(find_suggestions(['unknown.file~']),
([], ['unknown.file~']))
self.assertEqual(find_suggestions(['README.txt', 'CHANGES.txt']),
(['include *.txt'], []))
filelist = list(map(os.path.normpath, [
'docs/index.rst',
'docs/image.png',
'docs/Makefile',
'docs/unknown-file',
'src/etc/blah/blah/Makefile',
]))
expected_rules = [
'recursive-include docs *.png',
'recursive-include docs *.rst',
'recursive-include docs Makefile',
'recursive-include src Makefile',
]
expected_unknowns = [os.path.normpath('docs/unknown-file')]
self.assertEqual(find_suggestions(filelist),
(expected_rules, expected_unknowns))
def test_find_suggestions_generic_fallback_rules(self):
from check_manifest import find_suggestions
n = os.path.normpath
self.assertEqual(find_suggestions(['Changelog']),
(['include Changelog'], []))
self.assertEqual(find_suggestions(['id-lang.map']),
(['include *.map'], []))
self.assertEqual(find_suggestions([n('src/id-lang.map')]),
(['recursive-include src *.map'], []))
def test_find_suggestions_ignores_directories(self):
from check_manifest import find_suggestions
with mock.patch('os.path.isdir', lambda d: True):
self.assertEqual(find_suggestions(['SOMEDIR']),
([], []))
def test_is_package(self):
from check_manifest import is_package
j = os.path.join
exists = {j('a', 'setup.py'), j('c', 'pyproject.toml')}
with mock.patch('os.path.exists', lambda fn: fn in exists):
self.assertTrue(is_package('a'))
self.assertFalse(is_package('b'))
self.assertTrue(is_package('c'))
def test_extract_version_from_filename(self):
from check_manifest import extract_version_from_filename as e
self.assertEqual(e('dist/foo_bar-1.2.3.dev4+g12345.zip'), '1.2.3.dev4+g12345')
self.assertEqual(e('dist/foo_bar-1.2.3.dev4+g12345.tar.gz'), '1.2.3.dev4+g12345')
def test_glob_to_regexp(self):
from check_manifest import _glob_to_regexp as g2r
sep = os.path.sep.replace('\\', '\\\\')
if sys.version_info >= (3, 7):
self.assertEqual(g2r('foo.py'), r'(?s:foo\.py)\Z')
self.assertEqual(g2r('foo/bar.py'), r'(?s:foo/bar\.py)\Z')
self.assertEqual(g2r('foo*.py'), r'(?s:foo[^%s]*\.py)\Z' % sep)
self.assertEqual(g2r('foo?.py'), r'(?s:foo[^%s]\.py)\Z' % sep)
self.assertEqual(g2r('foo[123].py'), r'(?s:foo[123]\.py)\Z')
self.assertEqual(g2r('foo[!123].py'), r'(?s:foo[^123]\.py)\Z')
self.assertEqual(g2r('foo/*.py'), r'(?s:foo/[^%s]*\.py)\Z' % sep)
elif sys.version_info >= (3, 6):
self.assertEqual(g2r('foo.py'), r'(?s:foo\.py)\Z')
self.assertEqual(g2r('foo/bar.py'), r'(?s:foo\/bar\.py)\Z')
self.assertEqual(g2r('foo*.py'), r'(?s:foo[^%s]*\.py)\Z' % sep)
self.assertEqual(g2r('foo?.py'), r'(?s:foo[^%s]\.py)\Z' % sep)
self.assertEqual(g2r('foo[123].py'), r'(?s:foo[123]\.py)\Z')
self.assertEqual(g2r('foo[!123].py'), r'(?s:foo[^123]\.py)\Z')
self.assertEqual(g2r('foo/*.py'), r'(?s:foo\/[^%s]*\.py)\Z' % sep)
else:
self.assertEqual(g2r('foo.py'), r'foo\.py\Z(?ms)')
self.assertEqual(g2r('foo/bar.py'), r'foo\/bar\.py\Z(?ms)')
self.assertEqual(g2r('foo*.py'), r'foo[^%s]*\.py\Z(?ms)' % sep)
self.assertEqual(g2r('foo?.py'), r'foo[^%s]\.py\Z(?ms)' % sep)
self.assertEqual(g2r('foo[123].py'), r'foo[123]\.py\Z(?ms)')
self.assertEqual(g2r('foo[!123].py'), r'foo[^123]\.py\Z(?ms)')
self.assertEqual(g2r('foo/*.py'), r'foo\/[^%s]*\.py\Z(?ms)' % sep)
def test_get_ignore_from_manifest_lines(self):
from check_manifest import _get_ignore_from_manifest_lines as parse
from check_manifest import _glob_to_regexp as g2r
j = os.path.join
# The return value is a tuple with two lists:
# ([<list of filename ignores>], [<list of regular expressions>])
self.assertEqual(parse([]),
([], []))
self.assertEqual(parse(['', ' ']),
([], []))
self.assertEqual(parse(['exclude *.cfg']),
([], [g2r('*.cfg')]))
self.assertEqual(parse(['exclude *.cfg']),
([], [g2r('*.cfg')]))
self.assertEqual(parse(['\texclude\t*.cfg foo.* bar.txt']),
(['bar.txt'], [g2r('*.cfg'), g2r('foo.*')]))
self.assertEqual(parse(['exclude some/directory/*.cfg']),
([], [g2r('some/directory/*.cfg')]))
self.assertEqual(parse(['include *.cfg']),
([], []))
self.assertEqual(parse(['global-exclude *.pyc']),
(['*.pyc'], []))
self.assertEqual(parse(['global-exclude *.pyc *.sh']),
(['*.pyc', '*.sh'], []))
self.assertEqual(parse(['recursive-exclude dir *.pyc']),
([j('dir', '*.pyc')], []))
self.assertEqual(parse(['recursive-exclude dir *.pyc foo*.sh']),
([j('dir', '*.pyc'), j('dir', 'foo*.sh'),
j('dir', '*', 'foo*.sh')], []))
self.assertEqual(parse(['recursive-exclude dir nopattern.xml']),
([j('dir', 'nopattern.xml'),
j('dir', '*', 'nopattern.xml')], []))
# We should not fail when a recursive-exclude line is wrong:
self.assertEqual(parse(['recursive-exclude dirwithoutpattern']),
([], []))
self.assertEqual(parse(['prune dir']),
(['dir', j('dir', '*')], []))
# You should not add a slash at the end of a prune, but let's
# not fail over it or end up with double slashes.
self.assertEqual(parse(['prune dir/']),
(['dir', j('dir', '*')], []))
# You should also not have a leading slash
self.assertEqual(parse(['prune /dir']),
(['/dir', j('/dir', '*')], []))
# And a mongo test case of everything at the end
text = textwrap.dedent("""
exclude *.02
exclude *.03 04.* bar.txt
exclude *.05
exclude some/directory/*.cfg
global-exclude *.10 *.11
global-exclude *.12
include *.20
prune 30
recursive-exclude 40 *.41
recursive-exclude 42 *.43 44.*
""").splitlines()
self.assertEqual(
parse(text),
([
'bar.txt',
'*.10',
'*.11',
'*.12',
'30',
j('30', '*'),
j('40', '*.41'),
j('42', '*.43'),
j('42', '44.*'),
j('42', '*', '44.*'),
], [
g2r('*.02'),
g2r('*.03'),
g2r('04.*'),
g2r('*.05'),
g2r('some/directory/*.cfg'),
]))
def test_get_ignore_from_manifest(self):
from check_manifest import _get_ignore_from_manifest as parse
filename = os.path.join(self.make_temp_dir(), 'MANIFEST.in')
self.create_file(filename, textwrap.dedent('''
exclude \\
# yes, this is allowed!
test.dat
# https://github.com/mgedmin/check-manifest/issues/66
# docs/ folder
'''))
self.assertEqual(parse(filename), (['test.dat'], []))
self.assertEqual(self.warnings, [])
def test_get_ignore_from_manifest_warnings(self):
from check_manifest import _get_ignore_from_manifest as parse
filename = os.path.join(self.make_temp_dir(), 'MANIFEST.in')
self.create_file(filename, textwrap.dedent('''
# this is bad: a file should not end with a backslash
exclude test.dat \\
'''))
self.assertEqual(parse(filename), (['test.dat'], []))
self.assertEqual(self.warnings, [
"%s, line 2: continuation line immediately precedes end-of-file" % filename,
])
def test_should_use_pep517_no_pyproject_toml(self):
from check_manifest import should_use_pep_517, cd
src_dir = self.make_temp_dir()
with cd(src_dir):
self.assertFalse(should_use_pep_517())
def test_should_use_pep517_no_build_system(self):
from check_manifest import should_use_pep_517, cd
src_dir = self.make_temp_dir()
filename = os.path.join(src_dir, 'pyproject.toml')
self.create_file(filename, textwrap.dedent('''
[tool.check-manifest]
'''))
with cd(src_dir):
self.assertFalse(should_use_pep_517())
def test_should_use_pep517_no_build_backend(self):
from check_manifest import should_use_pep_517, cd
src_dir = self.make_temp_dir()
filename = os.path.join(src_dir, 'pyproject.toml')
self.create_file(filename, textwrap.dedent('''
[build-system]
requires = [
"setuptools >= 40.6.0",
"wheel",
]
'''))
with cd(src_dir):
self.assertFalse(should_use_pep_517())
def test_should_use_pep517_yes_please(self):
from check_manifest import should_use_pep_517, cd
src_dir = self.make_temp_dir()
filename = os.path.join(src_dir, 'pyproject.toml')
self.create_file(filename, textwrap.dedent('''
[build-system]
requires = [
"setuptools >= 40.6.0",
"wheel",
]
build-backend = "setuptools.build_meta"
'''))
with cd(src_dir):
self.assertTrue(should_use_pep_517())
def test_build_sdist(self):
from check_manifest import build_sdist, cd, get_one_file_in
src_dir = self.make_temp_dir()
filename = os.path.join(src_dir, 'pyproject.toml')
self.create_file(filename, textwrap.dedent('''
[build-system]
requires = [
"setuptools >= 40.6.0",
"wheel",
]
build-backend = "setuptools.build_meta"
'''))
out_dir = self.make_temp_dir()
python = os.path.abspath(sys.executable)
with cd(src_dir):
build_sdist(out_dir, python=python)
self.assertTrue(get_one_file_in(out_dir))
class TestConfiguration(unittest.TestCase):
def setUp(self):
import check_manifest
self.oldpwd = os.getcwd()
self.tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest')
os.chdir(self.tmpdir)
self.OLD_IGNORE = check_manifest.IGNORE
self.OLD_IGNORE_REGEXPS = check_manifest.IGNORE_REGEXPS
self.OLD_IGNORE_BAD_IDEAS = check_manifest.IGNORE_BAD_IDEAS
check_manifest.IGNORE = ['default-ignore-rules']
check_manifest.IGNORE_REGEXPS = ['default-ignore-regexps']
check_manifest.IGNORE_BAD_IDEAS = []
def tearDown(self):
import check_manifest
check_manifest.IGNORE = self.OLD_IGNORE
check_manifest.IGNORE_REGEXPS = self.OLD_IGNORE_REGEXPS
check_manifest.IGNORE_BAD_IDEAS = self.OLD_IGNORE_BAD_IDEAS
os.chdir(self.oldpwd)
rmtree(self.tmpdir)
def test_read_config_no_config(self):
import check_manifest
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE, ['default-ignore-rules'])
def test_read_setup_config_no_section(self):
import check_manifest
with open('setup.cfg', 'w') as f:
f.write('[pep8]\nignore =\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE, ['default-ignore-rules'])
def test_read_pyproject_config_no_section(self):
import check_manifest
with open('pyproject.toml', 'w') as f:
f.write('[tool.pep8]\nignore = []\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE, ['default-ignore-rules'])
def test_read_setup_config_no_option(self):
import check_manifest
with open('setup.cfg', 'w') as f:
f.write('[check-manifest]\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE, ['default-ignore-rules'])
def test_read_pyproject_config_no_option(self):
import check_manifest
with open('pyproject.toml', 'w') as f:
f.write('[tool.check-manifest]\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE, ['default-ignore-rules'])
def test_read_setup_config_extra_ignores(self):
import check_manifest
with open('setup.cfg', 'w') as f:
f.write('[check-manifest]\nignore = foo\n bar\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE,
['default-ignore-rules', 'foo', 'bar'])
def test_read_pyproject_config_extra_ignores(self):
import check_manifest
with open('pyproject.toml', 'w') as f:
f.write('[tool.check-manifest]\nignore = ["foo", "bar"]\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE,
['default-ignore-rules', 'foo', 'bar'])
def test_read_setup_config_override_ignores(self):
import check_manifest
with open('setup.cfg', 'w') as f:
f.write('[check-manifest]\nignore = foo\n\n bar\n')
f.write('ignore-default-rules = yes\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE,
['foo', 'bar'])
def test_read_pyproject_config_override_ignores(self):
import check_manifest
with open('pyproject.toml', 'w') as f:
f.write('[tool.check-manifest]\nignore = ["foo", "bar"]\n')
f.write('ignore-default-rules = true\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE,
['foo', 'bar'])
def test_read_setup_config_ignore_bad_ideas(self):
import check_manifest
with open('setup.cfg', 'w') as f:
f.write('[check-manifest]\n'
'ignore-bad-ideas = \n'
' foo\n'
' bar\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE_BAD_IDEAS, ['foo', 'bar'])
def test_read_pyproject_config_ignore_bad_ideas(self):
import check_manifest
with open('pyproject.toml', 'w') as f:
f.write('[tool.check-manifest]\n'
'ignore-bad-ideas = ["foo", "bar"]\n')
check_manifest.read_config()
self.assertEqual(check_manifest.IGNORE_BAD_IDEAS, ['foo', 'bar'])
def test_read_manifest_no_manifest(self):
import check_manifest
check_manifest.read_manifest()
self.assertEqual(check_manifest.IGNORE, ['default-ignore-rules'])
def test_read_manifest(self):
import check_manifest
from check_manifest import _glob_to_regexp as g2r
with open('MANIFEST.in', 'w') as f:
f.write('exclude *.gif\n')
f.write('global-exclude *.png\n')
check_manifest.read_manifest()
self.assertEqual(check_manifest.IGNORE,
['default-ignore-rules', '*.png'])
self.assertEqual(check_manifest.IGNORE_REGEXPS,
['default-ignore-regexps', g2r('*.gif')])
class TestMain(unittest.TestCase):
def setUp(self):
import check_manifest
self._cm_patcher = mock.patch('check_manifest.check_manifest')
self._check_manifest = self._cm_patcher.start()
self._se_patcher = mock.patch('sys.exit')
self._sys_exit = self._se_patcher.start()
self._error_patcher = mock.patch('check_manifest.error')
self._error = self._error_patcher.start()
self._orig_sys_argv = sys.argv
sys.argv = ['check-manifest']
self.OLD_IGNORE = check_manifest.IGNORE
self.OLD_IGNORE_REGEXPS = check_manifest.IGNORE_REGEXPS
self.OLD_IGNORE_BAD_IDEAS = check_manifest.IGNORE_BAD_IDEAS
check_manifest.IGNORE = ['default-ignore-rules']
check_manifest.IGNORE_REGEXPS = ['default-ignore-regexps']
check_manifest.IGNORE_BAD_IDEAS = []
def tearDown(self):
import check_manifest
check_manifest.IGNORE = self.OLD_IGNORE
check_manifest.IGNORE_REGEXPS = self.OLD_IGNORE_REGEXPS
check_manifest.IGNORE_BAD_IDEAS = self.OLD_IGNORE_BAD_IDEAS
sys.argv = self._orig_sys_argv
self._se_patcher.stop()
self._cm_patcher.stop()
self._error_patcher.stop()
def test(self):
from check_manifest import main
sys.argv.append('-v')
main()
def test_exit_code_1_on_error(self):
from check_manifest import main
self._check_manifest.return_value = False
main()
self._sys_exit.assert_called_with(1)
def test_exit_code_2_on_failure(self):
from check_manifest import main, Failure
self._check_manifest.side_effect = Failure('msg')
main()
self._error.assert_called_with('msg')
self._sys_exit.assert_called_with(2)
def test_extra_ignore_args(self):
import check_manifest
sys.argv.append('--ignore=x,y,z')
check_manifest.main()
self.assertEqual(check_manifest.IGNORE,
['default-ignore-rules', 'x', 'y', 'z'])
def test_ignore_bad_ideas_args(self):
import check_manifest
sys.argv.append('--ignore-bad-ideas=x,y,z')
check_manifest.main()
self.assertEqual(check_manifest.IGNORE_BAD_IDEAS,
['x', 'y', 'z'])
class TestZestIntegration(unittest.TestCase):
def setUp(self):
sys.modules['zest'] = mock.Mock()
sys.modules['zest.releaser'] = mock.Mock()
sys.modules['zest.releaser.utils'] = mock.Mock()
self.ask = sys.modules['zest.releaser.utils'].ask
def tearDown(self):
del sys.modules['zest.releaser.utils']
del sys.modules['zest.releaser']
del sys.modules['zest']
@mock.patch('check_manifest.is_package', lambda d: False)
@mock.patch('check_manifest.check_manifest')
def test_zest_releaser_check_not_a_package(self, check_manifest):
from check_manifest import zest_releaser_check
zest_releaser_check(dict(workingdir='.'))
check_manifest.assert_not_called()
@mock.patch('check_manifest.is_package', lambda d: True)
@mock.patch('check_manifest.check_manifest')
def test_zest_releaser_check_user_disagrees(self, check_manifest):
from check_manifest import zest_releaser_check
self.ask.return_value = False
zest_releaser_check(dict(workingdir='.'))
check_manifest.assert_not_called()
@mock.patch('check_manifest.is_package', lambda d: True)
@mock.patch('sys.exit')
@mock.patch('check_manifest.check_manifest')
def test_zest_releaser_check_all_okay(self, check_manifest, sys_exit):
from check_manifest import zest_releaser_check
self.ask.return_value = True
check_manifest.return_value = True
zest_releaser_check(dict(workingdir='.'))
sys_exit.assert_not_called()
@mock.patch('check_manifest.is_package', lambda d: True)
@mock.patch('check_manifest.error')
@mock.patch('sys.exit')
@mock.patch('check_manifest.check_manifest')
def test_zest_releaser_check_error_user_aborts(self, check_manifest,
sys_exit, error):
from check_manifest import zest_releaser_check
self.ask.side_effect = [True, False]
check_manifest.return_value = False
zest_releaser_check(dict(workingdir='.'))
sys_exit.assert_called_with(1)
@mock.patch('check_manifest.is_package', lambda d: True)
@mock.patch('check_manifest.error')
@mock.patch('sys.exit')
@mock.patch('check_manifest.check_manifest')
def test_zest_releaser_check_error_user_plods_on(self, check_manifest,
sys_exit, error):
from check_manifest import zest_releaser_check
self.ask.side_effect = [True, True]
check_manifest.return_value = False
zest_releaser_check(dict(workingdir='.'))
sys_exit.assert_not_called()
@mock.patch('check_manifest.is_package', lambda d: True)
@mock.patch('check_manifest.error')
@mock.patch('sys.exit')
@mock.patch('check_manifest.check_manifest')
def test_zest_releaser_check_failure_user_aborts(self, check_manifest,
sys_exit, error):
from check_manifest import zest_releaser_check, Failure
self.ask.side_effect = [True, False]
check_manifest.side_effect = Failure('msg')
zest_releaser_check(dict(workingdir='.'))
error.assert_called_with('msg')
sys_exit.assert_called_with(2)
@mock.patch('check_manifest.is_package', lambda d: True)
@mock.patch('check_manifest.error')
@mock.patch('sys.exit')
@mock.patch('check_manifest.check_manifest')
def test_zest_releaser_check_failure_user_plods_on(self, check_manifest,
sys_exit, error):
from check_manifest import zest_releaser_check, Failure
self.ask.side_effect = [True, True]
check_manifest.side_effect = Failure('msg')
zest_releaser_check(dict(workingdir='.'))
error.assert_called_with('msg')
sys_exit.assert_not_called()
class VCSHelper(object):
command = None # override in subclasses
def is_installed(self):
try:
p = subprocess.Popen([self.command, '--version'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = p.communicate()
rc = p.wait()
return (rc == 0)
except OSError:
return False
def _run(self, *command):
# Windows doesn't like Unicode arguments to subprocess.Popen(), on Py2:
# https://github.com/mgedmin/check-manifest/issues/23#issuecomment-33933031
if str is bytes:
command = [s.encode(locale.getpreferredencoding()) for s in command]
print('$', ' '.join(command))
p = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, stderr = p.communicate()
rc = p.wait()
if stdout:
print(
stdout if isinstance(stdout, str) else
stdout.decode('ascii', 'backslashreplace')
)
if rc:
raise subprocess.CalledProcessError(rc, command[0], output=stdout)
class VCSMixin(object):
def setUp(self):
if not self.vcs.is_installed() and CAN_SKIP_TESTS:
self.skipTest("%s is not installed" % self.vcs.command)
self.tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest')
self.olddir = os.getcwd()
os.chdir(self.tmpdir)
def tearDown(self):
os.chdir(self.olddir)
rmtree(self.tmpdir)
def _create_file(self, filename):
assert not os.path.isabs(filename)
basedir = os.path.dirname(filename)
if basedir and not os.path.isdir(basedir):
os.makedirs(basedir)
open(filename, 'w').close()
def _create_files(self, filenames):
for filename in filenames:
self._create_file(filename)
def _init_vcs(self):
self.vcs._init_vcs()
def _add_to_vcs(self, filenames):
self.vcs._add_to_vcs(filenames)
def _commit(self):
self.vcs._commit()
def _create_and_add_to_vcs(self, filenames):
self._create_files(filenames)
self._add_to_vcs(filenames)
def test_get_vcs_files(self):
from check_manifest import get_vcs_files
self._init_vcs()
self._create_and_add_to_vcs(['a.txt', 'b/b.txt', 'b/c/d.txt'])
self._commit()
self._create_files(['b/x.txt', 'd/d.txt', 'i.txt'])
j = os.path.join
self.assertEqual(get_vcs_files(),
['a.txt', 'b', j('b', 'b.txt'), j('b', 'c'),
j('b', 'c', 'd.txt')])
def test_get_vcs_files_added_but_uncommitted(self):
from check_manifest import get_vcs_files
self._init_vcs()
self._create_and_add_to_vcs(['a.txt', 'b/b.txt', 'b/c/d.txt'])
self._create_files(['b/x.txt', 'd/d.txt', 'i.txt'])
j = os.path.join
self.assertEqual(get_vcs_files(),
['a.txt', 'b', j('b', 'b.txt'), j('b', 'c'),
j('b', 'c', 'd.txt')])
def test_get_vcs_files_deleted_but_not_removed(self):
if self.vcs.command == 'bzr':
self.skipTest("this cosmetic feature is not supported with bzr")
# see the longer explanation in test_missing_source_files
from check_manifest import get_vcs_files
self._init_vcs()
self._create_and_add_to_vcs(['a.txt'])
self._commit()
os.unlink('a.txt')
self.assertEqual(get_vcs_files(), ['a.txt'])
def test_get_vcs_files_in_a_subdir(self):
from check_manifest import get_vcs_files
self._init_vcs()
self._create_and_add_to_vcs(['a.txt', 'b/b.txt', 'b/c/d.txt'])
self._commit()
self._create_files(['b/x.txt', 'd/d.txt', 'i.txt'])
os.chdir('b')
j = os.path.join
self.assertEqual(get_vcs_files(), ['b.txt', 'c', j('c', 'd.txt')])
def test_get_vcs_files_nonascii_filenames(self):
# This test will fail if your locale is incapable of expressing
# "eacute". UTF-8 or Latin-1 should work.
from check_manifest import get_vcs_files
self._init_vcs()
# A spelling of u"\xe9.txt" that works on Python 3.2 too
filename = b'\xc3\xa9.txt'.decode('UTF-8')
self._create_and_add_to_vcs([filename])
self.assertEqual(get_vcs_files(), [filename])
def test_get_vcs_files_empty(self):
from check_manifest import get_vcs_files
self._init_vcs()
self.assertEqual(get_vcs_files(), [])
class GitHelper(VCSHelper):
command = 'git'
def _init_vcs(self):
self._run('git', 'init')
self._run('git', 'config', 'user.name', 'Unit Test')
self._run('git', 'config', 'user.email', 'test@example.com')
def _add_to_vcs(self, filenames):
# Note that we use --force to prevent errors when we want to
# add foo.egg-info and the user running the tests has
# '*.egg-info' in her global .gitignore file.
self._run('git', 'add', '--force', '--', *filenames)
def _commit(self):
self._run('git', 'commit', '-m', 'Initial')
class TestGit(VCSMixin, unittest.TestCase):
vcs = GitHelper()
def _init_repo_with_files(self, dirname, filenames):
os.mkdir(dirname)
os.chdir(dirname)
self._init_vcs()
self._create_and_add_to_vcs(filenames)
self._commit()
os.chdir(self.tmpdir)
def _add_submodule(self, repo, subdir, subrepo):
os.chdir(repo)
self.vcs._run('git', 'submodule', 'add', subrepo, subdir)
self._commit()
os.chdir(self.tmpdir)
def test_detect_git_submodule(self):
from check_manifest import detect_vcs, Failure
with self.assertRaises(Failure) as cm:
detect_vcs()
self.assertEqual(str(cm.exception),
"Couldn't find version control data"
" (git/hg/bzr/svn supported)")
# now create a .git file like in a submodule
open(os.path.join(self.tmpdir, '.git'), 'w').close()
self.assertEqual(detect_vcs().metadata_name, '.git')
def test_get_versioned_files_with_git_submodules(self):
from check_manifest import get_vcs_files
self._init_repo_with_files('repo1', ['file1', 'file2'])
self._init_repo_with_files('repo2', ['file3'])
self._init_repo_with_files('repo3', ['file4'])
self._add_submodule('repo2', 'sub3', '../repo3')
self._init_repo_with_files('main', ['file5', 'subdir/file6'])
self._add_submodule('main', 'sub1', '../repo1')
self._add_submodule('main', 'subdir/sub2', '../repo2')
os.chdir('main')
self.vcs._run('git', 'submodule', 'update', '--init', '--recursive')
self.assertEqual(
get_vcs_files(),
[fn.replace('/', os.path.sep) for fn in [
'.gitmodules',
'file5',
'sub1',
'sub1/file1',
'sub1/file2',
'subdir',
'subdir/file6',
'subdir/sub2',
'subdir/sub2/.gitmodules',
'subdir/sub2/file3',
'subdir/sub2/sub3',
'subdir/sub2/sub3/file4',
]])
class BzrHelper(VCSHelper):
command = 'bzr'
def _init_vcs(self):
self._run('bzr', 'init')
self._run('bzr', 'whoami', '--branch', 'Unit Test <test@example.com>')
def _add_to_vcs(self, filenames):
self._run('bzr', 'add', '--', *filenames)
def _commit(self):
self._run('bzr', 'commit', '-m', 'Initial')
class TestBzr(VCSMixin, unittest.TestCase):
vcs = BzrHelper()
@unittest.skipIf(HAS_OEM_CODEC,
"Python 3.6 lets us use 'oem' codec instead of guessing")
class TestBzrTerminalCharsetDetectionOnOldPythons(unittest.TestCase):
@mock.patch('sys.stdin')
@mock.patch('sys.stdout')
def test_terminal_encoding_not_known(self, mock_stdout, mock_stdin):
from check_manifest import Bazaar
mock_stdout.encoding = None
mock_stdin.encoding = None
self.assertEqual(Bazaar._get_terminal_encoding(), None)
@mock.patch('sys.stdout')
def test_terminal_encoding_stdout_known(self, mock_stdout):
from check_manifest import Bazaar
mock_stdout.encoding = 'UTF-8'
self.assertEqual(Bazaar._get_terminal_encoding(), 'UTF-8')
@mock.patch('sys.stdin')
@mock.patch('sys.stdout')
def test_terminal_encoding_stdin_known(self, mock_stdout, mock_stdin):
from check_manifest import Bazaar
mock_stdout.encoding = None
mock_stdin.encoding = 'UTF-8'
self.assertEqual(Bazaar._get_terminal_encoding(), 'UTF-8')
@mock.patch('sys.stdout')
def test_terminal_encoding_cp0(self, mock_stdout):
from check_manifest import Bazaar
mock_stdout.encoding = 'cp0'
self.assertEqual(Bazaar._get_terminal_encoding(), None)
@unittest.skipIf(not HAS_OEM_CODEC,
"'oem' codec not available on Python before 3.6")
class TestBzrTerminalCharsetDetectionOnNewPythons(unittest.TestCase):
def test_terminal_encoding_cp0(self):
from check_manifest import Bazaar
self.assertEqual(Bazaar._get_terminal_encoding(), "oem")
class HgHelper(VCSHelper):
command = 'hg'
def _init_vcs(self):
self._run('hg', 'init')
with open('.hg/hgrc', 'a') as f:
f.write('\n[ui]\nusername = Unit Test <test@example.com\n')
def _add_to_vcs(self, filenames):
self._run('hg', 'add', '--', *filenames)
def _commit(self):
self._run('hg', 'commit', '-m', 'Initial')
class TestHg(VCSMixin, unittest.TestCase):
vcs = HgHelper()
class SvnHelper(VCSHelper):
command = 'svn'
def _init_vcs(self):
self._run('svnadmin', 'create', 'repo')
self._run('svn', 'co', 'file:///' + os.path.abspath('repo').replace(os.path.sep, '/'), 'checkout')
os.chdir('checkout')
def _add_to_vcs(self, filenames):
from check_manifest import add_directories
self._run('svn', 'add', '-N', '--', *add_directories(filenames))
def _commit(self):
self._run('svn', 'commit', '-m', 'Initial')
class TestSvn(VCSMixin, unittest.TestCase):
vcs = SvnHelper()
def test_svn_externals(self):
from check_manifest import get_vcs_files
self.vcs._run('svnadmin', 'create', 'repo2')
repo2_url = 'file:///' + os.path.abspath('repo2').replace(os.path.sep, '/')
self.vcs._init_vcs()
self.vcs._run('svn', 'propset', 'svn:externals', 'ext %s' % repo2_url, '.')
self.vcs._run('svn', 'up')
self._create_files(['a.txt', 'ext/b.txt'])
self.vcs._run('svn', 'add', 'a.txt', 'ext/b.txt')
j = os.path.join
self.assertEqual(get_vcs_files(),
['a.txt', 'ext', j('ext', 'b.txt')])
class UIMixin(object):
def setUp(self):
import check_manifest
self.old_VERBOSE = check_manifest.VERBOSE
self.real_stdout = sys.stdout
self.real_stderr = sys.stderr
sys.stdout = StringIO()
sys.stderr = StringIO()
def tearDown(self):
import check_manifest
sys.stderr = self.real_stderr
sys.stdout = self.real_stdout
check_manifest.VERBOSE = self.old_VERBOSE
class TestSvnExtraErrors(UIMixin, unittest.TestCase):
def test_svn_xml_parsing_warning(self):
from check_manifest import Subversion
entry = ET.XML('<entry path="foo/bar.txt"></entry>')
self.assertFalse(Subversion.is_interesting(entry))
self.assertEqual(
sys.stderr.getvalue(),
'svn status --xml parse error: <entry path="foo/bar.txt">'
' without <wc-status>\n')
class TestUserInterface(UIMixin, unittest.TestCase):
def test_info(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.info("Reticulating splines")
self.assertEqual(sys.stdout.getvalue(),
"Reticulating splines\n")
def test_info_verbose(self):
import check_manifest
check_manifest.VERBOSE = True
check_manifest.info("Reticulating splines")
self.assertEqual(sys.stdout.getvalue(),
"Reticulating splines\n")
def test_info_begin_continue_end(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.info_begin("Reticulating splines...")
check_manifest.info_continue(" nearly done...")
check_manifest.info_continue(" almost done...")
check_manifest.info_end(" done!")
self.assertEqual(sys.stdout.getvalue(), "")
def test_info_begin_continue_end_verbose(self):
import check_manifest
check_manifest.VERBOSE = True
check_manifest.info_begin("Reticulating splines...")
check_manifest.info_continue(" nearly done...")
check_manifest.info_continue(" almost done...")
check_manifest.info_end(" done!")
self.assertEqual(
sys.stdout.getvalue(),
"Reticulating splines... nearly done... almost done... done!\n")
def test_info_emits_newline_when_needed(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.info_begin("Computering...")
check_manifest.info("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
"Forgot to turn the gas off!\n")
def test_info_emits_newline_when_needed_verbose(self):
import check_manifest
check_manifest.VERBOSE = True
check_manifest.info_begin("Computering...")
check_manifest.info("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
"Computering...\n"
"Forgot to turn the gas off!\n")
def test_warning(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.info_begin("Computering...")
check_manifest.warning("Forgot to turn the gas off!")
self.assertEqual(sys.stdout.getvalue(), "")
self.assertEqual(
sys.stderr.getvalue(),
"Forgot to turn the gas off!\n")
def test_warning_verbose(self):
import check_manifest
check_manifest.VERBOSE = True
check_manifest.info_begin("Computering...")
check_manifest.warning("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
"Computering...\n")
self.assertEqual(
sys.stderr.getvalue(),
"Forgot to turn the gas off!\n")
def test_error(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.info_begin("Computering...")
check_manifest.error("Forgot to turn the gas off!")
self.assertEqual(sys.stdout.getvalue(), "")
self.assertEqual(
sys.stderr.getvalue(),
"Forgot to turn the gas off!\n")
def test_error_verbose(self):
import check_manifest
check_manifest.VERBOSE = True
check_manifest.info_begin("Computering...")
check_manifest.error("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
"Computering...\n")
self.assertEqual(
sys.stderr.getvalue(),
"Forgot to turn the gas off!\n")
def pick_installed_vcs():
preferred_order = [GitHelper, HgHelper, BzrHelper, SvnHelper]
force = os.getenv('FORCE_TEST_VCS')
if force:
for cls in preferred_order:
if force == cls.command:
return cls()
raise ValueError('Unsupported FORCE_TEST_VCS=%s (supported: %s)'
% (force, '/'.join(cls.command for cls in preferred_order)))
for cls in preferred_order:
vcs = cls()
if vcs.is_installed():
return vcs
return None
class TestCheckManifest(unittest.TestCase):
_vcs = pick_installed_vcs()
def setUp(self):
if self._vcs is None:
self.fail('at least one version control system should be installed')
self.oldpwd = os.getcwd()
self.tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest')
os.chdir(self.tmpdir)
self._stdout_patcher = mock.patch('sys.stdout', StringIO())
self._stdout_patcher.start()
self._stderr_patcher = mock.patch('sys.stderr', StringIO())
self._stderr_patcher.start()
def tearDown(self):
self._stderr_patcher.stop()
self._stdout_patcher.stop()
os.chdir(self.oldpwd)
rmtree(self.tmpdir)
def _create_repo_with_code(self, add_to_vcs=True):
self._vcs._init_vcs()
with open('setup.py', 'w') as f:
f.write("from setuptools import setup\n")
f.write("setup(name='sample', py_modules=['sample'])\n")
with open('sample.py', 'w') as f:
f.write("# wow. such code. so amaze\n")
if add_to_vcs:
self._vcs._add_to_vcs(['setup.py', 'sample.py'])
def _create_repo_with_code_in_subdir(self):
os.mkdir('subdir')
os.chdir('subdir')
self._create_repo_with_code()
# NB: when self._vcs is SvnHelper, we're actually in
# ./subdir/checkout rather than in ./subdir
subdir = os.path.basename(os.getcwd())
os.chdir(os.pardir)
return subdir
def _add_to_vcs(self, filename, content=''):
if os.path.sep in filename and not os.path.isdir(os.path.dirname(filename)):
os.makedirs(os.path.dirname(filename))
with open(filename, 'w') as f:
f.write(content)
self._vcs._add_to_vcs([filename])
def test_not_python_project(self):
from check_manifest import check_manifest, Failure
with self.assertRaises(Failure) as cm:
check_manifest()
self.assertEqual(
str(cm.exception),
"This is not a Python project (no setup.py/pyproject.toml).")
def test_forgot_to_git_add_anything(self):
from check_manifest import check_manifest, Failure
self._create_repo_with_code(add_to_vcs=False)
with self.assertRaises(Failure) as cm:
check_manifest()
self.assertEqual(str(cm.exception),
"There are no files added to version control!")
def test_all_is_well(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self.assertTrue(check_manifest(), sys.stderr.getvalue())
def test_relative_pathname(self):
from check_manifest import check_manifest
subdir = self._create_repo_with_code_in_subdir()
self.assertTrue(check_manifest(subdir), sys.stderr.getvalue())
def test_relative_python(self):
# https://github.com/mgedmin/check-manifest/issues/36
from check_manifest import check_manifest
subdir = self._create_repo_with_code_in_subdir()
python = os.path.relpath(sys.executable)
self.assertTrue(check_manifest(subdir, python=python),
sys.stderr.getvalue())
def test_python_from_path(self):
# https://github.com/mgedmin/check-manifest/issues/57
from check_manifest import check_manifest
# We need a Python interpeter to be in PATH.
python = 'python'
if hasattr(shutil, 'which'):
for python in 'python', 'python3', os.path.basename(sys.executable):
if shutil.which(python):
break
subdir = self._create_repo_with_code_in_subdir()
self.assertTrue(check_manifest(subdir, python=python),
sys.stderr.getvalue())
def test_suggestions(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('unrelated.txt')
self.assertFalse(check_manifest())
self.assertIn("missing from sdist:\n unrelated.txt",
sys.stderr.getvalue())
self.assertIn("suggested MANIFEST.in rules:\n include *.txt",
sys.stdout.getvalue())
def test_suggestions_create(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('unrelated.txt')
self.assertFalse(check_manifest(create=True))
self.assertIn("missing from sdist:\n unrelated.txt",
sys.stderr.getvalue())
self.assertIn("suggested MANIFEST.in rules:\n include *.txt",
sys.stdout.getvalue())
self.assertIn("creating MANIFEST.in",
sys.stdout.getvalue())
with open('MANIFEST.in') as f:
self.assertEqual(f.read(), "include *.txt\n")
def test_suggestions_update(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('unrelated.txt')
self._add_to_vcs('MANIFEST.in', '#tbd')
self.assertFalse(check_manifest(update=True))
self.assertIn("missing from sdist:\n unrelated.txt",
sys.stderr.getvalue())
self.assertIn("suggested MANIFEST.in rules:\n include *.txt",
sys.stdout.getvalue())
self.assertIn("updating MANIFEST.in",
sys.stdout.getvalue())
with open('MANIFEST.in') as f:
self.assertEqual(
f.read(),
"#tbd\n# added by check_manifest.py\ninclude *.txt\n")
def test_suggestions_all_unknown_patterns(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('.dunno-what-to-do-with-this')
self.assertFalse(check_manifest(update=True))
self.assertIn("missing from sdist:\n .dunno-what-to-do-with-this",
sys.stderr.getvalue())
self.assertIn(
"don't know how to come up with rules matching any of the files, sorry!",
sys.stdout.getvalue())
def test_suggestions_some_unknown_patterns(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('.dunno-what-to-do-with-this')
self._add_to_vcs('unrelated.txt')
self.assertFalse(check_manifest(update=True))
self.assertIn(
"don't know how to come up with rules matching\n .dunno-what-to-do-with-this",
sys.stdout.getvalue())
self.assertIn("creating MANIFEST.in",
sys.stdout.getvalue())
with open('MANIFEST.in') as f:
self.assertEqual(f.read(), "include *.txt\n")
def test_MANIFEST_in_does_not_need_to_be_added_to_be_considered(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('unrelated.txt')
with open('MANIFEST.in', 'w') as f:
f.write("include *.txt\n")
self.assertFalse(check_manifest())
self.assertIn("missing from VCS:\n MANIFEST.in", sys.stderr.getvalue())
self.assertNotIn("missing from sdist", sys.stderr.getvalue())
def test_setup_py_does_not_need_to_be_added_to_be_considered(self):
from check_manifest import check_manifest
self._create_repo_with_code(add_to_vcs=False)
self._add_to_vcs('sample.py')
self.assertFalse(check_manifest())
self.assertIn("missing from VCS:\n setup.py", sys.stderr.getvalue())
self.assertNotIn("missing from sdist", sys.stderr.getvalue())
def test_bad_ideas(self):
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('foo.egg-info')
self._add_to_vcs('moo.mo')
self.assertFalse(check_manifest())
self.assertIn("you have foo.egg-info in source control!",
sys.stderr.getvalue())
self.assertIn("this also applies to the following:\n moo.mo",
sys.stderr.getvalue())
def test_ignore_bad_ideas(self):
from check_manifest import check_manifest
self._create_repo_with_code()
with open('setup.cfg', 'w') as f:
f.write('[check-manifest]\n'
'ignore =\n'
' subdir/bar.egg-info\n'
'ignore-bad-ideas =\n'
' *.mo\n'
' subdir/bar.egg-info\n')
self._add_to_vcs('foo.egg-info')
self._add_to_vcs('moo.mo')
self._add_to_vcs(os.path.join('subdir', 'bar.egg-info'))
self.assertFalse(check_manifest())
self.assertIn("you have foo.egg-info in source control!",
sys.stderr.getvalue())
self.assertNotIn("moo.mo", sys.stderr.getvalue())
self.assertNotIn("bar.egg-info", sys.stderr.getvalue())
def test_missing_source_files(self):
# https://github.com/mgedmin/check-manifest/issues/32
from check_manifest import check_manifest
self._create_repo_with_code()
self._add_to_vcs('missing.py')
os.unlink('missing.py')
check_manifest()
if self._vcs.command != 'bzr':
# 'bzr ls' doesn't list files that were deleted but not
# marked for deletion. 'bzr st' does, but it doesn't list
# unmodified files. Importing bzrlib and using the API to
# get the file list we need is (a) complicated, (b) opens
# the optional dependency can of worms, and (c) not viable
# under Python 3 unless we fork off a Python 2 subprocess.
# Manually combining 'bzr ls' and 'bzr st' outputs just to
# produce a cosmetic warning message seems like overkill.
self.assertIn("some files listed as being under source control are missing:\n missing.py",
sys.stderr.getvalue())
def test_suite():
return unittest.TestSuite([
unittest.defaultTestLoader.loadTestsFromName(__name__),
doctest.DocTestSuite('check_manifest'),
])