
    j8                       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mZ ddlZ ee      j#                         j$                  d   Zedz  dz  ZddZdd	Z G d
 d      Z G d d      Z G d d      Z G d d      Zy)u\  
task-2576 TODO-3 신규 회귀: stash_audit.py invocation failure 시 fail-stop (sys.exit(1)) 보장.

배경:
- PR #125 (attempt-3) 에서 Gemini HIGH `:1196` finding 발견:
  `if res.returncode != 0 or not res.stdout.strip(): sys.exit(0)`
  → audit 실패를 success로 swallow하여 spec §3.2.2.3 fail-stop 위반.
- task-2576 TODO-3: sys.exit(0) → sys.exit(1) + `if debug:` 가드 제거 + FAIL-STOP 로그.

검증:
1. 정적: sys.exit(0) 회귀 0건, sys.exit(1) 존재, FAIL-STOP 로그 존재.
2. 동적: heredoc 추출 + audit 실패 시뮬레이션 후 non-zero exit + 메시지 확인.
    )annotationsN)Path   scriptszfinish-task.shc                 .    t         j                  d      S )Nutf-8encoding)FINISH_TASK_SH	read_text     O/home/jay/workspace/tests/regression/test_finish_task_audit_failure_failstop.py_read_shr       s    ##W#55r   c                    d}| j                  |      }|dk(  ryd}| j                  |d|      }|dk(  r|}d}| j                  ||      }|dk(  ry| || S )up  
    finish-task.sh 의 stash-lifecycle dispatch 블록 내 PYEOF heredoc 본문을 추출한다.
    (stash-lifecycle dispatch 헤레독: python3 - ... <<'PYEOF' … PYEOF 블록)

    dispatch heredoc 는 'task_id = sys.argv[1]' 로 시작하고 'PYEOF' 로 끝나는
    두 번째 대형 Python heredoc 블록이다. (첫 번째는 cleanup-audit.jsonl 블록)
    z audit_timeout = int(sys.argv[7]) ztask_id = sys.argv[1]r   z
PYEOF)findrfind)contentmarkeridxblock_start_marker	start_idx
end_markerend_idxs          r    _extract_stash_lifecycle_heredocr   $   sr     0F
,,v
C
by00!S9IB	Jll:y1G"}9w''r   c                      e Zd ZdZd Zd Zy) TestNoSysExit0InAuditFailurePathuN   TODO-3 회귀 — audit failure 처리부에 sys.exit(0) 가 없어야 한다.c                >   t               }t        j                  dt        j                        }|j	                  |      }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|j                          d|j                         d	      d
z   d|iz  }t        t        j                  |            dx}}y)u   
        finish-task.sh 의 stash-lifecycle dispatch python heredoc 내부에
        'if res.returncode != 0 or not res.stdout.strip(): ... sys.exit(0)' 패턴이
        0건이어야 한다. (PR #125 이전 회귀 탐지)
        z.if\s+res\.returncode\s*!=\s*0.*?sys\.exit\(0\)N)is)z%(py0)s is %(py3)smatchpy0py3uT   TODO-3 회귀 감지: audit failure 처리부에 sys.exit(0) 발견.
매칭 위치: u    — uC   
fix: sys.exit(0) → sys.exit(1) (spec §3.2.2.3 fail-stop 요건)
>assert %(py5)spy5)r   recompileDOTALLsearch
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgstartgroupAssertionError_format_explanationselfr   patternr"   @py_assert2@py_assert1@py_format4@py_format6s           r   -test_sys_exit_0_absent_in_audit_failure_blockzNTestNoSysExit0InAuditFailurePath.test_sys_exit_0_absent_in_audit_failure_blockH   s     * **=II
 w' 	
u} 	
 		
u 	
 	
 
6		
 	
 	  	
 	
 
		  	
 	
 
		  	
 	
 #kkm_E%++-1B CQQ	
 	
 	
 		
 	
