
    vDi_                     @   d Z ddlZddlZddlmZ ddlZ ed      Z ee      ej                  vr"ej                  j                  d ee             da
d Z ej                  d      d        Z G d	 d
      Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zy)u  
task-2431 회귀 테스트: post_merge_probe.py Scope-aware (단일 책임)

목적:
    익스텐션 PR이 server/tests 환경 결함으로 자동 revert되는 사고(task-2423/2429)를
    영구 차단한다. 본 task는 changed-path 기반 scope 판정과 scoped test 실행만
    책임진다 (baseline pre-flight는 별도 P1 task scope 외).

검증 시나리오:
    1. test_extension_pr_with_server_fail_does_not_revert — 핵심 회귀
    2. test_extension_pr_with_extension_fail_triggers_revert
    3. test_server_pr_with_server_fail_triggers_revert
    4. test_unmapped_changed_paths_runs_smoke_only
    5. test_no_changed_paths_fallback_to_smoke_not_full — full sweep 금지 회귀 가드
    6. test_run_tests_scoped_passes_rootdir_to_pytest — pytest 실제 인자 검증
    7. test_run_probe_does_not_have_baseline_check — baseline 코드 부재 회귀 가드
    8. test_resolve_test_scope_unions_multiple_areas — 단위
    9. test_changed_paths_returns_empty_on_git_failure — 단위
    N)Pathz/home/jay/workspacec                     t         t         S ddl} | j                  j                  dt        dz  dz        }||j
                  t        d      | j                  j                  |      }|j
                  j                  |       |a |S )u+  post_merge_probe 모듈을 한 번만 로드하여 캐시.

    fixture가 monkeypatch한 모듈 인스턴스를 테스트가 그대로 받도록
    프로세스 단위 캐시를 사용한다. monkeypatch는 fixture finalize 시
    자동 복원되므로 테스트 간 격리는 유지된다.
    Nr   post_merge_probescriptszpost_merge_probe.pyz+Could not load post_merge_probe module spec)	_PROBE_MODULE_CACHEimportlib.utilutilspec_from_file_location
_WORKSPACEloaderImportErrormodule_from_specexec_module)	importlibspecmods      Z/home/jay/workspace/.worktrees/task-2466-dev1/tests/scripts/test_post_merge_probe_scope.py_fresh_probe_moduler   +   s     &"">>11Y!66D |t{{*GHH
..
)
)$
/CKKC J    T)autousec              #      K   |dz  dz  }|dz  dz  }|j                  dd       |j                  dd       t               }| j                  |d|       | j                  |d|dz         | y	w)
uW   PROBE_MARK_DIR, AUDIT_LOG를 tmp_path 아래로 리다이렉트하여 테스트 격리.memoryeventsauditTparentsexist_okPROBE_MARK_DIR	AUDIT_LOGzauto-merge.logN)mkdirr   setattr)monkeypatchtmp_path
events_dir	audit_dirprobes        r   isolate_pathsr'   H   s      H$x/J8#g-ITD1OOD4O0!E/<{I8H,HI
s   A/A1c                   (    e Zd ZU dZeed<   d Zd Zy)_DummyPopenu8   subprocess.Popen 대체 — 호출 횟수/인자 기록.callsc                 B    t         j                  j                  |       y N)r)   r*   append)selfargs_kwargss      r   __init__z_DummyPopen.__init___   s      &r   c                      y)Nr    )r.   s    r   waitz_DummyPopen.waitb   s    r   N)__name__
__module____qualname____doc__list__annotations__r1   r4   r3   r   r   r)   r)   [   s    BK'r   r)   c                  $    g t         _        t         S )u/   DummyPopen 클래스를 초기화하고 반환.)r)   r*   r3   r   r   _make_popen_recorderr<   f   s    Kr   c                    t               }|dz  dz  }|j                  d       t               }| j                  t        d|       | j                  |dd        | j                  |dd	        | j                  |d
d        | j                  dd        |j                  dd|d      }t        |j                        dk(  sJ d       |j                  d      dv sJ d|j                  d              |j                  di       j                  d      dk(  s)J d|j                  di       j                  d              d|vsJ d|j                  d              y)u2  changed_paths가 extension/ 파일만 포함하면 server/tests fail은 무시되어야 한다.

    _resolve_test_scope → mode="scoped", test_paths=["extension/__tests__/"]
    _run_tests_scoped는 extension/__tests__/만 실행하고 PASS 반환.
    auto_revert(Popen) 호출이 없어야 한다.
    	extension	__tests__Tr   Popen_changed_pathsc                 
    ddgS )Nextension/content.jsextension/popup.jsr3   project_path	merge_shas     r   <lambda>zDtest_extension_pr_with_server_fail_does_not_revert.<locals>.<lambda>   s    9OQe8f r   _resolve_test_scopec                     dgdfS Nextension/__tests__/scopedr3   changeds    r   rI   zDtest_extension_pr_with_server_fail_does_not_revert.<locals>.<lambda>       *@)A8(L r   _run_tests_scopedc                      y)N)Tzextension tests okr3   rG   
