
    9jAX                    B   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mZm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mZmZ dZd	Zd
Z dZ!d(dZ"d)d*dZ#d+dZ$e!e eef	 	 	 	 	 	 	 	 	 d,dZ%d-dZ&d-dZ'd-dZ(d-dZ)d-dZ*d-dZ+d-dZ,d-dZ-d-dZ.d.dZ/d.dZ0d.dZ1d.dZ2d.dZ3d.dZ4d.dZ5d.d Z6d.d!Z7d-d"Z8d-d#Z9d-d$Z:d-d%Z;d.d&Z<d.d'Z=y)/u  회귀 테스트 — anu_v2.worktree_cleanup (task-2550+1 clean replacement).

PR #100 6 unresolved Gemini findings 의 corrected fix 회귀:
  - HIGH `task_id in headRefName` substring 오탐 차단 (boundary test 강제)
  - medium #1 `hashlib.sha256` 결정론 hash (Python `hash()` 비결정론 제거)
  - medium #2 `lsof +D` CWD 검사 추가 (pgrep -f argv-only 한계 보완)
  - medium #3 `is_safe_ignoring_apply` helper 회귀 (cleanup_candidates dry-run 가시성)

pytest 사용. 외부 부수효과(subprocess / file write / clock) 는 모두 fake callable 로 주입.

⚠️ 테스트 코드 내 "ghp_faketoken123abc" 등 raw token placeholder 는
   실제 토큰이 아닌 테스트 fake 값임 — leak detector 오탐 방지를 위해 명시.
    )annotationsN)datetimetimezone)Path   )CleanupResultSafetyConditionResultWorktreeCandidateWorktreeCleanup_matches_task_id_strictis_safe_ignoring_apply(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa	task-2550task/task-2550-dev5z-/home/jay/workspace/.worktrees/task-2550-dev5c            	     B    t        ddddddt        j                        S )Ni        r   )tzinfo)r   r   utc     C/home/jay/workspace/anu_v2/tests/test_worktree_cleanup_2550plus1.py_fake_clockr   ,   s    D!RQ(,,??r   c                4    t        j                  g | ||      S )N)args
