mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-02 07:45:54 +00:00
test: speed up cron and restart timing tests
Replace fixed sleep-based waits with condition polling in cron tests and mock the restart delay in CLI restart tests to reduce suite runtime without changing behavior.
This commit is contained in:
parent
b6d63fb1ec
commit
46e11a68a7
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
from types import SimpleNamespace
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -31,6 +32,15 @@ def _make_loop():
|
|||||||
return loop, bus
|
return loop, bus
|
||||||
|
|
||||||
|
|
||||||
|
async def _wait_until(predicate, *, timeout: float = 0.2, interval: float = 0.01) -> None:
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if predicate():
|
||||||
|
return
|
||||||
|
await asyncio.sleep(interval)
|
||||||
|
assert predicate()
|
||||||
|
|
||||||
|
|
||||||
class TestRestartCommand:
|
class TestRestartCommand:
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -47,7 +57,23 @@ class TestRestartCommand:
|
|||||||
msg = InboundMessage(channel="cli", sender_id="user", chat_id="direct", content="/restart")
|
msg = InboundMessage(channel="cli", sender_id="user", chat_id="direct", content="/restart")
|
||||||
ctx = CommandContext(msg=msg, session=None, key=msg.session_key, raw="/restart", loop=loop)
|
ctx = CommandContext(msg=msg, session=None, key=msg.session_key, raw="/restart", loop=loop)
|
||||||
|
|
||||||
|
async def _fast_sleep(_delay: float) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
scheduled: list[asyncio.Task] = []
|
||||||
|
|
||||||
|
def _capture_task(coro):
|
||||||
|
task = asyncio.create_task(coro)
|
||||||
|
scheduled.append(task)
|
||||||
|
return task
|
||||||
|
|
||||||
|
fake_asyncio = SimpleNamespace(
|
||||||
|
sleep=_fast_sleep,
|
||||||
|
create_task=_capture_task,
|
||||||
|
)
|
||||||
|
|
||||||
with patch.dict(os.environ, {}, clear=False), \
|
with patch.dict(os.environ, {}, clear=False), \
|
||||||
|
patch("nanobot.command.builtin.asyncio", new=fake_asyncio), \
|
||||||
patch("nanobot.command.builtin.os.execv") as mock_execv:
|
patch("nanobot.command.builtin.os.execv") as mock_execv:
|
||||||
out = await cmd_restart(ctx)
|
out = await cmd_restart(ctx)
|
||||||
assert "Restarting" in out.content
|
assert "Restarting" in out.content
|
||||||
@ -55,7 +81,8 @@ class TestRestartCommand:
|
|||||||
assert os.environ.get(RESTART_NOTIFY_CHAT_ID_ENV) == "direct"
|
assert os.environ.get(RESTART_NOTIFY_CHAT_ID_ENV) == "direct"
|
||||||
assert os.environ.get(RESTART_STARTED_AT_ENV)
|
assert os.environ.get(RESTART_STARTED_AT_ENV)
|
||||||
|
|
||||||
await asyncio.sleep(1.5)
|
assert scheduled
|
||||||
|
await scheduled[0]
|
||||||
mock_execv.assert_called_once()
|
mock_execv.assert_called_once()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@ -8,6 +8,15 @@ from nanobot.cron.service import CronService
|
|||||||
from nanobot.cron.types import CronJob, CronPayload, CronSchedule
|
from nanobot.cron.types import CronJob, CronPayload, CronSchedule
|
||||||
|
|
||||||
|
|
||||||
|
async def _wait_until(predicate, *, timeout: float = 1.0, interval: float = 0.01) -> None:
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if predicate():
|
||||||
|
return
|
||||||
|
await asyncio.sleep(interval)
|
||||||
|
assert predicate()
|
||||||
|
|
||||||
|
|
||||||
def test_add_job_rejects_unknown_timezone(tmp_path) -> None:
|
def test_add_job_rejects_unknown_timezone(tmp_path) -> None:
|
||||||
service = CronService(tmp_path / "cron" / "jobs.json")
|
service = CronService(tmp_path / "cron" / "jobs.json")
|
||||||
|
|
||||||
@ -201,18 +210,18 @@ async def test_start_server_not_jobs(tmp_path):
|
|||||||
async def on_job(job):
|
async def on_job(job):
|
||||||
called.append(job.name)
|
called.append(job.name)
|
||||||
|
|
||||||
service = CronService(store_path, on_job=on_job, max_sleep_ms=1000)
|
service = CronService(store_path, on_job=on_job, max_sleep_ms=100)
|
||||||
await service.start()
|
await service.start()
|
||||||
assert len(service.list_jobs()) == 0
|
assert len(service.list_jobs()) == 0
|
||||||
|
|
||||||
service2 = CronService(tmp_path / "cron" / "jobs.json")
|
service2 = CronService(tmp_path / "cron" / "jobs.json")
|
||||||
service2.add_job(
|
service2.add_job(
|
||||||
name="hist",
|
name="hist",
|
||||||
schedule=CronSchedule(kind="every", every_ms=500),
|
schedule=CronSchedule(kind="every", every_ms=100),
|
||||||
message="hello",
|
message="hello",
|
||||||
)
|
)
|
||||||
assert len(service.list_jobs()) == 1
|
assert len(service.list_jobs()) == 1
|
||||||
await asyncio.sleep(2)
|
await _wait_until(lambda: bool(called), timeout=0.8)
|
||||||
assert len(called) != 0
|
assert len(called) != 0
|
||||||
service.stop()
|
service.stop()
|
||||||
|
|
||||||
@ -248,10 +257,10 @@ async def test_running_service_picks_up_external_add(tmp_path):
|
|||||||
async def on_job(job):
|
async def on_job(job):
|
||||||
called.append(job.name)
|
called.append(job.name)
|
||||||
|
|
||||||
service = CronService(store_path, on_job=on_job)
|
service = CronService(store_path, on_job=on_job, max_sleep_ms=100)
|
||||||
service.add_job(
|
service.add_job(
|
||||||
name="heartbeat",
|
name="heartbeat",
|
||||||
schedule=CronSchedule(kind="every", every_ms=150),
|
schedule=CronSchedule(kind="every", every_ms=100),
|
||||||
message="tick",
|
message="tick",
|
||||||
)
|
)
|
||||||
await service.start()
|
await service.start()
|
||||||
@ -261,11 +270,11 @@ async def test_running_service_picks_up_external_add(tmp_path):
|
|||||||
external = CronService(store_path)
|
external = CronService(store_path)
|
||||||
external.add_job(
|
external.add_job(
|
||||||
name="external",
|
name="external",
|
||||||
schedule=CronSchedule(kind="every", every_ms=150),
|
schedule=CronSchedule(kind="every", every_ms=100),
|
||||||
message="ping",
|
message="ping",
|
||||||
)
|
)
|
||||||
|
|
||||||
await asyncio.sleep(2)
|
await _wait_until(lambda: "external" in called, timeout=0.8)
|
||||||
assert "external" in called
|
assert "external" in called
|
||||||
finally:
|
finally:
|
||||||
service.stop()
|
service.stop()
|
||||||
@ -287,16 +296,16 @@ async def test_add_job_during_jobs_exec(tmp_path):
|
|||||||
)
|
)
|
||||||
run_once = False
|
run_once = False
|
||||||
|
|
||||||
service = CronService(store_path, on_job=on_job)
|
service = CronService(store_path, on_job=on_job, max_sleep_ms=100)
|
||||||
service.add_job(
|
service.add_job(
|
||||||
name="heartbeat",
|
name="heartbeat",
|
||||||
schedule=CronSchedule(kind="every", every_ms=150),
|
schedule=CronSchedule(kind="every", every_ms=100),
|
||||||
message="tick",
|
message="tick",
|
||||||
)
|
)
|
||||||
assert len(service.list_jobs()) == 1
|
assert len(service.list_jobs()) == 1
|
||||||
await service.start()
|
await service.start()
|
||||||
try:
|
try:
|
||||||
await asyncio.sleep(3)
|
await _wait_until(lambda: len(service.list_jobs()) == 2, timeout=0.8)
|
||||||
jobs = service.list_jobs()
|
jobs = service.list_jobs()
|
||||||
assert len(jobs) == 2
|
assert len(jobs) == 2
|
||||||
assert "test" in [j.name for j in jobs]
|
assert "test" in [j.name for j in jobs]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user