
    Rj:                    6   d Z ddlmZ ddl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mZ ddlZej                  j                  dd      Z ee      dz  d	z  d
z  Zej$                  j'                  de      Zej$                  j+                  e      Zej.                  j1                  e       ej2                  Zej4                  Zej6                  Zd+dZd+dZd+dZd+dZd+dZ d+dZ!d+dZ"dZ#dZ$dZ%d,dZ&d-dZ' e'       Z(ejR                  jU                  e( d      d+d       Z+ ee      dz  dz  Z, ee      dz  dz  Z- ee      dz  dz  dz  Z. ee      dz  dz  d z  Z/ ej`                  d!      Z1d.d"Z2d.d#Z3d.d$Z4d/d%Z5d.d&Z6d.d'Z7d0d(Z8d.d)Z9d.d*Z:y)1u\   task-2962: 성공 알림 단일 소유자 + O_EXCL 원자적 선점 규약 회귀 테스트.    )annotationsN)ThreadPoolExecutor)PathWORKSPACE_ROOTz/home/jay/workspacescriptslibznotify_claim.pynotify_claimc                n    | dz  }t        t        |            du sJ t        t        |            du sJ y)uE   동일 마커에 대해 첫 claim은 True, 두 번째 claim은 False.notify.markerTFN)claim_notificationstrtmp_pathmarkers     'tests/test_notify_single_owner_claim.py/test_claim_notification_first_true_second_falser   "   s:    'Fc&k*d222c&k*e333    c                    | dz  dz  dz  }|j                   j                         rJ t        t        |            du sJ |j                   j	                         sJ |j                         sJ y)uK   부모 디렉토리가 없어도 claim_notification이 자동 생성한다.nesteddeepr   TN)parentexistsr   r   is_dirr   s     r   *test_claim_notification_creates_parent_dirr   )   sg     6)O;F}}##%%%c&k*d222==!!!==??r   c                    | dz  }t        t        |            du sJ t        t        |             |j                         rJ t        t        |            du sJ y)uU   claim -> release -> 재claim 이 True (전송 실패 시 재시도 가능해야 함).r   TN)r   r   release_notificationr   r   s     r   "test_release_then_reclaim_succeedsr   5   sU    'Fc&k*d222V%}}c&k*d222r   c                Z    | dz  }|j                         rJ t        t        |             y)uW   존재하지 않는 마커에 release를 호출해도 예외가 발생하지 않는다.znever-claimed.markerN)r   r   r   r   s     r   (test_release_nonexistent_marker_no_raiser   >   s(    ..F}}V%r   c                    | dz  }t        t        |            du sJ dddd}t        t        |      |       |j                  d      }t	        j
                  |      }||k(  sJ y	)
uW   claim -> finalize(payload) 후 마커 파일에 JSON이 기록되고 json.loads 가능.r   Tz	task-2962success   )task_idstatusnutf-8encodingN)r   r   finalize_notification	read_textjsonloads)r   r   payloadrawloadeds        r   !test_finalize_writes_json_payloadr0   H   sk    'Fc&k*d222%CG#f+w/


G

,CZZ_FWr   c                h    | dz  }t        t        |            du sJ t        t        |      d       y)uM   finalize를 payload=None으로 호출해도 예외가 발생하지 않는다.r   TN)r   r   r)   r   s     r   (test_finalize_with_none_payload_no_raiser2   S   s1    'Fc&k*d222#f+t,r   c           
        | dz  }d}t        |      5 }t        |      D cg c]!  }|j                  t        t	        |            # }}|D cg c]  }|j                          }}ddd       t              |k(  sJ |j                  d      dk(  s
J d|        |j                  d      |dz
  k(  sJ yc c}w c c}w # 1 sw Y   [xY w)	uM   N=20 스레드가 동일 마커를 동시에 claim -> True는 정확히 1개.zconcurrent-thread.marker   )max_workersNTr"      True 개수가 1이 아님: F)r   rangesubmitr   r   resultlencount)r   r   	n_workersexecutor_futuresfresultss           r   /test_concurrent_claim_thread_pool_single_winnerrB   ]   s    22FI			2 0hFKIFV
ABHOO.F<
 
 (//!188://	0 w<9$$$==!#M'CG9%MM#==9q=000
 0	0 0s(   C&B7CB<%C7
CC
g      4@g      >@zimport multiprocessing
def _f(x):
    return x
if __name__ == '__main__':
    ctx = multiprocessing.get_context('fork')
    with ctx.Pool(processes=2) as pool:
        r = pool.map(_f, [1, 2])
    assert r == [1, 2]
    print('MP_PROBE_OK')
c           	          d| d|d| d| d	S )Nav  import multiprocessing, importlib.util
def _claim(args):
    mpath, mkpath = args
    spec = importlib.util.spec_from_file_location('notify_claim_mp_worker', mpath)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod.claim_notification(mkpath)
if __name__ == '__main__':
    ctx = multiprocessing.get_context('fork')
    args = [(z, z)] * z
    with ctx.Pool(processes=za) as pool:
        results = pool.map(_claim, args)
    print(','.join(str(r) for r in results))
 )module_pathmarker_pathr<   s      r   _mp_claim_scriptrG      s4    		 $buYK H''0k 29	9r   c                     	 t        j                  ddt        gddt              } | j                  dk(  xr d| j
                  v S # t        $ r Y yw xY w)u  이 실행 환경이 multiprocessing(fork)을 실제로 지원하는지 격리된 서브프로세스로 확인.

    hang 하더라도 subprocess.run의 timeout이 강제로 끊어주므로 이 함수는
    collection을 절대 블로킹하지 않는다.
    python3-cTcapture_outputtexttimeoutr   MP_PROBE_OKF)
