Skip to content

Commit e725c82

Browse files
committed
Make the restore_use_shell_state fixture more robust
1 parent 6a35261 commit e725c82

File tree

1 file changed

+39
-13
lines changed

1 file changed

+39
-13
lines changed

test/deprecation/test_cmd_git.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"""
5555

5656
import contextlib
57+
import logging
5758
import sys
5859
from typing import Generator
5960
import unittest.mock
@@ -75,6 +76,8 @@
7576
_USE_SHELL_DANGEROUS_FRAGMENT = "Setting Git.USE_SHELL to True is unsafe and insecure"
7677
"""Beginning text of USE_SHELL deprecation warnings when USE_SHELL is set True."""
7778

79+
_logger = logging.getLogger(__name__)
80+
7881

7982
@contextlib.contextmanager
8083
def _suppress_deprecation_warning() -> Generator[None, None, None]:
@@ -85,37 +88,60 @@ def _suppress_deprecation_warning() -> Generator[None, None, None]:
8588

8689
@pytest.fixture
8790
def restore_use_shell_state() -> Generator[None, None, None]:
88-
"""Fixture to attempt to restore state associated with the ``USE_SHELL`` attribute.
91+
"""Fixture to attempt to restore state associated with the USE_SHELL attribute.
8992
9093
This is used to decrease the likelihood of state changes leaking out and affecting
91-
other tests. But the goal is not to assert that ``_USE_SHELL`` is used, nor anything
92-
about how or when it is used, which is an implementation detail subject to change.
94+
other tests. But the goal is not to assert implementation details of USE_SHELL.
95+
96+
This covers two of the common implementation strategies, for convenience in testing
97+
both. USE_SHELL could be implemented in the metaclass:
9398
94-
This is possible but inelegant to do with pytest's monkeypatch fixture, which only
95-
restores attributes that it has previously been used to change, create, or remove.
99+
* With a separate _USE_SHELL backing attribute. If using a property or other
100+
descriptor, this is the natural way to do it, but custom __getattribute__ and
101+
__setattr__ logic, if it does more than adding warnings, may also use that.
102+
* Like a simple attribute, using USE_SHELL itself, stored as usual in the class
103+
dictionary, with custom __getattribute__/__setattr__ logic only to warn.
104+
105+
This tries to save private state, tries to save the public attribute value, yields
106+
to the test case, tries to restore the public attribute value, then tries to restore
107+
private state. The idea is that if the getting or setting logic is wrong in the code
108+
under test, the state will still most likely be reset successfully.
96109
"""
97110
no_value = object()
98111

112+
# Try to save the original private state.
99113
try:
100-
old_backing_value = Git._USE_SHELL
114+
old_private_value = Git._USE_SHELL
101115
except AttributeError:
102-
old_backing_value = no_value
116+
separate_backing_attribute = False
117+
try:
118+
old_private_value = type.__getattribute__(Git, "USE_SHELL")
119+
except AttributeError:
120+
old_private_value = no_value
121+
_logger.error("Cannot retrieve old private _USE_SHELL or USE_SHELL value")
122+
else:
123+
separate_backing_attribute = True
124+
103125
try:
126+
# Try to save the original public value. Rather than attempt to restore a state
127+
# where the attribute is not set, if we cannot do this we allow AttributeError
128+
# to propagate out of the fixture, erroring the test case before its code runs.
104129
with _suppress_deprecation_warning():
105130
old_public_value = Git.USE_SHELL
106131

107132
# This doesn't have its own try-finally because pytest catches exceptions raised
108133
# during the yield. (The outer try-finally catches exceptions in this fixture.)
109134
yield
110135

136+
# Try to restore the original public value.
111137
with _suppress_deprecation_warning():
112138
Git.USE_SHELL = old_public_value
113139
finally:
114-
if old_backing_value is no_value:
115-
with contextlib.suppress(AttributeError):
116-
del Git._USE_SHELL
117-
else:
118-
Git._USE_SHELL = old_backing_value
140+
# Try to restore the original private state.
141+
if separate_backing_attribute:
142+
Git._USE_SHELL = old_private_value
143+
elif old_private_value is not no_value:
144+
type.__setattr__(Git, "USE_SHELL", old_private_value)
119145

120146

121147
def test_cannot_access_undefined_on_git_class() -> None:
@@ -277,7 +303,7 @@ def test_use_shell_is_mock_patchable_on_class_as_object_attribute(
277303
"""
278304
Git.USE_SHELL = original_value
279305
if Git.USE_SHELL is not original_value:
280-
raise RuntimeError(f"Can't set up the test")
306+
raise RuntimeError("Can't set up the test")
281307
new_value = not original_value
282308

283309
with unittest.mock.patch.object(Git, "USE_SHELL", new_value):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy