1
0
Fork 0
mirror of synced 2024-06-15 13:51:08 -04:00

Merge branch 'upstream_master'

This commit is contained in:
Andreas Schmidt 2020-02-18 17:56:50 +01:00
commit 58160cb1f3
No known key found for this signature in database
GPG key ID: FEE0A611BEA6DEA0
24 changed files with 228 additions and 202 deletions

View file

@ -6,10 +6,11 @@ python:
- "3.5" - "3.5"
- "3.6" - "3.6"
- "3.7" - "3.7"
- "3.8"
- "nightly" - "nightly"
- "pypy3" - "pypy3"
sudo: false sudo: false
script: script:
- ./test/test_travis - ./test/test

View file

@ -1,7 +1,7 @@
The MIT License (MIT) The MIT License (MIT)
===================== =====================
**Copyright (c) 2014-2019 Anish Athalye (me@anishathalye.com)** **Copyright (c) 2014-2020 Anish Athalye (me@anishathalye.com)**
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View file

@ -1,4 +1,4 @@
# Dotbot [![Build Status](https://travis-ci.org/anishathalye/dotbot.svg?branch=master)](https://travis-ci.org/anishathalye/dotbot) # Dotbot [![Build Status](https://travis-ci.com/anishathalye/dotbot.svg?branch=master)](https://travis-ci.com/anishathalye/dotbot)
Dotbot makes installing your dotfiles as easy as `git clone $url && cd dotfiles Dotbot makes installing your dotfiles as easy as `git clone $url && cd dotfiles
&& ./install`, even on a freshly installed system! && ./install`, even on a freshly installed system!
@ -176,8 +176,10 @@ Available extended configuration parameters:
| `relink` | Removes the old target if it's a symlink (default:false) | | `relink` | Removes the old target if it's a symlink (default:false) |
| `force` | Force removes the old target, file or folder, and forces a new link (default:false) | | `force` | Force removes the old target, file or folder, and forces a new link (default:false) |
| `relative` | Use a relative path to the source when creating the symlink (default:false, absolute links) | | `relative` | Use a relative path to the source when creating the symlink (default:false, absolute links) |
| `canonicalize-path` | Resolve any symbolic links encountered in the source to symlink to the canonical path (default:true, real paths) |
| `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default:false) | | `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default:false) |
| `if` | Execute this in your `$SHELL` and only link if it is successful. | | `if` | Execute this in your `$SHELL` and only link if it is successful. |
| `ignore-missing` | Do not fail if the source is missing and create the link anyway (default:false) |
#### Example #### Example
@ -302,7 +304,9 @@ Clean commands are specified as an array of directories to be cleaned.
Clean commands support an extended configuration syntax. In this type of Clean commands support an extended configuration syntax. In this type of
configuration, commands are specified as directory paths mapping to options. If configuration, commands are specified as directory paths mapping to options. If
the `force` option is set to `true`, dead links are removed even if they don't the `force` option is set to `true`, dead links are removed even if they don't
point to a file inside the dotfiles directory. point to a file inside the dotfiles directory. If `recursive` is set to `true`,
the directory is traversed recursively (not recommended for `~` because it will
be slow).
#### Example #### Example
@ -310,8 +314,10 @@ point to a file inside the dotfiles directory.
- clean: ['~'] - clean: ['~']
- clean: - clean:
~/.config: ~/:
force: true force: true
~/.config:
recursive: true
``` ```
### Defaults ### Defaults
@ -395,7 +401,7 @@ Do you have a feature request, bug report, or patch? Great! See
## License ## License
Copyright (c) 2014-2019 Anish Athalye. Released under the MIT License. See Copyright (c) 2014-2020 Anish Athalye. Released under the MIT License. See
[LICENSE.md][license] for details. [LICENSE.md][license] for details.
[PyPI]: https://pypi.org/project/dotbot/ [PyPI]: https://pypi.org/project/dotbot/

View file

@ -1,4 +1,4 @@
from .cli import main from .cli import main
from .plugin import Plugin from .plugin import Plugin
__version__ = '1.16.0' __version__ = '1.17.0'

View file

