diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..a977916 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +.vagrant/ diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..824dbca --- /dev/null +++ b/test/README.md @@ -0,0 +1,24 @@ +Testing +======= + +Dotbot testing code uses [Vagrant][vagrant] to run all tests inside a virtual +machine to have tests be completely isolated from the host machine. The test +driver relies on the [Sahara][sahara] plugin to snapshot and roll back virtual +machine state. The tests are deterministic, and each test is run in a virtual +machine with fresh state, ensuring that tests that modify system state are +easily repeatable. + +Running the Tests +----------------- + +Before running the tests, the virtual machine must be running. It can be +started by running `vagrant up`. + +The test suite can be run by running `./test`. Selected tests can be run by +passing paths to the tests as arguments to `./test`. + +When finished with testing, it is good to shut down the virtual machine by +running `vagrant halt`. + +[vagrant]: https://www.vagrantup.com/ +[sahara]: https://github.com/jedi4ever/sahara diff --git a/test/Vagrantfile b/test/Vagrantfile new file mode 100644 index 0000000..4463f99 --- /dev/null +++ b/test/Vagrantfile @@ -0,0 +1,10 @@ +Vagrant.configure(2) do |config| + config.vm.box = 'ubuntu/trusty64' + + # sync by copying for isolation + config.vm.synced_folder "..", "/dotbot", type: "rsync", + rsync__exclude: ".git/" + + # disable default synced folder + config.vm.synced_folder ".", "/vagrant", disabled: true +end diff --git a/test/driver-lib.bash b/test/driver-lib.bash new file mode 100644 index 0000000..82766b9 --- /dev/null +++ b/test/driver-lib.bash @@ -0,0 +1,117 @@ +MAXRETRY=5 +TIMEOUT=1 + +red() { + if [ -t 1 ]; then + printf "\033[31m%s\033[0m\n" "$*" + else + printf "%s\n" "$*" + fi +} + +green() { + if [ -t 1 ]; then + printf "\033[32m%s\033[0m\n" "$*" + else + printf "%s\n" "$*" + fi +} + +yellow() { + if [ -t 1 ]; then + printf "\033[33m%s\033[0m\n" "$*" + else + printf "%s\n" "$*" + fi +} + + +check_prereqs() { + if ! (vagrant ssh -c 'exit') >/dev/null 2>&1; then + >&2 echo "vagrant vm must be running." + return 1 + fi + if ! (vagrant plugin list | grep '^sahara\s\+') >/dev/null 2>&1; then + >&2 echo "vagrant plugin 'sahara' is not installed." + return 1 + fi +} + +until_success() { + local timeout=${TIMEOUT} + local attempt=0 + local success=0 + while [ $attempt -lt $MAXRETRY ]; do + if ($@) >/dev/null 2>&1; then + return 0 + fi + sleep $timeout + timeout=$((timeout * 2)) + attempt=$((attempt + 1)) + done + + return 1 +} + +wait_for_vagrant() { + until_success vagrant ssh -c 'exit' +} + +rollback() { + vagrant sandbox rollback >/dev/null 2>&1 && + wait_for_vagrant && + vagrant rsync >/dev/null 2>&1 +} + +initialize() { + echo "initializing." + vagrant sandbox on >/dev/null 2>&1 + tests_run=0 + tests_passed=0 + tests_failed=0 + tests_total="${1}" + local plural="" && [ ${tests_total} -gt 1 ] && plural="s" + printf -- "running %d test%s...\n\n" ${tests_total} $plural +} + +pass() { + tests_passed=$((tests_passed + 1)) + green "-> ok." + echo +} + +fail() { + tests_failed=$((tests_failed + 1)) + yellow "-> fail!" + echo +} + +run_test() { + tests_run=$((tests_run + 1)) + printf '[%d/%d]\n' ${tests_run} ${tests_total} + rollback || die "unable to rollback vm." # start with a clean slate + vagrant ssh -c "cd /dotbot/test/tests && bash ${1}" 2>/dev/null && pass || fail +} + +report() { + printf -- "test report\n" + printf -- "-----------\n" + printf -- "- %3d run\n" ${tests_run} + printf -- "- %3d passed\n" ${tests_passed} + if [ ${tests_failed} -gt 0 ]; then + printf -- "- %3d failed\n" ${tests_failed} + echo + red "==> not ok!" + return 1 + else + echo + green "==> all ok." + return 0 + fi +} + +die() { + >&2 echo $@ + >&2 echo "terminating..." + exit 1 +} diff --git a/test/test b/test/test new file mode 100755 index 0000000..2d1e3a8 --- /dev/null +++ b/test/test @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e + +BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${BASEDIR}" +. "./driver-lib.bash" + +start="$(date +%s)" + +check_prereqs || die "prerequisites unsatsfied." + +declare -a tests=() + +if [ $# -eq 0 ]; then + while read file; do + tests+=("${file}") + done < <(find tests -type f -name '*.bash') +else + tests=("$@") +fi + +initialize "${#tests[@]}" + +for file in "${tests[@]}"; do + run_test "$(basename ${file})" +done + +if report; then + ret=0 +else + ret=1 +fi + +echo "(tests run in $(($(date +%s) - start)) seconds)" +exit ${ret} diff --git a/test/test-lib.bash b/test/test-lib.bash new file mode 100644 index 0000000..425ae56 --- /dev/null +++ b/test/test-lib.bash @@ -0,0 +1,51 @@ +DEBUG=false +DOTFILES='/home/vagrant/dotfiles' +INSTALL_CONF='install.conf.yaml' + +test_run_() { + if ! ${DEBUG}; then + (eval "$*") >/dev/null 2>&1 + else + (eval "$*") + fi +} + +test_expect_success() { + local tag=${1} && shift + if ! test_run_ "$@"; then + >&2 echo "- ${tag} failed." + exit 1 + fi +} + +test_expect_failure() { + local tag=${1} && shift + if test_run_ "$@"; then + >&2 echo "- ${tag} failed." + exit 1 + fi +} + +check_vm() { + if [ "$(whoami)" != "vagrant" ]; then + >&2 echo "test can't run outside vm!" + exit 1 + fi +} + +initialize() { + check_vm + echo "${test_description}" + mkdir -p "${DOTFILES}" + cd +} + +run_dotbot() { + ( + cd "${DOTFILES}" + cat > "${INSTALL_CONF}" + /dotbot/bin/dotbot -d . -c "${INSTALL_CONF}" "${@}" + ) +} + +initialize diff --git a/test/tests/clean-missing.bash b/test/tests/clean-missing.bash new file mode 100644 index 0000000..dc6c84c --- /dev/null +++ b/test/tests/clean-missing.bash @@ -0,0 +1,19 @@ +test_description='clean deletes links to missing files' +. '../test-lib.bash' + +test_expect_success 'setup' ' +touch ${DOTFILES}/f && +ln -s ${DOTFILES}/f ~/.f && +ln -s ${DOTFILES}/g ~/.g +' + +test_expect_success 'run' ' +run_dotbot < ${DOTFILES}/f && +echo "grape" > ~/.f +' + +test_expect_failure 'run' ' +run_dotbot < ${DOTFILES}/f && +echo "grape" > ~/.f +' + +test_expect_failure 'run' ' +run_dotbot < ${DOTFILES}/f && +echo "grape" > ~/f && +ln -s ~/f ~/.f +' + +test_expect_success 'run' ' +run_dotbot <