returncodestdoutstderr)
subprocessCompletedProcess)r   r   r   s      r   _procr!   0   s    &&B:f]cddr   c                     ~ ~t        d      S )Nr   r!   )_args_kwargss     r   _noop_runnerr&   4   s    w8Or   c                     t        | |||      S )Npathbranchtask_idhead_sha)r
   r(   s       r   _make_candidater-   9   s     $vwQYZZr   c                    d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u\   task-25 substring 이 task/task-2550-dev5 에 매칭되면 안 됨 (HIGH unresolved 본질).r   task-25Fisz9%(py6)s
{%(py6)s = %(py0)s(%(py2)s, %(py4)s)
} is %(py9)sr   py0py2py4py6py9assert %(py11)spy11N	r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation@py_assert1@py_assert3@py_assert5@py_assert8@py_assert7@py_format10@py_format12s          r   9test_high_boundary_task25_does_not_match_task2550_headrefrL   F   s    #8M)M"#8)DMMDMMMMDMMMMMM"MMM"MMM#8MMM)MMMDMMMMMMMMMMMr   c                    d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u9   task-2500 이 task/task-25-dev5 에 매칭되면 안 됨.task/task-25-dev5	task-2500Fr0   r2   r   r3   r9   r:   Nr;   rD   s          r   9test_high_boundary_task2500_does_not_match_task25_headrefrP   K   s    #6MM"#6DMMDMMMMDMMMMMM"MMM"MMM#6MMMMMMDMMMMMMMMMMMr   c                    d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u:   task-250 이 task/task-2500-dev5 에 매칭되면 안 됨.task/task-2500-dev5ztask-250Fr0   r2   r   r3   r9   r:   Nr;   rD   s          r   :test_high_boundary_task250_does_not_match_task2500_headrefrS   P   s    #8N*N"#8*ENNENNNNENNNNNN"NNN"NNN#8NNN*NNNENNNNNNNNNNNr   c                    d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u?   정확히 일치하는 경우는 PASS — 정상 동작 회귀.r   r   Tr0   r2   r   r3   r9   r:   NrN   r/   rR   rO   r;   rD   s          r   %test_high_boundary_exact_match_passesrU   U   s   #8N+N"#8+FN$NF$NNNNF$NNNNNN"NNN"NNN#8NNN+NNNFNNN$NNNNNNN#6J	J"#6	BJdJBdJJJJBdJJJJJJ"JJJ"JJJ#6JJJ	JJJBJJJdJJJJJJJ#8N+N"#8+FN$NF$NNNNF$NNNNNN"NNN"NNN#8NNN+NNNFNNN$NNNNNNNNr   c                    d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)uK   task-2550+1 식별자가 task/task-2550+1-dev5 headRefName 에 매칭 PASS.task/task-2550+1-dev5ztask-2550+1Tr0   r2   r   r3   r9   r:   Nr;   rD   s          r   $test_high_boundary_plus_suffix_matchrX   \   s    #:RMR"#:MJRdRJdRRRRJdRRRRRR"RRR"RRR#:RRRMRRRJRRRdRRRRRRRRr   c                    d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u   task-2550 식별자가 task/task-2550+1-dev5 에 매칭되면 안 됨 (+ 도 경계).

    task-2550 ↔ task-2550+1 은 서로 다른 task → strict 분리.
    rW   r   Fr0   r2   r   r3   r9   r:   Nr;   rD   s          r   @test_high_boundary_task2550_does_not_match_task2550plus1_headrefrZ   a   s    
 $;QKQ"#:KHQEQHEQQQQHEQQQQQQ"QQQ"QQQ#:QQQKQQQHQQQEQQQQQQQQr   c                     d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)uJ   task-2550 이 headRefName 끝에 위치해도 (suffix 없음) 매칭 PASS.zrefs/heads/task/task-2550r   Tr0   r2   r   r3   r9   r:   Nr;   rD   s          r   2test_high_boundary_terminal_match_at_end_of_stringr\   i   s7   #>TT"#>LTPTTLPTTTTTLPTTTTTTT"TTT"TTT#>TTTTTTLTTTPTTTTTTTT#.DD";<DD<DDDD<DDDDDD"DDD"DDD;DDDDDD<DDDDDDDDDDDr   c                     d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u<   잘못된 task_id 형식 (task- 접두사 없음) 은 False.r   2550Fr0   r2   r   r3   r9   r:   N r;   rD   s          r   7test_high_boundary_invalid_task_id_format_returns_falser`   o   s5   #8J&J"#8&AJUJAUJJJJAUJJJJJJ"JJJ"JJJ#8JJJ&JJJAJJJUJJJJJJJ#8F"F"#8"=FF=FFFF=FFFFFF"FFF"FFF#8FFF"FFF=FFFFFFFFFFFr   c                    d} d}t        | |      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u   빈 headRefName 은 False.r_   r   Fr0   r2   r   r3   r9   r:   Nr;   rD   s          r   .test_high_boundary_empty_headref_returns_falserb   u   s    #%<{<"2{3<u<3u<<<<3u<<<<<<"<<<"<<<2<<<{<<<3<<<u<<<<<<<<r   c                   t        j                  ddddddddg      fd}t        |t        | 	      }|j	                  d
d      }|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                  }|	|v }
|
st        j                  d|
fd|	|f      t        j                  |	      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}	x}
}y)u   task_id=task-25 검사 시 PR headRefName=task/task-2550-dev5 은 매칭되면 안 됨.

    기존 substring 매칭 (`task-25` in `task/task-2550-dev5` == True) 이 BUG.
    d   MERGEDr   numberstateheadRefName2   OPENztask/task-25-dev2c                H    d| v rd| v rt        dd      S t        ddd      S Nprlistr   r_   r#   r   _pr_responses     r   fake_runnerzNtest_safety_2_strict_match_rejects_substring_unrelated_pr.<locals>.fake_runner   .    4<FdNK,,QBr   subprocess_runnerclockworkspace_rootr/   Nr*   Fr0   z.%(py2)s
{%(py2)s = %(py0)s.passed
} is %(py5)sresultr4   r5   py5assert %(py7)spy7inz.%(py1)s in %(py5)s
{%(py5)s = %(py3)s.detail
}py1py3r}   jsondumpsr   r   check_safety_2_pr_mergedpassedr<   r=   r>   r?   r@   rA   rB   rC   detailtmp_pathrs   cleanupr{   rE   @py_assert4rF   @py_format6@py_format8@py_assert0@py_assert2rr   s              @r   9test_safety_2_strict_match_rejects_substring_unrelated_prr   ~   s;   
 **:OP7JK K
 
 %G --i-EF ==!E!=E!!!!=E!!!!!!6!!!6!!!=!!!E!!!!!!!"V]]"6]""""6]"""6""""""V"""V"""]"""""""r   c                   t        j                  ddddg      fd}t        |t        |       }|j	                  dd	      }|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                  }|	|v }
