
    9j#                       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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mZ ddlmZmZ ddlmZ dd	lmZm Z m!Z!m"Z" dd
l#m$Z$ dZ%dZ&de%fdZ'ddZ(ddZ)ddZ*d Z+d Z,d Z-d Z.d Z/d Z0y)u  task-2556 §10 — duplicate same-head dedupe (fcntl atomic) 회귀.

회장 §명시 2026-05-12 §10:
  "duplicate same-head dedupe — fcntl.flock 기반 atomic, 이미 trigger 된 head 재호출 차단"

검증 포인트:
  1. audit 에 POSTED 가 이미 있는 (pr, head) 에 대해 scheduler 가 SAME_HEAD_DEDUPED 로 처리.
  2. http_post 호출 0 (재발사 차단).
  3. audit 에 PENDING (이전 crash) 가 있어도 dedupe (fail-closed).
  4. head 가 바뀌면 새 trigger 허용 (이전 record 무시).
  5. fixture (executor_duplicate_dedupe.json) 결과 어셀션.
  6. scheduler 가 fcntl lock 을 통해 동시 진입 차단 (lock file 존재 확인).
    )annotationsN)Path   )ACTION_OWNER_TRIGGER_DISPATCHEDACTION_SAME_HEAD_DEDUPEDExecutorSchedulerSCHEDULER_LOCK_REL_PATH)GeminiReviewMetaIdlePRSnapshot)MergeQueueExecutor)AUDIT_REL_PATHOwnerTriggerAuditRESULT_PENDINGRESULT_POSTED)OwnerTriggerOnly(cccccccccccccccccccccccccccccccccccccccc(ddddddddddddddddddddddddddddddddddddddddn   c                $    t        | |dddd      S )Nztask/task-2556-dev5z2026-05-12T10:00:00+00:00 Tnumberhead_shahead_ref
created_atgemini_reviewsci_required_all_success)r   r   heads     J/home/jay/workspace/anu_v2/tests/test_executor_duplicate_trigger_dedupe.py	_snapshotr!   2   s!    &. $     c                   fd}t        |       }t        | |d |      }t        | | dz  dz  fd|t        d d d	 d
 |       dd      S )Nc                4    j                  ||d       ddiS )N)pathbodyid   )append)methodr%   r&   headers
http_callss       r    	http_postz"_make_scheduler.<locals>.http_post>   s     467ayr"   c                      y)N#ghp_owner_fake_xxxxxxxxxxxxxxxxxxxxr   r   r"   r    <lambda>z!_make_scheduler.<locals>.<lambda>E       r"   )workspace_rootr-   token_providerauditmemoryeventsc                      S Nr   )	snapshotss   r    r0   z!_make_scheduler.<locals>.<lambda>K   s    ) r"   c                    i S r8   r   )aes     r    r0   z!_make_scheduler.<locals>.<lambda>N   s    2 r"   c                     y)N r   )r;   s    r    r0   z!_make_scheduler.<locals>.<lambda>O   r1   r"   c                     y)Nr   r   ps    r    r0   z!_make_scheduler.<locals>.<lambda>P   r1   r"   c                     y r8   r   r@   s    r    r0   z!_make_scheduler.<locals>.<lambda>Q   r1   r"   )	gh_runner
git_runnerpytest_runneraudit_writertask_md_rootor)r2   decision_dirsnapshot_providerowner_triggermerge_executorownerrepo)r   r   r   r   )tmp_pathr9   r,   r-   r4   runners    ``   r    _make_schedulerrR   =   sn     h'ED	F (83+)%#%'!
  r"   c                  | t         z  }|j                  j                  dd       ddd||dt        dd| d	d
