
    ixD                       d Z ddlmZ ddlZddlmc mZ ddl	Z	e	j                  j                  dd       ddlZddlZddlmZmZmZ ddlmZmZ ddlmZmZ d Z G d	 d
e      Z	 	 d:dZd;dZd Zd Zd Zd Zd Z d Z!d Z"d Z#d Z$d Z%d Z&d Z'd Z(d Z)d Z*d Z+d Z,d Z-e.dk(  rddl/Z/d efd!efd"efd#efd$e fd%e!fd&e"fd'e#fd(e$fd)e%fd*e&fd+e'fd,e(fd-e)fd.e*fd/e+fd0e,fd1e-fgZ0dZ1 e2e0      Z3e0D ]  \  Z4Z5	  e5         e6d2e4        e1d3z  Z1  e6d6e1 d7e3 d8       e1e3k(  r	 e6d9       y e	jt                  d3       yy# e7$ r)Z8 e6d4e4 d5e8         e/jr                          Y dZ8[8zdZ8[8ww xY w)<ua  
Tests for Story 6.04 (Track B): SwarmWorkerBase — XREADGROUP Consumer

Black Box tests (BB): verify public contract — ACK on success, no ACK on
    failure, stop() exits loop, _reclaim_pending runs on startup.
White Box tests (WB): verify internal mechanics — XREADGROUP uses ">" for
    new messages, XAUTOCLAIM called with min_idle_time=60000, bytes decoded,
    process() is abstract, PEL_TIMEOUT_MS constant, staging.submit_delta called.

Story: 6.04
File under test: core/coherence/swarm_worker_base.py

ALL tests use mocks — NO real Redis connection is made.
NO SQLite anywhere in this module.
    )annotationsNz/mnt/e/genesis-system)	AsyncMock	MagicMockcall)SwarmWorkerBasePEL_TIMEOUT_MS)
STREAM_KEYDEFAULT_GROUPc                    t        j                         }	 |j                  |       |j                          S # |j                          w xY w)z<Run a coroutine synchronously (pytest-asyncio not required).)asyncionew_event_looprun_until_completeclose)coroloops     6/mnt/e/genesis-system/tests/track_b/test_story_6_04.pyrunr   +   s6    !!#D&&t,



s	   7 A	c                  2     e Zd ZdZddd fdZddZ xZS )
EchoWorkerz
    Concrete test subclass.
    process() returns the task dict unchanged by default.
    Pass raise_exc to make process() raise that exception.
    N	raise_excc               B    t         |   ||       || _        g | _        y N)super__init__r   	processed)selfredis_clientstaging_arear   	__class__s       r   r   zEchoWorker.__init__?   s     |4"!    c                t   K   | j                   j                  |       | j                  | j                  |S wr   )r   appendr   r   tasks     r   processzEchoWorker.processD   s1     d#>>%.. s   68r   r%   dict)__name__
