
    Vin\              
          d 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	j"                  j%                  d e ee      j*                  j*                               ddlmZ dZdded	efd
Zdedededed	df
dZdedededed	df
dZ G d d      Z G d d      Z G d d      Z  G d d      Z! G d d      Z"y)u  
test_atomic_timer_write.py

task-timers.json 동시 쓰기 손상 수정(atomic write fix) 회귀 테스트.

배경:
  dispatch.py의 _patch_timer_metadata()와 memory/task-timer.py의 _save_timers()가
  task-timers.json에 동시 쓰기하여 JSON이 깨지는 버그가 발생함.
  수정 후 두 쪽 모두 아래 패턴을 사용한다:
    1. flock(LOCK_EX)로 진입 직렬화
    2. temp 파일 → fsync → os.replace() 원자적 교체
    3. (또는 utils/atomic_write.py의 atomic_json_write() 활용)

테스트는 dispatch.py / task-timer.py를 직접 임포트하지 않고
동일한 패턴을 복제하여 패턴 자체의 올바름을 검증한다.

실패 기준:
  - 원자적 쓰기 없이 open("w") + json.dump 만 쓸 경우 TC1/TC2/TC5는 확률적으로 FAIL.
  - atomic_json_write + flock 사용 시 모두 PASS.

작성자: 아르고스 (Argos) — dev1-team tester
    N)ThreadPoolExecutoras_completed)Path)Any)atomic_json_writez
task-100.1counterreturnc                 ,    t         t         ddddi| ddS )u/   테스트용 task-timers.json 초기 데이터.	dev1-teamrunning2026-04-12T10:00:00task_idteam_idstatus
start_timetasksr   last_updated)SAMPLE_TASK_IDr   s    4/home/jay/workspace/tests/test_atomic_timer_write.py_make_sample_datar   -   s/     )&#3	
 -     
timer_file	lock_filer   metadatac                    |j                   j                  dd       t        |d      5 }t        j                  |t        j
                         	 | j                         s.	 t        j                  |t        j                         ddd       yt        | dd      5 }t        j                  |      }ddd       j                  di       j                  |      }|"|j                  |       d	|d
<   t        | |       t        j                  |t        j                         	 ddd       y# 1 sw Y   |xY w# t        j                  |t        j                         w xY w# 1 sw Y   yxY w)u   dispatch.py _patch_timer_metadata() 의 수정 후 패턴을 복제.

    flock(LOCK_EX) → 읽기 → 패치 → atomic_json_write → flock(LOCK_UN)
    Tparentsexist_okwNrutf-8encodingr   2026-04-12T10:00:01r   )parentmkdiropenfcntlflockLOCK_EXexistsLOCK_UNjsonloadgetupdater   )r   r   r   r   lock_fdfdata
task_entrys           r   _atomic_patch_timerr8   A   s#   
 4$7	i	 0GU]]+	0$$& KK/0 0
 j#8 $Ayy|$'2.227;J%!!(+'<^$!*d3KK/0 0
