Adding a set of behavioural tests
This commit is contained in:
@@ -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:
|
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
|
- Contains a 'home' directory
|
||||||
* 'home' contains any number of files and directories that begin with '.'
|
- 'home' contains any number of files and directories that begin with '.'
|
||||||
|
|
||||||
To get started, install homesick first:
|
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
|
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_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.
|
`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:
|
Homesick is tested on the following Ruby versions:
|
||||||
|
|
||||||
* 2.2.6
|
- 2.2.6
|
||||||
* 2.3.3
|
- 2.3.3
|
||||||
* 2.4.0
|
- 2.4.0
|
||||||
|
|
||||||
## Note on Patches/Pull Requests
|
## Note on Patches/Pull Requests
|
||||||
|
|
||||||
* Fork the project.
|
- Fork the project.
|
||||||
* Make your feature addition or bug fix.
|
- 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.
|
- 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)
|
- 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.
|
- Send me a pull request. Bonus points for topic branches.
|
||||||
|
|
||||||
## Need homesick without the ruby dependency?
|
## 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