r   c                   t               }t        |      }|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}||v}|st        j                  d|fd||f      t        j                  |      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x}}y)u   
        stash-lifecycle heredoc 본문에 sys.exit(0) 가 없어야 한다.
        (heredoc 추출 후 isolated 검증)
        uY   stash-lifecycle dispatch heredoc 를 추출할 수 없음 — 구조 변경 확인 필요
>assert %(py0)sr$   heredoczsys.exit(0))not in)z%(py1)s not in %(py3)spy1r%   ut   TODO-3 회귀: stash-lifecycle heredoc 내 sys.exit(0) 발견.
fix: sys.exit(1) 로 교체 필수 (fail-stop 보장)r&   r'   Nr   r   r,   r2   r.   r/   r0   r1   r5   r6   r-   r8   r   rA   @py_format1@py_assert0r:   r<   r=   s           r   (test_sys_exit_0_not_in_lifecycle_heredoczITestNoSysExit0InAuditFailurePath.test_sys_exit_0_not_in_lifecycle_heredoc]   s    
 *27;ssssssssswssswsssss 	
}G+ 	
 	
}G 	
 	
 
	  	
 	
 
6	
 	
  %, 	
 	
 
	 %, 	
 	
 D	
 	
 	
 	
 	
r   N)__name__
__module____qualname____doc__r>   rI   r   r   r   r   r   E   s    X
*
r   r   c                  (    e Zd ZdZd Zd Zd Zd Zy)%TestSysExit1PresentInAuditFailurePathuR   TODO-3 fix 박제 — audit failure 처리부에 sys.exit(1) 가 있어야 한다.c                   t               }t        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	z   d
|iz  }t        t	        j                  |            dx}x}}y)um   
        'FAIL-STOP — stash_audit.py invocation failed' 문구가 1건 이상 존재해야 한다.
        u8   FAIL-STOP\s+[—\-]+\s+stash_audit\.py invocation failed   )>=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)slenmatchesr$   rD   r%   py6u   TODO-3 fix 실종: 'FAIL-STOP — stash_audit.py invocation failed' 메시지 없음.
finish-task.sh stash-lifecycle heredoc 에 FAIL-STOP 로그 추가 필요.
>assert %(py8)spy8N)r   r(   findallrS   r,   r-   r.   r/   r0   r1   r2   r5   r6   )r8   r   rT   r:   @py_assert5@py_assert4@py_format7@py_format9s           r   "test_fail_stop_log_message_presentzHTestSysExit1PresentInAuditFailurePath.test_fail_stop_log_message_presents   s(    ***G
 7| 	
q 	
|q  	
 	
 	
|q 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	  	
 	
 
	  ! 	
 	
 Y	
 	
 	
 	
 	
 	
r   c                   t               }t        j                  dt        j                        }|j	                  |      }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)
uN   
        FAIL-STOP 로그 직후에 sys.exit(1) 이 있어야 한다.
        uI   FAIL-STOP\s+[—\-]+\s+stash_audit\.py invocation failed.*?sys\.exit\(1\)Nis notz%(py0)s is not %(py3)sr"   r#   uu   TODO-3 fix 실종: FAIL-STOP 로그 후 sys.exit(1) 없음.
audit 실패 시 sys.exit(1) 로 fail-stop 보장 필요.r&   r'   )r   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r2   r5   r6   r7   s           r   )test_sys_exit_1_adjacent_to_fail_stop_logzOTestSysExit1PresentInAuditFailurePath.test_sys_exit_1_adjacent_to_fail_stop_log   s     ***XII
 w'  	
uD  	
 	
uD 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	 ! 	
 	
 H	
 	
 	
 	
 	