@ -73,10 +73,10 @@ def main():
if not isinstance(tasks, list): if not isinstance(tasks, list):
raise ReadingError('Configuration file must be a list of tasks') raise ReadingError('Configuration file must be a list of tasks')
if options.base_directory: if options.base_directory:
base_directory = options.base_directory base_directory = os.path.abspath(options.base_directory)
else: else:
# default to directory of config file # default to directory of config file
base_directory = os.path.dirname(os.path.realpath(options.config_file)) base_directory = os.path.dirname(os.path.abspath(options.config_file))
os.chdir(base_directory) os.chdir(base_directory)
dispatcher = Dispatcher(base_directory) dispatcher = Dispatcher(base_directory)
success = dispatcher.dispatch(tasks) success = dispatcher.dispatch(tasks)

View file

@ -1,4 +1,5 @@
import copy import copy
import os
class Context(object): class Context(object):
''' '''
@ -13,8 +14,11 @@ class Context(object):
def set_base_directory(self, base_directory): def set_base_directory(self, base_directory):
self._base_directory = base_directory self._base_directory = base_directory
def base_directory(self): def base_directory(self, canonical_path=True):
return self._base_directory base_directory = self._base_directory
if canonical_path:
base_directory = os.path.realpath(base_directory)
return base_directory
def set_defaults(self, defaults): def set_defaults(self, defaults):
self._defaults = defaults self._defaults = defaults

View file

@ -10,8 +10,8 @@ class Dispatcher(object):
self._load_plugins() self._load_plugins()
def _setup_context(self, base_directory): def _setup_context(self, base_directory):
path = os.path.abspath(os.path.realpath( path = os.path.abspath(
os.path.expanduser(base_directory))) os.path.expanduser(base_directory))
if not os.path.exists(path): if not os.path.exists(path):
raise DispatchError('Nonexistent base directory') raise DispatchError('Nonexistent base directory')
self._context = Context(path) self._context = Context(path)

View file

@ -18,18 +18,20 @@ class Clean(dotbot.Plugin):
def _process_clean(self, targets): def _process_clean(self, targets):
success = True success = True
defaults = self._context.defaults().get(self._directive, {}) defaults = self._context.defaults().get(self._directive, {})
force = defaults.get('force', False)
for target in targets: for target in targets:
if isinstance(targets, dict): force = defaults.get('force', False)
recursive = defaults.get('recursive', False)
if isinstance(targets, dict) and isinstance(targets[target], dict):
force = targets[target].get('force', force) force = targets[target].get('force', force)
success &= self._clean(target, force) recursive = targets[target].get('recursive', recursive)
success &= self._clean(target, force, recursive)
if success: if success:
self._log.info('All targets have been cleaned') self._log.info('All targets have been cleaned')
else: else:
self._log.error('Some targets were not successfully cleaned') self._log.error('Some targets were not successfully cleaned')
return success return success
def _clean(self, target, force): def _clean(self, target, force, recursive):
''' '''
Cleans all the broken symbolic links in target if they point to Cleans all the broken symbolic links in target if they point to
a subdirectory of the base directory or if forced to clean. a subdirectory of the base directory or if forced to clean.
@ -39,6 +41,11 @@ class Clean(dotbot.Plugin):
return True return True
for item in os.listdir(os.path.expandvars(os.path.expanduser(target))): for item in os.listdir(os.path.expandvars(os.path.expanduser(target))):
path = os.path.join(os.path.expandvars(os.path.expanduser(target)), item) path = os.path.join(os.path.expandvars(os.path.expanduser(target)), item)
if recursive and os.path.isdir(path):
# isdir implies not islink -- we don't want to descend into
# symlinked directories. okay to do a recursive call here
# because depth should be fairly limited
self._clean(path, force, recursive)
if not os.path.exists(path) and os.path.islink(path): if not os.path.exists(path) and os.path.islink(path):
points_at = os.path.join(os.path.dirname(path), os.readlink(path)) points_at = os.path.join(os.path.dirname(path), os.readlink(path))
if self._in_directory(path, self._context.base_directory()) or force: if self._in_directory(path, self._context.base_directory()) or force:

View file

