Browse Source

Refactor UI code to avoid global state

pull/114/head
Marius Gedminas 2 years ago
parent
commit
c7148aaf44
  1. 1
      .coveragerc
  2. 266
      check_manifest.py
  3. 285
      tests.py

1
.coveragerc

@ -7,3 +7,4 @@ exclude_lines =
except ImportError:
if __name__ == '__main__':
if sys.platform == 'darwin':
raise NotImplementedError

266
check_manifest.py

@ -9,7 +9,7 @@ supported).
Since the first check can fail to catch missing MANIFEST.in entries when
you've got the right setuptools version control system support plugins
installed, the script copies all the versioned files into a temporary
directory and building the source distribution again. This also avoids issues
directory and builds the source distribution again. This also avoids issues
with stale egg-info/SOURCES.txt files that may cause files not mentioned in
MANIFEST.in to be included nevertheless.
"""
@ -63,59 +63,59 @@ class Failure(Exception):
# User interface
#
VERBOSE = False
QUIET = False
_to_be_continued = False
def _check_tbc():
global _to_be_continued
if _to_be_continued:
print()
_to_be_continued = False
def info(message):
if QUIET:
return
_check_tbc()
print(message)
def info_begin(message):
if not VERBOSE:
return
_check_tbc()
sys.stdout.write(message)
sys.stdout.flush()
global _to_be_continued
_to_be_continued = True
def info_continue(message):
if not VERBOSE:
return
sys.stdout.write(message)
sys.stdout.flush()
global _to_be_continued
_to_be_continued = True
def info_end(message):
if not VERBOSE:
return
print(message)
global _to_be_continued
_to_be_continued = False
def error(message):
_check_tbc()
print(message, file=sys.stderr)
def warning(message):
_check_tbc()
print(message, file=sys.stderr)
class UI(object):
def __init__(self, verbosity=1):
self.verbosity = verbosity
self._to_be_continued = False
self.stdout = sys.stdout
self.stderr = sys.stderr
@property
def quiet(self):
return self.verbosity < 1
@property
def verbose(self):
return self.verbosity >= 2
def _check_tbc(self):
if self._to_be_continued:
print(file=self.stdout)
self._to_be_continued = False
def info(self, message):
if self.quiet:
return
self._check_tbc()
print(message, file=self.stdout)
def info_begin(self, message):
if not self.verbose:
return
self._check_tbc()
print(message, end="", file=self.stdout)
self._to_be_continued = True
def info_continue(self, message):
if not self.verbose:
return
print(message, end="", file=self.stdout)
self._to_be_continued = True
def info_end(self, message):
if not self.verbose:
return
print(message, file=self.stdout)
self._to_be_continued = False
def error(self, message):
self._check_tbc()
print(message, file=self.stderr)
def warning(self, message):
self._check_tbc()
print(message, file=self.stderr)
def format_list(list_of_strings):
@ -328,10 +328,16 @@ def add_prefix_to_each(prefix, filelist):
class VCS(object):
def __init__(self, ui):
self.ui = ui
@classmethod
def detect(cls, location):
return os.path.isdir(os.path.join(location, cls.metadata_name))
def get_versioned_files(self):
raise NotImplementedError('this is an abstract method')
class Git(VCS):
metadata_name = '.git'
@ -345,14 +351,13 @@ class Git(VCS):
# .git can be a file for submodules
return os.path.exists(os.path.join(location, cls.metadata_name))
@classmethod
def get_versioned_files(cls):
def get_versioned_files(self):
"""List all files versioned by git in the current directory."""
files = cls._git_ls_files()
submodules = cls._list_submodules()
files = self._git_ls_files()
submodules = self._list_submodules()
for subdir in submodules:
subdir = os.path.relpath(subdir).replace(os.path.sep, '/')
files += add_prefix_to_each(subdir, cls._git_ls_files(subdir))
files += add_prefix_to_each(subdir, self._git_ls_files(subdir))
return add_directories(files)
@classmethod
@ -377,8 +382,7 @@ class Git(VCS):
class Mercurial(VCS):
metadata_name = '.hg'
@staticmethod
def get_versioned_files():
def get_versioned_files(self):
"""List all files under Mercurial control in the current directory."""
output = run(['hg', 'status', '-ncamd', '.'])
return add_directories(output.splitlines())
@ -412,10 +416,9 @@ class Bazaar(VCS):
# first, since I don't have a Mac OS X machine and cannot test.
return encoding
@classmethod
def get_versioned_files(cls):
def get_versioned_files(self):
"""List all files versioned in Bazaar in the current directory."""
encoding = cls._get_terminal_encoding()
encoding = self._get_terminal_encoding()
output = run(['bzr', 'ls', '-VR'], encoding=encoding)
return output.splitlines()
@ -423,16 +426,14 @@ class Bazaar(VCS):
class Subversion(VCS):
metadata_name = '.svn'
@classmethod
def get_versioned_files(cls):
def get_versioned_files(self):
"""List all files under SVN control in the current directory."""
output = run(['svn', 'st', '-vq', '--xml'], decode=False)
tree = ET.XML(output)
return sorted(entry.get('path') for entry in tree.findall('.//entry')
if cls.is_interesting(entry))
if self.is_interesting(entry))
@staticmethod
def is_interesting(entry):
def is_interesting(self, entry):
"""Is this entry interesting?
``entry`` is an XML node representing one entry of the svn status
@ -464,8 +465,10 @@ class Subversion(VCS):
return False
status = entry.find('wc-status')
if status is None:
warning('svn status --xml parse error: <entry path="%s"> without'
' <wc-status>' % entry.get('path'))
self.ui.warning(
'svn status --xml parse error: <entry path="%s"> without'
' <wc-status>' % entry.get('path')
)
return False
# For SVN externals we get two entries: one mentioning the
# existence of the external, and one about the status of the external.
@ -474,13 +477,13 @@ class Subversion(VCS):
return True
def detect_vcs():
def detect_vcs(ui):
"""Detect the version control system used for the current directory."""
location = os.path.abspath('.')
while True:
for vcs in Git, Mercurial, Bazaar, Subversion:
if vcs.detect(location):
return vcs
return vcs(ui)
parent = os.path.dirname(location)
if parent == location:
raise Failure("Couldn't find version control data"
@ -488,9 +491,9 @@ def detect_vcs():
location = parent
def get_vcs_files():
def get_vcs_files(ui):
"""List all files under version control in the current directory."""
vcs = detect_vcs()
vcs = detect_vcs(ui)
return normalize_names(vcs.get_versioned_files())
@ -658,7 +661,7 @@ def _load_config():
return {}
def read_manifest():
def read_manifest(ui):
"""Read existing configuration from MANIFEST.in.
We use that to ignore anything the MANIFEST.in ignores.
@ -666,7 +669,7 @@ def read_manifest():
# XXX modifies global state, which is kind of evil
if not os.path.isfile('MANIFEST.in'):
return
ignore, ignore_regexps = _get_ignore_from_manifest('MANIFEST.in')
ignore, ignore_regexps = _get_ignore_from_manifest('MANIFEST.in', ui)
IGNORE.extend(ignore)
IGNORE_REGEXPS.extend(ignore_regexps)
@ -681,7 +684,7 @@ def _glob_to_regexp(pat):
return glob_to_re(pat)
def _get_ignore_from_manifest(filename):
def _get_ignore_from_manifest(filename, ui):
"""Gather the various ignore patterns from a MANIFEST.in.
Returns a list of standard ignore patterns and a list of regular
@ -694,7 +697,7 @@ def _get_ignore_from_manifest(filename):
raise Failure(self.gen_error(msg, line))
def warn(self, msg, line=None):
warning(self.gen_error(msg, line))
ui.warning(self.gen_error(msg, line))
template = MyTextFile(filename,
strip_comments=True,
@ -707,10 +710,10 @@ def _get_ignore_from_manifest(filename):
lines = template.readlines()
finally:
template.close()
return _get_ignore_from_manifest_lines(lines)
return _get_ignore_from_manifest_lines(lines, ui)
def _get_ignore_from_manifest_lines(lines):
def _get_ignore_from_manifest_lines(lines, ui):
"""Gather the various ignore patterns from a MANIFEST.in.
'lines' should be a list of strings with comments removed
@ -730,9 +733,9 @@ def _get_ignore_from_manifest_lines(lines):
for part in rest.split():
# distutils enforces these warnings on Windows only
if part.startswith('/'):
warning("ERROR: Leading slashes are not allowed in MANIFEST.in on Windows: %s" % part)
ui.warning("ERROR: Leading slashes are not allowed in MANIFEST.in on Windows: %s" % part)
if part.endswith('/'):
warning("ERROR: Trailing slashes are not allowed in MANIFEST.in on Windows: %s" % part)
ui.warning("ERROR: Trailing slashes are not allowed in MANIFEST.in on Windows: %s" % part)
if cmd == 'exclude':
# An exclude of 'dirname/*css' can match 'dirname/foo.css'
# but not 'dirname/subdir/bar.css'. We need a regular
@ -751,9 +754,11 @@ def _get_ignore_from_manifest_lines(lines):
dirname, patterns = rest.split(None, 1)
except ValueError:
# Wrong MANIFEST.in line.
warning("You have a wrong line in MANIFEST.in: %r\n"
"'recursive-exclude' expects <dir> <pattern1> "
"<pattern2> ..." % line)
ui.warning(
"You have a wrong line in MANIFEST.in: %r\n"
"'recursive-exclude' expects <dir> <pattern1> <pattern2>..."
% line
)
continue
# Strip path separator for clarity.
dirname = dirname.rstrip(os.path.sep)
@ -886,11 +891,13 @@ def build_sdist(tempdir, python=sys.executable):
def check_manifest(source_tree='.', create=False, update=False,
python=sys.executable):
python=sys.executable, ui=None):
"""Compare a generated source distribution with list of files in a VCS.
Returns True if the manifest is fine.
"""
if ui is None:
ui = UI()
all_ok = True
if os.path.sep in python:
python = os.path.abspath(python)
@ -899,28 +906,28 @@ def check_manifest(source_tree='.', create=False, update=False,
raise Failure(
'This is not a Python project (no setup.py/pyproject.toml).')
read_config()
read_manifest()
info_begin("listing source files under version control")
all_source_files = sorted(get_vcs_files())
read_manifest(ui)
ui.info_begin("listing source files under version control")
all_source_files = sorted(get_vcs_files(ui))
source_files = strip_sdist_extras(all_source_files)
info_continue(": %d files and directories" % len(source_files))
ui.info_continue(": %d files and directories" % len(source_files))
if not all_source_files:
raise Failure('There are no files added to version control!')
info_begin("building an sdist")
ui.info_begin("building an sdist")
with mkdtemp('-sdist') as tempdir:
build_sdist(tempdir, python=python)
sdist_filename = get_one_file_in(tempdir)
info_continue(": %s" % os.path.basename(sdist_filename))
ui.info_continue(": %s" % os.path.basename(sdist_filename))
sdist_files = sorted(normalize_names(strip_sdist_extras(
strip_toplevel_name(get_archive_file_list(sdist_filename)))))
info_continue(": %d files and directories" % len(sdist_files))
ui.info_continue(": %d files and directories" % len(sdist_files))
version = extract_version_from_filename(sdist_filename)
existing_source_files = list(filter(os.path.exists, all_source_files))
missing_source_files = sorted(set(all_source_files) - set(existing_source_files))
if missing_source_files:
warning("some files listed as being under source control are missing:\n%s"
% format_list(missing_source_files))
info_begin("copying source files to a temporary directory")
ui.warning("some files listed as being under source control are missing:\n%s"
% format_list(missing_source_files))
ui.info_begin("copying source files to a temporary directory")
with mkdtemp('-sources') as tempsourcedir:
copy_files(existing_source_files, tempsourcedir)
for filename in 'MANIFEST.in', 'setup.py', 'pyproject.toml':
@ -931,61 +938,60 @@ def check_manifest(source_tree='.', create=False, update=False,
# missing from source control; if we don't do this,
# things get very confusing for the user!
copy_files([filename], tempsourcedir)
info_begin("building a clean sdist")
ui.info_begin("building a clean sdist")
with cd(tempsourcedir):
with mkdtemp('-sdist') as tempdir:
os.environ['SETUPTOOLS_SCM_PRETEND_VERSION'] = version
build_sdist(tempdir, python=python)
sdist_filename = get_one_file_in(tempdir)
info_continue(": %s" % os.path.basename(sdist_filename))
ui.info_continue(": %s" % os.path.basename(sdist_filename))
clean_sdist_files = sorted(normalize_names(strip_sdist_extras(
strip_toplevel_name(get_archive_file_list(sdist_filename)))))
info_continue(": %d files and directories" % len(clean_sdist_files))
ui.info_continue(": %d files and directories" % len(clean_sdist_files))
missing_from_manifest = set(source_files) - set(clean_sdist_files)
missing_from_VCS = set(sdist_files + clean_sdist_files) - set(source_files)
if not missing_from_manifest and not missing_from_VCS:
info("lists of files in version control and sdist match")
ui.info("lists of files in version control and sdist match")
else:
error("lists of files in version control and sdist do not match!\n%s"
% format_missing(missing_from_VCS, missing_from_manifest,
"VCS", "sdist"))
ui.error(
"lists of files in version control and sdist do not match!\n%s"
% format_missing(missing_from_VCS, missing_from_manifest, "VCS", "sdist"))
suggestions, unknowns = find_suggestions(missing_from_manifest)
user_asked_for_help = update or (create and not
os.path.exists('MANIFEST.in'))
if 'MANIFEST.in' not in existing_source_files:
if suggestions and not user_asked_for_help:
info("no MANIFEST.in found; you can run 'check-manifest -c' to create one")
ui.info("no MANIFEST.in found; you can run 'check-manifest -c' to create one")
else:
info("no MANIFEST.in found")
ui.info("no MANIFEST.in found")
if suggestions:
info("suggested MANIFEST.in rules:\n%s"
% format_list(suggestions))
ui.info("suggested MANIFEST.in rules:\n%s" % format_list(suggestions))
if user_asked_for_help:
existed = os.path.exists('MANIFEST.in')
with open('MANIFEST.in', 'a') as f:
if not existed:
info("creating MANIFEST.in")
ui.info("creating MANIFEST.in")
else:
info("updating MANIFEST.in")
ui.info("updating MANIFEST.in")
f.write('\n# added by check_manifest.py\n')
f.write('\n'.join(suggestions) + '\n')
if unknowns:
info("don't know how to come up with rules matching\n%s"
% format_list(unknowns))
ui.info("don't know how to come up with rules matching\n%s"
% format_list(unknowns))
elif user_asked_for_help:
info("don't know how to come up with rules"
" matching any of the files, sorry!")
ui.info("don't know how to come up with rules matching any of the files, sorry!")
all_ok = False
bad_ideas = find_bad_ideas(all_source_files)
filtered_bad_ideas = [bad_idea for bad_idea in bad_ideas
if not file_matches(bad_idea, IGNORE_BAD_IDEAS)]
if filtered_bad_ideas:
warning("you have %s in source control!\nthat's a bad idea:"
" auto-generated files should not be versioned"
% filtered_bad_ideas[0])
ui.warning(
"you have %s in source control!\n"
"that's a bad idea: auto-generated files should not be versioned"
% filtered_bad_ideas[0])
if len(filtered_bad_ideas) > 1:
warning("this also applies to the following:\n%s"
% format_list(filtered_bad_ideas[1:]))
ui.warning("this also applies to the following:\n%s"
% format_list(filtered_bad_ideas[1:]))
all_ok = False
return all_ok
@ -1026,20 +1032,15 @@ def main():
if args.ignore_bad_ideas:
IGNORE_BAD_IDEAS.extend(args.ignore_bad_ideas.split(','))
verbosity = args.quiet + args.verbose
if verbosity >= 2:
global VERBOSE
VERBOSE = True
if not verbosity:
global QUIET
QUIET = True
ui = UI(verbosity=args.quiet + args.verbose)
try:
if not check_manifest(args.source_tree, create=args.create,
update=args.update, python=args.python):
update=args.update, python=args.python,
ui=ui):
sys.exit(1)
except Failure as e:
error(str(e))
ui.error(str(e))
sys.exit(2)
@ -1062,14 +1063,15 @@ def zest_releaser_check(data):
return
if not ask("Do you want to run check-manifest?"):
return
ui = UI()
try:
if not check_manifest(source_tree):
if not ask("MANIFEST.in has problems. "
if not check_manifest(source_tree, ui=ui):
if not ask("MANIFEST.in has problems."
" Do you want to continue despite that?", default=False):
sys.exit(1)
except Failure as e:
error(str(e))
if not ask("Something bad happened. "
ui.error(str(e))
if not ask("Something bad happened."
" Do you want to continue despite that?", default=False):
sys.exit(2)

285
tests.py

@ -13,6 +13,7 @@ import textwrap
import unittest
import zipfile
from contextlib import closing
from functools import partial
from io import BytesIO
from xml.etree import ElementTree as ET
@ -38,17 +39,36 @@ else:
HAS_OEM_CODEC = True
class Tests(unittest.TestCase):
class MockUI(object):
def setUp(self):
import check_manifest
def __init__(self, verbosity=1):
self.verbosity = verbosity
self.warnings = []
self._real_warning = check_manifest.warning
check_manifest.warning = self.warnings.append
self.errors = []
def tearDown(self):
import check_manifest
check_manifest.warning = self._real_warning
def info(self, message):
pass
def info_begin(self, message):
pass
def info_cont(self, message):
pass
def info_end(self, message):
pass
def warning(self, message):
self.warnings.append(message)
def error(self, message):
self.errors.append(message)
class Tests(unittest.TestCase):
def setUp(self):
self.ui = MockUI()
def make_temp_dir(self):
tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest')
@ -251,10 +271,11 @@ class Tests(unittest.TestCase):
def test_detect_vcs_no_vcs(self):
from check_manifest import detect_vcs, Failure
ui = MockUI()
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()
detect_vcs(ui)
self.assertEqual(str(cm.exception),
"Couldn't find version control data"
" (git/hg/bzr/svn supported)")
@ -324,7 +345,7 @@ class Tests(unittest.TestCase):
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
from check_manifest import _get_ignore_from_manifest_lines
orig_ignore = check_manifest.IGNORE[:]
orig_ignore_regexps = check_manifest.IGNORE_REGEXPS[:]
manifest_in = textwrap.dedent("""
@ -374,7 +395,7 @@ class Tests(unittest.TestCase):
# This will change the definitions.
try:
# This is normally done in read_manifest:
ignore, ignore_regexps = parse(manifest_in.splitlines())
ignore, ignore_regexps = _get_ignore_from_manifest_lines(manifest_in.splitlines(), self.ui)
check_manifest.IGNORE.extend(ignore)
check_manifest.IGNORE_REGEXPS.extend(ignore_regexps)
# Filter the file list.
@ -492,9 +513,10 @@ class Tests(unittest.TestCase):
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 _get_ignore_from_manifest_lines
from check_manifest import _glob_to_regexp as g2r
j = os.path.join
parse = partial(_get_ignore_from_manifest_lines, ui=self.ui)
# The return value is a tuple with two lists:
# ([<list of filename ignores>], [<list of regular expressions>])
self.assertEqual(parse([]),
@ -570,7 +592,7 @@ class Tests(unittest.TestCase):
]))
def test_get_ignore_from_manifest(self):
from check_manifest import _get_ignore_from_manifest as parse
from check_manifest import _get_ignore_from_manifest
filename = os.path.join(self.make_temp_dir(), 'MANIFEST.in')
self.create_file(filename, textwrap.dedent('''
exclude \\
@ -580,18 +602,20 @@ class Tests(unittest.TestCase):
# https://github.com/mgedmin/check-manifest/issues/66
# docs/ folder
'''))
self.assertEqual(parse(filename), (['test.dat'], []))
self.assertEqual(self.warnings, [])
ui = MockUI()
self.assertEqual(_get_ignore_from_manifest(filename, ui), (['test.dat'], []))
self.assertEqual(ui.warnings, [])
def test_get_ignore_from_manifest_warnings(self):
from check_manifest import _get_ignore_from_manifest as parse
from check_manifest import _get_ignore_from_manifest
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, [
ui = MockUI()
self.assertEqual(_get_ignore_from_manifest(filename, ui), (['test.dat'], []))
self.assertEqual(ui.warnings, [
"%s, line 2: continuation line immediately precedes end-of-file" % filename,
])
@ -672,6 +696,7 @@ class TestConfiguration(unittest.TestCase):
check_manifest.IGNORE = ['default-ignore-rules']
check_manifest.IGNORE_REGEXPS = ['default-ignore-regexps']
check_manifest.IGNORE_BAD_IDEAS = []
self.ui = MockUI()
def tearDown(self):
import check_manifest
@ -768,7 +793,7 @@ class TestConfiguration(unittest.TestCase):
def test_read_manifest_no_manifest(self):
import check_manifest
check_manifest.read_manifest()
check_manifest.read_manifest(self.ui)
self.assertEqual(check_manifest.IGNORE, ['default-ignore-rules'])
def test_read_manifest(self):
@ -777,7 +802,7 @@ class TestConfiguration(unittest.TestCase):
with open('MANIFEST.in', 'w') as f:
f.write('exclude *.gif\n')
f.write('global-exclude *.png\n')
check_manifest.read_manifest()
check_manifest.read_manifest(self.ui)
self.assertEqual(check_manifest.IGNORE,
['default-ignore-rules', '*.png'])
self.assertEqual(check_manifest.IGNORE_REGEXPS,
@ -792,8 +817,9 @@ class TestMain(unittest.TestCase):
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.ui = MockUI()
self._ui_patcher = mock.patch('check_manifest.UI', self._make_ui)
self._ui_patcher.start()
self._orig_sys_argv = sys.argv
sys.argv = ['check-manifest']
self.OLD_IGNORE = check_manifest.IGNORE
@ -811,7 +837,11 @@ class TestMain(unittest.TestCase):
sys.argv = self._orig_sys_argv
self._se_patcher.stop()
self._cm_patcher.stop()
self._error_patcher.stop()
self._ui_patcher.stop()
def _make_ui(self, verbosity):
self.ui.verbosity = verbosity
return self.ui
def test(self):
from check_manifest import main
@ -828,7 +858,7 @@ class TestMain(unittest.TestCase):
from check_manifest import main, Failure
self._check_manifest.side_effect = Failure('msg')
main()
self._error.assert_called_with('msg')
self.assertEqual(self.ui.errors, ['msg'])
self._sys_exit.assert_called_with(2)
def test_extra_ignore_args(self):
@ -847,31 +877,24 @@ class TestMain(unittest.TestCase):
def test_verbose_arg(self):
import check_manifest
check_manifest.VERBOSE = False
sys.argv.append('--verbose')
check_manifest.main()
self.assertTrue(check_manifest.VERBOSE)
check_manifest.VERBOSE = False
self.assertEqual(self.ui.verbosity, 2)
def test_quiet_arg(self):
import check_manifest
check_manifest.QUIET = False
sys.argv.append('--quiet')
check_manifest.main()
self.assertTrue(check_manifest.QUIET)
check_manifest.QUIET = False
self.assertEqual(self.ui.verbosity, 0)
def test_verbose_and_quiet_arg(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.QUIET = False
sys.argv.append('--verbose')
sys.argv.append('--quiet')
check_manifest.main()
self.assertFalse(check_manifest.VERBOSE)
self.assertFalse(check_manifest.QUIET)
check_manifest.VERBOSE = False
check_manifest.QUIET = False
# the two arguments cancel each other out:
# 1 (default verbosity) + 1 - 1 = 1.
self.assertEqual(self.ui.verbosity, 1)
class TestZestIntegration(unittest.TestCase):
@ -881,8 +904,12 @@ class TestZestIntegration(unittest.TestCase):
sys.modules['zest.releaser'] = mock.Mock()
sys.modules['zest.releaser.utils'] = mock.Mock()
self.ask = sys.modules['zest.releaser.utils'].ask
self.ui = MockUI()
self._ui_patcher = mock.patch('check_manifest.UI', return_value=self.ui)
self._ui_patcher.start()
def tearDown(self):
self._ui_patcher.stop()
del sys.modules['zest.releaser.utils']
del sys.modules['zest.releaser']
del sys.modules['zest']
@ -913,11 +940,10 @@ class TestZestIntegration(unittest.TestCase):
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):
sys_exit):
from check_manifest import zest_releaser_check
self.ask.side_effect = [True, False]
check_manifest.return_value = False
@ -925,11 +951,10 @@ class TestZestIntegration(unittest.TestCase):
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):
sys_exit):
from check_manifest import zest_releaser_check
self.ask.side_effect = [True, True]
check_manifest.return_value = False
@ -937,29 +962,27 @@ class TestZestIntegration(unittest.TestCase):
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):
sys_exit):
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')
self.assertEqual(self.ui.errors, ['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):
sys_exit):
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')
self.assertEqual(self.ui.errors, ['msg'])
sys_exit.assert_not_called()
@ -1004,6 +1027,7 @@ class VCSMixin(object):
self.tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest')
self.olddir = os.getcwd()
os.chdir(self.tmpdir)
self.ui = MockUI()
def tearDown(self):
os.chdir(self.olddir)
@ -1040,7 +1064,7 @@ class VCSMixin(object):
self._commit()
self._create_files(['b/x.txt', 'd/d.txt', 'i.txt'])
j = os.path.join
self.assertEqual(get_vcs_files(),
self.assertEqual(get_vcs_files(self.ui),
['a.txt', 'b', j('b', 'b.txt'), j('b', 'c'),
j('b', 'c', 'd.txt')])
@ -1050,7 +1074,7 @@ class VCSMixin(object):
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(),
self.assertEqual(get_vcs_files(self.ui),
['a.txt', 'b', j('b', 'b.txt'), j('b', 'c'),
j('b', 'c', 'd.txt')])
@ -1063,7 +1087,7 @@ class VCSMixin(object):
self._create_and_add_to_vcs(['a.txt'])
self._commit()
os.unlink('a.txt')
self.assertEqual(get_vcs_files(), ['a.txt'])
self.assertEqual(get_vcs_files(self.ui), ['a.txt'])
def test_get_vcs_files_in_a_subdir(self):
from check_manifest import get_vcs_files
@ -1073,7 +1097,7 @@ class VCSMixin(object):
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')])
self.assertEqual(get_vcs_files(self.ui), ['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
@ -1083,12 +1107,12 @@ class VCSMixin(object):
# 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])
self.assertEqual(get_vcs_files(self.ui), [filename])
def test_get_vcs_files_empty(self):
from check_manifest import get_vcs_files
self._init_vcs()
self.assertEqual(get_vcs_files(), [])
self.assertEqual(get_vcs_files(self.ui), [])
class GitHelper(VCSHelper):
@ -1130,13 +1154,13 @@ class TestGit(VCSMixin, unittest.TestCase):
def test_detect_git_submodule(self):
from check_manifest import detect_vcs, Failure
with self.assertRaises(Failure) as cm:
detect_vcs()
detect_vcs(self.ui)
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')
self.assertEqual(detect_vcs(self.ui).metadata_name, '.git')
def test_get_versioned_files_with_git_submodules(self):
from check_manifest import get_vcs_files
@ -1150,7 +1174,7 @@ class TestGit(VCSMixin, unittest.TestCase):
os.chdir('main')
self.vcs._run('git', 'submodule', 'update', '--init', '--recursive')
self.assertEqual(
get_vcs_files(),
get_vcs_files(self.ui),
[fn.replace('/', os.path.sep) for fn in [
'.gitmodules',
'file5',
@ -1278,144 +1302,123 @@ class TestSvn(VCSMixin, unittest.TestCase):
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(),
self.assertEqual(get_vcs_files(self.ui),
['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):
class TestSvnExtraErrors(unittest.TestCase):
def test_svn_xml_parsing_warning(self):
from check_manifest import Subversion
ui = MockUI()
svn = Subversion(ui)
entry = ET.XML('<entry path="foo/bar.txt"></entry>')
self.assertFalse(Subversion.is_interesting(entry))
self.assertFalse(svn.is_interesting(entry))
self.assertEqual(
sys.stderr.getvalue(),
'svn status --xml parse error: <entry path="foo/bar.txt">'
' without <wc-status>\n')
ui.warnings,
['svn status --xml parse error:'
' <entry path="foo/bar.txt"> without <wc-status>'])
class TestUserInterface(unittest.TestCase):
class TestUserInterface(UIMixin, unittest.TestCase):
def make_ui(self, verbosity=1):
from check_manifest import UI
ui = UI(verbosity=verbosity)
ui.stdout = StringIO()
ui.stderr = StringIO()
return ui
def test_info(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.info("Reticulating splines")
self.assertEqual(sys.stdout.getvalue(),
ui = self.make_ui(verbosity=1)
ui.info("Reticulating splines")
self.assertEqual(ui.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(),
ui = self.make_ui(verbosity=2)
ui.info("Reticulating splines")
self.assertEqual(ui.stdout.getvalue(),
"Reticulating splines\n")
def test_info_quiet(self):
import check_manifest
check_manifest.VERBOSE = False
check_manifest.QUIET = True
check_manifest.info("Reticulating splines")
self.assertEqual(sys.stdout.getvalue(), "")
check_manifest.QUIET = False
ui = self.make_ui(verbosity=0)
ui.info("Reticulating splines")
self.assertEqual(ui.stdout.getvalue(), "")
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(), "")
ui = self.make_ui(verbosity=1)
ui.info_begin("Reticulating splines...")
ui.info_continue(" nearly done...")
ui.info_continue(" almost done...")
ui.info_end(" done!")
self.assertEqual(ui.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!")
ui = self.make_ui(verbosity=2)
ui.info_begin("Reticulating splines...")
ui.info_continue(" nearly done...")
ui.info_continue(" almost done...")
ui.info_end(" done!")
self.assertEqual(
sys.stdout.getvalue(),
ui.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!")
ui = self.make_ui(verbosity=1)
ui.info_begin("Computering...")
ui.info("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
ui.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!")
ui = self.make_ui(verbosity=2)
ui.info_begin("Computering...")
ui.info("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
ui.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(), "")
ui = self.make_ui(verbosity=1)
ui.info_begin("Computering...")
ui.warning("Forgot to turn the gas off!")
self.assertEqual(ui.stdout.getvalue(), "")
self.assertEqual(
sys.stderr.getvalue(),
ui.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!")
ui = self.make_ui(verbosity=2)
ui.info_begin("Computering...")
ui.warning("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
ui.stdout.getvalue(),
"Computering...\n")
self.assertEqual(
sys.stderr.getvalue(),
ui.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(), "")
ui = self.make_ui(verbosity=1)
ui.info_begin("Computering...")
ui.error("Forgot to turn the gas off!")
self.assertEqual(ui.stdout.getvalue(), "")
self.assertEqual(
sys.stderr.getvalue(),
ui.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!")
ui = self.make_ui(verbosity=2)
ui.info_begin("Computering...")
ui.error("Forgot to turn the gas off!")
self.assertEqual(
sys.stdout.getvalue(),
ui.stdout.getvalue(),
"Computering...\n")
self.assertEqual(
sys.stderr.getvalue(),
ui.stderr.getvalue(),
"Forgot to turn the gas off!\n")

Loading…
Cancel
Save