subprocessrun_MP_PROBE_SCRIPT_MP_PROBE_TIMEOUT_S
returncodestdout	Exception)r9   s    r   _detect_mp_availablerW      s\    	./'	
   A%H-6==*HH s   AA 	AAu)  이 환경에서 multiprocessing(fork)이 동작하지 않음 (세마포어/샌드박스 clone 제약으로 추정 — 격리된 서브프로세스 프로브가 hang/실패함). 프로세스 간 O_EXCL 보장은 스레드 테스트(test_concurrent_claim_thread_pool_single_winner)로 대체 커버)reasonc           	         | dz  }d}t        t        t              t        |      |      }	 t        j                  dd|gddt
              }j                  dk(  s.J d|j                   d|j                  d	|j                         |j                  j                         j                  d      }|D cg c]  }|dk(  	 }}t        |      |k(  sJ dt        |       d| d|d       |j                  d      dk(  s
J d|        |j                  d      |dz
  k(  sJ y
# t        j                  $ rA}t        j                  dt
         d|j                  d	|j                         Y d
}~'d
}~ww xY wc c}w )u  N=20 프로세스가 동일 마커를 동시에 claim -> True는 정확히 1개 (프로세스 간 O_EXCL 보장).

    실제 claim 호출도 격리된 서브프로세스에서 수행하고 hard timeout을 건다.
    (mp 자체는 가용하다고 프로브에서 확인했으므로, 여기서 timeout이 발생하면
    claim_notification 구현의 fork 안전성 문제로 간주해 실패 처리한다.)
    zconcurrent-mp.markerr4   rI   rJ   TrK   u/   멀티프로세스 claim 서브프로세스가 uD   s 내에 끝나지 않음 (fork 안전성 문제 가능성): stdout= stderr=Nr   u9   멀티프로세스 claim 서브프로세스 실패 (exit=
): stdout=,Trueu   결과 개수 불일치: z != z (raw=)r"   r6   F)rG   r   _NOTIFY_CLAIM_PATHrP   rQ   _MP_CLAIM_TIMEOUT_STimeoutExpiredpytestfailrU   stderrrT   stripsplitr:   r;   )	r   r   r<   scriptr9   excraw_resultsrrA   s	            r   3test_concurrent_claim_multiprocessing_single_winnerrk      s     ..FIc"45s6{INF
f%'	
 ! 
CFDUDUCV W--"(6==*;	=! --%%'--c2K$/0qqF{0G0w<9$ 
#CL>i[{oUVW$ ==!#Q'CK=%QQ#==9q=000% $$ 
=>Q=R SJJM** Xjj^%	
 	

 1s    D$ 6E;$E876E33E8zdone-watcher.shzactivity-watcher.pydeploysystemdzactivity-watcher.servicezdone-watcher.servicez"\$\{?COKACDIR_CHAT_ID(:-[^}]*)?\}?c                 <    t         j                  d      } d| v sJ y)uZ   done-watcher.sh 텍스트에 DONE_WATCHER_SH_SUCCESS_NOTIFY 가드가 존재해야 한다.r&   r'   DONE_WATCHER_SH_SUCCESS_NOTIFYN)_DONE_WATCHER_SHr*   contents    r   -test_done_watcher_sh_has_success_notify_guardrs      s#    (('(:G+w666r   c                 <    t         j                  d      } d| v sJ y)u_   activity-watcher.py 텍스트에 ACTIVITY_WATCHER_SUCCESS_NOTIFY 가드가 존재해야 한다.r&   r'   ACTIVITY_WATCHER_SUCCESS_NOTIFYN)_ACTIVITY_WATCHER_PYr*   rq   s    r   1test_activity_watcher_py_has_success_notify_guardrw      s#    ",,g,>G,777r   c                     t         j                  d      } g }t        j                  |       D ]:  }|j	                  d      }|j                  d      rd|v r*|j                  |       < |r
J d|        y)u   done-watcher.sh 에 COKACDIR_CHAT_ID 가 가드 없는 bare 참조로 남아있지 않아야 한다.

    ${COKACDIR_CHAT_ID:-...} 형태(디폴트 값 가드)만 허용한다.
    r&   r'   r   z${z:-u3   가드 없는 bare COKACDIR_CHAT_ID 참조 발견: N)rp   r*   _CHAT_ID_REF_REfinditergroup