test_pathss     r   rI   zDtest_extension_pr_with_server_fail_does_not_revert.<locals>.<lambda>       r   
time.sleepc                      y r,   r3   ss    r   rI   zDtest_extension_pr_with_server_fail_does_not_revert.<locals>.<lambda>   rV   r   ztask-ext-testabc123r   delayu]   auto_revert가 호출되지 않아야 함: server/tests 환경 fail은 extension PR과 무관outcome)
probe_passscoped_passu(   probe 결과가 PASS여야 함, 실제: scopemoderN   u$   scope.mode='scoped' 기대, 실제: baseline_check:   baseline_check 키는 본 task에서 제거되어야 함: N	r   r    r<   r!   
subprocess	run_probelenr*   getr"   r#   r&   	ext_tests
popen_mockresults         r   2test_extension_pr_with_server_fail_does_not_revertrn   q   s     !E ;&4IOODO!%'J
GZ8 /fh 4LN 2UW n5___h_JFz A% g% ::i $AA 
26::i3H2IJA ::gr"&&v.(: 
.vzz'2/F/J/J6/R.ST: 6) 
DVZZP`EaDbc)r   c                    t               }|dz  dz  }|j                  d       t               }| j                  t        d|       | j                  |dd        | j                  |dd	        | j                  |d
d        | j                  dd        |j                  dd|d      }t        |j                        dk(  sJ dt        |j                                |j                  d      dv sJ d|j                  d              y)u   changed_paths=extension/, _run_tests_scoped는 extension/__tests__/에서 fail.

    baseline에서도 fail이 아닐 때 auto_revert가 1회 호출되어야 한다.
    r>   r?   Tr@   rA   rB   c                     dgS NrD   r3   rF   s     r   rI   zGtest_extension_pr_with_extension_fail_triggers_revert.<locals>.<lambda>   
    9O8P r   rJ   c                     dgdfS rL   r3   rO   s    r   rI   zGtest_extension_pr_with_extension_fail_triggers_revert.<locals>.<lambda>   rQ   r   rR   c                      y)N)Fz&FAILED 2 tests in extension/__tests__/r3   rT   s     r   rI   zGtest_extension_pr_with_extension_fail_triggers_revert.<locals>.<lambda>   rV   r   rW   c                      y r,   r3   rY   s    r   rI   zGtest_extension_pr_with_extension_fail_triggers_revert.<locals>.<lambda>   rV   r   ztask-ext-faildef456r   r\      u?   auto_revert가 1회 호출되어야 함, 실제 호출 횟수: r^   )
probe_failscoped_failu+   probe 결과가 FAIL이어야 함, 실제: Nre   rj   s         r   5test_extension_pr_with_extension_fail_triggers_revertrz      s   
  !E;&4IOODO!%'J
GZ8/PR4LN 2jl n5___h_JFz A% 
I#jN^N^J_I`a% ::i $AA 
5fjj6K5LMAr   c                    t               }|dz  dz  }|j                  d       t               }| j                  t        d|       | j                  |dd        | j                  |dd	        | j                  |d
d        | j                  dd        |j                  dd|d      }t        |j                        dk(  sJ dt        |j                                y)u   changed_paths=server/, server/tests fail은 server PR에 대한 정당한 revert 사유다.

    auto_revert가 1회 호출되어야 한다.
    servertestsTr@   rA   rB   c                 
    ddgS )Nzserver/app.pyserver/routes.pyr3   rF   s     r   rI   zAtest_server_pr_with_server_fail_triggers_revert.<locals>.<lambda>   s    J\8] r   rJ   c                     dgdfS )Nzserver/tests/rN   r3   rO   s    r   rI   zAtest_server_pr_with_server_fail_triggers_revert.<locals>.<lambda>   s    /):H(E r   rR   c                      y)N)Fz5ModuleNotFoundError: No module named 'tests.conftest'r3   rT   s     r   rI   zAtest_server_pr_with_server_fail_triggers_revert.<locals>.<lambda>   rV   r   rW   c                      y r,   r3   rY   s    r   rI   zAtest_server_pr_with_server_fail_triggers_revert.<locals>.<lambda>   rV   r   ztask-server-failghi789r   r\   rw   uP   server PR의 server/tests fail은 revert를 트리거해야 함, 호출 횟수: N)r   r    r<   r!   rf   rg   rh   r*   )r"   r#   r&   server_testsrl   rm   s         r   /test_server_pr_with_server_fail_triggers_revertr      s    
  !Eh&0Lt$%'J
