
    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ZddlmZmZ ddlmZ ddlmZ  ee      j'                         j(                  d   Z ee      ej.                  vr"ej.                  j1                  d ee             ddlmZ d	Zd
ZdZdZddZdddZ dded	 	 	 	 	 	 	 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)y)!u  통합 회귀 테스트 — PostMergeSmokeRunner + WorktreeCleanup 통합 (task-2550+1).

PR #100 medium unresolved fix 회귀:
  - medium #3: `r.all_safe` 가 dry-run 에서 항상 False → cleanup_candidates 0 (가시성 상실).
                fix 후: `is_safe_ignoring_apply` helper 사용 → 1~5 PASS 만 검사하여 후보 산정.

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

⚠️ 테스트 코드 내 raw token placeholder 는 실제 토큰 아님.
    )annotationsN)datetimetimezone)Path)Callable   )PostMergeSmokeRunner(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbztask-2550+1ztask/task-2550+1-dev5c            	     B    t        ddddddt        j                        S )Ni        r   )tzinfo)r   r   utc     L/home/jay/workspace/anu_v2/tests/test_post_merge_smoke_worktree_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   (   s    &&B:f]cddr   MERGED)worktree_porcelainpr_statepr_head_refc                J     t        j                  d||dg       fd}|S )u1   smoke PASS + worktree cleanup 지원 fake runner.i  numberstateheadRefNamec                   d| v rt        dt        dz   d      S | rGt        | d         j                  d      st        | d         j                  d      rt        ddd      S d| v rd	| v rd
| v rt        dd      S d| v rd
| v rt        ddd      S d| v rd	| v rt        dd      S 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 )N	rev-parser   
r   python3pytestzsmoke okworktreelist--porcelainstatuspr
merge-basepgrep   lsof)r   FAKE_MAIN_SHAstrendswithr   _pr_responser   s     r   fake_runnerz,_make_smoke_pass_runner.<locals>.fake_runner7   s   $MD0"55Sa\**95T!W9N9Nx9XJ++&D.]d5J.33t 5B##4<FdNK,,4B##d?B##DGv%B##QBr   )jsondumps)r   r    r!   r;   r:   s   `   @r   _make_smoke_pass_runnerr>   ,   s/     **+F K ( r   c                     d } | S )Nc                L    d| v rt        dt        dz   d      S t        ddd      S )Nr(   r   r)   r   r3   zsmoke failedr   r5   r   r9   s     r   r;   z,_make_smoke_fail_runner.<locals>.fake_runnerO   s-    $MD0"55QN++r   r   )r;   s    r   _make_smoke_fail_runnerrC   N   s    ,
 r   c                   t        | dz        }| dz  dz  }|j                  dd       |t         dz  j                          |t         dz  j                          d| d	t         d
t
         d}t        |      }t        |d t        |       }|j                  t        t        dg      }|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}||v }	|	st        j                  d|	fd||f      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}}	|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  }dd|iz  }t        t        j                  |            dx}}	|d$   }d%}||u }	|	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)*ua   smoke PASS → return dict 에 worktree_cleanup_summary 키 존재, apply=False, applied_count=0.some-worktreememoryeventsTparentsexist_ok.done.acked.merge-done	worktree 
