更新
This commit is contained in:
@@ -30,36 +30,127 @@ class AccountReplyQueueTests(unittest.IsolatedAsyncioTestCase):
|
||||
await asyncio.sleep(0.002)
|
||||
self.assertEqual(queue.pending_count, 0)
|
||||
|
||||
async def test_one_account_runs_three_jobs_at_successive_fifo_slots(self):
|
||||
async def test_immediate_if_idle_runs_first_now_then_successive_fifo_slots(self):
|
||||
queue = await self._start_queue(account_id=101)
|
||||
interval = 0.05
|
||||
loop = asyncio.get_running_loop()
|
||||
started_at = loop.time()
|
||||
calls: list[tuple[int, float]] = []
|
||||
finished = asyncio.Event()
|
||||
first_started = asyncio.Event()
|
||||
release_first = asyncio.Event()
|
||||
|
||||
def callback_for(index: int):
|
||||
async def callback() -> None:
|
||||
calls.append((index, loop.time() - started_at))
|
||||
if index == 0:
|
||||
first_started.set()
|
||||
await release_first.wait()
|
||||
if len(calls) == 3:
|
||||
finished.set()
|
||||
|
||||
return callback
|
||||
|
||||
for index in range(3):
|
||||
await queue.enqueue(interval, callback_for(index), description=str(index))
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
callback_for(0),
|
||||
description="0",
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
await asyncio.wait_for(first_started.wait(), timeout=0.1)
|
||||
for index in (1, 2):
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
callback_for(index),
|
||||
description=str(index),
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
release_first.set()
|
||||
|
||||
await asyncio.wait_for(finished.wait(), timeout=0.75)
|
||||
await self._wait_until_idle(queue)
|
||||
|
||||
self.assertEqual([index for index, _ in calls], [0, 1, 2])
|
||||
elapsed = [timestamp for _, timestamp in calls]
|
||||
for timestamp, expected in zip(elapsed, (interval, interval * 2, interval * 3)):
|
||||
for timestamp, expected in zip(elapsed, (0, interval, interval * 2)):
|
||||
self.assertGreaterEqual(timestamp, expected - 0.015)
|
||||
self.assertLess(timestamp, expected + 0.15)
|
||||
self.assertGreaterEqual(elapsed[1] - elapsed[0], interval - 0.02)
|
||||
self.assertGreaterEqual(elapsed[2] - elapsed[1], interval - 0.02)
|
||||
|
||||
async def test_immediate_if_idle_does_not_bypass_active_send(self):
|
||||
queue = await self._start_queue(account_id=102)
|
||||
interval = 1.0
|
||||
first_started = asyncio.Event()
|
||||
release_first = asyncio.Event()
|
||||
second_started = asyncio.Event()
|
||||
|
||||
async def first_callback() -> None:
|
||||
first_started.set()
|
||||
await release_first.wait()
|
||||
|
||||
async def second_callback() -> None:
|
||||
second_started.set()
|
||||
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
first_callback,
|
||||
description="first",
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
await asyncio.wait_for(first_started.wait(), timeout=0.1)
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
second_callback,
|
||||
description="second",
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
|
||||
snapshot = await queue.snapshot()
|
||||
self.assertEqual([item["status"] for item in snapshot], ["sending", "waiting"])
|
||||
self.assertEqual(snapshot[0]["interval_seconds"], 0)
|
||||
self.assertEqual(snapshot[1]["interval_seconds"], int(interval))
|
||||
|
||||
release_first.set()
|
||||
await asyncio.sleep(0.02)
|
||||
self.assertFalse(second_started.is_set())
|
||||
|
||||
async def test_immediate_if_idle_resets_after_queue_drains(self):
|
||||
queue = await self._start_queue(account_id=103)
|
||||
interval = 1.0
|
||||
first_finished = asyncio.Event()
|
||||
second_started = asyncio.Event()
|
||||
release_second = asyncio.Event()
|
||||
|
||||
async def first_callback() -> None:
|
||||
first_finished.set()
|
||||
|
||||
async def second_callback() -> None:
|
||||
second_started.set()
|
||||
await release_second.wait()
|
||||
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
first_callback,
|
||||
description="first wave",
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
await asyncio.wait_for(first_finished.wait(), timeout=0.1)
|
||||
await self._wait_until_idle(queue)
|
||||
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
second_callback,
|
||||
description="second wave",
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
await asyncio.wait_for(second_started.wait(), timeout=0.1)
|
||||
snapshot = await queue.snapshot()
|
||||
self.assertEqual(len(snapshot), 1)
|
||||
self.assertEqual(snapshot[0]["status"], "sending")
|
||||
self.assertEqual(snapshot[0]["interval_seconds"], 0)
|
||||
release_second.set()
|
||||
|
||||
async def test_separate_account_queues_reach_first_slot_without_blocking(self):
|
||||
first_queue = await self._start_queue(account_id=201)
|
||||
second_queue = await self._start_queue(account_id=202)
|
||||
@@ -194,6 +285,47 @@ class AccountReplyQueueTests(unittest.IsolatedAsyncioTestCase):
|
||||
new_due = datetime.fromisoformat(new["scheduled_at"]).timestamp()
|
||||
self.assertAlmostEqual(old_due - new_due, interval, delta=0.05)
|
||||
|
||||
async def test_send_now_on_zero_slot_does_not_claim_later_jobs_shifted(self):
|
||||
queue = await self._start_queue(account_id=512)
|
||||
interval = 1.0
|
||||
|
||||
async def noop() -> None:
|
||||
pass
|
||||
|
||||
# Both enqueues complete without yielding to the consumer, preserving
|
||||
# the narrow management-API window where the zero-slot first job is
|
||||
# still waiting and can be selected by send-now.
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
noop,
|
||||
description="immediate first",
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
await queue.enqueue(
|
||||
interval,
|
||||
noop,
|
||||
description="scheduled second",
|
||||
immediate_if_idle=True,
|
||||
)
|
||||
before = await queue.snapshot()
|
||||
second_before = next(
|
||||
item for item in before if item["description"] == "scheduled second"
|
||||
)
|
||||
|
||||
result = await queue.send_now(before[0]["job_id"])
|
||||
after = await queue.snapshot()
|
||||
second_after = next(
|
||||
item for item in after if item["description"] == "scheduled second"
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "accepted")
|
||||
self.assertEqual(result["shifted_count"], 0)
|
||||
second_due_before = datetime.fromisoformat(
|
||||
second_before["scheduled_at"]
|
||||
).timestamp()
|
||||
second_due_after = datetime.fromisoformat(second_after["scheduled_at"]).timestamp()
|
||||
self.assertAlmostEqual(second_due_after, second_due_before, delta=0.01)
|
||||
|
||||
async def test_send_now_middle_runs_first_and_only_shifts_jobs_behind_it(self):
|
||||
queue = await self._start_queue(account_id=503)
|
||||
interval = 0.12
|
||||
|
||||
Reference in New Issue
Block a user