From 6d24613b0b453e546fa4e9defc695d8f982743c9 Mon Sep 17 00:00:00 2001 From: Anish Athalye Date: Fri, 3 Jan 2020 15:20:00 -0500 Subject: [PATCH] Unify Vagrant and Travis-CI tests This patch makes the tests (including the test driver) run entirely inside Vagrant, which avoids calling the very slow `vagrant` driver many times for running the tests. On my machine, `./test` runs in 22 seconds, down from hundreds of seconds prior to this patch. This also has the nice side effect of matching how the Travis CI tests were run, so there's no need for a separate `test_travis` anymore. --- .travis.yml | 2 +- test/README.md | 22 +++++-- test/Vagrantfile | 4 +- test/driver-lib.bash | 53 +++++------------ test/test | 26 ++------- test/test-lib.bash | 13 ++--- test/test_travis | 81 -------------------------- test/tests/find-python-executable.bash | 8 +-- test/tests/shim.bash | 9 +-- 9 files changed, 49 insertions(+), 169 deletions(-) delete mode 100755 test/test_travis diff --git a/.travis.yml b/.travis.yml index bece3e9..f81b161 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,4 +13,4 @@ python: sudo: false script: - - ./test/test_travis + - ./test/test diff --git a/test/README.md b/test/README.md index 993a753..9ec79f5 100644 --- a/test/README.md +++ b/test/README.md @@ -36,14 +36,24 @@ git submodule update --init --recursive Running the Tests ----------------- -Before running the tests, the virtual machine must be running. It can be -started by running `vagrant up`. +Before running the tests, you must SSH into the VM. Start it with `vagrant up` +and SSH in with `vagrant ssh`. All following commands must be run inside the +VM. -The test suite can be run by running `./test`. Selected tests can be run by -passing paths to the tests as arguments to `./test`. +First, you must install a version of Python to test against, using `pyenv +install -s {version}`. You can choose any version you like, e.g. `3.8.1`. It +isn't particularly important to test against all supported versions of Python +in the VM, because they will be tested by CI. Once you've installed a specific +version of Python, activate it with `pyenv global {version}`. -Tests can be run with a specific Python version by running `./test --version -` - for example, `./test --version 3.4.3`. +The VM mounts the Dotbot directory in `/dotbot` as read-only (you can make +edits on your host machine). You can run the test suite by `cd /dotbot/test` +and then running `./test`. Selected tests can be run by passing paths to the +tests as arguments, e.g. `./test tests/create.bash tests/defaults.bash`. + +To debug tests, you can prepend the line `DEBUG=true` as the first line to any +individual test (a `.bash` file inside `test/tests`). This will enable printing +stdout/stderr. When finished with testing, it is good to shut down the virtual machine by running `vagrant halt`. diff --git a/test/Vagrantfile b/test/Vagrantfile index 347c402..fbdfe39 100644 --- a/test/Vagrantfile +++ b/test/Vagrantfile @@ -1,8 +1,8 @@ Vagrant.configure(2) do |config| - config.vm.box = 'debian/buster64' + config.vm.box = 'ubuntu/bionic64' # sync by copying for isolation - config.vm.synced_folder "..", "/dotbot", type: "rsync" + config.vm.synced_folder "..", "/dotbot", mount_options: ["ro"] # disable default synced folder config.vm.synced_folder ".", "/vagrant", disabled: true diff --git a/test/driver-lib.bash b/test/driver-lib.bash index 02a71a5..dcbcba6 100644 --- a/test/driver-lib.bash +++ b/test/driver-lib.bash @@ -1,6 +1,3 @@ -MAXRETRY=5 -TIMEOUT=1 - red() { if [ -t 1 ]; then printf "\033[31m%s\033[0m\n" "$*" @@ -26,52 +23,35 @@ yellow() { } -check_prereqs() { - if ! (vagrant ssh -c 'exit') >/dev/null 2>&1; then - >&2 echo "vagrant vm must be running." - return 1 +check_env() { + if [[ "$(whoami)" != "vagrant" && ( "${TRAVIS}" != true || "${CI}" != true ) ]]; then + die "tests must be run inside Travis or Vagrant" fi } -until_success() { - local timeout=${TIMEOUT} - local attempt=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' -} - cleanup() { - vagrant ssh -c " - find . -not \\( \ + ( + if [ "$(whoami)" == "vagrant" ]; then + cd $HOME + find . -not \( \ -path './.pyenv' -o \ -path './.pyenv/*' -o \ -path './.bashrc' -o \ -path './.profile' -o \ -path './.ssh' -o \ -path './.ssh/*' \ - \\) -delete" >/dev/null 2>&1 + \) -delete >/dev/null 2>&1 + else + find ~ -mindepth 1 -newermt "${date_stamp}" \ + -not \( -path ~ -o -path "${BASEDIR}/*" \ + -o -path ~/dotfiles \) \ + -exec rm -rf {} + + fi + ) || true } initialize() { echo "initializing." - if ! vagrant ssh -c "pyenv local ${2}" >/dev/null 2>&1; then - if ! vagrant ssh -c "pyenv install -s ${2} && pyenv local ${2}" >/dev/null 2>&1; then - die "could not install python ${2}" - fi - fi - vagrant rsync >/dev/null 2>&1 tests_run=0 tests_passed=0 tests_failed=0 @@ -96,8 +76,7 @@ run_test() { tests_run=$((tests_run + 1)) printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}" cleanup - vagrant ssh -c "pyenv local ${2}" >/dev/null 2>&1 - if vagrant ssh -c "cd /dotbot/test/tests && bash ${1}" 2>/dev/null; then + if (cd "${BASEDIR}/test/tests" && DOTBOT_TEST=true bash "${1}"); then pass else fail diff --git a/test/test b/test/test index c018c32..f944512 100755 --- a/test/test +++ b/test/test @@ -1,37 +1,21 @@ #!/usr/bin/env bash set -e -BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "${BASEDIR}" +export BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${BASEDIR}/test" . "./driver-lib.bash" +date_stamp="$(date --rfc-3339=ns)" start="$(date +%s)" -check_prereqs || die "prerequisites unsatsfied." - -# command line options -while [[ $# > 1 ]] -do - key="${1}" - case $key in - -v|--version) - VERSION="${2}" - shift && shift - ;; - *) - # unknown option - break - ;; - esac -done -VERSION="${VERSION:-3.6.4}" +check_env declare -a tests=() if [ $# -eq 0 ]; then while read file; do tests+=("${file}") - done < <(find tests -type f -name '*.bash') + done < <(find tests -type f -name '*.bash' | sort) else tests=("$@") fi diff --git a/test/test-lib.bash b/test/test-lib.bash index e4d9a4e..fba9aa0 100644 --- a/test/test-lib.bash +++ b/test/test-lib.bash @@ -1,6 +1,5 @@ DEBUG=${DEBUG:-false} -USE_VAGRANT=${USE_VAGRANT:-true} -DOTBOT_EXEC=${DOTBOT_EXEC:-"python /dotbot/bin/dotbot"} +DOTBOT_EXEC="${BASEDIR}/bin/dotbot" DOTFILES="/home/$(whoami)/dotfiles" INSTALL_CONF='install.conf.yaml' INSTALL_CONF_JSON='install.conf.json' @@ -29,17 +28,15 @@ test_expect_failure() { fi } -check_vm() { - if [ "$(whoami)" != "vagrant" ]; then - >&2 echo "test can't run outside vm!" +check_env() { + if [ "${DOTBOT_TEST}" != "true" ]; then + >&2 echo "test must be run by test driver" exit 1 fi } initialize() { - if ${USE_VAGRANT}; then - check_vm - fi + check_env echo "${test_description}" mkdir -p "${DOTFILES}" cd diff --git a/test/test_travis b/test/test_travis deleted file mode 100755 index 79439e1..0000000 --- a/test/test_travis +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -set -e - -# For debug only: -# export DEBUG=true -# set -x -# set -v - -export BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -# Prevent execution outside of Travis CI builds -if [[ "${TRAVIS}" != true || "${CI}" != true ]]; then - echo "Error: `basename "$0"` should only be used on Travis" - exit 2 -fi - -# Travis runs do not rely on Vagrant -export USE_VAGRANT=false -export DOTBOT_EXEC="${BASEDIR}/bin/dotbot" - -cd "${BASEDIR}" -. "test/driver-lib.bash" - -travis_initialize() { - echo "initializing." - 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}" -} - -travis_cleanup() { - # Remove all dotfiles installed since the start, ignoring the main - # dotfiles directory, and the dotbot source directory - find ~ -mindepth 1 -newermt "${date_stamp}" \ - -not \( -path ~ -o -path "${BASEDIR}/*" \ - -o -path ~/dotfiles \) \ - -exec rm -rf {} + -} - -travis_run_test() { - tests_run=$((tests_run + 1)) - printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}" - cd ${BASEDIR}/test/tests - if bash ${1} ; then - pass - else - fail - fi - travis_cleanup || die "unable to clean up system." -} - -date_stamp="$(date --rfc-3339=ns)" -start="$(date +%s)" - -declare -a tests=() - -if [ $# -eq 0 ]; then - while read file; do - tests+=("${file}") - done < <(find ${BASEDIR}/test/tests -type f -name '*.bash') -else - tests=("$@") -fi - -travis_initialize "${#tests[@]}" - -for file in "${tests[@]}"; do - travis_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/tests/find-python-executable.bash b/test/tests/find-python-executable.bash index 9a91a74..561b882 100644 --- a/test/tests/find-python-executable.bash +++ b/test/tests/find-python-executable.bash @@ -1,18 +1,16 @@ test_description='can find python executable with different names' . '../test-lib.bash' -if ${USE_VAGRANT}; then - DOTBOT_EXEC="/dotbot/bin/dotbot" # revert to calling it as a shell script -fi - # the test machine needs to have a binary named `python` test_expect_success 'setup' ' mkdir ~/tmp_bin && ( IFS=: for p in $PATH; do - find $p -maxdepth 1 -mindepth 1 -exec sh -c \ + if [ -d $p ]; then + find $p -maxdepth 1 -mindepth 1 -exec sh -c \ '"'"'ln -sf {} $HOME/tmp_bin/$(basename {})'"'"' \; + fi done ) && rm -f ~/tmp_bin/python && diff --git a/test/tests/shim.bash b/test/tests/shim.bash index 2ed7d54..ddacae3 100644 --- a/test/tests/shim.bash +++ b/test/tests/shim.bash @@ -4,11 +4,7 @@ test_description='install shim works' test_expect_success 'setup' ' cd ${DOTFILES} git init -if ${USE_VAGRANT}; then - git submodule add /dotbot dotbot -else - git submodule add ${BASEDIR} dotbot -fi +git submodule add ${BASEDIR} dotbot cp ./dotbot/tools/git-submodule/install . echo "pear" > ${DOTFILES}/foo ' @@ -18,9 +14,6 @@ cat > ${DOTFILES}/install.conf.yaml <