$ $ KK/0 0sH   %E"D9!$E"D9D-2AD9?$E"-D6	2D99&EE""E+
_lock_filec                    | j                         syt        | dd      5 }t        j                  |      }ddd       j	                  di       j	                  |      }||j                  |       d|d<   t        | dd      5 }t        j                  ||d	d
       ddd       y# 1 sw Y   sxY w# 1 sw Y   yxY w)u   수정 전 패턴: flock 없이 open("w") + json.dump 직접 쓰기.

    이 함수는 TC1/TC2가 원자적 패턴 없이 FAIL함을 보이기 위한 대조군이다.
    실제 프로덕션 코드에서는 절대 사용 금지.
    Nr#   r$   r%   r   r'   r   r"   F   )ensure_asciiindent)r.   r*   r0   r1   r2   r3   dump)r   r9   r   r   r5   r6   r7   s          r   _unsafe_patch_timerr?   W   s     	j#	0 Ayy|'2&**73J(#4^	j#	0 9A		$a89 9 9 9s   B(B4(B14B=c                   $    e Zd ZdZdZdZd Zd Zy)TestConcurrentDispatchPatchu   TC1: 2개 이상의 _patch_timer_metadata 호출이 동시에 실행되어도
    task-timers.json이 유효한 JSON 상태를 유지해야 한다.
      c                 |    |dz  dz  |dz  dz  j                   j                  dd       t        t                      g }dt        ddf fd	}t         j                  
      5 }t         j                        D cg c]  }|j                  ||       }}t        |      D ]&  }|j                         }||j                  |       ( 	 ddd       | }	|	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}	j)                  d      }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  }dd|iz  }t%        t        j&                  |            dx}}|d   }t0        |v }	|	st        j.                  d|	fdt0        |f      dt        j                         v st        j                   t0              rt        j"                  t0              ndt        j"                  |      dz  }dd|iz  }t%        t        j&                  |            dx}	}|d   t0           }|d   }|t0        k(  }|st        j.                  d|fd|t0        f      t        j"                  |      dt        j                         v st        j                   t0              rt        j"                  t0              nddz  }dd|iz  }t%        t        j&                  |            d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c c}w # 1 sw Y   ^xY w)$uM   TC1: 10개 스레드 × 5회 = 50번 동시 패치 후 JSON 유효성 확인.memorytask-timers.json.task-timers.lockTr   
thread_idxr	   Nc                 h    t        j                        D ]  }t        t        d|  |        y )Nzbot-)bot	iteration)range
ITERATIONSr8   r   )rH   ir   selfr   s     r   workerzJTestConcurrentDispatchPatch.test_concurrent_dispatch_patch.<locals>.worker   s9    4??+ #"zl+r   max_workers   워커 예외 발생: 
>assert not %(py0)spy0errorsr$   r%   r   inz%(py1)s in %(py3)sr6   py1py3assert %(py5)spy5z%(py0)s in %(py3)sr   rU   r\   r   ==z%(py1)s == %(py3)sr   r   z%(py1)s == %(py4)sr[   py4assert %(py6)spy6)r(   r)   r   r   intr   CONCURRENCYrL   submitr   	exceptionappend
@pytest_ar_format_assertmsg@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation	read_textr0   loads_call_reprcomparer   )rO   tmp_pathrV   rP   poolidxfuturesfutexc@py_assert1@py_format2rawr6   @py_assert0@py_assert2@py_format4@py_format6entry@py_assert3@py_format5@py_format7r   r   s   `                    @@r   test_concurrent_dispatch_patchz:TestConcurrentDispatchPatch.test_concurrent_dispatch_patchy   s   (+==
x'*==	t< 	*&7&9:	s 	t 	  D,<,<= 	';@AQAQ;RSCt{{63/SGS#G, 'mmo?MM#&'	' z<z<<3F8<<<<<<<6<<<6<<<<<< ""G"4zz# w$w$w$$!%g.~....~......~...~.......... Wn-Y1>1111>111111111>111>1111111Y.;.;....;......;.......' T	' 	's$   -P1P,"P1P1,P11P;c                 0    |dz  dz  |dz  dz  j                   j                  dd       t        t                      dt        ddf fd	}t         j                  
      5 }t         j                        D cg c]  }|j                  ||       }}t        |      D ]  }|j                           	 ddd       	 t        j                  j                  d             yc c}w # 1 sw Y   5xY w# t        j                  $ r Y yw xY w)u  대조 실험: atomic write 없이 동시 패치하면 JSON이 깨질 수 있음을 시연.

        이 테스트는 _unsafe_patch_timer를 사용하며,
        손상이 발생하면 json.loads가 JSONDecodeError를 던진다.
        손상이 발생하지 않더라도 pytest.warns 등을 쓰지 않고,
        손상이 '발생할 수 있음'을 문서화하는 smoke test다.

        주의: 파일시스템/OS 버퍼 타이밍에 따라 항상 손상이 발생하진 않으므로
        이 테스트 자체는 손상 여부를 assert하지 않는다.
        대신 atomic 패턴이 정상 동작함을 TC1_main이 보증한다.
        rE   rF   rG   Tr   _r	   Nc                     t        j                        D ]  }	 t        t        |        y # t        $ r Y %w xY w)N)rK   )rL   rM   r?   r   	Exception)r   rN   r   rO   r   s     r   unsafe_workerzfTestConcurrentDispatchPatch.test_concurrent_dispatch_patch_without_atomic_fails.<locals>.unsafe_worker   sH    4??+ 	'"!&"#		 ! s   1	==rQ   r$   r%   )r(   r)   r   r   ri   r   rj   rL   rk   r   rl   r0   rw   rv   JSONDecodeError)	rO   ry   r   rz   r{   r|   r}   r   r   s	   `      @@r   3test_concurrent_dispatch_patch_without_atomic_failszOTestConcurrentDispatchPatch.test_concurrent_dispatch_patch_without_atomic_fails   s
    (+==
x'*==	t<*&7&9:
	S 
	T 
	  D,<,<= 	 BGHXHXBYZ3t{{=#6ZGZ#G,   	 	JJz++W+=> [	  	  ## 		s0   +C3C."C3%C? .C33C<?DDN)__name__
__module____qualname____doc__rj   rM   r   r    r   r   rA   rA   r   s    I KJ)/V'r   rA   c                       e Zd ZdZdZd Zy)!TestConcurrentDispatchAndTimerEndut   TC2: dispatch 패치와 task-timer 저장이 동시에 실행되어도
    JSON 무결성이 유지되어야 한다.rB   c                 
   |dz  dz  |dz  dz  j                   j                  dd       t               }t        |       g }dt        ddffd	}d