HEAD 
branch refs/heads/r)   r   c                     y Nr   r9   s    r   <lambda>z:test_smoke_pass_triggers_cleanup_dry_run.<locals>.<lambda>l       r   subprocess_runnercapabilities_loaderclockworkspace_rootzanu_v2/worktree_cleanup.pytask_idmerge_commitexpected_filesoutcomePASS==z%(py1)s == %(py4)spy1py4assert %(py6)spy6Nworktree_cleanup_summary)in)z%(py1)s in %(py3)sresultre   py3assert %(py5)spy5)is not)z%(py0)s is not %(py3)ssummarypy0rm   applyFisz%(py1)s is %(py4)sapplied_countr   )r6   mkdirFAKE_TASK_IDtouch
FAKE_SHA40FAKE_BRANCHr>   r	   r   run_post_merge_smoke
@pytest_ar_call_reprcompare	_safereprAssertionError_format_explanation@py_builtinslocals_should_repr_global_name)tmp_pathwt_path
events_dirr   r;   runnerrk   @py_assert0@py_assert3@py_assert2@py_format5@py_format7@py_format4@py_format6rq   @py_assert1s                   r   (test_smoke_pass_triggers_cleanup_dry_runr   [   s   (_,-GH$x/JTD1\N+..557\N+..557 G9 | (M	-  *=OPK!%*	F ((45 ) F )&&&&&&&&&&&&&&&&&&&%/%////%///%/////////////////0G7$7$77$7$u$u$$$$u$$$$$$u$$$$$$$?#(q(#q((((#q(((#(((q(((((((r   c                   t        | dz        }| dz  dz  }|j                  dd       |t         dz  j                          |t         dz  j                          d| d	t         d
t
         d}t        |      }t        |d t        |       }|j                  t        d      }|d   }d}||u }	|	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}	}|d   }d}||k(  }	|	st        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
t        j                  d|d    d|d    d       d!z   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)$u  task-2550+1 medium #3 핵심 회귀:
       safety 1~5 모두 PASS 인 worktree 가 있을 때 dry-run 에서도
       cleanup_candidates >= 1 (가시성 회복).

    기존 BUG: `r.all_safe and not r.is_main and not r.dirty` 는
              dry-run 에서 항상 False → candidate 항상 0 (가시성 상실).
    수정 후: `is_safe_ignoring_apply(r)` 사용 → 1~5 PASS 시 candidate 1.
    zsome-worktree-2550plus1rF   rG   TrH   rK   rL   rM   rN   rO   r)   rP   c                     y rR   r   rS   s    r   rT   zXtest_medium_3_dry_run_cleanup_candidates_visible_when_safety_1to5_pass.<locals>.<lambda>   rU   r   rV   Fr\   rt   rt   ru   rw   rd   rg   rh   Ntotal_worktreesr3   ra   rc   cleanup_candidatesux   medium #3 회귀: dry-run 에서도 1~5 PASS 인 worktree 는 candidate 로 가시성 확보 — got cleanup_candidates=z
 (results=results)
>assert %(py6)srx   r   )r6   ry   rz   r{   r|   r}   r>   r	   r   'run_post_merge_worktree_cleanup_dry_runr   r   r   r   r   _format_assertmsgr   r   r   r   r;   r   rq   r   r   r   r   r   s               r   Ftest_medium_3_dry_run_cleanup_candidates_visible_when_safety_1to5_passr      sl    (667GH$x/JTD1\N+..557\N+..557 G9 | (M	-  *=OPK!%*	F <<\Y^<_G 7$u$u$$$$u$$$$$$u$$$$$$$$%**%****%***%**********'( A (A-  (A    )    -.   %&:;<JwyGYFZZ[	]    
 ?#(q(#q((((#q(((#(((q(((((((r   c                   t        | dz        }| dz  dz  }|j                  dd       |t         dz  j                          |t         dz  j                          d| d	t         d
t
         d}t        |d      }t        |d t        |       }|j                  t        d      }|d   }d}||k(  }	|	st        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
t        j                  d|d          dz   d|
iz  }t        t        j                  |            dx}x}	}y)uI   safety 2 FAIL (PR state OPEN 또는 strict match 불가) → candidate 0.rE   rF   rG   TrH   rK   rL   rM   rN   rO   r)   OPEN)r   r    c                     y rR   r   rS   s    r   rT   zStest_medium_3_dry_run_cleanup_candidates_zero_when_safety_2_fails.<locals>.<lambda>   rU   r   rV   Fr   r   r   ra   rc   rd   u5   safety 2 FAIL 시 candidate 는 0 이어야 함. got r   rh   N)r6   ry   rz   r{   r|   r}   r>   r	   r   r   r   r   r   r   r   r   r   s               r   Atest_medium_3_dry_run_cleanup_candidates_zero_when_safety_2_failsr      sh   (_,-GH$x/JTD1\N+..557\N+..557 G9 | (M	-  *-K "%*	F <<\Y^<_G'( A (A-  (A    )    -.    @H\@]?^_     r   c                   t        t               d t        |       }|j                  t        t
        g       }|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}}|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  }
