Skip to content

Commit

Permalink
tests: Prevent “unittest --buffer” from crashing
Browse files Browse the repository at this point in the history
Before this change, if certain tests were failing in certain ways, then
running “python -m unittest --buffer” would cause an AttributeError in
the unittest module itself.

Here’s what unittest does when you use the --buffer argument:

1. It sets sys.stdout and sys.stderr to StringIOs [1].
2. It runs a test.
3. If the test failed, it runs getvalue() on sys.stdout and sys.stderr
   to get data from its StringIOs.

tests/test_cli.py has its own RunContext class that does something
similar. Before this change, here’s what could happen:

1. unittest sets sys.stdout and sys.stderr to StringIOs.
2. unittest runs a test that uses RunContext.
3. A RunContext gets entered. It sets sys.stdout and sys.stderr to its
   own StringIOs.
4. The RunContext gets exited. It sets sys.stdout and sys.stderr to
   sys.__stdout__ and sys.__stderr__.
5. The test fails.
6. unittest assumes that sys.stdout is still set to one of its
   StringIOs, and runs sys.stdout.getvalue().
7. unittest crashes with this error:

	AttributeError: '_io.TextIOWrapper' object has no attribute 'getvalue'

[1]: <https://github.com/python/cpython/blob/2305ca51448552542b2414186252123a8dc87db7/Lib/unittest/result.py#L65>
[2]: <https://github.com/python/cpython/blob/2305ca51448552542b2414186252123a8dc87db7/Lib/unittest/result.py#L87>
  • Loading branch information
Jayman2000 committed Dec 30, 2023
1 parent 52b40c8 commit 37c2d5e
Showing 1 changed file with 6 additions and 2 deletions.
8 changes: 6 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ def __init__(self, case):

def __enter__(self):
self._raises_ctx.__enter__()
self.old_sys_stdout = sys.stdout
self.old_sys_stderr = sys.stderr
sys.stdout = self.outstream = StringIO()
sys.stderr = self.errstream = StringIO()
return self

def __exit__(self, *exc_info):
self.stdout, sys.stdout = self.outstream.getvalue(), sys.__stdout__
self.stderr, sys.stderr = self.errstream.getvalue(), sys.__stderr__
self.stdout = self.outstream.getvalue()
self.stderr = self.errstream.getvalue()
sys.stdout = self.old_sys_stdout
sys.stderr = self.old_sys_stderr
return self._raises_ctx.__exit__(*exc_info)

@property
Expand Down

0 comments on commit 37c2d5e

Please sign in to comment.