diff --git a/coalib/bearlib/abstractions/Linter.py b/coalib/bearlib/abstractions/Linter.py index 7cfc5fddbe..46703a8fc9 100644 --- a/coalib/bearlib/abstractions/Linter.py +++ b/coalib/bearlib/abstractions/Linter.py @@ -2,7 +2,7 @@ from functools import partial, partialmethod import logging import inspect -from itertools import chain, compress +from itertools import chain import re import shutil from subprocess import check_call, CalledProcessError, DEVNULL @@ -676,18 +676,45 @@ def run(self, filename=None, file=None, **kwargs): self.debug("Running '{}'".format( ' '.join(str(arg) for arg in arguments))) - output = run_shell_command( + result = run_shell_command( arguments, stdin=''.join(file) if options['use_stdin'] else None, cwd=self.get_config_dir()) - output = tuple(compress( - output, - (options['use_stdout'], options['use_stderr']))) + stdout, stderr = result + + output = [] + + if options['use_stdout']: + output.append(stdout) + elif stdout: + logging.warning( + '{}: Discarded stdout: {}'.format( + self.__class__.__name__, stdout)) + stdout = '' + + if options['use_stderr']: + output.append(stderr) + elif stderr: + logging.warning( + '{}: Discarded stderr: {}'.format( + self.__class__.__name__, stderr)) + stderr = '' + if options['strip_ansi']: output = tuple(map(strip_ansi, output)) + else: + output = tuple(output) + if len(output) == 1: output = output[0] + + if not any([stdout, stderr]): + logging.info( + '{}: No output; skipping processing'.format( + self.__class__.__name__)) + return + process_output_kwargs = FunctionMetadata.filter_parameters( self._get_process_output_metadata(), kwargs) return self.process_output(output, filename, file, diff --git a/tests/bearlib/abstractions/LinterTest.py b/tests/bearlib/abstractions/LinterTest.py index d6d87319c7..2b6b600052 100644 --- a/tests/bearlib/abstractions/LinterTest.py +++ b/tests/bearlib/abstractions/LinterTest.py @@ -298,7 +298,13 @@ def create_arguments(filename, file, config_file): uut = (linter(sys.executable, use_stdout=True) (TestLinter) (self.section, None)) - uut.run('', []) + + with self.assertLogs('', level='DEBUG') as cm: + uut.run('', []) + + self.assertEqual(cm.output, [ + 'WARNING:root:TestLinter: Discarded stderr: hello stderr\n', + ]) process_output_mock.assert_called_once_with('hello stdout\n', '', []) process_output_mock.reset_mock() @@ -306,7 +312,13 @@ def create_arguments(filename, file, config_file): uut = (linter(sys.executable, use_stdout=False, use_stderr=True) (TestLinter) (self.section, None)) - uut.run('', []) + + with self.assertLogs('', level='DEBUG') as cm: + uut.run('', []) + + self.assertEqual(cm.output, [ + 'WARNING:root:TestLinter: Discarded stdout: hello stdout\n', + ]) process_output_mock.assert_called_once_with('hello stderr\n', '', []) process_output_mock.reset_mock() @@ -320,6 +332,95 @@ def create_arguments(filename, file, config_file): process_output_mock.assert_called_once_with(('hello stdout\n', 'hello stderr\n'), '', []) + def test_no_output(self): + process_output_mock = Mock() + logging.getLogger().setLevel(logging.DEBUG) + + class TestLinter: + + @staticmethod + def process_output(output, filename, file): + process_output_mock(output, filename, file) + + @staticmethod + def create_arguments(filename, file, config_file): + return '-c', '' + + uut = (linter(sys.executable, use_stdout=True, use_stderr=True) + (TestLinter) + (self.section, None)) + + with self.assertLogs('', level='DEBUG') as cm: + uut.run('', []) + + process_output_mock.assert_not_called() + + self.assertEqual(cm.output, [ + 'INFO:root:TestLinter: No output; skipping processing', + ]) + + def test_discarded_stderr(self): + process_output_mock = Mock() + logging.getLogger().setLevel(logging.DEBUG) + + class TestLinter: + + @staticmethod + def process_output(output, filename, file): + process_output_mock(output, filename, file) + + @staticmethod + def create_arguments(filename, file, config_file): + code = '\n'.join(['import sys', + "print('hello stderr', file=sys.stderr)", + ]) + return '-c', code + + uut = (linter(sys.executable, use_stdout=True, use_stderr=False) + (TestLinter) + (self.section, None)) + + with self.assertLogs('', level='DEBUG') as cm: + uut.run('', []) + + process_output_mock.assert_not_called() + + self.assertEqual(cm.output, [ + 'WARNING:root:TestLinter: Discarded stderr: hello stderr\n', + 'INFO:root:TestLinter: No output; skipping processing', + ]) + + def test_discarded_stdout(self): + process_output_mock = Mock() + logging.getLogger().setLevel(logging.DEBUG) + + class TestLinter: + + @staticmethod + def process_output(output, filename, file): + process_output_mock(output, filename, file) + + @staticmethod + def create_arguments(filename, file, config_file): + code = '\n'.join(['import sys', + "print('hello stdout', file=sys.stdout)", + ]) + return '-c', code + + uut = (linter(sys.executable, use_stdout=False, use_stderr=True) + (TestLinter) + (self.section, None)) + + with self.assertLogs('', level='DEBUG') as cm: + uut.run('', []) + + process_output_mock.assert_not_called() + + self.assertEqual(cm.output, [ + 'WARNING:root:TestLinter: Discarded stdout: hello stdout\n', + 'INFO:root:TestLinter: No output; skipping processing', + ]) + def test_strip_ansi(self): process_output_mock = Mock()