t        ddffd}g }t        | j                        5 }t        | j                  dz        D ]D  }|j                  |j                  ||             |j                  |j                  ||             F t        |      D ]&  }	|	j                         }
|
|j                  |
       ( 	 ddd       | }|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}j)                  d      }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  }dd|iz  }t%        t        j&                  |            dx}}|d   }t0        |v }|st        j.                  d|fdt0        |f      dt        j                         v st        j                   t0              rt        j"                  t0              ndt        j"                  |      dz  }dd|iz  }t%        t        j&                  |            dx}}|d   t0           }|j2                  }d} ||      }|t0        k(  }|st        j.                  d|fd |t0        f      d!t        j                         v st        j                   |      rt        j"                  |      nd!t        j"                  |      t        j"                  |      t        j"                  |      dt        j                         v st        j                   t0              rt        j"                  t0              ndd"z  }d#d$|iz  }t%        t        j&                  |            dx}x}x}}|d   }t5        |t6              }|sd%d&t        j                         v st        j                   t4              rt        j"                  t4              nd&t        j"                  |      d't        j                         v st        j                   t6              rt        j"                  t6              nd't        j"                  |      d(z  }t%        t        j&                  |            dx}}y# 1 sw Y   QxY w))uA   TC2: 패치 워커 5개 + 저장 워커 5개가 동시에 실행.rE   rF   rG   Tr   rH   r	   Nc           	      `    t        d      D ]  }t        t        d| dz  dz    d       ! y )NrC   dev      claude-sonnet-4-6)rolemodel)rL   r8   r   )rH   r   r   r   s     r   patch_workerz^TestConcurrentDispatchAndTimerEnd.test_concurrent_dispatch_and_timer_end.<locals>.patch_worker   s>    1X #"zA~123-r   _thread_idxc                 ,   t        d      D ]  }t        d      5 }t        j                  |t        j                         	 t        dd      5 }t        j                  |      }d d d        d   j                  t        i       }||d<   ||d   t        <   t        |       t        j                  |t        j                         	 d d d         y # 1 sw Y   oxY w# t        j                  |t        j                         w xY w# 1 sw Y   xY w)NrC   r"   r#   r$   r%   r   retry_count)rL   r*   r+   r,   r-   r0   r1   r2   r   r   r/   )r   rN   lfdr5   r6   r   r   r   s         r   save_workerz]TestConcurrentDispatchAndTimerEnd.test_concurrent_dispatch_and_timer_end.<locals>.save_worker   s    1X 8 )S) 8SKKU]]3
