#!/usr/bin/env bash set -euo pipefail : "${HOMESICK_CMD:=ruby /workspace/bin/homesick}" : "${BEHAVIOR_VERBOSE:=0}" RUN_OUTPUT="" fail() { echo "FAIL: $*" >&2 exit 1 } pass() { if [[ -t 1 ]]; then printf ' \033[32mPassed\033[0m\n' else echo " Passed" fi } parse_args() { while [[ $# -gt 0 ]]; do case "$1" in -v|--verbose) BEHAVIOR_VERBOSE=1 ;; *) fail "unknown argument: $1" ;; esac shift done } run_git() { if [[ "$BEHAVIOR_VERBOSE" == "1" ]]; then git "$@" else git "$@" >/dev/null 2>&1 fi } assert_path_exists() { local path="$1" [[ -e "$path" ]] || fail "expected path to exist: $path" } assert_path_missing() { local path="$1" [[ ! -e "$path" ]] || fail "expected path to be missing: $path" } assert_symlink_target() { local link_path="$1" local expected_target="$2" [[ -L "$link_path" ]] || fail "expected symlink: $link_path" local actual_target actual_target="$(readlink "$link_path")" [[ "$actual_target" == "$expected_target" ]] || fail "expected symlink target '$expected_target' but got '$actual_target'" } run_homesick() { local out_file local output out_file="$(mktemp)" if ! bash -lc "$HOMESICK_CMD $*" >"$out_file" 2>&1; then cat "$out_file" >&2 rm -f "$out_file" fail "homesick command failed: $*" fi output="$(cat "$out_file")" RUN_OUTPUT="$output" if [[ "$BEHAVIOR_VERBOSE" == "1" && -n "$output" ]]; then printf '%s\n' "$output" fi rm -f "$out_file" } run_homesick_with_stdin() { local stdin_data="$1" shift local out_file local output out_file="$(mktemp)" if ! printf '%b' "$stdin_data" | bash -lc "$HOMESICK_CMD $*" >"$out_file" 2>&1; then cat "$out_file" >&2 rm -f "$out_file" fail "homesick command failed: $*" fi output="$(cat "$out_file")" RUN_OUTPUT="$output" if [[ "$BEHAVIOR_VERBOSE" == "1" && -n "$output" ]]; then printf '%s\n' "$output" fi rm -f "$out_file" } run_homesick_with_env() { local env_prefix="$1" shift local out_file local output out_file="$(mktemp)" if ! bash -lc "$env_prefix $HOMESICK_CMD $*" >"$out_file" 2>&1; then cat "$out_file" >&2 rm -f "$out_file" fail "homesick command failed: $*" fi output="$(cat "$out_file")" RUN_OUTPUT="$output" if [[ "$BEHAVIOR_VERBOSE" == "1" && -n "$output" ]]; then printf '%s\n' "$output" fi rm -f "$out_file" } setup_remote_castle() { local remote_dir="$1" local work_dir="$2" mkdir -p "$remote_dir" run_git init --bare "$remote_dir/base.git" mkdir -p "$work_dir/base" pushd "$work_dir/base" >/dev/null run_git init run_git config user.email "behavior@test.local" run_git config user.name "Behavior Test" mkdir -p home/.config/myapp echo "set number" > home/.vimrc echo "export PATH=\"$PATH:$HOME/bin\"" > home/.zshrc echo "option=true" > home/.config/myapp/config.toml printf '.config\n' > .homesick_subdir run_git add . run_git commit -m "initial castle" run_git remote add origin "$remote_dir/base.git" run_git push -u origin master popd >/dev/null } setup_local_test_file() { mkdir -p "$HOME/.local/bin" echo "#!/usr/bin/env bash" > "$HOME/.local/bin/tool" chmod +x "$HOME/.local/bin/tool" } run_suite() { local tmp_root tmp_root="$(mktemp -d)" trap "rm -rf '$tmp_root'" EXIT export HOME="$tmp_root/home" mkdir -p "$HOME" local remote_root="$tmp_root/remote" local work_root="$tmp_root/work" setup_remote_castle "$remote_root" "$work_root" echo "[1/18] help" run_homesick "help" [[ "$RUN_OUTPUT" == *"Usage:"* || "$RUN_OUTPUT" == *"Commands:"* ]] || fail "expected help output to include command usage information" run_homesick "help clone" [[ "$RUN_OUTPUT" == *"clone"* ]] || fail "expected command help output for clone" pass echo "[2/18] clone" run_homesick "clone file://$remote_root/base.git parity-castle" run_homesick "clone file://$remote_root/base.git parity-castle-2" assert_path_exists "$HOME/.homesick/repos/parity-castle/.git" assert_path_exists "$HOME/.homesick/repos/parity-castle/home/.vimrc" assert_path_exists "$HOME/.homesick/repos/parity-castle-2/.git" run_git -C "$HOME/.homesick/repos/parity-castle" config user.email "behavior@test.local" run_git -C "$HOME/.homesick/repos/parity-castle" config user.name "Behavior Test" run_git -C "$HOME/.homesick/repos/parity-castle-2" config user.email "behavior@test.local" run_git -C "$HOME/.homesick/repos/parity-castle-2" config user.name "Behavior Test" pass echo "[3/18] link" run_homesick "link parity-castle" assert_symlink_target "$HOME/.vimrc" "$HOME/.homesick/repos/parity-castle/home/.vimrc" assert_symlink_target "$HOME/.zshrc" "$HOME/.homesick/repos/parity-castle/home/.zshrc" assert_path_exists "$HOME/.config/myapp" assert_symlink_target "$HOME/.config/myapp" "$HOME/.homesick/repos/parity-castle/home/.config/myapp" pass echo "[4/18] unlink" run_homesick "unlink parity-castle" assert_path_missing "$HOME/.vimrc" assert_path_missing "$HOME/.zshrc" assert_path_exists "$HOME/.config" assert_path_missing "$HOME/.config/myapp" pass echo "[5/18] symlink alias" run_homesick "symlink parity-castle" assert_symlink_target "$HOME/.vimrc" "$HOME/.homesick/repos/parity-castle/home/.vimrc" pass echo "[6/18] relink + track" run_homesick "link parity-castle" setup_local_test_file run_homesick "track $HOME/.local/bin/tool parity-castle" assert_symlink_target "$HOME/.local/bin/tool" "$HOME/.homesick/repos/parity-castle/home/.local/bin/tool" assert_path_exists "$HOME/.homesick/repos/parity-castle/.homesick_subdir" grep -q "^.local/bin$" "$HOME/.homesick/repos/parity-castle/.homesick_subdir" || fail "expected .homesick_subdir to contain .local/bin" pass echo "[7/18] list and show_path" local list_output run_homesick "list" list_output="$RUN_OUTPUT" [[ "$list_output" == *"parity-castle"* ]] || fail "expected list output to include parity-castle" local show_path_output run_homesick "show_path parity-castle" show_path_output="$RUN_OUTPUT" [[ "$show_path_output" == "$HOME/.homesick/repos/parity-castle" ]] || fail "expected show_path output to equal parity-castle root path" pass echo "[8/18] status and diff" echo "change" >> "$HOME/.vimrc" local status_output run_homesick "status parity-castle" status_output="$RUN_OUTPUT" [[ "$status_output" == *"modified:"* ]] || fail "expected status output to include modified file" local diff_output run_homesick "diff parity-castle" diff_output="$RUN_OUTPUT" [[ "$diff_output" == *"diff --git"* ]] || fail "expected diff output to include git diff" pass echo "[9/18] pull --all" local pull_all_output run_homesick "pull --all" pull_all_output="$RUN_OUTPUT" [[ "$pull_all_output" == *"parity-castle:"* ]] || fail "expected pull --all output to include parity-castle" [[ "$pull_all_output" == *"parity-castle-2:"* ]] || fail "expected pull --all output to include parity-castle-2" pass echo "[10/18] single-castle pull" pushd "$work_root/base" >/dev/null echo "single-castle-pull" > home/.pull-single run_git add . run_git commit -m "single-castle pull fixture" run_git push popd >/dev/null run_homesick "pull parity-castle" assert_path_exists "$HOME/.homesick/repos/parity-castle/home/.pull-single" pass echo "[11/18] exec" local exec_marker="$HOME/.homesick/repos/parity-castle/.exec-marker" run_homesick "exec parity-castle touch .exec-marker" assert_path_exists "$exec_marker" pass echo "[12/18] exec_all" local exec_all_marker_a="$HOME/.homesick/repos/parity-castle/.exec-all-marker" local exec_all_marker_b="$HOME/.homesick/repos/parity-castle-2/.exec-all-marker" run_homesick "exec_all touch .exec-all-marker" assert_path_exists "$exec_all_marker_a" assert_path_exists "$exec_all_marker_b" pass echo "[13/18] generate" local generated_castle="$HOME/generated-castle" run_homesick "generate $generated_castle" assert_path_exists "$generated_castle/.git" assert_path_exists "$generated_castle/home" pass echo "[14/18] commit and push" echo "commit-change" >> "$HOME/.zshrc" run_homesick "commit parity-castle behavior-suite-commit" run_homesick "push parity-castle" local remote_head remote_head="$(git --git-dir "$remote_root/base.git" log --oneline -1)" [[ "$remote_head" == *"behavior-suite-commit"* ]] || fail "expected pushed commit in remote history" pass echo "[15/18] open" run_homesick_with_env "EDITOR=true" "open parity-castle" pass echo "[16/18] cd" run_homesick_with_env "SHELL=/bin/true" "cd parity-castle" pass echo "[17/18] rc --force" local rc_marker="$HOME/rc-force-was-here" cat > "$HOME/.homesick/repos/parity-castle/.homesickrc" <