|
st        j                  d|
fd|	|f      t        j                  |	      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}	x}
}y)u@   task_id 와 PR headRefName 이 정확히 일치 → MERGED PASS.rd   re   r   rf   c                H    d| v rd| v rt        dd      S t        ddd      S rm   r#   rp   s     r   rs   zEtest_safety_2_strict_match_accepts_exact_task_id.<locals>.fake_runner   rt   r   ru   r   Nry   Tr0   rz   r{   r|   r~   r   r   r   r   r   r   s              @r   0test_safety_2_strict_match_accepts_exact_task_idr      s,   **:OP K 
 %G
 --k$-GF== D =D    =D      6   6   =   D       $v}}$8}$$$$8}$$$8$$$$$$v$$$v$$$}$$$$$$$r   c                  	 t        j                  ddddg      		fd}t        |t        |       }|j	                  dd	      }|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)u   task-2550 검사 시 PR=task/task-2550+1-dev5 (replacement) 와 분리.

    Replacement chain doctrine: task-2550 과 task-2550+1 은 별개의 PR.
       re   rW   rf   c                H    d| v rd| v rt        dd      S t        ddd      S rm   r#   rp   s     r   rs   zXtest_safety_2_strict_match_isolates_task_2550_from_task_2550_plus_1.<locals>.fake_runner   rt   r   ru   r   Nry   Fr0   rz   r{   r|   r~   r   )r   r   r   r   r   r   r<   r=   r>   r?   r@   rA   rB   rC   )
r   rs   r   r{   rE   r   rF   r   r   rr   s
            @r   Ctest_safety_2_strict_match_isolates_task_2550_from_task_2550_plus_1r      s    
 **:QR K 
 %G --k$-GF==!E!=E!!!!=E!!!!!!6!!!6!!!=!!!E!!!!!!!r   c                D   t        t        t        |       }t        t	        | dz              }d}|j                  |d|       |j                  |d|       t        | dz  dz  j                  d            }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|D 	cg c]  }	|	j                    c}	       dz   d|iz  }
t#        t        j$                  |
            dx}x}}ddl}|d   j                   }|j)                  d|      }d}||u}|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c c}	w )u  동일 path 에 대해 PYTHONHASHSEED 가 달라도 동일한 hash 결과여야 함.

    기존: `abs(hash(path)) % 10**8` 는 PYTHONHASHSEED 의존 (Python 재시작마다 달라짐).
    신규: hashlib.sha256(path)[:8] — 모든 환경에서 동일.
    ru   zsome-worktreer)   2026-05-12T12:00:00+00:00test_reasonmemoryeventsworktree-cleanup-skipped-*.json   ==z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slen	log_filesr4   r   r   r7   uF   동일 path/ts 에서 결정론 hash 가 다르면 파일 충돌. got 
>assert %(py8)spy8Nr   z0worktree-cleanup-skipped-.+-([a-f0-9]{8})\.json$)is not)z%(py0)s is not %(py3)sm)r4   r   u4   파일명이 sha256 hex 8자 hash 패턴이 아님: z
>assert %(py5)sr}   )r   r&   r   r-   str_log_skippedro   globr   r<   r=   r>   r?   r@   rA   _format_assertmsgnamerB   rC   rematch)r   r   candtsr   r   rG   r   @py_format7p@py_format9_rer   r   rE   @py_format4r   s                    r   4test_medium_1_log_filename_uses_deterministic_sha256r      s    &G
 H$> ?@D	$B}b1}b1h)H4::;\]^Iy> Q >Q   >Q                                QbkQl]^RSRXRXQlPmn    
 Q<D		EtLAW1D=WWW1DWWWWWW1WWW1WWWDWWWPQUPVWWWWWWW Rms   1J
