Fix --fatal to honor LOG_FILTER

This commit is contained in:
Eric Dunham 2021-03-03 12:32:08 -06:00
commit f5cec6f13e
9 changed files with 63 additions and 51 deletions

View file

@ -1,3 +1,3 @@
Release type: minor Release type: patch
Add setting to allow for empty `alt` attributes in images; it defaults to `False`, which is the current behavior. Empty `alt` text can be indicative of an accessibility oversight, but can be intentional and desired, e.g. https://webaim.org/techniques/alttext/, https://www.w3.org/WAI/tutorials/images/decorative/. Address an issue where `--fatal=warnings|errors` would not honor entries in the `LOG_FILTER` setting.

View file

@ -364,10 +364,6 @@ Basic settings
A list of metadata fields containing reST/Markdown content to be parsed and A list of metadata fields containing reST/Markdown content to be parsed and
translated to HTML. translated to HTML.
.. data:: IMAGES_ALLOW_EMPTY_ALT_TEXT = False
If ``True``, warnings will not be emitted when empty ``alt`` attributes for images are found.
.. data:: PORT = 8000 .. data:: PORT = 8000
The TCP port to serve content from the output folder via HTTP when pelican The TCP port to serve content from the output folder via HTTP when pelican

View file

@ -149,24 +149,9 @@ class LimitLogger(logging.Logger):
self.addFilter(LimitLogger.limit_filter) self.addFilter(LimitLogger.limit_filter)
class FatalLogger(LimitLogger): logging.setLoggerClass(LimitLogger)
warnings_fatal = False
errors_fatal = False
def warning(self, *args, **kwargs):
super().warning(*args, **kwargs)
if FatalLogger.warnings_fatal:
raise RuntimeError('Warning encountered')
def error(self, *args, **kwargs):
super().error(*args, **kwargs)
if FatalLogger.errors_fatal:
raise RuntimeError('Error encountered')
logging.setLoggerClass(FatalLogger)
# force root logger to be of our preferred class # force root logger to be of our preferred class
logging.getLogger().__class__ = FatalLogger logging.getLogger().__class__ = LimitLogger
def supports_color(): def supports_color():
@ -194,11 +179,13 @@ def get_formatter():
return TextFormatter() return TextFormatter()
class FatalHandler(logging.Handler):
def emit(self, record):
sys.exit(1)
def init(level=None, fatal='', handler=logging.StreamHandler(), name=None, def init(level=None, fatal='', handler=logging.StreamHandler(), name=None,
logs_dedup_min_level=None): logs_dedup_min_level=None):
FatalLogger.warnings_fatal = fatal.startswith('warning')
FatalLogger.errors_fatal = bool(fatal)
logger = logging.getLogger(name) logger = logging.getLogger(name)
handler.setFormatter(get_formatter()) handler.setFormatter(get_formatter())
@ -209,6 +196,11 @@ def init(level=None, fatal='', handler=logging.StreamHandler(), name=None,
if logs_dedup_min_level: if logs_dedup_min_level:
LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level
if fatal.startswith('warning'):
logger.addHandler(FatalHandler(level=logging.WARNING))
if fatal:
logger.addHandler(FatalHandler(level=logging.ERROR))
def log_warnings(): def log_warnings():
import warnings import warnings

View file

@ -574,7 +574,7 @@ class Readers(FileStampDataCacher):
self.cache_data(path, (content, reader_metadata)) self.cache_data(path, (content, reader_metadata))
metadata.update(_filter_discardable_metadata(reader_metadata)) metadata.update(_filter_discardable_metadata(reader_metadata))
if not self.settings['IMAGES_ALLOW_EMPTY_ALT_TEXT'] and content: if content:
# find images with empty alt # find images with empty alt
find_empty_alt(content, path) find_empty_alt(content, path)

View file

@ -163,8 +163,7 @@ DEFAULT_CONFIG = {
'WRITE_SELECTED': [], 'WRITE_SELECTED': [],
'FORMATTED_FIELDS': ['summary'], 'FORMATTED_FIELDS': ['summary'],
'PORT': 8000, 'PORT': 8000,
'BIND': '127.0.0.1', 'BIND': '127.0.0.1'
'IMAGES_ALLOW_EMPTY_ALT_TEXT': False
} }
PYGMENTS_RST_OPTIONS = None PYGMENTS_RST_OPTIONS = None