__module____qualname____doc__r   r&   __classcell__)r    s   @r   r   r   8   s    "T "
r!   r   c                6   |ddd}t        | t              r| j                         n| }||ft        j                         t	               }t        dg g f      |_        t        d      |_        ddd	dfd
	}t        |      |_        |_	        |S )u  
    Build a mock Redis that:
    - xautoclaim → returns ("0-0", [], []) immediately
    - xreadgroup → returns one entry on first call, then sets worker._running=False
                   via a side-effect so the loop exits cleanly
    - xack → AsyncMock

    The worker loop exits when _running=False after the first iteration.
    This avoids any asyncio.sleep in tests (and infinite loops).
    Ns   tests   sess-single)	   task_type
   session_id0-0return_value   r   callsworkerc                v   K   dxx   dz  cc<   d   dk(  rgfgS d   d   j                          g S wNr6   r4   r7   stop)groupconsumer_idstreamscountblock_stateentrystream_names        r   _xreadgroupz,_make_single_shot_redis.<locals>._xreadgroupj   sR     w1'?a 5'*++('8!!#	s   69side_effectr4   i  )

isinstancestrencoder	   r   r   
xautoclaimxack
xreadgrouprA   )entry_idfields	eid_bytes
mock_redisrD   rA   rB   rC   s        @@@r   _make_single_shot_redisrR   O   s     ~ 'G%/#%>!HIE##%KJ%E2r?CJQ/JOD)F &+>JJr!   c                     t               }t        dg g f      |_        t        d      |_        dddd	 fd	}t        |      |_        |_        |S )
z
    Build a mock Redis that returns empty xreadgroup after `stop_after_calls` calls.
    xautoclaim returns ("0-0", [], []).
    Caller must bind worker ref via redis._state['worker'] to stop the loop.
    r1   r2   r4   r   Nr5   c                j   K   dxx   dz  cc<   d   k\  rd   d   j                          g S wr9   r:   )r<   r=   r>   r?   r@   rA   stop_after_callss        r   rD   z&_make_empty_redis.<locals>._xreadgroup   sB     w1'?..6(3C3O8!!#	s   03rE   rG   )r   r   rK   rL   rM   rA   )rU   rQ   rD   rA   s   `  @r   _make_empty_redisrV   y   sW     J%E2r?CJQ/JOD)F &+>JJr!   c                     d} t        |       }t        |      |j                  d<   fd}t         |              |j                  j                  t        t        |        y)z9BB1: When process() succeeds, XACK is sent for the entry.1700000000001-0rN   r7   c                 P   K    j                  t        d       d {    y 7 w)Nztest-c1r<   r=   run_worker_loopr
   r7   s   r   _runz4test_bb1_successful_process_sends_xack.<locals>._run         $$=i$PPP   &$&N)rR   r   rA   r   rL   assert_called_once_withr	   r
   rN   redisr_   r7   s      @r   &test_bb1_successful_process_sends_xackre      sQ     H#X6EF#ELLQ K	JJ&&z=(Kr!   c                     d} t        |       }t        |t        d            |j                  d<   fd}t	         |              |j
                  j                          y)z:BB2: When process() raises an exception, XACK is NOT sent.z1700000000002-0rY   boomr   r7   c                 P   K    j                  t        d       d {    y 7 w)Nztest-c2r[   r\   r^   s   r   r_   z8test_bb2_failed_process_does_not_send_xack.<locals>._run   r`   ra   N)rR   r   RuntimeErrorrA   r   rL   assert_not_calledrc   s      @r   *test_bb2_failed_process_does_not_send_xackrk      sR     H#X6Ef)=>F#ELLQ K	JJ  "r!   c                 x   t               } t        |       }|j                  }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}d	|_        |j                          |j                  }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)
z#BB3: stop() sets _running to False.Fis)z0%(py2)s
{%(py2)s = %(py0)s._running
} is %(py5)sr7   )py0py2py5zassert %(py7)spy7NT)rV   r   _running
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationr;   )rd   r7   @py_assert1@py_assert4@py_assert3@py_format6@py_format8s          r    test_bb3_stop_sets_running_falser      s    EF??#e#?e####?e######6###6###?###e#######FO
KKM??#e#?e####?e######6###6###?###e#######r!   c                 P   t        d      } t        |       | j                  d<   fd}t         |              | j                  }|j
                  }d}||k\  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}}y)z;BB4: XAUTOCLAIM is called once when run_worker_loop starts.r4   rU   r7   c                 P   K    j                  t        d       d {    y 7 w)Nztest-c4r[   r\   r^   s   r   r_   z8test_bb4_reclaim_pending_called_on_startup.<locals>._run   r`   ra   >=)zR%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.xautoclaim
}.call_count
} >= %(py7)srd   ro   rp   py4rr   assert %(py9)spy9N)rV   r   rA   r   rK   
call_countrt   ru   rv   rw   rx   ry   rz   r{   )	rd   r_   r|   r~   @py_assert6@py_assert5r   @py_format10r7   s	           @r   *test_bb4_reclaim_pending_called_on_startupr      s    q1EF#ELLQ K+&&+!+&!++++&!++++++5+++5++++++&+++!+++++++r!   c                    t        d      } t        |       | j                  d<   fd}t         |              | j                  }|j
                  }d}||k\  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}}| j                  j                  d   }|d   d   }	t        |	v }|st        j                  d|fdt        |	f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |	      rt        j                  |	      nddz  }
