From ba2d94888c2989da70fe025aad50eaee1206dc6f Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 1 Jun 2016 16:56:17 -0500 Subject: [PATCH] Add tests for BaseFormatter --- flake8/formatting/base.py | 36 +++++++---- tests/unit/test_base_formatter.py | 103 ++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 tests/unit/test_base_formatter.py 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), + ]