gosick #1
@@ -13,8 +13,8 @@ Homesick is sorta like [rip](http://github.com/defunkt/rip), but for dotfiles. I
|
||||
|
||||
We call a repository that is compatible with homesick to be a 'castle'. To act as a castle, a repository must be organized like so:
|
||||
|
||||
* Contains a 'home' directory
|
||||
* 'home' contains any number of files and directories that begin with '.'
|
||||
- Contains a 'home' directory
|
||||
- 'home' contains any number of files and directories that begin with '.'
|
||||
|
||||
To get started, install homesick first:
|
||||
|
||||
@@ -82,6 +82,35 @@ If you ever want to see what version of homesick you have type:
|
||||
|
||||
homesick version|-v|--version
|
||||
|
||||
## Docker Behavior Validation (Ruby vs Go)
|
||||
|
||||
To preserve behavior while migrating from Ruby to Go, this repository includes a containerized behavior suite that runs Homesick commands in a clean environment and validates filesystem and git outcomes.
|
||||
|
||||
The suite creates a dedicated git repository inside the container to act as a test castle fixture, then validates behavior for:
|
||||
|
||||
- clone
|
||||
- link / unlink
|
||||
- track
|
||||
- list / show_path
|
||||
- status / diff
|
||||
- version
|
||||
|
||||
Run against the current Ruby implementation:
|
||||
|
||||
./script/run-behavior-suite-docker.sh
|
||||
|
||||
Show full command output (including internal Homesick status lines) when needed:
|
||||
|
||||
./script/run-behavior-suite-docker.sh --verbose
|
||||
|
||||
This runner now builds an Alpine-based container and installs runtime dependencies with `apk`, so behavior validation is not tied to a Ruby base image.
|
||||
|
||||
Run against a future Go binary (example path):
|
||||
|
||||
HOMESICK_CMD="/workspace/dist/homesick" ./script/run-behavior-suite-docker.sh
|
||||
|
||||
The command under test is controlled with the `HOMESICK_CMD` environment variable. By running the same suite for both implementations, you can verify parity at the behavior level.
|
||||
|
||||
## .homesick_subdir
|
||||
|
||||
`homesick link` basically makes symlink to only first depth in `castle/home`. If you want to link nested files/directories, please use .homesick_subdir.
|
||||
@@ -164,17 +193,17 @@ and castle
|
||||
|
||||
Homesick is tested on the following Ruby versions:
|
||||
|
||||
* 2.2.6
|
||||
* 2.3.3
|
||||
* 2.4.0
|
||||
- 2.2.6
|
||||
- 2.3.3
|
||||
- 2.4.0
|
||||
|
||||
## Note on Patches/Pull Requests
|
||||
|
||||
* Fork the project.
|
||||
* Make your feature addition or bug fix.
|
||||
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
||||
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
||||
* Send me a pull request. Bonus points for topic branches.
|
||||
- Fork the project.
|
||||
- Make your feature addition or bug fix.
|
||||
- Add tests for it. This is important so I don't break it in a future version unintentionally.
|
||||
- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
||||
- Send me a pull request. Bonus points for topic branches.
|
||||
|
||||
## Need homesick without the ruby dependency?
|
||||
|
||||
13
docker/behavior/Dockerfile
Normal file
13
docker/behavior/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ca-certificates \
|
||||
git \
|
||||
ruby \
|
||||
ruby-thor
|
||||
|
||||
WORKDIR /workspace
|
||||
COPY . /workspace
|
||||
|
||||
ENTRYPOINT ["/workspace/test/behavior/behavior_suite.sh"]
|
||||
50
script/run-behavior-suite-docker.sh
Executable file
50
script/run-behavior-suite-docker.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
: "${HOMESICK_CMD:=ruby /workspace/bin/homesick}"
|
||||
behavior_verbose="${BEHAVIOR_VERBOSE:-0}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-v|--verbose)
|
||||
echo "Enabling verbose output for behavior suite"
|
||||
behavior_verbose=1
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
echo "Usage: $0 [--verbose]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "$script_dir/.." && pwd)"
|
||||
|
||||
run_docker_build() {
|
||||
echo "Building Docker image for behavior suite..."
|
||||
local build_log
|
||||
|
||||
if [[ "$behavior_verbose" == "1" ]]; then
|
||||
docker-buildx build -f "$repo_root/docker/behavior/Dockerfile" -t homesick-behavior:latest "$repo_root"
|
||||
return
|
||||
fi
|
||||
|
||||
build_log="$(mktemp)"
|
||||
if ! docker-buildx build -f "$repo_root/docker/behavior/Dockerfile" -t homesick-behavior:latest "$repo_root" >"$build_log" 2>&1; then
|
||||
cat "$build_log" >&2
|
||||
rm -f "$build_log"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$build_log"
|
||||
}
|
||||
|
||||
run_docker_build
|
||||
|
||||
echo "Running behavior suite in Docker container..."
|
||||
docker run --rm \
|
||||
-e HOMESICK_CMD="$HOMESICK_CMD" \
|
||||
-e BEHAVIOR_VERBOSE="$behavior_verbose" \
|
||||
homesick-behavior:latest
|
||||
193
test/behavior/behavior_suite.sh
Executable file
193
test/behavior/behavior_suite.sh
Executable file
@@ -0,0 +1,193 @@
|
||||
#!/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"
|
||||
}
|
||||
|
||||
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/7] clone"
|
||||
run_homesick "clone file://$remote_root/base.git parity-castle"
|
||||
assert_path_exists "$HOME/.homesick/repos/parity-castle/.git"
|
||||
assert_path_exists "$HOME/.homesick/repos/parity-castle/home/.vimrc"
|
||||
pass
|
||||
|
||||
echo "[2/7] 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 "[3/7] 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 "[4/7] 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 "[5/7] 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 include parity-castle path"
|
||||
pass
|
||||
|
||||
echo "[6/7] 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 "[7/7] version"
|
||||
local version_output
|
||||
run_homesick "version"
|
||||
version_output="$RUN_OUTPUT"
|
||||
[[ "$version_output" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "expected semantic version output, got: $version_output"
|
||||
pass
|
||||
|
||||
echo "PASS: behavior suite completed for command: $HOMESICK_CMD"
|
||||
}
|
||||
|
||||
parse_args "$@"
|
||||
run_suite
|
||||
Reference in New Issue
Block a user