t        j                   d|	       dz   d|
iz  }t        t        j                  |            d}|	t           }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                   d|	t                 dz   d|iz  }t        t        j                  |            dx}x}}y)zJWB1: XREADGROUP is called with streams={STREAM_KEY: '>'} for new messages.r4   r   r7   c                 H   K    j                  dd       d {    y 7 w)Ngenesis_workersztest-wb1r[   )r]   r^   s   r   r_   zDtest_wb1_xreadgroup_uses_greater_than_for_new_messages.<locals>._run   s!     $$+<*$UUUs   " "r   zR%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.xreadgroup
}.call_count
} >= %(py7)srd   r   r   r   Nr      )in)z%(py0)s in %(py2)sr	   streams_argro   rp   z$STREAM_KEY not in streams argument: z
>assert %(py4)sr   >==z%(py1)s == %(py4)spy1r   z#Expected '>' for new messages, got 
>assert %(py6)spy6)rV   r   rA   r   rM   r   rt   ru   rv   rw   rx   ry   rz   r{   call_args_listr	   _format_assertmsg)rd   r_   r|   r~   r   r   r   r   
first_callr   @py_format3@py_format5@py_assert0@py_assert2@py_format7r7   s                  @r   6test_wb1_xreadgroup_uses_greater_than_for_new_messagesr      s6   q1EF#ELLV K+&&+!+&!++++&!++++++5+++5++++++&+++!+++++++!!003JQ-"K$  :                %    %    /{m<     z" c "c)  "c    #    '*    .k*.E-HI     r!   c                     t               } t        |       }t        |j                  dd             | j                  j                          | j                  j                  }|d   d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d	|d   j                  d             d
z   d|iz  }t        t        j                  |            dx}x}}y)z@WB2: _reclaim_pending calls XAUTOCLAIM with min_idle_time=60000.r   ztest-wb2r4   min_idle_time`  r   r   r   z"Expected min_idle_time=60000, got r   r   N)rV   r   r   _reclaim_pendingrK   assert_called_once	call_argsrt   ru   ry   r   getrz   r{   rd   r7   call_kwargsr   r~   r   r   r   s           r   1test_wb2_xautoclaim_called_with_pel_timeout_60000r      s    EF 1:>?	'')"",,Kq>/* e *e3  *e    +    /4    -[^-?-?-P,QR     r!   c            	     
   d} dddd}t        | |      }t        |      |j                  d<   fd}t         |              j                  }t        |      }d	}||k(  }|s
t        j                  d
|fd||f      dt        j                         v st        j                  t
              rt        j                  t
              nddt        j                         v st        j                        rt        j                        ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}x}}j                  d   }
|
j                         D ]  \  }}t        |t               }|s:t        j"                  d|dt%        |      j&                         dz   dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t               rt        j                  t               ndt        j                  |      dz  }t        t        j                  |            d}t        |t               }|s:t        j"                  d|dt%        |      j&                         dz   dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t               rt        j                  t               ndt        j                  |      dz  }t        t        j                  |            d} |
d   }d}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd |iz  }t        t        j                  |            dx}x}}|
d!   }d"}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd |iz  }t        t        j                  |            dx}x}}y)#zJWB3: Bytes keys and values from Redis are decoded to str before process().z1700000000003-0s   researchs
   sess-bytess   {"query": "test"})r/   r0   s   payload)rN   rO   r7   c                 P   K    j                  t        d       d {    y 7 w)Nztest-wb3r[   r\   r^   s   r   r_   z/test_wb3_bytes_decoded_from_redis.<locals>._run        $$=j$QQQra   r4   r   )zO%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.processed
})
} == %(py8)slen)ro   r   py3rq   py8zassert %(py10)spy10Nr   zKey z should be str, got z7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}rH   krI   )ro   r   rp   r   zValue v	task_typeresearchr   r   zassert %(py6)sr   
session_idz
sess-bytes)rR   r   rA   r   r   r   rt   ru   rv   rw   rx   ry   rz   r{   itemsrH   rI   r   typer)   )rN   rO   rd   r_   r   r}   @py_assert7r   @py_format9@py_format11receivedr   r   r~   r   r   r   r7   s                    @r   !test_wb3_bytes_decoded_from_redisr      s!    H!$(F
 $XfEEF#ELLR K%3 %A% A%%%% A%%%%%%3%%%3%%%%%%v%%%v%%%%%% %%%A%%%%%%%"H  X1!S!U!UUT!.B47CSCSBT#UUUUUUUzUUUzUUUUUU!UUU!UUUUUUSUUUSUUU!UUUUUU!S!W!WWVA50DT!WEUEUDV#WWWWWWWzWWWzWWWWWW!WWW!WWWWWWSWWWSWWW!WWWWWWX K .J. J.... J... ...J.......L!1\1!\1111!\111!111\1111111r!   c                     t               } t        j                  t              5  t	        |        ddd       y# 1 sw Y   yxY w)uE   WB4: SwarmWorkerBase is abstract — cannot be instantiated directly.N)rV   pytestraises	TypeErrorr   )rd   s    r   4test_wb4_process_is_abstract_cannot_instantiate_baser     s3    E	y	!   s	   9Ac                    d} t         | k(  }|st        j                  d|fdt         | f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       dz  }t        j                  dt                dz   d|iz  }t        t        j                  |            d	x}} y	)
z7WB5: PEL_TIMEOUT_MS module-level constant equals 60000.r   r   z%(py0)s == %(py3)sr   ro   r   z$PEL_TIMEOUT_MS should be 60000, got 
>assert %(py5)srq   N)
r   rt   ru   rv   rw   rx   ry   r   rz   r{   )r   r|   @py_format4r   s       r   )test_wb5_pel_timeout_ms_constant_is_60000r     s    " >U"  >U              #    /~.>?    r!   c                     d} t        |       }t               }t               |_        t	        ||      |j
                  d<   fd}t         |              |j                  j                          y)zqWB6: When process() returns non-None and staging_area is set,
    staging.submit_delta is called with the result.z1700000000006-0rY   r   r7   c                 P   K    j                  t        d       d {    y 7 w)Nztest-wb6r[   r\   r^   s   r   r_   zGtest_wb6_staging_submit_delta_called_when_result_not_none.<locals>._run1  r   ra   N)rR   r   r   submit_deltar   rA   r   r   )rN   rd   stagingr_   r7   s       @r   9test_wb6_staging_submit_delta_called_when_result_not_noner   %  sb     !H#X6EkG$;GG4F#ELLR K++-r!   c                    ddl m}  | t        u }|st        j                  d|fd| t        f      dt	        j
                         v st        j                  |       rt        j                  |       nddt	        j
                         v st        j                  t              rt        j                  t              nddz  }dd	|iz  }t        t        j                  |            d
}y
)z>Package level: SwarmWorkerBase importable from core.coherence.r   )r   rm   )z%(py0)s is %(py2)sSWBr   r   zassert %(py4)sr   N)
core.coherencer   rt   ru   rv   rw   rx   ry   rz   r{   )r   r|   r   r   s       r   &test_package_exports_swarm_worker_baser   >  sm    5/!!!!3/!!!!!!3!!!3!!!!!!/!!!/!!!!!!!r!   c                 \   ddl m}  d}| |k(  }|st        j                  d|fd| |f      dt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}}y
)z=Package level: PEL_TIMEOUT_MS importable from core.coherence.r   )r   r   r   r   PTMr   assert %(py5)srq   N)
r   r   rt   ru   rv   rw   rx   ry   rz   r{   )r   r   r|   r   r   s        r   #test_package_exports_pel_timeout_msr   D  s]    43%<3%33%r!   c                 "   dddifdddifg} t               }t        d| g f      |_        t        |      }t	        |j                  dd	            }d
}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                  |            dx}}y)zH_reclaim_pending returns the count of reclaimed entries from XAUTOCLAIM.z1700000000010-0r/   s   oldz1700000000011-0s   staler1   r2   r   zworker-reclaimr   r   r   r?   r   z"Expected 2 reclaimed entries, got r   rq   N)rV   r   rK   r   r   r   rt   ru   rv   rw   rx   ry   r   rz   r{   )reclaimed_entriesrd   r7   r?   r   r|   r   r   s           r   7test_reclaim_pending_returns_count_of_reclaimed_entriesr   J  s     
\623	\845 E u6G.LMEF''(9;KLMEC5A:CCC5ACCCCCC5CCC5CCCACCC;E7CCCCCCCr!   c                    t               } t        t        d            | _        t	        |       }t        |j                  dd            }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}}y)zC_reclaim_pending returns 0 (does not raise) when xautoclaim throws.zNOGROUP No such grouprE   r   z
worker-errr   r   r   r?   r   r   rq   N)rV   r   	ExceptionrK   r   r   r   rt   ru   rv   rw   rx   ry   rz   r{   )rd   r7   r?   r   r|   r   r   s          r   9test_reclaim_pending_returns_zero_on_xautoclaim_exceptionr   Y  s    E Y7N-OPEF''(9<HIE5A:5A55Ar!   c                   	
 t               	dgd	fd	} t        |       	_        t        	      

	j                  d<   
fd}t         |              	j                  }|j                  }d}||k\  }|st        j                  d|fd||f      d	t        j                         v st        j                  	      rt        j                  	      nd	t        j                  |      t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}x}x}}y)uQ   xreadgroup raising an exception logs error and continues — loop does not crash.r   r4   c                   K   dxx   dz  cc<   d   dk(  rt        d      j                  d   j                          g S w)Nr   r4   zRedis connection lostr7   )ConnectionErrorrA   r;   )r<   r=   r>   r?   r@   _callsrd   s        r   _xreadgroup_fails_firstzNtest_xreadgroup_exception_does_not_crash_loop.<locals>._xreadgroup_fails_firstj  sE     q	Q	!9>!"9::X##%	s   A ArE   r7   c                 P   K    j                  t        d       d {    y 7 w)Nztest-excr[   r\   r^   s   r   r_   z;test_xreadgroup_exception_does_not_crash_loop.<locals>._runw  r   ra   r   r   rd   r   r   r   NrG   )rV   r   rM   r   rA   r   r   rt   ru   rv   rw   rx   ry   rz   r{   )r   r_   r|   r~   r   r   r   r   r   rd   r7   s           @@@r   -test_xreadgroup_exception_does_not_crash_loopr   d  s    ESF !-DEEF#ELLR K+&&+!+&!++++&!++++++5+++5++++++&+++!+++++++r!   c                 6    G d dt               } d}t        |      }t               }t               |_         | ||      |j
                  d<   fd}t         |              |j                  j                          |j                  j                          y)	zstaging.submit_delta is NOT called when process() returns None;
    but XACK is still sent (process returned None without raising).c                      e Zd ZddZy)Etest_staging_not_called_when_process_returns_none.<locals>.NoneWorkerc                   K   y wr    r$   s     r   r&   zMtest_staging_not_called_when_process_returns_none.<locals>.NoneWorker.process  s	     s   Nr'   )r)   r*   r+   r&   r   r!   r   
NoneWorkerr     s    	r!   r   z1700000000099-0rY   r   r7   c                 P   K    j                  t        d       d {    y 7 w)Nz	test-noner[   r\   r^   s   r   r_   z?test_staging_not_called_when_process_returns_none.<locals>._run  s      $$=k$RRRra   N)
r   rR   r   r   r   rA   r   rj   rL   r   )r   rN   rd   r   r_   r7   s        @r   1test_staging_not_called_when_process_returns_noner     s~    _  !H#X6EkG$;GG4F#ELLS K**,	JJ!!#r!   c                     d} t        |       }t        |d      |j                  d<   fd}t         |              |j                  j                          y)zLNo error when staging_area is None (default) and process() returns a result.z1700000000098-0rY   Nr   r7   c                 P   K    j                  t        d       d {    y 7 w)Nztest-nostagingr[   r\   r^   s   r   r_   z:test_staging_not_called_when_no_staging_area.<locals>._run  s!     $$=FV$WWWra   )rR   r   rA   r   rL   r   rc   s      @r   ,test_staging_not_called_when_no_staging_arear     sM     H#X6ED1F#ELLX K	JJ!!#r!   c                    t               } t        |       }t        |j                  dd             | j                  j
                  }|d   d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d	|d   j                  d            d
z   d|iz  }t        t        j                  |            dx}x}}y)zN_reclaim_pending calls XAUTOCLAIM with start_id='0-0' to scan all PEL entries.r   ztest-start-idr4   start_idr1   r   r   r   zExpected start_id='0-0', got r   r   N)rV   r   r   r   rK   r   rt   ru   ry   r   r   rz   r{   r   s           r   )test_xautoclaim_called_with_start_id_zeror     s    EF 1?CD"",,Kq>*%  %.  %    &    */    (A(:(::(F'IJ     r!   __main__u%   BB1: Successful process → XACK sentu$   BB2: Failed process → no XACK sentzBB3: stop() sets _running=Falsez!BB4: XAUTOCLAIM called on startupz)WB1: XREADGROUP uses '>' for new messagesz/WB2: XAUTOCLAIM called with min_idle_time=60000z$WB3: Bytes from Redis decoded to stru6   WB4: process() is abstract — cannot instantiate basez%WB5: PEL_TIMEOUT_MS constant is 60000z5WB6: staging.submit_delta called when result not Nonez3PKG: SwarmWorkerBase importable from core.coherencez2PKG: PEL_TIMEOUT_MS importable from core.coherencez3_reclaim_pending returns count of reclaimed entriesz2_reclaim_pending returns 0 on xautoclaim exceptionz(xreadgroup exception does not crash loopz,staging NOT called when process returns Nonez'staging NOT called when no staging_areaz%XAUTOCLAIM called with start_id='0-0'z	  [PASS] r4   z	  [FAIL] z: 
/z tests passedz(ALL TESTS PASSED -- Story 6.04 (Track B))rX   N)r   );r,   
__future__r   builtinsrv   _pytest.assertion.rewrite	assertionrewritert   syspathinsertr   r   unittest.mockr   r   r    core.coherence.swarm_worker_baser   r   core.coherence.task_dag_pusherr	   r
   r   r   rR   rV   re   rk   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r)   	tracebacktestspassedr   totalnamefnprintr   exc	print_excexitr   r!   r   <module>r     s\    #   
 * +   4 4 E 0 'T:L # 
$,&.28.2"D,8$6$ 
" z 
1	/	1	/	3	5	*	)	+	,	3	5	4	?	A	:	:	<	/	*	,	A	=	?	0	2	4	@	B	D	>	/	1	=	,	.	>	@	B	=	B	D	3	6	8	7	:	<	2	5	7	0	2	4G%EN FJE "b	"DIdV$%aKF	" 
Bvhawm
,-89s b  	"IdV2cU+,I!!	"s   :EE2	E--E2