Compare commits
30 commits
860a56580b
...
da928a4c6b
Author | SHA1 | Date | |
---|---|---|---|
|
da928a4c6b | ||
|
1d56409bc1 | ||
|
8468213bc6 | ||
|
e810f42ca2 | ||
|
593584154d | ||
|
3965e1a390 | ||
|
e0c78d9c56 | ||
|
d12aa83673 | ||
|
7a586aa4c5 | ||
|
d055802a66 | ||
|
59b1b85d07 | ||
|
ee3646bba3 | ||
|
57a27a770c | ||
|
5c0ddc6fc1 | ||
|
30f310e935 | ||
|
74aca02157 | ||
|
1ff796a9dc | ||
|
ea98e5eafc | ||
|
566ba0b853 | ||
|
b5499c7dc5 | ||
|
a8dd89f48f | ||
|
68246ba33e | ||
|
b8dfbae730 | ||
|
a2846d0a61 | ||
|
5b7db08e8a | ||
|
5d11c7954d | ||
|
b59b3af448 | ||
|
78bec43e33 | ||
|
4469b857aa | ||
|
c015f7bce8 |
95 changed files with 2402 additions and 2120 deletions
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
|
@ -6,11 +6,22 @@ on:
|
|||
- cron: '0 8 * * 6'
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python: ["2.7", "pypy2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "pypy3.9"]
|
||||
name: "Test: Python ${{ matrix.python }}"
|
||||
os: ["ubuntu-20.04", "macos-latest"]
|
||||
python: ["2.7", "pypy-2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.9"]
|
||||
include:
|
||||
- os: "windows-latest"
|
||||
python: "3.8"
|
||||
- os: "windows-latest"
|
||||
python: "3.9"
|
||||
- os: "windows-latest"
|
||||
python: "3.10"
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "Test: Python ${{ matrix.python }} on ${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
@ -18,10 +29,20 @@ jobs:
|
|||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
- run: ./test/test
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools
|
||||
python -m pip install tox tox-gh-actions
|
||||
- name: "Run tests"
|
||||
run: |
|
||||
python -m tox
|
||||
python -m tox -e coverage_report
|
||||
- uses: codecov/codecov-action@v3
|
||||
|
||||
fmt:
|
||||
name: Format
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: psf/black@stable
|
||||
- uses: isort/isort-action@v1
|
||||
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,4 +1,11 @@
|
|||
*.egg-info
|
||||
*.pyc
|
||||
.coverage*
|
||||
.eggs/
|
||||
.idea/
|
||||
.tox/
|
||||
.venv/
|
||||
build/
|
||||
coverage.xml
|
||||
dist/
|
||||
htmlcov/
|
||||
|
|
|
@ -50,6 +50,41 @@ used in the rest of the project. The version history should be clean, and
|
|||
commit messages should be descriptive and [properly
|
||||
formatted][commit-messages].
|
||||
|
||||
When preparing a patch, it's recommended that you add unit tests
|
||||
that demonstrate the bug is fixed (or that the feature works).
|
||||
You can run the tests on your local machine by installing the `dev` extras.
|
||||
The steps below do this using a virtual environment:
|
||||
|
||||
```shell
|
||||
# Create a local virtual environment
|
||||
$ python -m venv .venv
|
||||
|
||||
# Activate the virtual environment
|
||||
# Cygwin, Linux, and MacOS:
|
||||
$ . .venv/bin/activate
|
||||
# Windows Powershell:
|
||||
$ & .venv\Scripts\Activate.ps1
|
||||
|
||||
# Update pip and setuptools
|
||||
(.venv) $ python -m pip install -U pip setuptools
|
||||
|
||||
# Install dotbot and its development dependencies
|
||||
(.venv) $ python -m pip install -e .[dev]
|
||||
|
||||
# Run the unit tests
|
||||
(.venv) $ tox
|
||||
```
|
||||
|
||||
If you prefer to run the tests in an isolated container using Docker, you can
|
||||
do so with the following:
|
||||
|
||||
```
|
||||
docker run -it --rm -v "${PWD}:/dotbot" -w /dotbot python:3.10-alpine /bin/sh
|
||||
```
|
||||
|
||||
After spawning the container, follow the same instructions as above (create a
|
||||
virtualenv, ..., run the tests).
|
||||
|
||||
---
|
||||
|
||||
If you have any questions about anything, feel free to [ask][email]!
|
||||
|
|
|
@ -67,7 +67,9 @@ touch install.conf.yaml
|
|||
```
|
||||
|
||||
If you are using PowerShell instead of a POSIX shell, you can use the provided
|
||||
`install.ps1` script instead of `install`.
|
||||
`install.ps1` script instead of `install`. On Windows, Dotbot only supports
|
||||
Python 3.8+, and it requires that your account is [allowed to create symbolic
|
||||
links][windows-symlinks].
|
||||
|
||||
To get started, you just need to fill in the `install.conf.yaml` and Dotbot
|
||||
will take care of the rest. To help you get started we have [an
|
||||
|
@ -466,7 +468,7 @@ Copyright (c) 2014-2021 Anish Athalye. Released under the MIT License. See
|
|||
[init-dotfiles]: https://github.com/Vaelatern/init-dotfiles
|
||||
[dotfiles-template]: https://github.com/anishathalye/dotfiles_template
|
||||
[inspiration]: https://github.com/anishathalye/dotbot/wiki/Users
|
||||
[managing-dotfiles-post]: http://www.anishathalye.com/2014/08/03/managing-your-dotfiles/
|
||||
[windows-symlinks]: https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
|
||||
[json2yaml]: https://www.json2yaml.com/
|
||||
[plugins]: https://github.com/anishathalye/dotbot/wiki/Plugins
|
||||
[wiki]: https://github.com/anishathalye/dotbot/wiki
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .cli import main
|
||||
from .plugin import Plugin
|
||||
|
||||
__version__ = "1.19.0"
|
||||
__version__ = "1.19.1"
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import os, glob
|
||||
import sys
|
||||
|
||||
from argparse import ArgumentParser, RawTextHelpFormatter
|
||||
from .config import ConfigReader, ReadingError
|
||||
from .dispatcher import Dispatcher, DispatchError
|
||||
from .messenger import Messenger
|
||||
from .messenger import Level
|
||||
from .util import module
|
||||
|
||||
import dotbot
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from argparse import ArgumentParser, RawTextHelpFormatter
|
||||
|
||||
import dotbot
|
||||
|
||||
from .config import ConfigReader, ReadingError
|
||||
from .dispatcher import Dispatcher, DispatchError
|
||||
from .messenger import Level, Messenger
|
||||
from .plugins import Clean, Create, Link, Shell
|
||||
from .util import module
|
||||
|
||||
|
||||
def add_options(parser):
|
||||
|
@ -118,9 +118,10 @@ def main():
|
|||
else:
|
||||
log.use_color(sys.stdout.isatty())
|
||||
|
||||
plugins = []
|
||||
plugin_directories = list(options.plugin_dirs)
|
||||
if not options.disable_built_in_plugins:
|
||||
from .plugins import Clean, Create, Link, Shell
|
||||
plugins.extend([Clean, Create, Link, Shell])
|
||||
plugin_paths = []
|
||||
for directory in plugin_directories:
|
||||
for plugin_path in glob.glob(os.path.join(directory, "*.py")):
|
||||
|
@ -129,7 +130,7 @@ def main():
|
|||
plugin_paths.append(plugin_path)
|
||||
for plugin_path in plugin_paths:
|
||||
abspath = os.path.abspath(plugin_path)
|
||||
module.load(abspath)
|
||||
plugins.extend(module.load(abspath))
|
||||
if not options.config_file:
|
||||
log.error("No configuration file specified")
|
||||
exit(1)
|
||||
|
@ -151,6 +152,7 @@ def main():
|
|||
skip=options.skip,
|
||||
exit_on_failure=options.exit_on_failure,
|
||||
options=options,
|
||||
plugins=plugins,
|
||||
)
|
||||
success = dispatcher.dispatch(tasks)
|
||||
if success:
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import yaml
|
||||
import json
|
||||
import os.path
|
||||
|
||||
import yaml
|
||||
|
||||
from .util import string
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import os
|
||||
from argparse import Namespace
|
||||
from .plugin import Plugin
|
||||
from .messenger import Messenger
|
||||
|
||||
from .context import Context
|
||||
from .messenger import Messenger
|
||||
from .plugin import Plugin
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
def __init__(
|
||||
self, base_directory, only=None, skip=None, exit_on_failure=False, options=Namespace()
|
||||
self,
|
||||
base_directory,
|
||||
only=None,
|
||||
skip=None,
|
||||
exit_on_failure=False,
|
||||
options=Namespace(),
|
||||
plugins=None,
|
||||
):
|
||||
self._log = Messenger()
|
||||
self._setup_context(base_directory, options)
|
||||
self._load_plugins()
|
||||
plugins = plugins or []
|
||||
self._plugins = [plugin(self._context) for plugin in plugins]
|
||||
self._only = only
|
||||
self._skip = skip
|
||||
self._exit = exit_on_failure
|
||||
|
@ -65,9 +73,6 @@ class Dispatcher(object):
|
|||
return False
|
||||
return success
|
||||
|
||||
def _load_plugins(self):
|
||||
self._plugins = [plugin(self._context) for plugin in Plugin.__subclasses__()]
|
||||
|
||||
|
||||
class DispatchError(Exception):
|
||||
pass
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
from .messenger import Messenger
|
||||
from .level import Level
|
||||
from .messenger import Messenger
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from ..util.singleton import Singleton
|
||||
from ..util.compat import with_metaclass
|
||||
from ..util.singleton import Singleton
|
||||
from .color import Color
|
||||
from .level import Level
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from .messenger import Messenger
|
||||
from .context import Context
|
||||
from .messenger import Messenger
|
||||
|
||||
|
||||
class Plugin(object):
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import os
|
||||
import dotbot
|
||||
import sys
|
||||
|
||||
from ..plugin import Plugin
|
||||
|
||||
|
||||
class Clean(dotbot.Plugin):
|
||||
class Clean(Plugin):
|
||||
"""
|
||||
Cleans broken symbolic links.
|
||||
"""
|
||||
|
@ -42,7 +44,9 @@ class Clean(dotbot.Plugin):
|
|||
self._log.debug("Ignoring nonexistent directory %s" % target)
|
||||
return True
|
||||
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.abspath(
|
||||
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
|
||||
|
@ -50,6 +54,8 @@ class Clean(dotbot.Plugin):
|
|||
self._clean(path, force, recursive)
|
||||
if not os.path.exists(path) and os.path.islink(path):
|
||||
points_at = os.path.join(os.path.dirname(path), os.readlink(path))
|
||||
if sys.platform[:5] == "win32" and points_at.startswith("\\\\?\\"):
|
||||
points_at = points_at[4:]
|
||||
if self._in_directory(path, self._context.base_directory()) or force:
|
||||
self._log.lowinfo("Removing invalid link %s -> %s" % (path, points_at))
|
||||
os.remove(path)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import os
|
||||
import dotbot
|
||||
|
||||
from ..plugin import Plugin
|
||||
|
||||
|
||||
class Create(dotbot.Plugin):
|
||||
class Create(Plugin):
|
||||
"""
|
||||
Create empty paths.
|
||||
"""
|
||||
|
@ -21,7 +22,7 @@ class Create(dotbot.Plugin):
|
|||
success = True
|
||||
defaults = self._context.defaults().get("create", {})
|
||||
for key in paths:
|
||||
path = os.path.expandvars(os.path.expanduser(key))
|
||||
path = os.path.abspath(os.path.expandvars(os.path.expanduser(key)))
|
||||
mode = defaults.get("mode", 0o777) # same as the default for os.makedirs
|
||||
if isinstance(paths, dict):
|
||||
options = paths[key]
|
||||
|
@ -48,6 +49,9 @@ class Create(dotbot.Plugin):
|
|||
try:
|
||||
self._log.lowinfo("Creating path %s" % path)
|
||||
os.makedirs(path, mode)
|
||||
# On Windows, the *mode* argument to `os.makedirs()` is ignored.
|
||||
# The mode must be set explicitly in a follow-up call.
|
||||
os.chmod(path, mode)
|
||||
except OSError:
|
||||
self._log.warning("Failed to create path %s" % path)
|
||||
success = False
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import os
|
||||
import sys
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import dotbot
|
||||
import dotbot.util
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from ..plugin import Plugin
|
||||
from ..util import shell_command
|
||||
|
||||
|
||||
class Link(dotbot.Plugin):
|
||||
class Link(Plugin):
|
||||
"""
|
||||
Symbolically links dotfiles.
|
||||
"""
|
||||
|
@ -58,7 +58,7 @@ class Link(dotbot.Plugin):
|
|||
if test is not None and not self._test_success(test):
|
||||
self._log.lowinfo("Skipping %s" % destination)
|
||||
continue
|
||||
path = os.path.expandvars(os.path.expanduser(path))
|
||||
path = os.path.normpath(os.path.expandvars(os.path.expanduser(path)))
|
||||
if use_glob:
|
||||
glob_results = self._create_glob_results(path, exclude_paths)
|
||||
if len(glob_results) == 0:
|
||||
|
@ -140,7 +140,7 @@ class Link(dotbot.Plugin):
|
|||
return success
|
||||
|
||||
def _test_success(self, command):
|
||||
ret = dotbot.util.shell_command(command, cwd=self._context.base_directory())
|
||||
ret = shell_command(command, cwd=self._context.base_directory())
|
||||
if ret != 0:
|
||||
self._log.debug("Test '%s' returned false" % command)
|
||||
return ret == 0
|
||||
|
@ -166,6 +166,8 @@ class Link(dotbot.Plugin):
|
|||
return []
|
||||
# call glob.glob; only python >= 3.5 supports recursive globs
|
||||
found = glob.glob(path) if (sys.version_info < (3, 5)) else glob.glob(path, recursive=True)
|
||||
# normalize paths to ensure cross-platform compatibility
|
||||
found = [os.path.normpath(p) for p in found]
|
||||
# if using recursive glob (`**`), filter results to return only files:
|
||||
if "**" in path and not path.endswith(str(os.sep)):
|
||||
self._log.debug("Excluding directories from recursive glob: " + str(path))
|
||||
|
@ -197,7 +199,10 @@ class Link(dotbot.Plugin):
|
|||
Returns the destination of the symbolic link.
|
||||
"""
|
||||
path = os.path.expanduser(path)
|
||||
return os.readlink(path)
|
||||
path = os.readlink(path)
|
||||
if sys.platform[:5] == "win32" and path.startswith("\\\\?\\"):
|
||||
path = path[4:]
|
||||
return path
|
||||
|
||||
def _exists(self, path):
|
||||
"""
|
||||
|
@ -223,7 +228,7 @@ class Link(dotbot.Plugin):
|
|||
def _delete(self, source, path, relative, canonical_path, force):
|
||||
success = True
|
||||
source = os.path.join(self._context.base_directory(canonical_path=canonical_path), source)
|
||||
fullpath = os.path.expanduser(path)
|
||||
fullpath = os.path.abspath(os.path.expanduser(path))
|
||||
if relative:
|
||||
source = self._relative_path(source, fullpath)
|
||||
if (self._is_link(path) and self._link_destination(path) != source) or (
|
||||
|
@ -264,9 +269,10 @@ class Link(dotbot.Plugin):
|
|||
Returns true if successfully linked files.
|
||||
"""
|
||||
success = False
|
||||
destination = os.path.expanduser(link_name)
|
||||
destination = os.path.abspath(os.path.expanduser(link_name))
|
||||
base_directory = self._context.base_directory(canonical_path=canonical_path)
|
||||
absolute_source = os.path.join(base_directory, source)
|
||||
link_name = os.path.normpath(link_name)
|
||||
if relative:
|
||||
source = self._relative_path(absolute_source, destination)
|
||||
else:
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import os
|
||||
import subprocess
|
||||
import dotbot
|
||||
import dotbot.util
|
||||
from ..plugin import Plugin
|
||||
from ..util import shell_command
|
||||
|
||||
|
||||
class Shell(dotbot.Plugin):
|
||||
class Shell(Plugin):
|
||||
"""
|
||||
Run arbitrary shell commands.
|
||||
"""
|
||||
|
@ -51,7 +49,7 @@ class Shell(dotbot.Plugin):
|
|||
self._log.lowinfo("%s [%s]" % (msg, cmd))
|
||||
stdout = options.get("stdout", stdout)
|
||||
stderr = options.get("stderr", stderr)
|
||||
ret = dotbot.util.shell_command(
|
||||
ret = shell_command(
|
||||
cmd,
|
||||
cwd=self._context.base_directory(),
|
||||
enable_stdin=stdin,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import subprocess
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
|
||||
def shell_command(command, cwd=None, enable_stdin=False, enable_stdout=False, enable_stderr=False):
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import sys, os.path
|
||||
import os
|
||||
import sys
|
||||
|
||||
from dotbot.plugin import Plugin
|
||||
|
||||
# We keep references to loaded modules so they don't get garbage collected.
|
||||
loaded_modules = []
|
||||
|
@ -7,8 +10,17 @@ loaded_modules = []
|
|||
def load(path):
|
||||
basename = os.path.basename(path)
|
||||
module_name, extension = os.path.splitext(basename)
|
||||
plugin = load_module(module_name, path)
|
||||
loaded_modules.append(plugin)
|
||||
loaded_module = load_module(module_name, path)
|
||||
plugins = []
|
||||
for name in dir(loaded_module):
|
||||
possible_plugin = getattr(loaded_module, name)
|
||||
try:
|
||||
if issubclass(possible_plugin, Plugin) and possible_plugin is not Plugin:
|
||||
plugins.append(possible_plugin)
|
||||
except TypeError:
|
||||
pass
|
||||
loaded_modules.append(loaded_module)
|
||||
return plugins
|
||||
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
|
|
10
setup.py
10
setup.py
|
@ -1,8 +1,8 @@
|
|||
from setuptools import setup, find_packages
|
||||
import re
|
||||
from codecs import open # For a consistent encoding
|
||||
from os import path
|
||||
import re
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
here = path.dirname(__file__)
|
||||
|
||||
|
@ -58,6 +58,12 @@ setup(
|
|||
install_requires=[
|
||||
"PyYAML>=5.3,<6",
|
||||
],
|
||||
extras_require={
|
||||
"dev": {
|
||||
"pytest",
|
||||
"tox",
|
||||
}
|
||||
},
|
||||
# To provide executable scripts, use entry points in preference to the
|
||||
# "scripts" keyword. Entry points provide cross-platform support and allow
|
||||
# pip to create the appropriate form of executable for the target platform.
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[Vagrantfile]
|
||||
indent_size = 2
|
||||
|
||||
[{test,test_travis}]
|
||||
indent_size = 4
|
2
test/.gitignore
vendored
2
test/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
.vagrant/
|
||||
*.log
|
|
@ -1,86 +0,0 @@
|
|||
Testing
|
||||
=======
|
||||
|
||||
Dotbot testing code uses [Vagrant] to run all tests inside a virtual machine to
|
||||
have tests be completely isolated from the host machine. Specifically, you
|
||||
will need both:
|
||||
|
||||
- [VirtualBox]
|
||||
- [Vagrant]
|
||||
|
||||
Install Dotbot dependencies
|
||||
---------------------------
|
||||
|
||||
Ensure you have updated the `dotbot` submodule dependencies, on the host machine:
|
||||
|
||||
```bash
|
||||
git submodule sync --quiet --recursive
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
Install Vagrant
|
||||
---------------
|
||||
|
||||
### Debian-based distributions
|
||||
|
||||
```bash
|
||||
sudo apt install vagrant virtualbox
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
You can download those directly from the above URLs, or via some MacOS package managers.
|
||||
e.g. using [HomeBrew](https://brew.sh/):
|
||||
|
||||
```bash
|
||||
brew cask install virtualbox
|
||||
brew cask install vagrant
|
||||
# optional, adding menu-bar support:
|
||||
brew cask install vagrant-manager
|
||||
```
|
||||
|
||||
Running the Tests
|
||||
-----------------
|
||||
|
||||
Before running the tests, you must start and `ssh` into the VM:
|
||||
|
||||
```bash
|
||||
vagrant up
|
||||
vagrant ssh
|
||||
```
|
||||
|
||||
All remaining commands are run inside the VM.
|
||||
|
||||
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}
|
||||
|
||||
The VM mounts your host's Dotbot directory in `/dotbot` as read-only, allowing
|
||||
you to make edits on your host machine. Run the entire test suite by:
|
||||
|
||||
```bash
|
||||
cd /dotbot/test
|
||||
./test
|
||||
```
|
||||
|
||||
Selected tests can be run by passing paths to the tests as arguments, e.g.:
|
||||
|
||||
```bash
|
||||
./test tests/create.bash tests/defaults.bash
|
||||
```
|
||||
|
||||
To debug tests, you can run the test driver with the `--debug` (or `-d` short
|
||||
form) flag, e.g. `./test --debug tests/link-if.bash`. This will enable printing
|
||||
stdout/stderr.
|
||||
|
||||
When finished with testing, it is good to shut down the virtual machine by
|
||||
running `vagrant halt`.
|
||||
|
||||
[VirtualBox]: https://www.virtualbox.org/
|
||||
[Vagrant]: https://www.vagrantup.com/
|
28
test/Vagrantfile
vendored
28
test/Vagrantfile
vendored
|
@ -1,28 +0,0 @@
|
|||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = 'ubuntu/jammy64'
|
||||
|
||||
config.vm.synced_folder "..", "/dotbot", mount_options: ["ro"]
|
||||
|
||||
# disable default synced folder
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
|
||||
# install packages
|
||||
config.vm.provision "shell", inline: <<-EOS
|
||||
apt-get -y update
|
||||
apt-get install -y git make build-essential libssl-dev zlib1g-dev \
|
||||
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
|
||||
libncurses5-dev
|
||||
EOS
|
||||
|
||||
# install pyenv
|
||||
config.vm.provision "shell", privileged: false, inline: <<-EOS
|
||||
rm -rf ~/.pyenv
|
||||
git clone https://github.com/pyenv/pyenv.git ~/.pyenv
|
||||
cat <<-'PYENV' > ~/.bashrc
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||
eval "$(pyenv init --path)"
|
||||
eval "$(pyenv init -)"
|
||||
PYENV
|
||||
EOS
|
||||
end
|
|
@ -1,99 +0,0 @@
|
|||
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_env() {
|
||||
if [[ "$(whoami)" != "vagrant" && "${CI}" != true ]]; then
|
||||
die "tests must be run inside Vagrant or CI"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf ~/fakehome
|
||||
mkdir -p ~/fakehome
|
||||
}
|
||||
|
||||
initialize() {
|
||||
echo "initializing."
|
||||
tests_run=0
|
||||
tests_passed=0
|
||||
tests_failed=0
|
||||
tests_skipped=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))
|
||||
red "-> fail!"
|
||||
echo
|
||||
}
|
||||
|
||||
skip() {
|
||||
tests_skipped=$((tests_skipped + 1))
|
||||
yellow "-> skipped."
|
||||
echo
|
||||
}
|
||||
|
||||
run_test() {
|
||||
tests_run=$((tests_run + 1))
|
||||
printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}"
|
||||
cleanup
|
||||
if (cd "${BASEDIR}/test/tests" && HOME=~/fakehome DEBUG=${2} DOTBOT_TEST=true bash "${1}"); then
|
||||
pass
|
||||
elif [ $? -eq 42 ]; then
|
||||
skip
|
||||
else
|
||||
fail
|
||||
fi
|
||||
}
|
||||
|
||||
report() {
|
||||
printf -- "test report\n"
|
||||
printf -- "-----------\n"
|
||||
printf -- "- %3d run\n" ${tests_run}
|
||||
printf -- "- %3d passed\n" ${tests_passed}
|
||||
printf -- "- %3d skipped\n" ${tests_skipped}
|
||||
printf -- "- %3d failed\n" ${tests_failed}
|
||||
if [ ${tests_failed} -gt 0 ]; then
|
||||
red "==> FAIL! "
|
||||
return 1
|
||||
else
|
||||
green "==> PASS. "
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
die() {
|
||||
>&2 echo $@
|
||||
>&2 echo "terminating..."
|
||||
exit 1
|
||||
}
|
53
test/test
53
test/test
|
@ -1,53 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
export BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${BASEDIR}/test"
|
||||
. "./driver-lib.bash"
|
||||
|
||||
date_stamp="$(date --rfc-3339=ns)"
|
||||
start="$(date +%s)"
|
||||
|
||||
check_env
|
||||
|
||||
# parse flags; must come before positional arguments
|
||||
POSITIONAL=()
|
||||
DEBUG=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-d|--debug)
|
||||
DEBUG=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
POSITIONAL+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]}" # restore positional arguments
|
||||
|
||||
declare -a tests=()
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
while read file; do
|
||||
tests+=("${file}")
|
||||
done < <(find tests -type f -name '*.bash' | sort)
|
||||
else
|
||||
tests=("$@")
|
||||
fi
|
||||
|
||||
initialize "${#tests[@]}"
|
||||
|
||||
for file in "${tests[@]}"; do
|
||||
run_test "$(basename "${file}")" "${DEBUG}"
|
||||
done
|
||||
|
||||
if report; then
|
||||
ret=0
|
||||
else
|
||||
ret=1
|
||||
fi
|
||||
|
||||
echo "(tests run in $(($(date +%s) - start)) seconds)"
|
||||
exit ${ret}
|
|
@ -1,76 +0,0 @@
|
|||
DOTBOT_EXEC="${BASEDIR}/bin/dotbot"
|
||||
DOTFILES="${HOME}/dotfiles"
|
||||
INSTALL_CONF='install.conf.yaml'
|
||||
INSTALL_CONF_JSON='install.conf.json'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
skip_tests() {
|
||||
# exit with special exit code picked up by driver-lib.bash
|
||||
exit 42
|
||||
}
|
||||
|
||||
check_env() {
|
||||
if [ "${DOTBOT_TEST}" != "true" ]; then
|
||||
>&2 echo "test must be run by test driver"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# run comparison check on python version; args:
|
||||
# $1 - comparison operator (e.g. '>=')
|
||||
# $2 - version number, to be passed to python (e.g. '3', '3.5', '3.6.4')
|
||||
# status code will reflect if comparison is true/false
|
||||
# e.g. `check_python_version '>=' 3.5`
|
||||
check_python_version() {
|
||||
check="$1"
|
||||
version="$(echo "$2" | tr . , )"
|
||||
# this call to just `python` will work in the Vagrant-based testing VM
|
||||
# because `pyenv` will always create a link to the "right" version.
|
||||
python -c "import sys; exit( not (sys.version_info ${check} (${version})) )"
|
||||
}
|
||||
|
||||
initialize() {
|
||||
check_env
|
||||
echo "${test_description}"
|
||||
mkdir -p "${DOTFILES}"
|
||||
cd
|
||||
}
|
||||
|
||||
run_dotbot() {
|
||||
(
|
||||
cat > "${DOTFILES}/${INSTALL_CONF}"
|
||||
${DOTBOT_EXEC} -c "${DOTFILES}/${INSTALL_CONF}" "${@}"
|
||||
)
|
||||
}
|
||||
|
||||
run_dotbot_json() {
|
||||
(
|
||||
cat > "${DOTFILES}/${INSTALL_CONF_JSON}"
|
||||
${DOTBOT_EXEC} -c "${DOTFILES}/${INSTALL_CONF_JSON}" "${@}"
|
||||
)
|
||||
}
|
||||
|
||||
initialize
|
|
@ -1,19 +0,0 @@
|
|||
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
|
||||
'
|
|
@ -1,16 +0,0 @@
|
|||
test_description='clean expands environment variables'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
ln -s ${DOTFILES}/f ~/.f
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- clean: ["\$HOME"]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
! test -h ~/.f
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -1,8 +0,0 @@
|
|||
test_description='clean ignores nonexistent directories'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- clean: ["~", "~/fake"]
|
||||
EOF
|
||||
'
|
|
@ -1,18 +0,0 @@
|
|||
test_description='clean forced to remove files linking outside dotfiles directory'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
ln -s /nowhere ~/.g
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- clean:
|
||||
~/:
|
||||
force: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
! test -h ~/.g
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -1,34 +0,0 @@
|
|||
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
|
||||
'
|
|
@ -1,8 +0,0 @@
|
|||
test_description='blank config allowed'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
[]
|
||||
EOF
|
||||
'
|
|
@ -1,7 +0,0 @@
|
|||
test_description='empty config allowed'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
EOF
|
||||
'
|
|
@ -1,20 +0,0 @@
|
|||
test_description='json config with tabs allowed'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "grape" > ${DOTFILES}/h
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot_json <<EOF
|
||||
[{
|
||||
"link": {
|
||||
"~/.i": "h"
|
||||
}
|
||||
}]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "grape" ~/.i
|
||||
'
|
|
@ -1,20 +0,0 @@
|
|||
test_description='json config allowed'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "grape" > ${DOTFILES}/h
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot_json <<EOF
|
||||
[{
|
||||
"link": {
|
||||
"~/.i": "h"
|
||||
}
|
||||
}]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "grape" ~/.i
|
||||
'
|
|
@ -1,26 +0,0 @@
|
|||
test_description='create mode'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
create:
|
||||
mode: 0755
|
||||
- create:
|
||||
- ~/downloads
|
||||
- ~/.vim/undo-history
|
||||
- create:
|
||||
~/.ssh:
|
||||
mode: 0700
|
||||
~/projects:
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
[ -d ~/downloads ] &&
|
||||
[ -d ~/.vim/undo-history ] &&
|
||||
[ -d ~/.ssh ] &&
|
||||
[ -d ~/projects ] &&
|
||||
[ "$(stat -c %a ~/.ssh)" = "700" ] &&
|
||||
[ "$(stat -c %a ~/downloads)" = "755" ]
|
||||
'
|
|
@ -1,23 +0,0 @@
|
|||
test_description='create folders'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- create:
|
||||
- ~/somedir
|
||||
- ~/nested/somedir
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
[ -d ~/somedir ] &&
|
||||
[ -d ~/nested/somedir ]
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot <<EOF
|
||||
- create:
|
||||
- ~/somedir
|
||||
- ~/nested/somedir
|
||||
EOF
|
||||
'
|
|
@ -1,59 +0,0 @@
|
|||
test_description='defaults setting works'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/f &&
|
||||
echo "grape" > ~/f &&
|
||||
ln -s ~/f ~/.f &&
|
||||
ln -s /nowhere ~/.g
|
||||
'
|
||||
|
||||
test_expect_failure 'run-fail' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/.f: f
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'test-fail' '
|
||||
grep "apple" ~/.f
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
relink: true
|
||||
|
||||
- link:
|
||||
~/.f: f
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/.f
|
||||
'
|
||||
|
||||
test_expect_success 'run-fail 2' '
|
||||
run_dotbot <<EOF
|
||||
- clean: ["~"]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'test-fail 2' '
|
||||
! test -h ~/.g
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot <<EOF
|
||||
- defaults:
|
||||
clean:
|
||||
force: true
|
||||
|
||||
- clean: ["~"]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 2' '
|
||||
! test -h ~/.g
|
||||
'
|
|
@ -1,21 +0,0 @@
|
|||
test_description='--except with multiple arguments'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
ln -s ${DOTFILES}/nonexistent ~/bad && touch ${DOTFILES}/y
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot --except clean shell <<EOF
|
||||
- clean: ["~"]
|
||||
- shell:
|
||||
- echo "x" > ~/x
|
||||
- link:
|
||||
~/y: y
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
[ "$(readlink ~/bad | cut -d/ -f5-)" = "dotfiles/nonexistent" ] &&
|
||||
! test -f ~/x && test -f ~/y
|
||||
'
|
|
@ -1,32 +0,0 @@
|
|||
test_description='--except'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/x
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot --except link <<EOF
|
||||
- shell:
|
||||
- echo "pear" > ~/y
|
||||
- link:
|
||||
~/x: x
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "pear" ~/y && ! test -f ~/x
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot --except shell <<EOF
|
||||
- shell:
|
||||
- echo "pear" > ~/z
|
||||
- link:
|
||||
~/x: x
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/x && ! test -f ~/z
|
||||
'
|
|
@ -1,32 +0,0 @@
|
|||
test_description='test exit on failure'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/f1 &&
|
||||
echo "orange" > ${DOTFILES}/f2 &&
|
||||
echo "pineapple" > ${DOTFILES}/f3
|
||||
'
|
||||
|
||||
test_expect_failure 'run_case1' '
|
||||
run_dotbot -x <<EOF
|
||||
- shell:
|
||||
- "this_is_not_a_command"
|
||||
- link:
|
||||
~/f1:
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'run_case2' '
|
||||
run_dotbot -x <<EOF
|
||||
- link:
|
||||
~/f2:
|
||||
- shell:
|
||||
- "this_is_not_a_command"
|
||||
- link:
|
||||
~/f3:
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
[[ ! -f ~/f1 ]] && [[ -f ~/f2 ]] && [[ ! -f ~/f3 ]]
|
||||
'
|
|
@ -1,60 +0,0 @@
|
|||
test_description='can find python executable with different names'
|
||||
. '../test-lib.bash'
|
||||
|
||||
# the test machine needs to have a binary named `python`
|
||||
test_expect_success 'setup' '
|
||||
mkdir ~/tmp_bin &&
|
||||
(
|
||||
IFS=:
|
||||
for p in $PATH; do
|
||||
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 &&
|
||||
rm -f ~/tmp_bin/python2 &&
|
||||
rm -f ~/tmp_bin/python3
|
||||
'
|
||||
|
||||
test_expect_failure 'run' '
|
||||
PATH="$HOME/tmp_bin" run_dotbot <<EOF
|
||||
[]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'setup 2' '
|
||||
touch ~/tmp_bin/python &&
|
||||
chmod +x ~/tmp_bin/python &&
|
||||
cat >> ~/tmp_bin/python <<EOF
|
||||
#!$HOME/tmp_bin/bash
|
||||
exec $(command -v python)
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
PATH="$HOME/tmp_bin" run_dotbot <<EOF
|
||||
[]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'setup 3' '
|
||||
mv ~/tmp_bin/python ~/tmp_bin/python2
|
||||
'
|
||||
|
||||
test_expect_success 'run 3' '
|
||||
PATH="$HOME/tmp_bin" run_dotbot <<EOF
|
||||
[]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'setup 4' '
|
||||
mv ~/tmp_bin/python2 ~/tmp_bin/python3
|
||||
'
|
||||
|
||||
test_expect_success 'run 4' '
|
||||
PATH="$HOME/tmp_bin" run_dotbot <<EOF
|
||||
[]
|
||||
EOF
|
||||
'
|
|
@ -1,20 +0,0 @@
|
|||
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/ -f5-)" = "dotfiles/f" ]
|
||||
'
|
|
@ -1,26 +0,0 @@
|
|||
test_description='link uses destination if source is null'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/f &&
|
||||
echo "grape" > ${DOTFILES}/fd
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/f:
|
||||
~/.f:
|
||||
~/fd:
|
||||
force: false
|
||||
~/.fd:
|
||||
force: false
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/f &&
|
||||
grep "apple" ~/.f &&
|
||||
grep "grape" ~/fd &&
|
||||
grep "grape" ~/.fd
|
||||
'
|
|
@ -1,17 +0,0 @@
|
|||
test_description='link expands user in target'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ~/f
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/g: ~/f
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/g
|
||||
'
|
|
@ -1,20 +0,0 @@
|
|||
test_description='link expands environment variables in extended config syntax'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "grape" > ${DOTFILES}/h
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
export APPLE="h" &&
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/.i:
|
||||
path: \$APPLE
|
||||
relink: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "grape" ~/.i
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -1,24 +0,0 @@
|
|||
test_description='force leaves file when target nonexistent'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir ~/dir &&
|
||||
touch ~/file
|
||||
'
|
||||
|
||||
test_expect_failure 'run' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/dir:
|
||||
path: dir
|
||||
force: true
|
||||
~/file:
|
||||
path: file
|
||||
force: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
test -d ~/dir &&
|
||||
test -f ~/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
|
||||
'
|
|
@ -1,45 +0,0 @@
|
|||
test_description='link glob ambiguous'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir ${DOTFILES}/foo
|
||||
'
|
||||
|
||||
test_expect_failure 'run 1' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/foo/:
|
||||
path: foo
|
||||
glob: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'test 1' '
|
||||
test -d ~/foo
|
||||
'
|
||||
|
||||
test_expect_failure 'run 2' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/foo/:
|
||||
path: foo/
|
||||
glob: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'test 2' '
|
||||
test -d ~/foo
|
||||
'
|
||||
|
||||
test_expect_success 'run 3' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/foo:
|
||||
path: foo
|
||||
glob: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 3' '
|
||||
test -d ~/foo
|
||||
'
|
|
@ -1,123 +0,0 @@
|
|||
test_description='link glob exclude'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup 1' '
|
||||
mkdir -p ${DOTFILES}/config/{foo,bar,baz} &&
|
||||
echo "apple" > ${DOTFILES}/config/foo/a &&
|
||||
echo "banana" > ${DOTFILES}/config/bar/b &&
|
||||
echo "cherry" > ${DOTFILES}/config/bar/c &&
|
||||
echo "donut" > ${DOTFILES}/config/baz/d
|
||||
'
|
||||
|
||||
test_expect_success 'run 1' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/.config/:
|
||||
path: config/*
|
||||
exclude: [config/baz]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 1' '
|
||||
! readlink ~/.config/ &&
|
||||
readlink ~/.config/foo &&
|
||||
! readlink ~/.config/baz &&
|
||||
grep "apple" ~/.config/foo/a &&
|
||||
grep "banana" ~/.config/bar/b &&
|
||||
grep "cherry" ~/.config/bar/c
|
||||
'
|
||||
|
||||
test_expect_success 'setup 2' '
|
||||
rm -rf ~/.config &&
|
||||
mkdir ${DOTFILES}/config/baz/buzz &&
|
||||
echo "egg" > ${DOTFILES}/config/baz/buzz/e
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/.config/:
|
||||
path: config/*/*
|
||||
exclude: [config/baz/*]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 2' '
|
||||
! readlink ~/.config/ &&
|
||||
! readlink ~/.config/foo &&
|
||||
[ ! -d ~/.config/baz ] &&
|
||||
readlink ~/.config/foo/a &&
|
||||
grep "apple" ~/.config/foo/a &&
|
||||
grep "banana" ~/.config/bar/b &&
|
||||
grep "cherry" ~/.config/bar/c
|
||||
'
|
||||
|
||||
test_expect_success 'setup 3' '
|
||||
rm -rf ~/.config &&
|
||||
mkdir ${DOTFILES}/config/baz/bizz &&
|
||||
echo "grape" > ${DOTFILES}/config/baz/bizz/g
|
||||
'
|
||||
|
||||
test_expect_success 'run 3' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/.config/:
|
||||
path: config/*/*
|
||||
exclude: [config/baz/buzz]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 3' '
|
||||
! readlink ~/.config/ &&
|
||||
! readlink ~/.config/foo &&
|
||||
readlink ~/.config/foo/a &&
|
||||
! readlink ~/.config/baz/buzz &&
|
||||
readlink ~/.config/baz/bizz &&
|
||||
grep "apple" ~/.config/foo/a &&
|
||||
grep "banana" ~/.config/bar/b &&
|
||||
grep "cherry" ~/.config/bar/c &&
|
||||
grep "donut" ~/.config/baz/d &&
|
||||
grep "grape" ~/.config/baz/bizz/g
|
||||
'
|
||||
|
||||
test_expect_success 'setup 4' '
|
||||
rm -rf ~/.config &&
|
||||
mkdir ${DOTFILES}/config/fiz &&
|
||||
echo "fig" > ${DOTFILES}/config/fiz/f
|
||||
'
|
||||
|
||||
test_expect_success 'run 4' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/.config/:
|
||||
path: config/*/*
|
||||
exclude: [config/baz/*, config/fiz/*]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 4' '
|
||||
! readlink ~/.config/ &&
|
||||
! readlink ~/.config/foo &&
|
||||
[ ! -d ~/.config/baz ] &&
|
||||
[ ! -d ~/.config/fiz ] &&
|
||||
readlink ~/.config/foo/a &&
|
||||
grep "apple" ~/.config/foo/a &&
|
||||
grep "banana" ~/.config/bar/b &&
|
||||
grep "cherry" ~/.config/bar/c
|
||||
'
|
|
@ -1,31 +0,0 @@
|
|||
test_description='link glob multi star'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir ${DOTFILES}/config &&
|
||||
mkdir ${DOTFILES}/config/foo &&
|
||||
mkdir ${DOTFILES}/config/bar &&
|
||||
echo "apple" > ${DOTFILES}/config/foo/a &&
|
||||
echo "banana" > ${DOTFILES}/config/bar/b &&
|
||||
echo "cherry" > ${DOTFILES}/config/bar/c
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/.config/: config/*/*
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
! readlink ~/.config/ &&
|
||||
! readlink ~/.config/foo &&
|
||||
readlink ~/.config/foo/a &&
|
||||
grep "apple" ~/.config/foo/a &&
|
||||
grep "banana" ~/.config/bar/b &&
|
||||
grep "cherry" ~/.config/bar/c
|
||||
'
|
|
@ -1,106 +0,0 @@
|
|||
test_description='link glob, all patterns'
|
||||
. '../test-lib.bash'
|
||||
|
||||
allfruit=(apple apricot banana cherry currant cantalope)
|
||||
|
||||
test_expect_success 'glob patterns setup' '
|
||||
mkdir ${DOTFILES}/conf &&
|
||||
for fruit in "${allfruit[@]}"; do
|
||||
echo "${fruit}" > ${DOTFILES}/conf/${fruit}
|
||||
echo "dot-${fruit}" > ${DOTFILES}/conf/.${fruit}
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'glob patterns: "conf/*"' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/globtest: conf/*
|
||||
EOF
|
||||
|
||||
for fruit in "${allfruit[@]}"; do
|
||||
grep "${fruit}" ~/globtest/${fruit} &&
|
||||
test \! -e ~/globtest/.${fruit}
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'reset' 'rm -rf ~/globtest'
|
||||
|
||||
test_expect_success 'glob pattern: "conf/.*"' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/globtest: conf/.*
|
||||
EOF
|
||||
|
||||
for fruit in "${allfruit[@]}"; do
|
||||
test \! -e ~/globtest/${fruit} &&
|
||||
grep "dot-${fruit}" ~/globtest/.${fruit}
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'reset 2' 'rm -rf ~/globtest'
|
||||
|
||||
test_expect_success 'glob pattern: "conf/[bc]*"' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/globtest: conf/[bc]*
|
||||
EOF
|
||||
|
||||
for fruit in "${allfruit[@]}"; do
|
||||
[[ $fruit = [bc]* ]] &&
|
||||
grep "${fruit}" ~/globtest/${fruit} ||
|
||||
test \! -e ~/globtest/${fruit} &&
|
||||
test \! -e ~/globtest/.${fruit}
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'reset 3' 'rm -rf ~/globtest'
|
||||
|
||||
test_expect_success 'glob pattern: "conf/*e"' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/globtest: conf/*e
|
||||
EOF
|
||||
|
||||
for fruit in "${allfruit[@]}"; do
|
||||
[[ $fruit = *e ]] &&
|
||||
grep "${fruit}" ~/globtest/${fruit} ||
|
||||
test \! -e ~/globtest/${fruit} &&
|
||||
test \! -e ~/globtest/.${fruit}
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'reset 4' 'rm -rf ~/globtest'
|
||||
|
||||
test_expect_success 'glob pattern: "conf/??r*"' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/globtest: conf/??r*
|
||||
EOF
|
||||
|
||||
for fruit in "${allfruit[@]}"; do
|
||||
[[ $fruit = ??r* ]] &&
|
||||
grep "${fruit}" ~/globtest/${fruit} ||
|
||||
test \! -e ~/globtest/${fruit} &&
|
||||
test \! -e ~/globtest/.${fruit}
|
||||
done
|
||||
'
|
|
@ -1,46 +0,0 @@
|
|||
test_description='link glob recursive'
|
||||
. '../test-lib.bash'
|
||||
|
||||
check_python_version ">=" 3.5 \
|
||||
|| test_expect_failure 'expect-fail' '
|
||||
run_dotbot -v <<EOF
|
||||
- link:
|
||||
~/.config/:
|
||||
glob: true
|
||||
path: bogus/**
|
||||
EOF
|
||||
'
|
||||
|
||||
# Skip remaining tests if not supported
|
||||
check_python_version ">=" 3.5 \
|
||||
|| skip_tests
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir -p ${DOTFILES}/config/foo/bar &&
|
||||
echo "apple" > ${DOTFILES}/config/foo/bar/a &&
|
||||
echo "banana" > ${DOTFILES}/config/foo/bar/b &&
|
||||
echo "cherry" > ${DOTFILES}/config/foo/bar/c
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/.config/:
|
||||
path: config/**
|
||||
exclude: [config/**/b]
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
! readlink ~/.config/ &&
|
||||
! readlink ~/.config/foo &&
|
||||
! readlink ~/.config/foo/bar &&
|
||||
readlink ~/.config/foo/bar/a &&
|
||||
grep "apple" ~/.config/foo/bar/a &&
|
||||
test \! -e ~/.config/foo/bar/b &&
|
||||
grep "cherry" ~/.config/foo/bar/c
|
||||
'
|
|
@ -1,93 +0,0 @@
|
|||
test_description='link glob'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup 1' '
|
||||
mkdir ${DOTFILES}/bin &&
|
||||
echo "apple" > ${DOTFILES}/bin/a &&
|
||||
echo "banana" > ${DOTFILES}/bin/b &&
|
||||
echo "cherry" > ${DOTFILES}/bin/c
|
||||
'
|
||||
|
||||
test_expect_success 'run 1' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/bin: bin/*
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 1' '
|
||||
grep "apple" ~/bin/a &&
|
||||
grep "banana" ~/bin/b &&
|
||||
grep "cherry" ~/bin/c
|
||||
'
|
||||
|
||||
test_expect_success 'setup 2' '
|
||||
rm -rf ~/bin
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/bin/: bin/*
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 2' '
|
||||
grep "apple" ~/bin/a &&
|
||||
grep "banana" ~/bin/b &&
|
||||
grep "cherry" ~/bin/c
|
||||
'
|
||||
|
||||
test_expect_success 'setup 3' '
|
||||
rm -rf ~/bin &&
|
||||
echo "dot_apple" > ${DOTFILES}/bin/.a &&
|
||||
echo "dot_banana" > ${DOTFILES}/bin/.b &&
|
||||
echo "dot_cherry" > ${DOTFILES}/bin/.c
|
||||
'
|
||||
|
||||
test_expect_success 'run 3' '
|
||||
run_dotbot -v <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
glob: true
|
||||
create: true
|
||||
- link:
|
||||
~/bin/: bin/.*
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 3' '
|
||||
grep "dot_apple" ~/bin/.a &&
|
||||
grep "dot_banana" ~/bin/.b &&
|
||||
grep "dot_cherry" ~/bin/.c
|
||||
'
|
||||
|
||||
test_expect_success 'setup 4' '
|
||||
rm -rf ~/bin &&
|
||||
echo "dot_apple" > ${DOTFILES}/.a &&
|
||||
echo "dot_banana" > ${DOTFILES}/.b &&
|
||||
echo "dot_cherry" > ${DOTFILES}/.c
|
||||
'
|
||||
|
||||
test_expect_success 'run 4' '
|
||||
run_dotbot -v <<EOF
|
||||
- link:
|
||||
"~":
|
||||
path: .*
|
||||
glob: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 4' '
|
||||
grep "dot_apple" ~/.a &&
|
||||
grep "dot_banana" ~/.b &&
|
||||
grep "dot_cherry" ~/.c
|
||||
'
|
|
@ -1,51 +0,0 @@
|
|||
test_description='link if'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir ~/d
|
||||
echo "apple" > ${DOTFILES}/f
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/.f:
|
||||
path: f
|
||||
if: "true"
|
||||
~/.g:
|
||||
path: f
|
||||
if: "false"
|
||||
~/.h:
|
||||
path: f
|
||||
if: "[ -d ~/d ]"
|
||||
~/.i:
|
||||
path: f
|
||||
if: "badcommand"
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/.f &&
|
||||
! test -f ~/.g &&
|
||||
grep "apple" ~/.h &&
|
||||
! test -f ~/.i
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
if: "false"
|
||||
- link:
|
||||
~/.j:
|
||||
path: f
|
||||
if: "true"
|
||||
~/.k:
|
||||
path: f
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 2' '
|
||||
grep "apple" ~/.j &&
|
||||
! test -f ~/.k
|
||||
'
|
|
@ -1,23 +0,0 @@
|
|||
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
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -1,40 +0,0 @@
|
|||
test_description='linking path canonicalization can be disabled'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/f &&
|
||||
echo "grape" > ${DOTFILES}/g &&
|
||||
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/ -f5-)" = "dotfiles-symlink/f" ]
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
cat > "${DOTFILES}/${INSTALL_CONF}" <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
canonicalize: false
|
||||
- link:
|
||||
~/.g:
|
||||
path: g
|
||||
EOF
|
||||
${DOTBOT_EXEC} -c ./dotfiles-symlink/${INSTALL_CONF}
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
[ "$(readlink ~/.g | cut -d/ -f5-)" = "dotfiles-symlink/g" ]
|
||||
'
|
|
@ -1,23 +0,0 @@
|
|||
test_description='link prefix'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir ${DOTFILES}/conf &&
|
||||
echo "apple" > ${DOTFILES}/conf/a &&
|
||||
echo "banana" > ${DOTFILES}/conf/b &&
|
||||
echo "cherry" > ${DOTFILES}/conf/c
|
||||
'
|
||||
|
||||
test_expect_success 'test glob w/ prefix' '
|
||||
run_dotbot -v <<EOF
|
||||
- link:
|
||||
~/:
|
||||
glob: true
|
||||
path: conf/*
|
||||
prefix: '.'
|
||||
EOF
|
||||
|
||||
grep "apple" ~/.a &&
|
||||
grep "banana" ~/.b &&
|
||||
grep "cherry" ~/.c
|
||||
'
|
|
@ -1,36 +0,0 @@
|
|||
test_description='relative linking works'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/f &&
|
||||
mkdir ${DOTFILES}/d &&
|
||||
echo "grape" > ${DOTFILES}/d/e
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/.f:
|
||||
path: f
|
||||
~/.frel:
|
||||
path: f
|
||||
relative: true
|
||||
~/nested/.frel:
|
||||
path: f
|
||||
create: true
|
||||
relative: true
|
||||
~/.d:
|
||||
path: d
|
||||
relative: true
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/.f &&
|
||||
grep "apple" ~/.frel &&
|
||||
[[ "$(readlink ~/.f)" == "$(readlink -f dotfiles/f)" ]] &&
|
||||
[[ "$(readlink ~/.frel)" == "dotfiles/f" ]] &&
|
||||
[[ "$(readlink ~/nested/.frel)" == "../dotfiles/f" ]] &&
|
||||
grep "grape" ~/.d/e &&
|
||||
[[ "$(readlink ~/.d)" == "dotfiles/d" ]]
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -1,32 +0,0 @@
|
|||
test_description='relink relative does not incorrectly relink file'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/f &&
|
||||
echo "grape" > ~/.f
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/.folder/f:
|
||||
path: f
|
||||
create: true
|
||||
relative: true
|
||||
EOF
|
||||
'
|
||||
|
||||
# these are done in a single block because they run in a subshell, and it
|
||||
# wouldn't be possible to access `$mtime` outside of the subshell
|
||||
test_expect_success 'test' '
|
||||
mtime=$(stat ~/.folder/f | grep Modify)
|
||||
run_dotbot <<EOF
|
||||
- link:
|
||||
~/.folder/f:
|
||||
path: f
|
||||
create: true
|
||||
relative: true
|
||||
relink: true
|
||||
EOF
|
||||
[[ "$mtime" == "$(stat ~/.folder/f | grep Modify)" ]]
|
||||
'
|
|
@ -1,22 +0,0 @@
|
|||
test_description='--only does not skip defaults'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/x
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot --only link <<EOF
|
||||
- defaults:
|
||||
link:
|
||||
create: true
|
||||
- shell:
|
||||
- echo "pear" > ~/z
|
||||
- link:
|
||||
~/d/x: x
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/d/x && ! test -f ~/z
|
||||
'
|
|
@ -1,20 +0,0 @@
|
|||
test_description='--only with multiple arguments'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
ln -s ${DOTFILES}/nonexistent ~/bad && touch ${DOTFILES}/y
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot --only clean shell <<EOF
|
||||
- clean: ["~"]
|
||||
- shell:
|
||||
- echo "x" > ~/x
|
||||
- link:
|
||||
~/y: y
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
! test -f ~/bad && grep "x" ~/x && ! test -f ~/y
|
||||
'
|
|
@ -1,32 +0,0 @@
|
|||
test_description='--only'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/x
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot --only shell <<EOF
|
||||
- shell:
|
||||
- echo "pear" > ~/y
|
||||
- link:
|
||||
~/x: x
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "pear" ~/y && ! test -f ~/x
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot --only link <<EOF
|
||||
- shell:
|
||||
- echo "pear" > ~/z
|
||||
- link:
|
||||
~/x: x
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "apple" ~/x && ! test -f ~/z
|
||||
'
|
|
@ -1,29 +0,0 @@
|
|||
test_description='directory-based plugin loading works'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir ${DOTFILES}/plugins
|
||||
cat > ${DOTFILES}/plugins/test.py <<EOF
|
||||
import dotbot
|
||||
import os.path
|
||||
|
||||
class Test(dotbot.Plugin):
|
||||
def can_handle(self, directive):
|
||||
return directive == "test"
|
||||
|
||||
def handle(self, directive, data):
|
||||
with open(os.path.expanduser("~/flag"), "w") as f:
|
||||
f.write("it works")
|
||||
return True
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
run_dotbot --plugin-dir ${DOTFILES}/plugins <<EOF
|
||||
- test: ~
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "it works" ~/flag
|
||||
'
|
|
@ -1,17 +0,0 @@
|
|||
test_description='can disable built-in plugins'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
echo "apple" > ${DOTFILES}/f
|
||||
'
|
||||
|
||||
test_expect_failure 'run' '
|
||||
run_dotbot --disable-built-in-plugins <<EOF
|
||||
- link:
|
||||
~/.f: f
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'test' '
|
||||
test -f ~/.f
|
||||
'
|
|
@ -1,64 +0,0 @@
|
|||
test_description='plugin loading works'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup 1' '
|
||||
cat > ${DOTFILES}/test.py <<EOF
|
||||
import dotbot
|
||||
import os.path
|
||||
|
||||
class Test(dotbot.Plugin):
|
||||
def can_handle(self, directive):
|
||||
return directive == "test"
|
||||
|
||||
def handle(self, directive, data):
|
||||
with open(os.path.expanduser("~/flag"), "w") as f:
|
||||
f.write("it works")
|
||||
return True
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 1' '
|
||||
run_dotbot --plugin ${DOTFILES}/test.py <<EOF
|
||||
- test: ~
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 1' '
|
||||
grep "it works" ~/flag
|
||||
'
|
||||
|
||||
test_expect_success 'setup 2' '
|
||||
rm ${DOTFILES}/test.py;
|
||||
cat > ${DOTFILES}/test.py <<EOF
|
||||
import dotbot
|
||||
import os.path
|
||||
|
||||
class Test(dotbot.Plugin):
|
||||
def can_handle(self, directive):
|
||||
return directive == "test"
|
||||
|
||||
def handle(self, directive, data):
|
||||
self._log.debug("Attempting to get options from Context")
|
||||
options = self._context.options()
|
||||
if len(options.plugins) != 1:
|
||||
self._log.debug("Context.options.plugins length is %i, expected 1" % len(options.plugins))
|
||||
return False
|
||||
if not options.plugins[0].endswith("test.py"):
|
||||
self._log.debug("Context.options.plugins[0] is %s, expected end with test.py" % options.plugins[0])
|
||||
return False
|
||||
|
||||
with open(os.path.expanduser("~/flag"), "w") as f:
|
||||
f.write("it works")
|
||||
return True
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
run_dotbot --plugin ${DOTFILES}/test.py <<EOF
|
||||
- test: ~
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'test 2' '
|
||||
grep "it works" ~/flag
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -1,79 +0,0 @@
|
|||
test_description='cli options can override config file'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run 1' '
|
||||
(run_dotbot -vv | (grep "^apple")) <<EOF
|
||||
- shell:
|
||||
-
|
||||
command: echo apple
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
(run_dotbot -vv | (grep "^apple")) <<EOF
|
||||
- shell:
|
||||
-
|
||||
command: echo apple
|
||||
stdout: false
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 3' '
|
||||
(run_dotbot -vv | (grep "^apple")) <<EOF
|
||||
- defaults:
|
||||
shell:
|
||||
stdout: false
|
||||
- shell:
|
||||
- command: echo apple
|
||||
EOF
|
||||
'
|
||||
|
||||
# Control to make sure stderr redirection is working as expected
|
||||
test_expect_failure 'run 4' '
|
||||
(run_dotbot -vv | (grep "^apple")) <<EOF
|
||||
- shell:
|
||||
- command: echo apple >&2
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 5' '
|
||||
(run_dotbot -vv 2>&1 | (grep "^apple")) <<EOF
|
||||
- shell:
|
||||
- command: echo apple >&2
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 6' '
|
||||
(run_dotbot -vv 2>&1 | (grep "^apple")) <<EOF
|
||||
- shell:
|
||||
-
|
||||
command: echo apple >&2
|
||||
stdout: false
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 7' '
|
||||
(run_dotbot -vv 2>&1 | (grep "^apple")) <<EOF
|
||||
- defaults:
|
||||
shell:
|
||||
stdout: false
|
||||
- shell:
|
||||
- command: echo apple >&2
|
||||
EOF
|
||||
'
|
||||
|
||||
# Make sure that we must use verbose level 2
|
||||
# This preserves backwards compatability
|
||||
test_expect_failure 'run 8' '
|
||||
(run_dotbot -v | (grep "^apple")) <<EOF
|
||||
- shell:
|
||||
- command: echo apple
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'run 9' '
|
||||
(run_dotbot -v | (grep "^apple")) <<EOF
|
||||
- shell:
|
||||
- command: echo apple >&2
|
||||
EOF
|
||||
'
|
|
@ -1,22 +0,0 @@
|
|||
test_description='shell command stdout works in compact form'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run' '
|
||||
(run_dotbot | grep "^apple") <<EOF
|
||||
- defaults:
|
||||
shell:
|
||||
stdout: true
|
||||
- shell:
|
||||
- echo apple
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'run 2' '
|
||||
(run_dotbot | grep "^apple") <<EOF
|
||||
- defaults:
|
||||
shell:
|
||||
stdout: true
|
||||
- shell:
|
||||
- [echo apple, "echoing message"]
|
||||
EOF
|
||||
'
|
|
@ -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
|
||||
'
|
|
@ -1,14 +0,0 @@
|
|||
test_description='shell command can override default'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'run' '
|
||||
(run_dotbot | (! grep "^apple")) <<EOF
|
||||
- defaults:
|
||||
shell:
|
||||
stdout: true
|
||||
- shell:
|
||||
-
|
||||
command: echo apple
|
||||
stdout: false
|
||||
EOF
|
||||
'
|
|
@ -1,30 +0,0 @@
|
|||
test_description='shell command can be suppressed in output'
|
||||
. '../test-lib.bash'
|
||||
|
||||
# when not quiet, expect to see command that was run
|
||||
test_expect_success 'run' '
|
||||
(run_dotbot | grep "echo banana") <<EOF
|
||||
- shell:
|
||||
- command: echo banana
|
||||
description: echoing a thing...
|
||||
EOF
|
||||
'
|
||||
|
||||
# when quiet, expect command to be suppressed
|
||||
test_expect_success 'run 2' '
|
||||
(run_dotbot | (! grep "echo banana")) <<EOF
|
||||
- shell:
|
||||
- command: echo banana
|
||||
description: echoing a thing...
|
||||
quiet: true
|
||||
EOF
|
||||
'
|
||||
|
||||
# when no description, expect no output
|
||||
test_expect_success 'run 3' '
|
||||
(run_dotbot | (! grep "echo banana")) <<EOF
|
||||
- shell:
|
||||
- command: echo banana
|
||||
quiet: true
|
||||
EOF
|
||||
'
|
|
@ -1,23 +0,0 @@
|
|||
test_description='install shim works'
|
||||
. '../test-lib.bash'
|
||||
|
||||
test_expect_success 'setup' '
|
||||
git config --global protocol.file.allow always
|
||||
cd ${DOTFILES}
|
||||
git init
|
||||
git submodule add ${BASEDIR} dotbot
|
||||
cp ./dotbot/tools/git-submodule/install .
|
||||
echo "pear" > ${DOTFILES}/foo
|
||||
'
|
||||
|
||||
test_expect_success 'run' '
|
||||
cat > ${DOTFILES}/install.conf.yaml <<EOF
|
||||
- link:
|
||||
~/.foo: foo
|
||||
EOF
|
||||
${DOTFILES}/install
|
||||
'
|
||||
|
||||
test_expect_success 'test' '
|
||||
grep "pear" ~/.foo
|
||||
'
|
331
tests/conftest.py
Normal file
331
tests/conftest.py
Normal file
|
@ -0,0 +1,331 @@
|
|||
import ctypes
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from shutil import rmtree
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
import dotbot.cli
|
||||
|
||||
try:
|
||||
import builtins
|
||||
import unittest.mock as mock
|
||||
except ImportError:
|
||||
# Python 2.7 compatibility
|
||||
builtins = None
|
||||
import __builtin__
|
||||
import mock # noqa: module not found
|
||||
|
||||
|
||||
def get_long_path(path):
|
||||
"""Get the long path for a given path."""
|
||||
|
||||
# Do nothing for non-Windows platforms.
|
||||
if sys.platform[:5] != "win32":
|
||||
return path
|
||||
|
||||
buffer_size = 1000
|
||||
buffer = ctypes.create_unicode_buffer(buffer_size)
|
||||
get_long_path_name = ctypes.windll.kernel32.GetLongPathNameW
|
||||
get_long_path_name(path, buffer, buffer_size)
|
||||
return buffer.value
|
||||
|
||||
|
||||
# Python 2.7 compatibility:
|
||||
# On Linux, Python 2.7's tempfile.TemporaryFile() requires unlink access.
|
||||
# This list is updated by a tempfile._mkstemp_inner() wrapper,
|
||||
# and its contents are checked by wrapped functions.
|
||||
allowed_tempfile_internal_unlink_calls = []
|
||||
|
||||
|
||||
def wrap_function(function, function_path, arg_index, kwarg_key, root):
|
||||
def wrapper(*args, **kwargs):
|
||||
if kwarg_key in kwargs:
|
||||
value = kwargs[kwarg_key]
|
||||
else:
|
||||
value = args[arg_index]
|
||||
|
||||
# Python 2.7 compatibility:
|
||||
# Allow tempfile.TemporaryFile's internal unlink calls to work.
|
||||
if value in allowed_tempfile_internal_unlink_calls:
|
||||
return function(*args, **kwargs)
|
||||
|
||||
msg = "The '{0}' argument to {1}() must be an absolute path"
|
||||
msg = msg.format(kwarg_key, function_path)
|
||||
assert value == os.path.abspath(value), msg
|
||||
|
||||
msg = "The '{0}' argument to {1}() must be rooted in {2}"
|
||||
msg = msg.format(kwarg_key, function_path, root)
|
||||
assert value[: len(str(root))] == str(root), msg
|
||||
|
||||
return function(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def wrap_open(root):
|
||||
try:
|
||||
wrapped = getattr(builtins, "open")
|
||||
except AttributeError:
|
||||
# Python 2.7 compatibility
|
||||
wrapped = getattr(__builtin__, "open")
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
if "file" in kwargs:
|
||||
value = kwargs["file"]
|
||||
else:
|
||||
value = args[0]
|
||||
|
||||
mode = "r"
|
||||
if "mode" in kwargs:
|
||||
mode = kwargs["mode"]
|
||||
elif len(args) >= 2:
|
||||
mode = args[1]
|
||||
|
||||
msg = "The 'file' argument to open() must be an absolute path"
|
||||
if value != os.devnull and "w" in mode:
|
||||
assert value == os.path.abspath(value), msg
|
||||
|
||||
msg = "The 'file' argument to open() must be rooted in {0}"
|
||||
msg = msg.format(root)
|
||||
if value != os.devnull and "w" in mode:
|
||||
assert value[: len(str(root))] == str(root), msg
|
||||
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def rmtree_error_handler(_, path, __):
|
||||
# Handle read-only files and directories.
|
||||
os.chmod(path, 0o777)
|
||||
if os.path.isdir(path):
|
||||
rmtree(path)
|
||||
else:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def standardize_tmp():
|
||||
r"""Standardize the temporary directory path.
|
||||
|
||||
On MacOS, `/var` is a symlink to `/private/var`.
|
||||
This creates issues with link canonicalization and relative link tests,
|
||||
so this fixture rewrites environment variables and Python variables
|
||||
to ensure the tests work the same as on Linux and Windows.
|
||||
|
||||
On Windows in GitHub CI, the temporary directory may be a short path.
|
||||
For example, `C:\Users\RUNNER~1\...` instead of `C:\Users\runneradmin\...`.
|
||||
This causes string-based path comparisons to fail.
|
||||
"""
|
||||
|
||||
tmp = tempfile.gettempdir()
|
||||
# MacOS: `/var` is a symlink.
|
||||
tmp = os.path.abspath(os.path.realpath(tmp))
|
||||
# Windows: The temporary directory may be a short path.
|
||||
if sys.platform[:5] == "win32":
|
||||
tmp = get_long_path(tmp)
|
||||
os.environ["TMP"] = tmp
|
||||
os.environ["TEMP"] = tmp
|
||||
os.environ["TMPDIR"] = tmp
|
||||
tempfile.tempdir = tmp
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def root(standardize_tmp):
|
||||
"""Create a temporary directory for the duration of each test."""
|
||||
|
||||
# Reset allowed_tempfile_internal_unlink_calls.
|
||||
global allowed_tempfile_internal_unlink_calls
|
||||
allowed_tempfile_internal_unlink_calls = []
|
||||
|
||||
# Dotbot changes the current working directory,
|
||||
# so this must be reset at the end of each test.
|
||||
current_working_directory = os.getcwd()
|
||||
|
||||
# Create an isolated temporary directory from which to operate.
|
||||
current_root = tempfile.mkdtemp()
|
||||
|
||||
functions_to_wrap = [
|
||||
(os, "chflags", 0, "path"),
|
||||
(os, "chmod", 0, "path"),
|
||||
(os, "chown", 0, "path"),
|
||||
(os, "copy_file_range", 1, "dst"),
|
||||
(os, "lchflags", 0, "path"),
|
||||
(os, "lchmod", 0, "path"),
|
||||
(os, "link", 1, "dst"),
|
||||
(os, "makedirs", 0, "name"),
|
||||
(os, "mkdir", 0, "path"),
|
||||
(os, "mkfifo", 0, "path"),
|
||||
(os, "mknod", 0, "path"),
|
||||
(os, "remove", 0, "path"),
|
||||
(os, "removedirs", 0, "name"),
|
||||
(os, "removexattr", 0, "path"),
|
||||
(os, "rename", 0, "src"), # Check both
|
||||
(os, "rename", 1, "dst"),
|
||||
(os, "renames", 0, "old"), # Check both
|
||||
(os, "renames", 1, "new"),
|
||||
(os, "replace", 0, "src"), # Check both
|
||||
(os, "replace", 1, "dst"),
|
||||
(os, "rmdir", 0, "path"),
|
||||
(os, "setxattr", 0, "path"),
|
||||
(os, "splice", 1, "dst"),
|
||||
(os, "symlink", 1, "dst"),
|
||||
(os, "truncate", 0, "path"),
|
||||
(os, "unlink", 0, "path"),
|
||||
(os, "utime", 0, "path"),
|
||||
(shutil, "chown", 0, "path"),
|
||||
(shutil, "copy", 1, "dst"),
|
||||
(shutil, "copy2", 1, "dst"),
|
||||
(shutil, "copyfile", 1, "dst"),
|
||||
(shutil, "copymode", 1, "dst"),
|
||||
(shutil, "copystat", 1, "dst"),
|
||||
(shutil, "copytree", 1, "dst"),
|
||||
(shutil, "make_archive", 0, "base_name"),
|
||||
(shutil, "move", 0, "src"), # Check both
|
||||
(shutil, "move", 1, "dst"),
|
||||
(shutil, "rmtree", 0, "path"),
|
||||
(shutil, "unpack_archive", 1, "extract_dir"),
|
||||
]
|
||||
|
||||
patches = []
|
||||
for module, function_name, arg_index, kwarg_key in functions_to_wrap:
|
||||
# Skip anything that doesn't exist in this version of Python.
|
||||
if not hasattr(module, function_name):
|
||||
continue
|
||||
|
||||
# These values must be passed to a separate function
|
||||
# to ensure the variable closures work correctly.
|
||||
function_path = "{0}.{1}".format(module.__name__, function_name)
|
||||
function = getattr(module, function_name)
|
||||
wrapped = wrap_function(function, function_path, arg_index, kwarg_key, current_root)
|
||||
patches.append(mock.patch(function_path, wrapped))
|
||||
|
||||
# open() must be separately wrapped.
|
||||
if builtins is not None:
|
||||
function_path = "builtins.open"
|
||||
else:
|
||||
# Python 2.7 compatibility
|
||||
function_path = "__builtin__.open"
|
||||
wrapped = wrap_open(current_root)
|
||||
patches.append(mock.patch(function_path, wrapped))
|
||||
|
||||
# Block all access to bad functions.
|
||||
if hasattr(os, "chroot"):
|
||||
patches.append(mock.patch("os.chroot", lambda *_, **__: None))
|
||||
|
||||
# Patch tempfile._mkstemp_inner() so tempfile.TemporaryFile()
|
||||
# can unlink files immediately.
|
||||
mkstemp_inner = tempfile._mkstemp_inner
|
||||
|
||||
def wrap_mkstemp_inner(*args, **kwargs):
|
||||
(fd, name) = mkstemp_inner(*args, **kwargs)
|
||||
allowed_tempfile_internal_unlink_calls.append(name)
|
||||
return fd, name
|
||||
|
||||
patches.append(mock.patch("tempfile._mkstemp_inner", wrap_mkstemp_inner))
|
||||
|
||||
[patch.start() for patch in patches]
|
||||
try:
|
||||
yield current_root
|
||||
finally:
|
||||
[patch.stop() for patch in patches]
|
||||
os.chdir(current_working_directory)
|
||||
rmtree(current_root, onerror=rmtree_error_handler)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def home(monkeypatch, root):
|
||||
"""Create a home directory for the duration of the test.
|
||||
|
||||
On *nix, the environment variable "HOME" will be mocked.
|
||||
On Windows, the environment variable "USERPROFILE" will be mocked.
|
||||
"""
|
||||
|
||||
home = os.path.abspath(os.path.join(root, "home/user"))
|
||||
os.makedirs(home)
|
||||
if sys.platform[:5] == "win32":
|
||||
monkeypatch.setenv("USERPROFILE", home)
|
||||
else:
|
||||
monkeypatch.setenv("HOME", home)
|
||||
yield home
|
||||
|
||||
|
||||
class Dotfiles(object):
|
||||
"""Create and manage a dotfiles directory for a test."""
|
||||
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.config = None
|
||||
self.config_filename = None
|
||||
self.directory = os.path.join(root, "dotfiles")
|
||||
os.mkdir(self.directory)
|
||||
|
||||
def makedirs(self, path):
|
||||
os.makedirs(os.path.abspath(os.path.join(self.directory, path)))
|
||||
|
||||
def write(self, path, content=""):
|
||||
path = os.path.abspath(os.path.join(self.directory, path))
|
||||
if not os.path.exists(os.path.dirname(path)):
|
||||
os.makedirs(os.path.dirname(path))
|
||||
with open(path, "w") as file:
|
||||
file.write(content)
|
||||
|
||||
def write_config(self, config, serializer="yaml", path=None):
|
||||
"""Write a dotbot config and return the filename."""
|
||||
|
||||
assert serializer in {"json", "yaml"}, "Only json and yaml are supported"
|
||||
if serializer == "yaml":
|
||||
serialize = yaml.dump
|
||||
else: # serializer == "json"
|
||||
serialize = json.dumps
|
||||
|
||||
if path:
|
||||
msg = "The config file path must be an absolute path"
|
||||
assert path == os.path.abspath(path), msg
|
||||
|
||||
msg = "The config file path must be rooted in {0}"
|
||||
msg = msg.format(root)
|
||||
assert path[: len(str(root))] == str(root), msg
|
||||
|
||||
self.config_filename = path
|
||||
else:
|
||||
self.config_filename = os.path.join(self.directory, "install.conf.yaml")
|
||||
self.config = config
|
||||
|
||||
with open(self.config_filename, "w") as file:
|
||||
file.write(serialize(config))
|
||||
return self.config_filename
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dotfiles(root):
|
||||
"""Create a dotfiles directory."""
|
||||
|
||||
yield Dotfiles(root)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_dotbot(dotfiles):
|
||||
"""Run dotbot.
|
||||
|
||||
When calling `runner()`, only CLI arguments need to be specified.
|
||||
|
||||
If the keyword-only argument *custom* is True
|
||||
then the CLI arguments will not be modified,
|
||||
and the caller will be responsible for all CLI arguments.
|
||||
"""
|
||||
|
||||
def runner(*argv, **kwargs):
|
||||
argv = ["dotbot"] + list(argv)
|
||||
if kwargs.get("custom", False) is not True:
|
||||
argv.extend(["-c", dotfiles.config_filename])
|
||||
with mock.patch("sys.argv", argv):
|
||||
dotbot.cli.main()
|
||||
|
||||
yield runner
|
27
tests/dotbot_plugin_directory.py
Normal file
27
tests/dotbot_plugin_directory.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
"""Test that a plugin can be loaded by directory.
|
||||
|
||||
This file is copied to a location with the name "directory.py",
|
||||
and is then loaded from within the `test_cli.py` code.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
import dotbot
|
||||
|
||||
|
||||
class Directory(dotbot.Plugin):
|
||||
def can_handle(self, directive):
|
||||
return directive == "plugin_directory"
|
||||
|
||||
def handle(self, directive, data):
|
||||
self._log.debug("Attempting to get options from Context")
|
||||
options = self._context.options()
|
||||
if len(options.plugin_dirs) != 1:
|
||||
self._log.debug(
|
||||
"Context.options.plugins length is %i, expected 1" % len(options.plugins)
|
||||
)
|
||||
return False
|
||||
|
||||
with open(os.path.abspath(os.path.expanduser("~/flag")), "w") as file:
|
||||
file.write("directory plugin loading works")
|
||||
return True
|
32
tests/dotbot_plugin_file.py
Normal file
32
tests/dotbot_plugin_file.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""Test that a plugin can be loaded by filename.
|
||||
|
||||
This file is copied to a location with the name "file.py",
|
||||
and is then loaded from within the `test_cli.py` code.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
import dotbot
|
||||
|
||||
|
||||
class File(dotbot.Plugin):
|
||||
def can_handle(self, directive):
|
||||
return directive == "plugin_file"
|
||||
|
||||
def handle(self, directive, data):
|
||||
self._log.debug("Attempting to get options from Context")
|
||||
options = self._context.options()
|
||||
if len(options.plugins) != 1:
|
||||
self._log.debug(
|
||||
"Context.options.plugins length is %i, expected 1" % len(options.plugins)
|
||||
)
|
||||
return False
|
||||
if not options.plugins[0].endswith("file.py"):
|
||||
self._log.debug(
|
||||
"Context.options.plugins[0] is %s, expected end with file.py" % options.plugins[0]
|
||||
)
|
||||
return False
|
||||
|
||||
with open(os.path.abspath(os.path.expanduser("~/flag")), "w") as file:
|
||||
file.write("file plugin loading works")
|
||||
return True
|
55
tests/test_bin_dotbot.py
Normal file
55
tests/test_bin_dotbot.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def which(name):
|
||||
"""Find an executable.
|
||||
|
||||
Python 2.7 doesn't have shutil.which().
|
||||
"""
|
||||
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
if os.path.isfile(os.path.join(path, name)):
|
||||
return os.path.join(path, name)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"sys.platform[:5] == 'win32'",
|
||||
reason="The hybrid sh/Python dotbot script doesn't run on Windows platforms",
|
||||
)
|
||||
@pytest.mark.parametrize("python_name", (None, "python", "python2", "python3"))
|
||||
def test_find_python_executable(python_name, home, dotfiles):
|
||||
"""Verify that the sh/Python hybrid dotbot executable can find Python."""
|
||||
|
||||
dotfiles.write_config([])
|
||||
dotbot_executable = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "bin", "dotbot"
|
||||
)
|
||||
|
||||
# Create a link to sh.
|
||||
tmp_bin = os.path.join(home, "tmp_bin")
|
||||
os.makedirs(tmp_bin)
|
||||
sh_path = which("sh")
|
||||
os.symlink(sh_path, os.path.join(tmp_bin, "sh"))
|
||||
|
||||
if python_name:
|
||||
with open(os.path.join(tmp_bin, python_name), "w") as file:
|
||||
file.write("#!" + tmp_bin + "/sh\n")
|
||||
file.write("exit 0\n")
|
||||
os.chmod(os.path.join(tmp_bin, python_name), 0o777)
|
||||
env = dict(os.environ)
|
||||
env["PATH"] = tmp_bin
|
||||
|
||||
if python_name:
|
||||
subprocess.check_call(
|
||||
[dotbot_executable, "-c", dotfiles.config_filename],
|
||||
env=env,
|
||||
)
|
||||
else:
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
subprocess.check_call(
|
||||
[dotbot_executable, "-c", dotfiles.config_filename],
|
||||
env=env,
|
||||
)
|
136
tests/test_clean.py
Normal file
136
tests/test_clean.py
Normal file
|
@ -0,0 +1,136 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_clean_default(root, home, dotfiles, run_dotbot):
|
||||
"""Verify clean uses default unless overridden."""
|
||||
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, ".g"))
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"clean": {
|
||||
"~/nonexistent": {"force": True},
|
||||
"~/": None,
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.isdir(os.path.join(home, "nonexistent"))
|
||||
assert os.path.islink(os.path.join(home, ".g"))
|
||||
|
||||
|
||||
def test_clean_environment_variable_expansion(home, dotfiles, run_dotbot):
|
||||
"""Verify clean expands environment variables."""
|
||||
|
||||
os.symlink(os.path.join(dotfiles.directory, "f"), os.path.join(home, ".f"))
|
||||
variable = "$HOME"
|
||||
if sys.platform[:5] == "win32":
|
||||
variable = "$USERPROFILE"
|
||||
dotfiles.write_config([{"clean": [variable]}])
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".f"))
|
||||
|
||||
|
||||
def test_clean_missing(home, dotfiles, run_dotbot):
|
||||
"""Verify clean deletes links to missing files."""
|
||||
|
||||
dotfiles.write("f")
|
||||
os.symlink(os.path.join(dotfiles.directory, "f"), os.path.join(home, ".f"))
|
||||
os.symlink(os.path.join(dotfiles.directory, "g"), os.path.join(home, ".g"))
|
||||
dotfiles.write_config([{"clean": ["~"]}])
|
||||
run_dotbot()
|
||||
|
||||
assert os.path.islink(os.path.join(home, ".f"))
|
||||
assert not os.path.islink(os.path.join(home, ".g"))
|
||||
|
||||
|
||||
def test_clean_nonexistent(home, dotfiles, run_dotbot):
|
||||
"""Verify clean ignores nonexistent directories."""
|
||||
|
||||
dotfiles.write_config([{"clean": ["~", "~/fake"]}])
|
||||
run_dotbot() # Nonexistent directories should not raise exceptions.
|
||||
|
||||
assert not os.path.isdir(os.path.join(home, "fake"))
|
||||
|
||||
|
||||
def test_clean_outside_force(root, home, dotfiles, run_dotbot):
|
||||
"""Verify clean forced to remove files linking outside dotfiles directory."""
|
||||
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, ".g"))
|
||||
dotfiles.write_config([{"clean": {"~/": {"force": True}}}])
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".g"))
|
||||
|
||||
|
||||
def test_clean_outside(root, home, dotfiles, run_dotbot):
|
||||
"""Verify clean ignores files linking outside dotfiles directory."""
|
||||
|
||||
os.symlink(os.path.join(dotfiles.directory, "f"), os.path.join(home, ".f"))
|
||||
os.symlink(os.path.join(home, "g"), os.path.join(home, ".g"))
|
||||
dotfiles.write_config([{"clean": ["~"]}])
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".f"))
|
||||
assert os.path.islink(os.path.join(home, ".g"))
|
||||
|
||||
|
||||
def test_clean_recursive_1(root, home, dotfiles, run_dotbot):
|
||||
"""Verify clean respects when the recursive directive is off (default)."""
|
||||
|
||||
os.makedirs(os.path.join(home, "a", "b"))
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, "c"))
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, "a", "d"))
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, "a", "b", "e"))
|
||||
dotfiles.write_config([{"clean": {"~": {"force": True}}}])
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, "c"))
|
||||
assert os.path.islink(os.path.join(home, "a", "d"))
|
||||
assert os.path.islink(os.path.join(home, "a", "b", "e"))
|
||||
|
||||
|
||||
def test_clean_recursive_2(root, home, dotfiles, run_dotbot):
|
||||
"""Verify clean respects when the recursive directive is on."""
|
||||
|
||||
os.makedirs(os.path.join(home, "a", "b"))
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, "c"))
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, "a", "d"))
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, "a", "b", "e"))
|
||||
dotfiles.write_config([{"clean": {"~": {"force": True, "recursive": True}}}])
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, "c"))
|
||||
assert not os.path.islink(os.path.join(home, "a", "d"))
|
||||
assert not os.path.islink(os.path.join(home, "a", "b", "e"))
|
||||
|
||||
|
||||
def test_clean_defaults_1(root, home, dotfiles, run_dotbot):
|
||||
"""Verify that clean doesn't erase non-dotfiles links by default."""
|
||||
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, ".g"))
|
||||
dotfiles.write_config([{"clean": ["~"]}])
|
||||
run_dotbot()
|
||||
|
||||
assert os.path.islink(os.path.join(home, ".g"))
|
||||
|
||||
|
||||
def test_clean_defaults_2(root, home, dotfiles, run_dotbot):
|
||||
"""Verify that explicit clean defaults override the implicit default."""
|
||||
|
||||
os.symlink(os.path.join(root, "nowhere"), os.path.join(home, ".g"))
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"clean": {"force": True}}},
|
||||
{"clean": ["~"]},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".g"))
|
172
tests/test_cli.py
Normal file
172
tests/test_cli.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_except_create(capfd, home, dotfiles, run_dotbot):
|
||||
"""Verify that `--except` works as intended."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"create": ["~/a"]},
|
||||
{
|
||||
"shell": [
|
||||
{"command": "echo success", "stdout": True},
|
||||
]
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot("--except", "create")
|
||||
|
||||
assert not os.path.exists(os.path.join(home, "a"))
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("success") for line in stdout)
|
||||
|
||||
|
||||
def test_except_shell(capfd, home, dotfiles, run_dotbot):
|
||||
"""Verify that `--except` works as intended."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"create": ["~/a"]},
|
||||
{
|
||||
"shell": [
|
||||
{"command": "echo failure", "stdout": True},
|
||||
]
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot("--except", "shell")
|
||||
|
||||
assert os.path.exists(os.path.join(home, "a"))
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("failure") for line in stdout)
|
||||
|
||||
|
||||
def test_except_multiples(capfd, home, dotfiles, run_dotbot):
|
||||
"""Verify that `--except` works with multiple exceptions."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"create": ["~/a"]},
|
||||
{
|
||||
"shell": [
|
||||
{"command": "echo failure", "stdout": True},
|
||||
]
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot("--except", "create", "shell")
|
||||
|
||||
assert not os.path.exists(os.path.join(home, "a"))
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("failure") for line in stdout)
|
||||
|
||||
|
||||
def test_exit_on_failure(capfd, home, dotfiles, run_dotbot):
|
||||
"""Verify that processing can halt immediately on failures."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"create": ["~/a"]},
|
||||
{"shell": ["this_is_not_a_command"]},
|
||||
{"create": ["~/b"]},
|
||||
]
|
||||
)
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot("-x")
|
||||
|
||||
assert os.path.isdir(os.path.join(home, "a"))
|
||||
assert not os.path.isdir(os.path.join(home, "b"))
|
||||
|
||||
|
||||
def test_only(capfd, home, dotfiles, run_dotbot):
|
||||
"""Verify that `--only` works as intended."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"create": ["~/a"]},
|
||||
{"shell": [{"command": "echo success", "stdout": True}]},
|
||||
]
|
||||
)
|
||||
run_dotbot("--only", "shell")
|
||||
|
||||
assert not os.path.exists(os.path.join(home, "a"))
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("success") for line in stdout)
|
||||
|
||||
|
||||
def test_only_with_defaults(capfd, home, dotfiles, run_dotbot):
|
||||
"""Verify that `--only` does not suppress defaults."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"shell": {"stdout": True}}},
|
||||
{"create": ["~/a"]},
|
||||
{"shell": [{"command": "echo success"}]},
|
||||
]
|
||||
)
|
||||
run_dotbot("--only", "shell")
|
||||
|
||||
assert not os.path.exists(os.path.join(home, "a"))
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("success") for line in stdout)
|
||||
|
||||
|
||||
def test_only_with_multiples(capfd, home, dotfiles, run_dotbot):
|
||||
"""Verify that `--only` works as intended."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"create": ["~/a"]},
|
||||
{"shell": [{"command": "echo success", "stdout": True}]},
|
||||
{"link": ["~/.f"]},
|
||||
]
|
||||
)
|
||||
run_dotbot("--only", "create", "shell")
|
||||
|
||||
assert os.path.isdir(os.path.join(home, "a"))
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("success") for line in stdout)
|
||||
assert not os.path.exists(os.path.join(home, ".f"))
|
||||
|
||||
|
||||
def test_plugin_loading_file(home, dotfiles, run_dotbot):
|
||||
"""Verify that plugins can be loaded by file."""
|
||||
|
||||
plugin_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dotbot_plugin_file.py")
|
||||
shutil.copy(plugin_file, os.path.join(dotfiles.directory, "file.py"))
|
||||
dotfiles.write_config([{"plugin_file": "~"}])
|
||||
run_dotbot("--plugin", os.path.join(dotfiles.directory, "file.py"))
|
||||
|
||||
with open(os.path.join(home, "flag"), "r") as file:
|
||||
assert file.read() == "file plugin loading works"
|
||||
|
||||
|
||||
def test_plugin_loading_directory(home, dotfiles, run_dotbot):
|
||||
"""Verify that plugins can be loaded from a directory."""
|
||||
|
||||
dotfiles.makedirs("plugins")
|
||||
plugin_file = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "dotbot_plugin_directory.py"
|
||||
)
|
||||
shutil.copy(plugin_file, os.path.join(dotfiles.directory, "plugins", "directory.py"))
|
||||
dotfiles.write_config([{"plugin_directory": "~"}])
|
||||
run_dotbot("--plugin-dir", os.path.join(dotfiles.directory, "plugins"))
|
||||
|
||||
with open(os.path.join(home, "flag"), "r") as file:
|
||||
assert file.read() == "directory plugin loading works"
|
||||
|
||||
|
||||
def test_disable_builtin_plugins(home, dotfiles, run_dotbot):
|
||||
"""Verify that builtin plugins can be disabled."""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config([{"link": {"~/.f": "f"}}])
|
||||
|
||||
# The link directive will be unhandled so dotbot will raise SystemExit.
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot("--disable-built-in-plugins")
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".f"))
|
36
tests/test_config.py
Normal file
36
tests/test_config.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
|
||||
def test_config_blank(dotfiles, run_dotbot):
|
||||
"""Verify blank configs work."""
|
||||
|
||||
dotfiles.write_config([])
|
||||
run_dotbot()
|
||||
|
||||
|
||||
def test_config_empty(dotfiles, run_dotbot):
|
||||
"""Verify empty configs work."""
|
||||
|
||||
dotfiles.write("config.yaml", "")
|
||||
run_dotbot("-c", os.path.join(dotfiles.directory, "config.yaml"), custom=True)
|
||||
|
||||
|
||||
def test_json(home, dotfiles, run_dotbot):
|
||||
"""Verify JSON configs work."""
|
||||
|
||||
document = json.dumps([{"create": ["~/d"]}])
|
||||
dotfiles.write("config.json", document)
|
||||
run_dotbot("-c", os.path.join(dotfiles.directory, "config.json"), custom=True)
|
||||
|
||||
assert os.path.isdir(os.path.join(home, "d"))
|
||||
|
||||
|
||||
def test_json_tabs(home, dotfiles, run_dotbot):
|
||||
"""Verify JSON configs with tabs work."""
|
||||
|
||||
document = """[\n\t{\n\t\t"create": ["~/d"]\n\t}\n]"""
|
||||
dotfiles.write("config.json", document)
|
||||
run_dotbot("-c", os.path.join(dotfiles.directory, "config.json"), custom=True)
|
||||
|
||||
assert os.path.isdir(os.path.join(home, "d"))
|
55
tests/test_create.py
Normal file
55
tests/test_create.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import os
|
||||
import stat
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("directory", ("~/a", "~/b/c"))
|
||||
def test_directory_creation(home, directory, dotfiles, run_dotbot):
|
||||
"""Test creating directories, including nested directories."""
|
||||
|
||||
dotfiles.write_config([{"create": [directory]}])
|
||||
run_dotbot()
|
||||
|
||||
expanded_directory = os.path.abspath(os.path.expanduser(directory))
|
||||
assert os.path.isdir(expanded_directory)
|
||||
assert os.stat(expanded_directory).st_mode & 0o777 == 0o777
|
||||
|
||||
|
||||
def test_default_mode(home, dotfiles, run_dotbot):
|
||||
"""Test creating a directory with an explicit default mode.
|
||||
|
||||
Note: `os.chmod()` on Windows only supports changing write permissions.
|
||||
Therefore, this test is restricted to testing read-only access.
|
||||
"""
|
||||
|
||||
read_only = 0o777 - stat.S_IWUSR - stat.S_IWGRP - stat.S_IWOTH
|
||||
config = [{"defaults": {"create": {"mode": read_only}}}, {"create": ["~/a"]}]
|
||||
dotfiles.write_config(config)
|
||||
run_dotbot()
|
||||
|
||||
directory = os.path.abspath(os.path.expanduser("~/a"))
|
||||
assert os.stat(directory).st_mode & stat.S_IWUSR == 0
|
||||
assert os.stat(directory).st_mode & stat.S_IWGRP == 0
|
||||
assert os.stat(directory).st_mode & stat.S_IWOTH == 0
|
||||
|
||||
|
||||
def test_default_mode_override(home, dotfiles, run_dotbot):
|
||||
"""Test creating a directory that overrides an explicit default mode.
|
||||
|
||||
Note: `os.chmod()` on Windows only supports changing write permissions.
|
||||
Therefore, this test is restricted to testing read-only access.
|
||||
"""
|
||||
|
||||
read_only = 0o777 - stat.S_IWUSR - stat.S_IWGRP - stat.S_IWOTH
|
||||
config = [
|
||||
{"defaults": {"create": {"mode": read_only}}},
|
||||
{"create": {"~/a": {"mode": 0o777}}},
|
||||
]
|
||||
dotfiles.write_config(config)
|
||||
run_dotbot()
|
||||
|
||||
directory = os.path.abspath(os.path.expanduser("~/a"))
|
||||
assert os.stat(directory).st_mode & stat.S_IWUSR == stat.S_IWUSR
|
||||
assert os.stat(directory).st_mode & stat.S_IWGRP == stat.S_IWGRP
|
||||
assert os.stat(directory).st_mode & stat.S_IWOTH == stat.S_IWOTH
|
966
tests/test_link.py
Normal file
966
tests/test_link.py
Normal file
|
@ -0,0 +1,966 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_link_canonicalization(home, dotfiles, run_dotbot):
|
||||
"""Verify links to symlinked destinations are canonical.
|
||||
|
||||
"Canonical", here, means that dotbot does not create symlinks
|
||||
that point to intermediary symlinks.
|
||||
"""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config([{"link": {"~/.f": {"path": "f"}}}])
|
||||
|
||||
# Point to the config file in a symlinked dotfiles directory.
|
||||
dotfiles_symlink = os.path.join(home, "dotfiles-symlink")
|
||||
os.symlink(dotfiles.directory, dotfiles_symlink)
|
||||
config_file = os.path.join(dotfiles_symlink, os.path.basename(dotfiles.config_filename))
|
||||
run_dotbot("-c", config_file, custom=True)
|
||||
|
||||
expected = os.path.join(dotfiles.directory, "f")
|
||||
actual = os.readlink(os.path.abspath(os.path.expanduser("~/.f")))
|
||||
if sys.platform[:5] == "win32" and actual.startswith("\\\\?\\"):
|
||||
actual = actual[4:]
|
||||
assert expected == actual
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dst", ("~/.f", "~/f"))
|
||||
@pytest.mark.parametrize("include_force", (True, False))
|
||||
def test_link_default_source(root, home, dst, include_force, dotfiles, run_dotbot):
|
||||
"""Verify that default sources are calculated correctly.
|
||||
|
||||
This test includes verifying files with and without leading periods,
|
||||
as well as verifying handling of None dict values.
|
||||
"""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
config = [
|
||||
{
|
||||
"link": {
|
||||
dst: {"force": False} if include_force else None,
|
||||
}
|
||||
}
|
||||
]
|
||||
dotfiles.write_config(config)
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.abspath(os.path.expanduser(dst)), "r") as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
def test_link_environment_user_expansion_target(home, dotfiles, run_dotbot):
|
||||
"""Verify link expands user in target."""
|
||||
|
||||
src = "~/f"
|
||||
target = "~/g"
|
||||
with open(os.path.abspath(os.path.expanduser(src)), "w") as file:
|
||||
file.write("apple")
|
||||
dotfiles.write_config([{"link": {target: src}}])
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.abspath(os.path.expanduser(target)), "r") as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
def test_link_environment_variable_expansion_source(monkeypatch, root, home, dotfiles, run_dotbot):
|
||||
"""Verify link expands environment variables in source."""
|
||||
|
||||
monkeypatch.setenv("APPLE", "h")
|
||||
target = "~/.i"
|
||||
src = "$APPLE"
|
||||
dotfiles.write("h", "grape")
|
||||
dotfiles.write_config([{"link": {target: src}}])
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.abspath(os.path.expanduser(target)), "r") as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
def test_link_environment_variable_expansion_source_extended(
|
||||
monkeypatch, root, home, dotfiles, run_dotbot
|
||||
):
|
||||
"""Verify link expands environment variables in extended config syntax."""
|
||||
|
||||
monkeypatch.setenv("APPLE", "h")
|
||||
target = "~/.i"
|
||||
src = "$APPLE"
|
||||
dotfiles.write("h", "grape")
|
||||
dotfiles.write_config([{"link": {target: {"path": src, "relink": True}}}])
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.abspath(os.path.expanduser(target)), "r") as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
def test_link_environment_variable_expansion_target(monkeypatch, root, home, dotfiles, run_dotbot):
|
||||
"""Verify link expands environment variables in target.
|
||||
|
||||
If the variable doesn't exist, the "variable" must not be replaced.
|
||||
"""
|
||||
|
||||
monkeypatch.setenv("ORANGE", ".config")
|
||||
monkeypatch.setenv("BANANA", "g")
|
||||
monkeypatch.delenv("PEAR", raising=False)
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write("h", "grape")
|
||||
|
||||
config = [
|
||||
{
|
||||
"link": {
|
||||
"~/${ORANGE}/$BANANA": {
|
||||
"path": "f",
|
||||
"create": True,
|
||||
},
|
||||
"~/$PEAR": "h",
|
||||
}
|
||||
}
|
||||
]
|
||||
dotfiles.write_config(config)
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, ".config", "g"), "r") as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, "$PEAR"), "r") as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
def test_link_environment_variable_unset(monkeypatch, root, home, dotfiles, run_dotbot):
|
||||
"""Verify link leaves unset environment variables."""
|
||||
|
||||
monkeypatch.delenv("ORANGE", raising=False)
|
||||
dotfiles.write("$ORANGE", "apple")
|
||||
dotfiles.write_config([{"link": {"~/f": "$ORANGE"}}])
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, "f"), "r") as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
def test_link_force_leaves_when_nonexistent(root, home, dotfiles, run_dotbot):
|
||||
"""Verify force doesn't erase sources when targets are nonexistent."""
|
||||
|
||||
os.mkdir(os.path.join(home, "dir"))
|
||||
open(os.path.join(home, "file"), "a").close()
|
||||
config = [
|
||||
{
|
||||
"link": {
|
||||
"~/dir": {"path": "dir", "force": True},
|
||||
"~/file": {"path": "file", "force": True},
|
||||
}
|
||||
}
|
||||
]
|
||||
dotfiles.write_config(config)
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot()
|
||||
|
||||
assert os.path.isdir(os.path.join(home, "dir"))
|
||||
assert os.path.isfile(os.path.join(home, "file"))
|
||||
|
||||
|
||||
def test_link_force_overwrite_symlink(home, dotfiles, run_dotbot):
|
||||
"""Verify force overwrites a symlinked directory."""
|
||||
|
||||
os.mkdir(os.path.join(home, "dir"))
|
||||
dotfiles.write("dir/f")
|
||||
os.symlink(home, os.path.join(home, ".dir"))
|
||||
|
||||
config = [{"link": {"~/.dir": {"path": "dir", "force": True}}}]
|
||||
dotfiles.write_config(config)
|
||||
run_dotbot()
|
||||
|
||||
assert os.path.isfile(os.path.join(home, ".dir", "f"))
|
||||
|
||||
|
||||
def test_link_glob_1(home, dotfiles, run_dotbot):
|
||||
"""Verify globbing works."""
|
||||
|
||||
dotfiles.write("bin/a", "apple")
|
||||
dotfiles.write("bin/b", "banana")
|
||||
dotfiles.write("bin/c", "cherry")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"link": {"glob": True, "create": True}}},
|
||||
{"link": {"~/bin": "bin/*"}},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, "bin", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, "bin", "b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, "bin", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
def test_link_glob_2(home, dotfiles, run_dotbot):
|
||||
"""Verify globbing works with a trailing slash in the source."""
|
||||
|
||||
dotfiles.write("bin/a", "apple")
|
||||
dotfiles.write("bin/b", "banana")
|
||||
dotfiles.write("bin/c", "cherry")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"link": {"glob": True, "create": True}}},
|
||||
{"link": {"~/bin/": "bin/*"}},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, "bin", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, "bin", "b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, "bin", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
def test_link_glob_3(home, dotfiles, run_dotbot):
|
||||
"""Verify globbing works with hidden ("period-prefixed") files."""
|
||||
|
||||
dotfiles.write("bin/.a", "dot-apple")
|
||||
dotfiles.write("bin/.b", "dot-banana")
|
||||
dotfiles.write("bin/.c", "dot-cherry")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"link": {"glob": True, "create": True}}},
|
||||
{"link": {"~/bin/": "bin/.*"}},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, "bin", ".a")) as file:
|
||||
assert file.read() == "dot-apple"
|
||||
with open(os.path.join(home, "bin", ".b")) as file:
|
||||
assert file.read() == "dot-banana"
|
||||
with open(os.path.join(home, "bin", ".c")) as file:
|
||||
assert file.read() == "dot-cherry"
|
||||
|
||||
|
||||
def test_link_glob_4(home, dotfiles, run_dotbot):
|
||||
"""Verify globbing works at the root of the home and dotfiles directories."""
|
||||
|
||||
dotfiles.write(".a", "dot-apple")
|
||||
dotfiles.write(".b", "dot-banana")
|
||||
dotfiles.write(".c", "dot-cherry")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~": {
|
||||
"path": ".*",
|
||||
"glob": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, ".a")) as file:
|
||||
assert file.read() == "dot-apple"
|
||||
with open(os.path.join(home, ".b")) as file:
|
||||
assert file.read() == "dot-banana"
|
||||
with open(os.path.join(home, ".c")) as file:
|
||||
assert file.read() == "dot-cherry"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ("foo", "foo/"))
|
||||
def test_link_glob_ambiguous_failure(path, home, dotfiles, run_dotbot):
|
||||
"""Verify ambiguous link globbing fails."""
|
||||
|
||||
dotfiles.makedirs("foo")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~/foo/": {
|
||||
"path": path,
|
||||
"glob": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot()
|
||||
assert not os.path.exists(os.path.join(home, "foo"))
|
||||
|
||||
|
||||
def test_link_glob_ambiguous_success(home, dotfiles, run_dotbot):
|
||||
"""Verify the case where ambiguous link globbing succeeds."""
|
||||
|
||||
dotfiles.makedirs("foo")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~/foo": {
|
||||
"path": "foo",
|
||||
"glob": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
assert os.path.exists(os.path.join(home, "foo"))
|
||||
|
||||
|
||||
def test_link_glob_exclude_1(home, dotfiles, run_dotbot):
|
||||
"""Verify link globbing with an explicit exclusion."""
|
||||
|
||||
dotfiles.write("config/foo/a", "apple")
|
||||
dotfiles.write("config/bar/b", "banana")
|
||||
dotfiles.write("config/bar/c", "cherry")
|
||||
dotfiles.write("config/baz/d", "donut")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"defaults": {
|
||||
"link": {
|
||||
"glob": True,
|
||||
"create": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"~/.config/": {
|
||||
"path": "config/*",
|
||||
"exclude": ["config/baz"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".config", "baz"))
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".config"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "foo"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "bar"))
|
||||
with open(os.path.join(home, ".config", "foo", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".config", "bar", "b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, ".config", "bar", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
def test_link_glob_exclude_2(home, dotfiles, run_dotbot):
|
||||
"""Verify deep link globbing with a globbed exclusion."""
|
||||
|
||||
dotfiles.write("config/foo/a", "apple")
|
||||
dotfiles.write("config/bar/b", "banana")
|
||||
dotfiles.write("config/bar/c", "cherry")
|
||||
dotfiles.write("config/baz/d", "donut")
|
||||
dotfiles.write("config/baz/buzz/e", "egg")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"defaults": {
|
||||
"link": {
|
||||
"glob": True,
|
||||
"create": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"~/.config/": {
|
||||
"path": "config/*/*",
|
||||
"exclude": ["config/baz/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".config", "baz"))
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".config"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "foo"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "bar"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "foo", "a"))
|
||||
with open(os.path.join(home, ".config", "foo", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".config", "bar", "b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, ".config", "bar", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
def test_link_glob_exclude_3(home, dotfiles, run_dotbot):
|
||||
"""Verify deep link globbing with an explicit exclusion."""
|
||||
|
||||
dotfiles.write("config/foo/a", "apple")
|
||||
dotfiles.write("config/bar/b", "banana")
|
||||
dotfiles.write("config/bar/c", "cherry")
|
||||
dotfiles.write("config/baz/d", "donut")
|
||||
dotfiles.write("config/baz/buzz/e", "egg")
|
||||
dotfiles.write("config/baz/bizz/g", "grape")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"defaults": {
|
||||
"link": {
|
||||
"glob": True,
|
||||
"create": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"~/.config/": {
|
||||
"path": "config/*/*",
|
||||
"exclude": ["config/baz/buzz"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".config", "baz", "buzz"))
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".config"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "foo"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "bar"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "baz"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "baz", "bizz"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "foo", "a"))
|
||||
with open(os.path.join(home, ".config", "foo", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".config", "bar", "b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, ".config", "bar", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
with open(os.path.join(home, ".config", "baz", "d")) as file:
|
||||
assert file.read() == "donut"
|
||||
with open(os.path.join(home, ".config", "baz", "bizz", "g")) as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
def test_link_glob_exclude_4(home, dotfiles, run_dotbot):
|
||||
"""Verify deep link globbing with multiple globbed exclusions."""
|
||||
|
||||
dotfiles.write("config/foo/a", "apple")
|
||||
dotfiles.write("config/bar/b", "banana")
|
||||
dotfiles.write("config/bar/c", "cherry")
|
||||
dotfiles.write("config/baz/d", "donut")
|
||||
dotfiles.write("config/baz/buzz/e", "egg")
|
||||
dotfiles.write("config/baz/bizz/g", "grape")
|
||||
dotfiles.write("config/fiz/f", "fig")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"defaults": {
|
||||
"link": {
|
||||
"glob": True,
|
||||
"create": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"~/.config/": {
|
||||
"path": "config/*/*",
|
||||
"exclude": ["config/baz/*", "config/fiz/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".config", "baz"))
|
||||
assert not os.path.exists(os.path.join(home, ".config", "fiz"))
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".config"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "foo"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "bar"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "foo", "a"))
|
||||
with open(os.path.join(home, ".config", "foo", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".config", "bar", "b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, ".config", "bar", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
def test_link_glob_multi_star(home, dotfiles, run_dotbot):
|
||||
"""Verify link globbing with deep-nested stars."""
|
||||
|
||||
dotfiles.write("config/foo/a", "apple")
|
||||
dotfiles.write("config/bar/b", "banana")
|
||||
dotfiles.write("config/bar/c", "cherry")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"link": {"glob": True, "create": True}}},
|
||||
{"link": {"~/.config/": "config/*/*"}},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".config"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "foo"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "bar"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "foo", "a"))
|
||||
with open(os.path.join(home, ".config", "foo", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".config", "bar", "b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, ".config", "bar", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pattern, expect_file",
|
||||
(
|
||||
("conf/*", lambda fruit: fruit),
|
||||
("conf/.*", lambda fruit: "." + fruit),
|
||||
("conf/[bc]*", lambda fruit: fruit if fruit[0] in "bc" else None),
|
||||
("conf/*e", lambda fruit: fruit if fruit[-1] == "e" else None),
|
||||
("conf/??r*", lambda fruit: fruit if fruit[2] == "r" else None),
|
||||
),
|
||||
)
|
||||
def test_link_glob_patterns(pattern, expect_file, home, dotfiles, run_dotbot):
|
||||
"""Verify link glob pattern matching."""
|
||||
|
||||
fruits = ["apple", "apricot", "banana", "cherry", "currant", "cantalope"]
|
||||
[dotfiles.write("conf/" + fruit, fruit) for fruit in fruits]
|
||||
[dotfiles.write("conf/." + fruit, "dot-" + fruit) for fruit in fruits]
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"link": {"glob": True, "create": True}}},
|
||||
{"link": {"~/globtest": pattern}},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
for fruit in fruits:
|
||||
if expect_file(fruit) is None:
|
||||
assert not os.path.exists(os.path.join(home, "globtest", fruit))
|
||||
assert not os.path.exists(os.path.join(home, "globtest", "." + fruit))
|
||||
elif "." in expect_file(fruit):
|
||||
assert not os.path.islink(os.path.join(home, "globtest", fruit))
|
||||
assert os.path.islink(os.path.join(home, "globtest", "." + fruit))
|
||||
else: # "." not in expect_file(fruit)
|
||||
assert os.path.islink(os.path.join(home, "globtest", fruit))
|
||||
assert not os.path.islink(os.path.join(home, "globtest", "." + fruit))
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"sys.version_info < (3, 5)",
|
||||
reason="Python 3.5 required for ** globbing",
|
||||
)
|
||||
def test_link_glob_recursive(home, dotfiles, run_dotbot):
|
||||
"""Verify recursive link globbing and exclusions."""
|
||||
|
||||
dotfiles.write("config/foo/bar/a", "apple")
|
||||
dotfiles.write("config/foo/bar/b", "banana")
|
||||
dotfiles.write("config/foo/bar/c", "cherry")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"link": {"glob": True, "create": True}}},
|
||||
{"link": {"~/.config/": {"path": "config/**", "exclude": ["config/**/b"]}}},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.islink(os.path.join(home, ".config"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "foo"))
|
||||
assert not os.path.islink(os.path.join(home, ".config", "foo", "bar"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "foo", "bar", "a"))
|
||||
assert not os.path.exists(os.path.join(home, ".config", "foo", "bar", "b"))
|
||||
assert os.path.islink(os.path.join(home, ".config", "foo", "bar", "c"))
|
||||
with open(os.path.join(home, ".config", "foo", "bar", "a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".config", "foo", "bar", "c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"sys.platform[:5] == 'win32'",
|
||||
reason="These if commands won't run on Windows",
|
||||
)
|
||||
def test_link_if(home, dotfiles, run_dotbot):
|
||||
"""Verify 'if' directives are checked when linking."""
|
||||
|
||||
os.mkdir(os.path.join(home, "d"))
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~/.f": {"path": "f", "if": "true"},
|
||||
"~/.g": {"path": "f", "if": "false"},
|
||||
"~/.h": {"path": "f", "if": "[ -d ~/d ]"},
|
||||
"~/.i": {"path": "f", "if": "badcommand"},
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".g"))
|
||||
assert not os.path.exists(os.path.join(home, ".i"))
|
||||
with open(os.path.join(home, ".f")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".h")) as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"sys.platform[:5] == 'win32'",
|
||||
reason="These if commands won't run on Windows.",
|
||||
)
|
||||
def test_link_if_defaults(home, dotfiles, run_dotbot):
|
||||
"""Verify 'if' directive defaults are checked when linking."""
|
||||
|
||||
os.mkdir(os.path.join(home, "d"))
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"defaults": {
|
||||
"link": {
|
||||
"if": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"~/.j": {"path": "f", "if": "true"},
|
||||
"~/.k": {"path": "f"}, # default is false
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".k"))
|
||||
with open(os.path.join(home, ".j")) as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"sys.platform[:5] != 'win32'",
|
||||
reason="These if commands only run on Windows.",
|
||||
)
|
||||
def test_link_if_windows(home, dotfiles, run_dotbot):
|
||||
"""Verify 'if' directives are checked when linking (Windows only)."""
|
||||
|
||||
os.mkdir(os.path.join(home, "d"))
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~/.f": {"path": "f", "if": 'cmd /c "exit 0"'},
|
||||
"~/.g": {"path": "f", "if": 'cmd /c "exit 1"'},
|
||||
"~/.h": {"path": "f", "if": 'cmd /c "dir %USERPROFILE%\\d'},
|
||||
"~/.i": {"path": "f", "if": 'cmd /c "badcommand"'},
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".g"))
|
||||
assert not os.path.exists(os.path.join(home, ".i"))
|
||||
with open(os.path.join(home, ".f")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".h")) as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"sys.platform[:5] != 'win32'",
|
||||
reason="These if commands only run on Windows",
|
||||
)
|
||||
def test_link_if_defaults_windows(home, dotfiles, run_dotbot):
|
||||
"""Verify 'if' directive defaults are checked when linking (Windows only)."""
|
||||
|
||||
os.mkdir(os.path.join(home, "d"))
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"defaults": {
|
||||
"link": {
|
||||
"if": 'cmd /c "exit 1"',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"~/.j": {"path": "f", "if": 'cmd /c "exit 0"'},
|
||||
"~/.k": {"path": "f"}, # default is false
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
assert not os.path.exists(os.path.join(home, ".k"))
|
||||
with open(os.path.join(home, ".j")) as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ignore_missing", (True, False))
|
||||
def test_link_ignore_missing(ignore_missing, home, dotfiles, run_dotbot):
|
||||
"""Verify link 'ignore_missing' is respected when the target is missing."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~/missing_link": {
|
||||
"path": "missing",
|
||||
"ignore-missing": ignore_missing,
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
if ignore_missing:
|
||||
run_dotbot()
|
||||
assert os.path.islink(os.path.join(home, "missing_link"))
|
||||
else:
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot()
|
||||
|
||||
|
||||
def test_link_leaves_file(home, dotfiles, run_dotbot):
|
||||
"""Verify relink does not overwrite file."""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
with open(os.path.join(home, ".f"), "w") as file:
|
||||
file.write("grape")
|
||||
dotfiles.write_config([{"link": {"~/.f": "f"}}])
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, ".f"), "r") as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ("canonicalize-path", "canonicalize"))
|
||||
def test_link_no_canonicalize(key, home, dotfiles, run_dotbot):
|
||||
"""Verify link canonicalization can be disabled."""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config([{"defaults": {"link": {key: False}}}, {"link": {"~/.f": {"path": "f"}}}])
|
||||
try:
|
||||
os.symlink(
|
||||
dotfiles.directory,
|
||||
os.path.join(home, "dotfiles-symlink"),
|
||||
target_is_directory=True,
|
||||
)
|
||||
except TypeError:
|
||||
# Python 2 compatibility:
|
||||
# target_is_directory is only consistently available after Python 3.3.
|
||||
os.symlink(
|
||||
dotfiles.directory,
|
||||
os.path.join(home, "dotfiles-symlink"),
|
||||
)
|
||||
run_dotbot(
|
||||
"-c",
|
||||
os.path.join(home, "dotfiles-symlink", os.path.basename(dotfiles.config_filename)),
|
||||
custom=True,
|
||||
)
|
||||
assert "dotfiles-symlink" in os.readlink(os.path.join(home, ".f"))
|
||||
|
||||
|
||||
def test_link_prefix(home, dotfiles, run_dotbot):
|
||||
"""Verify link prefixes are prepended."""
|
||||
|
||||
dotfiles.write("conf/a", "apple")
|
||||
dotfiles.write("conf/b", "banana")
|
||||
dotfiles.write("conf/c", "cherry")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~/": {
|
||||
"glob": True,
|
||||
"path": "conf/*",
|
||||
"prefix": ".",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
with open(os.path.join(home, ".a")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".b")) as file:
|
||||
assert file.read() == "banana"
|
||||
with open(os.path.join(home, ".c")) as file:
|
||||
assert file.read() == "cherry"
|
||||
|
||||
|
||||
def test_link_relative(home, dotfiles, run_dotbot):
|
||||
"""Test relative linking works."""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write("d/e", "grape")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"~/.f": {
|
||||
"path": "f",
|
||||
},
|
||||
"~/.frel": {
|
||||
"path": "f",
|
||||
"relative": True,
|
||||
},
|
||||
"~/nested/.frel": {
|
||||
"path": "f",
|
||||
"relative": True,
|
||||
"create": True,
|
||||
},
|
||||
"~/.d": {
|
||||
"path": "d",
|
||||
"relative": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
f = os.readlink(os.path.join(home, ".f"))
|
||||
if sys.platform[:5] == "win32" and f.startswith("\\\\?\\"):
|
||||
f = f[4:]
|
||||
assert f == os.path.join(dotfiles.directory, "f")
|
||||
|
||||
frel = os.readlink(os.path.join(home, ".frel"))
|
||||
if sys.platform[:5] == "win32" and frel.startswith("\\\\?\\"):
|
||||
frel = frel[4:]
|
||||
assert frel == os.path.normpath("../../dotfiles/f")
|
||||
|
||||
nested_frel = os.readlink(os.path.join(home, "nested", ".frel"))
|
||||
if sys.platform[:5] == "win32" and nested_frel.startswith("\\\\?\\"):
|
||||
nested_frel = nested_frel[4:]
|
||||
assert nested_frel == os.path.normpath("../../../dotfiles/f")
|
||||
|
||||
d = os.readlink(os.path.join(home, ".d"))
|
||||
if sys.platform[:5] == "win32" and d.startswith("\\\\?\\"):
|
||||
d = d[4:]
|
||||
assert d == os.path.normpath("../../dotfiles/d")
|
||||
|
||||
with open(os.path.join(home, ".f")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".frel")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, "nested", ".frel")) as file:
|
||||
assert file.read() == "apple"
|
||||
with open(os.path.join(home, ".d", "e")) as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
def test_link_relink_leaves_file(home, dotfiles, run_dotbot):
|
||||
"""Verify relink does not overwrite file."""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
with open(os.path.join(home, ".f"), "w") as file:
|
||||
file.write("grape")
|
||||
dotfiles.write_config([{"link": {"~/.f": {"path": "f", "relink": True}}}])
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot()
|
||||
with open(os.path.join(home, ".f"), "r") as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
def test_link_relink_overwrite_symlink(home, dotfiles, run_dotbot):
|
||||
"""Verify relink overwrites symlinks."""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
with open(os.path.join(home, "f"), "w") as file:
|
||||
file.write("grape")
|
||||
os.symlink(os.path.join(home, "f"), os.path.join(home, ".f"))
|
||||
dotfiles.write_config([{"link": {"~/.f": {"path": "f", "relink": True}}}])
|
||||
run_dotbot()
|
||||
with open(os.path.join(home, ".f"), "r") as file:
|
||||
assert file.read() == "apple"
|
||||
|
||||
|
||||
def test_link_relink_relative_leaves_file(home, dotfiles, run_dotbot):
|
||||
"""Verify relink relative does not incorrectly relink file."""
|
||||
|
||||
dotfiles.write("f", "apple")
|
||||
with open(os.path.join(home, ".f"), "w") as file:
|
||||
file.write("grape")
|
||||
config = [
|
||||
{
|
||||
"link": {
|
||||
"~/.folder/f": {
|
||||
"path": "f",
|
||||
"create": True,
|
||||
"relative": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
dotfiles.write_config(config)
|
||||
run_dotbot()
|
||||
|
||||
mtime = os.stat(os.path.join(home, ".folder", "f")).st_mtime
|
||||
|
||||
config[0]["link"]["~/.folder/f"]["relink"] = True
|
||||
dotfiles.write_config(config)
|
||||
run_dotbot()
|
||||
|
||||
new_mtime = os.stat(os.path.join(home, ".folder", "f")).st_mtime
|
||||
assert mtime == new_mtime
|
||||
|
||||
|
||||
def test_link_defaults_1(home, dotfiles, run_dotbot):
|
||||
"""Verify that link doesn't overwrite non-dotfiles links by default."""
|
||||
|
||||
with open(os.path.join(home, "f"), "w") as file:
|
||||
file.write("grape")
|
||||
os.symlink(os.path.join(home, "f"), os.path.join(home, ".f"))
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"link": {"~/.f": "f"},
|
||||
}
|
||||
]
|
||||
)
|
||||
with pytest.raises(SystemExit):
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, ".f"), "r") as file:
|
||||
assert file.read() == "grape"
|
||||
|
||||
|
||||
def test_link_defaults_2(home, dotfiles, run_dotbot):
|
||||
"""Verify that explicit link defaults override the implicit default."""
|
||||
|
||||
with open(os.path.join(home, "f"), "w") as file:
|
||||
file.write("grape")
|
||||
os.symlink(os.path.join(home, "f"), os.path.join(home, ".f"))
|
||||
dotfiles.write("f", "apple")
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"link": {"relink": True}}},
|
||||
{"link": {"~/.f": "f"}},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
with open(os.path.join(home, ".f"), "r") as file:
|
||||
assert file.read() == "apple"
|
25
tests/test_noop.py
Normal file
25
tests/test_noop.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_success(root):
|
||||
path = os.path.join(root, "abc.txt")
|
||||
with open(path, "wt") as f:
|
||||
f.write("hello")
|
||||
with open(path, "rt") as f:
|
||||
assert f.read() == "hello"
|
||||
|
||||
|
||||
def test_failure():
|
||||
with pytest.raises(AssertionError):
|
||||
open("abc.txt", "w")
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
open(file="abc.txt", mode="w")
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
os.mkdir("a")
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
os.mkdir(path="a")
|
261
tests/test_shell.py
Normal file
261
tests/test_shell.py
Normal file
|
@ -0,0 +1,261 @@
|
|||
def test_shell_allow_stdout(capfd, dotfiles, run_dotbot):
|
||||
"""Verify shell command STDOUT works."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"shell": [
|
||||
{
|
||||
"command": "echo apple",
|
||||
"stdout": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
output = capfd.readouterr()
|
||||
assert any([line.startswith("apple") for line in output.out.splitlines()]), output
|
||||
|
||||
|
||||
def test_shell_cli_verbosity_overrides_1(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that '-vv' overrides the implicit default stdout=False."""
|
||||
|
||||
dotfiles.write_config([{"shell": [{"command": "echo apple"}]}])
|
||||
run_dotbot("-vv")
|
||||
|
||||
lines = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("apple") for line in lines)
|
||||
|
||||
|
||||
def test_shell_cli_verbosity_overrides_2(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that '-vv' overrides an explicit stdout=False."""
|
||||
|
||||
dotfiles.write_config([{"shell": [{"command": "echo apple", "stdout": False}]}])
|
||||
run_dotbot("-vv")
|
||||
|
||||
lines = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("apple") for line in lines)
|
||||
|
||||
|
||||
def test_shell_cli_verbosity_overrides_3(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that '-vv' overrides an explicit defaults:shell:stdout=False."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"shell": {"stdout": False}}},
|
||||
{"shell": [{"command": "echo apple"}]},
|
||||
]
|
||||
)
|
||||
run_dotbot("-vv")
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("apple") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_cli_verbosity_stderr(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that commands can output to STDERR."""
|
||||
|
||||
dotfiles.write_config([{"shell": [{"command": "echo apple >&2"}]}])
|
||||
run_dotbot("-vv")
|
||||
|
||||
stderr = capfd.readouterr().err.splitlines()
|
||||
assert any(line.startswith("apple") for line in stderr)
|
||||
|
||||
|
||||
def test_shell_cli_verbosity_stderr_with_explicit_stdout_off(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that commands can output to STDERR with STDOUT explicitly off."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"shell": [
|
||||
{
|
||||
"command": "echo apple >&2",
|
||||
"stdout": False,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot("-vv")
|
||||
|
||||
stderr = capfd.readouterr().err.splitlines()
|
||||
assert any(line.startswith("apple") for line in stderr)
|
||||
|
||||
|
||||
def test_shell_cli_verbosity_stderr_with_defaults_stdout_off(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that commands can output to STDERR with defaults:shell:stdout=False."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"defaults": {
|
||||
"shell": {
|
||||
"stdout": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"shell": [
|
||||
{"command": "echo apple >&2"},
|
||||
],
|
||||
},
|
||||
]
|
||||
)
|
||||
run_dotbot("-vv")
|
||||
|
||||
stderr = capfd.readouterr().err.splitlines()
|
||||
assert any(line.startswith("apple") for line in stderr)
|
||||
|
||||
|
||||
def test_shell_single_v_verbosity_stdout(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that a single '-v' verbosity doesn't override stdout=False."""
|
||||
|
||||
dotfiles.write_config([{"shell": [{"command": "echo apple"}]}])
|
||||
run_dotbot("-v")
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("apple") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_single_v_verbosity_stderr(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that a single '-v' verbosity doesn't override stderr=False."""
|
||||
|
||||
dotfiles.write_config([{"shell": [{"command": "echo apple >&2"}]}])
|
||||
run_dotbot("-v")
|
||||
|
||||
stderr = capfd.readouterr().err.splitlines()
|
||||
assert not any(line.startswith("apple") for line in stderr)
|
||||
|
||||
|
||||
def test_shell_compact_stdout_1(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that shell command stdout works in compact form."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"shell": {"stdout": True}}},
|
||||
{"shell": ["echo apple"]},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("apple") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_compact_stdout_2(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that shell command stdout works in compact form."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"shell": {"stdout": True}}},
|
||||
{"shell": [["echo apple", "echoing message"]]},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert any(line.startswith("apple") for line in stdout)
|
||||
assert any(line.startswith("echoing message") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_stdout_disabled_by_default(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that the shell command disables stdout by default."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"shell": ["echo banana"],
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("banana") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_can_override_defaults(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that the shell command can override defaults."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{"defaults": {"shell": {"stdout": True}}},
|
||||
{"shell": [{"command": "echo apple", "stdout": False}]},
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("apple") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_quiet_default(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that quiet is off by default."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"shell": [
|
||||
{
|
||||
"command": "echo banana",
|
||||
"description": "echoing a thing...",
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("banana") for line in stdout)
|
||||
assert any("echo banana" in line for line in stdout)
|
||||
assert any(line.startswith("echoing a thing...") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_quiet_enabled_with_description(capfd, dotfiles, run_dotbot):
|
||||
"""Verify that only the description is shown when quiet is enabled."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"shell": [
|
||||
{
|
||||
"command": "echo banana",
|
||||
"description": "echoing a thing...",
|
||||
"quiet": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("banana") for line in stdout)
|
||||
assert not any("echo banana" in line for line in stdout)
|
||||
assert any(line.startswith("echoing a thing...") for line in stdout)
|
||||
|
||||
|
||||
def test_shell_quiet_enabled_without_description(capfd, dotfiles, run_dotbot):
|
||||
"""Verify nothing is shown when quiet is enabled with no description."""
|
||||
|
||||
dotfiles.write_config(
|
||||
[
|
||||
{
|
||||
"shell": [
|
||||
{
|
||||
"command": "echo banana",
|
||||
"quiet": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
)
|
||||
run_dotbot()
|
||||
|
||||
stdout = capfd.readouterr().out.splitlines()
|
||||
assert not any(line.startswith("banana") for line in stdout)
|
||||
assert not any(line.startswith("echo banana") for line in stdout)
|
64
tests/test_shim.py
Normal file
64
tests/test_shim.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def which(name):
|
||||
"""Find an executable.
|
||||
|
||||
Python 2.7 doesn't have shutil.which().
|
||||
shutil.which() is used, if possible, to handle Windows' case-insensitivity.
|
||||
"""
|
||||
|
||||
if hasattr(shutil, "which"):
|
||||
return shutil.which(name)
|
||||
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
if os.path.isfile(os.path.join(path, name)):
|
||||
return os.path.join(path, name)
|
||||
|
||||
|
||||
def test_shim(root, home, dotfiles, run_dotbot):
|
||||
"""Verify install shim works."""
|
||||
|
||||
# Skip the test if git is unavailable.
|
||||
git = which("git")
|
||||
if git is None:
|
||||
pytest.skip("git is unavailable")
|
||||
|
||||
if sys.platform[:5] == "win32":
|
||||
install = os.path.join(
|
||||
dotfiles.directory, "dotbot", "tools", "git-submodule", "install.ps1"
|
||||
)
|
||||
shim = os.path.join(dotfiles.directory, "install.ps1")
|
||||
else:
|
||||
install = os.path.join(dotfiles.directory, "dotbot", "tools", "git-submodule", "install")
|
||||
shim = os.path.join(dotfiles.directory, "install")
|
||||
|
||||
# Set up the test environment.
|
||||
git_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.chdir(dotfiles.directory)
|
||||
subprocess.check_call([git, "init"])
|
||||
subprocess.check_call(
|
||||
[git, "-c", "protocol.file.allow=always", "submodule", "add", git_directory, "dotbot"]
|
||||
)
|
||||
shutil.copy(install, shim)
|
||||
dotfiles.write("foo", "pear")
|
||||
dotfiles.write_config([{"link": {"~/.foo": "foo"}}])
|
||||
|
||||
# Run the shim script.
|
||||
env = dict(os.environ)
|
||||
if sys.platform[:5] == "win32":
|
||||
args = [which("powershell"), "-ExecutionPolicy", "RemoteSigned", shim]
|
||||
env["USERPROFILE"] = home
|
||||
else:
|
||||
args = [shim]
|
||||
env["HOME"] = home
|
||||
subprocess.check_call(args, env=env, cwd=dotfiles.directory)
|
||||
|
||||
assert os.path.islink(os.path.join(home, ".foo"))
|
||||
with open(os.path.join(home, ".foo"), "r") as file:
|
||||
assert file.read() == "pear"
|
|
@ -10,7 +10,7 @@ Set-Location $BASEDIR
|
|||
git -C $DOTBOT_DIR submodule sync --quiet --recursive
|
||||
git submodule update --init --recursive $DOTBOT_DIR
|
||||
|
||||
foreach ($PYTHON in ('python', 'python3', 'python2')) {
|
||||
foreach ($PYTHON in ('python', 'python3')) {
|
||||
# Python redirects to Microsoft Store in Windows 10 when not installed
|
||||
if (& { $ErrorActionPreference = "SilentlyContinue"
|
||||
![string]::IsNullOrEmpty((&$PYTHON -V))
|
||||
|
|
|
@ -9,7 +9,7 @@ $BASEDIR = $PSScriptRoot
|
|||
Set-Location $BASEDIR
|
||||
|
||||
Set-Location $DOTBOT_DIR && git submodule update --init --recursive
|
||||
foreach ($PYTHON in ('python', 'python3', 'python2')) {
|
||||
foreach ($PYTHON in ('python', 'python3')) {
|
||||
# Python redirects to Microsoft Store in Windows 10 when not installed
|
||||
if (& { $ErrorActionPreference = "SilentlyContinue"
|
||||
![string]::IsNullOrEmpty((&$PYTHON -V))
|
||||
|
|
74
tox.ini
Normal file
74
tox.ini
Normal file
|
@ -0,0 +1,74 @@
|
|||
[tox]
|
||||
; On Windows, only CPython >= 3.8 is supported.
|
||||
; All older versions, and PyPy, lack full symlink support.
|
||||
envlist =
|
||||
coverage_erase
|
||||
py{38, 39, 310}-all_platforms
|
||||
py{27, 35, 36, 37}-most_platforms
|
||||
pypy{2, 3}-most_platforms
|
||||
coverage_report
|
||||
skip_missing_interpreters = true
|
||||
|
||||
|
||||
[testenv]
|
||||
platform =
|
||||
all_platforms: cygwin|darwin|linux|win32
|
||||
most_platforms: cygwin|darwin|linux
|
||||
|
||||
deps =
|
||||
coverage
|
||||
pytest
|
||||
pytest-randomly
|
||||
pyyaml
|
||||
mock; python_version == "2.7"
|
||||
|
||||
commands =
|
||||
coverage run -m pytest tests/
|
||||
|
||||
|
||||
[testenv:coverage_erase]
|
||||
skipsdist = true
|
||||
skip_install = true
|
||||
deps = coverage
|
||||
commands = coverage erase
|
||||
|
||||
|
||||
[testenv:coverage_report]
|
||||
skipsdist = true
|
||||
skip_install = true
|
||||
deps = coverage
|
||||
commands_pre =
|
||||
coverage combine
|
||||
|
||||
commands =
|
||||
coverage report
|
||||
coverage html
|
||||
coverage xml
|
||||
|
||||
|
||||
[coverage:run]
|
||||
branch = true
|
||||
parallel = true
|
||||
source =
|
||||
dotbot/
|
||||
tests/
|
||||
|
||||
|
||||
[coverage:html]
|
||||
directory = htmlcov
|
||||
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
; Run on all platforms (Linux, Mac, and Windows)
|
||||
3.8: py38-all_platforms
|
||||
3.9: py39-all_platforms
|
||||
3.10: py310-all_platforms
|
||||
|
||||
; Run on most platforms (Linux and Mac)
|
||||
pypy-2.7: pypy2-most_platforms
|
||||
pypy-3.9: pypy3-most_platforms
|
||||
2.7: py27-most_platforms
|
||||
3.5: py35-most_platforms
|
||||
3.6: py36-most_platforms
|
||||
3.7: py37-most_platforms
|
Loading…
Reference in a new issue