mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 00:03:01 +03:00
perf(tui): skip redundant dependency installs
This commit is contained in:
@@ -67,6 +67,8 @@ _TUI_RELEASE_LIMITS = {
|
||||
_TUI_DETACH_EXIT_CODE = 90
|
||||
_GATEWAY_READY_TIMEOUT_S = 20.0
|
||||
_GATEWAY_READY_POLL_S = 0.1
|
||||
_TUI_DEPENDENCY_METADATA = ("package.json", "bun.lock")
|
||||
_TUI_DEPENDENCY_CACHE = ".nanobot-install.sha256"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -224,6 +226,21 @@ def _tui_source_dir(project_root: Path) -> Path | None:
|
||||
|
||||
def _resolve_source_tui_command(source_dir: Path, bun: str) -> list[str]:
|
||||
dependency = source_dir / "node_modules" / "@opentui" / "core"
|
||||
cache = source_dir / "node_modules" / _TUI_DEPENDENCY_CACHE
|
||||
fingerprint = _tui_dependency_fingerprint(source_dir)
|
||||
if dependency.is_dir() and fingerprint is not None:
|
||||
try:
|
||||
if cache.read_text(encoding="ascii") == f"{fingerprint}\n":
|
||||
return _source_tui_command(source_dir, bun)
|
||||
except (OSError, UnicodeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
cache.unlink(missing_ok=True)
|
||||
except OSError as exc:
|
||||
raise TuiUnavailableError(
|
||||
f"could not prepare the TUI dependency install: {exc}"
|
||||
) from exc
|
||||
try:
|
||||
install = subprocess.run(
|
||||
[bun, "install", "--frozen-lockfile"],
|
||||
@@ -238,6 +255,37 @@ def _resolve_source_tui_command(source_dir: Path, bun: str) -> list[str]:
|
||||
detail = (install.stderr or install.stdout).strip().splitlines()
|
||||
suffix = f": {detail[-1]}" if detail else ""
|
||||
raise TuiUnavailableError(f"could not install TUI dependencies{suffix}")
|
||||
|
||||
current_fingerprint = _tui_dependency_fingerprint(source_dir)
|
||||
if fingerprint is not None and current_fingerprint == fingerprint:
|
||||
pending = cache.with_name(f"{cache.name}.tmp-{os.getpid()}")
|
||||
try:
|
||||
pending.write_text(f"{fingerprint}\n", encoding="ascii")
|
||||
pending.replace(cache)
|
||||
except OSError:
|
||||
try:
|
||||
pending.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return _source_tui_command(source_dir, bun)
|
||||
|
||||
|
||||
def _tui_dependency_fingerprint(source_dir: Path) -> str | None:
|
||||
digest = hashlib.sha256()
|
||||
try:
|
||||
for name in _TUI_DEPENDENCY_METADATA:
|
||||
content = (source_dir / name).read_bytes()
|
||||
digest.update(name.encode())
|
||||
digest.update(b"\0")
|
||||
digest.update(len(content).to_bytes(8, "big"))
|
||||
digest.update(content)
|
||||
except OSError:
|
||||
return None
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _source_tui_command(source_dir: Path, bun: str) -> list[str]:
|
||||
executable = named_executable(
|
||||
bun,
|
||||
name="nanobot-tui",
|
||||
|
||||
@@ -51,6 +51,14 @@ def _release_archive(
|
||||
return payload, checksum
|
||||
|
||||
|
||||
def _tui_source(tmp_path: Path) -> Path:
|
||||
source_dir = tmp_path / "tui"
|
||||
source_dir.mkdir()
|
||||
(source_dir / "package.json").write_text('{"dependencies": {}}\n', encoding="utf-8")
|
||||
(source_dir / "bun.lock").write_text('lockfileVersion = 1\n', encoding="utf-8")
|
||||
return source_dir
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("session_id", "expected"),
|
||||
[
|
||||
@@ -525,18 +533,18 @@ def test_classic_options_require_an_explicit_classic_prompt(
|
||||
)
|
||||
|
||||
|
||||
def test_source_checkout_refreshes_locked_tui_dependencies(
|
||||
def test_source_checkout_installs_missing_locked_tui_dependencies(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
source_dir = tmp_path / "tui"
|
||||
source_dir.mkdir()
|
||||
(source_dir / "node_modules" / "@opentui" / "core").mkdir(parents=True)
|
||||
source_dir = _tui_source(tmp_path)
|
||||
dependency = source_dir / "node_modules" / "@opentui" / "core"
|
||||
bun = str(tmp_path / "bun")
|
||||
|
||||
def install(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
assert command == [bun, "install", "--frozen-lockfile"]
|
||||
assert kwargs["cwd"] == source_dir
|
||||
dependency.mkdir(parents=True)
|
||||
return subprocess.CompletedProcess(command, 0, "", "")
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.subprocess.run", install)
|
||||
@@ -551,6 +559,82 @@ def test_source_checkout_refreshes_locked_tui_dependencies(
|
||||
]
|
||||
|
||||
|
||||
def test_source_checkout_skips_install_when_locked_dependencies_are_current(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
source_dir = _tui_source(tmp_path)
|
||||
dependency = source_dir / "node_modules" / "@opentui" / "core"
|
||||
installs: list[list[str]] = []
|
||||
|
||||
def install(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
installs.append(command)
|
||||
dependency.mkdir(parents=True)
|
||||
return subprocess.CompletedProcess(command, 0, "", "")
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.subprocess.run", install)
|
||||
|
||||
_resolve_source_tui_command(source_dir, "bun")
|
||||
_resolve_source_tui_command(source_dir, "bun")
|
||||
|
||||
assert installs == [["bun", "install", "--frozen-lockfile"]]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("metadata_name", ["package.json", "bun.lock"])
|
||||
def test_source_checkout_refreshes_dependencies_when_metadata_changes(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
metadata_name: str,
|
||||
) -> None:
|
||||
source_dir = _tui_source(tmp_path)
|
||||
dependency = source_dir / "node_modules" / "@opentui" / "core"
|
||||
installs: list[list[str]] = []
|
||||
|
||||
def install(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
installs.append(command)
|
||||
dependency.mkdir(parents=True, exist_ok=True)
|
||||
return subprocess.CompletedProcess(command, 0, "", "")
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.subprocess.run", install)
|
||||
|
||||
_resolve_source_tui_command(source_dir, "bun")
|
||||
with (source_dir / metadata_name).open("a", encoding="utf-8") as metadata:
|
||||
metadata.write("changed\n")
|
||||
_resolve_source_tui_command(source_dir, "bun")
|
||||
|
||||
assert installs == [
|
||||
["bun", "install", "--frozen-lockfile"],
|
||||
["bun", "install", "--frozen-lockfile"],
|
||||
]
|
||||
|
||||
|
||||
def test_failed_source_dependency_install_does_not_leave_a_valid_cache(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
source_dir = _tui_source(tmp_path)
|
||||
dependency = source_dir / "node_modules" / "@opentui" / "core"
|
||||
outcomes = iter((0, 1, 0))
|
||||
installs = 0
|
||||
|
||||
def install(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
nonlocal installs
|
||||
installs += 1
|
||||
dependency.mkdir(parents=True, exist_ok=True)
|
||||
returncode = next(outcomes)
|
||||
return subprocess.CompletedProcess(command, returncode, "", "partial install")
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.subprocess.run", install)
|
||||
|
||||
_resolve_source_tui_command(source_dir, "bun")
|
||||
dependency.rmdir()
|
||||
with pytest.raises(TuiUnavailableError, match="partial install"):
|
||||
_resolve_source_tui_command(source_dir, "bun")
|
||||
_resolve_source_tui_command(source_dir, "bun")
|
||||
|
||||
assert installs == 3
|
||||
|
||||
|
||||
def test_source_checkout_fails_when_locked_dependencies_cannot_be_refreshed(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
|
||||
Reference in New Issue
Block a user