8!*cGD 0#'99Q<D0 $W 1 1." E/0m,8=Wn5 **d;C78 8	80 0 C78 8s;   %D	C C&>C $$D	CC  &DD		D	rQ   r;   rS   rT   rU   rV   r$   r%   r   rW   rY   r6   rZ   r]   r^   r_   r   r`   r   ra   zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py8)sr   rU   py2rf   rh   py8assert %(py10)spy105assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstancedictrU   r   r\   r^   )r(   r)   r   r   ri   r   rj   rL   rm   rk   r   rl   rn   ro   rp   rq   rr   rs   rt   ru   rv   r0   rw   rx   r   r2   r   r   )rO   ry   initial_datarV   r   r   futures_listrz   r{   r}   r~   r   r   r   r6   r   r   r   r   r   r   @py_assert5@py_assert7@py_format9@py_format11@py_assert4r   r   s                             @@r   &test_concurrent_dispatch_and_timer_endzHTestConcurrentDispatchAndTimerEnd.test_concurrent_dispatch_and_timer_end   s   (+==
x'*==	t<(**l3	S 	T 		8S 	8T 	8& D,<,<= 	'T--23 C##DKKc$BC##DKKS$ABC $L1 'mmo?MM#&'		' z<z<<3F8<<<<<<<6<<<6<<<<<< ""G"4zz# w$w$w$$!%g.~....~......~...~..........Wn-yy55y#5#~5555#~555555u555u555y555555#555555~555~5555555 w-.z-........z...z...-.................../	' 	's   >B U"?U""U,N)r   r   r   r   rj   r   r   r   r   r   r      s    1 K@/r   r   c                   "    e Zd ZdZd Zd Zd Zy)TestDispatchRegressionBasicu?   TC3: _patch_timer_metadata 단순 1회 호출 회귀 테스트.c                    |dz  dz  }|dz  dz  }|j                   j                  dd       t        |t                      t	        ||t
        dddd	d
d	       t        j                  |j                  d            }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   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                  |
      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}
x}x}x}}|	j"                  }
d} |
|      }d}||k(  }|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}}|	j"                  }
d} |
|      }d}||k(  }|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}}|	j"                  }
d } |
|      }d	}||k(  }|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}}|	j"                  }
d!} |
|      }d
}||k(  }|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}}|	j"                  }
d"} |
|      }d}||k(  }|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}}|	j"                  }
d#} |
|      }|t
        k(  }|st        j                  d|fd$|t
        f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |
      t        j                  |      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}
x}x}}|	j"                  }
d)} |
|      }d*}||k(  }|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}}|	j"                  }
d+} |
|      }d,}||k(  }|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)-u\   TC3: 패치 1회 후 해당 필드가 올바르게 기록되고 JSON이 유효해야 한다.rE   rF   rG   Tr   zbot-bdev1r   B8C44F05r   r;   )rJ   r   r   schedule_idr   	max_retryr$   r%   r   rW   rY   r6   rZ   r]   r^   NrJ   ra   )zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)sr   )rU   r   rf   rh   py9zassert %(py11)spy11r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )r(   r)   r   r   r8   r   r0   rw   rv   rn   rx   rs   rp   rq   rr   rt   ru   r2   )rO   ry   r   r   r6   r   r   r   r   r   r   r   r   @py_assert8r   @py_format10@py_format12r   r   s                      r   test_dispatch_regression_basicz:TestDispatchRegressionBasic.test_dispatch_regression_basic"  sI   (+==
x'*==	t<*&7&9:%"
	
 zz*...@A w$w$w$$Wn- yy**y*7*7****7******u***u***y*********7*******yy**y *F* F**** F******u***u***y****** ***F*******yy88y!8%88!%88888!%8888888u888u888y888888!888%88888888yy55y'5:5':5555':555555u555u555y555555'555:5555555yy,,y',1,'1,,,,'1,,,,,,u,,,u,,,y,,,,,,',,,1,,,,,,,yy**y%**%****%******u***u***y******%********** yy55y#5#~5555#~555555u555u555y555555#555555~555~5555555yy22y#2{2#{2222#{222222u222u222y222222#222{2222222yy//y"/i/"i////"i//////u///u///y//////"///i////////r   c                 \   |dz  dz  }|dz  dz  }|j                   j                  dd       t        |t                      t	        ||dd       t        j                  |j                  d	
            }|d   }t        |v }|st        j                  d|fdt        |f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }dd|iz  }t        t        j                   |            dx}}d}	|d   }