c                   t        t        t        |       }t        t	        | dz              }t        t	        | dz              }d}|j                  |d|       |j                  |d|       t        | dz  d	z  j                  d
            }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|D 
cg c]  }
|
j                    c}
       dz   d|	iz  }t#        t        j$                  |            dx}x}}yc c}
w )u8   다른 path 는 다른 hash → 파일명 충돌 회피.ru   zwt-ar   zwt-br   test_atest_br   r   r   r   r   r   r   r   r   u,   다른 path 는 다른 파일이어야 함: r   r   N)r   r&   r   r-   r   r   sortedr   r   r<   r=   r>   r?   r@   rA   r   r   rB   rC   )r   r   cand_acand_br   r   r   rG   r   r   r   r   s               r   4test_medium_1_different_paths_yield_different_hashesr      s?   &G
 #h&7"89F#h&7"89F	$B2.2.8+h6<<=^_`Iy>lQl>Qlll>Qllllll3lll3llllllylllylll>lllQlll"N`iOj[\PQPVPVOjNk llllllllOjs   	G
c                v    dt          d  fd}t        |t        |       }|j                  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}}g }d}	|j                  }
|
j                  } |       }|	|v }|}|sPd}|j                  }|j                  } |       }||v }|}|s'd}|j                  }|j                  } |       }||v }|}|st        j                  d|fd|	|f      t        j                  |	      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }|j                  |       |st        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                        t        j                        t        j                  |      dz  }dd|iz  }|j                  |       |st        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                        t        j                        t        j                  |      dz  }dd|iz  }|j                  |       t        j                   |d      i z  }dd|iz  }t        t        j                  |            dx}x}x}	x}x}
x}x}x}x}x}x}x}x}x}x}x}}y) u  lsof +D 로 path 에 file handle 가 열려있으면 FAIL (pgrep argv 미감지 보완).

    시나리오: pgrep -f 는 argv 에 path 가 없어서 rc=1 (no match),
    그러나 lsof 는 process CWD 가 path 라서 rc=0 (match).
    → 사용 중으로 판정 (FAIL).
    ziCOMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash     999 jay  cwd    DIR   8,1     4096    1 