@ -26,19 +26,23 @@ class Link(dotbot.Plugin):
for destination, source in links.items(): for destination, source in links.items():
destination = os.path.expandvars(destination) destination = os.path.expandvars(destination)
relative = defaults.get('relative', False) relative = defaults.get('relative', False)
canonical_path = defaults.get('canonicalize-path', True)
force = defaults.get('force', False) force = defaults.get('force', False)
relink = defaults.get('relink', False) relink = defaults.get('relink', False)
create = defaults.get('create', False) create = defaults.get('create', False)
use_glob = defaults.get('glob', False) use_glob = defaults.get('glob', False)
test = defaults.get('if', None) test = defaults.get('if', None)
ignore_missing = defaults.get('ignore-missing', False)
if isinstance(source, dict): if isinstance(source, dict):
# extended config # extended config
test = source.get('if', test) test = source.get('if', test)
relative = source.get('relative', relative) relative = source.get('relative', relative)
canonical_path = source.get('canonicalize-path', canonical_path)
force = source.get('force', force) force = source.get('force', force)
relink = source.get('relink', relink) relink = source.get('relink', relink)
create = source.get('create', create) create = source.get('create', create)
use_glob = source.get('glob', use_glob) use_glob = source.get('glob', use_glob)
ignore_missing = source.get('ignore-missing', ignore_missing)
path = self._default_source(destination, source.get('path')) path = self._default_source(destination, source.get('path'))
else: else:
path = self._default_source(destination, source) path = self._default_source(destination, source)
@ -49,25 +53,25 @@ class Link(dotbot.Plugin):
if use_glob: if use_glob:
self._log.debug("Globbing with path: " + str(path)) self._log.debug("Globbing with path: " + str(path))
glob_results = glob.glob(path) glob_results = glob.glob(path)
if len(glob_results) is 0: if len(glob_results) == 0:
self._log.warning("Globbing couldn't find anything matching " + str(path)) self._log.warning("Globbing couldn't find anything matching " + str(path))
success = False success = False
continue continue
glob_star_loc = path.find('*') glob_star_loc = path.find('*')
if glob_star_loc is -1 and destination[-1] is '/': if glob_star_loc == -1 and destination[-1] == '/':
self._log.error("Ambiguous action requested.") self._log.error("Ambiguous action requested.")
self._log.error("No wildcard in glob, directory use undefined: " + self._log.error("No wildcard in glob, directory use undefined: " +
destination + " -> " + str(glob_results)) destination + " -> " + str(glob_results))
self._log.warning("Did you want to link the directory or into it?") self._log.warning("Did you want to link the directory or into it?")
success = False success = False
continue continue
elif glob_star_loc is -1 and len(glob_results) is 1: elif glob_star_loc == -1 and len(glob_results) == 1:
# perform a normal link operation # perform a normal link operation
if create: if create:
success &= self._create(destination) success &= self._create(destination)
if force or relink: if force or relink:
success &= self._delete(path, destination, relative, force) success &= self._delete(path, destination, relative, canonical_path, force)
success &= self._link(path, destination, relative) success &= self._link(path, destination, relative, canonical_path, ignore_missing)
else: else:
self._log.lowinfo("Globs from '" + path + "': " + str(glob_results)) self._log.lowinfo("Globs from '" + path + "': " + str(glob_results))
glob_base = path[:glob_star_loc] glob_base = path[:glob_star_loc]
@ -77,19 +81,23 @@ class Link(dotbot.Plugin):
if create: if create:
success &= self._create(glob_link_destination) success &= self._create(glob_link_destination)
if force or relink: if force or relink:
success &= self._delete(glob_full_item, glob_link_destination, relative, force) success &= self._delete(glob_full_item, glob_link_destination, relative, canonical_path, force)
success &= self._link(glob_full_item, glob_link_destination, relative) success &= self._link(glob_full_item, glob_link_destination, relative, canonical_path, ignore_missing)
else: else:
if create: if create:
success &= self._create(destination) success &= self._create(destination)
if not self._exists(os.path.join(self._context.base_directory(), path)): if not ignore_missing and not self._exists(os.path.join(self._context.base_directory(), path)):
# we seemingly check this twice (here and in _link) because
# if the file doesn't exist and force is True, we don't
# want to remove the original (this is tested by
# link-force-leaves-when-nonexistent.bash)
success = False success = False
self._log.warning('Nonexistent source %s -> %s' % self._log.warning('Nonexistent source %s -> %s' %
(destination, path)) (destination, path))
continue continue
if force or relink: if force or relink:
success &= self._delete(path, destination, relative, force) success &= self._delete(path, destination, relative, canonical_path, force)
success &= self._link(path, destination, relative) success &= self._link(path, destination, relative, canonical_path, ignore_missing)
if success: if success:
self._log.info('All links have been set up') self._log.info('All links have been set up')
else: else:
@ -153,9 +161,9 @@ class Link(dotbot.Plugin):
self._log.lowinfo('Creating directory %s' % parent) self._log.lowinfo('Creating directory %s' % parent)
return success return success
def _delete(self, source, path, relative, force): def _delete(self, source, path, relative, canonical_path, force):
success = True success = True
source = os.path.join(self._context.base_directory(), source) source = os.path.join(self._context.base_directory(canonical_path=canonical_path), source)
fullpath = os.path.expanduser(path) fullpath = os.path.expanduser(path)
if relative: if relative:
source = self._relative_path(source, fullpath) source = self._relative_path(source, fullpath)
@ -189,7 +197,7 @@ class Link(dotbot.Plugin):
destination_dir = os.path.dirname(destination) destination_dir = os.path.dirname(destination)
return os.path.relpath(source, destination_dir) return os.path.relpath(source, destination_dir)
def _link(self, source, link_name, relative): def _link(self, source, link_name, relative, canonical_path, ignore_missing):
''' '''
Links link_name to source. Links link_name to source.
@ -197,7 +205,8 @@ class Link(dotbot.Plugin):
''' '''
success = False success = False
destination = os.path.expanduser(link_name) destination = os.path.expanduser(link_name)
absolute_source = os.path.join(self._context.base_directory(), source) base_directory = self._context.base_directory(canonical_path=canonical_path)
absolute_source = os.path.join(base_directory, source)
if relative: if relative:
source = self._relative_path(absolute_source, destination) source = self._relative_path(absolute_source, destination)
else: else:
@ -209,7 +218,7 @@ class Link(dotbot.Plugin):
# we need to use absolute_source below because our cwd is the dotfiles # we need to use absolute_source below because our cwd is the dotfiles
# directory, and if source is relative, it will be relative to the # directory, and if source is relative, it will be relative to the
# destination directory # destination directory
elif not self._exists(link_name) and self._exists(absolute_source): elif not self._exists(link_name) and (ignore_missing or self._exists(absolute_source)):
try: try:
os.symlink(source, destination) os.symlink(source, destination)
except OSError: except OSError:

