1
0
Fork 0
mirror of synced 2024-06-22 17:01:10 -04:00

Switched to unittest/tox tests for possible better python2/3 testing. anishathalye/dotbot#40

This commit is contained in:
Brian Knobbs 2015-06-27 01:53:57 -04:00
parent dcc3a1947a
commit b622c5ea20
24 changed files with 222 additions and 453 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
*.pyc
.coverage
.tox

View file

@ -1,24 +1,41 @@
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.
Testing is run against multiple Python using ``tox``. It is recommended to use ``pyenv`` to manage
your Python version.
Running the Tests
-----------------
Before running the tests, the virtual machine must be running. It can be
started by running `vagrant up`.
Setup
=====
The test suite can be run by running `./test`. Selected tests can be run by
passing paths to the tests as arguments to `./test`.
* Ensure git submodules are up to date
* Install pyenv
* Install Python versions
When finished with testing, it is good to shut down the virtual machine by
running `vagrant halt`.
```
pyenv install 3.4.3
pyenv install 3.3.6
pyenv install 3.2.6
pyenv install 2.7.10
pyenv install 2.6.9
```
* *cd* into the *dotbot* repository and set the local Python versions for pyenv
```
pyenv local 3.4.3 3.3.6 3.2.6 2.7.10 2.6.9
```
* Install test requirements
```
pip install tox
pyenv rehash
```
Running the Test Suite
======================
Once the environment has been setup, simply run the ``tox`` command in the ``dotbot`` directory
[vagrant]: https://www.vagrantup.com/
[sahara]: https://github.com/jedi4ever/sahara

10
test/Vagrantfile vendored
View file

@ -1,10 +0,0 @@
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

View file

@ -1,120 +0,0 @@
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
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] (%s)\n' "${tests_run}" "${tests_total}" "${1}"
rollback || die "unable to rollback vm." # start with a clean slate
if vagrant ssh -c "cd /dotbot/test/tests && bash ${1}" 2>/dev/null; then
pass
else
fail
fi
}
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
}

View file

@ -1,35 +0,0 @@
#!/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}

View file

@ -1,51 +0,0 @@
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

33
test/test_clean.py Normal file
View file

@ -0,0 +1,33 @@
import os
from utils import DotbotTestCase
class MissingTestCase(DotbotTestCase):
def test_clean(self):
""" clean deletes links to missing files """
self.add_file('f')
self.add_symlink('f')
self.add_symlink('g')
self.run_dotbot(config='- clean: ["~"]')
self.assertIsLinked('f')
self.assertDoesNotExist('g')
def test_ignores_nonexistant(self):
""" clean ignores nonexistant directories """
self.run_dotbot(config='- clean: ["~", "~/fake"]')
def test_ignores_outside_linking(self):
""" clean ignores files linking outside dotfiles directory """
self.add_symlink('f')
with open(os.path.join(self.home_dir, 'g'), 'w') as g:
g.write('')
os.symlink(os.path.join(self.home_dir, 'g'), os.path.join(self.home_dir, '.g'))
self.run_dotbot(config='- clean: ["~"]')
self.assertDoesNotExist('f')
self.assertEqual(os.stat(os.path.join(self.home_dir, 'g')),
os.stat(os.path.join(self.home_dir, '.g')))

10
test/test_config.py Normal file
View file

@ -0,0 +1,10 @@
import os
from utils import DotbotTestCase
class ConfigTestCase(DotbotTestCase):
def test_blank_config_allowed(self):
self.run_dotbot(config='[]')
def test_empty_config_not_allowed(self):
self.assertRaises(SystemExit, self.run_dotbot, skip_config=True)

View file

@ -1,19 +0,0 @@
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 <<EOF
- clean: ["~"]
EOF
'
test_expect_success 'test' '
test -f ~/.f &&
! test -h ~/.g
'

View file

@ -1,8 +0,0 @@
test_description='clean ignores nonexistent directories'
. '../test-lib.bash'
test_expect_success 'run' '
run_dotbot <<EOF
- clean: ["~", "~/fake"]
EOF
'

View file

@ -1,18 +0,0 @@
test_description='clean ignores files linking outside dotfiles directory'
. '../test-lib.bash'
test_expect_success 'setup' '
ln -s ${DOTFILES}/f ~/.f &&
ln -s ~/g ~/.g
'
test_expect_success 'run' '
run_dotbot <<EOF
- clean: ["~"]
EOF
'
test_expect_success 'test' '
! test -h ~/.f &&
test -h ~/.g
'