c                    d| v rd| v rt        ddd      S d| v rt        ddd      S | r| d   dk(  rt        dd      S t        ddd      S Nworktreero   r   r_   pgrepr   lsofr#   )r   rq   lsof_outputs     r   rs   z<test_medium_2_safety_5_lsof_match_fails.<locals>.fake_runner  sb    &D.B##d?B##DGv%K,,QBr   ru   Fr0   rz   r{   r|   r~   r   Nr   zfile-handlecwdr   zb%(py3)s in %(py11)s
{%(py11)s = %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s.detail
}.lower
}()
}r   r}   r   r8   r:   %(py13)spy13zh%(py16)s in %(py24)s
{%(py24)s = %(py22)s
{%(py22)s = %(py20)s
{%(py20)s = %(py18)s.detail
}.lower
}()
}py16py18py20py22py24%(py26)spy26)zh%(py29)s in %(py37)s
{%(py37)s = %(py35)s
{%(py35)s = %(py33)s
{%(py33)s = %(py31)s.detail
}.lower
}()
})py29py31py33py35py37z%(py39)spy39r   zassert %(py42)spy42)FAKE_WORKTREE_PATHr   r   check_safety_5_not_in_user   r<   r=   r>   r?   r@   rA   rB   rC   r   lowerappend_format_boolop)!r   rs   r   r{   rE   r   rF   r   r   r   @py_assert6rH   @py_assert10r   @py_assert15@py_assert19@py_assert21@py_assert23@py_assert17@py_assert28@py_assert32@py_assert34@py_assert36@py_assert30rK   @py_format14@py_format25@py_format27@py_format38@py_format40@py_format41@py_format43r   s!                                   @r   'test_medium_2_safety_5_lsof_match_failsr      s   	<<N;Or	S 
  %G
 ../ABF==!E!=E!!!!=E!!!!!!6!!!6!!!=!!!E!!!!!!!v6vV]]v]((v(*v6**vmvv}}v}?R?Rv?R?Tvm?T.TvX]vagananvanatatvatavvX]avXvvvvv6*vvv6vvvvvvVvvvVvvv]vvv(vvv*vvvvvvvm?Tvvvmvvvvvvvvvvvvvv}vvv?Rvvv?TvvvvvvvX]avvvvX]vvvvvvagvvvagvvvanvvvatvvvavvvvvvvvvvvvvvvvvvr   c                `   d }t        |t        |       }|j                  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}}g }d}	|j                  }
|
j                  } |       }|	|v }|}|s'd}|j                  }|j                  } |       }||v }|}|st        j                  d|fd|	|f      t        j                  |	      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }|j                  |       |st        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                        t        j                        t        j                  |      dz  }dd|iz  }|j                  |       t        j                   |d      i z  }dd|iz  }t        t        j                  |            d
x}x}x}	x}x}
x}x}x}x}x}x}}y
)u?   pgrep no match + lsof no match → PASS (정상 안전 경로).c                    d| v rd| v rt        ddd      S d| v rt        ddd      S | r| d   dk(  rt        ddd      S t        ddd      S r   r#   r   rq   s     r   rs   z@test_medium_2_safety_5_lsof_no_match_passes.<locals>.fake_runner!  sa    &D.B##d?B##DGv%B##QBr   ru   Tr0   rz   r{   r|   r~   r   Nr   fhr   r   r   r   r   r   r   r   r   r   zassert %(py29)sr   )r   r   r   r   r   r<   r=   r>   r?   r@   rA   rB   rC   r   r   r   r   )r   rs   r   r{   rE   r   rF   r   r   r   r   rH   r   r   r   r   r   r   r   rK   r   r   r   @py_format28@py_format30s                            r   +test_medium_2_safety_5_lsof_no_match_passesr     s     %G
 ../ABF== D =D    =D      6   6   =   D       K6KV]]K]((K(*K6**KdKfmmKm6I6IK6I6KKd6K.KKKKK6*KKK6KKKKKKVKKKVKKK]KKK(KKK*KKKKKKKd6KKKKdKKKKKKfKKKfKKKmKKK6IKKK6KKKKKKKKKKKKKKKKr   c                t   d }t        |t        |       }|j                  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                  }|	|v }
|
st        j                  d|
fd|	|f      t        j                  |	      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}	x}
}y
)u@   lsof rc != 0,1 (예: signal / access denied) → 보수적 FAIL.c                    d| v rd| v rt        ddd      S d| v rt        ddd      S | r| d   dk(  rt        ddd	      S t        ddd      S )
Nr   ro   r   r_   r   r   r   r   zpermission deniedr#   r   s     r   rs   zPtest_medium_2_safety_5_lsof_unknown_rc_fails_conservatively.<locals>.fake_runner7  sb    &D.B##d?B##DGv%B 344QBr   ru   Fr0   rz   r{   r|   r~   r   Nz	lsof rc=2r   r   r   )r   r   r   r   r   r<   r=   r>   r?   r@   rA   rB   rC   r   )r   rs   r   r{   rE   r   rF   r   r   r   r   s              r   ;test_medium_2_safety_5_lsof_unknown_rc_fails_conservativelyr  5  s	     %G
 ../ABF==!E!=E!!!!=E!!!!!!6!!!6!!!=!!!E!!!!!!!'&--';-'''';-''';''''''&'''&'''-'''''''r   c                    ddifd}t        |t        |       }|j                  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   }	d}|	|k(  }
|
st        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}	x}
}y)uQ   pgrep 매치 단계에서 이미 FAIL → lsof 호출 X (성능 + 단락 평가).nr   c                    d| v rd| v rt        ddd      S d| v rt        ddd      S | r"| d   dk(  rdxx   d	z  cc<   t        d	dd      S t        ddd      S )
Nr   ro   r   r_   r   z1234
r   r  r   r#   )r   rq   lsof_calleds     r   rs   zRtest_medium_2_safety_5_pgrep_match_short_circuits_before_lsof.<locals>.fake_runnerN  sr    &D.B##d?Hb))DGv%!B##QBr   ru   Fr0   rz   r{   r|   r~   r   Nr   )z%(py1)s == %(py4)s)r   r6   u/   pgrep match 후에는 lsof 호출되면 안 됨z
>assert %(py6)sr7   )r   r   r   r   r   r<   r=   r>   r?   r@   rA   rB   rC   r   )r   rs   r   r{   rE   r   rF   r   r   r   r   @py_format5r   r  s                @r   =test_medium_2_safety_5_pgrep_match_short_circuits_before_lsofr	  J  s   (K  %G
 ../ABF==!E!=E!!!!=E!!!!!!6!!!6!!!=!!!E!!!!!!!sSqSq SSSqSSSSSSqSSS"SSSSSSSSr   c                    t        dd      D  cg c]  } t        | d|  dd       }} |j                  t        dddd	             t        d
d|dddddd	d
      }t	        |      }d}||u }|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 )u   safety 1~5 PASS + safety_6 (apply_explicit) 만 FAIL → helper True.

    이는 dry-run 에서 cleanup_candidates 가 가시성 회복하는 정확한 경계 조건.
    r      cTok	conditionr   r   r   apply_explicitFdry-run
/some/pathr   r   
worktree_pathr+   safety_resultsall_safedirtyis_mainappliedskippedskip_reasonr   r0   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} is %(py6)sr   r{   r   assert %(py8)sr   N)ranger	   r   r   r   r<   r=   r>   r?   r@   rA   rB   rC   )isrr{   r   rG   r   r   r   s           r   ?test_medium_3_is_safe_ignoring_apply_true_when_only_apply_failsr!  f  s    q!
 	!A3TR
B 
 II#a6Fu]fgh"&F "&)1T1)T1111)T111111!111!111111&111&111)111T1111111#
s   E-c                    t        dddd      t        dddd	      t        d
ddd      t        dddd      t        dddd      t        dddd      g} t        dd| ddddddd
      }t        |      }d}||u }|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) u0   safety 1~5 중 1개라도 FAIL → helper False.r   
done_ackedTr  r  r   	pr_mergedFz
not merged   
merge_done   branch_in_mainr   
not_in_user  r  r  r  r   zsafety failedr   r  r0   r  r   r{   r   r  r   N)r	   r   r   r<   r=   r>   r?   r@   rA   rB   rC   )r   r{   r   rG   r   r   r   s          r   :test_medium_3_is_safe_ignoring_apply_false_when_1to5_failsr*    s%    	TRVWER^_TRVW0@VZ[TRVW0@W`a
B "#&F "&)2U2)U2222)U222222!222!222222&222&222)222U2222222r   c                    t        dd      D  cg c]  } t        | d|  dd       }} t        dd|dd	dd	dd
d
      }t        |      }d	}||u }|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}}t        dd|ddd	d	ddd
      }t        |      }d	}||u }|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 )u;   main / dirty 면 모든 safety PASS 여도 candidate False.r      r  Tr  r  r  r   Fmain
