Current File : //proc/self/root/lib/python3/dist-packages/twisted/logger/test/test_filter.py |
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Test cases for L{twisted.logger._filter}.
"""
from typing import Iterable, List, Tuple, Union, cast
from zope.interface import implementer
from zope.interface.exceptions import BrokenMethodImplementation
from zope.interface.verify import verifyObject
from constantly import NamedConstant
from twisted.trial import unittest
from .._filter import (
FilteringLogObserver,
ILogFilterPredicate,
LogLevelFilterPredicate,
PredicateResult,
)
from .._interfaces import ILogObserver, LogEvent
from .._levels import InvalidLogLevelError, LogLevel
from .._observer import LogPublisher, bitbucketLogObserver
class FilteringLogObserverTests(unittest.TestCase):
"""
Tests for L{FilteringLogObserver}.
"""
def test_interface(self) -> None:
"""
L{FilteringLogObserver} is an L{ILogObserver}.
"""
observer = FilteringLogObserver(cast(ILogObserver, lambda e: None), ())
try:
verifyObject(ILogObserver, observer)
except BrokenMethodImplementation as e:
self.fail(e)
def filterWith(
self, filters: Iterable[str], other: bool = False
) -> Union[List[int], Tuple[List[int], List[int]]]:
"""
Apply a set of pre-defined filters on a known set of events and return
the filtered list of event numbers.
The pre-defined events are four events with a C{count} attribute set to
C{0}, C{1}, C{2}, and C{3}.
@param filters: names of the filters to apply.
Options are:
- C{"twoMinus"} (count <=2),
- C{"twoPlus"} (count >= 2),
- C{"notTwo"} (count != 2),
- C{"no"} (False).
@param other: Whether to return a list of filtered events as well.
@return: event numbers or 2-tuple of lists of event numbers.
"""
events: List[LogEvent] = [
dict(count=0),
dict(count=1),
dict(count=2),
dict(count=3),
]
class Filters:
@staticmethod
def twoMinus(event: LogEvent) -> NamedConstant:
"""
count <= 2
@param event: an event
@return: L{PredicateResult.yes} if C{event["count"] <= 2},
otherwise L{PredicateResult.maybe}.
"""
if event["count"] <= 2:
return PredicateResult.yes
return PredicateResult.maybe
@staticmethod
def twoPlus(event: LogEvent) -> NamedConstant:
"""
count >= 2
@param event: an event
@return: L{PredicateResult.yes} if C{event["count"] >= 2},
otherwise L{PredicateResult.maybe}.
"""
if event["count"] >= 2:
return PredicateResult.yes
return PredicateResult.maybe
@staticmethod
def notTwo(event: LogEvent) -> NamedConstant:
"""
count != 2
@param event: an event
@return: L{PredicateResult.yes} if C{event["count"] != 2},
otherwise L{PredicateResult.maybe}.
"""
if event["count"] == 2:
return PredicateResult.no
return PredicateResult.maybe
@staticmethod
def no(event: LogEvent) -> NamedConstant:
"""
No way, man.
@param event: an event
@return: L{PredicateResult.no}
"""
return PredicateResult.no
@staticmethod
def bogus(event: LogEvent) -> NamedConstant:
"""
Bogus result.
@param event: an event
@return: something other than a valid predicate result.
"""
return None
predicates = (getattr(Filters, f) for f in filters)
eventsSeen: List[LogEvent] = []
eventsNotSeen: List[LogEvent] = []
trackingObserver = cast(ILogObserver, eventsSeen.append)
if other:
negativeObserver = cast(ILogObserver, eventsNotSeen.append)
else:
negativeObserver = bitbucketLogObserver
filteringObserver = FilteringLogObserver(
trackingObserver, predicates, negativeObserver
)
for e in events:
filteringObserver(e)
if other:
return (
[cast(int, e["count"]) for e in eventsSeen],
[cast(int, e["count"]) for e in eventsNotSeen],
)
else:
return [cast(int, e["count"]) for e in eventsSeen]
def test_shouldLogEventNoFilters(self) -> None:
"""
No filters: all events come through.
"""
self.assertEqual(self.filterWith([]), [0, 1, 2, 3])
def test_shouldLogEventNoFilter(self) -> None:
"""
Filter with negative predicate result.
"""
self.assertEqual(self.filterWith(["notTwo"]), [0, 1, 3])
def test_shouldLogEventOtherObserver(self) -> None:
"""
Filtered results get sent to the other observer, if passed.
"""
self.assertEqual(self.filterWith(["notTwo"], True), ([0, 1, 3], [2]))
def test_shouldLogEventYesFilter(self) -> None:
"""
Filter with positive predicate result.
"""
self.assertEqual(self.filterWith(["twoPlus"]), [0, 1, 2, 3])
def test_shouldLogEventYesNoFilter(self) -> None:
"""
Series of filters with positive and negative predicate results.
"""
self.assertEqual(self.filterWith(["twoPlus", "no"]), [2, 3])
def test_shouldLogEventYesYesNoFilter(self) -> None:
"""
Series of filters with positive, positive and negative predicate
results.
"""
self.assertEqual(self.filterWith(["twoPlus", "twoMinus", "no"]), [0, 1, 2, 3])
def test_shouldLogEventBadPredicateResult(self) -> None:
"""
Filter with invalid predicate result.
"""
self.assertRaises(TypeError, self.filterWith, ["bogus"])
def test_call(self) -> None:
"""
Test filtering results from each predicate type.
"""
e: LogEvent = dict(obj=object())
def callWithPredicateResult(result: NamedConstant) -> List[LogEvent]:
seen: List[LogEvent] = []
observer = FilteringLogObserver(
cast(ILogObserver, lambda e: seen.append(e)),
(cast(ILogFilterPredicate, lambda e: result),),
)
observer(e)
return seen
self.assertIn(e, callWithPredicateResult(PredicateResult.yes))
self.assertIn(e, callWithPredicateResult(PredicateResult.maybe))
self.assertNotIn(e, callWithPredicateResult(PredicateResult.no))
def test_trace(self) -> None:
"""
Tracing keeps track of forwarding through the filtering observer.
"""
event: LogEvent = dict(log_trace=[])
oYes = cast(ILogObserver, lambda e: None)
oNo = cast(ILogObserver, lambda e: None)
@implementer(ILogObserver)
def testObserver(e: LogEvent) -> None:
self.assertIs(e, event)
self.assertEqual(
event["log_trace"],
[
(publisher, yesFilter),
(yesFilter, oYes),
(publisher, noFilter),
# ... noFilter doesn't call oNo
(publisher, oTest),
],
)
oTest = testObserver
yesFilter = FilteringLogObserver(
oYes, (cast(ILogFilterPredicate, lambda e: PredicateResult.yes),)
)
noFilter = FilteringLogObserver(
oNo, (cast(ILogFilterPredicate, lambda e: PredicateResult.no),)
)
publisher = LogPublisher(yesFilter, noFilter, testObserver)
publisher(event)
class LogLevelFilterPredicateTests(unittest.TestCase):
"""
Tests for L{LogLevelFilterPredicate}.
"""
def test_defaultLogLevel(self) -> None:
"""
Default log level is used.
"""
predicate = LogLevelFilterPredicate()
# Test using both "" and None as default namespace, because None was the
# documented default value in the past.
for default in ("", cast(str, None)):
self.assertEqual(
predicate.logLevelForNamespace(default), predicate.defaultLogLevel
)
self.assertEqual(
predicate.logLevelForNamespace("rocker.cool.namespace"),
predicate.defaultLogLevel,
)
def test_setLogLevel(self) -> None:
"""
Setting and retrieving log levels.
"""
predicate = LogLevelFilterPredicate()
# Test using both "" and None as default namespace, because None was the
# documented default value in the past.
for default in ("", cast(str, None)):
predicate.setLogLevelForNamespace(default, LogLevel.error)
predicate.setLogLevelForNamespace("twext.web2", LogLevel.debug)
predicate.setLogLevelForNamespace("twext.web2.dav", LogLevel.warn)
self.assertEqual(predicate.logLevelForNamespace(""), LogLevel.error)
self.assertEqual(
predicate.logLevelForNamespace(cast(str, None)), LogLevel.error
)
self.assertEqual(predicate.logLevelForNamespace("twisted"), LogLevel.error)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2"), LogLevel.debug
)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2.dav"), LogLevel.warn
)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2.dav.test"), LogLevel.warn
)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2.dav.test1.test2"),
LogLevel.warn,
)
def test_setInvalidLogLevel(self) -> None:
"""
Can't pass invalid log levels to C{setLogLevelForNamespace()}.
"""
predicate = LogLevelFilterPredicate()
self.assertRaises(
InvalidLogLevelError,
predicate.setLogLevelForNamespace,
"twext.web2",
object(),
)
# Level must be a constant, not the name of a constant
self.assertRaises(
InvalidLogLevelError,
predicate.setLogLevelForNamespace,
"twext.web2",
"debug",
)
def test_clearLogLevels(self) -> None:
"""
Clearing log levels.
"""
predicate = LogLevelFilterPredicate()
predicate.setLogLevelForNamespace("twext.web2", LogLevel.debug)
predicate.setLogLevelForNamespace("twext.web2.dav", LogLevel.error)
predicate.clearLogLevels()
self.assertEqual(
predicate.logLevelForNamespace("twisted"), predicate.defaultLogLevel
)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2"), predicate.defaultLogLevel
)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2.dav"), predicate.defaultLogLevel
)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2.dav.test"),
predicate.defaultLogLevel,
)
self.assertEqual(
predicate.logLevelForNamespace("twext.web2.dav.test1.test2"),
predicate.defaultLogLevel,
)
def test_filtering(self) -> None:
"""
Events are filtered based on log level/namespace.
"""
predicate = LogLevelFilterPredicate()
predicate.setLogLevelForNamespace("", LogLevel.error)
predicate.setLogLevelForNamespace("twext.web2", LogLevel.debug)
predicate.setLogLevelForNamespace("twext.web2.dav", LogLevel.warn)
def checkPredicate(
namespace: str, level: NamedConstant, expectedResult: NamedConstant
) -> None:
event: LogEvent = dict(log_namespace=namespace, log_level=level)
self.assertEqual(expectedResult, predicate(event))
checkPredicate("", LogLevel.debug, PredicateResult.no)
checkPredicate(cast(str, None), LogLevel.debug, PredicateResult.no)
checkPredicate("", LogLevel.error, PredicateResult.no)
checkPredicate(cast(str, None), LogLevel.error, PredicateResult.no)
checkPredicate("twext.web2", LogLevel.debug, PredicateResult.maybe)
checkPredicate("twext.web2", LogLevel.error, PredicateResult.maybe)
checkPredicate("twext.web2.dav", LogLevel.debug, PredicateResult.no)
checkPredicate("twext.web2.dav", LogLevel.error, PredicateResult.maybe)
checkPredicate("", LogLevel.critical, PredicateResult.no)
checkPredicate(cast(str, None), LogLevel.critical, PredicateResult.no)
checkPredicate("twext.web2", None, PredicateResult.no)