View file

@ -1,8 +0,0 @@
test_description='blank config allowed'
. '../test-lib.bash'
test_expect_success 'run' '
run_dotbot <<EOF
[]
EOF
'

View file

@ -1,7 +0,0 @@
test_description='empty config disallowed'
. '../test-lib.bash'
test_expect_failure 'run' '
run_dotbot <<EOF
EOF
'

View file

@ -1,18 +0,0 @@
test_description='link expands environment variables in source'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "grape" > ${DOTFILES}/h
'
test_expect_success 'run' '
export APPLE="h" &&
run_dotbot <<EOF
- link:
~/.i: \$APPLE
EOF
'
test_expect_success 'test' '
grep "grape" ~/.i
'

View file

@ -1,25 +0,0 @@
test_description='link expands environment variables in target'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/f &&
echo "grape" > ${DOTFILES}/h
'
test_expect_success 'run' '
export ORANGE=".config" &&
export BANANA="g" &&
unset PEAR &&
run_dotbot <<EOF
- link:
~/\${ORANGE}/\$BANANA:
path: f
create: true
~/\$PEAR: h
EOF
'
test_expect_success 'test' '
grep "apple" ~/.config/g &&
grep "grape" ~/\$PEAR
'

View file

@ -1,18 +0,0 @@
test_description='link leaves unset environment variables'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/\$ORANGE
'
test_expect_success 'run' '
unset ORANGE &&
run_dotbot <<EOF
- link:
~/.f: \$ORANGE
EOF
'
test_expect_success 'test' '
grep "apple" ~/.f
'

View file

@ -1,21 +0,0 @@
test_description='force overwrites symlinked directory'
. '../test-lib.bash'
test_expect_success 'setup' '
mkdir ${DOTFILES}/dir ~/dir &&
touch ${DOTFILES}/dir/f &&
ln -s ~/ ~/.dir
'
test_expect_success 'run' '
run_dotbot <<EOF
- link:
~/.dir:
path: dir
force: true
EOF
'
test_expect_success 'test' '
test -f ~/.dir/f
'

View file

@ -1,18 +0,0 @@
test_description='relink does not overwrite file'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/f &&
echo "grape" > ~/.f
'
test_expect_failure 'run' '
run_dotbot <<EOF
- link:
~/.f: f
EOF
'
test_expect_success 'test' '
grep "grape" ~/.f
'

View file

@ -1,20 +0,0 @@
test_description='relink does not overwrite file'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/f &&
echo "grape" > ~/.f
'
test_expect_failure 'run' '
run_dotbot <<EOF
- link:
~/.f:
path: f
relink: true
EOF
'
test_expect_success 'test' '
grep "grape" ~/.f
'

View file

@ -1,21 +0,0 @@
test_description='relink overwrites symlink'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/f &&
echo "grape" > ~/f &&
ln -s ~/f ~/.f
'
test_expect_success 'run' '
run_dotbot <<EOF
- link:
~/.f:
path: f
relink: true
EOF
'
test_expect_success 'test' '
grep "apple" ~/.f
'

View file

@ -1,11 +0,0 @@
test_description='shell command stdout works'
. '../test-lib.bash'
test_expect_success 'run' '
(run_dotbot | grep "^apple") <<EOF
- shell:
-
command: echo apple
stdout: true
EOF
'

View file

@ -1,9 +0,0 @@
test_description='shell command stdout disabled by default'
. '../test-lib.bash'
test_expect_success 'run' '
(run_dotbot | (! grep "^banana")) <<EOF
- shell:
- echo banana
EOF
'

129
test/utils.py Normal file
View file