@ -1 +1 @@
Subproject commit 0f64cbfa54b0b22dc7b776b7b98a7cd657e84d78 Subproject commit 2f463cf5b0e98a52bc20e348d1e69761bf263b86

View file

@ -73,7 +73,7 @@ setup(
], ],
install_requires=[ install_requires=[
'PyYAML>=5.1.2,<6', 'PyYAML>=5.3,<6',
], ],
# To provide executable scripts, use entry points in preference to the # To provide executable scripts, use entry points in preference to the

View file

@ -36,14 +36,24 @@ git submodule update --init --recursive
Running the Tests Running the Tests
----------------- -----------------
Before running the tests, the virtual machine must be running. It can be Before running the tests, you must SSH into the VM. Start it with `vagrant up`
started by running `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 First, you must install a version of Python to test against, using `pyenv
passing paths to the tests as arguments to `./test`. 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 The VM mounts the Dotbot directory in `/dotbot` as read-only (you can make
<version>` - for example, `./test --version 3.4.3`. 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 When finished with testing, it is good to shut down the virtual machine by
running `vagrant halt`. running `vagrant halt`.

4
test/Vagrantfile vendored
View file

@ -1,8 +1,8 @@
Vagrant.configure(2) do |config| Vagrant.configure(2) do |config|
config.vm.box = 'debian/buster64' config.vm.box = 'ubuntu/bionic64'
# sync by copying for isolation # sync by copying for isolation
config.vm.synced_folder "..", "/dotbot", type: "rsync" config.vm.synced_folder "..", "/dotbot", mount_options: ["ro"]
# disable default synced folder # disable default synced folder
config.vm.synced_folder ".", "/vagrant", disabled: true config.vm.synced_folder ".", "/vagrant", disabled: true

View file

@ -1,6 +1,3 @@
MAXRETRY=5
TIMEOUT=1
red() { red() {
if [ -t 1 ]; then if [ -t 1 ]; then
printf "\033[31m%s\033[0m\n" "$*" printf "\033[31m%s\033[0m\n" "$*"
@ -26,52 +23,35 @@ yellow() {
} }
check_prereqs() { check_env() {
if ! (vagrant ssh -c 'exit') >/dev/null 2>&1; then if [[ "$(whoami)" != "vagrant" && ( "${TRAVIS}" != true || "${CI}" != true ) ]]; then
>&2 echo "vagrant vm must be running." die "tests must be run inside Travis or Vagrant"
return 1
fi 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() { cleanup() {
vagrant ssh -c " (
find . -not \\( \ if [ "$(whoami)" == "vagrant" ]; then
cd $HOME
find . -not \( \
-path './.pyenv' -o \ -path './.pyenv' -o \
-path './.pyenv/*' -o \ -path './.pyenv/*' -o \
-path './.bashrc' -o \ -path './.bashrc' -o \
-path './.profile' -o \ -path './.profile' -o \
-path './.ssh' -o \ -path './.ssh' -o \
-path './.ssh/*' \ -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() { initialize() {
echo "initializing." 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_run=0
tests_passed=0 tests_passed=0
tests_failed=0 tests_failed=0
@ -96,8 +76,7 @@ run_test() {
tests_run=$((tests_run + 1)) tests_run=$((tests_run + 1))
printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}" printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}"
cleanup cleanup
vagrant ssh -c "pyenv local ${2}" >/dev/null 2>&1 if (cd "${BASEDIR}/test/tests" && DOTBOT_TEST=true bash "${1}"); then
if vagrant ssh -c "cd /dotbot/test/tests && bash ${1}" 2>/dev/null; then
pass pass
else else
fail fail

View file

@ -1,37 +1,21 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" export BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${BASEDIR}" cd "${BASEDIR}/test"
. "./driver-lib.bash" . "./driver-lib.bash"
date_stamp="$(date --rfc-3339=ns)"
start="$(date +%s)" start="$(date +%s)"
check_prereqs || die "prerequisites unsatsfied." check_env
# 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}"
declare -a tests=() declare -a tests=()
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
while read file; do while read file; do
tests+=("${file}") tests+=("${file}")
done < <(find tests -type f -name '*.bash') done < <(find tests -type f -name '*.bash' | sort)
else else
tests=("$@") tests=("$@")
fi fi

View file

@ -1,6 +1,5 @@
DEBUG=${DEBUG:-false} DEBUG=${DEBUG:-false}
USE_VAGRANT=${USE_VAGRANT:-true} DOTBOT_EXEC="${BASEDIR}/bin/dotbot"
DOTBOT_EXEC=${DOTBOT_EXEC:-"python /dotbot/bin/dotbot"}
DOTFILES="/home/$(whoami)/dotfiles" DOTFILES="/home/$(whoami)/dotfiles"
INSTALL_CONF='install.conf.yaml' INSTALL_CONF='install.conf.yaml'
INSTALL_CONF_JSON='install.conf.json' INSTALL_CONF_JSON='install.conf.json'
@ -29,17 +28,15 @@ test_expect_failure() {
fi fi
} }
check_vm() { check_env() {
if [ "$(whoami)" != "vagrant" ]; then if [ "${DOTBOT_TEST}" != "true" ]; then
>&2 echo "test can't run outside vm!" >&2 echo "test must be run by test driver"
exit 1 exit 1
fi fi
} }
initialize() { initialize() {
if ${USE_VAGRANT}; then check_env
check_vm
fi
echo "${test_description}" echo "${test_description}"
mkdir -p "${DOTFILES}" mkdir -p "${DOTFILES}"
cd cd

View file

@ -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}

View file

@ -0,0 +1,19 @@
test_description='clean uses default unless overridden'
. '../test-lib.bash'
test_expect_success 'setup' '
ln -s /nowhere ~/.g
'
test_expect_success 'run' '
run_dotbot <<EOF
- clean:
~/nonexistent:
force: true
~/:
EOF
'
test_expect_success 'test' '
test -h ~/.g
'

View file

@ -0,0 +1,34 @@
test_description='clean removes recursively'
. '../test-lib.bash'
test_expect_success 'setup' '
mkdir -p ~/a/b
ln -s /nowhere ~/c
ln -s /nowhere ~/a/d
ln -s /nowhere ~/a/b/e
'
test_expect_success 'run' '
run_dotbot <<EOF
- clean:
~/:
force: true
EOF
'
test_expect_success 'test' '
! test -h ~/c && test -h ~/a/d && test -h ~/a/b/e
'
test_expect_success 'run 2' '
run_dotbot <<EOF
- clean:
~/:
force: true
recursive: true
EOF
'
test_expect_success 'test 2' '
! test -h ~/a/d && ! test -h ~/a/b/e
'

View file

@ -1,18 +1,16 @@
test_description='can find python executable with different names' test_description='can find python executable with different names'
. '../test-lib.bash' . '../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` # the test machine needs to have a binary named `python`
test_expect_success 'setup' ' test_expect_success 'setup' '
mkdir ~/tmp_bin && mkdir ~/tmp_bin &&
( (
IFS=: IFS=:
for p in $PATH; do 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 {})'"'"' \; '"'"'ln -sf {} $HOME/tmp_bin/$(basename {})'"'"' \;
fi
done done
) && ) &&
rm -f ~/tmp_bin/python && rm -f ~/tmp_bin/python &&

