@@ -173,17 +173,9 @@ def stop(self):
173173 def _exc_info_to_string (self , err , test ):
174174 """Converts a sys.exc_info()-style tuple of values into a string."""
175175 exctype , value , tb = err
176- # Skip test runner traceback levels
177- while tb and self ._is_relevant_tb_level (tb ):
178- tb = tb .tb_next
179-
180- if exctype is test .failureException :
181- # Skip assert*() traceback levels
182- length = self ._count_relevant_tb_levels (tb )
183- else :
184- length = None
176+ tb = self ._clean_tracebacks (exctype , value , tb , test )
185177 tb_e = traceback .TracebackException (
186- exctype , value , tb , limit = length , capture_locals = self .tb_locals )
178+ exctype , value , tb , capture_locals = self .tb_locals )
187179 msgLines = list (tb_e .format ())
188180
189181 if self .buffer :
@@ -199,16 +191,49 @@ def _exc_info_to_string(self, err, test):
199191 msgLines .append (STDERR_LINE % error )
200192 return '' .join (msgLines )
201193
194+ def _clean_tracebacks (self , exctype , value , tb , test ):
195+ ret = None
196+ first = True
197+ excs = [(exctype , value , tb )]
198+ while excs :
199+ (exctype , value , tb ) = excs .pop ()
200+ # Skip test runner traceback levels
201+ while tb and self ._is_relevant_tb_level (tb ):
202+ tb = tb .tb_next
203+
204+ # Skip assert*() traceback levels
205+ if exctype is test .failureException :
206+ self ._remove_unittest_tb_frames (tb )
207+
208+ if first :
209+ ret = tb
210+ first = False
211+ else :
212+ value .__traceback__ = tb
213+
214+ if value is not None :
215+ for c in (value .__cause__ , value .__context__ ):
216+ if c is not None :
217+ excs .append ((type (c ), c , c .__traceback__ ))
218+ return ret
202219
203220 def _is_relevant_tb_level (self , tb ):
204221 return '__unittest' in tb .tb_frame .f_globals
205222
206- def _count_relevant_tb_levels (self , tb ):
207- length = 0
223+ def _remove_unittest_tb_frames (self , tb ):
224+ '''Truncates usercode tb at the first unittest frame.
225+
226+ If the first frame of the traceback is in user code,
227+ the prefix up to the first unittest frame is returned.
228+ If the first frame is already in the unittest module,
229+ the traceback is not modified.
230+ '''
231+ prev = None
208232 while tb and not self ._is_relevant_tb_level (tb ):
209- length += 1
233+ prev = tb
210234 tb = tb .tb_next
211- return length
235+ if prev is not None :
236+ prev .tb_next = None
212237
213238 def __repr__ (self ):
214239 return ("<%s run=%i errors=%i failures=%i>" %
0 commit comments