dddd}t	        |dd      5 }|j                  t        j                  |dd      dz          ddd       y# 1 sw Y   yxY w)uD   audit JSONL 에 기존 POSTED 한 줄 직접 seed (race simulation).Tparentsexist_okanu_v2.owner_trigger_audit.v12026-05-12T10:40:00+00:00	task-2556"POST_GEMINI_REVIEW_TRIGGER_COMMENT/gemini review/repos/o/r/issues/	/comments/tmp/decision.jsondeadbeefFschematstask_idprr   actionresultcomment_bodyendpointdecision_pathtoken_presenttoken_hash_prefixtoken_value_loggedr;   utf-8encodingensure_ascii	sort_keys
N)r   parentmkdirr   openwritejsondumpsrP   rd   r   
audit_pathrecfhs         r    _seed_audit_postedr~   Y   s    N*JD481)6((I6-'#C 
j#	0 MB
CetDtKLM M M   +BB
c                  | t         z  }|j                  j                  dd       ddd||dt        dd| d	d
dddd}t	        |dd      5 }|j                  t        j                  |dd      dz          ddd       y# 1 sw Y   yxY w)u<   audit JSONL 에 기존 PENDING 한 줄 (crashed mid-flight).TrT   rW   rX   rY   rZ   r[   r\   r]   r^   r_   Fr`   r;   rm   rn   rp   rs   N)r   rt   ru   r   rv   rw   rx   ry   rz   s         r    _seed_audit_pendingr   p   s    N*JD481)6 ((I6-'#C 
j#	0 MB
CetDtKLM M Mr   c                z   t        | dt               g }t        | t        dt              g|      }|j	                  ddid      }|j
                  d   }|j                  }|t        k(  }|st        j                  d	|fd
|t        f      t        j                  |      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d 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 )Nr   rd   r   r,   OWNER_GEMINI_TRIGGER_TOKENghp_xxxxxxxxxxxxxxxxxxxxxxxx2026-05-12T11:00:00+00:00envnowr   ==z.%(py3)s
{%(py3)s = %(py1)s.action
} == %(py5)sr   py1py3py5assert %(py7)spy7z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr,   py0r   r   py6assert %(py8)spy8)r~   _HEADrR   r!   run_one_cycle
pr_actionsre   r   
@pytest_ar_call_reprcompare	_saferepr@py_builtinslocals_should_repr_global_nameAssertionError_format_explanationr   rP   r,   	schedulerrf   @py_assert0@py_assert2@py_assert4@py_format6@py_format8@py_assert5@py_format7@py_format9s               r    (test_audit_posted_same_head_blocks_rerunr      s]   xCe4J9S%+@*AjYI$$)+IJ' % F QB&&B&*BBBBB&*BBBBBBB&BBBBBB*BBBB*BBBBBBBBz?a?a?a33zz?ar"   c                z   t        | dt               g }t        | t        dt              g|      }|j	                  ddid      }|j
                  d   }|j                  }|t        k(  }|st        j                  d	|fd
|t        f      t        j                  |      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d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)u8   PENDING (crashed mid-flight) 도 dedupe — fail-closed.r   r   r   r   r   r   r   r   r   r   r   r   r   r   Nr   r   r,   r   r   r   )r   r   rR   r!   r   r   re   r   r   r   r   r   r   r   r   r   r   r   s               r    5test_audit_pending_same_head_blocks_rerun_fail_closedr      s]   Su5J9S%+@*AjYI$$)+IJ' % F QB&&B&*BBBBB&*BBBBBBB&BBBBBB*BBBB*BBBBBBBBz?a?a?a33zz?ar"   c                D   t        | dt               g }t        | t        dt              g|      }|j                  ddid      }|j                  d   }|j                  }|t        k(  }|st        j                  d	|fd
|t        f      t        j                  |      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                   |            dx}x}}|j                  d   }|j"                  }|t        k(  }|st        j                  d	|fd|t        f      t        j                  |      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                   |            d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)uA   head 가 바뀌면 새 trigger 허용 (회장 §4 박제 보완).r   r   r   r   r   r   r   r   r   r   r   r   r   r   N)z0%(py3)s
{%(py3)s = %(py1)s.head_sha
} == %(py5)s	_HEAD_NEWr(   r   r   r,   r   r   r   )r~   r   rR   r!   r   r   r   re   r   r   r   r   r   r   r   r   r   r   r   r   s               r    1test_new_head_after_old_posted_allows_new_triggerr      s   xCe4J9S)+D*ER\]I$$)+IJ' % F QI&&I&*IIIII&*IIIIIII&IIIIII*IIII*IIIIIIIIQ5((5(I5555(I555555(555555I555I5555555z?a?a?a33zz?ar"   c           	        t        t              j                         j                  d   dz  dz  }t	        j
                  |j                  d            }| t        z  }|j                  j                  dd       |d   D ]C  }t        |d	d      5 }|j                  t	        j                  |d