2026-05-12r  r0   r  r   main_resultr   r  r   Nr  dirty_result)r  r	   r   r   r<   r=   r>   r?   r@   rA   rB   rC   )	r  r   r/  r   rG   r   r   r   r0  s	            r   =test_medium_3_is_safe_ignoring_apply_false_when_main_or_dirtyr1    s    q!
 	!A3TR
B 
  "KUD%|K
 "+.7%7.%7777.%777777!777!777777+777+777.777%7777777 "KT5%L
 ",/858/58888/5888888!888!888888,888,888/88858888888!
s   I3c                 L   t        ddg ddddddd
      } t        |       }d}||u }|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)u7   safety_results 비어있으면 (skip 경로 등) False.r  NFTztask_id missingr.  r  r0   r  r   r{   r   r  r   )
r   r   r<   r=   r>   r?   r@   rA   rB   rC   )r{   r   rG   r   r   r   s         r   Dtest_medium_3_is_safe_ignoring_apply_false_when_safety_results_emptyr3    s    "%F "&)2U2)U2222)U222222!222!222222&222&222)222U2222222r   c                   ddl }t        | dz        }|j                  |j                  d            j	                         dd }|j                  |j                  d            j	                         dd }||k(  }|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)u   PYTHONHASHSEED 가 변해도 동일 path → 동일 파일명 hash.

    `hash()` 는 PYTHONHASHSEED 의존 (Python 재시작마다 다름).
    sha256 은 결정론 — 환경 변동에 무관.
    r   Nzdeterministic-wtzutf-8   r   )z%(py0)s == %(py2)sh_ah_b)r4   r5   uC   sha256 은 동일 input 에 대해 항상 동일 output (결정론)z
