diff --git a/openbot b/openbot index 2601df5..b8e8f66 100755 --- a/openbot +++ b/openbot @@ -2,6 +2,9 @@ # Start OpenBot using the repository's production-style launcher. set -Eeuo pipefail +# Set OPENBOT_REPOSITORY when this launcher is installed outside the repository. +OPENBOT_REPOSITORY="${OPENBOT_REPOSITORY:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)}" + usage() { cat >&2 <<'EOF' Usage: openbot [--root DIRECTORY] [--db-root DIRECTORY] @@ -72,11 +75,11 @@ if [[ ! -e "$db_root" ]]; then fi fi db_root="$(canonical_directory "$db_root")" +repository_root="$(canonical_directory "$(expand_path "$OPENBOT_REPOSITORY")")" # `make run` remains the canonical production-style startup path; these environment variables # provide the caller-specific paths without reimplementing backend initialization here. export WORKSPACE_ROOT="$workspace_root" export DATABASE_URL="sqlite+aiosqlite:///$db_root/openbot.db" -script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" -exec make -C "$script_dir" run +exec make -C "$repository_root" run diff --git a/scripts/test_openbot.py b/scripts/test_openbot.py index 2bdf891..9cecdd9 100755 --- a/scripts/test_openbot.py +++ b/scripts/test_openbot.py @@ -10,7 +10,7 @@ class OpenBotLauncherTests(unittest.TestCase): - def run_launcher(self, *args, cwd=None, home=None): + def run_launcher(self, *args, cwd=None, home=None, repository=None): with tempfile.TemporaryDirectory() as tmp: fake_make = Path(tmp) / "make" fake_make.write_text( @@ -25,6 +25,8 @@ def run_launcher(self, *args, cwd=None, home=None): env["PATH"] = f"{tmp}{os.pathsep}{env['PATH']}" if home is not None: env["HOME"] = str(home) + if repository is not None: + env["OPENBOT_REPOSITORY"] = str(repository) return subprocess.run( [str(SCRIPT), *args], cwd=cwd, env=env, text=True, capture_output=True, check=False ) @@ -62,6 +64,24 @@ def test_both_overrides_are_forwarded(self): self.assertIn(f"workspace={root.resolve()}", result.stdout) self.assertIn(f"database=sqlite+aiosqlite:///{db.resolve() / 'openbot.db'}", result.stdout) + def test_repository_location_is_used_from_another_working_directory(self): + with tempfile.TemporaryDirectory() as tmp, tempfile.TemporaryDirectory() as home: + caller = Path(tmp) / "caller" + repository = Path(tmp) / "repository with spaces" + caller.mkdir() + repository.mkdir() + result = self.run_launcher(cwd=caller, home=home, repository=repository) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn(f"args=-C|{repository.resolve()}|run|", result.stdout) + self.assertIn(f"workspace={caller.resolve()}", result.stdout) + + def test_repository_location_must_be_a_directory(self): + with tempfile.TemporaryDirectory() as tmp: + repository = Path(tmp) / "missing-repository" + result = self.run_launcher(cwd=tmp, repository=repository) + self.assertEqual(result.returncode, 2) + self.assertIn("does not exist", result.stderr) + def test_invalid_usage_is_clear(self): result = self.run_launcher("--root") self.assertEqual(result.returncode, 2)