r   c                   t               }t        |      }|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}||v }|st        j                  d|fd||f      t        j                  |      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x}}y)u[   
        stash-lifecycle heredoc 본문에 sys.exit(1) 가 포함되어야 한다.
        .   stash-lifecycle dispatch heredoc 추출 실패r@   r$   rA   zsys.exit(1)inz%(py1)s in %(py3)srC   uB   TODO-3 fix 실종: stash-lifecycle heredoc 내 sys.exit(1) 없음.r&   r'   NrE   rF   s           r   $test_sys_exit_1_in_lifecycle_heredoczJTestSysExit1PresentInAuditFailurePath.test_sys_exit_1_in_lifecycle_heredoc   s     *27;HHHHHHHHHwHHHwHHHHH 	
}' 	
 	
} 	
 	
 		  	
 	
	6	
 	
  !( 	
 	
 		 !( 	
 	
  Q	
 	
 	
 	
 	
r   c                   t               }t        |      }|s{t        j                  d      dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            t        j                  d      }|j                  } ||      }d}||u}|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                  |      rt        j                  |      ndt        j                  |      t        j                  |      d
z  }	t        j                  d      dz   d|	iz  }
t        t        j                  |
            dx}x}x}}y)u   
        'if res.returncode != 0 or not res.stdout.strip():' 패턴이 stash-lifecycle
        heredoc 내에 있어야 한다 (audit 결과 체크 로직 존재 확인).
        re   r@   r$   rA   Eif\s+res\.returncode\s*!=\s*0\s+or\s+not\s+res\.stdout\.strip\(\)\s*:Nr`   )zP%(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.search
}(%(py3)s)
} is not %(py8)sr9   )r$   py2r%   r'   rX   u\   stash-lifecycle heredoc 내 returncode 체크 로직 없음 — 구조 변경 확인 필요.z
>assert %(py10)spy10)r   r   r,   r2   r.   r/   r0   r1   r5   r6   r(   r)   r+   r-   )r8   r   rA   rG   r9   r;   r[   @py_assert7@py_assert6r]   @py_format11s              r   %test_returncode_check_pattern_presentzKTestSysExit1PresentInAuditFailurePath.test_returncode_check_pattern_present   s{   
 *27;HHHHHHHHHwHHHwHHHHH**ef~~ 	
~g& 	
d 	
&d2 	
 	
 	
&d 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
	6	
 	
  & 	
 	
 		 & 	
 	
 		 ' 	
 	
 		 /3 	
 	
  k	
 	
 	
 	
 	
 	
r   N)rJ   rK   rL   rM   r^   rc   ri   rq   r   r   r   rO   rO   p   s    \

 


r   rO   c                      e Zd ZdZd Zd Zy)"TestNoDebugGuardBeforeAuditFailureuV   TODO-3 fix 박제 — audit failure 처리부에 if debug: 가드가 없어야 한다.c                   t               }|j                  d      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d| d      d	z   d
|iz  }t        t        j                  |            dx}}y)u   
        'audit failed, skipping' 문구가 0건이어야 한다.
        (이전 버전에서 if debug: 가드 하에 audit 실패를 skip 했던 패턴)
        zaudit failed, skippingr   )==)z%(py0)s == %(py3)scountr#   u/   TODO-3 회귀: 'audit failed, skipping' 문구 uU   건 발견.
audit 실패는 반드시 fail-stop 처리되어야 함 (spec §3.2.2.3).r&   r'   N)r   rv   r,   r-   r.   r/   r0   r1   r2   r5   r6   )r8   r   rv   r:   r;   r<   r=   s          r   !test_audit_failed_skipping_absentzDTestNoDebugGuardBeforeAuditFailure.test_audit_failed_skipping_absent   s    
 *67 	
uz 	
 	
u 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	  	
 	
  >eW EX X	
 	
 	
 	
 	
r   c                "   t               }t        |      }|s{t        j                  d      dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            t        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}}|t        d|j                         dz
        |j                          }	t        t        j                   d|	            }
|
r\|
d   }|	|j#                         d }t        j$                  d|      }t'        |      }d}||kD  }|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d       dz   d|iz  }t        t        j                  |            dx}x}}yy)u.  
        stash-lifecycle heredoc 내에서 returncode 체크 → sys.exit(1) 블록이
        `if debug:` 가드 안에 있지 않아야 한다.

        허용 패턴: 직접 if res.returncode != 0: ... sys.exit(1)
        금지 패턴: if debug: ... if res.returncode != 0: ... sys.exit(1)
        re   r@   r$   rA   rk   Nr`   rb   rc_matchr#   u   returncode 체크 로직 없음r&   r'   r      zif\s+debug\s*:r   z&\n\s*(if|for|with|def|try|else|elif)\b)>)z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)srS   other_blocksrU   ut   TODO-3 회귀 의심: audit failure 처리부가 `if debug:` 가드 내부에 있을 수 있음.