@ -0,0 +1,129 @@
import sys, os
PROJECT_ROOT_DIRECTORY = os.path.dirname(
os.path.dirname(os.path.realpath(__file__)))
def inject(lib_path):
path = os.path.join(PROJECT_ROOT_DIRECTORY, 'lib', lib_path)
sys.path.insert(0, path)
# version dependent libraries
if sys.version_info[0] >= 3:
inject('pyyaml/lib3')
else:
inject('pyyaml/lib')
if os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dotbot')):
if PROJECT_ROOT_DIRECTORY not in sys.path:
sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY)
import shutil
import dotbot
import tempfile
import unittest
import shlex
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
# mock is built-in after py3.3, otherwise import the 3rd party mock
try:
from unittest import mock
except ImportError:
import mock
class DotbotTestCase(unittest.TestCase):
""" Dotbot specific TestCase that will take care of setting up temporary directories and a convenience
function for simulating running Dotbot from the CLI """
def setUp(self):
""" Creates a temporary directory to run in for every test """
self.tempdir = tempfile.mkdtemp()
self.dotbot_dir = os.path.join(self.tempdir, 'dotbot')
self.home_dir = os.path.join(self.tempdir, 'home')
self.config_file = os.path.join(self.dotbot_dir, 'dotbot.config')
if int(os.environ.get('NO_CLEAN', '0')):
print(self.tempdir) # If we're not going to clean up, print out where we're runnin
os.mkdir(self.dotbot_dir)
os.mkdir(self.home_dir)
def add_file(self, filename, contents=""):
""" Create a file in temporary dotbot_dir. Optionally with content """
with open(os.path.join(self.dotbot_dir, filename), 'w') as f:
f.write(contents)
def add_dirs(self, path):
""" Create directories within the temporary dotbot_dir. Acts like ``mkdir -p``. Path is relative to the
dotbot dir """
os.makedirs(os.path.join(self.dotbot_dir, path))
def add_symlink(self, path):
""" Creates a symlink from ``self.home_dir``/path to ``self.dotbot_dir``/path """
os.symlink(os.path.join(self.dotbot_dir, path), os.path.join(self.home_dir, path))
def assertIsLinked(self, path):
""" Asserts that the given ``path`` in self.home_dir is symlinked to the corresponding ``path``
in self.dotbot_dir """
self.assertTrue(os.path.islink(os.path.join(self.home_dir, path)))
self.assertEqual(os.stat(os.path.join(self.dotbot_dir, path)),
os.stat(os.path.join(self.home_dir, path)))
def assertDoesNotExist(self, path):
""" Asserts the given ``path`` in self.home_dir does not exist """
self.assertFalse(os.path.exists(os.path.join(self.home_dir, path)) or
os.path.lexists(os.path.join(self.home_dir, path)))
def run_dotbot(self, config="", args="", skip_config=False):
""" Runs dotbot in a simulated way temporarily intercepting stdout, stderr, setting the HOME
environment variable to ``self.home_dir``, and setting sys.argv to the simulated command
line options. ``run_dotbot`` will automatically set the ``--base-directory`` and
``--config-file`` command line arguments appropriately.
The ``config`` argument is a string that is written out as the configuration file for dotbot
to use. The ``args`` argument is a string of extra command line arguments to pass to dotbot
just like they would be passed on the command line.
If ``skip_config`` is True, a config file will not be written.
Returns a tuple (out, err) of the stdout and stderr from dotbot.
"""
if not skip_config:
with open(self.config_file, 'w') as f:
f.write(config)
base_args = [
'dotbot',
'--base-directory', self.tempdir,
'--config-file', self.config_file,
]
old_home, os.environ['HOME'] = os.path.expanduser('~'), self.home_dir
old_stdout, sys.stdout = sys.stdout, StringIO()
old_stderr, sys.stderr = sys.stderr, StringIO()
old_argv, sys.argv = sys.argv, base_args + shlex.split(args)
try:
dotbot.cli.main()
finally:
os.environ['HOME'] = old_home
out, sys.stdout = sys.stdout.getvalue(), old_stdout
err, sys.stderr = sys.stderr.getvalue(), old_stderr
sys.argv = old_argv
print("\nDotbot Output:")
print('out:\n', out)
print('err:\n', err)
return out, err
def tearDown(self):
""" Clean up the temporary directory that was created. Set NO_CLEAN=1 to disable cleanup """
if not int(os.environ.get('NO_CLEAN', '0')):
shutil.rmtree(self.tempdir)

15
tox.ini Normal file
View file

@ -0,0 +1,15 @@
[tox]
skipsdist=True
envlist =
{py26,py27,py32,py33,py34}
[testenv]
deps=
{py26,py27,py32}: mock
nose
coverage
python-coveralls
commands=
nosetests {posargs:--with-coverage --cover-package=dotbot}