dd|
iz  }t        t        j                  |            dx}	}y)uA   smoke FAIL → worktree_cleanup_summary 키 없음 (또는 None).c                     y rR   r   rS   s    r   rT   z:test_smoke_fail_does_not_trigger_cleanup.<locals>.<lambda>   rU   r   rV   r[   r_   FAILra   rc   rd   rg   rh   Nri   ru   )z%(py0)s is %(py3)scleanup_summaryrr   rn   ro   )r	   rC   r   r~   rz   r|   r   r   r   r   r   getr   r   r   )r   r   rk   r   r   r   r   r   r   r   r   r   s               r   (test_smoke_fail_does_not_trigger_cleanupr      s    !13*	F (( ) F )&&&&&&&&&&&&&&&&&&&jj!;<O""?d""""?d""""""?"""?"""d"""""""r   c                d   d }t        |d t        |       }|j                  t        t        g       }|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}}|j                  }	d} |	|      }
d}|
|u }|st        j                  d|fd|
|f      dt        j                         v st        j                  |      rt        j                  |      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)uC   cleanup raise 해도 smoke PASS 결과는 PASS (try/except 보호).c                    d| v rt        dt        dz   d      S ddj                  d | D              v s| rdt        | d         v rt        dd	d      S d
| v rd| v rt	        d      t        ddd      S )Nr(   r   r)   r   r+    c              3  2   K   | ]  }t        |        y wrR   )r6   ).0as     r   	<genexpr>zctest_smoke_pass_cleanup_failure_does_not_break_smoke_result.<locals>.fake_runner.<locals>.<genexpr>   s     51A5s   r*   okr,   r-   zsimulated cleanup failure)r   r5   joinr6   RuntimeErrorrB   s     r   r;   zPtest_smoke_pass_cleanup_failure_does_not_break_smoke_result.<locals>.fake_runner   s    $MD0"55sxx5555$9PSTXYZT[P\C\D"%%&D.:;;QBr   c                     y rR   r   rS   s    r   rT   zMtest_smoke_pass_cleanup_failure_does_not_break_smoke_result.<locals>.<lambda>   rU   r   rV   r[   r_   r`   ra   rc   rd   rg   rh   N	exit_coder   ri   ru   )zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} is %(py9)srk   )rs   py2rf   rh   py9zassert %(py11)spy11)r	   r   r~   rz   r|   r   r   r   r   r   r   r   r   r   )r   r;   r   rk   r   r   r   r   r   r   @py_assert5@py_assert8@py_assert7@py_format10@py_format12s                  r   ;test_smoke_pass_cleanup_failure_does_not_break_smoke_resultr      s     "%*	F (( ) F )&&&&&&&&&&&&&&&&&&&+#!#!####!######!#######::909:019T91T99991T99999969996999:99909991999T99999999r   c                T
   t        | dz        }d| dt         dt         d}t        |      }t	        |d t
        |       }|j                  t        d	
      }h d}|t        |j                               z
  }| }|s~t        j                  d|       dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }	t!        t        j"                  |	            d}|d   }t%        |t&              }
|
sddt        j                         v st        j                  t$              rt        j                  t$              ndt        j                  |      dt        j                         v st        j                  t&              rt        j                  t&              ndt        j                  |
      dz  }t!        t        j"                  |            dx}}
|d   }t%        |t(              }
|
sddt        j                         v st        j                  t$              rt        j                  t$              ndt        j                  |      dt        j                         v st        j                  t(              rt        j                  t(              ndt        j                  |
      dz  }t!        t        j"                  |            dx}}
|d   }|t        k(  }|st        j*                  d|fd|t        f      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}}|d   }d	}||u }|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}}|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)*u1   리턴 dict 필수 키 완전성 + 타입 검증.rE   rM   rN   rO   r)   rP   c                     y rR   r   rS   s    r   rT   zAtest_post_merge_worktree_cleanup_dry_run_schema.<locals>.<lambda>  rU   r   rV   Fr   >
   tsrt   r   r\   rx   dirty_skippedskipped_countmain_protectedr   r   u   필수 키 누락: z
>assert not %(py0)srs   missingNr   z5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstanceint)rs   r   rm   ro   rt   boolr\   ra   )z%(py1)s == %(py3)srz   rl   rn   ro   ru   rw   rd   rg   rh   rx   r   rc   r   )>=)z%(py1)s >= %(py4)s)r6   r|   r}   r>   r	   r   r   rz   setkeysr   r   r   r   r   r   r   r   r   r   r   r   )r   r   r   r;   r   rk   requiredr   r   @py_format2@py_assert4r   r   r   r   r   r   r   s                     r   /test_post_merge_worktree_cleanup_dry_run_schemar     s.   (_,-G
G9 | (M	- 
 *=OPK!%*	F ;;LX];^FH
 V[[]++G;7;77-gY7777777w777w777777128:2C88888888:888:8882888888C888C8888888888Wo,:ot,,,,,,,,:,,,:,,,o,,,,,,t,,,t,,,,,,,,,,),,,,,,,,,,,,,,,,,,,,,,,,'?#e#?e####?e###?###e#######/"'a'"a''''"a'''"'''a'''''''#$))$))))$)))$))))))))))r   c                   t        | dz        }| dz  dz  }|j                  dd       |dz  j                          |dz  j                          d| d	t         d
t	        j
                  ddddg      fd}t        |d t        |       }|j                  dd      }|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	t        j                  d|d          dz   d|	iz  }
t        t        j                  |
            dx}x}}y)u   smoke 통합 시점에서 strict task_id 매칭 — task-25 검색 시 task-2550 PR 채택 X.

    cleanup_candidates 가 task-25 의 PR 미발견으로 0 이어야 함
    (BUG 였다면 task-2550 의 MERGED 가 task-25 에 매칭되어 candidate 1).
    zsmall-task-wtrF   rG   TrH   ztask-25.done.ackedztask-25.merge-donerM   rN   z%
branch refs/heads/task/task-25-dev3
d   r   ztask/task-2550-dev5r#   c                >   d| v rt        dt        dz   d      S d| v rd| v rt        dd      S d| v rd| v rt        ddd      S d	| v rd| v rt        dd      S 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(   r   r)   r   r,   r-   r/   r.   r0   r1   r2   r3   r4   rA   r8   s     r   r;   zctest_high_boundary_integration_task25_does_not_match_task2550_in_smoke_cleanup.<locals>.fake_runnerN  s    $MD0"55&D..33t 5B##4<FdNK,,4B##d?B##DGv%B##QBr   c                     y rR   r   rS   s    r   rT   z`test_high_boundary_integration_task25_does_not_match_task2550_in_smoke_cleanup.<locals>.<lambda>a  rU   r   rV   ztask-25Fr   r   r   ra   rc   rd   ur   HIGH 통합 회귀: task-25 가 task-2550 PR 에 부분 일치 매칭 차단되어야 함. got cleanup_candidates=r   rh   N)r6   ry   r{   r|   r<   r=   r	   r   r   r   r   r   r   r   r   )r   r   r   r;   r   rq   r   r   r   r   r   r:   r   s              @@r   Ntest_high_boundary_integration_task25_does_not_match_task2550_in_smoke_cleanupr   7  sn    (_,-GH$x/JTD1&&--/&&--/ G9 | 0	1  **:OP K " "%*	F <<YV[<\G'( A (A-  (A    )    -.   "")*>"?!@	B     r   )returnr   )r   r   r   )r   r   r   r6   r   r6   r   zsubprocess.CompletedProcess)r   r6   r    r6   r!   r6   r   *Callable[..., subprocess.CompletedProcess])r   r   )r   r   r   None)*__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   r<   r   sysr   r   pathlibr   typingr   __file__resolverI   WORKSPACE_ROOTr6   pathinsertanu_v2.post_merge_smoke_runnerr	   r|   r5   rz   r}   r   r   r>   rC   r   r   r   r   r   r   r   r   r   r   <module>r      s   	 #     
 '  h'')11!4~chh&HHOOAs>*+
 
%@e !"	  	
 0D!)P))`H#0:F*J4r   