"""数据库短事务 worker 的容量、取消与关闭合同测试。""" import asyncio import threading import pytest from app.db.worker import ( DatabaseWorker, DatabaseWorkerClosedError, DatabaseWorkerOverloadedError, ) @pytest.mark.asyncio async def test_worker_requires_explicit_start() -> None: """构造对象不会隐式创建可执行线程池。""" worker = DatabaseWorker(max_workers=1, capacity=1) with pytest.raises(DatabaseWorkerClosedError): await worker.run(lambda: None) @pytest.mark.asyncio async def test_worker_rejects_work_beyond_running_and_queue_capacity() -> None: """运行与排队任务达到总容量后立即拒绝新任务。""" worker = DatabaseWorker(max_workers=1, capacity=2) await worker.start() started = threading.Event() release = threading.Event() def block() -> None: started.set() release.wait(1) running = asyncio.create_task(worker.run(block)) await asyncio.to_thread(started.wait) queued = asyncio.create_task(worker.run(lambda: None)) await asyncio.sleep(0) with pytest.raises(DatabaseWorkerOverloadedError): await worker.run(lambda: None) assert worker.snapshot().running == 1 assert worker.snapshot().queued == 1 assert worker.snapshot().rejected == 1 release.set() await asyncio.gather(running, queued) await worker.shutdown() @pytest.mark.asyncio async def test_cancelling_queued_work_prevents_execution() -> None: """尚未取得线程的任务取消后不得执行数据库操作。""" worker = DatabaseWorker(max_workers=1, capacity=2) await worker.start() started = threading.Event() release = threading.Event() queued_executed = threading.Event() def block() -> None: started.set() release.wait(1) running = asyncio.create_task(worker.run(block)) await asyncio.to_thread(started.wait) queued = asyncio.create_task(worker.run(queued_executed.set)) await asyncio.sleep(0) queued.cancel() with pytest.raises(asyncio.CancelledError): await queued release.set() await running await worker.shutdown() assert queued_executed.is_set() is False assert worker.snapshot().queued == 0 assert worker.snapshot().running == 0 @pytest.mark.asyncio async def test_cancelling_running_work_waits_for_transaction_terminal_state() -> None: """线程内操作开始后,取消结果必须晚于操作的最终状态。""" worker = DatabaseWorker(max_workers=1, capacity=1) await worker.start() started = threading.Event() release = threading.Event() completed = threading.Event() def operation() -> None: started.set() release.wait(1) completed.set() task = asyncio.create_task(worker.run(operation)) await asyncio.to_thread(started.wait) task.cancel() await asyncio.sleep(0.01) assert task.done() is False assert completed.is_set() is False release.set() with pytest.raises(asyncio.CancelledError): await task assert completed.is_set() is True await worker.shutdown() @pytest.mark.asyncio async def test_shutdown_rejects_new_work_and_waits_for_running_work() -> None: """关闭期间不接收新任务,并等待已开始的操作结束。""" worker = DatabaseWorker(max_workers=1, capacity=2) await worker.start() started = threading.Event() release = threading.Event() def operation() -> None: started.set() release.wait(1) running = asyncio.create_task(worker.run(operation)) await asyncio.to_thread(started.wait) shutdown = asyncio.create_task(worker.shutdown()) await asyncio.sleep(0) with pytest.raises(DatabaseWorkerClosedError): await worker.run(lambda: None) assert shutdown.done() is False release.set() await running await shutdown assert worker.snapshot().closing is True assert worker.snapshot().queued == 0 assert worker.snapshot().running == 0