>assert %(py4)sr6   )hashlibr   sha256encode	hexdigestr<   r=   r>   r?   r@   rA   r   rB   rC   )r   _hashlibr)   r6  r7  rE   @py_format3r  s           r   9test_concurrency_log_filename_stable_under_pythonhashseedr>    s     x,,-D
//$++g.
/
9
9
;BQ
?C
//$++g.
/
9
9
;BQ
?C#:\\\3#\\\\\\3\\\3\\\\\\#\\\#\\\\\\\\\\\r   c                <   t        j                  ddddddddddd	dg      fd
}t        |t        |       }|j	                  dd      }|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                  }|	|v }
|
st        j                  d|
fd|	|f      t        j                  |	      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}	x}
}g }d}
|j                  }|
|v}|}	|sd}|j                  }||v }|}	|	st        j                  d|fd|
|f      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }|j                  |       |st        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d d!|iz  }|j                  |       t        j                   |d"      i z  }d#d$|iz  }t        t        j                  |            dx}	x}x}
x}x}x}x}}y)%u6  PR #100 HIGH unresolved 회귀 시나리오 직접 재현:
       task-25 검사 + PR list 에 task-2550 MERGED + task-25 OPEN 이 있을 때
       기존 BUG 였다면 task-25 가 task-2550 의 MERGED 에 잘못 매칭되어 PASS 했을 것.
       수정 후 task-25 strict 매칭만 → OPEN → FAIL.
    rd   re   r   rf   rj   rk   ztask/task-25-dev3c   ztask/task-2500+1-dev4c                H    d| v rd| v rt        dd      S t        ddd      S rm   r#   rp   s     r   rs   zFtest_pr100_high_unresolved_regression_bug_blocked.<locals>.fake_runner  rt   r   ru   r/   Nry   Fr0   rz   r{   r|   r~   r   r   r   r   )not in)z2%(py3)s not in %(py7)s
{%(py7)s = %(py5)s.detail
})r   r}   r   z%(py9)sr8   )z2%(py12)s in %(py16)s
{%(py16)s = %(py14)s.detail
})py12py14r   z%(py18)sr   r   zassert %(py21)spy21)r   r   r   r   r   r   r<   r=   r>   r?   r@   rA   rB   rC   r   r   r   )r   rs   r   r{   rE   r   rF   r   r   r   r   r   @py_assert11r   @py_assert13rJ   @py_format17@py_format19@py_format20@py_format22rr   s                       @r   1test_pr100_high_unresolved_regression_bug_blockedrL    s1    **:OP7JK9PQ K 
 %G --i-EF==!E!=E!!!!=E!!!!!!6!!!6!!!=!!!E!!!!!!!"V]]"6]""""6]"""6""""""V"""V"""]"""""""C8C6==C8=(CFCfmmCFm,CCCCC8=CCC8CCCCCC6CCC6CCC=CCCCCCCFmCCCFCCCCCCfCCCfCCCmCCCCCCCCCCCCCCr   )returnr   )r   r_   r_   )r   intr   r   r   r   rM  subprocess.CompletedProcess)r$   objectr%   rP  rM  rO  )
r)   r   r*   r   r+   z
str | Noner,   r   rM  r
   )rM  None)r   r   rM  rQ  )>__doc__
__future__r   builtinsr>   _pytest.assertion.rewrite	assertionrewriter<   r   r   sysr   r   pathlibr   __file__resolveparentsWORKSPACE_ROOTr   r)   insertanu_v2.worktree_cleanupr   r	   r
   r   r   r   FAKE_SHAFAKE_TASK_IDFAKE_BRANCHr   r   r!   r&   r-   rL   rP   rS   rU   rX   rZ   r\   r`   rb   r   r   r   r   r   r   r   r  r	  r!  r*  r1  r3  r>  rL  r   r   r   <module>rc     si   #     
 '  h'')11!4~chh&HHOOAs>*+  #D @e #&	[
[[ [ 	[
 [N
N
O
OS
REG=#<%*"8X<m*w>L,(*T822329*3*
]"Dr   