From 73534cf6d071c8a2fdb1ad72b6c8e567d72601e9 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 16 Jan 2017 12:32:25 -0600 Subject: [PATCH 1/2] Add support for attaching logs on error. Sometimes, code being tested can produce a large amount of output which is needed to debug issues. Adding that output to successful tests can lead to blowing out size maximums for a full test suite run, even if only one test fails, because of adding the log streaming output to every test all the time. To empower users to only collect that output on failures, expose a flag on the constructor which causes addOnException to be used instead of the normal addDetail. The default stays at off so existing users should not see a behavior change. --- fixtures/_fixtures/logger.py | 10 ++++++++-- fixtures/_fixtures/streams.py | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/fixtures/_fixtures/logger.py b/fixtures/_fixtures/logger.py index b60b7ec..de99407 100644 --- a/fixtures/_fixtures/logger.py +++ b/fixtures/_fixtures/logger.py @@ -73,7 +73,8 @@ class FakeLogger(Fixture): """Replace a logger and capture its output.""" def __init__(self, name="", level=INFO, format=None, - datefmt=None, nuke_handlers=True, formatter=None): + datefmt=None, nuke_handlers=True, formatter=None, + only_on_error=False): """Create a FakeLogger fixture. :param name: The name of the logger to replace. Defaults to "". @@ -86,6 +87,9 @@ def __init__(self, name="", level=INFO, format=None, existing messages going to e.g. stdout). Defaults to True. :param formatter: a custom log formatter class. Use this if you want to use a log Formatter other than the default one in python. + :param only_on_error: Only attach the captured output to the TestResult + if the test fails. This can be important for some test suites where + the full debug logging needed is enormous. Example: @@ -101,10 +105,12 @@ def test_log(self) self._datefmt = datefmt self._nuke_handlers = nuke_handlers self._formatter = formatter + self._only_on_error = only_on_error def _setUp(self): name = _u("pythonlogging:'%s'") % self._name - output = self.useFixture(StringStream(name)).stream + output = self.useFixture( + StringStream(name, only_on_error=self._only_on_error)).stream self._output = output handler = StreamHandlerRaiseException(output) if self._format: diff --git a/fixtures/_fixtures/streams.py b/fixtures/_fixtures/streams.py index 8ceb4fc..1627145 100644 --- a/fixtures/_fixtures/streams.py +++ b/fixtures/_fixtures/streams.py @@ -32,17 +32,25 @@ class Stream(Fixture): :attr stream: The file-like object. """ - def __init__(self, detail_name, stream_factory): + def __init__(self, detail_name, stream_factory, only_on_error=False): """Create a ByteStream. :param detail_name: Use this as the name of the stream. :param stream_factory: Called to construct a pair of streams: (write_stream, content_stream). + :param only_on_error: Only attach the stream output if an error occurs. """ self._detail_name = detail_name self._stream_factory = stream_factory + self._only_on_error = only_on_error def _setUp(self): + if self._only_on_error: + self.addOnException(self._add_stream_detail) + else: + self._add_stream_detail() + + def _add_stream_detail(self): write_stream, read_stream = self._stream_factory() self.stream = write_stream self.addDetail(self._detail_name, From 798d885159176f6d2ac4057c167773d0ebcc79ea Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 16 Jan 2017 12:46:34 -0600 Subject: [PATCH 2/2] Plumb only_on_error through StringStream facade. --- fixtures/_fixtures/streams.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/fixtures/_fixtures/streams.py b/fixtures/_fixtures/streams.py index 1627145..28b3c66 100644 --- a/fixtures/_fixtures/streams.py +++ b/fixtures/_fixtures/streams.py @@ -45,16 +45,18 @@ def __init__(self, detail_name, stream_factory, only_on_error=False): self._only_on_error = only_on_error def _setUp(self): + write_stream, read_stream = self._stream_factory() + self.stream = write_stream + self._read_stream = read_stream if self._only_on_error: self.addOnException(self._add_stream_detail) else: self._add_stream_detail() def _add_stream_detail(self): - write_stream, read_stream = self._stream_factory() - self.stream = write_stream self.addDetail(self._detail_name, - testtools.content.content_from_stream(read_stream, seek_offset=0)) + testtools.content.content_from_stream( + self._read_stream, seek_offset=0)) def _byte_stream_factory(): @@ -62,14 +64,16 @@ def _byte_stream_factory(): return (result, result) -def ByteStream(detail_name): +def ByteStream(detail_name, only_on_error=only_on_error): """Provide a file-like object that accepts bytes and expose as a detail. :param detail_name: The name of the detail. + :param only_on_error: Only attach the stream output if an error occurs. :return: A fixture which has an attribute `stream` containing the file-like object. """ - return Stream(detail_name, _byte_stream_factory) + return Stream( + detail_name, _byte_stream_factory, only_on_error=only_on_error) def _string_stream_factory(): @@ -89,14 +93,16 @@ def safe_write(str_or_bytes): return upper, lower -def StringStream(detail_name): +def StringStream(detail_name, only_on_error=False): """Provide a file-like object that accepts strings and expose as a detail. :param detail_name: The name of the detail. + :param only_on_error: Only attach the stream output if an error occurs. :return: A fixture which has an attribute `stream` containing the file-like object. """ - return Stream(detail_name, _string_stream_factory) + return Stream( + detail_name, _string_stream_factory, only_on_error=only_on_error) def DetailStream(detail_name):