|	|
v}|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_id에 패치해도 파일이 유효 상태를 유지해야 한다.rE   rF   rG   Tr   ztask-9999.9ghostrJ   r$   r%   r   rW   r_   r   r`   r]   r^   N)not in)z%(py1)s not in %(py4)sre   rg   rh   )r(   r)   r   r   r8   r0   rw   rv   r   rn   rx   rp   rq   rr   rs   rt   ru   )rO   ry   r   r   r6   r   r   r   r   r   r   r   r   s                r   &test_patch_nonexistent_task_id_is_noopzBTestDispatchRegressionBasic.test_patch_nonexistent_task_id_is_noopJ  s    (+==
x'*==	t<*&7&9:J	=gN zz*...@A!%g.~....~......~...~..........1DM1}M1111}M111}111M1111111r   c                 J   |dz  dz  }|dz  dz  }|j                   j                  dd       t               }dddd	d
|d   d<   t        ||       t	        ||t
        d       t        j                  |j                  d            }d}|d   }||v }|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}}|d   d   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 항목이 유실되지 않아야 한다.rE   rF   rG   Tr   
task-200.1	dev2-team	completedz2026-04-12T09:00:00r   r   zbot-ar   r$   r%   rW   z%(py1)s in %(py4)sre   u   다른 task 항목이 유실됨z
>assert %(py6)srh   Nr   ra   rd   rg   )r(   r)   r   r   r8   r   r0   rw   rv   rn   rx   rs   ro   rt   ru   )rO   ry   r   r   r6   resultr   r   r   r   r   s              r    test_patch_preserves_other_tasksz<TestDispatchRegressionBasic.test_patch_preserves_other_tasksZ  sJ   (+==
x'*==	t< "#"!/	'
Wl# 	*d+J	>wOJ00'0BCQvgQ|.QQQ|QQQ|QQQQQQ0QQQQQQQQg|,Y7F;F7;FFFF7;FFF7FFF;FFFFFFFr   N)r   r   r   r   r   r   r   r   r   r   r   r     s    I&0P2 Gr   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestAtomicWriteFsyncuc   atomic_json_write()가 fsync를 포함하여 쓰기 후 데이터 일관성을 보장함을 검증.c                 
   |dz  }t        d      }t        ||       |j                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}t        |dd	
      5 }t        j                  |      }d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  }	dd|	iz  }t        t        j                  |            d}|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   }t        |v }|st        j                  d|fdt        |f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y# 1 sw Y   xY w)uY   쓰기 완료 후 파일을 다시 읽었을 때 동일 데이터를 반환해야 한다.rF   *   r   zAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}target)rU   r   rf   Nr#   r$   r%   ra   )z%(py0)s == %(py2)sloadedpayloadrU   r   zassert %(py4)srf   r   rd   re   rg   rh   r   rW   r_   r   r`   r]   r^   )r   r   r.   rp   rq   rn   rr   rs   rt   ru   r*   r0   r1   rx   r   )rO   ry   r   r   r   r   r   r5   r   @py_format3r   r   r   r   r   s                  r   test_atomic_write_fsyncz,TestAtomicWriteFsync.test_atomic_write_fsyncw  s   ..#B/&'* }}}vv} &#0 	"AYYq\F	"     v      v   v                i &B& B&&&& B&&& &&&B&&&&&&&!'0~0000~000000~000~0000000000	" 	"s   K88Lc                    |dz  }t        |t                      t        |j                  d            }g }||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z   d	|iz  }t        t	        j                  |            d
