fix(install): исправлена установка dotfiles и добавлены тесты
- Зачем: - нужно было убрать баги install.sh и сделать регрессии воспроизводимыми. - Что: - исправлена установка через stow в HOME, миграция ~/.claude и копирование settings.json без разрушения рабочей директории. - стабилизирована WSL-логика через проверки интерактивности и защиту от дублирования boot command. - добавлен tests/test_install.sh с regression-сценариями для install.sh. - Проверка: - bash -n install.sh - bash -n tests/test_install.sh - ./tests/test_install.sh
This commit is contained in:
1 parent
e00611015a
commit
edeb8da3c2
2 files changed
+375
-35
No files matched your search
+118
-35
@@ -2,6 +2,11 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
DOTFILES_DIR="$(cd "$(dirname "$0")" && pwd)"
|
DOTFILES_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
STOW_BIN="${STOW_BIN:-stow}"
|
||||||
|
GIT_BIN="${GIT_BIN:-git}"
|
||||||
|
SUDO_BIN="${SUDO_BIN:-sudo}"
|
||||||
|
WSL_CONF_PATH="${WSL_CONF_PATH:-/etc/wsl.conf}"
|
||||||
|
INSTALL_INTERACTIVE_MODE="${INSTALL_INTERACTIVE_MODE:-auto}"
|
||||||
|
|
||||||
# --- Цвета ---
|
# --- Цвета ---
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -13,25 +18,80 @@ info() { echo -e "${GREEN}[+]${NC} $*"; }
|
|||||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||||||
error() { echo -e "${RED}[✗]${NC} $*"; }
|
error() { echo -e "${RED}[✗]${NC} $*"; }
|
||||||
|
|
||||||
|
stow_package() {
|
||||||
|
"$STOW_BIN" --target="$HOME" -v --restow "$@" 2>&1 | sed "s/^/ /"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_symlink() {
|
||||||
|
local src="$1"
|
||||||
|
local dst="$2"
|
||||||
|
|
||||||
|
if [ -L "$dst" ]; then
|
||||||
|
ln -sfnT "$src" "$dst"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$dst" ]; then
|
||||||
|
if [ -L "$dst/skills" ] && [ "$(find "$dst" -mindepth 1 -maxdepth 1 | wc -l)" -eq 1 ]; then
|
||||||
|
rm -rf "$dst"
|
||||||
|
ln -s "$src" "$dst"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$(find "$dst" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
|
||||||
|
rmdir "$dst"
|
||||||
|
ln -s "$src" "$dst"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
warn "$dst уже существует как директория, пропускаю замену"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$dst" ]; then
|
||||||
|
warn "$dst уже существует и не является симлинком, пропускаю замену"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -s "$src" "$dst"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_interactive() {
|
||||||
|
case "$INSTALL_INTERACTIVE_MODE" in
|
||||||
|
always) return 0 ;;
|
||||||
|
never) return 1 ;;
|
||||||
|
auto) [ -t 0 ] ;;
|
||||||
|
*)
|
||||||
|
warn "Неизвестное значение INSTALL_INTERACTIVE_MODE=$INSTALL_INTERACTIVE_MODE, использую auto"
|
||||||
|
[ -t 0 ]
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# --- Проверка stow ---
|
# --- Проверка stow ---
|
||||||
if ! command -v stow &>/dev/null; then
|
if ! command -v "$STOW_BIN" &>/dev/null; then
|
||||||
error "stow не найден. Установите: sudo apt install stow"
|
error "stow не найден. Установите: sudo apt install stow"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Stow пакеты ---
|
# --- Stow пакеты ---
|
||||||
PACKAGES=(bash tmux vim zed vscode agents opencode codex gemini)
|
PACKAGES=(bash tmux vim zed vscode agents opencode codex gemini)
|
||||||
|
AVAILABLE_PACKAGES=()
|
||||||
|
|
||||||
info "Применяю stow пакеты..."
|
info "Применяю stow пакеты..."
|
||||||
cd "$DOTFILES_DIR"
|
cd "$DOTFILES_DIR"
|
||||||
for pkg in "${PACKAGES[@]}"; do
|
for pkg in "${PACKAGES[@]}"; do
|
||||||
if [ -d "$pkg" ]; then
|
if [ -d "$pkg" ]; then
|
||||||
stow -v --restow "$pkg" 2>&1 | sed "s/^/ /"
|
AVAILABLE_PACKAGES+=("$pkg")
|
||||||
else
|
else
|
||||||
warn "Пакет $pkg не найден, пропускаю"
|
warn "Пакет $pkg не найден, пропускаю"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then
|
||||||
|
stow_package "${AVAILABLE_PACKAGES[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Claude Code: установка без stow ---
|
# --- Claude Code: установка без stow ---
|
||||||
# settings.json КОПИРУЕТСЯ (не линкуется), чтобы Claude Code не модифицировал
|
# settings.json КОПИРУЕТСЯ (не линкуется), чтобы Claude Code не модифицировал
|
||||||
# файл в репозитории. Локальные настройки — в settings.local.json.
|
# файл в репозитории. Локальные настройки — в settings.local.json.
|
||||||
@@ -39,18 +99,22 @@ done
|
|||||||
# Миграция: если ~/.claude — stow-симлинк, раскрываем в реальную директорию
|
# Миграция: если ~/.claude — stow-симлинк, раскрываем в реальную директорию
|
||||||
if [ -L "$HOME/.claude" ]; then
|
if [ -L "$HOME/.claude" ]; then
|
||||||
info "Миграция: ~/.claude — симлинк, преобразую в директорию..."
|
info "Миграция: ~/.claude — симлинк, преобразую в директорию..."
|
||||||
CLAUDE_STOW_DIR=$(readlink -f "$HOME/.claude")
|
CLAUDE_STOW_DIR="$(readlink -f "$HOME/.claude" 2>/dev/null || true)"
|
||||||
rm "$HOME/.claude"
|
rm "$HOME/.claude"
|
||||||
mkdir -p "$HOME/.claude"
|
mkdir -p "$HOME/.claude"
|
||||||
# Переносим рантайм-данные из stow-директории
|
if [ -n "$CLAUDE_STOW_DIR" ] && [ -d "$CLAUDE_STOW_DIR" ]; then
|
||||||
for item in "$CLAUDE_STOW_DIR"/* "$CLAUDE_STOW_DIR"/.[!.]*; do
|
# Переносим рантайм-данные из stow-директории
|
||||||
[ -e "$item" ] || continue
|
for item in "$CLAUDE_STOW_DIR"/* "$CLAUDE_STOW_DIR"/.[!.]*; do
|
||||||
base=$(basename "$item")
|
[ -e "$item" ] || continue
|
||||||
# Пропускаем git-tracked файлы — они останутся в репозитории
|
base=$(basename "$item")
|
||||||
case "$base" in .gitignore|settings.json|skills) continue ;; esac
|
# Пропускаем git-tracked файлы — они останутся в репозитории
|
||||||
mv "$item" "$HOME/.claude/" 2>/dev/null || true
|
case "$base" in .gitignore|settings.json|skills) continue ;; esac
|
||||||
done
|
mv "$item" "$HOME/.claude/" 2>/dev/null || true
|
||||||
info "Рантайм-данные перенесены в ~/.claude/"
|
done
|
||||||
|
info "Рантайм-данные перенесены в ~/.claude/"
|
||||||
|
else
|
||||||
|
warn "Не удалось прочитать источник старого симлинка ~/.claude, продолжаю без миграции данных"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$HOME/.claude"
|
mkdir -p "$HOME/.claude"
|
||||||
@@ -58,22 +122,24 @@ mkdir -p "$HOME/.claude"
|
|||||||
# Skills (симлинк)
|
# Skills (симлинк)
|
||||||
SKILLS_SRC="$DOTFILES_DIR/agents/.agents/skills"
|
SKILLS_SRC="$DOTFILES_DIR/agents/.agents/skills"
|
||||||
if [ -d "$SKILLS_SRC" ]; then
|
if [ -d "$SKILLS_SRC" ]; then
|
||||||
ln -sfn "$SKILLS_SRC" "$HOME/.claude/skills"
|
ensure_symlink "$SKILLS_SRC" "$HOME/.claude/skills"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# settings.json — копия из репозитория (не симлинк!)
|
# settings.json — копия из репозитория (не симлинк!)
|
||||||
CLAUDE_SETTINGS_SRC="$DOTFILES_DIR/claude/.claude/settings.json"
|
CLAUDE_SETTINGS_SRC="$DOTFILES_DIR/claude/.claude/settings.json"
|
||||||
CLAUDE_SETTINGS_DST="$HOME/.claude/settings.json"
|
CLAUDE_SETTINGS_DST="$HOME/.claude/settings.json"
|
||||||
if [ -f "$CLAUDE_SETTINGS_SRC" ]; then
|
if [ -f "$CLAUDE_SETTINGS_SRC" ]; then
|
||||||
# Если settings.json в репо был изменён (например, Claude Code писал через симлинк) — сбрасываем
|
|
||||||
if cd "$DOTFILES_DIR" && ! git diff --quiet claude/.claude/settings.json 2>/dev/null; then
|
|
||||||
warn "claude/.claude/settings.json имеет локальные изменения — сбрасываю"
|
|
||||||
git checkout claude/.claude/settings.json
|
|
||||||
fi
|
|
||||||
# Защищаем оригинал в репозитории
|
# Защищаем оригинал в репозитории
|
||||||
chmod 444 "$CLAUDE_SETTINGS_SRC"
|
chmod 444 "$CLAUDE_SETTINGS_SRC"
|
||||||
# Копируем в ~/.claude/ (перезаписывает при повторном запуске)
|
# Если доступен git, копируем committed-версию без изменений рабочей директории.
|
||||||
cp -f "$CLAUDE_SETTINGS_SRC" "$CLAUDE_SETTINGS_DST"
|
if "$GIT_BIN" -C "$DOTFILES_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
if ! "$GIT_BIN" -C "$DOTFILES_DIR" show "HEAD:claude/.claude/settings.json" > "$CLAUDE_SETTINGS_DST"; then
|
||||||
|
warn "Не удалось прочитать committed settings.json, копирую текущий файл из репозитория"
|
||||||
|
cp -f "$CLAUDE_SETTINGS_SRC" "$CLAUDE_SETTINGS_DST"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cp -f "$CLAUDE_SETTINGS_SRC" "$CLAUDE_SETTINGS_DST"
|
||||||
|
fi
|
||||||
chmod 644 "$CLAUDE_SETTINGS_DST"
|
chmod 644 "$CLAUDE_SETTINGS_DST"
|
||||||
info "settings.json скопирован (оригинал защищён chmod 444)"
|
info "settings.json скопирован (оригинал защищён chmod 444)"
|
||||||
fi
|
fi
|
||||||
@@ -107,23 +173,40 @@ fi
|
|||||||
# --- WSL: hproxy.local (автоматический резолв при старте) ---
|
# --- WSL: hproxy.local (автоматический резолв при старте) ---
|
||||||
if [ -n "${WSL_DISTRO_NAME:-}" ]; then
|
if [ -n "${WSL_DISTRO_NAME:-}" ]; then
|
||||||
HPROXY_SCRIPT="$DOTFILES_DIR/scripts/setup-hproxy.sh"
|
HPROXY_SCRIPT="$DOTFILES_DIR/scripts/setup-hproxy.sh"
|
||||||
if grep -q "hproxy.local" /etc/wsl.conf 2>/dev/null; then
|
WSL_BOOT_COMMAND="command = \"$HPROXY_SCRIPT\""
|
||||||
warn "/etc/wsl.conf уже содержит hproxy.local, пропускаю"
|
if grep -Fq "$WSL_BOOT_COMMAND" "$WSL_CONF_PATH" 2>/dev/null; then
|
||||||
|
warn "/etc/wsl.conf уже содержит boot command для setup-hproxy.sh, пропускаю"
|
||||||
|
elif ! is_interactive; then
|
||||||
|
warn "WSL обнаружен, но stdin не интерактивный. Пропускаю настройку /etc/wsl.conf"
|
||||||
|
info "Добавьте вручную: $WSL_BOOT_COMMAND"
|
||||||
else
|
else
|
||||||
echo ""
|
if awk '
|
||||||
info "Обнаружен WSL. Скрипт $HPROXY_SCRIPT может автоматически"
|
/^\[boot\]$/ { in_boot=1; next }
|
||||||
info "прописывать hproxy.local в /etc/hosts при каждом старте WSL (NAT mode)."
|
/^\[/ { in_boot=0 }
|
||||||
echo -ne "${YELLOW}[?]${NC} Добавить boot command в /etc/wsl.conf? [y/N] "
|
in_boot && /^[[:space:]]*command[[:space:]]*=/ { found=1 }
|
||||||
read -r answer
|
END { exit !found }
|
||||||
if [[ "$answer" =~ ^[Yy]$ ]]; then
|
' "$WSL_CONF_PATH" 2>/dev/null; then
|
||||||
# Добавляем [boot] секцию, если её нет
|
warn "/etc/wsl.conf уже содержит [boot] command. Добавьте setup-hproxy.sh вручную"
|
||||||
if ! grep -q '^\[boot\]' /etc/wsl.conf 2>/dev/null; then
|
info "Нужная строка: $WSL_BOOT_COMMAND"
|
||||||
printf '\n[boot]\n' | sudo tee -a /etc/wsl.conf > /dev/null
|
|
||||||
fi
|
|
||||||
sudo sed -i "/^\[boot\]/a command = \"$HPROXY_SCRIPT\"" /etc/wsl.conf
|
|
||||||
info "Добавлено в /etc/wsl.conf. Перезапустите WSL: wsl --shutdown"
|
|
||||||
else
|
else
|
||||||
warn "Пропущено. Можно настроить вручную — см. README.md"
|
echo ""
|
||||||
|
info "Обнаружен WSL. Скрипт $HPROXY_SCRIPT может автоматически"
|
||||||
|
info "прописывать hproxy.local в /etc/hosts при каждом старте WSL (NAT mode)."
|
||||||
|
echo -ne "${YELLOW}[?]${NC} Добавить boot command в /etc/wsl.conf? [y/N] "
|
||||||
|
answer=""
|
||||||
|
if ! read -r answer; then
|
||||||
|
answer=""
|
||||||
|
fi
|
||||||
|
if [[ "$answer" =~ ^[Yy]$ ]]; then
|
||||||
|
# Добавляем [boot] секцию, если её нет
|
||||||
|
if ! grep -q '^\[boot\]' "$WSL_CONF_PATH" 2>/dev/null; then
|
||||||
|
printf '\n[boot]\n' | "$SUDO_BIN" tee -a "$WSL_CONF_PATH" > /dev/null
|
||||||
|
fi
|
||||||
|
"$SUDO_BIN" sed -i "/^\[boot\]/a command = \"$HPROXY_SCRIPT\"" "$WSL_CONF_PATH"
|
||||||
|
info "Добавлено в /etc/wsl.conf. Перезапустите WSL: wsl --shutdown"
|
||||||
|
else
|
||||||
|
warn "Пропущено. Можно настроить вручную — см. README.md"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
Executable
+257
@@ -0,0 +1,257 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_file_contains() {
|
||||||
|
local file="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
grep -Fq "$pattern" "$file" || fail "Expected '$pattern' in $file"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_not_exists() {
|
||||||
|
local path="$1"
|
||||||
|
[ ! -e "$path" ] || fail "Path should not exist: $path"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_fixture() {
|
||||||
|
local fixture_root repo_root bin_dir
|
||||||
|
fixture_root="$(mktemp -d /tmp/dotfiles-install-test.XXXXXX)"
|
||||||
|
repo_root="$fixture_root/repo"
|
||||||
|
bin_dir="$fixture_root/bin"
|
||||||
|
|
||||||
|
mkdir -p "$repo_root" "$bin_dir"
|
||||||
|
cp -a \
|
||||||
|
"$REPO_ROOT/install.sh" \
|
||||||
|
"$REPO_ROOT/bash" \
|
||||||
|
"$REPO_ROOT/tmux" \
|
||||||
|
"$REPO_ROOT/vim" \
|
||||||
|
"$REPO_ROOT/zed" \
|
||||||
|
"$REPO_ROOT/vscode" \
|
||||||
|
"$REPO_ROOT/agents" \
|
||||||
|
"$REPO_ROOT/opencode" \
|
||||||
|
"$REPO_ROOT/codex" \
|
||||||
|
"$REPO_ROOT/gemini" \
|
||||||
|
"$REPO_ROOT/claude" \
|
||||||
|
"$REPO_ROOT/scripts" \
|
||||||
|
"$repo_root/"
|
||||||
|
|
||||||
|
cat > "$bin_dir/fake-git" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "${1:-}" = "-C" ]; then
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
rev-parse)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
show)
|
||||||
|
printf '%s' "${FAKE_GIT_SHOW_CONTENT:-}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unexpected git invocation: $*" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$bin_dir/fake-sudo" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
exec "$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$bin_dir/fake-git" "$bin_dir/fake-sudo"
|
||||||
|
|
||||||
|
printf '%s\n' "$fixture_root"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_install() {
|
||||||
|
local repo_root="$1"
|
||||||
|
local home_dir="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
HOME="$home_dir" \
|
||||||
|
GIT_BIN="$repo_root/../bin/fake-git" \
|
||||||
|
SUDO_BIN="$repo_root/../bin/fake-sudo" \
|
||||||
|
"$@" \
|
||||||
|
"$repo_root/install.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_fresh_install_uses_home_target() {
|
||||||
|
local fixture repo_root home_dir
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out
|
||||||
|
|
||||||
|
[ -L "$home_dir/.bash_custom" ] || fail "Expected .bash_custom symlink in HOME"
|
||||||
|
[ -L "$home_dir/.claude/skills" ] || fail "Expected ~/.claude/skills symlink"
|
||||||
|
assert_file_contains "$home_dir/.bashrc" "[ -f ~/.bash_custom ] && source ~/.bash_custom"
|
||||||
|
assert_not_exists "$fixture/.bash_custom"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_dirty_repo_settings_copy_uses_committed_version() {
|
||||||
|
local fixture repo_root home_dir
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
|
||||||
|
chmod u+w "$repo_root/claude/.claude/settings.json"
|
||||||
|
cat > "$repo_root/claude/.claude/settings.json" <<'EOF'
|
||||||
|
{"source":"working-tree"}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
FAKE_GIT_SHOW_CONTENT='{"source":"head"}' \
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out
|
||||||
|
|
||||||
|
assert_file_contains "$home_dir/.claude/settings.json" '"source":"head"'
|
||||||
|
assert_file_contains "$repo_root/claude/.claude/settings.json" '"source":"working-tree"'
|
||||||
|
}
|
||||||
|
|
||||||
|
test_rerun_does_not_duplicate_bashrc_line() {
|
||||||
|
local fixture repo_root home_dir count
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out
|
||||||
|
|
||||||
|
count="$(grep -Fxc '[ -f ~/.bash_custom ] && source ~/.bash_custom' "$home_dir/.bashrc")"
|
||||||
|
[ "$count" -eq 1 ] || fail "Expected bash_custom line once, got $count"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_repairs_nested_skills_directory_bug() {
|
||||||
|
local fixture repo_root home_dir
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
mkdir -p "$home_dir/.claude/skills"
|
||||||
|
ln -s "$repo_root/agents/.agents/skills" "$home_dir/.claude/skills/skills"
|
||||||
|
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out
|
||||||
|
|
||||||
|
[ -L "$home_dir/.claude/skills" ] || fail "Expected repaired ~/.claude/skills symlink"
|
||||||
|
assert_not_exists "$home_dir/.claude/skills/skills"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_preserves_nonempty_skills_directory() {
|
||||||
|
local fixture repo_root home_dir
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
mkdir -p "$home_dir/.claude/skills"
|
||||||
|
echo local > "$home_dir/.claude/skills/local.txt"
|
||||||
|
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out
|
||||||
|
|
||||||
|
[ -d "$home_dir/.claude/skills" ] || fail "Expected ~/.claude/skills directory to be preserved"
|
||||||
|
[ -f "$home_dir/.claude/skills/local.txt" ] || fail "Expected local skill data to be preserved"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_broken_claude_symlink_does_not_fail() {
|
||||||
|
local fixture repo_root home_dir
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
ln -s "$fixture/missing-claude-dir" "$home_dir/.claude"
|
||||||
|
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out
|
||||||
|
|
||||||
|
[ -d "$home_dir/.claude" ] || fail "Expected ~/.claude directory after migration"
|
||||||
|
[ ! -L "$home_dir/.claude" ] || fail "Expected ~/.claude to stop being a symlink"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_wsl_noninteractive_skips_file_changes() {
|
||||||
|
local fixture repo_root home_dir wsl_conf output
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
wsl_conf="$fixture/wsl.conf"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
printf '[automount]\n' > "$wsl_conf"
|
||||||
|
|
||||||
|
output="$(
|
||||||
|
run_install "$repo_root" "$home_dir" \
|
||||||
|
env WSL_DISTRO_NAME=Ubuntu INSTALL_INTERACTIVE_MODE=never WSL_CONF_PATH="$wsl_conf"
|
||||||
|
)"
|
||||||
|
|
||||||
|
assert_file_contains "$wsl_conf" "[automount]"
|
||||||
|
if grep -Fq '[boot]' "$wsl_conf"; then
|
||||||
|
fail "WSL config should not be modified in non-interactive mode"
|
||||||
|
fi
|
||||||
|
printf '%s' "$output" | grep -Fq 'Добавьте вручную:' || fail "Expected manual WSL hint"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_wsl_interactive_adds_boot_command() {
|
||||||
|
local fixture repo_root home_dir wsl_conf command_line
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
wsl_conf="$fixture/wsl.conf"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
printf '[automount]\n' > "$wsl_conf"
|
||||||
|
command_line="command = \"$repo_root/scripts/setup-hproxy.sh\""
|
||||||
|
|
||||||
|
printf 'y\n' | run_install "$repo_root" "$home_dir" \
|
||||||
|
env WSL_DISTRO_NAME=Ubuntu INSTALL_INTERACTIVE_MODE=always WSL_CONF_PATH="$wsl_conf" >/tmp/test-install.out
|
||||||
|
|
||||||
|
assert_file_contains "$wsl_conf" "[boot]"
|
||||||
|
assert_file_contains "$wsl_conf" "$command_line"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_wsl_existing_boot_command_is_preserved() {
|
||||||
|
local fixture repo_root home_dir wsl_conf output
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
wsl_conf="$fixture/wsl.conf"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
cat > "$wsl_conf" <<'EOF'
|
||||||
|
[boot]
|
||||||
|
command = "/usr/local/bin/existing.sh"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
output="$(
|
||||||
|
printf 'y\n' | run_install "$repo_root" "$home_dir" \
|
||||||
|
env WSL_DISTRO_NAME=Ubuntu INSTALL_INTERACTIVE_MODE=always WSL_CONF_PATH="$wsl_conf"
|
||||||
|
)"
|
||||||
|
|
||||||
|
assert_file_contains "$wsl_conf" 'command = "/usr/local/bin/existing.sh"'
|
||||||
|
if grep -Fq 'setup-hproxy.sh' "$wsl_conf"; then
|
||||||
|
fail "Expected existing boot command to stay untouched"
|
||||||
|
fi
|
||||||
|
printf '%s' "$output" | grep -Fq 'уже содержит [boot] command' || fail "Expected warning about existing boot command"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
command -v stow >/dev/null 2>&1 || fail "stow is required for tests"
|
||||||
|
|
||||||
|
test_fresh_install_uses_home_target
|
||||||
|
test_dirty_repo_settings_copy_uses_committed_version
|
||||||
|
test_rerun_does_not_duplicate_bashrc_line
|
||||||
|
test_repairs_nested_skills_directory_bug
|
||||||
|
test_preserves_nonempty_skills_directory
|
||||||
|
test_broken_claude_symlink_does_not_fail
|
||||||
|
test_wsl_noninteractive_skips_file_changes
|
||||||
|
test_wsl_interactive_adds_boot_command
|
||||||
|
test_wsl_existing_boot_command_is_preserved
|
||||||
|
|
||||||
|
echo "All install tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in new issue
Block a user