mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 05:12:26 +00:00
Consolidates two reports that overlap in scope: - #476 (@senki): install.sh hardcoded `--python 3.11` but pyproject.toml declares `requires-python = ">=3.10,<3.14"`. The installer should track the project's allowed range, not pin a conservative-three-years-ago version. - #484 (@sanjayravit): install.sh crashed on hosts with no python3/python on PATH because `mark_done`, `beacon`, and `get_anon_id` all called $PY_CMD via inline Python heredocs. This PR closes both gaps with five surgical edits to install.sh (all behavior-preserving for the existing happy path; the bats tests prove it): 1. get_anon_id — replace `python3 -c uuid` with POSIX `/dev/urandom + od` + bash substring expansion. Same UUID v4 shape; works on Python-less hosts. 2. beacon — replace the 40-line Python heredoc with curl + a shell-built JSON payload. All inputs are from controlled sources (event ∈ fixed vocabulary, stage from stage_label(), numeric ids/codes from validated arithmetic, anon_id from a fresh UUID) so no general-purpose JSON escaping is needed. `|| true` is load-bearing — PostHog 5xx must never abort an install via the ERR trap. 3. mark_done — replace `python3 -c json.load+update+dump` with awk that regenerates the file from scratch. Idempotent: if the key is already marked, return early. Robust against prior format drift. wsl key is always rewritten last so a later FORCE_WSL=1 re-run correctly updates it. 4. parse_requires_python — new helper. Greps the project's requires-python field, handles inclusive (`<=3.13`) and exclusive (`<3.14`) upper bounds correctly, falls back to 3.11 if pyproject can't be parsed (the previous hardcoded value — safe under the existing 3.10-3.13 range). 5. create_venv — call parse_requires_python instead of hardcoding 3.11. The existing uv-managed-Python fallback (from #444) still kicks in if the host doesn't have the target version installed. Adversarial review caught two real bugs before commit: - HIGH: parse_requires_python's exclusive-bound regex would also match the digits after `<=` (inclusive bound) and then incorrectly subtract 1 — producing 3.12 from `<=3.13`. Fixed by checking the inclusive form first. - MEDIUM: the new "no Python on PATH" bats test silently skips symlinking `pgrep` on hosts where it isn't at /usr/bin or /bin (some minimal BusyBox configurations). Added an explicit "no matching process" fallback so start_ollama's check works. New bats coverage: - `install succeeds with no system Python on PATH (#484)` — builds a PATH that excludes python3/python and exercises the full install. Asserts state file is written and contains greppable step keys. - `mark_done is idempotent — second mark of same key doesn't duplicate` — re-runs the install and verifies install_uv appears exactly once in install-state.json. - `create_venv picks newest in requires-python range, not hardcoded 3.11 (#476)` — asserts uv was called with `--python 3.13` (the upper minor of `>=3.10,<3.14`). The git stub now includes `requires-python = ">=3.10,<3.14"` in its fake pyproject.toml so create_venv has something realistic to parse. @sanjayravit — your PR #484 motivated this consolidation; closing that one as superseded with credit. Co-authored-by: krypticmouse <herumbshandilya123@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
231 lines
8.1 KiB
Bash
Executable File
231 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
|
|
setup() {
|
|
TEST_TMPDIR=$(mktemp -d)
|
|
export FAKE_HOME="$TEST_TMPDIR/home"
|
|
mkdir -p "$FAKE_HOME"
|
|
export HOME="$FAKE_HOME"
|
|
export OPENJARVIS_HOME="$FAKE_HOME/.openjarvis"
|
|
|
|
export STUBS_DIR="$BATS_TEST_DIRNAME/stubs"
|
|
# Use a copy of stubs without uv-style real binaries that might be on the host.
|
|
# Just put our stubs first in PATH.
|
|
export PATH="$STUBS_DIR:/usr/bin:/bin"
|
|
|
|
export GIT_STUB_LOG="$TEST_TMPDIR/git.log"
|
|
export OLLAMA_STUB_LOG="$TEST_TMPDIR/ollama.log"
|
|
export UV_STUB_LOG="$TEST_TMPDIR/uv.log"
|
|
export CURL_STUB_LOG="$TEST_TMPDIR/curl.log"
|
|
: > "$GIT_STUB_LOG"
|
|
: > "$OLLAMA_STUB_LOG"
|
|
: > "$UV_STUB_LOG"
|
|
: > "$CURL_STUB_LOG"
|
|
|
|
# uv stub needs to fake creating a venv with a jarvis binary.
|
|
# Replace the stubs/uv with one that creates the venv tree on `venv` command.
|
|
# Since we don't want to mutate the real stub, override via a per-test stub dir.
|
|
export PER_TEST_STUBS="$TEST_TMPDIR/stubs"
|
|
mkdir -p "$PER_TEST_STUBS"
|
|
cat "$STUBS_DIR/uv" > "$PER_TEST_STUBS/uv"
|
|
chmod +x "$PER_TEST_STUBS/uv"
|
|
# Wrap uv to handle 'venv' specially.
|
|
cat > "$PER_TEST_STUBS/uv" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "\$@" >> "\$UV_STUB_LOG"
|
|
case "\$1" in
|
|
venv)
|
|
# uv venv [--python X.Y] <path>
|
|
# Find the path arg (last non-flag).
|
|
for a in "\$@"; do
|
|
if [[ "\$a" != --* ]] && [[ "\$a" != -* ]] && [[ "\$a" != "venv" ]] && [[ ! "\$a" =~ ^[0-9]+\\.[0-9]+\$ ]]; then
|
|
venv_path="\$a"
|
|
fi
|
|
done
|
|
if [[ -n "\$venv_path" ]]; then
|
|
mkdir -p "\$venv_path/bin"
|
|
cat > "\$venv_path/bin/jarvis" <<'EOJ'
|
|
#!/usr/bin/env bash
|
|
# fake jarvis for tests
|
|
echo "fake jarvis: \$@"
|
|
exit 0
|
|
EOJ
|
|
chmod +x "\$venv_path/bin/jarvis"
|
|
cat > "\$venv_path/bin/python" <<'EOJ'
|
|
#!/usr/bin/env bash
|
|
# fake python that prints empty for the inline embedded scripts (recommend_model, etc.)
|
|
exit 0
|
|
EOJ
|
|
chmod +x "\$venv_path/bin/python"
|
|
fi
|
|
;;
|
|
esac
|
|
exit 0
|
|
EOF
|
|
chmod +x "$PER_TEST_STUBS/uv"
|
|
|
|
# Copy other stubs, with the per-test uv first.
|
|
cp "$STUBS_DIR"/{git,curl,ollama,cargo,rustup} "$PER_TEST_STUBS/"
|
|
chmod +x "$PER_TEST_STUBS"/*
|
|
|
|
export PATH="$PER_TEST_STUBS:/usr/bin:/bin"
|
|
|
|
export SCRIPT="$BATS_TEST_DIRNAME/../../../scripts/install/install.sh"
|
|
}
|
|
|
|
teardown() {
|
|
[[ -n "${TEST_TMPDIR:-}" ]] && rm -rf "$TEST_TMPDIR"
|
|
}
|
|
|
|
@test "refuses to run as root" {
|
|
# EUID is readonly in bash, so we stub `id` to return uid 0 instead.
|
|
local root_stubs="$TEST_TMPDIR/root_stubs"
|
|
mkdir -p "$root_stubs"
|
|
cp "$PER_TEST_STUBS"/* "$root_stubs/"
|
|
cat > "$root_stubs/id" <<'IDEOF'
|
|
#!/usr/bin/env bash
|
|
if [[ "$1" == "-u" ]]; then echo 0; else echo "uid=0(root) gid=0(root) groups=0(root)"; fi
|
|
IDEOF
|
|
chmod +x "$root_stubs/id"
|
|
PATH="$root_stubs:/usr/bin:/bin" run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -ne 0 ]
|
|
echo "$output" | grep -qi "root"
|
|
}
|
|
|
|
@test "creates ~/.openjarvis directory tree" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
[ -d "$OPENJARVIS_HOME/src" ]
|
|
[ -d "$OPENJARVIS_HOME/.state" ]
|
|
[ -d "$OPENJARVIS_HOME/.scripts" ]
|
|
}
|
|
|
|
@test "writes install-state.json on success" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
[ -f "$OPENJARVIS_HOME/.state/install-state.json" ]
|
|
}
|
|
|
|
@test "calls git clone for the OpenJarvis repo" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
grep -q "clone" "$GIT_STUB_LOG"
|
|
}
|
|
|
|
@test "creates jarvis symlink in ~/.local/bin" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
[ -L "$FAKE_HOME/.local/bin/jarvis" ] || [ -f "$FAKE_HOME/.local/bin/jarvis" ]
|
|
}
|
|
|
|
@test "is idempotent — second run skips completed steps" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
: > "$GIT_STUB_LOG"
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
! grep -q "clone" "$GIT_STUB_LOG"
|
|
}
|
|
|
|
@test "detects WSL2 and writes platform note to install-state" {
|
|
OPENJARVIS_FORCE_WSL=1 run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
grep -q "wsl" "$OPENJARVIS_HOME/.state/install-state.json"
|
|
}
|
|
|
|
@test "fails loudly when git is missing" {
|
|
# Build a minimal stubs dir that has curl/uv/ollama but NO git.
|
|
# We must NOT include /usr/bin or /bin in PATH because those dirs have real
|
|
# git binaries on this system. Instead, provide only the commands the script
|
|
# needs before it reaches the `need git` check (id, cat, bash).
|
|
local no_git_stubs="$TEST_TMPDIR/no_git_stubs"
|
|
mkdir -p "$no_git_stubs"
|
|
for f in curl uv ollama cargo rustup; do
|
|
cp "$PER_TEST_STUBS/$f" "$no_git_stubs/"
|
|
chmod +x "$no_git_stubs/$f"
|
|
done
|
|
# Provide minimal system utilities (no git) using explicit symlinks.
|
|
cat > "$no_git_stubs/id" <<'IDEOF'
|
|
#!/bin/bash
|
|
echo "1000"
|
|
IDEOF
|
|
chmod +x "$no_git_stubs/id"
|
|
ln -sf /bin/cat "$no_git_stubs/cat"
|
|
ln -sf /bin/bash "$no_git_stubs/bash"
|
|
PATH="$no_git_stubs" run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -ne 0 ]
|
|
echo "$output" | grep -qi "git"
|
|
}
|
|
|
|
@test "--force re-runs completed steps" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
: > "$GIT_STUB_LOG"
|
|
run bash "$SCRIPT" --no-bg-orchestrator --force
|
|
[ "$status" -eq 0 ]
|
|
# With --force, second run re-clones (clone is in git.log again).
|
|
grep -q "clone" "$GIT_STUB_LOG"
|
|
}
|
|
|
|
@test "--minimal skips foreground model pull" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator --minimal
|
|
[ "$status" -eq 0 ]
|
|
# Ollama 'pull' should NOT have been called.
|
|
if [ -s "$OLLAMA_STUB_LOG" ]; then
|
|
! grep -q "^pull " "$OLLAMA_STUB_LOG"
|
|
fi
|
|
}
|
|
|
|
@test "install succeeds with no system Python on PATH (#484)" {
|
|
# Build a stubs dir with NO python3 / python — verifies the
|
|
# bootstrap shims (get_anon_id, beacon, mark_done) are Python-free.
|
|
local no_py_stubs="$TEST_TMPDIR/no_py_stubs"
|
|
mkdir -p "$no_py_stubs"
|
|
for f in git curl uv ollama cargo rustup; do
|
|
cp "$PER_TEST_STUBS/$f" "$no_py_stubs/"
|
|
chmod +x "$no_py_stubs/$f"
|
|
done
|
|
# Provide minimal core utilities the shims need (od, sed, awk, tr, grep).
|
|
for util in id cat bash sh head tail awk sed grep od tr cut mv rm mkdir cp chmod ln test pgrep date sleep ps; do
|
|
if [[ -x "/usr/bin/$util" ]]; then
|
|
ln -sf "/usr/bin/$util" "$no_py_stubs/$util"
|
|
elif [[ -x "/bin/$util" ]]; then
|
|
ln -sf "/bin/$util" "$no_py_stubs/$util"
|
|
fi
|
|
done
|
|
# pgrep may be absent on minimal BusyBox/Alpine hosts; stub it as
|
|
# "no matching process" so start_ollama's existing-daemon check
|
|
# falls through to the spawn path (which the ollama stub handles).
|
|
if [[ ! -x "$no_py_stubs/pgrep" ]]; then
|
|
printf '#!/bin/sh\nexit 1\n' > "$no_py_stubs/pgrep"
|
|
chmod +x "$no_py_stubs/pgrep"
|
|
fi
|
|
PATH="$no_py_stubs" run bash "$SCRIPT" --no-bg-orchestrator --minimal
|
|
[ "$status" -eq 0 ]
|
|
# State file was still written (mark_done is Python-free).
|
|
[ -f "$OPENJARVIS_HOME/.state/install-state.json" ]
|
|
# And it still has valid-looking JSON for state_done's grep.
|
|
grep -q '"install_uv":' "$OPENJARVIS_HOME/.state/install-state.json"
|
|
}
|
|
|
|
@test "mark_done is idempotent — second mark of same key doesn't duplicate" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
# Capture first state file
|
|
local first_count
|
|
first_count="$(grep -c '"install_uv":' "$OPENJARVIS_HOME/.state/install-state.json")"
|
|
[ "$first_count" -eq 1 ]
|
|
# Second run reuses state — keys should not duplicate even if some path re-marks.
|
|
run bash "$SCRIPT" --no-bg-orchestrator
|
|
[ "$status" -eq 0 ]
|
|
local second_count
|
|
second_count="$(grep -c '"install_uv":' "$OPENJARVIS_HOME/.state/install-state.json")"
|
|
[ "$second_count" -eq 1 ]
|
|
}
|
|
|
|
@test "create_venv picks newest in requires-python range, not hardcoded 3.11 (#476)" {
|
|
run bash "$SCRIPT" --no-bg-orchestrator --minimal
|
|
[ "$status" -eq 0 ]
|
|
# uv stub logs every invocation. We should see `venv --python 3.13 ...`
|
|
# (the upper bound of >=3.10,<3.14 is 3.14, so the highest usable is 3.13).
|
|
grep -q '^venv --python 3.13' "$UV_STUB_LOG"
|
|
}
|