x}}y
)uG   쓰기 성공 후 임시 파일(.tmp)이 남아있지 않아야 한다.rF   z*.tmpra   )z%(py0)s == %(py3)s	tmp_filesr`   u   임시 파일이 남아있음: 
>assert %(py5)sr^   N)r   r   listglobrn   rx   rp   rq   rr   rs   ro   rt   ru   )rO   ry   r   r   r   r   r   r   s           r   #test_atomic_write_no_temp_file_leftz8TestAtomicWriteFsync.test_atomic_write_no_temp_file_left  s    ..&"3"56w/0	MyBMMMyBMMMMMMyMMMyMMMBMMM"A) MMMMMMMr   c                    |dz  }t        |t        d             t        d      }ddddd	|d
   d<   t        ||       t        j                  |j	                  d            }|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
   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}y)uQ   기존 파일을 덮어쓸 때 새 데이터만 정확히 기록되어야 한다.rF   r   r   i  r   r   r   z2026-04-12T11:00:00r   r   r$   r%   r   ra   rd   re   rg   rh   NrW   r   )
r   r   r0   rw   rv   rn   rx   rs   rt   ru   )
rO   ry   r   new_datar   r   r   r   r   r   s
             r   &test_atomic_write_overwrites_correctlyz;TestAtomicWriteFsync.test_atomic_write_overwrites_correctly  s   .. 	&"3A">? %S1#"/	+
,' 	&(+F,,g,>?i 'C' C'''' C''' '''C'''''''.vg.|....|...|..........r   c                    |dz  }t        |t        d             |j                         j                  }t        |t        d             |j                         j                  }||k7  }|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)uB  atomic_json_write는 os.replace 기반으로 동작해야 한다 (in-place 쓰기 아님).

        검증 방법: 쓰기 전 원본 파일의 inode를 기록하고,
        쓰기 후 inode가 달라졌는지 확인한다.
        (os.replace는 새 inode를 갖는 파일로 교체하므로 inode가 바뀐다.)
        rF   r   r   r;   )!=)z%(py0)s != %(py2)sinode_beforeinode_afterr   uW   inode가 같음 — os.replace 대신 in-place 쓰기가 사용되고 있을 수 있음z
>assert %(py4)srf   N)r   r   statst_inorn   rx   rp   rq   rr   rs   ro   rt   ru   )rO   ry   r   r   r   r   r   r   s           r   +test_atomic_write_uses_replace_not_in_placez@TestAtomicWriteFsync.test_atomic_write_uses_replace_not_in_place  s
    ..&"3A">?{{}++&"3A">?kkm** {* 	
 	
|{ 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
   + 	
 	
 		  + 	
 	
  f	
 	
 	
 	
 	
r   N)r   r   r   r   r   r   r   r   r   r   r   r   r   t  s    m1$N/*
r   r   c                   $    e Zd ZdZdZdZd Zd Zy)TestFlockPreventsRaceuU   flock(LOCK_EX)가 동시 쓰기를 직렬화하여 데이터 손실 없음을 검증.rB      c                 "    |dz  dz  |dz  dz  j                   j                  dd       t        i ddd       t               }g }d	t        d
df fd}t         j                        D ]7  }t         j                        D ]  }|j                  d|dz  |z    d        9 t         j                        5 }t         j                        D cg c]  }|j                  ||       }	}t        |	      D ]&  }
|
j                         }||j                  |       ( 	 ddd       | }|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}j-                  d      }t/        j0                  |      }t        |d   j3                               }||z
  }| }|st        j                  dt5        |       dt7        |      dd  d      dz   ddt!        j"                         v st        j$                  |      rt        j&                  |      ndiz  }t)        t        j*                  |            d} j                   j                  z  }|d   }||k(  }|st        j8                  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          d$z   d%|iz  }t)        t        j*                  |            dx}}yc c}w # 1 sw Y   \xY w)&u;  10개 스레드가 각각 다른 task_id를 추가할 때 모든 항목이 보존되어야 한다.

        flock 없이 동시에 쓰면 나중에 쓴 스레드가 이전 스레드의 쓰기를 덮어쓰므로
        일부 task_id가 유실된다. flock이 올바르게 동작하면 모두 보존된다.
        rE   rF   rG   Tr   r    r   rH   r	   Nc                 L   t        j                        D ]  }d| dz  |z    d}t        d      5 }t        j                  |t        j
                         	 t        dd      5 }t        j                  |      }d d d        |d| d	z  d
z    ddddd   |<   |dxx   d
z  cc<   t        |       t        j                  |t        j                         	 d d d         y # 1 sw Y   ixY w# t        j                  |t        j                         w xY w# 1 sw Y   xY w)Ntask-d   .1r"   r#   r$   r%   r   r   r   z-teamr   r   r   r   r   )
rL   TASKS_PER_WORKERr*   r+   r,   r-   r0   r1   r   r/   )	rH   rN   r   r   r5   r6   r   rO   r   s	         r   add_tasks_workerzHTestFlockPreventsRace.test_flock_prevents_race.<locals>.add_tasks_worker  s   4001 8!*s"2Q"6!7r:)S) 8SKKU]]38!*cGD 0#'99Q<D0 (/),Z!^a-?,@'F&/*?	2Wg. Y1,)*d;C78 88
0 0 C78 8s;   %DC0&C$<8C04$D$C-)C00&DDD#	r   r   r   rQ   rS   rT   rU   rV   r$   r%   r   u)   flock 미적용 시 손실되는 task_id u   개: rC   z...missingr   ra   rc   total_expectedrZ   u   counter 불일치: 예상=u	   , 실제=r   r^   )r(   r)   r   setri   rL   rj   r   addr   rk   r   rl   rm   rn   ro   rp   rq   rr   rs   rt   ru   rv   r0   rw   keyslensortedrx   )rO   ry   expected_task_idsrV   r   t_idxrN   rz   r{   r|   r}   r~   r   r   r   
final_dataactual_task_idsr   r   r   r   r   r   r   r   s   `                      @@r   test_flock_prevents_racez.TestFlockPreventsRace.test_flock_prevents_race  sD    (+==
x'*==	t< 	*qRT&UVE	8 	8 	8( 4++, 	CE4001 C!%%eckAo->b&ABC	C  D,<,<= 	'EJ4K[K[E\]ct{{#3S9]G]#G, 'mmo?MM#&'	' z<z<<3F8<<<<<<<6<<<6<<<<<< ""G"4ZZ_
 j16689#o5{ 	
{ 	
  8G~U6RY?[]\]K^J__bc	
 	
	6	
 	
   	
 	
 		  	
 	
 	
 	
 	

 ))D,A,AA)$ 	
$6 	
 	
$ 	
 	
 		 % 	
 	
	6	
 	
  )7 	
 	
 		 )7 	
 	
  )(8	*YBWAXY	
 	
 	
 	
 	
+ ^	' 	's$   NM?3"NN?NNc           
      @   |dz  dz  |dz  dz  j                   j                  dd       t        t                      g dt        ddffd	}t        | j                  
      5 }t        | j                        D cg c]  }|j                  ||       }}t        |      D ]  }|j                           	 ddd       t        j                  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}|d   }	||	k  }| j                  }|	|k  }|r|st#        j,                  d||fd||	|f      t#        j&                  |      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}x}x}	}t/              }| j                  }||k(  }	|	s7t#        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&                  |      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}	}t1              }t/        |      }| j                  }||k(  }|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$                  t0              rt#        j&                  t0              nd!dt        j                          v st#        j$                        rt#        j&                        ndt#        j&                  |      t#        j&                  |      dt        j                          v st#        j$                  |       rt#        j&                  |       ndt#        j&                  |      d"z  }t#        j2                  d#      d$z   d%|iz  }t)        t#        j*                  |            dx}x}x}}yc c}w # 1 sw Y   TxY w)&uh   flock이 쓰기를 직렬화하므로 마지막 writer의 데이터가 완전히 기록되어야 한다.rE   rF   rG   Tr   valuer	   Nc                    t        d      5 }t        j                  |t        j                         	 t        dd      5 }t	        j
                  |      }d d d        | d<   d| d|d<   t        |       j                  |        t        j                  |t        j                         	 d d d        y # 1 sw Y   bxY w# t        j                  |t        j                         w xY w# 1 sw Y   y xY w)	Nr"   r#   r$   r%   r   z2026-04-12T10:00:02dr   )	r*   r+   r,   r-   r0   r1   r   rm   r/   )r  r   r5   r6   r   r   	write_logs       r   sequential_writerzRTestFlockPreventsRace.test_flock_serializes_write_order.<locals>.sequential_writer  s    i% 
