Add tests for BaseFormatter

This commit is contained in:
Ian Cordasco 2016-06-01 16:56:17 -05:00
parent 15043a4ab7
commit ba2d94888c
2 changed files with 127 additions and 12 deletions

View file

@ -55,7 +55,7 @@ class BaseFormatter(object):
def handle(self, error): def handle(self, error):
"""Handle an error reported by Flake8. """Handle an error reported by Flake8.
This defaults to calling :meth:`format`, :meth:`format_source`, and This defaults to calling :meth:`format`, :meth:`show_source`, and
then :meth:`write`. To extend how errors are handled, override this then :meth:`write`. To extend how errors are handled, override this
method. method.
@ -65,7 +65,7 @@ class BaseFormatter(object):
flake8.style_guide.Error flake8.style_guide.Error
""" """
line = self.format(error) line = self.format(error)
source = self.format_source(error) source = self.show_source(error)
self.write(line, source) self.write(line, source)
def format(self, error): def format(self, error):
@ -85,11 +85,19 @@ class BaseFormatter(object):
raise NotImplementedError('Subclass of BaseFormatter did not implement' raise NotImplementedError('Subclass of BaseFormatter did not implement'
' format.') ' format.')
def format_source(self, error): def show_benchmarks(self, benchmarks):
"""Format the physical line generating the error. pass
def show_source(self, error):
"""Show the physical line generating the error.
This also adds an indicator for the particular part of the line that
is reported as generating the problem.
:param error: :param error:
This will be an instance of :class:`~flake8.style_guide.Error`. This will be an instance of :class:`~flake8.style_guide.Error`.
:type error:
flake8.style_guide.Error
:returns: :returns:
The formatted error string if the user wants to show the source. The formatted error string if the user wants to show the source.
If the user does not want to show the source, this will return If the user does not want to show the source, this will return
@ -104,6 +112,13 @@ class BaseFormatter(object):
# one # one
return error.physical_line + pointer return error.physical_line + pointer
def _write(self, output):
"""Handle logic of whether to use an output file or print()."""
if self.output_fd is not None:
self.output_fd.write(output + self.newline)
else:
print(output)
def write(self, line, source): def write(self, line, source):
"""Write the line either to the output file or stdout. """Write the line either to the output file or stdout.
@ -113,16 +128,13 @@ class BaseFormatter(object):
:param str line: :param str line:
The formatted string to print or write. The formatted string to print or write.
:param str source:
The source code that has been formatted and associated with the
line of output.
""" """
if self.output_fd is not None: self._write(line)
write = self.output_fd.write
output_func = lambda line: write(line + self.newline)
else:
output_func = print
output_func(line)
if source: if source:
output_func(source) self._write(source)
def stop(self): def stop(self):
"""Clean up after reporting is finished.""" """Clean up after reporting is finished."""

View file

@ -0,0 +1,103 @@
"""Tests for the BaseFormatter object."""
import optparse
import mock
import pytest
from flake8.formatting import base
from flake8 import style_guide
def options(**kwargs):
"""Create an optparse.Values instance."""
kwargs.setdefault('output_file', None)
return optparse.Values(kwargs)
@pytest.mark.parametrize('filename', [None, 'out.txt'])
def test_start(filename):
"""Verify we open a new file in the start method."""
mock_open = mock.mock_open()
formatter = base.BaseFormatter(options(output_file=filename))
with mock.patch('flake8.formatting.base.open', mock_open):
formatter.start()
if filename is None:
assert mock_open.called is False
else:
mock_open.assert_called_once_with(filename, 'w')
def test_stop():
"""Verify we close open file objects."""
filemock = mock.Mock()
formatter = base.BaseFormatter(options())
formatter.output_fd = filemock
formatter.stop()
filemock.close.assert_called_once_with()
assert formatter.output_fd is None
def test_format_needs_to_be_implemented():
"""Ensure BaseFormatter#format raises a NotImplementedError."""
formatter = base.BaseFormatter(options())
with pytest.raises(NotImplementedError):
formatter.format('foo')
def test_show_source_returns_nothing_when_not_showing_source():
"""Ensure we return nothing when users want nothing."""
formatter = base.BaseFormatter(options(show_source=False))
assert formatter.show_source(
style_guide.Error('A000', 'file.py', 1, 1, 'error text', 'line')
) is None
@pytest.mark.parametrize('line, column', [
('x=1\n', 2),
(' x=(1\n +2)\n', 5),
# TODO(sigmavirus24): Add more examples
])
def test_show_source_updates_physical_line_appropriately(line, column):
"""Ensure the error column is appropriately indicated."""
formatter = base.BaseFormatter(options(show_source=True))
error = style_guide.Error('A000', 'file.py', 1, column, 'error', line)
output = formatter.show_source(error)
_, pointer = output.rsplit('\n', 1)
assert pointer.count(' ') == column
def test_write_uses_an_output_file():
"""Verify that we use the output file when it's present."""
line = 'Something to write'
source = 'source'
filemock = mock.Mock()
formatter = base.BaseFormatter(options())
formatter.output_fd = filemock
formatter.write(line, source)
assert filemock.write.called is True
assert filemock.write.call_count == 2
assert filemock.write.mock_calls == [
mock.call(line + formatter.newline),
mock.call(source + formatter.newline),
]
@mock.patch('flake8.formatting.base.print')
def test_write_uses_print(print_function):
"""Verify that we use the print function without an output file."""
line = 'Something to write'
source = 'source'
formatter = base.BaseFormatter(options())
formatter.write(line, source)
assert print_function.called is True
assert print_function.call_count == 2
assert print_function.mock_calls == [
mock.call(line),
mock.call(source),
]