157 lines
3.9 KiB
Python
157 lines
3.9 KiB
Python
|
import signal
|
||
|
|
||
|
from asyncio import Event
|
||
|
from pathlib import Path
|
||
|
from unittest.mock import Mock
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
from sanic.app import Sanic
|
||
|
from sanic.worker.loader import AppLoader
|
||
|
from sanic.worker.reloader import Reloader
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def reloader():
|
||
|
...
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def app():
|
||
|
app = Sanic("Test")
|
||
|
|
||
|
@app.route("/")
|
||
|
def handler(_):
|
||
|
...
|
||
|
|
||
|
return app
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def app_loader(app):
|
||
|
return AppLoader(factory=lambda: app)
|
||
|
|
||
|
|
||
|
def run_reloader(reloader):
|
||
|
def stop(*_):
|
||
|
reloader.stop()
|
||
|
|
||
|
signal.signal(signal.SIGALRM, stop)
|
||
|
signal.alarm(1)
|
||
|
reloader()
|
||
|
|
||
|
|
||
|
def is_python_file(filename):
|
||
|
return (isinstance(filename, Path) and (filename.suffix == "py")) or (
|
||
|
isinstance(filename, str) and filename.endswith(".py")
|
||
|
)
|
||
|
|
||
|
|
||
|
def test_reload_send():
|
||
|
publisher = Mock()
|
||
|
reloader = Reloader(publisher, 0.1, set(), Mock())
|
||
|
reloader.reload("foobar")
|
||
|
publisher.send.assert_called_once_with("__ALL_PROCESSES__:foobar")
|
||
|
|
||
|
|
||
|
def test_iter_files():
|
||
|
reloader = Reloader(Mock(), 0.1, set(), Mock())
|
||
|
len_python_files = len(list(reloader.files()))
|
||
|
assert len_python_files > 0
|
||
|
|
||
|
static_dir = Path(__file__).parent.parent / "static"
|
||
|
len_static_files = len(list(static_dir.glob("**/*")))
|
||
|
reloader = Reloader(Mock(), 0.1, set({static_dir}), Mock())
|
||
|
len_total_files = len(list(reloader.files()))
|
||
|
assert len_static_files > 0
|
||
|
assert len_total_files == len_python_files + len_static_files
|
||
|
|
||
|
|
||
|
def test_reloader_triggers_start_stop_listeners(
|
||
|
app: Sanic, app_loader: AppLoader
|
||
|
):
|
||
|
results = []
|
||
|
|
||
|
@app.reload_process_start
|
||
|
def reload_process_start(_):
|
||
|
results.append("reload_process_start")
|
||
|
|
||
|
@app.reload_process_stop
|
||
|
def reload_process_stop(_):
|
||
|
results.append("reload_process_stop")
|
||
|
|
||
|
reloader = Reloader(Mock(), 0.1, set(), app_loader)
|
||
|
run_reloader(reloader)
|
||
|
|
||
|
assert results == ["reload_process_start", "reload_process_stop"]
|
||
|
|
||
|
|
||
|
def test_not_triggered(app_loader):
|
||
|
reload_dir = Path(__file__).parent.parent / "fake"
|
||
|
publisher = Mock()
|
||
|
reloader = Reloader(publisher, 0.1, {reload_dir}, app_loader)
|
||
|
run_reloader(reloader)
|
||
|
|
||
|
publisher.send.assert_not_called()
|
||
|
|
||
|
|
||
|
def test_triggered(app_loader):
|
||
|
paths = set()
|
||
|
|
||
|
def check_file(filename, mtimes):
|
||
|
if (isinstance(filename, Path) and (filename.name == "server.py")) or (
|
||
|
isinstance(filename, str) and "sanic/app.py" in filename
|
||
|
):
|
||
|
paths.add(str(filename))
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
reload_dir = Path(__file__).parent.parent / "fake"
|
||
|
publisher = Mock()
|
||
|
reloader = Reloader(publisher, 0.1, {reload_dir}, app_loader)
|
||
|
reloader.check_file = check_file # type: ignore
|
||
|
run_reloader(reloader)
|
||
|
|
||
|
assert len(paths) == 2
|
||
|
|
||
|
publisher.send.assert_called()
|
||
|
call_arg = publisher.send.call_args_list[0][0][0]
|
||
|
assert call_arg.startswith("__ALL_PROCESSES__:")
|
||
|
assert call_arg.count(",") == 1
|
||
|
for path in paths:
|
||
|
assert str(path) in call_arg
|
||
|
|
||
|
|
||
|
def test_reloader_triggers_reload_listeners(app: Sanic, app_loader: AppLoader):
|
||
|
before = Event()
|
||
|
after = Event()
|
||
|
|
||
|
def check_file(filename, mtimes):
|
||
|
return not after.is_set()
|
||
|
|
||
|
@app.before_reload_trigger
|
||
|
async def before_reload_trigger(_):
|
||
|
before.set()
|
||
|
|
||
|
@app.after_reload_trigger
|
||
|
async def after_reload_trigger(_):
|
||
|
after.set()
|
||
|
|
||
|
reloader = Reloader(Mock(), 0.1, set(), app_loader)
|
||
|
reloader.check_file = check_file # type: ignore
|
||
|
run_reloader(reloader)
|
||
|
|
||
|
assert before.is_set()
|
||
|
assert after.is_set()
|
||
|
|
||
|
|
||
|
def test_check_file(tmp_path):
|
||
|
current = tmp_path / "testing.txt"
|
||
|
current.touch()
|
||
|
mtimes = {}
|
||
|
assert Reloader.check_file(current, mtimes) is False
|
||
|
assert len(mtimes) == 1
|
||
|
assert Reloader.check_file(current, mtimes) is False
|
||
|
mtimes[current] = mtimes[current] - 1
|
||
|
assert Reloader.check_file(current, mtimes) is True
|