1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
|
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.
#
# --------------------( SYNOPSIS )--------------------
# Project-wide tox configuration, applied to all invocations of the tox test
# harness within this project.
#
# tox is a high-level Python-specific testing utility wrapping comparatively
# lower-level Python-specific testing frameworks (e.g., py.test, unittest2).
# Whereas the latter only exercise this project's codebase from the current
# working directory (CWD) without installing this project and hence exercising
# this project's installation, tox exercises both.
#
# Specifically, tox iteratively:
# 1. Creates a source-based tarball distribution of this project (e.g., via
# "python setup.py sdist").
# 2. Installs this tarball *AND* a system-agnostic Python interpreter into one
# isolated virtual environment for each testing configuration.
# 3. Tests this installation with the specified testing framework.
#
# --------------------( CAVEATS )--------------------
# *THIS CONFIGURATION DOES NOT SUPPORT PARALLELIZATION.* Although "tox" can be
# configured to run "tox" environments in parallel, doing so is fraught with
# hardship, produces unreadable output in the best of circumstances, and invites
# non-trivial race conditions between competing "tox" environments. It's best
# *NOT* to go there at all. Anyone requiring parallelization should simply be
# running our GitHub Actions-based "tests" workflow instead, which implicitly
# runs tests in a sane, safe, paralellized manner.
#
# *THIS CONFIGURATION IS INTOLERANT OF UNICODE CHARACTERS.* Note that setting
# "PYIOENCODING = UTF-8" under the "setenv" section below has no meaningful
# effect. For unknown reasons, "tox" is incapable of processing UTF-8 here.
# This is why nobody gets good things. If this file contains one or more
# UTF-8-encoded characters, "tox" fails with a non-human-readable traceback:
# Traceback (most recent call last):
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/runpy.py", line 194, in _run_module_as_main
# return _run_code(code, main_globals, None,
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/runpy.py", line 87, in _run_code
# exec(code, run_globals)
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/__main__.py", line 4, in <module>
# tox.cmdline()
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/session/__init__.py", line 44, in cmdline
# main(args)
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/session/__init__.py", line 65, in main
# config = load_config(args)
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/session/__init__.py", line 81, in load_config
# config = parseconfig(args)
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/config/__init__.py", line 282, in parseconfig
# ParseIni(config, config_file, content)
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/config/__init__.py", line 1145, in __init__
# self._cfg = py.iniconfig.IniConfig(config.toxinipath, ini_data)
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/py/_vendored_packages/iniconfig/__init__.py", line 54, in __init__
# tokens = self._parse(iter(f))
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/py/_vendored_packages/iniconfig/__init__.py", line 82, in _parse
# for lineno, line in enumerate(line_iter):
# File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/codecs.py", line 322, in decode
# (result, consumed) = self._buffer_decode(data, self.errors, final)
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 3335: invalid continuation byte
# Error: Process completed with exit code 1.
#
# --------------------( VARIABLES )--------------------
# tox dynamically substitutes "{"- and "}"-delimited variable names with the
# strings to which those variables expand. Supported variable names include:
# * "{envtmpdir}", the absolute dirname of a temporary directory specific to
# the current virtual environment to which this project has been installed.
# * "{posargs}", the whitespace-delimited list of all command-line arguments
# passed to the current invocation of the "tox" command.
# * "{toxinidir}", the absolute dirname of the directory containing this file
# (e.g., the project root).
# ....................{ TODO }....................
#FIXME: This configuration has become intolerably slow. It's not tox's fault.
#It's pip's fault. Or, rather, it's our fault for continuing to use pip below.
#Using pip was "fine" (for certain definitions of "fine") when the "[test-tox]"
#extra in our "pyproject.toml" file listed only a handful of optional
#dependencies. That is *ABSOLUTELY* no longer the case. *ANY* modification
#whatesover to those extras now incurs literally tens of minutes of time merely
#spent on pip installing dependencies. So... what do we do?
#
#Obviously, we switch to uv. Our ".github/workflows" have *ALL* already switched
#to uv. So, there are no longer any reasonable blockers to getting this done.
#Do this as soon as feasible, please. *sigh*
# ....................{ TOX }....................
# Metadata specific to tox itself.
[tox]
# Comma- and newline-delimited string listing the names and optional versions
# of all mandatory core dependencies required to merely create a new venv.
#
# Note that project dependencies should *NOT* be listed here.
#requires =
# pip >= 20.0.0
# setuptools < 50.0.0
# ....................{ TOX ~ py }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: Changes to this setting *MUST* be manually synchronized with:
# * The "tox-env" setting in ".github/workflows/python_test.yml".
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Whitelist-delimited list of all "tox" environments to be tested, where "tox":
# * Expands Bash-style glob expressions in all environments.
# * Expands a prefixing "py" to "python".
# * Expands a suffixing "t" to the free-threading GIL-free variant of that
# interpreter.
# * Delimits the subsequent two digits with a dot to associate each resulting
# test configuration with the basename of an external command running an
# externally installed Python interpreter.
# * Expands "-"-delimited lists via the Cartesian set product A x B,
# effectively "multiplying" each environment on the left of each "-" against
# each environment on the right of that "-". Moreover, each such environment
# remains preserved and thus distinctly testable as that environment.
#
# Notable "tox" environments defined below include:
# * The "init" environment, run before *ALL* other "tox" environments and thus
# preparing the local filesystem for those subsequently run environments.
# * The "py"-prefixed environment group, which "tox" expands via the rules above
# into the test matrix of *ALL* Python interpreters to be tested.
# * The "finally" environment, run *AFTER* all other "tox" environments and thus
# aggregating results produced by those previously run environments onto the
# local filesystem.
#
# Order is typically important between "tox" environments. Thankfully, this
# order may be specified via the "depends =" setting in each "tox" environment.
#
# For example, setting "envlist = py27,py38" produces a test matrix exercising
# the externally installed "python2.7" and "python3.8" commands. See also:
# https://tox.readthedocs.io/en/latest/config.html#generating-environments-conditional-settings
envlist =
init
py{310,311,312,313,313t,314,314t,315,315t}-coverage
#FIXME: Include this back in the above "envlist" if and when we resurrect this.
# finally
# Ignore Python environments unavailable on the current system. By default,
# "tox" fails on the first unavailable Python environment. While sensible for
# continuous integration (CI), this default fails to generalize for local
# developers lacking one or more Python environments.
#
# Note that our CI configuration explicitly falsifies this setting back to its
# CI-friendly default via the "--skip-missing-interpreters=false" CLI option,
# forcing CI failures for unavailable Python environments. See also:
# https://github.com/tox-dev/tox/issues/903
skip_missing_interpreters = true
#FIXME: Disabled until required. Since "tox" currently behaves as expected,
#there's no incentive to break what's worky.
# # Comma- and newline-delimited string listing the names of all PyPI-hosted
# # projects required as mandatory dependencies to bootstrap "tox" -- typically
# # including some combination of "tox" itself, "virtualenv", and/or "pip".
# #
# # Note that all application-specific dependencies (e.g., "numpy", "scipy")
# # should be listed in the "deps" and/or "extras" settings under each
# # "[testenv]" section below.
# requires =
# ....................{ INIT }....................
# Job run *BEFORE* all other "tox" environments, preparing the local filesystem
# for subsequently run environments.
[testenv:init]
# Human-readable string synopsizing this environment.
description = Prepare "{toxinidir}" for testing.
#FIXME: We *MIGHT* also need to explicitly order environments like so:
# depends = py{310,311,312,313,313t,314,314t,315,315t}-coverage
#Let's avoid doing this unless we absolutely must. DRY for life, yo! \o/
# Avoid installing this package under this environment.
skip_install = true
#FIXME: Not right. This *SHOULD* be using PEP 735-compliant dependency groups
#managed by "pyproject.toml", but... eh? Who's got the time, huh? *sigh*
# Whitespace-delimited string listing the names of all mandatory dependencies
# (i.e., third-party packages) required to run the commands listed below.
#
# Note that "tox" effectively requires this setting to be explicitly defined. If
# this setting is *NOT* explicitly defined, "tox" defaults this setting to the
# default "deps" setting defined by the default "[testenv]" configuration, which
# then erroneously installs this *ENTIRE* package and *ALL* dependencies simply
# to trivially run the "coverage erase" command below. Truly, a facepalm.
deps = coverage
# Newline-delimited string listing all shell commands required to test this
# project under this environment. See comments below for detailed discussion.
commands =
# Unconditionally remove all prior coverage-specific temporary files (e.g.,
# ".coverage.*") created by the prior "tox" run, regardless of whether we're
# subsequently collecting coverage metrics. It's easiest to do this
# unconditionally rather than depending on "coverage:", which only invites
# fragile failure-prone edge case logic.
{envpython} -m coverage erase
# coverage: {envpython} -m coverage erase --data-file="{toxinidir}/.coverage"
# ....................{ TEST }....................
# Default job run for each "tox" environment lacking an environment-specific job
# named "[testenv:{environment_name}]", testing this project under that
# environment.
[testenv]
# Human-readable string synopsizing this environment.
description = Test "{toxinidir}" with "{basepython} -m pytest".
# ....................{ TEST ~ shell }....................
#FIXME: Sadly conflicts with Coverage.py. This used to work. We strongly suspect
#that our ".coveragerc" configuration is to blame. Oh, well. What you gonna do?
# Absolute dirname of the directory to change to for this environment, required
# to avoid accidental import collisions with uninstalled packages of the same
# name residing in "{toxinidir}". See also the following pertinent blog post,
# "Testing your python package as installed":
# https://blog.ganssle.io/articles/2019/08/test-as-installed.html
#changedir = {envtmpdir}
# Newline-delimited string listing all environment variables to be temporarily
# set in each shell subprocess running tests.
setenv =
# Permit the "pip" installation commands internally invoked under each
# "tox" venv to optionally install wheels from an external third-party PyPI
# repository explicitly supporting PyPy. If this is *NOT* done,
# PyPy-specific "tox" venvs typically fail to install one or more Python
# packages in the standard scientific stack.
#
# Note that this repository resides at:
# https://github.com/antocuni/pypy-wheels
PIP_EXTRA_INDEX_URL = https://antocuni.github.io/pypy-wheels/manylinux2010
#FIXME: This doesn't actually work. Gods... why is "pyright" so sucky!?!?
#"pyright" doesn't respect a "latest" value for this environment variable
#despite explicitly advising the user to do just that. I laugh and then cry.
# Prevent the third-party static type-checker "pyright" from emitting
# ignorable warnings that the current "pyright" version is not the most
# recent. No one cares about you or your versioning, "pyright". Go away.
# Specifically, this is what "pyright" now wastes everyone's time with:
# WARNING: there is a new pyright version available (v1.1.391 ->
# v1.1.392.post0). Please install the new version or set
# PYRIGHT_PYTHON_FORCE_VERSION to `latest`
#PYRIGHT_PYTHON_FORCE_VERSION = latest
# Enable the Python Development Mode (PDM), which:
# "Introduces additional runtime checks that are too expensive to be
# enabled by default. It should not be more verbose than the default if
# the code is correct; new warnings are only emitted when an issue is
# detected."
# Specifically, the PDM enables:
# * "-W default", emitting warnings ignored by default. Yes, Python
# insanely ignores various categories of warnings by default -- including
# deprecating warnings, which *ABSOLUTELY* should be emitted by default,
# but aren't. We can't resolve that for end users but we can resolve that
# for ourselves.
# * "PYTHONMALLOC=debug", registering memory allocators hooks detecting
# unsafe call stack, memory, and GIL violations.
# * "PYTHONFAULTHANDLER=1", registering fault handlers emitting Python
# tracebacks on segmentation faults.
# * "PYTHONASYNCIODEBUG=1", enabling asyncio debug mode logging unawaited
# coroutines.
# * Detections for unsafe string encoding and decoding operations.
# * Logging io.IOBase.close() exceptions on object finalization.
# * Enabling the "dev_mode" attribute of "sys.flags".
# See also:
# https://docs.python.org/3/library/devmode.html
#PYTHONDEVMODE = 1
# Prevent Python from buffering and hence failing to log output in the
# unlikely (but feasible) event of catastrophic failure from either the
# active Python process or OS kernel.
PYTHONUNBUFFERED = 1
#FIXME: Seemingly unneeded at the moment, but you never know. *shrug*
# # Absolute filename of the Coverage.py-specific output file containing
# # coverage metrics collected for the current "tox" environment.
# COVERAGE_FILE = {toxinidir}/.coverage
#
# # Absolute filename of the Coverage.py-specific configuration file.
# # Coverage.py *SHOULD* detect this implicitly, but appears to have
# # issues when run outside the "{toxinidir}". *shrug*
# COVERAGE_RCFILE = {toxinidir}/.coveragerc
# If the caller explicitly concatenated the current environment name by
# "-coverage" (e.g., "py310-coverage")...
coverage:
# Command fragment measuring coverage while running tests, defined
# *ONLY* when the caller explicitly concatenated the current environment
# name by "-coverage" (e.g., "py310-coverage"). Dismantled, this is:
#
# Note that we intentionally do *NOT* leverage the "pytest-cov" plugin,
# which lacks sufficient configurability and friendly maintainership to
# warrant yet another fragile dependency.
_COVERAGE_COMMAND = coverage run -m
# _COVERAGE_COMMAND = coverage run --data-file="{toxinidir}/.coverage" -m
# Newline-delimited string listing all environment variables to be passed from
# the current shell process to each shell subprocess running tests.
# Dismantled, this is:
# * "CI" and "GITHUB_ACTIONS", enabling our test suite to programatically
# detect execution by a remote continuous integration (CI) host.
passenv =
CI
GITHUB_ACTIONS
PIP_CACHE_DIR
# ....................{ TEST ~ dependencies }....................
# Whitespace-delimited string listing the names of all "pyproject.toml"-based
# "extras" required as mandatory or optional dependencies.
extras =
# Install all mandatory test-specific dependencies. This is the official
# solution supported by "tox" developers for eliminating redundancy between
# testing dependencies listed within this file and the top-level
# "pyproject.toml" configuration. While non-intuitive, we have little
# recourse. See also:
# https://stackoverflow.com/questions/29870629/pip-install-test-dependencies-for-tox-from-setup-py
# https://stackoverflow.com/questions/39922650/tox-tests-use-setup-py-extra-require-as-tox-deps-source
# https://github.com/tox-dev/tox/issues/13#issuecomment-247788280
#
# Note that this also requires ".[test-tox]" to be listed as a dependency.
test-tox
# If the caller explicitly concatenated the current environment name by
# "-coverage" (e.g., "py310-coverage"), install all optional
# coverage-specific dependencies as well.
coverage: test-tox-coverage
# Whitespace-delimited string listing the names of all mandatory dependencies
# (i.e., third-party packages) required to test this environment.
#
# Note that this also requires "test-tox" to be listed as an extra above.
deps = .[test-tox]
# ....................{ TEST ~ commands }....................
# Shell command with which to install project dependencies.
#
# This command extends the default "install_command" with support for an
# optional "${_TOX_PIP_INSTALL_OPTIONS}" environment variable defaulting to the
# empty string. This variable is typically defined by the higher-level
# ".github/workflows/python_test.yml" continuous integration (CI) configuration
# file on a platform-specific basis (e.g., "--force-reinstall" under macOS).
install_command =
python -m pip install {env:_TOX_PIP_INSTALL_OPTIONS:} {opts} {packages}
# Newline-delimited string listing all shell commands required to test this
# project under this environment. Note that:
# * For disambiguity, avoid running any Python-based commands *EXCEPT* those
# explicitly prefixed by "{envpython}" (i.e., the absolute filename of the
# venv-specific Python interpreter).
# * For portability between POSIX-compliant platforms (e.g., Linux, macOS) and
# POSIX-noncompliant platforms (e.g., Windows), the current platform and
# shell should *NOT* assumed. Ergo, commands should be confined to those
# explicitly prefixed by "{envpython}".
# * By default, the failure (i.e., non-zero exit status) of a single command
# terminates this environment with failure. To prevent that, conditionally
# squelch the failure of specific commands by prefixing those commands with
# the "-" character (i.e., dash, minus); this nomenclature is inherited from
# the "make" ecosystem, whose recipes utilize similar syntax.
commands =
# Print metadata on the current versions of Python and pytest (in order).
{envpython} --version
{envpython} -m pytest --version
# Run our entire pytest-based test suite. Dismantled, this is:
# * "{env:_COVERAGE_COMMAND:}", expanding to either:
# * If measuring coverage, the value of the "${_COVERAGE_COMMAND}"
# environment variable defined above.
# * Else, the empty string.
# * "--maxfail={n}", halting testing on the {n}th failure.
# * "-p no:*", disabling various pytest plugins known to be harmful. See our
# "pytest.ini" file for further commentary on the hideous state of pytest
# plugins and why they are True Evil Personified (TEP).
{envpython} \
-m {env:_COVERAGE_COMMAND:} \
pytest --maxfail=1 -p no:asyncio -p no:xvfb {posargs} "{toxinidir}"
# {envpython} -m {env:_COVERAGE_COMMAND:} pytest --maxfail=1 -vvvv {posargs} "{toxinidir}"
# {envpython} -m {env:_COVERAGE_COMMAND:} pytest --maxfail=1 -k test_pep563_closure_nested {posargs} "{toxinidir}"
# If the caller explicitly concatenated the current environment name by
# "-coverage" (e.g., "py310-coverage")...
#
# Note that this DRY violation with the "finally" environment defined below
# is required by Codecov integration in our GitHub Actions-based
# ".github/workflows/python_test.yml" workflow. This is *EXTREMELY*
# non-ideal, but seemingly insurmountable unless we walk away from Codecov,
# which we're increasingly inclined to do. Codecov: you bother us.
coverage:
# Combine all environment-specific ".coverage.*" files created by the
# "{env:_COVERAGE_COMMAND:}" (e.g., "coverage run") on previously run
# environments into a single unified ".coverage" file *BEFORE*
# running any further coverage subcommands requiring this file.
-{envpython} -m coverage combine "{toxinidir}/"
# Generate a coverage report in the specific format expected by Codecov.
-{envpython} -m coverage xml -o "{toxinidir}/coverage.xml"
#FIXME: Super, but basically doesn't work. Why? Because Codecov requires the
#"coverage:" functionality defined in the default "[testenv]" above. You can do
#one or the other, but you can't do both. *sigh*
# # ....................{ FINALLY }....................
# # Job run *AFTER* all other "tox" environments, aggregating all results output
# # from previously run environments onto the local filesystem.
# [testenv:finally]
#
# # Human-readable string synopsizing this environment.
# description = Finalizing "{toxinidir}" after testing.
#
# #FIXME: We *MIGHT* also need to explicitly order environments like so:
# # depends = py{310,311,312,313,313t,314,314t,315,315t}-coverage
# #Let's avoid doing this unless we absolutely must. DRY for life, yo! \o/
#
# # Avoid installing this project under this environment.
# skip_install = true
#
# #FIXME: Not right. This *SHOULD* be using PEP 735-compliant dependency groups
# #managed by "pyproject.toml", but... eh? Who's got the time, huh? *sigh*
# # Whitespace-delimited string listing the names of all mandatory dependencies
# # (i.e., third-party packages) required to run the commands listed below.
# #
# # Note that "tox" effectively requires this setting to be explicitly defined. If
# # this setting is *NOT* explicitly defined, "tox" defaults this setting to the
# # default "deps" setting defined by the default "[testenv]" configuration, which
# # then erroneously installs this *ENTIRE* package and *ALL* dependencies simply
# # to trivially run the "coverage erase" command below. Truly, a facepalm.
# deps = coverage
#
# # Newline-delimited string listing all shell commands required to test this
# # project under this environment. See comments below for detailed discussion.
# commands =
# # If the caller explicitly concatenated the current environment name by
# # "-coverage" (e.g., "py310-coverage")...
# coverage:
# # Combine all environment-specific ".coverage.*" files created by the
# # "{env:_COVERAGE_COMMAND:}" (e.g., "coverage run") on previously run
# # environments into a single unified ".coverage" file *BEFORE*
# # running any further coverage subcommands requiring this file.
# -{envpython} -m coverage combine "{toxinidir}/"
#
# # Generate a coverage report in the specific format expected by Codecov.
# -{envpython} -m coverage xml -o "{toxinidir}/coverage.xml"
|