From 37c2d5e834de93556f55a0dff70f8f1fd166ef6d Mon Sep 17 00:00:00 2001 From: Jason Yundt Date: Sat, 30 Dec 2023 05:40:15 -0500 Subject: [PATCH] =?UTF-8?q?tests:=20Prevent=20=E2=80=9Cunittest=20--buffer?= =?UTF-8?q?=E2=80=9D=20from=20crashing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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]: [2]: --- tests/test_cli.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index d42865da..28a05999 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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