GZ8/]_4EG 2y{ n5__/81_MFz A% 
Z[^_i_o_o[pZqr%r   c                 4   t               |dz  dz  }|j                  d       | j                  dd        i fd}| j                  d|       | j                  d	d
        | j                  t        dt	                      | j                  dd        j                  dd|d       j                  d      dk(  sJ dj                  d              t        ddg      }j                  d      |k(  sJ dj                  d              y)u   changed_paths가 docs/ README.md 등 SCOPE_MAP 미매치 경로일 때
    mode='smoke', test_paths=SMOKE_TEST_PATHS 가 반환되어야 한다.
    r}   smokeTr@   rB   c                 
    ddgS )Nzdocs/README.mdzdocs/api.mdr3   rF   s     r   rI   z=test_unmapped_changed_paths_runs_smoke_only.<locals>.<lambda>   s    9I=8Y r   c                 @   t        di       }g }|j                         D ]+  \  }t        fd| D              s|j                  |       - |st        ddg      }dd<   |d<   |dfS dd<   t	        t        |            d<   t	        t        |            dfS )	N	SCOPE_MAPc              3   @   K   | ]  }|j                          y wr,   )
startswith).0pprefixs     r   	<genexpr>zUtest_unmapped_changed_paths_runs_smoke_only.<locals>._mock_resolve.<locals>.<genexpr>  s     ?A1<<'?s   SMOKE_TEST_PATHStests/smoke/r   rb   rU   rN   )getattritemsanyextendsortedset)changed_paths	scope_mapmatched	test_dirssmoke_pathsr   capturedr&   s        @r   _mock_resolvezBtest_unmapped_changed_paths_runs_smoke_only.<locals>._mock_resolve  s    E;3	!*!2 	*FI???y)	* !%);n=MNK&HV%0H\"))#!'G!5s7|$h//r   rJ   rR   c                      y)NTzsmoke okr3   rT   s     r   rI   z=test_unmapped_changed_paths_runs_smoke_only.<locals>.<lambda>  rV   r   rA   rW   c                      y r,   r3   rY   s    r   rI   z=test_unmapped_changed_paths_runs_smoke_only.<locals>.<lambda>  rV   r   z	task-docsjkl012r   r\   rb   u:   SCOPE_MAP 미매치 PR은 smoke 모드여야 함, 실제: r   r   rU   u:   smoke 모드의 test_paths는 SMOKE_TEST_PATHS여야 함: N)r   r    r!   rf   r<   rg   ri   r   )r"   r#   	smoke_dirr   smoke_expectedr   r&   s        @@r   +test_unmapped_changed_paths_runs_smoke_onlyr      s/     !E 7"W,IOODO!/Y[ H0  4mD2KM
G-A-CDn5	OOK81O=<<7* 
DX\\RXEYDZ[* U$68HIN<<%7 
DX\\R^E_D`a7r   c                    t               }| j                  |dd        i fd}| j                  |d|       | j                  t        dt                      | j                  dd        |j	                  dd	|d
      }|j                  di       j                  d      }|dk(  s
J d|       |dk7  sJ d       d   |j                  k(  sJ dd           y)u   _changed_paths가 빈 list 반환(merge_sha 없음 또는 git diff 실패)할 때
    run_probe는 mode='smoke'로 fallback해야 한다. 'full' 모드는 절대 사용 금지.
    rB   c                     g S r,   r3   rF   s     r   rI   zBtest_no_changed_paths_fallback_to_smoke_not_full.<locals>.<lambda>/  s     r   c                 "    t        |      d<   y)NrU   r   r9   )rG   rU   r   s     r   _capture_scopedzItest_no_changed_paths_fallback_to_smoke_not_full.<locals>._capture_scoped4  s    !%j!1!r   rR   rA   rW   c                      y r,   r3   rY   s    r   rI   zBtest_no_changed_paths_fallback_to_smoke_not_full.<locals>.<lambda>:  rV   r   ztask-no-diffxyz999r   r\   ra   rb   r   uG   changed_paths 빈 경우 mode='smoke'여야 함 (full 금지), 실제: fullu"   full sweep은 절대 사용 금지rU   uD   smoke fallback 시 SMOKE_TEST_PATHS로 호출되어야 함, 실제: N)r   r!   rf   r<   rg   ri   r   )r"   r#   r&   r   rm   rb   r   s         @r   0test_no_changed_paths_fallback_to_smoke_not_fullr   '  s     !E /:< H" 2OD
G-A-CDn5__^Xxq_IF::gr"&&v.D7? 
QRVQYZ? 6>???>L!U%;%;; 
NxXdOeNfg;r   c                 p  	
 t               }|dz  dz  }|j                  d       i 
 G d d      		
fd}| j                  t        d|       |j	                  |d	g      \  }}|du s
J d
|        
j                  dg       }|sJ d       |d   dk(  s
J d|        d|v s
J d|        |j                  d      }||dz      t        |      k(  sJ d||dz              d|v rd|v s
J d|        
d   j                  d      |k(  sJ d
d   j                  d              d	|v s
J d|        y)u   _run_tests_scoped가 subprocess.run 호출 시 --rootdir과 -p no:cacheprovider를
    전달해야 한다. (collection 부작용 차단 + scope 강제)
    r>   r?   Tr@   c                       e Zd ZdZdZdZy)@test_run_tests_scoped_passes_rootdir_to_pytest.<locals>._FakeRunr   z1 passed Nr5   r6   r7   
returncodestdoutstderrr3   r   r   _FakeRunr   Z  s    
r   r   c                 8    t        |       d<   |d<           S )Nr/   kwargsr   )r/   r   r   r   s     r   	_fake_runzAtest_run_tests_scoped_passes_rootdir_to_pytest.<locals>._fake_run_  s#    :#zr   runrM   u,   테스트가 PASS여야 함, 실제 출력: r/   u%   subprocess.run이 호출되어야 함r   pytestu    첫 인자는 pytest여야 함: z	--rootdiru:   --rootdir 옵션이 pytest 인자에 포함되어야 함: rw   u)   --rootdir 값은 project_path여야 함: z-pzno:cacheprovideru3   -p no:cacheprovider 옵션이 포함되어야 함: r   cwdu   cwd는 project_path여야 함: u:   scoped test path가 pytest 인자에 포함되어야 함: N)r   r    r!   rf   rR   ri   indexstr)r"   r#   r&   
scoped_dirr   okoutr/   rootdir_idxr   r   s            @@r   .test_run_tests_scoped_passes_rootdir_to_pytestr   N  s     !E K'+5JT"H 

 
E95%%h1G0HIGB:KEcUKK:<<#D88847hI"B4& II$ 
DTFK **[)Ka CM1 
3Dq4I3JK1 4<.$6 
=dVD6 H!!%(H4 
)(8*<*@*@*G)HI4 "T) 
DTFK)r   c                    t               }|dz  dz  }|j                  dd       | j                  |d|       |dz  dz  }|j                  d       t               }| j                  t        d	|       | j                  |d
d        | j                  |dd        | j                  |dd        | j                  dd        d}|j                  |d|d      }d|vsJ d|j                  d              || dz  }|j                         r
J d|        t        |d      rJ d       t        |j                        dk(  sJ dt        |j                         d       y) u   probe FAIL 시에도 record["baseline_check"] 키는 절대 존재하면 안 된다.
    baseline pre-flight는 별도 task scope이며, 본 task는 단일 책임만 가진다.
    또한 .probe-baseline-fail 마커도 생성되면 안 된다.
    r   r   Tr   r   r>   r?   r@   rA   rB   c                     dgS rq   r3   rF   s     r   rI   z=test_run_probe_does_not_have_baseline_check.<locals>.<lambda>  rr   r   rJ   c                     dgdfS rL   r3   rO   s    r   rI   z=test_run_probe_does_not_have_baseline_check.<locals>.<lambda>  rQ   r   rR   c                      y)N)Fzfail in extensionr3   rT   s     r   rI   z=test_run_probe_does_not_have_baseline_check.<locals>.<lambda>  rV   r   rW   c                      y r,   r3   rY   s    r   rI   z=test_run_probe_does_not_have_baseline_check.<locals>.<lambda>  rV   r   ztask-no-baselinedeadbeefr   r\   rc   rd   z.probe-baseline-failuK   .probe-baseline-fail 마커는 본 task에서 절대 생성되면 안 됨: _baseline_test_checkuK   _baseline_test_check 함수는 본 task에서 완전히 제거되어야 함rw   uL   baseline 게이트가 없으니 probe FAIL은 즉시 auto_revert 트리거: u   회N)r   r    r!   r<   rf   rg   ri   existshasattrrh   r*   )	r"   r#   r&   r$   rk   rl   task_idrm   marker_files	            r   +test_run_probe_does_not_have_baseline_checkr     s   
  !EH$x/JTD1/<;&4IOODO!%'J
GZ8/PR4LN 2UWn5 G__Wj(!_DF 6) 
DVZZP`EaDbc) '*>??K!!# 
UVaUbc# u45 U5 z A% 
VWZ[e[k[kWlVmmpq%r   c                     	 t               } | j                  }| j                  }ddg} |      \  }}j                  dg       }|j                  dg       }t        t        ||z               }|dk(  s
J d|        t        |      |k(  sJ d| d	t        |               ||      \  }	}
||	k(  sJ d
       y# t        t        f$ r t        j                  d       Y w xY w)u   changed_paths에 extension/과 server/ 파일이 모두 있으면
    두 영역의 test_paths를 union하고 결정적으로 정렬해야 한다.
    u6   토르 구현 대기 — _resolve_test_scope 미존재rD   r   z
extension/zserver/rN   u$   mode는 'scoped'여야 함, 실제: u'   test_paths union이 잘못됨. 기대: u
   , 실제: u@   동일 입력에 대해 test_paths 순서가 일관되어야 함N)
