mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-13 18:46:17 +05:00
262 lines
9.2 KiB
Python
262 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for archivebox server command.
|
|
Verify server can start (basic smoke tests only, no full server testing).
|
|
"""
|
|
|
|
import os
|
|
import asyncio
|
|
import builtins
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from types import SimpleNamespace
|
|
from unittest.mock import Mock
|
|
|
|
|
|
def test_sqlite_connections_use_explicit_30_second_busy_timeout():
|
|
from archivebox.core.settings import SQLITE_CONNECTION_OPTIONS
|
|
|
|
assert SQLITE_CONNECTION_OPTIONS["OPTIONS"]["timeout"] == 30
|
|
assert "PRAGMA busy_timeout = 30000;" in SQLITE_CONNECTION_OPTIONS["OPTIONS"]["init_command"]
|
|
assert "PRAGMA journal_mode = WAL;" in SQLITE_CONNECTION_OPTIONS["OPTIONS"]["init_command"]
|
|
|
|
|
|
def test_server_shows_usage_info(tmp_path, process):
|
|
"""Test that server command shows usage or starts."""
|
|
os.chdir(tmp_path)
|
|
|
|
# Just check that the command is recognized
|
|
# We won't actually start a full server in tests
|
|
result = subprocess.run(
|
|
["archivebox", "server", "--help"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
assert "server" in result.stdout.lower() or "http" in result.stdout.lower()
|
|
|
|
|
|
def test_server_init_flag(tmp_path, process):
|
|
"""Test that --init flag runs init before starting server."""
|
|
os.chdir(tmp_path)
|
|
|
|
# Check init flag is recognized
|
|
result = subprocess.run(
|
|
["archivebox", "server", "--help"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
assert "--init" in result.stdout or "init" in result.stdout.lower()
|
|
|
|
|
|
def test_runner_worker_uses_current_interpreter():
|
|
"""The supervised runner should use the active Python environment, not PATH."""
|
|
from archivebox.workers.supervisord_util import RUNNER_WORKER
|
|
|
|
assert RUNNER_WORKER["command"] == f"{sys.executable} -m archivebox run --daemon"
|
|
|
|
|
|
def test_reload_workers_use_current_interpreter_and_supervisord_managed_runner():
|
|
from archivebox.workers.supervisord_util import RUNNER_WATCH_WORKER, RUNSERVER_WORKER
|
|
|
|
runserver = RUNSERVER_WORKER("127.0.0.1", "8000", reload=True, pidfile="/tmp/runserver.pid")
|
|
watcher = RUNNER_WATCH_WORKER("/tmp/runserver.pid")
|
|
|
|
assert runserver["name"] == "worker_runserver"
|
|
assert runserver["command"] == f"{sys.executable} -m archivebox manage runserver 127.0.0.1:8000"
|
|
assert 'ARCHIVEBOX_RUNSERVER="1"' in runserver["environment"]
|
|
assert 'ARCHIVEBOX_AUTORELOAD="1"' in runserver["environment"]
|
|
assert 'ARCHIVEBOX_RUNSERVER_PIDFILE="/tmp/runserver.pid"' in runserver["environment"]
|
|
|
|
assert watcher["name"] == "worker_runner_watch"
|
|
assert watcher["command"] == f"{sys.executable} -m archivebox manage runner_watch --pidfile=/tmp/runserver.pid"
|
|
|
|
|
|
def test_start_server_workers_starts_plugin_owned_sonic_worker(monkeypatch):
|
|
from archivebox.workers import supervisord_util
|
|
|
|
supervisor = Mock()
|
|
supervisor.getPID.return_value = 123
|
|
started_workers = []
|
|
|
|
monkeypatch.setattr(supervisord_util, "get_or_create_supervisord_process", lambda daemonize=False: supervisor)
|
|
monkeypatch.setattr(supervisord_util, "tail_multiple_worker_logs", lambda *args, **kwargs: None)
|
|
monkeypatch.setattr(
|
|
supervisord_util,
|
|
"get_sonic_supervisord_worker_from_plugin",
|
|
lambda config: {
|
|
"name": "worker_sonic",
|
|
"command": "sonic -c /data/sonic/config.cfg",
|
|
"stdout_logfile": "logs/worker_sonic.log",
|
|
},
|
|
)
|
|
monkeypatch.setattr(
|
|
supervisord_util,
|
|
"start_worker",
|
|
lambda _supervisor, worker, lazy=False: started_workers.append((worker["name"], lazy)) or {"name": worker["name"]},
|
|
)
|
|
monkeypatch.setattr("archivebox.config.common.get_config", lambda: SimpleNamespace(TMP_DIR="/tmp"))
|
|
|
|
supervisord_util.start_server_workers(daemonize=True, debug=False)
|
|
|
|
assert started_workers == [
|
|
("worker_daphne", False),
|
|
("worker_sonic", False),
|
|
("worker_runner", False),
|
|
]
|
|
|
|
|
|
def test_missing_plugin_owned_sonic_worker_is_optional(monkeypatch):
|
|
from archivebox.workers.supervisord_util import get_sonic_supervisord_worker_from_plugin
|
|
|
|
original_import = builtins.__import__
|
|
|
|
def fake_import(name, *args, **kwargs):
|
|
if name == "abx_plugins.plugins.search_backend_sonic.daemon":
|
|
raise ModuleNotFoundError("No module named 'abx_plugins.plugins.search_backend_sonic.daemon'", name=name)
|
|
return original_import(name, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(builtins, "__import__", fake_import)
|
|
|
|
assert get_sonic_supervisord_worker_from_plugin(SimpleNamespace()) is None
|
|
|
|
|
|
def test_sonic_daemon_event_handler_requires_running_supervised_worker(monkeypatch):
|
|
from abx_dl.events import ProcessStdoutEvent
|
|
from abx_dl.orchestrator import create_bus
|
|
from archivebox.search.sonic_daemon import register_sonic_daemon_event_handler
|
|
|
|
supervisor = Mock()
|
|
monkeypatch.setattr("archivebox.workers.supervisord_util.get_existing_supervisord_process", lambda: supervisor)
|
|
monkeypatch.setattr(
|
|
"archivebox.workers.supervisord_util.get_worker",
|
|
lambda _supervisor, name: {"name": name, "statename": "RUNNING", "description": "pid 123"},
|
|
)
|
|
monkeypatch.setattr("abx_plugins.plugins.search_backend_sonic.daemon.is_port_listening", lambda host, port: True)
|
|
|
|
async def run_test():
|
|
bus = create_bus(name="test_sonic_daemon_event_handler_requires_running_supervised_worker")
|
|
try:
|
|
register_sonic_daemon_event_handler(bus)
|
|
await bus.emit(
|
|
ProcessStdoutEvent(
|
|
line=json.dumps(
|
|
{
|
|
"type": "SonicDaemonStartEvent",
|
|
"worker_name": "worker_sonic",
|
|
"url": "tcp://127.0.0.1:1491",
|
|
"host": "127.0.0.1",
|
|
"port": 1491,
|
|
"config_path": "/data/sonic/config.cfg",
|
|
"output_dir": "/data/sonic",
|
|
},
|
|
),
|
|
),
|
|
).now()
|
|
finally:
|
|
await bus.destroy()
|
|
|
|
asyncio.run(run_test())
|
|
|
|
|
|
def test_stop_existing_background_runner_cleans_up_and_stops_orchestrators():
|
|
from archivebox.cli.archivebox_server import stop_existing_background_runner
|
|
|
|
runner_a = Mock()
|
|
runner_a.kill_tree = Mock()
|
|
runner_a.terminate = Mock()
|
|
runner_b = Mock()
|
|
runner_b.kill_tree = Mock(side_effect=RuntimeError("boom"))
|
|
runner_b.terminate = Mock()
|
|
|
|
process_model = Mock()
|
|
process_model.StatusChoices.RUNNING = "running"
|
|
process_model.TypeChoices.ORCHESTRATOR = "orchestrator"
|
|
queryset = Mock()
|
|
queryset.order_by.return_value = [runner_a, runner_b]
|
|
process_model.objects.filter.return_value = queryset
|
|
|
|
supervisor = Mock()
|
|
stop_worker = Mock()
|
|
log = Mock()
|
|
|
|
stopped = stop_existing_background_runner(
|
|
machine=Mock(),
|
|
process_model=process_model,
|
|
supervisor=supervisor,
|
|
stop_worker_fn=stop_worker,
|
|
log=log,
|
|
)
|
|
|
|
assert stopped == 2
|
|
assert process_model.cleanup_stale_running.call_count == 2
|
|
stop_worker.assert_any_call(supervisor, "worker_runner")
|
|
stop_worker.assert_any_call(supervisor, "worker_runner_watch")
|
|
runner_a.kill_tree.assert_called_once_with(graceful_timeout=2.0)
|
|
runner_b.terminate.assert_called_once_with(graceful_timeout=2.0)
|
|
log.assert_called_once()
|
|
|
|
|
|
def test_stop_existing_server_workers_takes_over_same_runserver_port(monkeypatch):
|
|
from archivebox.cli.archivebox_server import stop_existing_server_workers
|
|
|
|
supervisor = Mock()
|
|
supervisor.getProcessInfo.side_effect = lambda name: {
|
|
"worker_runserver": {"statename": "RUNNING"},
|
|
"worker_daphne": {"statename": "STOPPED"},
|
|
}.get(name, None)
|
|
stop_worker = Mock()
|
|
log = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"archivebox.cli.archivebox_server._read_supervisor_worker_command",
|
|
lambda worker_name: f"{sys.executable} -m archivebox manage runserver 0.0.0.0:8000" if worker_name == "worker_runserver" else "",
|
|
)
|
|
|
|
stopped = stop_existing_server_workers(
|
|
supervisor=supervisor,
|
|
stop_worker_fn=stop_worker,
|
|
host="0.0.0.0",
|
|
port="8000",
|
|
log=log,
|
|
)
|
|
|
|
assert stopped == 1
|
|
stop_worker.assert_called_once_with(supervisor, "worker_runserver")
|
|
log.assert_called_once()
|
|
|
|
|
|
def test_stop_existing_server_workers_leaves_different_port_running(monkeypatch):
|
|
from archivebox.cli.archivebox_server import stop_existing_server_workers
|
|
|
|
supervisor = Mock()
|
|
supervisor.getProcessInfo.side_effect = lambda name: {
|
|
"worker_runserver": {"statename": "RUNNING"},
|
|
"worker_daphne": {"statename": "STOPPED"},
|
|
}.get(name, None)
|
|
stop_worker = Mock()
|
|
log = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"archivebox.cli.archivebox_server._read_supervisor_worker_command",
|
|
lambda worker_name: f"{sys.executable} -m archivebox manage runserver 127.0.0.1:9000" if worker_name == "worker_runserver" else "",
|
|
)
|
|
|
|
stopped = stop_existing_server_workers(
|
|
supervisor=supervisor,
|
|
stop_worker_fn=stop_worker,
|
|
host="0.0.0.0",
|
|
port="8000",
|
|
log=log,
|
|
)
|
|
|
|
assert stopped == 0
|
|
stop_worker.assert_not_called()
|
|
log.assert_not_called()
|