From 211943d39f65b197ff197b207c69b49470b0392d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 16 Feb 2020 23:27:37 +0100 Subject: [PATCH 1/3] capture: do not delete attributes Especially for `_old`, which was still used unchecked in `__repr__`. --- src/_pytest/capture.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index d3f10da2f14..ea4eb8d96e7 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -438,6 +438,7 @@ def __getattr__(self, name): class MultiCapture: out = err = in_ = None _state = None + _in_suspended = False def __init__(self, out=True, err=True, in_=True, Capture=None): if in_: @@ -449,11 +450,7 @@ def __init__(self, out=True, err=True, in_=True, Capture=None): def __repr__(self): return "".format( - self.out, - self.err, - self.in_, - self._state, - getattr(self, "_in_suspended", ""), + self.out, self.err, self.in_, self._state, self._in_suspended, ) def start_capturing(self): @@ -490,9 +487,9 @@ def resume_capturing(self): self.out.resume() if self.err: self.err.resume() - if hasattr(self, "_in_suspended"): + if self._in_suspended: self.in_.resume() - del self._in_suspended + self._in_suspended = False def stop_capturing(self): """ stop capturing and reset capturing streams """ @@ -653,7 +650,7 @@ def snap(self): def done(self): setattr(sys, self.name, self._old) - del self._old + self._old = None self.tmpfile.close() self._state = "done" From 1c1d2b3f6318d02bc2ae4282cb226245c6d92d3f Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 18 Feb 2020 22:19:02 +0100 Subject: [PATCH 2/3] keep SysCapture._old, improve/align __repr__ --- src/_pytest/capture.py | 18 +++++++++++++----- testing/test_capture.py | 12 ++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index ea4eb8d96e7..29462ea6415 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -552,8 +552,12 @@ def __init__(self, targetfd, tmpfile=None): self.tmpfile_fd = tmpfile.fileno() def __repr__(self): - return "".format( - self.targetfd, getattr(self, "targetfd_save", None), self._state + return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format( + self.__class__.__name__, + self.targetfd, + getattr(self, "targetfd_save", ""), + self._state, + self.tmpfile, ) def _start(self): @@ -634,8 +638,12 @@ def __init__(self, fd, tmpfile=None): self.tmpfile = tmpfile def __repr__(self): - return "".format( - self.name, self._old, self.tmpfile, self._state + return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( + self.__class__.__name__, + self.name, + hasattr(self, "_old") and repr(self._old) or "", + self._state, + self.tmpfile, ) def start(self): @@ -650,7 +658,7 @@ def snap(self): def done(self): setattr(sys, self.name, self._old) - self._old = None + del self._old self.tmpfile.close() self._state = "done" diff --git a/testing/test_capture.py b/testing/test_capture.py index 7447d97428b..2d452996541 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1005,6 +1005,18 @@ def test_simple_resume_suspend(self): cap.done() pytest.raises(AttributeError, cap.suspend) + assert repr(cap) == ( + " _state='done' tmpfile={!r}>".format( + cap.tmpfile + ) + ) + # Should not crash with missing "_old". + assert repr(cap.syscapture) == ( + " _state='done' tmpfile={!r}>".format( + cap.syscapture.tmpfile + ) + ) + def test_capfd_sys_stdout_mode(self, capfd): assert "b" not in sys.stdout.mode From facf3aac25403354de78b2240a0347025776461e Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 18 Feb 2020 22:30:19 +0100 Subject: [PATCH 3/3] fixup! keep SysCapture._old, improve/align __repr__ --- src/_pytest/capture.py | 4 ++-- testing/test_capture.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 29462ea6415..54c0842055c 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -552,12 +552,12 @@ def __init__(self, targetfd, tmpfile=None): self.tmpfile_fd = tmpfile.fileno() def __repr__(self): - return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format( + return "<{} {} oldfd={} _state={!r} tmpfile={}>".format( self.__class__.__name__, self.targetfd, getattr(self, "targetfd_save", ""), self._state, - self.tmpfile, + hasattr(self, "tmpfile") and repr(self.tmpfile) or "", ) def _start(self): diff --git a/testing/test_capture.py b/testing/test_capture.py index 2d452996541..e8e09ae73aa 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1224,19 +1224,19 @@ def StdCaptureFD(out=True, err=True, in_=True): def test_stdout(): os.close(1) cap = StdCaptureFD(out=True, err=False, in_=False) - assert repr(cap.out) == "" + assert repr(cap.out) == " _state=None tmpfile=>" cap.stop_capturing() def test_stderr(): os.close(2) cap = StdCaptureFD(out=False, err=True, in_=False) - assert repr(cap.err) == "" + assert repr(cap.err) == " _state=None tmpfile=>" cap.stop_capturing() def test_stdin(): os.close(0) cap = StdCaptureFD(out=False, err=False, in_=True) - assert repr(cap.in_) == "" + assert repr(cap.in_) == " _state=None tmpfile=>" cap.stop_capturing() """ )