summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flake8/formatting/base.py36
-rw-r--r--tests/unit/test_base_formatter.py103
2 files changed, 127 insertions, 12 deletions
diff --git a/flake8/formatting/base.py b/flake8/formatting/base.py
index ecc5973..f66fb1f 100644
--- a/flake8/formatting/base.py
+++ b/flake8/formatting/base.py
@@ -55,7 +55,7 @@ class BaseFormatter(object):
def handle(self, error):
"""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
method.
@@ -65,7 +65,7 @@ class BaseFormatter(object):
flake8.style_guide.Error
"""
line = self.format(error)
- source = self.format_source(error)
+ source = self.show_source(error)
self.write(line, source)
def format(self, error):
@@ -85,11 +85,19 @@ class BaseFormatter(object):
raise NotImplementedError('Subclass of BaseFormatter did not implement'
' format.')
- def format_source(self, error):
- """Format the physical line generating the error.
+ def show_benchmarks(self, benchmarks):
+ 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:
This will be an instance of :class:`~flake8.style_guide.Error`.
+ :type error:
+ flake8.style_guide.Error
:returns:
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
@@ -104,6 +112,13 @@ class BaseFormatter(object):
# one
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):
"""Write the line either to the output file or stdout.
@@ -113,16 +128,13 @@ class BaseFormatter(object):
:param str line:
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:
- write = self.output_fd.write
- output_func = lambda line: write(line + self.newline)
- else:
- output_func = print
-
- output_func(line)
+ self._write(line)
if source:
- output_func(source)
+ self._write(source)
def stop(self):
"""Clean up after reporting is finished."""
diff --git a/tests/unit/test_base_formatter.py b/tests/unit/test_base_formatter.py
new file mode 100644
index 0000000..625e4c1
--- /dev/null
+++ b/tests/unit/test_base_formatter.py
@@ -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),
+ ]