
    wj3                    v   d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlZddlZddlZddlmZ  ee      j%                         j&                  d   Z ee      ej,                  vr"ej,                  j/                  d ee             ddlmZmZmZ ddlmZ dZd	ed
ddZd Z d Z!d Z"ddZ#d Z$d Z%y)u  anu_v2.tests.test_owner_trigger_concurrency_2554plus1 — 동시 trigger race 차단 (task-2554+1).

회장 §명시 (2026-05-12 KST) 필수 fix §6 1:1 박제:
  - same PR/head 동시 2 process (or thread) → comment 정확히 1 회 생성 어셀션.
  - HIGH race condition (anu_v2/owner_trigger_only.py:352 baseline) 제거 회귀.

본 파일은 sidecar lock + transaction 으로 직렬화되는 trigger 흐름이 실제 동시 호출에서도
정확히 1회만 http_post 를 발생시키는지 검증한다. dry-run/mock 만 — actual OWNER token /
GitHub comment 작성 0.

implementation note:
  - thread 기반: threading.Thread + Barrier 로 두 worker 가 같은 순간에 trigger 시도.
  - process 기반: multiprocessing.Process — fcntl.flock 은 process 간에도 보호되므로
    별도 process 테스트도 포함.

본 회귀는 anu_v2/* 모듈만 import 한다 (one-way isolation).
    )annotationsN)Path   )OwnerTriggerAuditRESULT_DEDUPEDRESULT_POSTEDOwnerTriggerOnly(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag   prheadc                   dd||dddddddd}| j                  dd	       | d
z  }|j                  t        j                  |      d       |S )N anu_v2.owner_trigger_decision.v1task-2554+1TFr   "POST_GEMINI_REVIEW_TRIGGER_COMMENT/gemini reviewschematask_idr   current_head
queue_headcurrent_head_confirmedgemini_evidence_freshnudge_count_for_pr_headallowed_actioncomment_bodyallowedparentsexist_okdecision.jsonutf-8encoding)mkdir
write_textjsondumps)tmp_pathr   r   dps        L/home/jay/workspace/anu_v2/tests/test_owner_trigger_concurrency_2554plus1.py_write_decisionr/   ,   sc    4 "&!&#$>(	A NN4$N/?"ALLAL1H    c                |
    t               t               g t        j                         fdd d fdt        j                  d      g g t        j                         fd}t        d      D cg c]  }t        j                  |       }}|D ]  }|j                           |D ]  }|j                  d        g }|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}}j$                  } |t&              }	d}
|	|
k(  }|s
t        j                  d	|fd|	|
f      dt        j                         v st        j                        rt        j                        ndt        j                  |      dt        j                         v st        j                  t&              rt        j                  t&              ndt        j                  |	      t        j                  |
      dz  }dd|iz  }t!        t        j"                  |            dx}x}	x}}
j$                  } |t(              }	d}
|	|
k(  }|s
t        j                  d	|fd|	|
f      dt        j                         v st        j                        rt        j                        ndt        j                  |      dt        j                         v st        j                  t(              rt        j                  t(              ndt        j                  |	      t        j                  |
      dz  }dd|iz  }t!        t        j"                  |            dx}x}	x}}
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                  |      dz  }t        j                  dt+                     dz   d|iz  }t!        t        j"                  |            dx}x}	}yc c}w )!uZ   2 thread 가 같은 (pr, head) trigger_gemini_review 호출 → http_post 정확히 1 회.c                    t        j                  d       5  j                  | ||d       d d d        ddiS # 1 sw Y   ddiS xY w)N{Gz?)methodpathbodystatus   timesleepappendr4   r5   r6   headersposts
posts_locks       r.   	http_postzItest_same_pr_head_concurrent_two_threads_one_post_only.<locals>.http_postK   sL    

4 	ILLFD$GH	I#	I#s	   ;Ac                      y)Nz"concurrency-test-tok-MUST-NOT-LEAK rC   r0   r.   token_providerzNtest_same_pr_head_concurrent_two_threads_one_post_only.<locals>.token_providerR   s    3r0   c                 "    t               S )Nworkspace_rootrA   rD   auditr	   )rH   rA   r+   rD   s   r.   make_modulezKtest_same_pr_head_concurrent_two_threads_one_post_only.<locals>.make_moduleU   s    #)	
 	
r0   r   c                 T           } 	 j                          | j                  ddt              }5  j                  |j                         d d d        y # 1 sw Y   y xY w# t
        $ r8}5  j                  |       d d d        n# 1 sw Y   nxY wY d }~y Y d }~y d }~ww xY w)Nordecision_pathownerrepocurrent_head_actual)waittrigger_gemini_review_HEAD_Ar<   r7   BaseException)	modrL   ebarrierrN   errorsrI   resultsresults_locks	      r.   workerzFtest_same_pr_head_concurrent_two_threads_one_post_only.<locals>.workerb   s    m	!LLN))+$+	 * A  )qxx() ) ) 	! !a ! ! ! ! !	!sL   +A& AA& A#A& #A& &	B'/B"1B	B"B	B""B'target
   timeout==z%(py0)s == %(py3)srY   py0py3zunexpected errors: 
>assert %(py5)spy5N   zK%(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.count
}(%(py3)s)
} == %(py8)srZ   r   rf   py2rg   ri   py8assert %(py10)spy10r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr?   rf   py1rg   py6z'expected exactly 1 http_post call, got 
>assert %(py8)srn   )returnstr)r/   r   	threadingLockBarrierrangeThreadstartjoin
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgAssertionError_format_explanationcountr   r   rr   )r+   r\   _threadst@py_assert2@py_assert1@py_format4@py_format6@py_assert4@py_assert7@py_assert6@py_format9@py_format11@py_assert5@py_format7rH   rX   rN   rY   rA   rI   r?   r@   rZ   r[   rD   s   `               @@@@@@@@@@@r.   6test_same_pr_head_concurrent_two_threads_one_post_onlyr   C   s   #H-Mh'EE!J4
 "GG"$F>>#L! !  9>aA1yv.AGA 		 	r96R<9996R99999969996999R999.vj9999999==,=',1,'1,,,,'1,,,,,,7,,,7,,,=,,,,,,,,,,,,',,,1,,,,,,,==-=(-A-(A----(A------7---7---=------------(---A-------u:RR:?RRR:RRRRRR3RRR3RRRRRRuRRRuRRR:RRRRRREc%j\RRRRRRRR Bs   T9c                    t               t               g t        j                         fdt        j                  d      g t        j                          fd}t        d      D cg c]  }t        j                  |       }}|D ]  }|j                           |D ]  }|j                  d        j                  } |t              }d}||k(  }|s
t        j                  d|fd	||f      d
t        j                         v st        j                        rt        j                         nd
t        j                   |      dt        j                         v st        j                  t              rt        j                   t              ndt        j                   |      t        j                   |      dz  }	dd|	iz  }
t#        t        j$                  |
            dx}x}x}}j                  } |t&              }d}||k(  }|s
t        j                  d|fd	||f      d
t        j                         v st        j                        rt        j                         nd
t        j                   |      dt        j                         v st        j                  t&              rt        j                   t&              ndt        j                   |      t        j                   |      dz  }	dd|	iz  }
t#        t        j$                  |
            dx}x}x}}t)              }d}||k(  }|st        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                   |      dz  }dd|iz  }	t#        t        j$                  |	            dx}x}}yc c}w )uF   8 thread 동시 trigger → POSTED 1회, DEDUPED 7회, http_post 1회.c                    t        j                  d       5  j                  | |d       d d d        ddiS # 1 sw Y   ddiS xY w)N{Gz?)r4   r5   r7   r8   r9   r=   s       r.   rA   z=test_same_pr_head_8_threads_only_one_posts.<locals>.http_post   sG    

4 	;LLFD9:	;#	;#s	   :A   c                     t        d       } j                          | j                  ddt              }5  j	                  |j
                         d d d        y # 1 sw Y   y xY w)Nc                      y)Nz	any-tokenrC   rC   r0   r.   <lambda>zLtest_same_pr_head_8_threads_only_one_posts.<locals>.worker.<locals>.<lambda>       r0   rF   rK   rL   rM   )r
   rR   rS   rT   r<   r7   )	rV   rL   rH   rX   rN   rA   rZ   r[   r+   s	     r.   r\   z:test_same_pr_head_8_threads_only_one_posts.<locals>.worker   so    #.	
 	%%' '	 & 
  	%NN188$	% 	% 	%s   A""A+r]      r`   rj   rb   rk   rZ   r   rl   ro   rp   N   r   rq   rr   r?   rs   assert %(py8)srn   )r/   r   ry   rz   r{   r|   r}   r~   r   r   r   r   r   r   r   r   r   r   r   r   rr   )r+   r\   r   r   r   r   r   r   r   r   r   r   r   r   rH   rX   rN   rA   r?   r@   rZ   r[   s   `             @@@@@@@@r.   *test_same_pr_head_8_threads_only_one_postsr      s   #H-Mh'EE!J "GG>>#L% %" 9>aA1yv.AGA 		 	r==,=',1,'1,,,,'1,,,,,,7,,,7,,,=,,,,,,,,,,,,',,,1,,,,,,,==-=(-A-(A----(A------7---7---=------------(---A-------u::?:33uu: Bs   ?Qc                "    t               t         dz  dt              } dz  dz  }|j                  j	                  d       |j                  t        j                  dd	d
ddddddddd      d       g t        j                         fdt        j                  d      g t        j                          fd}t        j                  ||t        df      }t        j                  ||dd
f      }|j                          |j                          |j                  d       |j                  d       j                  } |t              }d}||k(  }	|	s
t!        j"                  d|	fd||f      dt%        j&                         v st!        j(                        rt!        j*                        ndt!        j*                  |      dt%        j&                         v st!        j(                  t              rt!        j*                  t              ndt!        j*                  |      t!        j*                  |      dz  }
dd|
iz  }t-        t!        j.                  |            d x}x}x}	}t1              }d}||k(  }|st!        j"                  d|fd!||f      d"t%        j&                         v st!        j(                  t0              rt!        j*                  t0              nd"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 )'uJ   다른 (pr, head) 동시 trigger → 각자 POSTED (서로 dedupe 안함).ar   r   br#   T)r!   r   r      (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbFr   r   r   r   r$   r%   c                    t        j                  d       5  j                  d|i       d d d        ddiS # 1 sw Y   ddiS xY w)Nr   r5   r7   r8   r9   r=   s       r.   rA   z9test_different_pr_concurrent_both_post.<locals>.http_post   sF    

4 	)LL&$(	)#	)#s	   9Ar   c                   t        d 	      }
j                          |j                  | dd|      }5  j                  |j                         d d d        |j
                  }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd	z  }d
d|iz  }t        t        j                  |            d x}}y # 1 sw Y   xY w)Nc                      y)Nr   rC   rC   r0   r.   r   zHtest_different_pr_concurrent_both_post.<locals>.worker.<locals>.<lambda>   r   r0   rF   rK   rL   rM   rb   )z*%(py2)s
{%(py2)s = %(py0)s.pr
} == %(py4)spr_num)rf   rm   py4zassert %(py6)sru   )r
   rR   rS   r<   r7   r   r   r   r   r   r   r   r   r   )rN   r   r   rV   rL   r   @py_assert3@py_format5r   rH   rX   rA   rZ   r[   r+   s            r.   r\   z6test_different_pr_concurrent_both_post.<locals>.worker   s    #&	
 	%%' $	 & 
  	%NN188$	%tttv~tvqqtvv	% 	%s   EEr^   argsr_   r`   rb   rk   rZ   r   rl   ro   rp   Nrq   rr   r?   rs   r   rn   )r   r/   rT   parentr'   r(   r)   r*   ry   rz   r{   r}   r~   r   r   r   r   r   r   r   r   r   r   r   rr   )r+   
decision_adecision_b_pathr\   t1t2r   r   r   r   r   r   r   r   r   rH   rX   rA   r?   r@   rZ   r[   s   `              @@@@@@@r.   &test_different_pr_concurrent_both_postr      sd   h'E CCgFJn6O   .

<( ("*.).+,"F 0	
 !  & E!J "GG>>#L $ 
		z7C.H	IB			#.N	OBHHJHHJGGBGGGBG==,=',1,'1,,,,'1,,,,,,7,,,7,,,=,,,,,,,,,,,,',,,1,,,,,,,u::?:33uu:r0   c                :   ddl }|j                  j                  dt        |             ddlm} ddlm}  ||      }t        |      dz  j                  dd       fd}t        |       j                          t        j                         d	z   }	t        j                         |	k  rTt        |       j                  d
z  j                         rn-t        j                  d       t        j                         |	k  rT |||d |      }
	 |
j                  |ddt               }|j#                  d|j$                  t'        j(                         f       y# t*        $ r9}|j#                  dt        |      t'        j(                         f       Y d}~yd}~ww xY w)u   별도 process 에서 trigger_gemini_review 실행. 결과 (status, http_post_call_marker) 를
    큐에 push.

    barrier_path: 두 process 가 동시에 진입하도록 파일 기반 barrier.
    r   N)r   r	   _post_markersTr    c                    dt        j                          dz  }|j                  dd       t        j                  d       ddiS )	Nzpost-pidz.markerpostedr$   r%   r3   r7   r8   )osgetpidr(   r:   r;   )r4   r5   r6   r>   marker
marker_dirs        r.   rA   z"_process_worker.<locals>.http_post  sE    W==(W5

4#r0      ready{Gzt?c                      y)Nz
proc-tokenrC   rC   r0   r.   r   z!_process_worker.<locals>.<lambda>  r   r0   rF   rK   rL   rM   okerr)sysr5   insertrx   anu_v2.owner_trigger_auditr   anu_v2.owner_trigger_onlyr
   r   r'   touchr:   r   existsr;   rS   rT   putr7   r   r   	Exception)barrier_pathrN   
audit_rootoutput_queuer   r   r
   	audit_objrA   deadlinerV   rL   rW   r   s                @r.   _process_workerr      sS    HHOOAs:'<:!*-I j!O3JTD1 	yy{QH
))+
 %%/779

5	 ))+
  !+	C	7%%' '	 & 
 	$"))+67 7%Q5667s   A	E 	F!/FFc                (   t        |       }| dz  }|j                  dd       t        |dz        }t        |dz        }|dz  }t        j                  d      }|j                         }|j                  t        |t        |      t        |       |f      }|j                  t        |t        |      t        |       |f      }	|j                          |	j                          t        j                         d	z   }
t        j                         |
k  rpt        |      j                         r*t        |      j                         r|j                          n-t        j                  d
       t        j                         |
k  rp|j                  d       |	j                  d       |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*                  |      dz  }t!        j,                  d|j                         dz   d|iz  }t/        t!        j0                  |            dx}x}}|	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*                  |      dz  }t!        j,                  d|	j                         dz   d|iz  }t/        t!        j0                  |            dx}x}}g }|j3                         s0|j5                  |j7                                |j3                         s0t9        |      }d}||k(  }|st!        j"                  d|fd||f      dt%        j&                         v st!        j(                  t8              rt!        j*                  t8              nddt%        j&                         v st!        j(                  |      rt!        j*                  |      ndt!        j*                  |      t!        j*                  |      dz  }dd|iz  }t/        t!        j0                  |            dx}x}}t;        |D cg c]  }|d   dk(  s|d     c}      }t<        t>        g}||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!        j0                  |            dx}}| d'z  }t;        |jA                  d(            }t9        |      }d }||k(  }|st!        j"                  d|fd||f      dt%        j&                         v st!        j(                  t8              rt!        j*                  t8              ndd)t%        j&                         v st!        j(                  |      rt!        j*                  |      nd)t!        j*                  |      t!        j*                  |      dz  }t!        j,                  d*t9        |             d+z   d|iz  }t/        t!        j0                  |            dx}x}}yc c}w ),u   별도 2 process 가 같은 (pr, head) trigger → http_post 마커 정확히 1 개 (fcntl.flock 보호).

    spec §6 1:1: same PR/head 동시 2 proc → comment 1회.
    _barrierTr    z	a.barrierz	b.barrierr   forkr   r   r   r   r`   r   rb   )z0%(py2)s
{%(py2)s = %(py0)s.exitcode
} == %(py5)sp1)rf   rm   ri   zprocess 1 exit z
>assert %(py7)spy7Np2zprocess 2 exit r   rq   rr   rZ   rs   r   rn   r   rj   rd   statusesre   zgot rh   ri   r   zpost-pid*.markermarkersz>expected exactly 1 http_post marker (one process posted), got rv   )!r/   r'   rx   multiprocessingget_contextQueueProcessr   r~   r:   r   r   r   r;   r   exitcoder   r   r   r   r   r   r   r   r   emptyr<   getrr   sortedr   r   glob)r+   rN   barrier_rootbarrier_file_abarrier_file_b
ready_filectxqueuer   r   r   r   r   r   r   @py_format8rZ   r   r   r   r   rL   r   r   r   r   s                             r.   8test_same_pr_head_concurrent_two_processes_one_post_onlyr   &  su   
 $H-Mj(Ltd334N34N'J

%
%f
-CIIKE	O>3}CUWZ[cWdfk2l	mB	O>3}CUWZ[cWdfk2l	mBHHJHHJ yy{QH
))+
 &&(T.-A-H-H-J

5	 ))+
  GGBGGGBG;;<!<;!<<<;!<<<<<<2<<<2<<<;<<<!<<<r{{m<<<<<<<<;;<!<;!<<<;!<<<<<<2<<<2<<<;<<<!<<<r{{m<<<<<<<<Gkkmuyy{# kkmw<1<1<133ww<1W=!qt=>H&6K866KKK86KKKKKK8KKK8KKK6KKK$xl8KKKKKKK O+JZ__%789Gw<m1m<1mmm<1mmmmmm3mmm3mmmmmmwmmmwmmm<mmm1mmm ^_bcj_k^lmmmmmmmm >s   \%\c                   
 t               t               
dd t        j                  d      
 fd}t	        d      D cg c]  }t        j
                  |       }}|D ]  }|j                           |D ]  }|j                  d        
j                  j                  d	      }|v}|st        j                  d
|fd|f      dt        j                         v st        j                        rt        j                        nddt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t!        t        j"                  |            d}dD ]  }	|	|v}|st        j                  d
|fd|	|f      dt        j                         v st        j                  |	      rt        j                  |	      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} yc c}w )u@   8 thread 동시 trigger 후 audit jsonl 에 token sentinel 0건.2ghp_CONCURRENCY_RACE_SECRET_MUST_NEVER_LEAK_qwertyc                4    t        j                  d       ddiS )Nr   r7   r8   )r:   r;   )r4   r5   r6   r>   s       r.   rA   zAtest_concurrent_trigger_no_token_leak_to_audit.<locals>.http_post^  s    

5#r0   r   c                     t        fd      } j                          	 | j                  ddt               y # t        $ r Y y w xY w)Nc                      S )NrC   )secrets   r.   r   zPtest_concurrent_trigger_no_token_leak_to_audit.<locals>.worker.<locals>.<lambda>h  s    6 r0   rF   rK   rL   rM   )r
   rR   rS   rT   r   )rV   rH   rX   rN   rA   r   r+   s    r.   r\   z>test_concurrent_trigger_no_token_leak_to_audit.<locals>.workerd  s\    #)	
 		%%+$+	 &   		s   ? 	A
Ar]   r   r`   r$   r%   )not in)z%(py0)s not in %(py2)sr   raw)rf   rm   zassert %(py4)sr   N)zBearer ghp_github_pat_sentzaudit contains sentinel z
>assert %(py4)s)r/   r   ry   r{   r|   r}   r~   r   r5   	read_textr   r   r   r   r   r   r   r   r   )r+   r\   r   r   r   r   r   @py_format3r   r   rH   rX   rN   rA   r   s   `         @@@@@r.   .test_concurrent_trigger_no_token_leak_to_auditr   X  s   #H-Mh'EAF "G $ 9>aA1yv.AGA 		 	r **



0C6662 D3CCCt3CCCCCCtCCCtCCCCCC3CCC3CCCC":4( CCCCCCCD Bs   I6)r+   r   r   intr   rx   rw   r   )r   rx   rN   rx   r   rx   )&__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   r)   r   r   r   ry   r:   pathlibr   __file__resolver!   WORKSPACE_ROOTrx   r5   r   r   r   r   r   r   r
   rT   r/   r   r   r   r   r   r   rC   r0   r.   <module>r     s   $ #     	 
   h'')11!4~chh&HHOOAs>*+ 
 7  25' .9Sx*Z?J27j,nd'Dr0   