99import contextlib
1010import sys
1111from typing import Generator
12+ import warnings
1213
1314if sys .version_info >= (3 ,11 ):
1415from typing import assert_type
2627"""Beginning text of USE_SHELL deprecation warnings when USE_SHELL is set True."""
2728
2829
30+ @contextlib .contextmanager
31+ def _suppress_deprecation_warning ()-> Generator [None ,None ,None ]:
32+ with warnings .catch_warnings ():
33+ warnings .filterwarnings ("ignore" ,category = DeprecationWarning )
34+ yield
35+
36+
2937@pytest .fixture
30- def reset_backing_attribute ()-> Generator [None ,None ,None ]:
31- """Fixture toreset the private ``_USE_SHELL `` attribute.
38+ def try_restore_use_shell_state ()-> Generator [None ,None ,None ]:
39+ """Fixture toattempt to restore state associated with the ``USE_SHELL `` attribute.
3240
3341 This is used to decrease the likelihood of state changes leaking out and affecting
3442 other tests. But the goal is not to assert that ``_USE_SHELL`` is used, nor anything
@@ -38,18 +46,27 @@ def reset_backing_attribute() -> Generator[None, None, None]:
3846 restores attributes that it has previously been used to change, create, or remove.
3947 """
4048no_value = object ()
49+
4150try :
42- old_value = Git ._USE_SHELL
51+ old_backing_value = Git ._USE_SHELL
4352except AttributeError :
44- old_value = no_value
53+ old_backing_value = no_value
54+ try :
55+ with _suppress_deprecation_warning ():
56+ old_public_value = Git .USE_SHELL
4557
46- yield
58+ # This doesn't have its own try-finally because pytest catches exceptions raised
59+ # during the yield. (The outer try-finally catches exceptions in this fixture.)
60+ yield
4761
48- if old_value is no_value :
49- with contextlib .suppress (AttributeError ):
50- del Git ._USE_SHELL
51- else :
52- Git ._USE_SHELL = old_value
62+ with _suppress_deprecation_warning ():
63+ Git .USE_SHELL = old_public_value
64+ finally :
65+ if old_backing_value is no_value :
66+ with contextlib .suppress (AttributeError ):
67+ del Git ._USE_SHELL
68+ else :
69+ Git ._USE_SHELL = old_backing_value
5370
5471
5572def test_cannot_access_undefined_on_git_class ()-> None :
@@ -76,23 +93,26 @@ def test_get_use_shell_on_class_default() -> None:
7693assert not use_shell
7794
7895
79- # FIXME: More robustly check that each operation really issues exactly one deprecation
80- # warning, even if this requires relying more on reset_backing_attribute doing its job.
81- def test_use_shell_on_class (reset_backing_attribute )-> None :
96+ def test_use_shell_on_class (try_restore_use_shell_state )-> None :
8297"""USE_SHELL can be written and re-read as a class attribute, always warning."""
83- # We assert in a "safe" order, using reset_backing_attribute only as a backstop.
84- with pytest .deprecated_call ()as ctx :
98+ with pytest .deprecated_call ()as setting :
8599Git .USE_SHELL = True
100+ with pytest .deprecated_call ()as checking :
86101set_value = Git .USE_SHELL
102+ with pytest .deprecated_call ()as resetting :
87103Git .USE_SHELL = False
104+ with pytest .deprecated_call ()as rechecking :
88105reset_value = Git .USE_SHELL
89106
90107# The attribute should take on the values set to it.
91108assert set_value is True
92109assert reset_value is False
93110
94- messages = [str (entry .message )for entry in ctx ]
95- set_message ,check_message ,reset_message ,recheck_message = messages
111+ # Each access should warn exactly once.
112+ (set_message ,)= [str (entry .message )for entry in setting ]
113+ (check_message ,)= [str (entry .message )for entry in checking ]
114+ (reset_message ,)= [str (entry .message )for entry in resetting ]
115+ (recheck_message ,)= [str (entry .message )for entry in rechecking ]
96116
97117# Setting it to True should produce the special warning for that.
98118assert _USE_SHELL_DEPRECATED_FRAGMENT in set_message