22
33import pytest
44
5+ from pluggy ._tracing import saferepr , DEFAULT_REPR_MAX_SIZE
56from pluggy ._tracing import TagTracer
67
78
@@ -77,3 +78,173 @@ def test_setprocessor(rootlogger: TagTracer) -> None:
7778 log2 ("seen" )
7879 tags , args = l2 [0 ]
7980 assert args == ("seen" ,)
81+
82+ def test_saferepr_simple_repr ():
83+ assert saferepr (1 ) == "1"
84+ assert saferepr (None ) == "None"
85+
86+
87+ def test_saferepr_maxsize ():
88+ s = saferepr ("x" * 50 , maxsize = 25 )
89+ assert len (s ) == 25
90+ expected = repr ("x" * 10 + "..." + "x" * 10 )
91+ assert s == expected
92+
93+
94+ def test_saferepr_no_maxsize ():
95+ text = "x" * DEFAULT_REPR_MAX_SIZE * 10
96+ s = saferepr (text , maxsize = None )
97+ expected = repr (text )
98+ assert s == expected
99+
100+
101+ def test_saferepr_maxsize_error_on_instance ():
102+ class A :
103+ def __repr__ (self ):
104+ raise ValueError ("..." )
105+
106+ s = saferepr (("*" * 50 , A ()), maxsize = 25 )
107+ assert len (s ) == 25
108+ assert s [0 ] == "(" and s [- 1 ] == ")"
109+
110+
111+ def test_saferepr_exceptions () -> None :
112+ class BrokenRepr :
113+ def __init__ (self , ex ):
114+ self .ex = ex
115+
116+ def __repr__ (self ):
117+ raise self .ex
118+
119+ class BrokenReprException (Exception ):
120+ __str__ = None # type: ignore[assignment]
121+ __repr__ = None # type: ignore[assignment]
122+
123+ assert "Exception" in saferepr (BrokenRepr (Exception ("broken" )))
124+ s = saferepr (BrokenReprException ("really broken" ))
125+ assert "TypeError" in s
126+ assert "TypeError" in saferepr (BrokenRepr ("string" ))
127+
128+ none = None
129+ try :
130+ none () # type: ignore[misc]
131+ except BaseException as exc :
132+ exp_exc = repr (exc )
133+ obj = BrokenRepr (BrokenReprException ("omg even worse" ))
134+ s2 = saferepr (obj )
135+ assert s2 == (
136+ "<[unpresentable exception ({!s}) raised in repr()] BrokenRepr object at 0x{:x}>" .format (
137+ exp_exc , id (obj )
138+ )
139+ )
140+
141+
142+ def test_saferepr_baseexception ():
143+ """Test saferepr() with BaseExceptions, which includes pytest outcomes."""
144+
145+ class RaisingOnStrRepr (BaseException ):
146+ def __init__ (self , exc_types ):
147+ self .exc_types = exc_types
148+
149+ def raise_exc (self , * args ):
150+ try :
151+ self .exc_type = self .exc_types .pop (0 )
152+ except IndexError :
153+ pass
154+ if hasattr (self .exc_type , "__call__" ):
155+ raise self .exc_type (* args )
156+ raise self .exc_type
157+
158+ def __str__ (self ):
159+ self .raise_exc ("__str__" )
160+
161+ def __repr__ (self ):
162+ self .raise_exc ("__repr__" )
163+
164+ class BrokenObj :
165+ def __init__ (self , exc ):
166+ self .exc = exc
167+
168+ def __repr__ (self ):
169+ raise self .exc
170+
171+ __str__ = __repr__
172+
173+ baseexc_str = BaseException ("__str__" )
174+ obj = BrokenObj (RaisingOnStrRepr ([BaseException ]))
175+ assert saferepr (obj ) == (
176+ "<[unpresentable exception ({!r}) "
177+ "raised in repr()] BrokenObj object at 0x{:x}>" .format (baseexc_str , id (obj ))
178+ )
179+ obj = BrokenObj (RaisingOnStrRepr ([RaisingOnStrRepr ([BaseException ])]))
180+ assert saferepr (obj ) == (
181+ "<[{!r} raised in repr()] BrokenObj object at 0x{:x}>" .format (
182+ baseexc_str , id (obj )
183+ )
184+ )
185+
186+ with pytest .raises (KeyboardInterrupt ):
187+ saferepr (BrokenObj (KeyboardInterrupt ()))
188+
189+ with pytest .raises (SystemExit ):
190+ saferepr (BrokenObj (SystemExit ()))
191+
192+ with pytest .raises (KeyboardInterrupt ):
193+ saferepr (BrokenObj (RaisingOnStrRepr ([KeyboardInterrupt ])))
194+
195+ with pytest .raises (SystemExit ):
196+ saferepr (BrokenObj (RaisingOnStrRepr ([SystemExit ])))
197+
198+ with pytest .raises (KeyboardInterrupt ):
199+ print (saferepr (BrokenObj (RaisingOnStrRepr ([BaseException , KeyboardInterrupt ]))))
200+
201+ with pytest .raises (SystemExit ):
202+ saferepr (BrokenObj (RaisingOnStrRepr ([BaseException , SystemExit ])))
203+
204+
205+ def test_saferepr_buggy_builtin_repr ():
206+ # Simulate a case where a repr for a builtin raises.
207+ # reprlib dispatches by type name, so use "int".
208+
209+ class int :
210+ def __repr__ (self ):
211+ raise ValueError ("Buggy repr!" )
212+
213+ assert "Buggy" in saferepr (int ())
214+
215+
216+ def test_saferepr_big_repr ():
217+ from _pytest ._io .saferepr import SafeRepr
218+
219+ assert len (saferepr (range (1000 ))) <= len ("[" + SafeRepr (0 ).maxlist * "1000" + "]" )
220+
221+
222+ def test_saferepr_repr_on_newstyle () -> None :
223+ class Function :
224+ def __repr__ (self ):
225+ return "<%s>" % (self .name ) # type: ignore[attr-defined]
226+
227+ assert saferepr (Function ())
228+
229+
230+ def test_saferepr_unicode ():
231+ val = "£€"
232+ reprval = "'£€'"
233+ assert saferepr (val ) == reprval
234+
235+
236+ def test_saferepr_broken_getattribute ():
237+ """saferepr() can create proper representations of classes with
238+ broken __getattribute__ (#7145)
239+ """
240+
241+ class SomeClass :
242+ def __getattribute__ (self , attr ):
243+ raise RuntimeError
244+
245+ def __repr__ (self ):
246+ raise RuntimeError
247+
248+ assert saferepr (SomeClass ()).startswith (
249+ "<[RuntimeError() raised in repr()] SomeClass object at 0x"
250+ )
0 commit comments