View file

@ -1,9 +0,0 @@
<html>
<head>
</head>
<body>
Images
<img alt="" src="test-image.png" width="300px" />
<img src="test-image.png" width="300px" alt="" />
</body>
</html>

View file

@ -153,7 +153,7 @@ class TestCache(unittest.TestCase):
- empty.md - empty.md
- empty_with_bom.md - empty_with_bom.md
""" """
self.assertEqual(generator.readers.read_file.call_count, 7) self.assertEqual(generator.readers.read_file.call_count, 6)
def test_article_reader_content_caching(self): def test_article_reader_content_caching(self):
"""Test raw article content caching at the reader level""" """Test raw article content caching at the reader level"""

View file

@ -2,6 +2,7 @@ import logging
import unittest import unittest
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from unittest import mock
from pelican import log from pelican import log
from pelican.tests.support import LogCountHandler from pelican.tests.support import LogCountHandler
@ -130,3 +131,48 @@ class TestLog(unittest.TestCase):
self.assertEqual( self.assertEqual(
self.handler.count_logs('Another log \\d', logging.WARNING), self.handler.count_logs('Another log \\d', logging.WARNING),
0) 0)
@mock.patch.object(log, 'sys')
class TestLogInit(unittest.TestCase):
def init(self, fatal, name):
logger = logging.getLogger(name)
log.init(fatal=fatal, name=name)
return logger
def test_fatal_warnings(self, sys):
logger = self.init('warnings', 'test_fatal_warnings')
logger.warning('foo')
logger.error('bar')
sys.exit.assert_called_with(1)
def test_fatal_errors(self, sys):
logger = self.init('errors', 'test_fatal_errors')
logger.warning('foo')
logger.error('bar')
sys.exit.assert_called_with(1)
def test_no_fatal(self, sys):
logger = self.init('', 'test_no_fatal')
logger.warning('foo')
logger.error('bar')
sys.exit.assert_not_called()
def test_fatal_warnings_log_filter(self, sys):
limit_filter = log.LimitFilter()
limit_filter._ignore = {(logging.WARNING, 'foo')}
lf = mock.PropertyMock(return_value=limit_filter)
with mock.patch('pelican.log.LimitLogger.limit_filter', new=lf):
logger = self.init('warnings', 'test_fatal_warnings_log_filter')
logger.warning('foo')
sys.exit.assert_not_called()
def test_fatal_errors_log_filter(self, sys):
limit_filter = log.LimitFilter()
limit_filter.LOGS_DEDUP_MIN_LEVEL = logging.CRITICAL
limit_filter._ignore = {(logging.ERROR, 'bar')}
lf = mock.PropertyMock(return_value=limit_filter)
with mock.patch('pelican.log.LimitLogger.limit_filter', new=lf):
logger = self.init('errors', 'test_fatal_errors_log_filter')
logger.error('bar')
sys.exit.assert_not_called()

View file

@ -134,18 +134,6 @@ class DefaultReaderTest(ReaderTest):
'Other images have empty alt attributes'} 'Other images have empty alt attributes'}
) )
@patch('pelican.readers.logger')
def test_read_file_with_images_allow_empty_alt_text_false(self, log_mock):
test_file = 'article_with_images.html'
self.read_file(path=test_file, IMAGES_ALLOW_EMPTY_ALT_TEXT=False)
assert 2 == log_mock.warning.call_count
@patch('pelican.readers.logger')
def test_read_file_with_images_allow_empty_alt_text_true(self, log_mock):
test_file = 'article_with_images.html'
self.read_file(path=test_file, IMAGES_ALLOW_EMPTY_ALT_TEXT=True)
log_mock.warning.assert_not_called()
class RstReaderTest(ReaderTest): class RstReaderTest(ReaderTest):