mirror of
https://github.com/Hommy-master/capcut-mate.git
synced 2026-06-25 09:57:23 +08:00
352 lines
12 KiB
Python
352 lines
12 KiB
Python
"""
|
||
草稿并发锁管理器单元测试
|
||
测试 DraftLockManager 的所有功能
|
||
|
||
测试覆盖:
|
||
1. 正常场景:锁的获取和释放
|
||
2. 边界场景:超时、重入、并发访问
|
||
3. 异常场景:无效输入、重复释放
|
||
"""
|
||
import asyncio
|
||
import pytest
|
||
import sys
|
||
import os
|
||
|
||
# 添加项目根目录到 Python 路径
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||
|
||
from src.utils.draft_lock_manager import DraftLockManager, get_draft_lock_manager
|
||
|
||
|
||
class TestDraftLockManager:
|
||
"""DraftLockManager 测试类"""
|
||
|
||
@pytest.fixture
|
||
def lock_manager(self):
|
||
"""创建锁管理器实例"""
|
||
return DraftLockManager()
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_acquire_and_release_lock(self, lock_manager):
|
||
"""测试基本的锁获取和释放"""
|
||
draft_id = "test-draft-001"
|
||
|
||
# 获取锁
|
||
result = await lock_manager.acquire_lock(draft_id)
|
||
assert result is True
|
||
assert lock_manager.is_locked(draft_id) is True
|
||
assert lock_manager.get_lock_count(draft_id) == 1
|
||
|
||
# 释放锁
|
||
await lock_manager.release_lock(draft_id)
|
||
assert lock_manager.is_locked(draft_id) is False
|
||
assert lock_manager.get_lock_count(draft_id) == 0
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_acquire_lock_with_timeout(self, lock_manager):
|
||
"""测试带超时的锁获取"""
|
||
draft_id = "test-draft-002"
|
||
|
||
# 立即获取锁(无超时)
|
||
result = await lock_manager.acquire_lock(draft_id, timeout=None)
|
||
assert result is True
|
||
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
# 带超时获取锁
|
||
result = await lock_manager.acquire_lock(draft_id, timeout=5.0)
|
||
assert result is True
|
||
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_acquire_lock_timeout_exception(self, lock_manager):
|
||
"""测试锁获取超时异常"""
|
||
draft_id = "test-draft-003"
|
||
|
||
# 第一次获取锁
|
||
await lock_manager.acquire_lock(draft_id)
|
||
|
||
# 尝试再次获取(应该超时)
|
||
with pytest.raises(asyncio.TimeoutError):
|
||
await lock_manager.acquire_lock(draft_id, timeout=0.1)
|
||
|
||
# 清理
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_release_nonexistent_lock(self, lock_manager):
|
||
"""测试释放不存在的锁"""
|
||
draft_id = "nonexistent-draft"
|
||
|
||
with pytest.raises(KeyError):
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_is_locked_method(self, lock_manager):
|
||
"""测试 is_locked 方法"""
|
||
draft_id = "test-draft-004"
|
||
|
||
# 未锁定时
|
||
assert lock_manager.is_locked(draft_id) is False
|
||
|
||
# 锁定后
|
||
await lock_manager.acquire_lock(draft_id)
|
||
assert lock_manager.is_locked(draft_id) is True
|
||
|
||
# 释放后
|
||
await lock_manager.release_lock(draft_id)
|
||
assert lock_manager.is_locked(draft_id) is False
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_lock_count(self, lock_manager):
|
||
"""测试获取锁计数"""
|
||
draft_id = "test-draft-005"
|
||
|
||
# 初始计数为 0
|
||
assert lock_manager.get_lock_count(draft_id) == 0
|
||
|
||
# 获取一次锁
|
||
await lock_manager.acquire_lock(draft_id)
|
||
assert lock_manager.get_lock_count(draft_id) == 1
|
||
|
||
# 释放锁
|
||
await lock_manager.release_lock(draft_id)
|
||
assert lock_manager.get_lock_count(draft_id) == 0
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_all_locked_drafts(self, lock_manager):
|
||
"""测试获取所有被锁定的草稿"""
|
||
draft_ids = ["test-draft-006-a", "test-draft-006-b", "test-draft-006-c"]
|
||
|
||
# 锁定前两个
|
||
await lock_manager.acquire_lock(draft_ids[0])
|
||
await lock_manager.acquire_lock(draft_ids[1])
|
||
|
||
locked = lock_manager.get_all_locked_drafts()
|
||
assert len(locked) == 2
|
||
assert draft_ids[0] in locked
|
||
assert draft_ids[1] in locked
|
||
assert draft_ids[2] not in locked
|
||
|
||
# 释放一个
|
||
await lock_manager.release_lock(draft_ids[0])
|
||
locked = lock_manager.get_all_locked_drafts()
|
||
assert len(locked) == 1
|
||
assert draft_ids[1] in locked
|
||
|
||
# 全部释放
|
||
await lock_manager.release_lock(draft_ids[1])
|
||
locked = lock_manager.get_all_locked_drafts()
|
||
assert len(locked) == 0
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_clear_all_locks(self, lock_manager):
|
||
"""测试清除所有锁"""
|
||
draft_ids = ["test-draft-007-a", "test-draft-007-b"]
|
||
|
||
# 获取多个锁
|
||
for draft_id in draft_ids:
|
||
await lock_manager.acquire_lock(draft_id)
|
||
|
||
# 验证都被锁定
|
||
for draft_id in draft_ids:
|
||
assert lock_manager.is_locked(draft_id) is True
|
||
|
||
# 清除所有锁
|
||
await lock_manager.clear_all_locks()
|
||
|
||
# 验证都已释放(锁对象已被删除)
|
||
for draft_id in draft_ids:
|
||
assert lock_manager.is_locked(draft_id) is False
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_stats(self, lock_manager):
|
||
"""测试获取统计信息"""
|
||
draft_ids = ["test-draft-008-a", "test-draft-008-b"]
|
||
|
||
# 初始状态
|
||
stats = lock_manager.get_stats()
|
||
assert stats["total_locks"] == 0
|
||
assert stats["locked_drafts"] == 0
|
||
|
||
# 获取锁后
|
||
for draft_id in draft_ids:
|
||
await lock_manager.acquire_lock(draft_id)
|
||
|
||
stats = lock_manager.get_stats()
|
||
assert stats["total_locks"] == 2
|
||
assert stats["locked_drafts"] == 2
|
||
assert stats["total_holders"] == 2
|
||
|
||
# 清理
|
||
for draft_id in draft_ids:
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_concurrent_access_different_drafts(self, lock_manager):
|
||
"""测试并发访问不同草稿"""
|
||
results = []
|
||
|
||
async def acquire_and_release(draft_id):
|
||
await lock_manager.acquire_lock(draft_id)
|
||
await asyncio.sleep(0.1) # 模拟工作
|
||
results.append(draft_id)
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
# 并发访问 3 个不同的草稿
|
||
tasks = [
|
||
acquire_and_release("draft-a"),
|
||
acquire_and_release("draft-b"),
|
||
acquire_and_release("draft-c")
|
||
]
|
||
|
||
await asyncio.gather(*tasks)
|
||
|
||
# 所有任务都应该完成
|
||
assert len(results) == 3
|
||
assert "draft-a" in results
|
||
assert "draft-b" in results
|
||
assert "draft-c" in results
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_serial_access_same_draft(self, lock_manager):
|
||
"""测试串行访问同一草稿"""
|
||
draft_id = "test-draft-same"
|
||
execution_order = []
|
||
|
||
async def worker(worker_id):
|
||
await lock_manager.acquire_lock(draft_id)
|
||
try:
|
||
execution_order.append(f"{worker_id}_start")
|
||
await asyncio.sleep(0.1)
|
||
execution_order.append(f"{worker_id}_end")
|
||
finally:
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
# 3 个 worker 按顺序访问同一草稿(不是并发)
|
||
# 因为 asyncio.gather 会同时启动所有任务,但锁会强制它们串行执行
|
||
tasks = [
|
||
worker(1),
|
||
worker(2),
|
||
worker(3)
|
||
]
|
||
|
||
await asyncio.gather(*tasks)
|
||
|
||
# 验证执行顺序是串行的(每个 worker 必须等待前一个完成)
|
||
# 由于锁的保护,应该是一个接一个执行
|
||
assert len(execution_order) == 6
|
||
|
||
# 验证每个 worker 都是成对的 start->end
|
||
for i in range(0, len(execution_order), 2):
|
||
worker_num = execution_order[i].split('_')[0]
|
||
assert execution_order[i+1] == f"{worker_num}_end"
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_singleton_pattern(self):
|
||
"""测试单例模式"""
|
||
manager1 = DraftLockManager()
|
||
manager2 = DraftLockManager()
|
||
|
||
# 应该是同一个实例
|
||
assert manager1 is manager2
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_draft_lock_manager_function(self):
|
||
"""测试全局获取器函数"""
|
||
manager1 = get_draft_lock_manager()
|
||
manager2 = get_draft_lock_manager()
|
||
|
||
# 应该是同一个实例
|
||
assert manager1 is manager2
|
||
|
||
|
||
class TestDraftLockManagerEdgeCases:
|
||
"""DraftLockManager 边界情况测试"""
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_empty_draft_id(self):
|
||
"""测试空字符串草稿 ID"""
|
||
lock_manager = DraftLockManager()
|
||
|
||
# 空字符串也应该能获取锁
|
||
draft_id = ""
|
||
result = await lock_manager.acquire_lock(draft_id)
|
||
assert result is True
|
||
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_special_characters_in_draft_id(self):
|
||
"""测试特殊字符的草稿 ID"""
|
||
lock_manager = DraftLockManager()
|
||
|
||
special_ids = [
|
||
"draft-with-dash",
|
||
"draft_with_underscore",
|
||
"draft.with.dots",
|
||
"draft/with/slashes",
|
||
"draft@with#special$chars"
|
||
]
|
||
|
||
for draft_id in special_ids:
|
||
await lock_manager.acquire_lock(draft_id)
|
||
assert lock_manager.is_locked(draft_id) is True
|
||
await lock_manager.release_lock(draft_id)
|
||
assert lock_manager.is_locked(draft_id) is False
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_very_long_draft_id(self):
|
||
"""测试超长草稿 ID"""
|
||
lock_manager = DraftLockManager()
|
||
|
||
# 创建一个很长的 ID
|
||
long_id = "draft-" + "a" * 1000
|
||
|
||
await lock_manager.acquire_lock(long_id)
|
||
assert lock_manager.is_locked(long_id) is True
|
||
await lock_manager.release_lock(long_id)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_rapid_acquire_release_cycle(self):
|
||
"""测试快速连续获取释放循环"""
|
||
lock_manager = DraftLockManager()
|
||
draft_id = "rapid-cycle"
|
||
|
||
# 快速循环 100 次
|
||
for i in range(100):
|
||
await lock_manager.acquire_lock(draft_id)
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
# 最后应该是未锁定状态
|
||
assert lock_manager.is_locked(draft_id) is False
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_multiple_workers_same_draft_stress(self):
|
||
"""测试多 worker 压力测试"""
|
||
lock_manager = DraftLockManager()
|
||
draft_id = "stress-test"
|
||
counter = 0
|
||
|
||
async def increment_counter():
|
||
nonlocal counter
|
||
await lock_manager.acquire_lock(draft_id)
|
||
try:
|
||
current = counter
|
||
await asyncio.sleep(0.01) # 增加竞争
|
||
counter = current + 1
|
||
finally:
|
||
await lock_manager.release_lock(draft_id)
|
||
|
||
# 10 个 worker 同时尝试增加计数器
|
||
tasks = [increment_counter() for _ in range(10)]
|
||
await asyncio.gather(*tasks)
|
||
|
||
# 由于锁的保护,counter 应该正好是 10
|
||
assert counter == 10
|
||
|
||
|
||
if __name__ == "__main__":
|
||
pytest.main([__file__, "-v"])
|