startswithappend)rr   bare_matchesmfulls       r   .test_done_watcher_sh_no_bare_chat_id_referencer      s    
 (('(:GL%%g. "wwqz??4 TT\D!	"
 aRS_R`aa|r   c                    | j                         D ]<  }|j                         }|j                  d      r%t        j                  d|      s< y y)u	  실제 systemd `EnvironmentFile=` 지시자 라인이 있는지 검사한다.

    주석(`#...`)에 설명 목적으로 "EnvironmentFile=" 문자열이 등장하는 것은
    허용한다 — 실제 지시자로 파싱될 라인만 위반으로 간주한다.
    #z^EnvironmentFile\s*=TF)
splitlinesre   r|   rematch)rr   linestrippeds      r   _has_environmentfile_directiver   	  sO     ""$ ::<s#88+X6 r   c                 T    t         j                  d      } t        |       rJ d       y)u  activity-watcher.service 에 EnvironmentFile= 지시자가 없어야 한다.

    (.env.keys 의 `export` 접두 때문에 systemd EnvironmentFile= 파싱이 실패하는
    함정을 해소 — load-env-keys.sh 를 통한 source 방식으로 대체되어야 함)
    r&   r'   uK   activity-watcher.service 에 실제 EnvironmentFile= 지시자가 존재함N)_ACTIVITY_WATCHER_SERVICEr*   r   rq   s    r   4test_activity_watcher_service_has_no_environmentfiler     s2     (1171CG-g6 U66r   c                 T    t         j                  d      } t        |       rJ d       y)uH   done-watcher.service 에 EnvironmentFile= 지시자가 없어야 한다.r&   r'   uG   done-watcher.service 에 실제 EnvironmentFile= 지시자가 존재함N)_DONE_WATCHER_SERVICEr*   r   rq   s    r   0test_done_watcher_service_has_no_environmentfiler   $  s0    #--w-?G-g6 Q66r   c                     t        t              dz  dz  } t        j                  | j	                  d            }t        |d         S )Nconfigzconstants.jsonr&   r'   chat_id)r   r   r+   r,   r*   r   )constants_pathdatas     r   _expected_chat_idr   /  sB    .)H47GGN::n...@ADtIr   c                 ~   t               } d}t        j                  dd|gt        i t        j
                  dt        iddd      }|j                  dk(  s.J d	|j                   d
|j                  d|j                         |j                  j                         }|| k(  sJ d|d| d|j                  d       y)uW   load-env-keys.sh source 후 COKACDIR_CHAT_ID 가 constants.json 의 chat_id 와 일치.zWset -u; source "$WORKSPACE_ROOT/scripts/lib/load-env-keys.sh"; echo "$COKACDIR_CHAT_ID"bashrJ   r   T   cwdenvrL   rM   rN   r   u%   load-env-keys.sh source 실패 (exit=r[   rZ   u    COKACDIR_CHAT_ID 불일치: got=z
 expected=z	 (stderr=r^   N)
r   rP   rQ   r   osenvironrT   rU   rd   re   )expectedrg   r9   actuals       r   .test_load_env_keys_sets_chat_id_from_constantsr   5  s     "H	#  ^^	v<rzz<+^<F ! 
/0A0A/B C--"(6==*;	=! ]]  "FX 
*6*Jxl K==#1	&r   c                     d} t        j                  dd| gt        i t        j                  dt        iddd      }|j
                  dk(  s!J d	|j                  d
|j                         d|j                  v sJ y)uX   set -u 상태에서 load-env-keys.sh 를 source 해도 exit code 0 (unbound var 없음).zKset -u; source "$WORKSPACE_ROOT/scripts/lib/load-env-keys.sh"; echo LOAD_OKr   rJ   r   Tr   r   r   u8   set -u 하에서 load-env-keys.sh source 실패: stdout=rZ   LOAD_OKN)rP   rQ   r   r   r   rT   rU   rd   )rg   r9   s     r   (test_load_env_keys_safe_under_set_dash_ur   O  s    ZF^^	v<rzz<+^<F ! --"(6==*;	=! %%%r   )r   r   returnNone)rE   r   rF   r   r<   intr   r   )r   bool)r   r   )rr   r   r   r   )r   r   );__doc__
__future__r   importlib.util	importlibr+   r   r   rP   concurrent.futuresr   pathlibr   rb   r   getr   r_   utilspec_from_file_location_specmodule_from_spec_notify_claimloaderexec_moduler   r   r)   r   r   r   r   r0   r2   rB   rS   r`   rR   rG   rW   _MP_AVAILABLEmarkskipifrk   rp   rv   r   r   compilery   rs   rw   r   r   r   r   r   r   r   rD   r   r   <module>r      s   b "   	 	  1   02GH .)I5=@QQ ..~?QR//6    '"55 $99 %;; 43&-1,   ! $$ %& 	  $1$1Z ')36GG N+i7:OO 8#i/2LL  	8#i/2HH  "**BC78b	 4&r   