Refactor architecture to use standalone scripts in ~/.local/bin/ instead of ~/.bashrc
This commit is contained in:
1033
claude_setup.sh
1033
claude_setup.sh
File diff suppressed because it is too large
Load Diff
@@ -1,135 +0,0 @@
|
|||||||
import os
|
|
||||||
import pty
|
|
||||||
import time
|
|
||||||
import subprocess
|
|
||||||
import signal
|
|
||||||
|
|
||||||
def run_test():
|
|
||||||
# Create pseudo-terminal
|
|
||||||
master, slave = pty.openpty()
|
|
||||||
|
|
||||||
# Start bash in interactive mode
|
|
||||||
p = subprocess.Popen(
|
|
||||||
['bash', '-i'],
|
|
||||||
stdin=slave,
|
|
||||||
stdout=slave,
|
|
||||||
stderr=slave,
|
|
||||||
close_fds=True,
|
|
||||||
preexec_fn=os.setsid # Create a new session/process group
|
|
||||||
)
|
|
||||||
|
|
||||||
# Close slave end in parent
|
|
||||||
os.close(slave)
|
|
||||||
|
|
||||||
# Set master to non-blocking
|
|
||||||
os.set_blocking(master, False)
|
|
||||||
|
|
||||||
# Helper to write to bash
|
|
||||||
def write_to_bash(cmd):
|
|
||||||
os.write(master, cmd.encode())
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# Define function in the interactive shell
|
|
||||||
func_def = """
|
|
||||||
my_func() {
|
|
||||||
sleep 100 &
|
|
||||||
local bg_pid=$!
|
|
||||||
echo "ACTUAL_BG_PID:$bg_pid"
|
|
||||||
sleep 10
|
|
||||||
echo "RUNNING CLEANUP"
|
|
||||||
kill $bg_pid 2>/dev/null
|
|
||||||
wait $bg_pid 2>/dev/null
|
|
||||||
echo "CLEANUP DONE"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
write_to_bash(func_def + "\n")
|
|
||||||
|
|
||||||
# Run the function
|
|
||||||
write_to_bash("my_func\n")
|
|
||||||
|
|
||||||
# Read output to wait for "ACTUAL_BG_PID:"
|
|
||||||
output = b""
|
|
||||||
start_time = time.time()
|
|
||||||
bg_pid = None
|
|
||||||
while time.time() - start_time < 5:
|
|
||||||
try:
|
|
||||||
r = os.read(master, 1024)
|
|
||||||
if r:
|
|
||||||
output += r
|
|
||||||
except BlockingIOError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if b"ACTUAL_BG_PID:" in output:
|
|
||||||
parts = output.split(b"ACTUAL_BG_PID:")
|
|
||||||
if len(parts) > 1:
|
|
||||||
# Get the part after ACTUAL_BG_PID:
|
|
||||||
rest = parts[-1].strip()
|
|
||||||
# Split by newline or carriage return or spaces
|
|
||||||
potential_pid = rest.split(b"\r")[0].split(b"\n")[0].split(b" ")[0].decode().strip()
|
|
||||||
if potential_pid.isdigit():
|
|
||||||
bg_pid = int(potential_pid)
|
|
||||||
break
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
print("Found BG PID:", bg_pid)
|
|
||||||
print("Output so far:", output.decode('utf-8', errors='ignore'))
|
|
||||||
|
|
||||||
if bg_pid is None:
|
|
||||||
print("Error: Could not find BG PID.")
|
|
||||||
p.terminate()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Wait a bit to ensure it is sleeping
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# In a terminal, Ctrl+C sends SIGINT to the foreground process group.
|
|
||||||
# The shell sets the foreground process group of the terminal to the active job.
|
|
||||||
# Let's get the foreground process group of the master terminal.
|
|
||||||
# We can use os.tcgetpgrp(master) to get the process group currently in the foreground!
|
|
||||||
try:
|
|
||||||
fore_pgid = os.tcgetpgrp(master)
|
|
||||||
print(f"Foreground process group of tty: {fore_pgid}")
|
|
||||||
# Send SIGINT to the foreground process group
|
|
||||||
os.killpg(fore_pgid, signal.SIGINT)
|
|
||||||
except Exception as e:
|
|
||||||
print("Failed to get foreground pgid via tcgetpgrp or killpg:", e)
|
|
||||||
# Fallback: kill the bash process group
|
|
||||||
pgid = os.getpgid(p.pid)
|
|
||||||
print(f"Fallback: sending SIGINT to bash process group {pgid}")
|
|
||||||
os.killpg(pgid, signal.SIGINT)
|
|
||||||
|
|
||||||
# Give it some time to process
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Let's read the remaining output to see if "RUNNING CLEANUP" was printed
|
|
||||||
try:
|
|
||||||
time.sleep(1)
|
|
||||||
remaining = b""
|
|
||||||
start_time = time.time()
|
|
||||||
while time.time() - start_time < 2:
|
|
||||||
try:
|
|
||||||
r = os.read(master, 4096)
|
|
||||||
if r:
|
|
||||||
remaining += r
|
|
||||||
except BlockingIOError:
|
|
||||||
pass
|
|
||||||
time.sleep(0.1)
|
|
||||||
print("Remaining output:", remaining.decode('utf-8', errors='ignore'))
|
|
||||||
except Exception as e:
|
|
||||||
print("Read error or no more output:", e)
|
|
||||||
|
|
||||||
# Check if bg_pid is still running
|
|
||||||
try:
|
|
||||||
os.kill(bg_pid, 0)
|
|
||||||
print(f"VERDICT: Process {bg_pid} is STILL RUNNING! It was orphaned!")
|
|
||||||
# Clean it up
|
|
||||||
os.kill(bg_pid, signal.SIGKILL)
|
|
||||||
except OSError:
|
|
||||||
print(f"VERDICT: Process {bg_pid} is NOT running. Cleanup worked!")
|
|
||||||
|
|
||||||
# Clean up bash
|
|
||||||
p.terminate()
|
|
||||||
p.wait()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_test()
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
test_func() {
|
|
||||||
echo "Starting background process..."
|
|
||||||
sleep 100 &
|
|
||||||
local pid=$!
|
|
||||||
echo "Background PID: $pid"
|
|
||||||
|
|
||||||
echo "Starting foreground sleep (simulate claude)..."
|
|
||||||
sleep 10
|
|
||||||
|
|
||||||
echo "Foreground sleep finished. Cleaning up background process $pid..."
|
|
||||||
kill "$pid"
|
|
||||||
wait "$pid" 2>/dev/null
|
|
||||||
echo "Cleanup done."
|
|
||||||
}
|
|
||||||
|
|
||||||
test_func
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# tests/test_fixes.sh — unit tests for code-review fixes in claude_setup.sh
|
# tests/test_fixes.sh — unit tests for code-review fixes in claude_setup.sh
|
||||||
# Run: bash tests/test_fixes.sh
|
# Run: bash tests/test_fixes.sh
|
||||||
# Requires: bash 4+, curl (can be mocked via PATH)
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -11,56 +10,28 @@ PASS=0; FAIL=0
|
|||||||
ok() { echo "[PASS] $1"; PASS=$((PASS+1)); }
|
ok() { echo "[PASS] $1"; PASS=$((PASS+1)); }
|
||||||
fail() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); }
|
fail() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); }
|
||||||
|
|
||||||
# ── helpers ──────────────────────────────────────────────────────────────────
|
# Extract sections
|
||||||
|
GPT_SECTION=$(awk '/^cat > "\$BIN_DIR\/claude_gpt"/,/^GPTEOF/' "$SCRIPT")
|
||||||
|
GEMINI_SECTION=$(awk '/^cat > "\$BIN_DIR\/claude_gemini"/,/^GEMINIEOF/' "$SCRIPT")
|
||||||
|
|
||||||
# Source only the heredoc functions, not the setup-script body.
|
# ── Fix 2: trap EXIT kills proxy ──────────────────────────────────────────────
|
||||||
# The heredoc begins after "cat >> \"$BASHRC\" << 'BASHEOF'" and contains
|
test_fix2_trap_exit() {
|
||||||
# all the launcher functions; we extract and source that block directly.
|
if echo "$GPT_SECTION" | grep -q "trap .* EXIT"; then
|
||||||
_source_functions() {
|
ok "Fix2: trap EXIT for proxy cleanup present in claude_gpt"
|
||||||
local tmp
|
|
||||||
tmp=$(mktemp)
|
|
||||||
awk '/^# === CLAUDE LAUNCHER ===/,/^# === END CLAUDE LAUNCHER ===/' "$SCRIPT" > "$tmp"
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
source "$tmp"
|
|
||||||
rm -f "$tmp"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Fix 1: ANTHROPIC_API_KEY exported in manual-key path ────────────────────
|
|
||||||
test_fix1_export_api_key() {
|
|
||||||
# Extract the [Kk] branch from the script and confirm `export` keyword exists
|
|
||||||
local kk_block
|
|
||||||
kk_block=$(awk '/\[Kk\]/,/\[Ll\]/' "$SCRIPT" | grep 'ANTHROPIC_API_KEY')
|
|
||||||
if echo "$kk_block" | grep -q 'export ANTHROPIC_API_KEY'; then
|
|
||||||
ok "Fix1: [K] branch uses 'export ANTHROPIC_API_KEY'"
|
|
||||||
else
|
else
|
||||||
fail "Fix1: [K] branch missing 'export' for ANTHROPIC_API_KEY"
|
fail "Fix2: trap EXIT for proxy cleanup missing in claude_gpt"
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Fix 2: trap RETURN kills proxy on early exit ─────────────────────────────
|
|
||||||
test_fix2_trap_return() {
|
|
||||||
if grep -q "trap '.*kill.*proxy_pid.*' RETURN" "$SCRIPT"; then
|
|
||||||
ok "Fix2: trap RETURN for proxy cleanup present in claude_gpt"
|
|
||||||
else
|
|
||||||
fail "Fix2: trap RETURN for proxy cleanup missing in claude_gpt"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Fix 3: readiness loop replaces bare sleep 1 ──────────────────────────────
|
# ── Fix 3: readiness loop replaces bare sleep 1 ──────────────────────────────
|
||||||
test_fix3_readiness_loop() {
|
test_fix3_readiness_loop() {
|
||||||
# The old code had just "sleep 1" after starting proxy; now there's a while loop
|
if echo "$GPT_SECTION" | grep -q 'while \[ \$_i -lt'; then
|
||||||
local gpt_section
|
|
||||||
gpt_section=$(awk '/^claude_gpt\(\)/,/^}/' "$SCRIPT")
|
|
||||||
|
|
||||||
if echo "$gpt_section" | grep -q 'while \[ \$_i -lt'; then
|
|
||||||
ok "Fix3: readiness poll loop present in claude_gpt proxy start"
|
ok "Fix3: readiness poll loop present in claude_gpt proxy start"
|
||||||
else
|
else
|
||||||
fail "Fix3: readiness poll loop missing in claude_gpt"
|
fail "Fix3: readiness poll loop missing in claude_gpt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Confirm bare "sleep 1" is gone from the proxy-start section (the loop contains sleep 1 but in context)
|
if echo "$GPT_SECTION" | grep -qP 'proxy_pid=\$!\n\s+sleep 1\n\s+fi'; then
|
||||||
# The old pattern was: proxy_pid=$!\n sleep 1\n fi
|
|
||||||
if echo "$gpt_section" | grep -qP 'proxy_pid=\$!\n\s+sleep 1\n\s+fi'; then
|
|
||||||
fail "Fix3: bare 'sleep 1' still present right after proxy_pid=\$!"
|
fail "Fix3: bare 'sleep 1' still present right after proxy_pid=\$!"
|
||||||
else
|
else
|
||||||
ok "Fix3: bare 'sleep 1; fi' pattern removed"
|
ok "Fix3: bare 'sleep 1; fi' pattern removed"
|
||||||
@@ -69,15 +40,14 @@ test_fix3_readiness_loop() {
|
|||||||
|
|
||||||
# ── Fix 3b: curl exit-7 logic correct ────────────────────────────────────────
|
# ── Fix 3b: curl exit-7 logic correct ────────────────────────────────────────
|
||||||
test_fix3b_exit7_logic() {
|
test_fix3b_exit7_logic() {
|
||||||
# Verify the comment and condition are as expected
|
if echo "$GPT_SECTION" | grep -q 'exit 7 = connection refused'; then
|
||||||
if grep -q 'exit 7 = connection refused' "$SCRIPT"; then
|
|
||||||
ok "Fix3b: exit-7 comment present (connection refused check documented)"
|
ok "Fix3b: exit-7 comment present (connection refused check documented)"
|
||||||
else
|
else
|
||||||
fail "Fix3b: exit-7 comment missing"
|
fail "Fix3b: exit-7 comment missing"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -q '_ce.*-ne 7' "$SCRIPT"; then
|
if echo "$GPT_SECTION" | grep -q '\[ "\$?" -ne 7 \]'; then
|
||||||
ok "Fix3b: [ \$_ce -ne 7 ] break condition present"
|
ok "Fix3b: [ \$? -ne 7 ] break condition present"
|
||||||
else
|
else
|
||||||
fail "Fix3b: exit-7 break condition missing"
|
fail "Fix3b: exit-7 break condition missing"
|
||||||
fi
|
fi
|
||||||
@@ -85,27 +55,51 @@ test_fix3b_exit7_logic() {
|
|||||||
|
|
||||||
# ── Fix 4: re-validate after claude_gpt reauth ───────────────────────────────
|
# ── Fix 4: re-validate after claude_gpt reauth ───────────────────────────────
|
||||||
test_fix4_gpt_revalidate() {
|
test_fix4_gpt_revalidate() {
|
||||||
local gpt_section
|
if echo "$GPT_SECTION" | grep -q '_claude_test_api.*http://localhost:18765'; then
|
||||||
gpt_section=$(awk '/^claude_gpt\(\)/,/^}/' "$SCRIPT")
|
ok "Fix4: _claude_test_api called in claude_gpt"
|
||||||
|
|
||||||
if echo "$gpt_section" | grep -q 'Проверяю авторизацию после входа'; then
|
|
||||||
ok "Fix4: re-validate after codex auth login present in claude_gpt"
|
|
||||||
else
|
else
|
||||||
fail "Fix4: re-validate after codex auth login missing in claude_gpt"
|
fail "Fix4: _claude_test_api missing in claude_gpt"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Fix 5: re-validate after claude_gemini reauth (both 401 and 429) ─────────
|
# ── Fix 5: re-validate after claude_gemini reauth (both 401 and 429) ─────────
|
||||||
test_fix5_gemini_revalidate() {
|
test_fix5_gemini_revalidate() {
|
||||||
local gemini_section
|
|
||||||
gemini_section=$(awk '/^claude_gemini\(\)/,/^}/' "$SCRIPT")
|
|
||||||
|
|
||||||
local count
|
local count
|
||||||
count=$(echo "$gemini_section" | grep -c 'Проверяю авторизацию Gemini' || true)
|
count=$(echo "$GEMINI_SECTION" | grep -c '_claude_test_api' || true)
|
||||||
if [ "$count" -ge 2 ]; then
|
if [ "$count" -ge 2 ]; then
|
||||||
ok "Fix5: re-validate after gemini reauth present in both 401/403 and 429 branches ($count occurrences)"
|
ok "Fix5: _claude_test_api present in gemini reauth flow ($count occurrences)"
|
||||||
else
|
else
|
||||||
fail "Fix5: re-validate after gemini reauth missing or only in one branch (found $count)"
|
fail "Fix5: _claude_test_api missing or only in one branch (found $count)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Fix 7: trap quotes $TMP correctly ────────────────────────────────────────
|
||||||
|
test_fix7_trap_tmp() {
|
||||||
|
if grep -q "trap 'rm -rf \"\$TMP\"' EXIT" "$SCRIPT"; then
|
||||||
|
ok "Fix7: trap uses single quotes with quoted \"\$TMP\""
|
||||||
|
else
|
||||||
|
fail "Fix7: trap still uses double quotes or \$TMP still unquoted at execution"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── bash syntax of the whole script ─────────────────────────────────────────
|
||||||
|
test_script_syntax() {
|
||||||
|
if bash -n "$SCRIPT" 2>&1; then
|
||||||
|
ok "syntax: claude_setup.sh passes 'bash -n'"
|
||||||
|
else
|
||||||
|
fail "syntax: claude_setup.sh has syntax errors"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── run all tests ─────────────────────────────────────────────────────────────
|
||||||
|
test_script_syntax
|
||||||
|
test_fix2_trap_exit
|
||||||
|
test_fix3_readiness_loop
|
||||||
|
test_fix3b_exit7_logic
|
||||||
|
test_fix4_gpt_revalidate
|
||||||
|
test_fix5_gemini_revalidate
|
||||||
|
test_fix7_trap_tmp
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Results: $PASS passed, $FAIL failed"
|
||||||
|
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user