mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:47:12 +01:00
Indexes the arch-wiki-docs dump that is already on disk and exposes it
to the knowledge-rag MCP server, so Arch questions can be asked by
symptom ("why doesn't my systemd user unit see DISPLAY under i3")
rather than by page title, with no network round-trip at query time.
The one Arch-specific MCP server in the wild fetches pages over the
network and bundles pacman operations that duplicate the local yay-mcp.
This does neither: it only searches, against a local corpus.
install.sh installs its own dependencies, wires the MCP config and
registers a pacman hook so the index follows package updates. The
generated index is machine-specific and regenerable, so it is
gitignored rather than committed.
_reindex_entry runs under the knowledge-rag interpreter, where
mcp_server is importable and this repo's packages are not. Resolving
that import with importlib.import_module states the deferral outright
instead of a function-level import statement that reads as an oversight,
and the backend failure path now re-raises as SystemExit, so the
exit-code translation is explicit rather than a swallowed exception.
Both replace lint suppressions; the package carries none.
pylint runs in its own venv, so beautifulsoup4 and markdownify are
declared there too — being in meta/requirements.txt does not make them
resolvable to the hook.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YQLwdquDCNmWtvgi3Lk72J
158 lines
5.7 KiB
Python
158 lines
5.7 KiB
Python
"""Tests for archwiki_rag.reindex."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from python_pkg.archwiki_rag import reindex
|
|
from python_pkg.archwiki_rag.constants import DATA_SUBDIR, LOCK_FILENAME
|
|
|
|
MOD = "python_pkg.archwiki_rag.reindex"
|
|
|
|
|
|
def _write_lock(store: Path, contents: str) -> None:
|
|
"""Create a knowledge-rag instance lock file.
|
|
|
|
Parameters:
|
|
store (Path): Store root.
|
|
contents (str): Raw lock file body.
|
|
"""
|
|
data = store / DATA_SUBDIR
|
|
data.mkdir(parents=True, exist_ok=True)
|
|
(data / LOCK_FILENAME).write_text(contents, encoding="utf-8")
|
|
|
|
|
|
class TestReadLockPid:
|
|
def test_missing_lock(self, tmp_path: Path) -> None:
|
|
assert reindex.read_lock_pid(tmp_path) is None
|
|
|
|
def test_reads_pid(self, tmp_path: Path) -> None:
|
|
_write_lock(tmp_path, "4321\n")
|
|
assert reindex.read_lock_pid(tmp_path) == 4321
|
|
|
|
def test_non_numeric_lock(self, tmp_path: Path) -> None:
|
|
_write_lock(tmp_path, "not-a-pid\n")
|
|
assert reindex.read_lock_pid(tmp_path) is None
|
|
|
|
def test_empty_lock(self, tmp_path: Path) -> None:
|
|
_write_lock(tmp_path, "")
|
|
assert reindex.read_lock_pid(tmp_path) is None
|
|
|
|
|
|
class TestIsServerRunning:
|
|
def test_no_lock(self, tmp_path: Path) -> None:
|
|
assert reindex.is_server_running(tmp_path) is False
|
|
|
|
def test_live_process(self, tmp_path: Path) -> None:
|
|
_write_lock(tmp_path, "1\n") # pid 1 always exists
|
|
assert reindex.is_server_running(tmp_path) is True
|
|
|
|
def test_stale_lock_treated_as_free(self, tmp_path: Path) -> None:
|
|
_write_lock(tmp_path, "999999999\n")
|
|
assert reindex.is_server_running(tmp_path) is False
|
|
|
|
|
|
class TestLoadPerCore:
|
|
def test_divides_load_by_cpu_count(self, tmp_path: Path) -> None:
|
|
loadavg = tmp_path / "loadavg"
|
|
loadavg.write_text("8.00 4.00 2.00 3/1234 5678\n", encoding="utf-8")
|
|
with (
|
|
patch(f"{MOD}.LOADAVG_PATH", loadavg),
|
|
patch(f"{MOD}.os.cpu_count", return_value=4),
|
|
):
|
|
assert reindex.load_per_core() == 2.0
|
|
|
|
def test_missing_file_is_not_busy(self, tmp_path: Path) -> None:
|
|
with patch(f"{MOD}.LOADAVG_PATH", tmp_path / "absent"):
|
|
assert reindex.load_per_core() == 0.0
|
|
|
|
def test_unparseable_content_is_not_busy(self, tmp_path: Path) -> None:
|
|
loadavg = tmp_path / "loadavg"
|
|
loadavg.write_text("garbage\n", encoding="utf-8")
|
|
with patch(f"{MOD}.LOADAVG_PATH", loadavg):
|
|
assert reindex.load_per_core() == 0.0
|
|
|
|
def test_empty_file_is_not_busy(self, tmp_path: Path) -> None:
|
|
loadavg = tmp_path / "loadavg"
|
|
loadavg.write_text("", encoding="utf-8")
|
|
with patch(f"{MOD}.LOADAVG_PATH", loadavg):
|
|
assert reindex.load_per_core() == 0.0
|
|
|
|
def test_unknown_cpu_count_falls_back_to_one(self, tmp_path: Path) -> None:
|
|
loadavg = tmp_path / "loadavg"
|
|
loadavg.write_text("3.00 1.00 1.00 1/1 1\n", encoding="utf-8")
|
|
with (
|
|
patch(f"{MOD}.LOADAVG_PATH", loadavg),
|
|
patch(f"{MOD}.os.cpu_count", return_value=None),
|
|
):
|
|
assert reindex.load_per_core() == 3.0
|
|
|
|
|
|
class TestBlockingReason:
|
|
@patch(f"{MOD}.is_server_running", return_value=True)
|
|
def test_server_running_blocks(self, mock_running: MagicMock) -> None:
|
|
reason = reindex.blocking_reason(Path("/store"))
|
|
assert reason is not None
|
|
assert "already using this store" in reason
|
|
|
|
@patch(f"{MOD}.load_per_core", return_value=4.0)
|
|
@patch(f"{MOD}.is_server_running", return_value=False)
|
|
def test_busy_machine_blocks(
|
|
self,
|
|
mock_running: MagicMock,
|
|
mock_load: MagicMock,
|
|
) -> None:
|
|
reason = reindex.blocking_reason(Path("/store"))
|
|
assert reason is not None
|
|
assert "machine is busy" in reason
|
|
|
|
@patch(f"{MOD}.load_per_core", return_value=4.0)
|
|
@patch(f"{MOD}.is_server_running", return_value=False)
|
|
def test_ignore_load_overrides(
|
|
self,
|
|
mock_running: MagicMock,
|
|
mock_load: MagicMock,
|
|
) -> None:
|
|
assert reindex.blocking_reason(Path("/store"), ignore_load=True) is None
|
|
|
|
@patch(f"{MOD}.load_per_core", return_value=0.2)
|
|
@patch(f"{MOD}.is_server_running", return_value=False)
|
|
def test_idle_machine_is_clear(
|
|
self,
|
|
mock_running: MagicMock,
|
|
mock_load: MagicMock,
|
|
) -> None:
|
|
assert reindex.blocking_reason(Path("/store")) is None
|
|
|
|
|
|
class TestRunReindex:
|
|
def test_missing_interpreter(self) -> None:
|
|
with patch(f"{MOD}.KNOWLEDGE_RAG_PYTHON", Path("/nonexistent/python")):
|
|
assert reindex.run_reindex(Path("/store")) == 127
|
|
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_passes_store_and_repo_root(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = subprocess.CompletedProcess([], returncode=0)
|
|
with (
|
|
patch.object(Path, "exists", return_value=True),
|
|
patch.dict(f"{MOD}.os.environ", {}, clear=True),
|
|
):
|
|
assert reindex.run_reindex(Path("/store")) == 0
|
|
|
|
env = mock_run.call_args.kwargs["env"]
|
|
assert env["KNOWLEDGE_RAG_DIR"] == "/store"
|
|
assert env["PYTHONPATH"].endswith("testsAndMisc")
|
|
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_prepends_to_existing_pythonpath(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = subprocess.CompletedProcess([], returncode=3)
|
|
with (
|
|
patch.object(Path, "exists", return_value=True),
|
|
patch.dict(f"{MOD}.os.environ", {"PYTHONPATH": "/pre"}, clear=True),
|
|
):
|
|
assert reindex.run_reindex(Path("/store")) == 3
|
|
|
|
assert mock_run.call_args.kwargs["env"]["PYTHONPATH"].endswith(":/pre")
|