Drop support for Python 2 and Python < 3.6
This patch also deletes all of the compatibility code that we no longer need.
This commit is contained in:
parent
712b30a445
commit
81d4a434ef
21 changed files with 98 additions and 203 deletions
26
.github/workflows/build.yml
vendored
26
.github/workflows/build.yml
vendored
|
@ -5,14 +5,14 @@ on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 8 * * 6'
|
- cron: '0 8 * * 6'
|
||||||
jobs:
|
jobs:
|
||||||
test-py3:
|
test:
|
||||||
env:
|
env:
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: ["ubuntu-20.04", "macos-latest"]
|
os: ["ubuntu-20.04", "macos-latest"]
|
||||||
python: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.9"]
|
python: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.9"]
|
||||||
include:
|
include:
|
||||||
- os: "windows-latest"
|
- os: "windows-latest"
|
||||||
python: "3.8"
|
python: "3.8"
|
||||||
|
@ -38,28 +38,6 @@ jobs:
|
||||||
python -m tox
|
python -m tox
|
||||||
python -m tox -e coverage_report
|
python -m tox -e coverage_report
|
||||||
- uses: codecov/codecov-action@v3
|
- uses: codecov/codecov-action@v3
|
||||||
|
|
||||||
test-py2:
|
|
||||||
env:
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
container:
|
|
||||||
image: python:2.7.18-buster
|
|
||||||
name: "Test: Python 2.7 on ubuntu-20.04"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- 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:
|
fmt:
|
||||||
name: Format
|
name: Format
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
91
CHANGELOG.md
91
CHANGELOG.md
|
@ -1,49 +1,52 @@
|
||||||
Note: this changelog only lists feature additions, not bugfixes. For details on
|
Note: this changelog only lists feature additions, not bugfixes. For details on
|
||||||
those, see the Git history.
|
those, see the Git history.
|
||||||
|
|
||||||
* v1.19
|
- v1.20
|
||||||
* Add `mode:` option for `create`
|
- Drop support for Python 2 and old versions of Python 3: the minimum
|
||||||
* Add `exclude:` option for `link`
|
version supported is now Python 3.6
|
||||||
* v1.18
|
- v1.19
|
||||||
* Add `--only` and `--except` flags
|
- Add `mode:` option for `create`
|
||||||
* Add support to run with `python -m dotbot`
|
- Add `exclude:` option for `link`
|
||||||
* Add `--force-color` option
|
- v1.18
|
||||||
* v1.17
|
- Add `--only` and `--except` flags
|
||||||
* Add `canonicalize-path:` option for `link`
|
- Add support to run with `python -m dotbot`
|
||||||
* v1.16
|
- Add `--force-color` option
|
||||||
* Add `create` plugin
|
- v1.17
|
||||||
* v1.15
|
- Add `canonicalize-path:` option for `link`
|
||||||
* Add `quiet:` option for `shell`
|
- v1.16
|
||||||
* v1.14
|
- Add `create` plugin
|
||||||
* Add `if:` option for `link`
|
- v1.15
|
||||||
* v1.13
|
- Add `quiet:` option for `shell`
|
||||||
* Add `--no-color` flag
|
- v1.14
|
||||||
* v1.12
|
- Add `if:` option for `link`
|
||||||
* Add globbing support to `link`
|
- v1.13
|
||||||
* v1.11
|
- Add `--no-color` flag
|
||||||
* Add force option to `clean` to remove all broken symlinks
|
- v1.12
|
||||||
* v1.10
|
- Add globbing support to `link`
|
||||||
* Update `link` to support shorthand syntax for links
|
- v1.11
|
||||||
* v1.9
|
- Add force option to `clean` to remove all broken symlinks
|
||||||
* Add support for default options for commands
|
- v1.10
|
||||||
* v1.8
|
- Update `link` to support shorthand syntax for links
|
||||||
* Update `link` to be able to create relative links
|
- v1.9
|
||||||
* v1.7
|
- Add support for default options for commands
|
||||||
* Add support for plugins
|
- v1.8
|
||||||
* v1.6
|
- Update `link` to be able to create relative links
|
||||||
* Update `link` to expand environment variables in paths
|
- v1.7
|
||||||
* v1.5
|
- Add support for plugins
|
||||||
* Update `link` to be able to automatically overwrite broken symlinks
|
- v1.6
|
||||||
* v1.4
|
- Update `link` to expand environment variables in paths
|
||||||
* Update `shell` to allow for selectively enabling/disabling stdin, stdout,
|
- v1.5
|
||||||
|
- Update `link` to be able to automatically overwrite broken symlinks
|
||||||
|
- v1.4
|
||||||
|
- Update `shell` to allow for selectively enabling/disabling stdin, stdout,
|
||||||
and stderr
|
and stderr
|
||||||
* v1.3
|
- v1.3
|
||||||
* Add support for YAML format configs
|
- Add support for YAML format configs
|
||||||
* v1.2
|
- v1.2
|
||||||
* Update `link` to be able to force create links (deleting things that were
|
- Update `link` to be able to force create links (deleting things that were
|
||||||
previously there)
|
previously there)
|
||||||
* Update `link` to be able to create parent directories
|
- Update `link` to be able to create parent directories
|
||||||
* v1.1
|
- v1.1
|
||||||
* Update `clean` to remove old broken symlinks
|
- Update `clean` to remove old broken symlinks
|
||||||
* v1.0
|
- v1.0
|
||||||
* Initial commit
|
- Initial commit
|
||||||
|
|
|
@ -192,9 +192,9 @@ mapped to extended configuration dictionaries.
|
||||||
When `glob: True`, Dotbot uses [glob.glob](https://docs.python.org/3/library/glob.html#glob.glob) to resolve glob paths, expanding Unix shell-style wildcards, which are **not** the same as regular expressions; Only the following are expanded:
|
When `glob: True`, Dotbot uses [glob.glob](https://docs.python.org/3/library/glob.html#glob.glob) to resolve glob paths, expanding Unix shell-style wildcards, which are **not** the same as regular expressions; Only the following are expanded:
|
||||||
|
|
||||||
| Pattern | Meaning |
|
| Pattern | Meaning |
|
||||||
|:---------|:-------------------------------------------------------|
|
|:---------|:-----------------------------------|
|
||||||
| `*` | matches anything |
|
| `*` | matches anything |
|
||||||
| `**` | matches any **file**, recursively (Python >= 3.5 only) |
|
| `**` | matches any **file**, recursively |
|
||||||
| `?` | matches any single character |
|
| `?` | matches any single character |
|
||||||
| `[seq]` | matches any character in `seq` |
|
| `[seq]` | matches any character in `seq` |
|
||||||
| `[!seq]` | matches any character not in `seq` |
|
| `[!seq]` | matches any character not in `seq` |
|
||||||
|
|
15
bin/dotbot
15
bin/dotbot
|
@ -7,9 +7,8 @@
|
||||||
# is useful because we don't know the name of the python binary.
|
# is useful because we don't know the name of the python binary.
|
||||||
|
|
||||||
''':' # begin python string; this line is interpreted by the shell as `:`
|
''':' # begin python string; this line is interpreted by the shell as `:`
|
||||||
command -v python >/dev/null 2>&1 && exec python "$0" "$@"
|
|
||||||
command -v python3 >/dev/null 2>&1 && exec python3 "$0" "$@"
|
command -v python3 >/dev/null 2>&1 && exec python3 "$0" "$@"
|
||||||
command -v python2 >/dev/null 2>&1 && exec python2 "$0" "$@"
|
command -v python >/dev/null 2>&1 && exec python "$0" "$@"
|
||||||
>&2 echo "error: cannot find python"
|
>&2 echo "error: cannot find python"
|
||||||
exit 1
|
exit 1
|
||||||
'''
|
'''
|
||||||
|
@ -18,6 +17,14 @@ exit 1
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
|
# this file is syntactically valid Python 2; bail out if the interpreter is Python 2
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
print('error: this version of Dotbot is not compatible with Python 2:\nhttps://github.com/anishathalye/dotbot/wiki/Troubleshooting#python-2')
|
||||||
|
exit(1)
|
||||||
|
if sys.version_info < (3, 6):
|
||||||
|
print('error: this version of Dotbot requires Python 3.6+')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
PROJECT_ROOT_DIRECTORY = os.path.dirname(
|
PROJECT_ROOT_DIRECTORY = os.path.dirname(
|
||||||
os.path.dirname(os.path.realpath(__file__)))
|
os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
|
||||||
|
@ -25,11 +32,7 @@ def inject(lib_path):
|
||||||
path = os.path.join(PROJECT_ROOT_DIRECTORY, 'lib', lib_path)
|
path = os.path.join(PROJECT_ROOT_DIRECTORY, 'lib', lib_path)
|
||||||
sys.path.insert(0, path)
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
# version dependent libraries
|
|
||||||
if sys.version_info[0] >= 3:
|
|
||||||
inject('pyyaml/lib3')
|
inject('pyyaml/lib3')
|
||||||
else:
|
|
||||||
inject('pyyaml/lib')
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dotbot')):
|
if os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dotbot')):
|
||||||
if PROJECT_ROOT_DIRECTORY not in sys.path:
|
if PROJECT_ROOT_DIRECTORY not in sys.path:
|
||||||
|
|
|
@ -6,7 +6,7 @@ import yaml
|
||||||
from .util import string
|
from .util import string
|
||||||
|
|
||||||
|
|
||||||
class ConfigReader(object):
|
class ConfigReader:
|
||||||
def __init__(self, config_file_path):
|
def __init__(self, config_file_path):
|
||||||
self._config = self._read(config_file_path)
|
self._config = self._read(config_file_path)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import os
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
|
|
||||||
|
|
||||||
class Context(object):
|
class Context:
|
||||||
"""
|
"""
|
||||||
Contextual data and information for plugins.
|
Contextual data and information for plugins.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,7 +6,7 @@ from .messenger import Messenger
|
||||||
from .plugin import Plugin
|
from .plugin import Plugin
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_directory,
|
base_directory,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Color(object):
|
class Color:
|
||||||
NONE = ""
|
NONE = ""
|
||||||
RESET = "\033[0m"
|
RESET = "\033[0m"
|
||||||
RED = "\033[91m"
|
RED = "\033[91m"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Level(object):
|
class Level:
|
||||||
NOTSET = 0
|
NOTSET = 0
|
||||||
DEBUG = 10
|
DEBUG = 10
|
||||||
LOWINFO = 15
|
LOWINFO = 15
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from ..util.compat import with_metaclass
|
|
||||||
from ..util.singleton import Singleton
|
from ..util.singleton import Singleton
|
||||||
from .color import Color
|
from .color import Color
|
||||||
from .level import Level
|
from .level import Level
|
||||||
|
|
||||||
|
|
||||||
class Messenger(with_metaclass(Singleton, object)):
|
class Messenger(metaclass=Singleton):
|
||||||
def __init__(self, level=Level.LOWINFO):
|
def __init__(self, level=Level.LOWINFO):
|
||||||
self.set_level(level)
|
self.set_level(level)
|
||||||
self.use_color(True)
|
self.use_color(True)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from .context import Context
|
||||||
from .messenger import Messenger
|
from .messenger import Messenger
|
||||||
|
|
||||||
|
|
||||||
class Plugin(object):
|
class Plugin:
|
||||||
"""
|
"""
|
||||||
Abstract base class for commands that process directives.
|
Abstract base class for commands that process directives.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -137,13 +137,7 @@ class Link(Plugin):
|
||||||
"""
|
"""
|
||||||
Wrap `glob.glob` in a python agnostic way, catching errors in usage.
|
Wrap `glob.glob` in a python agnostic way, catching errors in usage.
|
||||||
"""
|
"""
|
||||||
if sys.version_info < (3, 5) and "**" in path:
|
found = glob.glob(path, recursive=True)
|
||||||
self._log.error(
|
|
||||||
'Link cannot handle recursive glob ("**") for Python < version 3.5: "%s"' % path
|
|
||||||
)
|
|
||||||
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
|
# normalize paths to ensure cross-platform compatibility
|
||||||
found = [os.path.normpath(p) for p in found]
|
found = [os.path.normpath(p) for p in found]
|
||||||
# if using recursive glob (`**`), filter results to return only files:
|
# if using recursive glob (`**`), filter results to return only files:
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
def with_metaclass(meta, *bases):
|
|
||||||
class metaclass(meta):
|
|
||||||
def __new__(cls, name, this_bases, d):
|
|
||||||
return meta(name, bases, d)
|
|
||||||
|
|
||||||
return type.__new__(metaclass, "temporary_class", (), {})
|
|
|
@ -23,23 +23,11 @@ def load(path):
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 5):
|
|
||||||
import importlib.util
|
import importlib.util
|
||||||
|
|
||||||
|
|
||||||
def load_module(module_name, path):
|
def load_module(module_name, path):
|
||||||
spec = importlib.util.spec_from_file_location(module_name, path)
|
spec = importlib.util.spec_from_file_location(module_name, path)
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
elif sys.version_info >= (3, 3):
|
|
||||||
from importlib.machinery import SourceFileLoader
|
|
||||||
|
|
||||||
def load_module(module_name, path):
|
|
||||||
return SourceFileLoader(module_name, path).load_module()
|
|
||||||
|
|
||||||
else:
|
|
||||||
import imp
|
|
||||||
|
|
||||||
def load_module(module_name, path):
|
|
||||||
return imp.load_source(module_name, path)
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[bdist_wheel]
|
|
||||||
universal=1
|
|
4
setup.py
4
setup.py
|
@ -1,5 +1,4 @@
|
||||||
import re
|
import re
|
||||||
from codecs import open # For a consistent encoding
|
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
@ -38,10 +37,7 @@ setup(
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Programming Language :: Python :: 2",
|
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import builtins
|
||||||
import ctypes
|
import ctypes
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import unittest.mock as mock
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -11,15 +13,6 @@ import yaml
|
||||||
|
|
||||||
import dotbot.cli
|
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):
|
def get_long_path(path):
|
||||||
"""Get the long path for a given path."""
|
"""Get the long path for a given path."""
|
||||||
|
@ -35,8 +28,7 @@ def get_long_path(path):
|
||||||
return buffer.value
|
return buffer.value
|
||||||
|
|
||||||
|
|
||||||
# Python 2.7 compatibility:
|
# On Linux, tempfile.TemporaryFile() requires unlink access.
|
||||||
# On Linux, Python 2.7's tempfile.TemporaryFile() requires unlink access.
|
|
||||||
# This list is updated by a tempfile._mkstemp_inner() wrapper,
|
# This list is updated by a tempfile._mkstemp_inner() wrapper,
|
||||||
# and its contents are checked by wrapped functions.
|
# and its contents are checked by wrapped functions.
|
||||||
allowed_tempfile_internal_unlink_calls = []
|
allowed_tempfile_internal_unlink_calls = []
|
||||||
|
@ -49,7 +41,6 @@ def wrap_function(function, function_path, arg_index, kwarg_key, root):
|
||||||
else:
|
else:
|
||||||
value = args[arg_index]
|
value = args[arg_index]
|
||||||
|
|
||||||
# Python 2.7 compatibility:
|
|
||||||
# Allow tempfile.TemporaryFile's internal unlink calls to work.
|
# Allow tempfile.TemporaryFile's internal unlink calls to work.
|
||||||
if value in allowed_tempfile_internal_unlink_calls:
|
if value in allowed_tempfile_internal_unlink_calls:
|
||||||
return function(*args, **kwargs)
|
return function(*args, **kwargs)
|
||||||
|
@ -68,11 +59,7 @@ def wrap_function(function, function_path, arg_index, kwarg_key, root):
|
||||||
|
|
||||||
|
|
||||||
def wrap_open(root):
|
def wrap_open(root):
|
||||||
try:
|
|
||||||
wrapped = getattr(builtins, "open")
|
wrapped = getattr(builtins, "open")
|
||||||
except AttributeError:
|
|
||||||
# Python 2.7 compatibility
|
|
||||||
wrapped = getattr(__builtin__, "open")
|
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
if "file" in kwargs:
|
if "file" in kwargs:
|
||||||
|
@ -207,11 +194,7 @@ def root(standardize_tmp):
|
||||||
patches.append(mock.patch(function_path, wrapped))
|
patches.append(mock.patch(function_path, wrapped))
|
||||||
|
|
||||||
# open() must be separately wrapped.
|
# open() must be separately wrapped.
|
||||||
if builtins is not None:
|
|
||||||
function_path = "builtins.open"
|
function_path = "builtins.open"
|
||||||
else:
|
|
||||||
# Python 2.7 compatibility
|
|
||||||
function_path = "__builtin__.open"
|
|
||||||
wrapped = wrap_open(current_root)
|
wrapped = wrap_open(current_root)
|
||||||
patches.append(mock.patch(function_path, wrapped))
|
patches.append(mock.patch(function_path, wrapped))
|
||||||
|
|
||||||
|
@ -256,7 +239,7 @@ def home(monkeypatch, root):
|
||||||
yield home
|
yield home
|
||||||
|
|
||||||
|
|
||||||
class Dotfiles(object):
|
class Dotfiles:
|
||||||
"""Create and manage a dotfiles directory for a test."""
|
"""Create and manage a dotfiles directory for a test."""
|
||||||
|
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
|
|
|
@ -1,25 +1,15 @@
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import pytest
|
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(
|
@pytest.mark.skipif(
|
||||||
"sys.platform[:5] == 'win32'",
|
"sys.platform[:5] == 'win32'",
|
||||||
reason="The hybrid sh/Python dotbot script doesn't run on Windows platforms",
|
reason="The hybrid sh/Python dotbot script doesn't run on Windows platforms",
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("python_name", (None, "python", "python2", "python3"))
|
@pytest.mark.parametrize("python_name", (None, "python", "python3"))
|
||||||
def test_find_python_executable(python_name, home, dotfiles):
|
def test_find_python_executable(python_name, home, dotfiles):
|
||||||
"""Verify that the sh/Python hybrid dotbot executable can find Python."""
|
"""Verify that the sh/Python hybrid dotbot executable can find Python."""
|
||||||
|
|
||||||
|
@ -31,7 +21,7 @@ def test_find_python_executable(python_name, home, dotfiles):
|
||||||
# Create a link to sh.
|
# Create a link to sh.
|
||||||
tmp_bin = os.path.join(home, "tmp_bin")
|
tmp_bin = os.path.join(home, "tmp_bin")
|
||||||
os.makedirs(tmp_bin)
|
os.makedirs(tmp_bin)
|
||||||
sh_path = which("sh")
|
sh_path = shutil.which("sh")
|
||||||
os.symlink(sh_path, os.path.join(tmp_bin, "sh"))
|
os.symlink(sh_path, os.path.join(tmp_bin, "sh"))
|
||||||
|
|
||||||
if python_name:
|
if python_name:
|
||||||
|
|
|
@ -537,10 +537,6 @@ def test_link_glob_patterns(pattern, expect_file, home, dotfiles, run_dotbot):
|
||||||
assert not 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):
|
def test_link_glob_recursive(home, dotfiles, run_dotbot):
|
||||||
"""Verify recursive link globbing and exclusions."""
|
"""Verify recursive link globbing and exclusions."""
|
||||||
|
|
||||||
|
@ -773,19 +769,11 @@ def test_link_no_canonicalize(key, home, dotfiles, run_dotbot):
|
||||||
|
|
||||||
dotfiles.write("f", "apple")
|
dotfiles.write("f", "apple")
|
||||||
dotfiles.write_config([{"defaults": {"link": {key: False}}}, {"link": {"~/.f": {"path": "f"}}}])
|
dotfiles.write_config([{"defaults": {"link": {key: False}}}, {"link": {"~/.f": {"path": "f"}}}])
|
||||||
try:
|
|
||||||
os.symlink(
|
os.symlink(
|
||||||
dotfiles.directory,
|
dotfiles.directory,
|
||||||
os.path.join(home, "dotfiles-symlink"),
|
os.path.join(home, "dotfiles-symlink"),
|
||||||
target_is_directory=True,
|
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(
|
run_dotbot(
|
||||||
"-c",
|
"-c",
|
||||||
os.path.join(home, "dotfiles-symlink", os.path.basename(dotfiles.config_filename)),
|
os.path.join(home, "dotfiles-symlink", os.path.basename(dotfiles.config_filename)),
|
||||||
|
|
|
@ -6,26 +6,11 @@ import sys
|
||||||
import pytest
|
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):
|
def test_shim(root, home, dotfiles, run_dotbot):
|
||||||
"""Verify install shim works."""
|
"""Verify install shim works."""
|
||||||
|
|
||||||
# Skip the test if git is unavailable.
|
# Skip the test if git is unavailable.
|
||||||
git = which("git")
|
git = shutil.which("git")
|
||||||
if git is None:
|
if git is None:
|
||||||
pytest.skip("git is unavailable")
|
pytest.skip("git is unavailable")
|
||||||
|
|
||||||
|
@ -52,7 +37,7 @@ def test_shim(root, home, dotfiles, run_dotbot):
|
||||||
# Run the shim script.
|
# Run the shim script.
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
if sys.platform[:5] == "win32":
|
if sys.platform[:5] == "win32":
|
||||||
args = [which("powershell"), "-ExecutionPolicy", "RemoteSigned", shim]
|
args = [shutil.which("powershell"), "-ExecutionPolicy", "RemoteSigned", shim]
|
||||||
env["USERPROFILE"] = home
|
env["USERPROFILE"] = home
|
||||||
else:
|
else:
|
||||||
args = [shim]
|
args = [shim]
|
||||||
|
|
8
tox.ini
8
tox.ini
|
@ -4,8 +4,8 @@
|
||||||
envlist =
|
envlist =
|
||||||
coverage_erase
|
coverage_erase
|
||||||
py{38, 39, 310}-all_platforms
|
py{38, 39, 310}-all_platforms
|
||||||
py{27, 35, 36, 37}-most_platforms
|
py{36, 37}-most_platforms
|
||||||
pypy{2, 3}-most_platforms
|
pypy3-most_platforms
|
||||||
coverage_report
|
coverage_report
|
||||||
skip_missing_interpreters = true
|
skip_missing_interpreters = true
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ deps =
|
||||||
pytest
|
pytest
|
||||||
pytest-randomly
|
pytest-randomly
|
||||||
pyyaml
|
pyyaml
|
||||||
mock; python_version == "2.7"
|
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
coverage run -m pytest tests/
|
coverage run -m pytest tests/
|
||||||
|
@ -66,10 +65,7 @@ python =
|
||||||
3.10: py310-all_platforms
|
3.10: py310-all_platforms
|
||||||
|
|
||||||
; Run on most platforms (Linux and Mac)
|
; Run on most platforms (Linux and Mac)
|
||||||
pypy-2.7: pypy2-most_platforms
|
|
||||||
pypy-3.9: pypy3-most_platforms
|
pypy-3.9: pypy3-most_platforms
|
||||||
2.7: py27-most_platforms
|
|
||||||
3.5: py35-most_platforms
|
|
||||||
3.6: py36-most_platforms
|
3.6: py36-most_platforms
|
||||||
3.7: py37-most_platforms
|
3.7: py37-most_platforms
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue