-
-
Notifications
You must be signed in to change notification settings - Fork 402
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2469 from SnoopJ/contrib/tox
Add optional tox configuration
- Loading branch information
Showing
4 changed files
with
191 additions
and
11 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
This folder contains contributed files for use with Sopel. | ||
|
||
## Service configuration | ||
|
||
`sopel.service` and `sopel.cfg` are designed to be distributed by third parties | ||
such as Fedora Project or Arch Linux. | ||
|
||
`sopel.cfg` is a default configuration file for Sopel, that assumes the OS is | ||
new enough to have `/run` and `/usr/lib/tmpfiles.d` | ||
|
||
`sopel.service` is a systemd service file, and `[email protected]` is a | ||
multi-instance template. Both assume you are using a rather recent Sopel; that | ||
the system has a special user named `sopel` designated for running the bot; and | ||
that this user has access to `/run/sopel` (should be setup by `sopel.conf` in | ||
`/usr/lib/tmpfiles.d`), `/var/log/sopel` and `/var/lib/sopel` | ||
|
||
Default installation paths: | ||
|
||
``` | ||
sopel.cfg /etc | ||
sopel.conf /usr/lib/tmpfiles.d | ||
sopel.service /usr/lib/systemd/system | ||
[email protected] /usr/lib/systemd/system | ||
``` | ||
|
||
## tox | ||
|
||
`tox.ini` and `toxfile.py` provide support for running Sopel's QA automation | ||
against multiple Python versions locally. To run QA for all configured versions | ||
in parallel, run `tox --conf contrib/ -p` from the repository root. You may | ||
also want to set `TOX_CONFIG_FILE=path/to/contrib` to shorten that invocation | ||
to just `tox -p`. | ||
|
||
## githooks | ||
|
||
Git hooks for development use |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
[tox] | ||
package_root = . | ||
min_version = 4.3.3 | ||
envlist = | ||
py{37,38,39,310,311}-qa | ||
skip_missing_interpreters = true | ||
ignore_base_python_conflict = true | ||
labels = | ||
mypy = py{37,38,39,310,311}-mypy | ||
quality = py{37,38,39,310,311}-quality | ||
test = py{37,38,39,310,311}-test | ||
|
||
|
||
[testenv] | ||
runner = ignore_env_name_mismatch | ||
package = sdist | ||
allowlist_externals = | ||
make | ||
envname = | ||
py37: py37 | ||
py38: py38 | ||
py39: py39 | ||
py310: py310 | ||
py311: py311 | ||
envdir = | ||
py37: {toxinidir}/.tox/py37 | ||
py38: {toxinidir}/.tox/py38 | ||
py39: {toxinidir}/.tox/py39 | ||
py310: {toxinidir}/.tox/py310 | ||
py311: {toxinidir}/.tox/py311 | ||
depends = | ||
base | ||
deps = | ||
-r ../dev-requirements.txt | ||
passenv = | ||
PYTEST_ADDOPTS | ||
setenv = | ||
COVERAGE_FILE = {envdir}/.coverage | ||
|
||
commands = | ||
qa: make -C.. qa | ||
mypy: make -C.. mypy | ||
quality: make -C.. quality | ||
test: make -C.. test | ||
# NOTE:there's currently no way to specify separate output directories for | ||
# the HTML coverage report, but the CLI report is probably fine anyway | ||
test: make -C.. coverage_report |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
from pathlib import Path | ||
|
||
from tox.execute.request import StdinSource | ||
from tox.plugin import impl | ||
|
||
|
||
CWD = Path().resolve() | ||
HERE = Path(__file__).parent.resolve() | ||
|
||
if CWD != HERE.parent: | ||
raise RuntimeError("Tox must be run from the sopel repository root") | ||
|
||
|
||
@impl | ||
def tox_on_install(tox_env, arguments, section, of_type): | ||
EXCLUDED_DEPS = ("sopel-help",) | ||
|
||
# NOTE: this hook is a workaround for the cyclic dependency between sopel and sopel-help | ||
if of_type == "deps": | ||
cmd = ["python", "-m", "pip", "install", "--no-deps", "sopel-help"] | ||
tox_env.execute(cmd, stdin=StdinSource.OFF) | ||
elif of_type == "package": | ||
sopel_sdist = arguments[0] | ||
deps = sopel_sdist.deps | ||
deps[:] = [d for d in deps if d.name not in EXCLUDED_DEPS] | ||
|
||
|
||
# NOTE:The following is included verbatim from upstream (v0.2.0.post2 @ ebed159) | ||
# to allow re-use of tox environments for each Python version. We *can* depend | ||
# on the plugin from tox.ini but it makes sense to use the recommended vendoring | ||
# approach since we have a toxfile.py anyway. | ||
""" | ||
https://github.com/masenf/tox-ignore-env-name-mismatch | ||
MIT License | ||
Copyright (c) 2023 Masen Furer | ||
""" | ||
from contextlib import contextmanager | ||
from typing import Any, Iterator, Optional, Sequence, Tuple | ||
|
||
from tox.plugin import impl | ||
from tox.tox_env.api import ToxEnv | ||
from tox.tox_env.info import Info | ||
from tox.tox_env.python.virtual_env.runner import VirtualEnvRunner | ||
from tox.tox_env.register import ToxEnvRegister | ||
|
||
|
||
class FilteredInfo(Info): | ||
"""Subclass of Info that optionally filters specific keys during compare().""" | ||
|
||
def __init__( | ||
self, | ||
*args: Any, | ||
filter_keys: Optional[Sequence[str]] = None, | ||
filter_section: Optional[str] = None, | ||
**kwargs: Any, | ||
): | ||
""" | ||
:param filter_keys: key names to pop from value | ||
:param filter_section: if specified, only pop filter_keys when the compared section matches | ||
All other args and kwargs are passed to super().__init__ | ||
""" | ||
self.filter_keys = filter_keys | ||
self.filter_section = filter_section | ||
super().__init__(*args, **kwargs) | ||
|
||
@contextmanager | ||
def compare( | ||
self, | ||
value: Any, | ||
section: str, | ||
sub_section: Optional[str] = None, | ||
) -> Iterator[Tuple[bool, Optional[Any]]]: | ||
"""Perform comparison and update cached info after filtering `value`.""" | ||
if self.filter_section is None or section == self.filter_section: | ||
try: | ||
value = value.copy() | ||
except AttributeError: # pragma: no cover | ||
pass | ||
else: | ||
for fkey in self.filter_keys or []: | ||
value.pop(fkey, None) | ||
with super().compare(value, section, sub_section) as rv: | ||
yield rv | ||
|
||
|
||
class IgnoreEnvNameMismatchVirtualEnvRunner(VirtualEnvRunner): | ||
"""EnvRunner that does NOT save the env name as part of the cached info.""" | ||
|
||
@staticmethod | ||
def id() -> str: | ||
return "ignore_env_name_mismatch" | ||
|
||
@property | ||
def cache(self) -> Info: | ||
"""Return a modified Info class that does NOT pass "name" key to `Info.compare`.""" | ||
return FilteredInfo( | ||
self.env_dir, | ||
filter_keys=["name"], | ||
filter_section=ToxEnv.__name__, | ||
) | ||
|
||
|
||
@impl | ||
def tox_register_tox_env(register: ToxEnvRegister) -> None: | ||
"""tox4 entry point: add IgnoreEnvNameMismatchVirtualEnvRunner to registry.""" | ||
register.add_run_env(IgnoreEnvNameMismatchVirtualEnvRunner) |