# -*- coding: utf-8 -*- from __future__ import print_function import contextlib import os import os.path as op import subprocess import sys import tempfile import warnings from nose.tools import with_setup, assert_equal, assert_true import codespell_lib as cs def reload_codespell_lib(): try: reload # Python 2.7 except NameError: try: from importlib import reload # Python 3.4+ except ImportError: from imp import reload # Python 3.0 - 3.3 reload(cs._codespell) def run_codespell(args=(), cwd=None): """Helper to run codespell""" return subprocess.Popen( ['codespell'] + list(args), cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).wait() def test_command(): """Test running the codespell executable""" # With no arguments does "." with TemporaryDirectory() as d: assert_equal(run_codespell(cwd=d), 0) with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned\nAbandonned\nABANDONNED\nAbAnDoNnEd') assert_equal(run_codespell(cwd=d), 4) def test_basic(): """Test some basic functionality""" assert_equal(cs.main('_does_not_exist_'), 0) with tempfile.NamedTemporaryFile('w') as f: with CaptureStdout() as sio: assert_equal(cs.main('-D', 'foo', f.name), 1) # missing dictionary assert_true('cannot find dictionary' in sio[1]) assert_equal(cs.main(f.name), 0) # empty file f.write('this is a test file\n') f.flush() assert_equal(cs.main(f.name), 0) # good f.write('abandonned\n') f.flush() assert_equal(cs.main(f.name), 1) # bad f.write('abandonned\n') f.flush() assert_equal(cs.main(f.name), 2) # worse with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned\nAbandonned\nABANDONNED\nAbAnDoNnEd') assert_equal(cs.main(d), 4) with CaptureStdout() as sio: assert_equal(cs.main('-w', d), 0) assert_true('FIXED:' in sio[1]) with open(op.join(d, 'bad.txt')) as f: new_content = f.read() assert_equal(cs.main(d), 0) assert_equal(new_content, 'abandoned\nAbandoned\nABANDONED\nabandoned') with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned abandonned\n') assert_equal(cs.main(d), 2) with CaptureStdout() as sio: assert_equal(cs.main('-q', '16', '-w', d), 0) assert_equal(sio[0], '') assert_equal(cs.main(d), 0) # empty directory os.mkdir(op.join(d, 'test')) assert_equal(cs.main(d), 0) # hidden file with open(op.join(d, 'test.txt'), 'w') as f: f.write('abandonned\n') assert_equal(cs.main(op.join(d, 'test.txt')), 1) os.rename(op.join(d, 'test.txt'), op.join(d, '.test.txt')) assert_equal(cs.main(op.join(d, '.test.txt')), 0) def test_interactivity(): """Test interaction""" with tempfile.NamedTemporaryFile('w') as f: assert_equal(cs.main(f.name), 0) # empty file f.write('abandonned\n') f.flush() assert_equal(cs.main('-i', '-1', f.name), 1) # bad with FakeStdin('y\n'): assert_equal(cs.main('-i', '3', f.name), 1) with CaptureStdout() as sio: with FakeStdin('n\n'): assert_equal(cs.main('-w', '-i', '3', f.name), 0) assert_true('==>' in sio[0]) with CaptureStdout(): with FakeStdin('x\ny\n'): assert_equal(cs.main('-w', '-i', '3', f.name), 0) assert_equal(cs.main(f.name), 0) with tempfile.NamedTemporaryFile('w') as f: f.write('abandonned\n') f.flush() assert_equal(cs.main(f.name), 1) with CaptureStdout(): with FakeStdin(' '): # blank input -> Y assert_equal(cs.main('-w', '-i', '3', f.name), 0) assert_equal(cs.main(f.name), 0) # multiple options with tempfile.NamedTemporaryFile('w') as f: f.write('ackward\n') f.flush() assert_equal(cs.main(f.name), 1) with CaptureStdout(): with FakeStdin(' \n'): # blank input -> nothing assert_equal(cs.main('-w', '-i', '3', f.name), 0) assert_equal(cs.main(f.name), 1) with CaptureStdout(): with FakeStdin('0\n'): # blank input -> nothing assert_equal(cs.main('-w', '-i', '3', f.name), 0) assert_equal(cs.main(f.name), 0) with open(f.name, 'r') as f_read: assert_equal(f_read.read(), 'awkward\n') f.seek(0) f.write('ackward\n') f.flush() assert_equal(cs.main(f.name), 1) with CaptureStdout() as sio: with FakeStdin('x\n1\n'): # blank input -> nothing assert_equal(cs.main('-w', '-i', '3', f.name), 0) assert_true('a valid option' in sio[0]) assert_equal(cs.main(f.name), 0) with open(f.name, 'r') as f: assert_equal(f.read(), 'backward\n') def test_summary(): """Test summary functionality""" with tempfile.NamedTemporaryFile('w') as f: with CaptureStdout() as sio: cs.main(f.name) for ii in range(2): assert_equal(sio[ii], '') # no output with CaptureStdout() as sio: cs.main(f.name, '--summary') assert_equal(sio[1], '') # stderr assert_true('SUMMARY' in sio[0]) assert_equal(len(sio[0].split('\n')), 5) # no output f.write('abandonned\nabandonned') f.flush() with CaptureStdout() as sio: cs.main(f.name, '--summary') assert_equal(sio[1], '') # stderr assert_true('SUMMARY' in sio[0]) assert_equal(len(sio[0].split('\n')), 7) assert_true('abandonned' in sio[0].split()[-2]) @with_setup(reload_codespell_lib, reload_codespell_lib) def test_ignore_dictionary(): """Test ignore dictionary functionality""" with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned\nabondon\n') with tempfile.NamedTemporaryFile('w') as f: f.write('abandonned\n') f.flush() assert_equal(cs.main('-I', f.name, d), 1) @with_setup(reload_codespell_lib, reload_codespell_lib) def test_custom_regex(): """Test custom word regex""" with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned_abondon\n') assert_equal(cs.main(d), 0) assert_equal(cs.main('-r', "[a-z]+", d), 2) def test_exclude_file(): """Test exclude file functionality""" with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned 1\nabandonned 2\n') with tempfile.NamedTemporaryFile('w') as f: f.write('abandonned 1\n') f.flush() assert_equal(cs.main(d), 2) assert_equal(cs.main('-x', f.name, d), 1) def test_encoding(): """Test encoding handling""" # Some simple Unicode things with tempfile.NamedTemporaryFile('wb') as f: # with CaptureStdout() as sio: assert_equal(cs.main(f.name), 0) f.write(u'naïve\n'.encode('utf-8')) f.flush() assert_equal(cs.main(f.name), 0) assert_equal(cs.main('-e', f.name), 0) f.write(u'naieve\n'.encode('utf-8')) f.flush() assert_equal(cs.main(f.name), 1) # Binary file warning with tempfile.NamedTemporaryFile('wb') as f: assert_equal(cs.main(f.name), 0) f.write(b'\x00\x00naiive\x00\x00') f.flush() with CaptureStdout() as sio: assert_equal(cs.main(f.name), 0) assert_true('WARNING: Binary file' in sio[1]) with CaptureStdout() as sio: assert_equal(cs.main('-q', '2', f.name), 0) assert_equal(sio[1], '') def test_ignore(): """Test ignoring of files and directories""" with TemporaryDirectory() as d: with open(op.join(d, 'good.txt'), 'w') as f: f.write('this file is okay') assert_equal(cs.main(d), 0) with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned') assert_equal(cs.main(d), 1) assert_equal(cs.main('--skip=bad*', d), 0) assert_equal(cs.main('--skip=bad.txt', d), 0) subdir = op.join(d, 'ignoredir') os.mkdir(subdir) with open(op.join(subdir, 'bad.txt'), 'w') as f: f.write('abandonned') assert_equal(cs.main(d), 2) assert_equal(cs.main('--skip=bad*', d), 0) assert_equal(cs.main('--skip=*ignoredir*', d), 1) assert_equal(cs.main('--skip=ignoredir', d), 1) def test_check_filename(): """Test filename check""" with TemporaryDirectory() as d: with open(op.join(d, 'abandonned.txt'), 'w') as f: f.write('.') assert_equal(cs.main('-f', d), 1) class TemporaryDirectory(object): """Backport for 2.7""" def __init__(self, suffix="", prefix="tmp", dir=None): self._closed = False self.name = None self.name = tempfile.mkdtemp(suffix, prefix, dir) def __enter__(self): return self.name def cleanup(self, _warn=False): if self.name and not self._closed: try: self._rmtree(self.name) except (TypeError, AttributeError) as ex: # Issue #10188: Emit a warning on stderr # if the directory could not be cleaned # up due to missing globals if "None" not in str(ex): raise print("ERROR: {!r} while cleaning up {!r}".format(ex, self,), file=sys.stderr) return self._closed = True if _warn: self._warn("Implicitly cleaning up {!r}".format(self)) def __exit__(self, exc, value, tb): self.cleanup() def __del__(self): # Issue a ResourceWarning if implicit cleanup needed self.cleanup(_warn=True) # XXX (ncoghlan): The following code attempts to make # this class tolerant of the module nulling out process # that happens during CPython interpreter shutdown # Alas, it doesn't actually manage it. See issue #10188 _listdir = staticmethod(os.listdir) _path_join = staticmethod(os.path.join) _isdir = staticmethod(os.path.isdir) _islink = staticmethod(os.path.islink) _remove = staticmethod(os.remove) _rmdir = staticmethod(os.rmdir) _warn = warnings.warn def _rmtree(self, path): # Essentially a stripped down version of shutil.rmtree. We can't # use globals because they may be None'ed out at shutdown. for name in self._listdir(path): fullname = self._path_join(path, name) try: isdir = self._isdir(fullname) and not self._islink(fullname) except OSError: isdir = False if isdir: self._rmtree(fullname) else: try: self._remove(fullname) except OSError: pass try: self._rmdir(path) except OSError: pass @contextlib.contextmanager def CaptureStdout(): if sys.version[0] == '2': from StringIO import StringIO else: from io import StringIO oldout, olderr = sys.stdout, sys.stderr try: out = [StringIO(), StringIO()] sys.stdout, sys.stderr = out yield out finally: sys.stdout, sys.stderr = oldout, olderr out[0] = out[0].getvalue() out[1] = out[1].getvalue() @contextlib.contextmanager def FakeStdin(text): if sys.version[0] == '2': from StringIO import StringIO else: from io import StringIO oldin = sys.stdin try: in_ = StringIO(text) sys.stdin = in_ yield finally: sys.stdin = oldin