View file

@ -0,0 +1,20 @@
test_description='linking canonicalizes path by default'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/f &&
ln -s dotfiles dotfiles-symlink
'
test_expect_success 'run' '
cat > "${DOTFILES}/${INSTALL_CONF}" <<EOF
- link:
~/.f:
path: f
EOF
${DOTBOT_EXEC} -c dotfiles-symlink/${INSTALL_CONF}
'
test_expect_success 'test' '
[ "$(readlink ~/.f | cut -d/ -f4-)" = "dotfiles/f" ]
'

View file

@ -0,0 +1,23 @@
test_description='link is created even if source is missing'
. '../test-lib.bash'
test_expect_failure 'run' '
run_dotbot <<EOF
- link:
~/missing_link:
path: missing
EOF
'
test_expect_success 'run 2' '
run_dotbot <<EOF
- link:
~/missing_link:
path: missing
ignore-missing: true
EOF
'
test_expect_success 'test' '
test -L ~/missing_link
'

View file

@ -0,0 +1,23 @@
test_description='linking path canonicalization can be disabled'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/f &&
ln -s dotfiles dotfiles-symlink
'
test_expect_success 'run' '
cat > "${DOTFILES}/${INSTALL_CONF}" <<EOF
- defaults:
link:
canonicalize-path: false
- link:
~/.f:
path: f
EOF
${DOTBOT_EXEC} -c ./dotfiles-symlink/${INSTALL_CONF}
'
test_expect_success 'test' '
[ "$(readlink ~/.f | cut -d/ -f4-)" = "dotfiles-symlink/f" ]
'

View file

@ -4,11 +4,7 @@ test_description='install shim works'
test_expect_success 'setup' ' test_expect_success 'setup' '
cd ${DOTFILES} cd ${DOTFILES}
git init git init
if ${USE_VAGRANT}; then git submodule add ${BASEDIR} dotbot
git submodule add /dotbot dotbot
else
git submodule add ${BASEDIR} dotbot
fi
cp ./dotbot/tools/git-submodule/install . cp ./dotbot/tools/git-submodule/install .
echo "pear" > ${DOTFILES}/foo echo "pear" > ${DOTFILES}/foo
' '
@ -18,9 +14,6 @@ cat > ${DOTFILES}/install.conf.yaml <<EOF
- link: - link:
~/.foo: foo ~/.foo: foo
EOF EOF
if ! ${USE_VAGRANT}; then
sed -i "" "1 s/sh$/python/" ${DOTFILES}/dotbot/bin/dotbot
fi
${DOTFILES}/install ${DOTFILES}/install
' '