f0d78b5da1
tracker-policy-pr / check-pr (pull_request) Successful in 15s
test / integration-docker (pull_request) Successful in 41s
test / unit (pull_request) Successful in 49s
lint / lint (push) Successful in 1m7s
test / integration-firecracker (pull_request) Successful in 3m55s
test / coverage (pull_request) Failing after 25s
test / publish-infra (pull_request) Has been skipped
Addresses the review on PR #481. Self-contained wheel (review point 1): the gateway/infra/orchestrator images build from a context that must hold bot_bottle/, pyproject.toml, and the root-level Dockerfiles. Modules previously located these by walking __file__ to the repo root, so an installed wheel (package in site-packages, no repo root) passed `doctor` but failed `start`. - Add bot_bottle/resources.py: build_root() returns the repo root in a checkout (unchanged) or a staged copy from the wheel's bundled _resources/ otherwise; dockerfile()/nix_netpool_module()/ netpool_script() derive from it. - setup.py bundles the root Dockerfiles, nix module, netpool script, and pyproject.toml into bot_bottle/_resources/ at build; MANIFEST.in ships them in the sdist. - Route every _REPO_ROOT/_REPO_DIR call site (docker/macos launch, macos infra, firecracker infra_vm/infra_artifact/setup, orchestrator lifecycle/gateway) through resources. Checkout behavior is unchanged. install.sh prerequisites (review point 2): check for git when installing a git+ spec, and — before the pip fallback — that pip is usable and the interpreter isn't externally managed (PEP 668), pointing at pipx. Tests: test_resources covers checkout + staged-wheel layouts; test_wheel_install builds the wheel, installs it into an isolated venv, and asserts `doctor` runs and build_root() yields a valid context. Running `start` end-to-end still needs a Docker/KVM host (CI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.6 KiB
Python
74 lines
2.6 KiB
Python
"""Unit: install.sh bootstrapper contract.
|
|
|
|
The installer is a thin, sudo-free, idempotent bootstrapper. These are
|
|
static checks on the script text (no network / no real install) so CI can
|
|
run them anywhere: it must be executable, fail-fast, never call sudo,
|
|
create the config tree, install the package, and verify with `doctor`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
INSTALL_SH = REPO_ROOT / "install.sh"
|
|
|
|
|
|
class TestInstallScript(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.text = INSTALL_SH.read_text()
|
|
|
|
def test_exists_and_executable(self):
|
|
self.assertTrue(INSTALL_SH.is_file())
|
|
self.assertTrue(os.access(INSTALL_SH, os.X_OK), "install.sh must be executable")
|
|
|
|
def test_posix_shebang_and_failfast(self):
|
|
first = self.text.splitlines()[0]
|
|
self.assertEqual("#!/bin/sh", first)
|
|
self.assertIn("set -eu", self.text)
|
|
|
|
def test_never_uses_sudo(self):
|
|
# Only executable lines matter; the header comment may mention sudo.
|
|
code = [
|
|
ln for ln in self.text.splitlines()
|
|
if ln.strip() and not ln.lstrip().startswith("#")
|
|
]
|
|
self.assertNotIn("sudo", "\n".join(code))
|
|
|
|
def test_creates_config_tree(self):
|
|
self.assertIn(".bot-bottle/agents", self.text)
|
|
self.assertIn(".bot-bottle/bottles", self.text)
|
|
|
|
def test_installs_via_pipx_with_pip_fallback(self):
|
|
self.assertIn("pipx install", self.text)
|
|
self.assertIn("pip install --user", self.text)
|
|
|
|
def test_runs_doctor_after_install(self):
|
|
self.assertIn("doctor", self.text)
|
|
|
|
def test_install_spec_is_overridable(self):
|
|
# Tests / local installs point BOT_BOTTLE_INSTALL_SPEC at a checkout.
|
|
self.assertIn("BOT_BOTTLE_INSTALL_SPEC", self.text)
|
|
|
|
def test_requires_git_for_git_specs(self):
|
|
# A git+ / .git spec (the default) shells out to git; the script must
|
|
# gate on it rather than failing opaquely inside pipx/pip.
|
|
self.assertIn("command -v git", self.text)
|
|
self.assertIn("git+*|*.git", self.text)
|
|
|
|
def test_checks_pip_usable_before_fallback(self):
|
|
self.assertIn("python3 -m pip --version", self.text)
|
|
|
|
def test_detects_externally_managed_python(self):
|
|
# PEP 668: 'pip install --user' is blocked on externally-managed
|
|
# interpreters; the script must detect this and point at pipx.
|
|
self.assertIn("EXTERNALLY-MANAGED", self.text)
|
|
self.assertIn("pipx", self.text)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|