preceding context: irW   rX   )r   r   r,   r2   r.   r/   r0   r1   r5   r6   r(   r+   r-   maxr3   listfinditerendrY   rS   )r8   r   rA   rG   ry   r:   r;   r<   r=   	precedingdebug_matches
last_debuggapr|   rZ   r[   r\   r]   s                     r   /test_no_debug_guard_wrapping_audit_failure_exitzRTestNoDebugGuardBeforeAuditFailure.test_no_debug_guard_wrapping_audit_failure_exit   s\    *27;HHHHHHHHHwHHHwHHHHH 99T
  $Fxt#FFFxtFFFFFFxFFFxFFFtFFF%FFFFFFF C8>>#3c#9:X^^=MN	 R[[):IFG&r*JJNN,-.C::&OQTUL|$ q $q(   $q   v      I    v   $   I $   I %   I ()   &&/&6%9;      r   N)rJ   rK   rL   rM   rw   r   r   r   r   rs   rs      s    `

 r   rs   c                      e Zd ZdZd Zd Zy)!TestDynamicAuditFailureSimulationu   
    TODO-3 동적 회귀: stash-lifecycle heredoc 를 AST 파싱하여
    audit failure 경로에서 sys.exit(1) 호출 보장을 검증한다.
    c                   ddl }t               }t        |      }|s{t        j                  d      dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            	 |j                  |      }g }|j                        D ]  }t!        ||j"                        s|j$                  }	t!        |	|j&                        s=|	j(                  dk(  sMt!        |	j*                  |j,                        sn|	j*                  j.                  d	k(  s|j0                  st!        |j0                  d   |j2                        s|j5                  |j0                  d   j*                          d
}
|
|v }|st        j6                  d|fd|
|f      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d| d      dz   d|iz  }t        t        j                  |            dx}
}y# t        $ r#}t        j                  d|        Y d}~d}~ww xY w)u   
        stash-lifecycle heredoc Python 코드를 AST 파싱하여
        returncode 체크 → sys.exit(1) 호출 구조를 확인한다.
        r   Nre   r@   r$   rA   uF   heredoc Python 코드 파싱 불가 (의존성 코드일 수 있음): exitsysrQ   rf   rh   
exit_callsrC   uI   AST 검증 실패: sys.exit(1) 호출 없음. 발견된 sys.exit 인수: u8   
TODO-3 fix: audit failure 경로에 sys.exit(1) 필수.r&   r'   )astr   r   r,   r2   r.   r/   r0   r1   r5   r6   parseSyntaxErrorpytestskipwalk
isinstanceCallfunc	AttributeattrvalueNameidargsConstantappendr-   )r8   r   r   rA   rG   treeer   noder   rH   r:   r<   r=   s                 r   +test_ast_sys_exit_1_in_audit_failure_branchzMTestDynamicAuditFailureSimulation.test_ast_sys_exit_1_in_audit_failure_branch   s   
 	*27;HHHHHHHHHwHHHwHHHHH	f99W%D
 
HHTN 	>D$)yy tS]]3		V+"4::sxx8

.yyZ		!cll%K"))$))A,*<*<=	>  	
qJ 	
 	
qJ 	
 	
 
	  	
 	
 
6	
 	
   	
 	
 
	  	
 	
  XXbWc dF F	
 	
 	
 	
 	
%  	fKK`ab`cdee	fs   I 	J(JJc                   t               }t        |      }|s{t        j                  d      dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            t        j                  d      }|dz  }t        |      }t        j                  d|d      }|j                  ||z   |z   d	
       t        j                  t         j"                  t        |      gddd      }	|	j$                  }
