|
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | | -import asyncio |
6 | 5 | import os |
7 | 6 | import pickle # Used to test exception picklability for multiprocessing support |
8 | 7 | import threading |
|
14 | 13 |
|
15 | 14 | from libvcs._internal.file_lock import ( |
16 | 15 | AcquireReturnProxy, |
17 | | - AsyncAcquireReturnProxy, |
18 | | - AsyncFileLock, |
19 | 16 | FileLock, |
20 | 17 | FileLockContext, |
21 | 18 | FileLockError, |
22 | 19 | FileLockStale, |
23 | 20 | FileLockTimeout, |
24 | | - async_atomic_init, |
25 | 21 | atomic_init, |
26 | 22 | ) |
27 | 23 |
|
@@ -284,128 +280,6 @@ def test_stale_detection( |
284 | 280 | lock.acquire() |
285 | 281 |
|
286 | 282 |
|
287 | | -# ============================================================================= |
288 | | -# AsyncFileLock Tests |
289 | | -# ============================================================================= |
290 | | - |
291 | | - |
292 | | -class TestAsyncFileLock: |
293 | | - """Tests for AsyncFileLock asynchronous operations.""" |
294 | | - |
295 | | - @pytest.mark.asyncio |
296 | | - async def test_async_context_manager(self, tmp_path: Path) -> None: |
297 | | - """Test AsyncFileLock as async context manager.""" |
298 | | - lock_path = tmp_path / "test.lock" |
299 | | - lock = AsyncFileLock(lock_path) |
300 | | - |
301 | | - assert not lock.is_locked |
302 | | - async with lock: |
303 | | - assert lock.is_locked |
304 | | - assert lock_path.exists() |
305 | | - assert not lock.is_locked |
306 | | - |
307 | | - @pytest.mark.asyncio |
308 | | - async def test_async_explicit_acquire_release(self, tmp_path: Path) -> None: |
309 | | - """Test explicit acquire() and release() for async lock.""" |
310 | | - lock_path = tmp_path / "test.lock" |
311 | | - lock = AsyncFileLock(lock_path) |
312 | | - |
313 | | - proxy = await lock.acquire() |
314 | | - assert isinstance(proxy, AsyncAcquireReturnProxy) |
315 | | - assert lock.is_locked |
316 | | - |
317 | | - await lock.release() |
318 | | - assert not lock.is_locked |
319 | | - |
320 | | - @pytest.mark.asyncio |
321 | | - async def test_async_reentrant(self, tmp_path: Path) -> None: |
322 | | - """Test async reentrant locking.""" |
323 | | - lock_path = tmp_path / "test.lock" |
324 | | - lock = AsyncFileLock(lock_path) |
325 | | - |
326 | | - await lock.acquire() |
327 | | - assert lock.lock_counter == 1 |
328 | | - |
329 | | - await lock.acquire() |
330 | | - assert lock.lock_counter == 2 |
331 | | - |
332 | | - await lock.release() |
333 | | - assert lock.lock_counter == 1 |
334 | | - |
335 | | - await lock.release() |
336 | | - assert lock.lock_counter == 0 |
337 | | - |
338 | | - @pytest.mark.asyncio |
339 | | - async def test_async_timeout(self, tmp_path: Path) -> None: |
340 | | - """Test async lock timeout.""" |
341 | | - lock_path = tmp_path / "test.lock" |
342 | | - |
343 | | - lock1 = AsyncFileLock(lock_path) |
344 | | - await lock1.acquire() |
345 | | - |
346 | | - lock2 = AsyncFileLock(lock_path, timeout=0.1) |
347 | | - with pytest.raises(FileLockTimeout): |
348 | | - await lock2.acquire() |
349 | | - |
350 | | - await lock1.release() |
351 | | - |
352 | | - @pytest.mark.asyncio |
353 | | - async def test_async_non_blocking(self, tmp_path: Path) -> None: |
354 | | - """Test async non-blocking acquire.""" |
355 | | - lock_path = tmp_path / "test.lock" |
356 | | - |
357 | | - lock1 = AsyncFileLock(lock_path) |
358 | | - await lock1.acquire() |
359 | | - |
360 | | - lock2 = AsyncFileLock(lock_path) |
361 | | - with pytest.raises(FileLockTimeout): |
362 | | - await lock2.acquire(blocking=False) |
363 | | - |
364 | | - await lock1.release() |
365 | | - |
366 | | - @pytest.mark.asyncio |
367 | | - async def test_async_acquire_proxy_context(self, tmp_path: Path) -> None: |
368 | | - """Test AsyncAcquireReturnProxy as async context manager.""" |
369 | | - lock_path = tmp_path / "test.lock" |
370 | | - lock = AsyncFileLock(lock_path) |
371 | | - |
372 | | - proxy = await lock.acquire() |
373 | | - async with proxy as acquired_lock: |
374 | | - assert acquired_lock is lock |
375 | | - assert lock.is_locked |
376 | | - |
377 | | - assert not lock.is_locked |
378 | | - |
379 | | - @pytest.mark.asyncio |
380 | | - async def test_async_concurrent_acquisition(self, tmp_path: Path) -> None: |
381 | | - """Test concurrent async lock acquisition.""" |
382 | | - lock_path = tmp_path / "test.lock" |
383 | | - results: list[int] = [] |
384 | | - |
385 | | - async def worker(lock: AsyncFileLock, worker_id: int) -> None: |
386 | | - async with lock: |
387 | | - results.append(worker_id) |
388 | | - await asyncio.sleep(0.01) |
389 | | - |
390 | | - lock = AsyncFileLock(lock_path) |
391 | | - await asyncio.gather(*[worker(lock, i) for i in range(3)]) |
392 | | - |
393 | | - # All workers should have completed |
394 | | - assert len(results) == 3 |
395 | | - # Results should be sequential (one at a time) |
396 | | - assert sorted(results) == list(range(3)) |
397 | | - |
398 | | - @pytest.mark.asyncio |
399 | | - async def test_async_repr(self, tmp_path: Path) -> None: |
400 | | - """Test __repr__ for async lock.""" |
401 | | - lock_path = tmp_path / "test.lock" |
402 | | - lock = AsyncFileLock(lock_path) |
403 | | - |
404 | | - assert "unlocked" in repr(lock) |
405 | | - async with lock: |
406 | | - assert "locked" in repr(lock) |
407 | | - |
408 | | - |
409 | 283 | # ============================================================================= |
410 | 284 | # FileLockContext Tests |
411 | 285 | # ============================================================================= |
@@ -613,81 +487,3 @@ def init_fn() -> None: |
613 | 487 |
|
614 | 488 | # Only one thread should have initialized |
615 | 489 | assert init_count["count"] == 1 |
616 | | - |
617 | | - |
618 | | -# ============================================================================= |
619 | | -# async_atomic_init Tests |
620 | | -# ============================================================================= |
621 | | - |
622 | | - |
623 | | -class TestAsyncAtomicInit: |
624 | | - """Tests for async_atomic_init function.""" |
625 | | - |
626 | | - @pytest.mark.asyncio |
627 | | - async def test_async_atomic_init_first(self, tmp_path: Path) -> None: |
628 | | - """Test first async_atomic_init performs initialization.""" |
629 | | - resource_path = tmp_path / "resource" |
630 | | - resource_path.mkdir() |
631 | | - init_called = [] |
632 | | - |
633 | | - async def async_init_fn() -> None: |
634 | | - init_called.append(True) |
635 | | - await asyncio.sleep(0) |
636 | | - |
637 | | - result = await async_atomic_init(resource_path, async_init_fn) |
638 | | - |
639 | | - assert result is True |
640 | | - assert len(init_called) == 1 |
641 | | - assert (resource_path / ".initialized").exists() |
642 | | - |
643 | | - @pytest.mark.asyncio |
644 | | - async def test_async_atomic_init_already_done(self, tmp_path: Path) -> None: |
645 | | - """Test async_atomic_init skips when already initialized.""" |
646 | | - resource_path = tmp_path / "resource" |
647 | | - resource_path.mkdir() |
648 | | - (resource_path / ".initialized").touch() |
649 | | - |
650 | | - init_called = [] |
651 | | - |
652 | | - async def async_init_fn() -> None: |
653 | | - init_called.append(True) |
654 | | - |
655 | | - result = await async_atomic_init(resource_path, async_init_fn) |
656 | | - |
657 | | - assert result is False |
658 | | - assert len(init_called) == 0 |
659 | | - |
660 | | - @pytest.mark.asyncio |
661 | | - async def test_async_atomic_init_sync_fn(self, tmp_path: Path) -> None: |
662 | | - """Test async_atomic_init works with sync init function.""" |
663 | | - resource_path = tmp_path / "resource" |
664 | | - resource_path.mkdir() |
665 | | - init_called = [] |
666 | | - |
667 | | - def sync_init_fn() -> None: |
668 | | - init_called.append(True) |
669 | | - |
670 | | - result = await async_atomic_init(resource_path, sync_init_fn) |
671 | | - |
672 | | - assert result is True |
673 | | - assert len(init_called) == 1 |
674 | | - |
675 | | - @pytest.mark.asyncio |
676 | | - async def test_async_atomic_init_concurrent(self, tmp_path: Path) -> None: |
677 | | - """Test async_atomic_init handles concurrent calls.""" |
678 | | - resource_path = tmp_path / "resource" |
679 | | - resource_path.mkdir() |
680 | | - init_count = {"count": 0} |
681 | | - |
682 | | - async def init_fn() -> None: |
683 | | - init_count["count"] += 1 |
684 | | - await asyncio.sleep(0.1) # Simulate slow init |
685 | | - |
686 | | - results = await asyncio.gather( |
687 | | - *[async_atomic_init(resource_path, init_fn) for _ in range(5)] |
688 | | - ) |
689 | | - |
690 | | - # Only one should have returned True |
691 | | - assert sum(results) == 1 |
692 | | - # Only one init should have run |
693 | | - assert init_count["count"] == 1 |
0 commit comments