0b5043a9f3
gates / consistency-and-conventions (push) Successful in 26s
User report after the llamacpp app install: 'installed llama.cpp unknown', valid flags rejected (randomly per run), 'Model not found' for the HF downloader's own layout, and a systemd user-bus failure over SSH. Detective (real b10822 binary, FACT) found four independent causes: - version: llama-server --version prints to STDERR; detect_llama_version's 2>/dev/null swallowed it -> always 'unknown'. Now captures 2>&1 + accepts semver/build tokens (incl. build 1.2.3 edge) - validation: printf|grep -q under pipefail -> SIGPIPE rc=141 race randomly rejected flags present in the 59 KB --help. Now pipe-less grep (no race); 20x determinism regression test - model resolution: resolve_model accepted files only, but the HF downloader creates <models>/<repo>/file.gguf dirs. Now expands a dir with exactly one *.gguf (never silently picks; multi-gguf lists + errs) - port: llama.cpp default 8080 vs tool/adapter 8088; validation reliability means --port is now always pinned in the unit - user bus: headless/SSH sessions lack XDG_RUNTIME_DIR -> ensure_user_bus in lib/common.sh pre-flights all three systemctl --user tools with remediation text; pos ai server --no-unit direct-run escape hatch (pidfile) for boxes with no bus - find_llamacpp narrowed to llama-server/llama-server-cuda (bare 'server' fallback hazard); installer post-install sanity (version+help execute, symlink targets resolve) Architect decisions DQ1-DQ6 recorded. Tester: 4 new regression files (version-from-stderr, 25x flag-validation determinism, model dir expansion, bus pre-flight + E2E) + 3 fixture updates; suite 16 files / 269 checks. Verified: make gen idempotent; make check OK; make lint 0 FAIL, 0 WARN; make test 269/269 (~49s); bash -n clean; git diff --check clean.
135 lines
5.8 KiB
Bash
135 lines
5.8 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# t-ai-server-model.sh — F3: `resolve_model` directory expansion regression.
|
|
# The HF downloader writes weights as `$HF_DOWNLOAD_DIR/<repo-slug>/<file>.gguf`,
|
|
# but the old resolve_model accepted only FILE paths, so the user's
|
|
# `pos ai server start Qwen-Qwen3-1.7B-GGUF` failed with "Model not found".
|
|
# F3 adds: dir with exactly one .gguf → resolve; multiple → err listing each
|
|
# as `<dir>/<file>` + "pick one"; zero → fall through to not-found. It also
|
|
# accepts the `<name>/<file>.gguf` slug/file form. Flat-file + absolute paths
|
|
# must keep working (backward compatibility).
|
|
#
|
|
# Unit level: call the REAL shipped resolve_model / resolve_gguf_in_dir with a
|
|
# sandbox HF_DOWNLOAD_DIR. Integration level: `start` DRY_RUN resolves the
|
|
# user's real dir name.
|
|
|
|
# extract_fn <source-file> <fnname> — print one brace-delimited function body.
|
|
extract_fn() {
|
|
local file="$1" fn="$2"
|
|
awk -v fn="$fn" '
|
|
BEGIN { found=0; depth=0 }
|
|
{
|
|
if (!found && $0 ~ ("^" fn "\\(\\)")) { found=1; depth=0 }
|
|
if (found) {
|
|
n_open = gsub(/\{/, "{")
|
|
n_close = gsub(/\}/, "}")
|
|
depth = depth + n_open - n_close
|
|
print
|
|
if (depth <= 0) exit
|
|
}
|
|
}
|
|
' "$file"
|
|
}
|
|
|
|
run_test() {
|
|
source "$ROOT/lib/common.sh" # err/log for resolve_model
|
|
|
|
local sandbox models aid
|
|
sandbox="$(mksandbox ai-server-model)"
|
|
models="$sandbox/models"
|
|
mkdir -p "$models"
|
|
aid="$ROOT/bin/pos-ai-server"
|
|
|
|
# Extract the REAL model-resolution functions.
|
|
local fn_file="$sandbox/fns.sh" com="$ROOT/lib/common.sh"
|
|
{
|
|
extract_fn "$aid" resolve_gguf_in_dir
|
|
extract_fn "$aid" resolve_model
|
|
} > "$fn_file"
|
|
|
|
# ── Fixtures (mirror the user's real layout) ──
|
|
# single-gguf dir (the reported case): Qwen-Qwen3-1.7B-GGUF/Qwen3-1.7B-Q8_0.gguf
|
|
mkdir -p "$models/Qwen-Qwen3-1.7B-GGUF"
|
|
touch "$models/Qwen-Qwen3-1.7B-GGUF/Qwen3-1.7B-Q8_0.gguf"
|
|
# multi-gguf dir
|
|
mkdir -p "$models/multi"
|
|
touch "$models/multi/q1.gguf" "$models/multi/q2.gguf"
|
|
# empty dir
|
|
mkdir -p "$models/empty"
|
|
# flat-file legacy form
|
|
touch "$models/flat.gguf"
|
|
# absolute file
|
|
touch "$models/abs.gguf"
|
|
|
|
# ── F3 unit cases (direct function calls) ──
|
|
# 1. single-gguf dir resolves to the inner file
|
|
test_run_env HF_DOWNLOAD_DIR="$models" -- bash -c "source '$com'; source '$fn_file'; resolve_model 'Qwen-Qwen3-1.7B-GGUF'"
|
|
check_rc "F3 single-gguf dir resolves (rc 0)" 0 "$TR_RC"
|
|
check_eq "F3 resolves to inner gguf" "$models/Qwen-Qwen3-1.7B-GGUF/Qwen3-1.7B-Q8_0.gguf" "$TR_OUT"
|
|
|
|
# 2. multi-gguf dir errors listing each as <dir>/<file> + "pick one"
|
|
test_run_env HF_DOWNLOAD_DIR="$models" -- bash -c "source '$com'; source '$fn_file'; resolve_model 'multi'"
|
|
check_rc "F3 multi-gguf dir errors (rc 1)" 1 "$TR_RC"
|
|
check_contains "F3 multi lists q1" "multi/q1.gguf" "$TR_OUT"
|
|
check_contains "F3 multi lists q2" "multi/q2.gguf" "$TR_OUT"
|
|
check_contains "F3 multi says pick one" "pick one" "$TR_OUT"
|
|
|
|
# 3. zero-gguf dir → falls through to today's not-found
|
|
test_run_env HF_DOWNLOAD_DIR="$models" -- bash -c "source '$com'; source '$fn_file'; resolve_model 'empty'"
|
|
check_rc "F3 empty dir errors (rc 1)" 1 "$TR_RC"
|
|
check_contains "F3 empty dir -> Model not found" "Model not found" "$TR_OUT"
|
|
|
|
# 4. slug/file form <name>/<file>.gguf resolves
|
|
test_run_env HF_DOWNLOAD_DIR="$models" -- bash -c "source '$com'; source '$fn_file'; resolve_model 'Qwen-Qwen3-1.7B-GGUF/Qwen3-1.7B-Q8_0.gguf'"
|
|
check_rc "F3 slug/file form resolves (rc 0)" 0 "$TR_RC"
|
|
check_eq "F3 slug/file resolves to inner gguf" "$models/Qwen-Qwen3-1.7B-GGUF/Qwen3-1.7B-Q8_0.gguf" "$TR_OUT"
|
|
|
|
# 5. flat-file legacy form still works
|
|
test_run_env HF_DOWNLOAD_DIR="$models" -- bash -c "source '$com'; source '$fn_file'; resolve_model 'flat.gguf'"
|
|
check_rc "F3 flat file resolves (rc 0)" 0 "$TR_RC"
|
|
check_eq "F3 flat file stays flat (backward compat)" "$models/flat.gguf" "$TR_OUT"
|
|
|
|
# 6. absolute file path still works
|
|
test_run_env HF_DOWNLOAD_DIR="$models" -- bash -c "source '$com'; source '$fn_file'; resolve_model '$models/abs.gguf'"
|
|
check_rc "F3 absolute file resolves (rc 0)" 0 "$TR_RC"
|
|
check_eq "F3 absolute file unchanged" "$models/abs.gguf" "$TR_OUT"
|
|
|
|
# ── F3 integration: `start <dir>` DRY_RUN resolves through the full CLI ──
|
|
local stubs="$sandbox/stubs"
|
|
mkdir -p "$stubs"
|
|
cat > "$stubs/llama-server" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
case "$1" in
|
|
--version) printf 'version: 0.4.0-dev (build 10822, commit c457e3bf7)\n' >&2 ;;
|
|
--help)
|
|
cat <<'HELP'
|
|
usage: llama-server [options]
|
|
options:
|
|
--host <addr> bind address
|
|
--port <port> server port
|
|
--n-gpu-layers <n> layers to offload
|
|
--ctx-size <n> context size
|
|
--threads <n> cpu threads
|
|
HELP
|
|
;;
|
|
esac
|
|
STUB
|
|
printf '#!/usr/bin/env bash\nexit 1\n' > "$stubs/nvidia-smi"
|
|
chmod +x "$stubs/llama-server" "$stubs/nvidia-smi"
|
|
|
|
local server="$ROOT/bin/pos-ai-server"
|
|
test_run_env PATH="$stubs:/usr/bin:/bin" DRY_RUN=1 \
|
|
HF_DOWNLOAD_DIR="$models" USER_SYSTEMD_DIR="$sandbox/userunits" -- \
|
|
"$server" start Qwen-Qwen3-1.7B-GGUF --no-unit
|
|
check_rc "F3 CLI start with dir arg exits 0" 0 "$TR_RC"
|
|
check_contains "F3 CLI resolves dir to inner file" \
|
|
"-m \"$models/Qwen-Qwen3-1.7B-GGUF/Qwen3-1.7B-Q8_0.gguf\"" "$TR_OUT"
|
|
|
|
# multi-gguf via CLI → clean err naming pick-one, rc 1
|
|
test_run_env PATH="$stubs:/usr/bin:/bin" DRY_RUN=1 \
|
|
HF_DOWNLOAD_DIR="$models" USER_SYSTEMD_DIR="$sandbox/userunits" -- \
|
|
"$server" start multi --no-unit
|
|
check_rc "F3 CLI multi-gguf start errors (rc 1)" 1 "$TR_RC"
|
|
check_contains "F3 CLI multi-gguf says pick one" "pick one" "$TR_OUT"
|
|
}
|