%PDF- %PDF-
Direktori : /lib/python3/dist-packages/twisted/logger/test/ |
Current File : //lib/python3/dist-packages/twisted/logger/test/test_global.py |
# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Test cases for L{twisted.logger._global}. """ import io from typing import IO, Any, List, Optional, TextIO, Tuple, Type, cast from twisted.python.failure import Failure from twisted.trial import unittest from .._file import textFileLogObserver from .._global import MORE_THAN_ONCE_WARNING, LogBeginner from .._interfaces import ILogObserver, LogEvent from .._levels import LogLevel from .._logger import Logger from .._observer import LogPublisher from ..test.test_stdlib import nextLine def compareEvents( test: unittest.TestCase, actualEvents: List[LogEvent], expectedEvents: List[LogEvent], ) -> None: """ Compare two sequences of log events, examining only the the keys which are present in both. @param test: a test case doing the comparison @param actualEvents: A list of log events that were emitted by a logger. @param expectedEvents: A list of log events that were expected by a test. """ if len(actualEvents) != len(expectedEvents): test.assertEqual(actualEvents, expectedEvents) allMergedKeys = set() for event in expectedEvents: allMergedKeys |= set(event.keys()) def simplify(event: LogEvent) -> LogEvent: copy = event.copy() for key in event.keys(): if key not in allMergedKeys: copy.pop(key) return copy simplifiedActual = [simplify(event) for event in actualEvents] test.assertEqual(simplifiedActual, expectedEvents) class LogBeginnerTests(unittest.TestCase): """ Tests for L{LogBeginner}. """ def setUp(self) -> None: self.publisher = LogPublisher() self.errorStream = io.StringIO() class NotSys: stdout = object() stderr = object() class NotWarnings: def __init__(self) -> None: self.warnings: List[ Tuple[ str, Type[Warning], str, int, Optional[IO[Any]], Optional[int] ] ] = [] def showwarning( self, message: str, category: Type[Warning], filename: str, lineno: int, file: Optional[IO[Any]] = None, line: Optional[int] = None, ) -> None: """ Emulate warnings.showwarning. @param message: A warning message to emit. @param category: A warning category to associate with C{message}. @param filename: A file name for the source code file issuing the warning. @param lineno: A line number in the source file where the warning was issued. @param file: A file to write the warning message to. If L{None}, write to L{sys.stderr}. @param line: A line of source code to include with the warning message. If L{None}, attempt to read the line from C{filename} and C{lineno}. """ self.warnings.append((message, category, filename, lineno, file, line)) self.sysModule = NotSys() self.warningsModule = NotWarnings() self.beginner = LogBeginner( self.publisher, self.errorStream, self.sysModule, self.warningsModule ) def test_beginLoggingToAddObservers(self) -> None: """ Test that C{beginLoggingTo()} adds observers. """ event = dict(foo=1, bar=2) events1: List[LogEvent] = [] events2: List[LogEvent] = [] o1 = cast(ILogObserver, lambda e: events1.append(e)) o2 = cast(ILogObserver, lambda e: events2.append(e)) self.beginner.beginLoggingTo((o1, o2)) self.publisher(event) self.assertEqual([event], events1) self.assertEqual([event], events2) def test_beginLoggingToBufferedEvents(self) -> None: """ Test that events are buffered until C{beginLoggingTo()} is called. """ event = dict(foo=1, bar=2) events1: List[LogEvent] = [] events2: List[LogEvent] = [] o1 = cast(ILogObserver, lambda e: events1.append(e)) o2 = cast(ILogObserver, lambda e: events2.append(e)) self.publisher(event) # Before beginLoggingTo; this is buffered self.beginner.beginLoggingTo((o1, o2)) self.assertEqual([event], events1) self.assertEqual([event], events2) def _bufferLimitTest(self, limit: int, beginner: LogBeginner) -> None: """ Verify that when more than C{limit} events are logged to L{LogBeginner}, only the last C{limit} are replayed by L{LogBeginner.beginLoggingTo}. @param limit: The maximum number of events the log beginner should buffer. @param beginner: The L{LogBeginner} against which to verify. @raise: C{self.failureException} if the wrong events are replayed by C{beginner}. """ for count in range(limit + 1): self.publisher(dict(count=count)) events: List[LogEvent] = [] beginner.beginLoggingTo([cast(ILogObserver, events.append)]) self.assertEqual( list(range(1, limit + 1)), list(event["count"] for event in events), ) def test_defaultBufferLimit(self) -> None: """ Up to C{LogBeginner._DEFAULT_BUFFER_SIZE} log events are buffered for replay by L{LogBeginner.beginLoggingTo}. """ limit = LogBeginner._DEFAULT_BUFFER_SIZE self._bufferLimitTest(limit, self.beginner) def test_overrideBufferLimit(self) -> None: """ The size of the L{LogBeginner} event buffer can be overridden with the C{initialBufferSize} initilizer argument. """ limit = 3 beginner = LogBeginner( self.publisher, self.errorStream, self.sysModule, self.warningsModule, initialBufferSize=limit, ) self._bufferLimitTest(limit, beginner) def test_beginLoggingToTwice(self) -> None: """ When invoked twice, L{LogBeginner.beginLoggingTo} will emit a log message warning the user that they previously began logging, and add the new log observers. """ events1: List[LogEvent] = [] events2: List[LogEvent] = [] fileHandle = io.StringIO() textObserver = textFileLogObserver(fileHandle) self.publisher(dict(event="prebuffer")) firstFilename, firstLine = nextLine() self.beginner.beginLoggingTo([cast(ILogObserver, events1.append), textObserver]) self.publisher(dict(event="postbuffer")) secondFilename, secondLine = nextLine() self.beginner.beginLoggingTo([cast(ILogObserver, events2.append), textObserver]) self.publisher(dict(event="postwarn")) warning = dict( log_format=MORE_THAN_ONCE_WARNING, log_level=LogLevel.warn, fileNow=secondFilename, lineNow=secondLine, fileThen=firstFilename, lineThen=firstLine, ) self.maxDiff = None compareEvents( self, events1, [ dict(event="prebuffer"), dict(event="postbuffer"), warning, dict(event="postwarn"), ], ) compareEvents(self, events2, [warning, dict(event="postwarn")]) output = fileHandle.getvalue() self.assertIn(f"<{firstFilename}:{firstLine}>", output) self.assertIn(f"<{secondFilename}:{secondLine}>", output) def test_criticalLogging(self) -> None: """ Critical messages will be written as text to the error stream. """ log = Logger(observer=self.publisher) log.info("ignore this") log.critical("a critical {message}", message="message") self.assertEqual(self.errorStream.getvalue(), "a critical message\n") def test_criticalLoggingStops(self) -> None: """ Once logging has begun with C{beginLoggingTo}, critical messages are no longer written to the output stream. """ log = Logger(observer=self.publisher) self.beginner.beginLoggingTo(()) log.critical("another critical message") self.assertEqual(self.errorStream.getvalue(), "") def test_beginLoggingToRedirectStandardIO(self) -> None: """ L{LogBeginner.beginLoggingTo} will re-direct the standard output and error streams by setting the C{stdio} and C{stderr} attributes on its sys module object. """ events: List[LogEvent] = [] self.beginner.beginLoggingTo([cast(ILogObserver, events.append)]) print("Hello, world.", file=cast(TextIO, self.sysModule.stdout)) compareEvents( self, events, [dict(log_namespace="stdout", log_io="Hello, world.")] ) del events[:] print("Error, world.", file=cast(TextIO, self.sysModule.stderr)) compareEvents( self, events, [dict(log_namespace="stderr", log_io="Error, world.")] ) def test_beginLoggingToDontRedirect(self) -> None: """ L{LogBeginner.beginLoggingTo} will leave the existing stdout/stderr in place if it has been told not to replace them. """ oldOut = self.sysModule.stdout oldErr = self.sysModule.stderr self.beginner.beginLoggingTo((), redirectStandardIO=False) self.assertIs(self.sysModule.stdout, oldOut) self.assertIs(self.sysModule.stderr, oldErr) def test_beginLoggingToPreservesEncoding(self) -> None: """ When L{LogBeginner.beginLoggingTo} redirects stdout/stderr streams, the replacement streams will preserve the encoding of the replaced streams, to minimally disrupt any application relying on a specific encoding. """ weird = io.TextIOWrapper(io.BytesIO(), "shift-JIS") weirderr = io.TextIOWrapper(io.BytesIO(), "big5") self.sysModule.stdout = weird self.sysModule.stderr = weirderr events: List[LogEvent] = [] self.beginner.beginLoggingTo([cast(ILogObserver, events.append)]) stdout = cast(TextIO, self.sysModule.stdout) stderr = cast(TextIO, self.sysModule.stderr) self.assertEqual(stdout.encoding, "shift-JIS") self.assertEqual(stderr.encoding, "big5") stdout.write(b"\x97\x9B\n") # type: ignore[call-overload] stderr.write(b"\xBC\xFC\n") # type: ignore[call-overload] compareEvents(self, events, [dict(log_io="\u674e"), dict(log_io="\u7469")]) def test_warningsModule(self) -> None: """ L{LogBeginner.beginLoggingTo} will redirect the warnings of its warnings module into the logging system. """ self.warningsModule.showwarning("a message", DeprecationWarning, __file__, 1) events: List[LogEvent] = [] self.beginner.beginLoggingTo([cast(ILogObserver, events.append)]) self.warningsModule.showwarning( "another message", DeprecationWarning, __file__, 2 ) f = io.StringIO() self.warningsModule.showwarning( "yet another", DeprecationWarning, __file__, 3, file=f ) self.assertEqual( self.warningsModule.warnings, [ ("a message", DeprecationWarning, __file__, 1, None, None), ("yet another", DeprecationWarning, __file__, 3, f, None), ], ) compareEvents( self, events, [ dict( warning="another message", category=( DeprecationWarning.__module__ + "." + DeprecationWarning.__name__ ), filename=__file__, lineno=2, ) ], ) def test_failuresAppendTracebacks(self) -> None: """ The string resulting from a logged failure contains a traceback. """ f = Failure(Exception("this is not the behavior you are looking for")) log = Logger(observer=self.publisher) log.failure("a failure", failure=f) msg = self.errorStream.getvalue() self.assertIn("a failure", msg) self.assertIn("this is not the behavior you are looking for", msg) self.assertIn("Traceback", msg)