Hide stderr output in unit tests
Asked Answered
T

4

16

I'm writing a few unit tests of some code which uses sys.stderr.write to report errors in input. This is as it should be, but this clobbers the unit test output. Is there any way to tell Python to not output error messages for single commands, à la 2> /dev/null?

Templin answered 27/11, 2009 at 17:47 Comment(0)
G
27

I suggest writing a context manager:

import contextlib
import sys

@contextlib.contextmanager
def nostderr():
    savestderr = sys.stderr
    class Devnull(object):
        def write(self, _): pass
        def flush(self): pass
    sys.stderr = Devnull()
    try:
        yield
    finally:
        sys.stderr = savestderr

Now, wrap any code snippet whose stderr you want suppressed in a with nostderr(): and you have the localized, temporary, guaranteed-reversible stderr suppression that you want.

Gravitate answered 27/11, 2009 at 18:20 Comment(4)
Wow! This is wonderful. Wasn't aware of contextlib before coz I still use Python 2.4.Erinn
@shailesh, yes, contextlib facilitates the writing of most simple context managers (writing a class with __enter__ and __exit__ isn't hard of course, but this way, for sufficiently simple cases, you have even less boilerplate code to write and read;-).Gravitate
Why not sys.stderr = os.devnullIngeingeberg
@Matt3o12: Because os.devnull is simply a string ("/dev/null" on Linux systems, for example), not a file descriptor.Metallurgy
D
12

You could create a dummy file object that did nothing with its output, and set stderr to that:

class NullWriter:
    def write(self, s):
        pass

sys.stderr = NullWriter()

If you only want to quiet stderr for a specific duration, you can use a with statement like so:

class Quieter:
    def __enter__(self):
        self.old_stderr = sys.stderr
        sys.stderr = NullWriter()

    def __exit__(self, type, value, traceback):
        sys.stderr = self.old_stderr

with Quieter():
    # Do stuff; stderr will be suppressed, and it will be restored
    # when this block exits

Requires Python 2.6 or higher, or you can use it in Python 2.5 with a from __future__ import with_statement.

Devorahdevore answered 27/11, 2009 at 17:49 Comment(0)
E
5

Another possibility (besides assigning to sys.stderr) is to structure your code to write errors to a file provided, but to default that file to sys.stderr. Then you can provide a DevNull writer during testing.

If you do want to reassign sys.stderr, you can use the unittest framework to manage it for you:

class DevNull(object):
    def write(self, data): 
        pass

class MyTestCase(unittest.TestCase):
    def setUp(self):
        self.old_stderr = sys.stderr
        sys.stderr = DevNull()

    def tearDown(self):
        sys.stderr = self.old_stderr

This way, every test dev-null's stderr, but then restores it at the end of the test.

Effieeffigy answered 27/11, 2009 at 17:53 Comment(1)
This also has the advantage that you could check the output in your test if you wanted to.Dermatophyte
E
4
class DevNull(object):
    def write(self, data): pass

sys.stderr = DevNull()

To have a less permanent solution, one could figure out something like as follows:

_stderr = None
def quiet():
    global _stderr
    if _stderr is None:
        _stderr = sys.stderr
        sys.stderr = DevNull()

def verbose():
   global _stderr
   if _stderr is not None:
       sys.stderr = _stderr
      _stderr = None

Function names can probably be better

Erinn answered 27/11, 2009 at 17:49 Comment(3)
I was hoping for a less permanent solution. Is there no way to wrap a command like quiet(...)?Templin
No need to store a backup. Python already have one sys.__stderr__ docs.python.org/library/sys.html#sys.__stderr__Unnecessary
The reason to store the backup is so that nothing is absolute: who knows if some other larger code entity changed sys.stderr before you got invoked? This way, you are simply undoing your first action.Effieeffigy

© 2022 - 2024 — McMap. All rights reserved.