d      dz          d d d        E |d   }t        |d   |d   |d   |d   d|d         }g }t        | |g|      }	|	j                  ddi|d         }
|
j                   d   }|j"                  }|d   }||k(  }|st%        j&                  d|fd||f      t%        j(                  |      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#t1        j2                         v st%        j4                  t.              rt%        j(                  t.              nd#d$t1        j2                         v st%        j4                  |      rt%        j(                  |      nd$t%        j(                  |      t%        j(                  |      d%z  }dd |iz  }t+        t%        j,                  |            d x}x}}y # 1 sw Y   axY w)&Nr(   fixtureszexecutor_duplicate_dedupe.jsonrm   rn   TrT   preexisting_audit_recordsr;   Frp   rs   snapshotr   r   r   r   r   r   r   r   r   r   r   r   r   expected_scheduler_actionr   )z.%(py3)s
{%(py3)s = %(py1)s.action
} == %(py6)s)r   r   r   r   r   expected_http_post_callsr   r   r,   r   )r   __file__resolverU   rx   loads	read_textr   rt   ru   rv   rw   ry   r   rR   r   r   re   r   r   r   r   r   r   r   r   r   )rP   fixture_pathfixturer{   r|   r}   	snap_dictsnapr,   r   rf   r   r   r   r   r   r   s                    r    ,test_dedupe_fixture_matches_expected_outcomer      s^   X ((+j8;[[  jj///ABGN*JD4823 Q*cG4 	QHHTZZ%4H4OP	Q 	QQ 
#I":&:&\* )*C DD J4&ZHI$$)+IJEN % F QN&&N'2M*NN&*NNNNN&*NNNNNNN&NNN*NNNNNNNNz?Ag&@AA?AAAAA?AAAAAAA3AAA3AAAAAAzAAAzAAA?AAAAAAAAAAA%	Q 	Qs   +KK"	c                   g }t        | t               g|      }|j                  ddid       | t        z  }|j                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            d	x}}y	)
uA   fcntl-backed scheduler lock 파일이 cycle 실행 시 생성됨.r   r   r   r   r   zAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}	lock_path)r   py2py4N)rR   r!   r   r	   existsr   r   r   r   r   r   r   )rP   r,   r   r   @py_assert1@py_assert3@py_format5s          r    &test_scheduler_creates_fcntl_lock_filer      s    J9;-JOI)+IJ'   22I99r"   c                0   g }t        dd      }t        | ||g|      }|j                  ddid      }|j                  }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}}|d   }|j                  }|t        k(  }|st        j                  d
|fd|t        f      t        j                  |      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            dx}x}}|d   }|j                  }|t        k(  }|st        j                  d
|fd|t        f      t        j                  |      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d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)un   동일 cycle 안에서 동일 PR snapshot 2 개가 들어와도 첫 번째는 DISPATCH, 두 번째는 DEDUPED.   (9999999999999999999999999999999999999999r   r   r   r   r   r   r   r   r   r   actionsr   r   r   Nr   r   r   r   r   r   r(   r   r,   )r!   rR   r   r   r   r   r   r   r   r   r   r   r   re   r   r   )rP   r,   r   r   rf   r   r   r   r   r   r   r   r   r   s                 r    *test_same_cycle_same_pr_no_double_dispatchr      s_   JCh/D4,:NI$$)+IJ' % F Gw<1<1<133ww<11:?:? ????? ????:????????? ???? ????????1:8:8 88888 8888:888888888 8888 88888888z?a?a?a33zz?ar"   )rP   r   r,   list)rP   r   rd   intr   strreturnNone)1__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   rx   syspathlibr   r   r   rU   WORKSPACE_ROOTr   r%   insertanu_v2.executor_schedulerr   r   r   r	   anu_v2.idle_pr_diagnoserr
   r   anu_v2.merge_queue_executorr   anu_v2.owner_trigger_auditr   r   r   r   anu_v2.owner_trigger_onlyr   r   r   r!   rR   r~   r   r   r   r   r   r   r   r   r"   r    <module>r      s    #    
  h'')11!4~chh&HHOOAs>*+  ;  7 		 u 8M.M.	 
  B:
 r"   