r   rJ   r   r   AttributeErrorr   skipri   r   r   )r&   _resolver   rP   rU   rb   expected_extensionexpected_serverexpected_uniontest_paths2_s              r   -test_resolve_test_scope_unions_multiple_areasr     s   N#%,,OO	 &'9:G(J"|R8mmIr2OC 2_ DEFN8JCD6JJ*/ 
1.1AFS]L^K_`/ g&NK$h&hh$! ( NLMNs   "B9 9$C C c                    	 t               }|j                  } G d d      | j                  t        dfd        t        d      d      }|g k(  s
J d|       y	# t        t        f$ r t	        j
                  d       Y lw xY w)
u^   git diff 명령이 rc != 0으로 종료하면 _changed_paths는 [] 반환 (예외 안 던짐).u1   토르 구현 대기 — _changed_paths 미존재c                       e Zd ZdZdZdZy)Ctest_changed_paths_returns_empty_on_git_failure.<locals>._FailedRunrw   r   zfatal: not a git repositoryNr   r3   r   r   
_FailedRunr     s    
.r   r   r   c                              S r,   r3   )akwr   s     r   rI   zAtest_changed_paths_returns_empty_on_git_failure.<locals>.<lambda>  s	    JL r   z/tmp/nonexistentbadshau?   git diff 실패 시 빈 리스트를 반환해야 함, 실제: N)	r   rB   r   r   r   r   r!   rf   r   )r"   r&   rB   rm   r   s       @r   /test_changed_paths_returns_empty_on_git_failurer     s    I#%--/ /
 
E+HID!34h?FR< 
I&T< ( IGHIs   A $BBc                    t               }| dz  dz  j                  d       | dz  dz  j                  d       | dz  dz  dz  j                  d       | dz  dz  d	z  j                  d
       |j                  ddg      \  }}|dk(  s
J d|       |dgk(  s
J d|       |j	                  | |      \  }}|du sJ d| d|dd         d|vsJ d|dd         d|v s d|j                         v sJ d|dd         yy)u  통합형: 실제 _resolve_test_scope + _run_tests_scoped 동작 검증.

    - 임시 repo에 server/tests/ (의도적 fail 테스트) + extension/__tests__/ (PASS) 배치
    - extension/ 변경에 대해 _resolve_test_scope가 server/tests/를 포함하지 않음
    - _run_tests_scoped가 실제 pytest를 호출했을 때 server 영역 fail이 결과에 포함되지 않음
    r>   r?   Tr@   r|   r}   z
test_ok.pyz%def test_extension_ok(): assert True
ztest_broken.pyzOimport nonexistent_module_xyz_abc  # noqa
def test_server_fail(): assert False
rD   rE   rN   u#   scope mode 기대 'scoped', 실제 rM   u.   server/tests/는 절대 포함되면 안 됨: u=   extension 테스트만 실행되었으니 PASS여야 함: ok=z	, output=iNtest_brokenuI   server/tests/ 의 broken 테스트가 출력에 나타나면 scope leak: itest_okpassedu/   extension 테스트가 실행되었어야 함: )r   r    
write_textrJ   rR   lower)r#   r&   rU   rb   r   outputs         r   9test_integration_extension_pr_scope_excludes_server_testsr     s     !E k)000>7"))$)7 k)L8DD0 7"%55AA	1 00	!56J 8KB4(KK011 
8G1
 ((:>JB: 
Gt9U[\`\aUbTcd: & 
STZ[_[`TaSbc& (flln"< 
9&-I<"<r   c                      t               } | j                  }|D ]X  }t        |z  }|j                         sJ d| d       t	        |j                  d            }t        |      dk\  rPJ d|         y)u   SMOKE_TEST_PATHS가 가리키는 디렉토리가 실제 workspace에 존재해야 함.

    이 디렉토리가 없으면 unmapped PR이 _run_tests_scoped의
    "no scoped test target — skipped" 분기로 무검증 PASS 처리됨.
    u9   SMOKE_TEST_PATHS가 가리키는 디렉토리가 부재: u+    — unmapped PR이 무검증 통과 위험z	test_*.pyrw   u:   smoke 디렉토리에 최소 1개의 test_*.py가 필요: N)r   r   r   is_dirr9   globrh   )r&   r   spr   py_testss        r   (test_smoke_directory_exists_in_workspacer   .  s      !E((K 

B{{} 	
Gv N9 :	
}
 		+./8}! 	
HO	
!

r   )r8   rf   syspathlibr   r   r   r   pathinsertr   r   fixturer'   r)   r<   rn   rz   r   r   r   r   r   r   r   r   r   r3   r   r   <module>r      s   (  
  
 '(
z?#(("HHOOAs:'  :  $ .lNH-jN1r-ji@:,j
r   