d}|
|k7  }|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|	j(                        dz   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  }t        j                  d|	j(                        dz   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  }t        j                  d|	j(                        dz   d|iz  }t        t        j                  |            dx}x}}y)ue  
        stash-lifecycle heredoc 에서 stash_audit.py 를 모킹하여
        audit 실패(returncode=1) 시 python3 프로세스가 exit code 1 로 종료되는지 확인.

        전략: heredoc 본문을 임시 .py 파일에 쓰되, subprocess.run 을 monkeypatch 하는
        헤더를 prepend 하여 stash_audit.py 호출 시 rc=1 반환.
        re   r@   r$   rA   a              import subprocess as _real_subprocess
            import sys as _sys_orig
            _original_subprocess_run = _real_subprocess.run

            class _MockResult:
                def __init__(self):
                    self.returncode = 1
                    self.stdout = ""
                    self.stderr = "mocked stash_audit.py failure"

            def _patched_run(cmd, **kwargs):
                if isinstance(cmd, (list, tuple)) and any("stash_audit.py" in str(c) for c in cmd):
                    return _MockResult()
                return _original_subprocess_run(cmd, **kwargs)

            import subprocess
            subprocess.run = _patched_run
        ztest_heredoc_failstop.pyz            import sys
            # argv: script_name, task_id, approve, indices, debug, workspace, done_file, audit_timeout
            sys.argv = ["test", "test-task-2576", "0", "", "0", z", "/tmp/fake.done", "30"]
        r   r	   T   )capture_outputtexttimeoutr   )!=)z2%(py2)s
{%(py2)s = %(py0)s.returncode
} != %(py5)sresult)r$   rl   r'   uW   TODO-3 동적 회귀: audit 실패 시 sys.exit(1) 이 호출되지 않음.
returncode=z	, stderr=z
>assert %(py7)spy7Nz	FAIL-STOPrf   )z.%(py1)s in %(py5)s
{%(py5)s = %(py3)s.stderr
})rD   r%   r'   uG   TODO-3 동적 회귀: FAIL-STOP 메시지가 stderr 에 없음.
stderr=z stash_audit.py invocation failedu`   TODO-3 동적 회귀: 'stash_audit.py invocation failed' 메시지가 stderr 에 없음.
stderr=)r   r   r,   r2   r.   r/   r0   r1   r5   r6   textwrapdedentstr
write_text
subprocessrunr   
executable
returncoder-   stderr)r8   tmp_pathr   rA   rG   mock_headertest_scriptfake_ws
argv_setupr   r;   r[   @py_assert3r=   @py_format8rH   r:   s                    r   *test_mock_subprocess_heredoc_exits_nonzerozLTestDynamicAuditFailureSimulation.test_mock_subprocess_heredoc_exits_nonzero  s~    *27;HHHHHHHHHwHHHwHHHHH oo ' ( !;; h-__ *A BI L	& 
 	{Z7'AGT^^S-.	
    	
A 	
 A% 	
 	
 A 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	 ! 	
 	
 
	 %& 	
 	
  ++,Ifmm5FH	
 	
 	
 	
 	

  	
fmm 	
{m+ 	
 	
{m 	
 	
 
	  	
 	
 
6	
 	
  % 	
 	
 
	 % 	
 	
 
	 , 	
 	
 mm&(	
 	
 	
 	
 	

 2 	
V]] 	
1]B 	
 	
1] 	
 	
 
	 2 	
 	
 
6	
 	
  6< 	
 	
 
	 6< 	
 	
 
	 6C 	
 	
 mm&(	
 	
 	
 	
 	
 	
r   N)rJ   rK   rL   rM   r   r   r   r   r   r   r      s    
#
JB
r   r   )returnr   )r   r   r   r   )rM   
__future__r   builtinsr.   _pytest.assertion.rewrite	assertionrewriter,   r(   r   r   r   pathlibr   r   __file__resolveparents	WORKSPACEr   r   r   r   rO   rs   r   r   r   r   <module>r      s    #   	  
   N""$,,Q/	Y&)996(B$
 $
V9
 9
@/ /lm
 m
r   