4C/4j#@ ,A#yy|,&+DO->uSk+JD(%j$7$$U+KKU]]3
4 
4, , KKU]]3
4 
4s:   %C.CB93C$C.9C	>C&C++C..C7rQ   r$   r%   r   r   r   ri   r   r   )<=<)z%(py1)s <= %(py5)sz2%(py5)s < %(py8)s
{%(py8)s = %(py6)s.CONCURRENCY
}rO   )r[   r^   rh   r   r   r   ra   )zQ%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py7)s
{%(py7)s = %(py5)s.CONCURRENCY
}r   r  )rU   r[   r\   r^   py7zassert %(py9)sr   )zq%(py6)s
{%(py6)s = %(py0)s(%(py4)s
{%(py4)s = %(py1)s(%(py2)s)
})
} == %(py10)s
{%(py10)s = %(py8)s.CONCURRENCY
}r   )rU   r[   r   rf   rh   r   r   u(   flock 미적용 시 중복 쓰기 발생z
>assert %(py12)spy12)r(   r)   r   r   ri   r   rj   rL   rk   r   r   r0   rw   rv   r   rp   rq   rn   rr   rs   rt   ru   rx   r   r   ro   )rO   ry   r  rz   vr|   r}   finalr   r   r   r   r   r   r   r   r   @py_assert6@py_format8r   r   @py_assert9@py_format13r   r   r  s                          @@@r   !test_flock_serializes_write_orderz7TestFlockPreventsRace.test_flock_serializes_write_order  s   (+==
x'*==	t<*&7&9:		4S 	4T 	4  D,<,<= 	BGHXHXBYZQt{{#4a8ZGZ#G, 

	 

:///AB	*0z*C00000000z000z000*000000C000C00000000007E)$7q$77t'7'77$'777777q$'7777q777$777777t777t777'77777777 9~1!1!11~!11111~!1111111s111s11111191119111~111111111111!11111111y>bs>"bd&6&6b"&66bbb"&6bbbbbbsbbbsbbbbbb3bbb3bbbbbbybbbybbb>bbb"bbbbbbdbbbdbbb&6bbb8bbbbbbbb [	 	s   -XX"XXXN)r   r   r   r   rj   r   r  r  r   r   r   r   r     s    _K@
D#cr   r   )r   )#r   builtinsrp   _pytest.assertion.rewrite	assertionrewritern   r+   r0   sysconcurrent.futuresr   r   pathlibr   typingr   pathinsertstr__file__r(   utils.atomic_writer   r   ri   r   r   r8   r?   rA   r   r   r   r   r   r   r   <module>r!     s   .    
 ?  
 3tH~,,334 5 0 s T (0D 0T 0C 0UX 0]a 0,9D 9d 9S 9VY 9^b 96Y Y@F/ F/ZNG NGjC
 C
Tkc kcr   