pax_global_header 0000666 0000000 0000000 00000000064 14764326555 0014533 g ustar 00root root 0000000 0000000 52 comment=d4ed1c673f2143656d8848ba4dd3f05d371a1560
tox-current-env-0.0.16/ 0000775 0000000 0000000 00000000000 14764326555 0014677 5 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/.github/ 0000775 0000000 0000000 00000000000 14764326555 0016237 5 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/.github/workflows/ 0000775 0000000 0000000 00000000000 14764326555 0020274 5 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/.github/workflows/main.yaml 0000664 0000000 0000000 00000001600 14764326555 0022101 0 ustar 00root root 0000000 0000000 on:
push:
pull_request:
schedule:
- cron: '0 0 * * 1' # every Monday
name: Run tox tests
jobs:
tox_test:
name: tox test
steps:
- uses: actions/checkout@v4
- name: Run tox tests
id: test
uses: fedora-python/tox-github-action@main
with:
tox_env: ${{ matrix.tox_env }}
strategy:
matrix:
tox_env:
# This information is repeated in tox.ini
# (see https://github.com/fedora-python/tox-github-action/issues/8)
# Generate it by: tox -l | sed "s/^/- /"
- py36-tox3
- py38-tox3
- py38-tox4
- py39-tox3
- py39-tox4
- py310-tox3
- py310-tox4
- py311-tox3
- py311-tox4
- py312-tox3
- py312-tox4
- py313-tox3
- py313-tox4
- py314-tox4
# Use GitHub's Linux Docker host
runs-on: ubuntu-latest
tox-current-env-0.0.16/.gitignore 0000664 0000000 0000000 00000000106 14764326555 0016664 0 ustar 00root root 0000000 0000000 __pycache__/
build/
dist/
.tox/
*.tar.gz
*.zip
*.egg-info
*.dist-info
tox-current-env-0.0.16/LICENSE 0000664 0000000 0000000 00000002054 14764326555 0015705 0 ustar 00root root 0000000 0000000 Copyright 2019 tox-current-env contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
tox-current-env-0.0.16/MANIFEST.in 0000664 0000000 0000000 00000000252 14764326555 0016434 0 ustar 00root root 0000000 0000000 include LICENSE
include pyproject.toml
include tox.ini
recursive-include tests *.py
recursive-include tests *.ini
recursive-include tests pyproject.toml
prune tests/.tox
tox-current-env-0.0.16/README.rst 0000664 0000000 0000000 00000034001 14764326555 0016364 0 ustar 00root root 0000000 0000000 ===============
tox-current-env
===============
---------------------------------------------------------------------------------------
`tox `_ plugin to run tests in current Python environment
---------------------------------------------------------------------------------------
The ``tox-current-env`` plugin adds these options:
``tox --current-env``
Runs the tox testenv's ``commands`` in the current Python environment
(that is, the environment where ``tox`` is invoked from and installed in).
Unlike regular ``tox`` invocation, this installs no dependencies declared in ``deps``.
An attempt to run this with a Python version that doesn't match will fail
(if ``tox`` is invoked from an Python 3.7 environment, any non 3.7 testenv will fail).
``tox --print-deps-to=FILE``
Instead of running any ``commands``, simply prints the
`declared dependencies `_
in ``deps`` to the specified ``FILE``.
This is useful for preparing the current environment for ``tox --current-env``.
Use ``-`` for ``FILE`` to print to standard output.
``tox --print-extras-to=FILE``
Instead of running any ``commands``, simply prints the names of the
`declared extras `_
in ``extras`` to the specified ``FILE``.
This is useful for preparing the current environment for ``tox --current-env``.
Use ``-`` for ``FILE`` to print to standard output.
``tox --print-dependency-groups-to=FILE``
Instead of running any ``commands``, simply prints the names of the
`declared dependency_groups `_
in ``dependency_groups`` to the specified ``FILE``.
This is useful for preparing the current environment for ``tox --current-env``.
Use ``-`` for ``FILE`` to print to standard output.
This option only exists with tox 4 and requires at least tox 4.22.
``tox --assert-config``
In tox 4, this option ensures that tox fails (raises an exception) if no configuration is found.
By default, tox 4 does not terminate when no configuration exists.
In tox 3, this option has no effect, but it can still be specified without causing errors.
This option can be used alongside other options.
It is possible to use the three printing options together, as long as the ``FILE`` is different.
Invoking ``tox`` without any of the above options should behave as regular ``tox`` invocation without this plugin.
Any deviation from this behavior is considered a bug.
The plugin disables *tox's way* of providing a testing environment,
but assumes that you supply one in *some other way*.
Always run ``tox`` with this plugin in a fresh isolated environment,
such as Python virtualenv, Linux container or chroot.
\
See other caveats below.
Motivation
----------
Obviously, ``tox`` was created to run tests in isolated Python virtual environments.
The ``--current-env`` flag totally defeats the purpose of ``tox``.
Why would anybody do that, you might ask?
Well, it turns out that ``tox`` became too popular and gained another purpose.
The Python ecosystem now has formal `specifications `_ for many pieces of package metadata like versions or dependencies.
However, there is no standardization yet for declaring *test dependencies* or *running tests*.
The most popular de-facto standard for that today is ``tox``,
and we expect a future standard to evolve from ``tox.ini``.
This plugin lets us use ``tox``'s dependency lists and testing commands for environments other than Python venvs.
We hope this plugin will enable community best practices around ``tox`` configuration
to grow to better accomodate non-virtualenv environments in general – for example,
Linux distros, Conda, or containers.
Specifically, this plugin was created for `Fedora `_'s needs.
When we package Python software as RPM packages, we try to run the project's test suite during package build.
However, we need to test if the software works integrated into Fedora,
not with packages downloaded from PyPI into a fresh environment.
By running the tests in *current environment*, we can achieve that.
If you are interested in the RPM packaging part of this,
see Fedora's `%pyproject RPM macros `_.
Installation
------------
Install this via ``pip``:
.. code-block:: console
$ python -m pip install tox-current-env
Or install the development version by cloning `the git repository `_
and ``pip``-installing locally:
.. code-block:: console
$ git clone https://github.com/fedora-python/tox-current-env
$ cd tox-current-env
$ python -m pip install -e .
Usage
-----
When the plugin is installed,
use ``tox`` with ``--current-env``, ``--print-deps-to``, ``--print-extras-to`` or ``--print-dependency-groups-to``
and all the other options as usual.
Assuming your ``tox`` is installed on Python 3.7:
.. code-block:: console
$ tox -e py37 --current-env
py37 create: /home/pythonista/projects/holy-grail/tests/.tox/py37
py37 installed: ...list of packages from the current environment...
py37 run-test-pre: PYTHONHASHSEED='3333333333'
py37 run-test: commands...
...runs tests in current environment's Python...
___________________________________ summary ____________________________________
py37: commands succeeded
congratulations :)
Attempting to run the ``py36`` environment's test will fail:
.. code-block:: console
$ tox -e py36 --current-env
py36 create: /home/pythonista/projects/holy-grail/tests/.tox/py36
ERROR: InterpreterMismatch: tox_current_env: interpreter versions do not match:
in current env: (3, 7, 4, 'final', 0)
requested: (3, 6, 9, 'final', 0)
___________________________________ summary ____________________________________
ERROR: py36: InterpreterMismatch: tox_current_env: interpreter versions do not match:
in current env: (3, 7, 4, 'final', 0)
requested: (3, 6, 9, 'final', 0)
To get list of test dependencies, run:
.. code-block:: console
$ tox -e py37 --print-deps-to -
py37 create: /home/pythonista/projects/holy-grail/tests/.tox/py37
py37 installed: ...you can see almost anything here...
py37 run-test-pre: PYTHONHASHSEED='3333333333'
dep1
dep2
...
___________________________________ summary ____________________________________
py37: commands succeeded
congratulations :)
To get a list of names of extras, run:
.. code-block:: console
$ tox -e py37 --print-extras-to -
py37 create: /home/pythonista/projects/holy-grail/tests/.tox/py37
py37 installed: ...you can see almost anything here...
py37 run-test-pre: PYTHONHASHSEED='3333333333'
extra1
extra2
...
___________________________________ summary ____________________________________
py37: commands succeeded
congratulations :)
To get a list of names of dependency groups, run:
.. code-block:: console
$ tox -e py37 --print-dependency-groups-to -
py37 create: /home/pythonista/projects/holy-grail/tests/.tox/py37
py37 installed: ...you can see almost anything here...
py37 run-test-pre: PYTHONHASHSEED='3333333333'
group1
...
___________________________________ summary ____________________________________
py37: commands succeeded
congratulations :)
Caveats, warnings and limitations
---------------------------------
tox 4
~~~~~
The plugin is available also for tox 4. Differences in behavior between tox 3 and 4 are these:
- ``--recreate`` is no longer needed when you switch from the plugin back to standard tox.
Tox detects it and handles the recreation automatically.
- The plugin does not check the requested Python version nor the environment name.
If you let it run for multiple environments they'll all use the same Python.
- Deprecated ``--print-deps-only`` option is no longer available.
- The ``--print-dependency-groups-to`` is only defined on tox 4.
Use an isolated environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running (especially third party software's) tests in your system Python environment is dangerous.
Always use this plugin in an isolated environment,
such as a Linux container, virtual machine or chroot.
You have been warned.
Do not rely on virtualenv details
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In order to support the ``python`` command in the ``commands`` section,
the current environment invocation of ``tox`` creates a fake virtual environment
that just has a symbolic link to the Python executable.
The link is named ``python`` even if the real interpreter's name is different
(such as ``python3.7`` or ``pypy``).
Any other commands are not linked anywhere and it is the users' responsibility
to make sure such commands are in ``$PATH`` and use the correct Python.
This can lead to slightly different results of tests than invoking them directly,
especially if you have assumptions about ``sys.executable`` or other commands
in your tests.
As a specific example, tests should invoke ``python -m pytest`` rather than assuming
the ``pytest`` command is present and uses the correct version of Python.
Don't mix current-env and regular tox runs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tox caches the virtualenvs it creates, and doesn't distinguish between
regular virtualenvs and ``--current-env``.
Don't mix ``tox --current-env``, ``tox --print-deps-to`` or ``tox --print-extras-to``
runs and regular ``tox`` runs (without the flags provided by this plugin).
If you ever need to do this, use tox's ``--recreate/-r`` flag to clear the cache.
The plugin should abort with a meaningful error message if this is detected,
but in some corner cases (such as running ``tox --current-env``,
forcefully killing it before it finished, uninstalling the plugin,
and running ``tox``), you will get undefined results
(such as installing packages from PyPI into your current environment).
Environment variables are passed by default
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since 0.0.9, all Shell environment variables are passed by default when using
this plugin. The `passenv` tox configuration is set to `*`.
Read `the documentation for more information about passing environment variables to tox
`_.
tox provisioning
~~~~~~~~~~~~~~~~
The tested projects can specify the
`minimal tox version `_
and/or
`additional requires `_
needed in the environment where ``tox`` is installed.
Normally, ``tox`` uses *provisioning* when such requirements are not met.
It creates a virtual environment,
installs (a newer version of) ``tox`` and the missing packages
into that environment and proxies all ``tox`` invocations trough that.
Unfortunately, this is undesired for ``tox-current-env``.
1. It is possible to invoke ``tox`` with ``--no-provision``
to prevent the provision entirely.
When requirements are missing, ``tox`` fails instead of provisioning.
If a path is passed as a value for ``--no-provision``,
the requirements will be serialized to the file, as JSON.
2. The requires, if specified, are included in the
results of ``tox --print-deps-to``.
This only works when they are installed (otherwise see the first point).
3. The minimal tox version, if specified, is included in the results of
``tox --print-deps-to``.
This only works when the version requirement is satisfied
(otherwise see the first point).
The recommend way to handle this is:
1. Run ``tox --no-provision provision.json --print-deps-to=...`` or similar.
2. If the command fails, install requirements from ``provision.json`` to the
current environment and try again.
Note that the specified requirements are likely to contain
`other tox plugins `_
and many of them might interfere with ``tox-current-env`` in an undesired way.
If that is the case, the recommended way is to patch/sed such undesired plugins
out of the configuration before running ``tox``.
Other limitations and known bugs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``installed:`` line in the output of ``tox --print-deps-to``/``tox --print-extras-to`` shows irrelevant output
(based on the content of the real or faked virtual environment).
Regardless of any `Python flags `_ used in the shebang of ``tox``,
the tests are invoked with ``sys.executable`` without any added flags
(unless explicitly invoked with them in the ``commands`` section).
The current environment's Python is tested for the major and minor version only.
Different interpreters with the same Python version (such as CPython and PyPy) are treated as equal.
Only Linux is supported, with special emphasis on Fedora.
This plugin might work on other Unix-like systems,
but does not work on Microsoft Windows.
This is alpha quality software.
Use it at your on your own risk.
Pull requests with improvements are welcome.
Development, issues, support
----------------------------
The development happens on GitHub,
at the `fedora-python/tox-current-env `_ repository.
You can use the `issue tracker `_ there for any discussion
or send Pull Requests.
Tests
~~~~~
In order to run the tests, you'll need ``tox`` and Python from 3.6 to 3.10 installed.
The integration tests assume all of them are available.
On Fedora, you just need to ``dnf install tox``.
Run ``tox`` to invoke the tests.
Running tests of this plugin with its own ``--current-env`` flag will most likely blow up.
License
-------
The ``tox-current-env`` project is licensed under the so-called MIT license, full text available in the `LICENSE `_ file.
Code of Conduct
---------------
The ``tox-current-env`` project follows the `Fedora's Code of Conduct `_.
tox-current-env-0.0.16/pyproject.toml 0000664 0000000 0000000 00000000133 14764326555 0017610 0 ustar 00root root 0000000 0000000 [build-system]
requires = [ "setuptools", "wheel"]
build-backend = "setuptools.build_meta"
tox-current-env-0.0.16/setup.py 0000664 0000000 0000000 00000003053 14764326555 0016412 0 ustar 00root root 0000000 0000000 from setuptools import setup, find_packages
def long_description():
with open("README.rst", encoding="utf-8") as f:
return f.read()
setup(
name="tox-current-env",
description="Use current environment instead of virtualenv for tox testenvs",
long_description=long_description(),
author="Miro Hrončok",
author_email="miro@hroncok.cz",
url="https://github.com/fedora-python/tox-current-env",
license="MIT",
version="0.0.16",
package_dir={"": "src"},
packages=find_packages("src"),
entry_points={"tox": ["current-env = tox_current_env.hooks"]},
install_requires=[
"tox>=3.28",
"importlib_metadata; python_version < '3.8'"
],
extras_require={
"tests": [
"pytest",
"pytest-xdist",
"packaging",
],
},
python_requires=">=3.6",
classifiers=[
"Development Status :: 3 - Alpha",
"Framework :: tox",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development :: Testing",
],
)
tox-current-env-0.0.16/src/ 0000775 0000000 0000000 00000000000 14764326555 0015466 5 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/src/tox_current_env/ 0000775 0000000 0000000 00000000000 14764326555 0020712 5 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/src/tox_current_env/__init__.py 0000664 0000000 0000000 00000000000 14764326555 0023011 0 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/src/tox_current_env/hooks.py 0000664 0000000 0000000 00000000236 14764326555 0022410 0 ustar 00root root 0000000 0000000 from tox import __version__ as TOX_VERSION
if TOX_VERSION[0] == "4":
from tox_current_env.hooks4 import *
else:
from tox_current_env.hooks3 import *
tox-current-env-0.0.16/src/tox_current_env/hooks3.py 0000664 0000000 0000000 00000023442 14764326555 0022477 0 ustar 00root root 0000000 0000000 import os
import shutil
import subprocess
import sys
import tox
import warnings
import argparse
try:
import importlib.metadata as importlib_metadata
except ImportError:
import importlib_metadata
@tox.hookimpl
def tox_addoption(parser):
parser.add_argument(
"--current-env",
action="store_true",
dest="current_env",
default=False,
help="Run tests in current environment, not creating any virtual environment",
)
parser.add_argument(
"--print-deps-only",
action="store_true",
dest="print_deps_only",
default=False,
help="Deprecated, equivalent to `--print-deps-to -`. Not available with tox 4.",
)
parser.add_argument(
"--print-deps-to",
"--print-deps-to-file",
action="store",
type=argparse.FileType('w'),
metavar="FILE",
default=None,
help="Don't run tests, only print the dependencies to the given file "
+ "(use `-` for stdout)",
)
parser.add_argument(
"--print-extras-to",
"--print-extras-to-file",
action="store",
type=argparse.FileType('w'),
metavar="FILE",
default=None,
help="Don't run tests, only print the names of the required extras to the given file "
+ "(use `-` for stdout)",
)
parser.add_argument(
"--assert-config",
action="store_true",
default=False,
help="Fail if no tox configuration is found (default on tox 3, does nothing)",
)
def _plugin_active(option):
return option.current_env or option.print_deps_to or option.print_extras_to
def _allow_all_externals(envconfig):
for option in ["allowlist_externals", "whitelist_externals"]:
# If either is set, we change it, as we cannot have both set at the same time:
if getattr(envconfig, option, None):
setattr(envconfig, option, "*")
break
else:
# If none was set, we set the new one
envconfig.allowlist_externals = "*"
@tox.hookimpl
def tox_configure(config):
"""Stores options in the config. Makes all commands external and skips sdist"""
if config.option.print_deps_only:
warnings.warn(
"--print-deps-only is deprecated; use `--print-deps-to -`",
DeprecationWarning,
)
if not config.option.print_deps_to:
config.option.print_deps_to = sys.stdout
else:
raise tox.exception.ConfigError(
"--print-deps-only cannot be used together "
+ "with --print-deps-to"
)
if _plugin_active(config.option):
config.skipsdist = True
for testenv in config.envconfigs:
config.envconfigs[testenv].usedevelop = False
_allow_all_externals(config.envconfigs[testenv])
# Because tox 4 no longer reads $TOX_TESTENV_PASSENV,
# this plugin always passes all environment variables by default,
# even on tox 3.
# Unfortunately at this point the set contains actual values, not globs:
config.envconfigs[testenv].passenv |= set(os.environ.keys())
# When printing dependencies/extras we don't run any commands.
# Unfortunately tox_runtest_pre/tox_runtest_post hooks don't use firstresult=True,
# so we cannot override running commands_pre/commands_post.
# We empty the lists of commands instead.
if config.option.print_deps_to or config.option.print_extras_to:
for testenv in config.envconfigs:
config.envconfigs[testenv].commands_pre = []
config.envconfigs[testenv].commands_post = []
if (getattr(config.option.print_deps_to, "name", object()) ==
getattr(config.option.print_extras_to, "name", object())):
raise tox.exception.ConfigError(
"The paths given to --print-deps-to and --print-extras-to cannot be identical."
)
return config
class InterpreterMismatch(tox.exception.InterpreterNotFound):
"""Interpreter version in current env does not match requested version"""
def _python_activate_exists(venv):
python = venv.envconfig.get_envpython()
bindir = os.path.dirname(python)
activate = os.path.join(bindir, "activate")
return os.path.exists(python), os.path.exists(activate)
def is_current_env_link(venv):
python, activate = _python_activate_exists(venv)
return python and not activate
def is_proper_venv(venv):
python, activate = _python_activate_exists(venv)
return python and activate
def is_any_env(venv):
python, activate = _python_activate_exists(venv)
return python
def rm_venv(venv):
link = venv.envconfig.get_envpython()
shutil.rmtree(os.path.dirname(os.path.dirname(link)), ignore_errors=True)
def unsupported_raise(config, venv):
if config.option.recreate:
return
if not _plugin_active(config.option) and is_current_env_link(venv):
if hasattr(tox.hookspecs, "tox_cleanup"):
raise tox.exception.ConfigError(
"Looks like previous --current-env, --print-deps-to or --print-extras-to tox run didn't finish the cleanup. "
"Run tox run with --recreate (-r) or manually remove the environment in .tox."
)
else:
raise tox.exception.ConfigError(
"Regular tox run after --current-env, --print-deps-to or --print-extras-to tox run "
"is not supported without --recreate (-r)."
)
elif config.option.current_env and is_proper_venv(venv):
raise tox.exception.ConfigError(
"--current-env after regular tox run is not supported without --recreate (-r)."
)
@tox.hookimpl
def tox_testenv_create(venv, action):
"""We create a fake virtualenv with just the symbolic link"""
config = venv.envconfig.config
create_fake_env = check_version = config.option.current_env
if config.option.print_deps_to or config.option.print_extras_to:
if is_any_env(venv):
# We don't need anything
return True
else:
# We need at least some kind of environment,
# or tox fails without a python command
# We fallback to --current-env behavior,
# because it's cheaper, faster and won't install stuff
create_fake_env = True
if check_version:
# With real --current-env, we check this, but not with --print-deps/extras-to only
version_info = venv.envconfig.python_info.version_info
if version_info is None:
raise tox.exception.InterpreterNotFound(venv.envconfig.basepython)
if version_info[:2] != sys.version_info[:2]:
raise InterpreterMismatch(
f"tox_current_env: interpreter versions do not match:\n"
+ f" in current env: {tuple(sys.version_info)}\n"
+ f" requested: {version_info}"
)
if create_fake_env:
# Make sure the `python` command on path is sys.executable.
# (We might have e.g. /usr/bin/python3, not `python`.)
# Remove the rest of the virtualenv.
link = venv.envconfig.get_envpython()
target = sys.executable
rm_venv(venv)
os.makedirs(os.path.dirname(link))
if sys.platform == "win32":
# Avoid requiring admin rights on Windows
subprocess.check_call(f'mklink /J "{link}" "{target}"', shell=True)
else:
os.symlink(target, link)
# prevent tox from creating the venv
return True
if not is_proper_venv(venv):
rm_venv(venv)
return None # let tox handle the rest
@tox.hookimpl
def tox_package(session, venv):
"""Fail early when unsupported"""
config = venv.envconfig.config
unsupported_raise(config, venv)
@tox.hookimpl
def tox_testenv_install_deps(venv, action):
"""We don't install anything"""
config = venv.envconfig.config
unsupported_raise(config, venv)
if _plugin_active(config.option):
return True
def tox_dependencies(config):
"""Get dependencies of tox itself, 'minversion' and 'requires' config options"""
if config.minversion is not None:
yield f"tox >= {config.minversion}"
yield from config.requires
@tox.hookimpl
def tox_runtest(venv, redirect):
"""If --print-deps-to, prints deps instead of running tests.
If --print-extras-to, prints extras instead of running tests.
Both options can be used together."""
config = venv.envconfig.config
unsupported_raise(config, venv)
ret = None
if config.option.print_deps_to:
print(
*tox_dependencies(config),
*venv.get_resolved_dependencies(),
sep="\n",
file=config.option.print_deps_to,
)
config.option.print_deps_to.flush()
ret = True
if config.option.print_extras_to:
print(
*venv.envconfig.extras,
sep="\n",
file=config.option.print_extras_to,
)
config.option.print_extras_to.flush()
ret = True
return ret
@tox.hookimpl
def tox_cleanup(session):
"""Remove the fake virtualenv not to collide with regular tox
Collisions can happen anyway (when tox is killed forcefully before this happens)
Note that we don't remove real venvs, as recreating them is expensive"""
for venv in session.venv_dict.values():
if is_current_env_link(venv):
rm_venv(venv)
@tox.hookimpl
def tox_runenvreport(venv, action):
"""Prevent using pip to display installed packages,
use importlib.metadata instead, but fallback to default without our flags."""
if not _plugin_active(venv.envconfig.config.option):
return None
return (
"{}=={}".format(d.metadata.get("name"), d.version)
for d in sorted(
importlib_metadata.distributions(), key=lambda d: d.metadata.get("name")
)
)
tox-current-env-0.0.16/src/tox_current_env/hooks4.py 0000664 0000000 0000000 00000022467 14764326555 0022506 0 ustar 00root root 0000000 0000000 import argparse
import os
import platform
import sys
import sysconfig
from pathlib import Path
from typing import Set
from tox.config.loader.memory import MemoryLoader
from tox.execute.local_sub_process import (
Execute,
LocalSubProcessExecuteInstance,
)
from tox.plugin import impl
from tox.tox_env.python.api import PythonInfo, PythonSpec
from tox.tox_env.python.runner import PythonRun
try:
import importlib.metadata as importlib_metadata
except ImportError:
import importlib_metadata
@impl
def tox_register_tox_env(register):
register.add_run_env(CurrentEnv)
register.add_run_env(PrintEnv)
@impl
def tox_add_option(parser):
parser.add_argument(
"--current-env",
action="store_true",
dest="current_env",
default=False,
help="Run tests in current environment, not creating any virtual environment",
)
parser.add_argument(
"--print-deps-to",
"--print-deps-to-file",
action="store",
type=argparse.FileType("w"),
metavar="FILE",
default=False,
help="Don't run tests, only print the dependencies to the given file "
+ "(use `-` for stdout)",
)
parser.add_argument(
"--print-extras-to",
"--print-extras-to-file",
action="store",
type=argparse.FileType("w"),
metavar="FILE",
default=False,
help="Don't run tests, only print the names of the required extras to the given file "
+ "(use `-` for stdout)",
)
parser.add_argument(
"--print-dependency-groups-to",
"--print-dependency-groups-to-file",
action="store",
type=argparse.FileType("w"),
metavar="FILE",
default=False,
help="Don't run tests, only print the names of the required dependency-groups to the given file "
+ "(use `-` for stdout)",
)
parser.add_argument(
"--assert-config",
action="store_true",
default=False,
help="Fail if no tox configuration is found",
)
@impl
def tox_add_core_config(core_conf, state):
opt = state.conf.options
if opt.assert_config and not state.conf.src_path.exists():
raise LookupError(
"tox configuration not found. "
"To use tox, please ensure tox configuration is located in current directory "
"(in tox.ini, setup.cfg, pyproject.toml, or tox.toml). "
"See https://tox.wiki/en/latest/config.html for details."
)
if opt.current_env or opt.print_deps_to or opt.print_extras_to or opt.print_dependency_groups_to:
# We do not want to install the main package.
# no_package is the same as skipsdist.
loader = MemoryLoader(no_package=True)
core_conf.loaders.insert(0, loader)
if opt.current_env:
opt.default_runner = "current-env"
return
exclusive = [getattr(getattr(opt, o), "name", object())
for o in ("print_deps_to", "print_extras_to", "print_dependency_groups_to")]
if len(exclusive) != len(set(exclusive)):
raise RuntimeError(
"The paths given to --print-*-to options cannot be identical."
)
if opt.print_deps_to or opt.print_extras_to or opt.print_dependency_groups_to:
opt.default_runner = "print-env"
return
@impl
def tox_add_env_config(env_conf, state):
opt = state.conf.options
# This allows all external commands.
# All of them are external for us.
# Because tox 4 no longer reads $TOX_TESTENV_PASSENV,
# this plugin always passes all environment variables by default.
if opt.current_env:
allow_external_cmds = MemoryLoader(allowlist_externals=["*"], pass_env=["*"])
env_conf.loaders.insert(0, allow_external_cmds)
# For print-*-to, use empty list of commands so that tox does nothing.
if opt.print_deps_to or opt.print_extras_to or opt.print_dependency_groups_to:
empty_commands = MemoryLoader(commands=[], commands_pre=[], commands_post=[])
env_conf.loaders.insert(0, empty_commands)
class Installer:
"""Noop installer"""
def install(self, *args, **kwargs):
return None
def installed(self):
"""Return list of installed packages like `pip freeze`."""
return [
"{}=={}".format(d.metadata.get("name"), d.version)
for d in sorted(
importlib_metadata.distributions(), key=lambda d: d.metadata.get("name")
)
]
class CurrentEnvLocalSubProcessExecutor(Execute):
def build_instance(
self,
request,
options,
out,
err,
):
request.env["PATH"] = ":".join(
(str(options._env.env_dir / "bin"), request.env.get("PATH", ""))
)
return LocalSubProcessExecuteInstance(request, options, out, err)
class CurrentEnv(PythonRun):
def __init__(self, create_args):
self._executor = None
self._installer = None
self._path = []
super().__init__(create_args)
@staticmethod
def id():
return "current-env"
@property
def _default_package_tox_env_type(self):
return None
@property
def _external_pkg_tox_env_type(self):
return None
@property
def _package_tox_env_type(self):
return None
@property
def executor(self):
if self._executor is None:
self._executor = CurrentEnvLocalSubProcessExecutor(self.options.is_colored)
return self._executor
def _get_python(self, base_python):
return PythonInfo(
implementation=sys.implementation,
version_info=sys.version_info,
version=sys.version,
is_64=(platform.architecture()[0] == "64bit"),
platform=platform.platform(),
extra={"executable": Path(sys.executable)},
)
def create_python_env(self):
"""Fake Python environment just to make sure all possible
commands like python or python3 works."""
bindir = self.env_dir / "bin"
if not bindir.exists():
os.mkdir(bindir)
for suffix in (
"",
f"{sys.version_info.major}",
f"{sys.version_info.major}.{sys.version_info.minor}",
):
symlink = bindir / f"python{suffix}"
if not symlink.exists():
os.symlink(sys.executable, symlink)
def env_bin_dir(self):
return Path(sysconfig.get_path("scripts"))
def env_python(self):
return sys.executable
def env_site_package_dir(self):
return Path(sysconfig.get_path("purelib"))
@property
def installer(self):
return Installer()
def prepend_env_var_path(self):
return [self.env_bin_dir()]
@property
def runs_on_platform(self):
return sys.platform
@classmethod
def python_spec_for_path(cls, path):
# Needed for https://github.com/fedora-python/tox-current-env/issues/77
# This is a copy of an internal tox method added in
# https://github.com/tox-dev/tox/pull/3327
implementation = sys.implementation.name
version = sys.version_info
bits = "64" if sys.maxsize > 2**32 else "32"
string_spec = f"{implementation}{version.major}{version.minor}-{bits}"
return PythonSpec.from_string_spec(string_spec)
class PrintEnv(CurrentEnv):
def __init__(self, create_args):
super().__init__(create_args)
if self.options.print_extras_to:
if "extras" not in self.conf:
# Unfortunately, if there is skipsdist/no_package or skip_install
# in the config, this section is not parsed at all so we have to
# do it here manually to be able to read its content.
self.conf.add_config(
keys=["extras"],
of_type=Set[str],
default=set(),
desc="extras to install of the target package",
)
def create_python_env(self):
"""We don't need any environment for this plugin"""
return None
def prepend_env_var_path(self):
"""Usage of this method for the core of this plugin is far from perfect
but this method is called every time even without recreated environment"""
if self.options.print_deps_to:
print(
*self.core["requires"],
*self.conf["deps"].lines(),
sep="\n",
file=self.options.print_deps_to,
)
self.options.print_deps_to.flush()
if self.options.print_extras_to:
print(
*self.conf["extras"],
sep="\n",
file=self.options.print_extras_to,
)
self.options.print_extras_to.flush()
if self.options.print_dependency_groups_to:
if "dependency_groups" not in self.conf:
raise RuntimeError(
"tox is too old to know about dependency_groups."
)
print(
*self.conf["dependency_groups"],
sep="\n",
file=self.options.print_dependency_groups_to,
)
self.options.print_dependency_groups_to.flush()
# https://github.com/fedora-python/tox-current-env/issues/75
return super().prepend_env_var_path()
@staticmethod
def id():
return "print-env"
tox-current-env-0.0.16/tests/ 0000775 0000000 0000000 00000000000 14764326555 0016041 5 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/tests/conftest.py 0000664 0000000 0000000 00000003364 14764326555 0020246 0 ustar 00root root 0000000 0000000 import os
import shutil
import pytest
from utils import FIXTURES_DIR, TOX_VERSION, TOX4, modify_config, drop_unsupported_pythons
@pytest.fixture(autouse=True)
def projdir(tmp_path, monkeypatch, worker_id):
pwd = tmp_path / "projdir"
pwd.mkdir()
for fname in "tox.ini", "setup.py", "pyproject.toml":
shutil.copy(FIXTURES_DIR / fname, pwd)
if TOX4:
with modify_config(pwd / "tox.ini") as config:
config["tox"]["envlist"] = drop_unsupported_pythons(config["tox"]["envlist"])
monkeypatch.chdir(pwd)
# https://github.com/pypa/pip/issues/5345#issuecomment-386424455
monkeypatch.setenv("XDG_CACHE_HOME",
os.path.expanduser(f"~/.cache/pytest-xdist-{worker_id}"))
return pwd
if TOX4:
available_options = ("--print-deps-to-file=-", "--print-deps-to=-")
else:
available_options = (
"--print-deps-only",
"--print-deps-to-file=-",
"--print-deps-to=-",
)
@pytest.fixture(params=available_options)
def print_deps_stdout_arg(request):
"""Argument for printing deps to stdout"""
return request.param
@pytest.fixture(params=("--print-extras-to-file=-", "--print-extras-to=-"))
def print_extras_stdout_arg(request):
"""Argument for printing extras to stdout"""
return request.param
@pytest.fixture
def dependency_groups_support():
"""Support for dependency groups"""
if (TOX_VERSION.major, TOX_VERSION.minor) < (4, 22):
raise pytest.skip(reason="requires tox 4.22 or higher")
@pytest.fixture(params=("--print-dependency-groups-to-file=-", "--print-dependency-groups-to=-"))
def print_dependency_groups_stdout_arg(request, dependency_groups_support):
"""Argument for printing dependency groups to stdout"""
return request.param
tox-current-env-0.0.16/tests/fixtures/ 0000775 0000000 0000000 00000000000 14764326555 0017712 5 ustar 00root root 0000000 0000000 tox-current-env-0.0.16/tests/fixtures/pyproject.toml 0000664 0000000 0000000 00000000171 14764326555 0022625 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[dependency-groups]
dg1 = ["build>=1"]
tox-current-env-0.0.16/tests/fixtures/setup.py 0000664 0000000 0000000 00000000173 14764326555 0021425 0 ustar 00root root 0000000 0000000 from setuptools import setup
setup(
name="test",
extras_require={
"dev": [],
"full": [],
}
)
tox-current-env-0.0.16/tests/fixtures/tox.ini 0000664 0000000 0000000 00000000703 14764326555 0021225 0 ustar 00root root 0000000 0000000 [tox]
isolated_build=true
envlist = py36,py37,py38,py39,py310,py311
[testenv]
passenv =
XDG_CACHE_HOME
deps =
six
py
extras =
dev
full
dependency_groups =
dg1
commands =
python -c 'import os, sys; print(os.path.realpath(sys.exec_prefix), "is the exec_prefix")'
# we explicitly clear this because the inner tox does not need to know
# see https://github.com/fedora-python/tox-current-env/issues/52
setenv =
PYTHONPATH=
tox-current-env-0.0.16/tests/test_integration_tox3.py 0000664 0000000 0000000 00000050615 14764326555 0022761 0 ustar 00root root 0000000 0000000 import os
import re
import shutil
import subprocess
import sys
import textwrap
import pytest
from utils import (
DOT_TOX,
NATIVE_EXEC_PREFIX_MSG,
NATIVE_EXECUTABLE,
NATIVE_SITE_PACKAGES,
NATIVE_TOXENV,
TOX_VERSION,
envs_from_tox_ini,
is_available,
modify_config,
needs_all_pythons,
tox,
tox_footer,
)
if TOX_VERSION.major != 3:
pytest.skip("skipping tests for tox 3", allow_module_level=True)
def test_native_toxenv_current_env():
result = tox("-e", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
assert not (DOT_TOX / NATIVE_TOXENV / "lib").is_dir()
@needs_all_pythons
def test_all_toxenv_current_env():
result = tox("--current-env", check=False)
if (3, 6) <= sys.version_info < (3, 12):
assert NATIVE_EXEC_PREFIX_MSG in result.stdout.splitlines()
assert result.stdout.count("InterpreterMismatch:") >= 2
assert result.returncode > 0
@pytest.mark.parametrize("python", ["python3.4", "python3.5"])
def test_missing_toxenv_current_env(python):
if is_available(python):
pytest.skip(f"Only works if {python} is not available in $PATH")
env = python.replace("python", "py").replace(".", "")
result = tox("-e", env, "--current-env", check=False)
assert f"InterpreterNotFound: {python}" in result.stdout
assert "Traceback" not in (result.stderr + result.stdout)
assert result.returncode > 0
@needs_all_pythons
def test_all_toxenv_current_env_skip_missing():
result = tox("--current-env", "--skip-missing-interpreters", check=False)
assert "InterpreterMismatch:" in result.stdout
assert "congratulations" in result.stdout
assert result.returncode == 0
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps(toxenv, print_deps_stdout_arg):
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
@pytest.mark.parametrize("pre_post", ["pre", "post", "both"])
def test_print_deps_with_commands_pre_post(projdir, toxenv, pre_post, print_deps_stdout_arg):
with modify_config(projdir / 'tox.ini') as config:
if pre_post == "both":
config["testenv"]["commands_pre"] = "echo unexpected"
config["testenv"]["commands_post"] = "echo unexpected"
else:
config["testenv"][f"commands_{pre_post}"] = "echo unexpected"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
assert result.stderr == ""
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
tox >= 3.13
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["requires"] = "\n pytest > 5\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
pytest > 5
pluggy
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion_and_requires(
projdir, toxenv, print_deps_stdout_arg
):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13"
config["tox"]["requires"] = "\n pytest > 5\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
tox >= 3.13
pytest > 5
pluggy
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras(toxenv, print_extras_stdout_arg):
result = tox("-e", toxenv, print_extras_stdout_arg)
expected = textwrap.dedent(
f"""
dev
full
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
@pytest.mark.parametrize("pre_post", ["pre", "post", "both"])
def test_print_extras_with_commands_pre_post(projdir, toxenv, pre_post, print_extras_stdout_arg):
with modify_config(projdir / 'tox.ini') as config:
if pre_post == "both":
config["testenv"]["commands_pre"] = "echo unexpected"
config["testenv"]["commands_post"] = "echo unexpected"
else:
config["testenv"][f"commands_{pre_post}"] = "echo unexpected"
result = tox("-e", toxenv, print_extras_stdout_arg)
expected = textwrap.dedent(
f"""
dev
full
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
assert result.stderr == ""
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_only_deprecated(toxenv):
result = tox(
"-e",
toxenv,
"--print-deps-only",
env={**os.environ, "PYTHONWARNINGS": "always"},
)
waring_text = (
"DeprecationWarning: --print-deps-only is deprecated; "
+ "use `--print-deps-to -`"
)
assert waring_text in result.stderr
def test_allenvs_print_deps(print_deps_stdout_arg):
result = tox(print_deps_stdout_arg)
expected = ""
for env in envs_from_tox_ini():
expected += "six\npy\n"
expected += tox_footer(spaces=0) + "\n"
assert result.stdout == expected
def test_allenvs_print_extras(print_extras_stdout_arg):
result = tox(print_extras_stdout_arg)
expected = ""
for env in envs_from_tox_ini():
expected += "dev\nfull\n"
expected += tox_footer(spaces=0) + "\n"
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_to_file(toxenv, tmp_path):
depspath = tmp_path / "deps"
result = tox("-e", toxenv, "--print-deps-to", str(depspath))
assert depspath.read_text().splitlines() == ["six", "py"]
expected = textwrap.dedent(
f"""
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras_to_file(toxenv, tmp_path):
extraspath = tmp_path / "extras"
result = tox("-e", toxenv, "--print-extras-to", str(extraspath))
assert extraspath.read_text().splitlines() == ["dev", "full"]
expected = textwrap.dedent(
f"""
{tox_footer(toxenv)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("option", ("--print-deps-to", "--print-deps-to-file"))
def test_allenvs_print_deps_to_file(tmp_path, option):
depspath = tmp_path / "deps"
result = tox(option, str(depspath))
assert depspath.read_text().splitlines() == ["six", "py"] * len(envs_from_tox_ini())
expected = textwrap.dedent(
f"""
{tox_footer()}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("option", ("--print-extras-to", "--print-extras-to-file"))
def test_allenvs_print_extras_to_file(tmp_path, option):
extraspath = tmp_path / "extras"
result = tox(option, str(extraspath))
assert extraspath.read_text().splitlines() == ["dev", "full"] * len(envs_from_tox_ini())
expected = textwrap.dedent(
f"""
{tox_footer()}
"""
).lstrip()
assert result.stdout == expected
def test_allenvs_print_deps_to_existing_file(tmp_path):
depspath = tmp_path / "deps"
depspath.write_text("nada")
_ = tox("--print-deps-to", str(depspath))
lines = depspath.read_text().splitlines()
assert "nada" not in lines
assert "six" in lines
assert "py" in lines
def test_allenvs_print_extras_to_existing_file(tmp_path):
extraspath = tmp_path / "extras"
extraspath.write_text("nada")
_ = tox("--print-extras-to", str(extraspath))
lines = extraspath.read_text().splitlines()
assert "nada" not in lines
assert "dev" in lines
assert "full" in lines
@pytest.mark.parametrize("deps_stdout", [True, False])
@pytest.mark.parametrize("extras_stdout", [True, False])
def test_allenvs_print_deps_to_file_print_extras_to_other_file(
tmp_path, deps_stdout, extras_stdout
):
if deps_stdout and extras_stdout:
pytest.xfail("Unsupported combination of parameters")
depspath = "-" if deps_stdout else tmp_path / "deps"
extraspath = "-" if extras_stdout else tmp_path / "extras"
result = tox("--print-deps-to", str(depspath), "--print-extras-to", str(extraspath))
if deps_stdout:
depslines = result.stdout.splitlines()
extraslines = extraspath.read_text().splitlines()
elif extras_stdout:
depslines = depspath.read_text().splitlines()
extraslines = result.stdout.splitlines()
else:
extraslines = extraspath.read_text().splitlines()
depslines = depspath.read_text().splitlines()
assert "six" in depslines
assert "py" in depslines
assert "full" in extraslines
assert "dev" in extraslines
assert "six" not in extraslines
assert "py" not in extraslines
assert "full" not in depslines
assert "dev" not in depslines
def test_print_deps_extras_to_same_file_is_not_possible(tmp_path):
depsextraspath = tmp_path / "depsextras"
result = tox(
"-e",
NATIVE_TOXENV,
"--print-deps-to",
str(depsextraspath),
"--print-extras-to",
str(depsextraspath),
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
def test_print_deps_extras_to_stdout_is_not_possible(
tmp_path,
print_deps_stdout_arg,
print_extras_stdout_arg,
):
result = tox(
"-e",
NATIVE_TOXENV,
print_deps_stdout_arg,
print_extras_stdout_arg,
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
def test_print_deps_only_print_deps_to_file_are_mutually_exclusive():
result = tox(
"-e",
NATIVE_TOXENV,
"--print-deps-only",
"--print-deps-to",
"foobar",
check=False,
)
assert result.returncode > 0
assert "cannot be used together" in result.stderr
@needs_all_pythons
def test_regular_run():
result = tox()
lines = result.stdout.splitlines()[:5]
for line, env in zip(lines, envs_from_tox_ini()):
assert f"/.tox/{env} is the exec_prefix" in line
assert "congratulations" in result.stdout
for env in envs_from_tox_ini():
major, minor = re.match(r"py(\d)(\d+)", env).groups()
for pkg in "py", "six", "test":
sitelib = DOT_TOX / f"{env}/lib/python{major}.{minor}/site-packages"
assert sitelib.is_dir()
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
def test_regular_run_native_toxenv():
result = tox("-e", NATIVE_TOXENV)
lines = sorted(result.stdout.splitlines()[:1])
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0]
assert "congratulations" in result.stdout
for pkg in "py", "six", "test":
sitelib = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}"
assert sitelib.is_dir()
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
def test_regular_after_current_is_supported():
result = tox("-e", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
result = tox("-e", NATIVE_TOXENV)
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
assert "--recreate" not in result.stderr
def test_regular_after_killed_current_is_not_supported():
# fake broken tox run
shutil.rmtree(DOT_TOX, ignore_errors=True)
(DOT_TOX / NATIVE_TOXENV / "bin").mkdir(parents=True)
(DOT_TOX / NATIVE_TOXENV / "bin" / "python").symlink_to(NATIVE_EXECUTABLE)
result = tox("-e", NATIVE_TOXENV, check=False)
assert result.returncode > 0
assert "--recreate" in result.stderr
def test_regular_after_first_print_deps_is_supported(print_deps_stdout_arg):
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
assert result.stdout.splitlines()[0] == "six"
result = tox("-e", NATIVE_TOXENV)
lines = sorted(result.stdout.splitlines()[:1])
assert "--recreate" not in result.stderr
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0]
# check that "test" was not installed to current environment
shutil.rmtree("./test.egg-info")
pip_freeze = subprocess.run(
(sys.executable, "-m", "pip", "freeze"),
encoding="utf-8",
stdout=subprocess.PIPE,
).stdout.splitlines()
# XXX when this fails, recreate your current environment
assert "test==0.0.0" not in pip_freeze
def test_regular_recreate_after_current():
result = tox("-e", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
result = tox("-re", NATIVE_TOXENV)
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
assert "not supported" not in result.stderr
assert "--recreate" not in result.stderr
def test_current_after_regular_is_not_supported():
result = tox("-e", NATIVE_TOXENV)
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
result = tox("-e", NATIVE_TOXENV, "--current-env", check=False)
assert result.returncode > 0
assert "not supported" in result.stderr
def test_current_recreate_after_regular():
result = tox("-e", NATIVE_TOXENV)
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
result = tox("-re", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
def test_current_after_print_deps(print_deps_stdout_arg):
# this is quite fast, so we can do it several times
for _ in range(3):
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
assert "bin/python" not in result.stdout
assert "six" in result.stdout
result = tox("-re", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
def test_current_after_print_extras(print_extras_stdout_arg):
# this is quite fast, so we can do it several times
for _ in range(3):
result = tox("-e", NATIVE_TOXENV, print_extras_stdout_arg)
assert "bin/python" not in result.stdout
assert "full" in result.stdout
result = tox("-re", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
def test_regular_recreate_after_print_deps(print_deps_stdout_arg):
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
assert "bin/python" not in result.stdout
assert "six" in result.stdout
result = tox("-re", NATIVE_TOXENV)
assert result.stdout.splitlines()[0] != NATIVE_EXEC_PREFIX_MSG
sitelib = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}"
assert sitelib.is_dir()
assert len(list(sitelib.glob("test-*.dist-info"))) == 1
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
assert "bin/python" not in result.stdout
assert "six" in result.stdout
def test_print_deps_without_python_command(tmp_path, print_deps_stdout_arg):
bin = tmp_path / "bin"
bin.mkdir()
tox_link = bin / "tox"
tox_path = shutil.which("tox")
tox_link.symlink_to(tox_path)
env = {**os.environ, "PATH": str(bin)}
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg, env=env)
expected = textwrap.dedent(
f"""
six
py
{tox_footer(NATIVE_TOXENV)}
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("flag", [None, "--print-deps-to=-", "--current-env"])
def test_noquiet_installed_packages(flag):
flags = (flag,) if flag else ()
result = tox("-e", NATIVE_TOXENV, *flags, quiet=False, check=False)
assert f"\n{NATIVE_TOXENV} installed: " in result.stdout
for line in result.stdout.splitlines():
if line.startswith(f"{NATIVE_TOXENV} installed: "):
packages = line.rpartition(" installed: ")[-1].split(",")
break
# default tox produces output sorted by package names
assert not flag or packages == sorted(
packages, key=lambda p: p.partition("==")[0].partition(" @ ")[0].lower()
)
# without a flag, the output must match tox defaults
if not flag:
pytest.xfail("the test is unstable")
assert len(packages) == 3
assert packages[0].startswith("py==")
assert packages[1].startswith("six==")
assert packages[2].startswith(("test==", "test @ ")) # old and new pip
# with our flags, uses the absolutely current environment by default, hence has tox
else:
assert len({p for p in packages if p.startswith("tox==")}) == 1
assert all(re.match(r"\S+==\S+", p) for p in packages)
@pytest.mark.parametrize(
"flag", ["--print-deps-to=-", "--print-extras-to=-", "--current-env"]
)
@pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_not_installed(projdir, flag, usedevelop):
with modify_config(projdir / "tox.ini") as config:
config["testenv"]["usedevelop"] = str(usedevelop)
result = tox("-e", NATIVE_TOXENV, flag, quiet=False)
assert "test==0.0.0" not in result.stdout
assert "test @ file://" not in result.stdout
@pytest.mark.parametrize("externals", [None, "allowlist_externals", "whitelist_externals"])
def test_externals(projdir, externals):
with modify_config(projdir / 'tox.ini') as config:
config['testenv']['commands'] = "echo assertme"
if externals is not None:
config['testenv'][externals] = "foo"
result = tox("-e", NATIVE_TOXENV, "--current-env", quiet=False)
assert result.returncode == 0
assert "assertme" in result.stdout
assert "test command found but not installed in testenv" not in (result.stdout + result.stderr)
@pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_installed_with_regular_tox(projdir, usedevelop):
with modify_config(projdir / "tox.ini") as config:
config["testenv"]["usedevelop"] = str(usedevelop)
result = tox("-e", NATIVE_TOXENV, quiet=False)
assert "test==0.0.0" in result.stdout or "test @ file://" in result.stdout
@pytest.mark.parametrize("passenv", [None, "different list", "__var", "*"])
def test_passenv(projdir, passenv):
with modify_config(projdir / "tox.ini") as config:
config["testenv"]["commands"] = """python -c 'import os; print(os.getenv("__var"))'"""
if passenv is not None:
existing = config["testenv"].get("passenv", "") + " "
config["testenv"]["passenv"] = existing + passenv
env = {"__var": "assertme"}
result = tox("-e", NATIVE_TOXENV, "--current-env", env=env, quiet=False)
assert result.returncode == 0
assert "\nassertme\n" in result.stdout
assert "\nNone\n" not in result.stdout
def test_assert_config_option_with_config(projdir):
result = tox("-l", "--assert-config", check=False)
assert result.returncode == 0
def test_assert_config_option_without_config(projdir):
(projdir / "tox.ini").unlink()
result = tox("-l", "--assert-config", check=False)
assert result.returncode > 0
assert "config" in result.stderr
assert "not found" in result.stderr
def test_assert_config_option_with_setup_cfg(projdir):
(projdir / "tox.ini").unlink()
setup_cfg = projdir / "setup.cfg"
setup_cfg.write_text(textwrap.dedent("""
[tox:tox]
env_list =
py310
"""))
result = tox("-l", "--assert-config", check=False)
assert result.returncode == 0
def test_assert_config_option_with_pyproject_toml(projdir):
(projdir / "tox.ini").unlink()
pyproject_toml = projdir / "pyproject.toml"
pyproject_toml.write_text(textwrap.dedent('''
[tool.tox]
legacy_tox_ini = """
[tox]
envlist =
py310
"""
'''))
result = tox("-l", "--assert-config", check=False)
assert result.returncode == 0
tox-current-env-0.0.16/tests/test_integration_tox4.py 0000664 0000000 0000000 00000053022 14764326555 0022755 0 ustar 00root root 0000000 0000000 import os
import re
import shutil
import textwrap
import pytest
from utils import (
DOT_TOX,
NATIVE_EXEC_PREFIX_MSG,
NATIVE_SITE_PACKAGES,
NATIVE_TOXENV,
TOX_VERSION,
envs_from_tox_ini,
modify_config,
needs_all_pythons,
prep_tox_output,
tox,
tox_footer,
)
if TOX_VERSION.major != 4:
pytest.skip("skipping tests for tox 4", allow_module_level=True)
def test_native_toxenv_current_env():
result = tox("-e", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
assert not (DOT_TOX / NATIVE_TOXENV / "lib").is_dir()
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps(toxenv, print_deps_stdout_arg):
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
tox
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
@pytest.mark.parametrize("pre_post", ["pre", "post", "both"])
def test_print_deps_with_commands_pre_post(projdir, toxenv, pre_post, print_deps_stdout_arg):
with modify_config(projdir / 'tox.ini') as config:
if pre_post == "both":
config["testenv"]["commands_pre"] = "echo unexpected"
config["testenv"]["commands_post"] = "echo unexpected"
else:
config["testenv"][f"commands_{pre_post}"] = "echo unexpected"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
tox
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(
expected.splitlines()
)
assert result.stderr == ""
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
tox>=3.13
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["requires"] = "\n pytest > 5\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
pytest>5
pluggy
tox
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion_and_requires(
projdir, toxenv, print_deps_stdout_arg
):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13"
config["tox"]["requires"] = "\n pytest > 5\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent(
f"""
pytest>5
pluggy
tox>=3.13
six
py
{tox_footer(toxenv)}
"""
).lstrip()
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras(toxenv, print_extras_stdout_arg):
result = tox("-e", toxenv, print_extras_stdout_arg)
expected = textwrap.dedent(
f"""
dev
full
{tox_footer(toxenv)}
"""
).lstrip()
assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(
expected.splitlines()
)
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_dependency_groups(toxenv, print_dependency_groups_stdout_arg):
result = tox("-e", toxenv, print_dependency_groups_stdout_arg)
expected = textwrap.dedent(
f"""
dg1
{tox_footer(toxenv)}
"""
).lstrip()
assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(
expected.splitlines()
)
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_dependency_groups_empty(projdir, toxenv, print_dependency_groups_stdout_arg):
with modify_config(projdir / 'tox.ini') as config:
del config["testenv"]["dependency_groups"]
result = tox("-e", toxenv, print_dependency_groups_stdout_arg)
expected = [l.strip() for l in tox_footer(toxenv).splitlines() if l.strip()]
got = [l.strip() for l in prep_tox_output(result.stdout).splitlines() if l.strip()]
assert got == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
@pytest.mark.parametrize("pre_post", ["pre", "post", "both"])
def test_print_extras_with_commands_pre_post(projdir, toxenv, pre_post, print_extras_stdout_arg):
with modify_config(projdir / 'tox.ini') as config:
if pre_post == "both":
config["testenv"]["commands_pre"] = "echo unexpected"
config["testenv"]["commands_post"] = "echo unexpected"
else:
config["testenv"][f"commands_{pre_post}"] = "echo unexpected"
result = tox("-e", toxenv, print_extras_stdout_arg)
expected = textwrap.dedent(
f"""
dev
full
{tox_footer(toxenv)}
"""
).lstrip()
assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(
expected.splitlines()
)
assert result.stderr == ""
def test_allenvs_print_deps(print_deps_stdout_arg):
result = tox(print_deps_stdout_arg)
expected = []
for env in envs_from_tox_ini():
expected.extend(("tox", "six", "py", f"{env}: OK"))
expected.pop() # The last "py310: OK" is not there
expected.append(tox_footer(spaces=0))
expected = ("\n".join(expected)).splitlines()
assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(expected)
def test_allenvs_print_extras(print_extras_stdout_arg):
result = tox(print_extras_stdout_arg)
expected = []
for env in envs_from_tox_ini():
expected.extend(("dev", "full", f"{env}: OK"))
expected.pop() # The last "py310: OK" is not there
expected.append(tox_footer(spaces=0))
expected = ("\n".join(expected)).splitlines()
assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(expected)
def test_allenvs_print_dependency_groups(print_dependency_groups_stdout_arg):
result = tox(print_dependency_groups_stdout_arg)
expected = []
for env in envs_from_tox_ini():
expected.extend(("dg1", f"{env}: OK"))
expected.pop() # The last "py310: OK" is not there
expected.append(tox_footer(spaces=0))
expected = ("\n".join(expected)).splitlines()
assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(expected)
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_to_file(toxenv, tmp_path):
depspath = tmp_path / "deps"
result = tox("-e", toxenv, "--print-deps-to", str(depspath))
assert sorted(depspath.read_text().splitlines()) == sorted(
["tox", "six", "py"]
)
expected = tox_footer(toxenv, spaces=0) + "\n"
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras_to_file(toxenv, tmp_path):
extraspath = tmp_path / "extras"
result = tox("-e", toxenv, "--print-extras-to", str(extraspath))
assert sorted(extraspath.read_text().splitlines()) == sorted(["dev", "full"])
expected = tox_footer(toxenv, spaces=0) + "\n"
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_dependency_groups_to_file(toxenv, tmp_path, dependency_groups_support):
groupspath = tmp_path / "dependency_groups"
result = tox("-e", toxenv, "--print-dependency-groups-to", str(groupspath))
assert sorted(groupspath.read_text().splitlines()) == ["dg1"]
expected = tox_footer(toxenv, spaces=0) + "\n"
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("option", ("--print-deps-to", "--print-deps-to-file"))
def test_allenvs_print_deps_to_file(tmp_path, option):
depspath = tmp_path / "deps"
result = tox(option, str(depspath))
assert sorted(depspath.read_text().splitlines()) == sorted(
["tox", "six", "py"] * len(envs_from_tox_ini())
)
expected = ""
for env in envs_from_tox_ini()[:-1]:
expected += f"{env}: OK\n"
expected += tox_footer(spaces=0) + "\n"
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("option", ("--print-extras-to", "--print-extras-to-file"))
def test_allenvs_print_extras_to_file(tmp_path, option):
extraspath = tmp_path / "extras"
result = tox(option, str(extraspath))
assert sorted(extraspath.read_text().splitlines()) == sorted(
["dev", "full"] * len(envs_from_tox_ini())
)
expected = ""
for env in envs_from_tox_ini()[:-1]:
expected += f"{env}: OK\n"
expected += tox_footer(spaces=0) + "\n"
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("option", ("--print-dependency-groups-to", "--print-dependency-groups-to-file"))
def test_allenvs_print_dependency_groups_to_file(tmp_path, option, dependency_groups_support):
groupspath = tmp_path / "dependency_groups"
result = tox(option, str(groupspath))
assert sorted(groupspath.read_text().splitlines()) == (
["dg1"] * len(envs_from_tox_ini())
)
expected = ""
for env in envs_from_tox_ini()[:-1]:
expected += f"{env}: OK\n"
expected += tox_footer(spaces=0) + "\n"
assert prep_tox_output(result.stdout) == expected
def test_allenvs_print_deps_to_existing_file(tmp_path):
depspath = tmp_path / "deps"
depspath.write_text("nada")
_ = tox("--print-deps-to", str(depspath))
lines = depspath.read_text().splitlines()
assert "nada" not in lines
assert "six" in lines
assert "py" in lines
def test_allenvs_print_extras_to_existing_file(tmp_path):
extraspath = tmp_path / "extras"
extraspath.write_text("nada")
_ = tox("--print-extras-to", str(extraspath))
lines = extraspath.read_text().splitlines()
assert "nada" not in lines
assert "dev" in lines
assert "full" in lines
def test_allenvs_print_dependency_groups_to_existing_file(tmp_path, dependency_groups_support):
groupspath = tmp_path / "dependency_groups"
groupspath.write_text("nada")
_ = tox("--print-dependency-groups-to", str(groupspath))
lines = groupspath.read_text().splitlines()
assert "nada" not in lines
assert "dg1" in lines
@pytest.mark.parametrize("deps_stdout", [True, False])
@pytest.mark.parametrize("extras_stdout", [True, False])
def test_allenvs_print_deps_to_file_print_extras_to_other_file(
tmp_path, deps_stdout, extras_stdout
):
if deps_stdout and extras_stdout:
pytest.skip("Unsupported combination of parameters")
depspath = "-" if deps_stdout else tmp_path / "deps"
extraspath = "-" if extras_stdout else tmp_path / "extras"
result = tox("--print-deps-to", str(depspath), "--print-extras-to", str(extraspath))
depslines = result.stdout.splitlines() if deps_stdout else depspath.read_text().splitlines()
extraslines = result.stdout.splitlines() if extras_stdout else extraspath.read_text().splitlines()
assert "six" in depslines
assert "py" in depslines
assert "full" in extraslines
assert "dev" in extraslines
assert "six" not in extraslines
assert "py" not in extraslines
assert "full" not in depslines
assert "dev" not in depslines
@pytest.mark.parametrize("deps_stdout", [True, False])
@pytest.mark.parametrize("extras_stdout", [True, False])
@pytest.mark.parametrize("dependency_groups_stdout", [True, False])
def test_allenvs_print_deps_to_file_print_extras_to_other_file_print_dependency_groups_to_other_file(
tmp_path, deps_stdout, extras_stdout, dependency_groups_stdout, dependency_groups_support
):
if deps_stdout + extras_stdout + dependency_groups_stdout > 1:
pytest.skip("Unsupported combination of parameters")
depspath = "-" if deps_stdout else tmp_path / "deps"
extraspath = "-" if extras_stdout else tmp_path / "extras"
groupspath = "-" if dependency_groups_stdout else tmp_path / "dependency_groups"
result = tox("--print-deps-to", str(depspath),
"--print-extras-to", str(extraspath),
"--print-dependency-groups-to", str(groupspath))
depslines = result.stdout.splitlines() if deps_stdout else depspath.read_text().splitlines()
extraslines = result.stdout.splitlines() if extras_stdout else extraspath.read_text().splitlines()
groupslines = result.stdout.splitlines() if dependency_groups_stdout else groupspath.read_text().splitlines()
assert "six" in depslines
assert "py" in depslines
assert "full" in extraslines
assert "dev" in extraslines
assert "dg1" in groupslines
assert "six" not in extraslines
assert "py" not in extraslines
assert "dg1" not in extraslines
assert "full" not in depslines
assert "dev" not in depslines
assert "dg1" not in depslines
assert "six" not in groupslines
assert "py" not in groupslines
assert "full" not in groupslines
assert "dev" not in groupslines
def test_print_deps_extras_to_same_file_is_not_possible(tmp_path):
depsextraspath = tmp_path / "depsextras"
result = tox(
"-e",
NATIVE_TOXENV,
"--print-deps-to",
str(depsextraspath),
"--print-extras-to",
str(depsextraspath),
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
def test_print_deps_dependency_groups_to_same_file_is_not_possible(tmp_path, dependency_groups_support):
depsgroupspath = tmp_path / "depsgroups"
result = tox(
"-e",
NATIVE_TOXENV,
"--print-deps-to",
str(depsgroupspath),
"--print-dependency-groups-to",
str(depsgroupspath),
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
def test_print_extras_dependency_groups_to_same_file_is_not_possible(tmp_path, dependency_groups_support):
extrasgroupspath = tmp_path / "extrasgroups"
result = tox(
"-e",
NATIVE_TOXENV,
"--print-extras-to",
str(extrasgroupspath),
"--print-dependency-groups-to",
str(extrasgroupspath),
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
def test_print_deps_extras_to_stdout_is_not_possible(
tmp_path,
print_deps_stdout_arg,
print_extras_stdout_arg,
):
result = tox(
"-e",
NATIVE_TOXENV,
print_deps_stdout_arg,
print_extras_stdout_arg,
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
def test_print_deps_dependency_groups_to_stdout_is_not_possible(
tmp_path,
print_deps_stdout_arg,
print_dependency_groups_stdout_arg,
):
result = tox(
"-e",
NATIVE_TOXENV,
print_deps_stdout_arg,
print_dependency_groups_stdout_arg,
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
def test_print_extras_dependency_groups_to_stdout_is_not_possible(
tmp_path,
print_extras_stdout_arg,
print_dependency_groups_stdout_arg,
):
result = tox(
"-e",
NATIVE_TOXENV,
print_extras_stdout_arg,
print_dependency_groups_stdout_arg,
check=False,
)
assert result.returncode > 0
assert "cannot be identical" in result.stderr
@needs_all_pythons
def test_regular_run():
result = tox()
lines = result.stdout.splitlines()[:5]
for line, env in zip(lines, envs_from_tox_ini()):
assert f"/.tox/{env} is the exec_prefix" in line
assert "congratulations" in result.stdout
for env in envs_from_tox_ini():
major, minor = re.match(r"py(\d)(\d+)", env).groups()
for pkg in "py", "six", "test":
sitelib = DOT_TOX / f"{env}/lib/python{major}.{minor}/site-packages"
assert sitelib.is_dir()
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
def test_regular_run_native_toxenv():
result = tox("-e", NATIVE_TOXENV)
lines = sorted(result.stdout.splitlines()[:1])
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0]
assert "congratulations" in result.stdout
for pkg in "py", "six", "test":
sitelib = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}"
assert sitelib.is_dir()
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
def test_print_deps_without_python_command(tmp_path, print_deps_stdout_arg):
bin = tmp_path / "bin"
bin.mkdir()
tox_link = bin / "tox"
tox_path = shutil.which("tox")
tox_link.symlink_to(tox_path)
env = {**os.environ, "PATH": str(bin)}
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg, env=env)
expected = textwrap.dedent(
f"""
tox
six
py
{tox_footer(NATIVE_TOXENV)}
"""
).lstrip()
assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("flag", ["--print-deps-to=-", "--current-env"])
def test_recreate_environment(flag):
flags = (flag,) if flag else ()
_ = tox("-e", NATIVE_TOXENV, check=False)
result = tox("-e", NATIVE_TOXENV, *flags, quiet=False, check=False)
assert f"{NATIVE_TOXENV}: recreate env because env type changed" in prep_tox_output(
result.stdout
)
@pytest.mark.parametrize(
"flag", ["--print-deps-to=-", "--print-extras-to=-", "--current-env"]
)
@pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_not_installed(projdir, flag, usedevelop):
with modify_config(projdir / "tox.ini") as config:
config["testenv"]["usedevelop"] = str(usedevelop)
_ = tox("-e", NATIVE_TOXENV, flag, quiet=False)
egg_link = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test.egg-link"
dist_info = (
DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test-0.0.0.dist-info"
)
assert not egg_link.exists()
assert not dist_info.exists()
@pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_installed_with_regular_tox(projdir, usedevelop):
with modify_config(projdir / "tox.ini") as config:
config["testenv"]["usedevelop"] = str(usedevelop)
result = tox("-e", NATIVE_TOXENV, "-v", quiet=False)
assert "test-0.0.0" in result.stdout
if usedevelop:
assert "test-0.0.0-0.editable" in result.stdout
@pytest.mark.parametrize("passenv", [None, "different list", "__var", "*"])
def test_passenv(projdir, passenv):
with modify_config(projdir / "tox.ini") as config:
config["testenv"]["commands"] = """python -c 'import os; print(os.getenv("__var"))'"""
if passenv is not None:
existing = config["testenv"].get("passenv", "") + " "
config["testenv"]["passenv"] = existing + passenv
env = {"__var": "assertme"}
result = tox("-e", NATIVE_TOXENV, "--current-env", env=env, quiet=False)
assert result.returncode == 0
assert "\nassertme\n" in result.stdout
assert "\nNone\n" not in result.stdout
@pytest.mark.parametrize("pass_env", [None, "different\nlist", "__var", "*"])
def test_pass_env(projdir, pass_env):
with modify_config(projdir / "tox.ini") as config:
config["testenv"]["commands"] = """python -c 'import os; print(os.getenv("__var"))'"""
if pass_env is not None:
config["testenv"]["pass_env"] = pass_env
env = {"__var": "assertme"}
result = tox("-e", NATIVE_TOXENV, "--current-env", env=env, quiet=False)
assert result.returncode == 0
assert "\nassertme\n" in result.stdout
assert "\nNone\n" not in result.stdout
def test_report_installed(projdir):
# tox4 only reports installed when a CI is detected
env = {"CI": "true"}
result = tox("-e", NATIVE_TOXENV, "--current-env", env=env, quiet=False)
assert result.returncode == 0
assert "tox==" in result.stdout
assert "pytest==" in result.stdout
def test_assert_config_option_with_config(projdir):
result = tox("-l", "--assert-config", check=False)
assert result.returncode == 0
def test_assert_config_option_without_config(projdir):
(projdir / "tox.ini").unlink()
result = tox("-l", "--assert-config", check=False)
assert result.returncode > 0
assert "config" in result.stderr
assert "not found" in result.stderr
def test_assert_config_option_with_setup_cfg(projdir):
(projdir / "tox.ini").unlink()
setup_cfg = projdir / "setup.cfg"
setup_cfg.write_text(textwrap.dedent("""
[tox:tox]
env_list =
py310
"""))
result = tox("-l", "--assert-config", check=False)
assert result.returncode == 0
def test_assert_config_option_with_pyproject_toml(projdir):
(projdir / "tox.ini").unlink()
pyproject_toml = projdir / "pyproject.toml"
pyproject_toml.write_text(textwrap.dedent('''
[tool.tox]
legacy_tox_ini = """
[tox]
envlist =
py310
"""
'''))
result = tox("-l", "--assert-config", check=False)
assert result.returncode == 0
def test_assert_config_option_with_pyproject_toml_native(projdir):
(projdir / "tox.ini").unlink()
pyproject_toml = projdir / "pyproject.toml"
pyproject_toml.write_text(textwrap.dedent("""
[tool.tox]
env_list = ["3.13"]
"""))
result = tox("-l", "--assert-config", check=False)
assert result.returncode == 0
tox-current-env-0.0.16/tests/utils.py 0000664 0000000 0000000 00000007577 14764326555 0017573 0 ustar 00root root 0000000 0000000 import configparser
import contextlib
import functools
import os
import pathlib
import re
import subprocess
import sys
from configparser import ConfigParser
import pytest
from packaging.version import parse as ver
PYTHON_VERSION_DOT = f"{sys.version_info[0]}.{sys.version_info[1]}"
PYTHON_VERSION_NODOT = f"{sys.version_info[0]}{sys.version_info[1]}"
NATIVE_TOXENV = f"py{PYTHON_VERSION_NODOT}"
NATIVE_SITE_PACKAGES = f"lib/python{PYTHON_VERSION_DOT}/site-packages"
NATIVE_EXECUTABLE = str(pathlib.Path(sys.executable).resolve())
FIXTURES_DIR = pathlib.Path(__file__).parent / "fixtures"
DOT_TOX = pathlib.Path("./.tox")
def _exec_prefix(executable):
"""Returns sys.exec_prefix for the given executable"""
cmd = (executable, "-c", "import sys; print(sys.exec_prefix)")
return subprocess.check_output(cmd, encoding="utf-8").strip()
NATIVE_EXEC_PREFIX = _exec_prefix(NATIVE_EXECUTABLE)
NATIVE_EXEC_PREFIX_MSG = f"{NATIVE_EXEC_PREFIX} is the exec_prefix"
def tox(*args, quiet=True, **kwargs):
kwargs.setdefault("encoding", "utf-8")
kwargs.setdefault("stdout", subprocess.PIPE)
kwargs.setdefault("stderr", subprocess.PIPE)
kwargs.setdefault("check", True)
kwargs.setdefault("cwd", os.getcwd())
q = ("-q",) if quiet else ()
env = dict(os.environ)
env.pop("TOX_WORK_DIR", None)
kwargs["env"] = {**env, **kwargs.get("env", {})}
try:
print("current", os.getcwd(), "running in", kwargs["cwd"])
cp = subprocess.run((sys.executable, "-m", "tox") + q + args, **kwargs)
except subprocess.CalledProcessError as e:
print(e.stdout, file=sys.stdout)
print(e.stderr, file=sys.stderr)
raise
print(cp.stdout, file=sys.stdout)
print(cp.stderr, file=sys.stderr)
return cp
TOX_VERSION = ver(tox("--version").stdout.split(" ")[0].split("+")[0])
TOX4 = TOX_VERSION.major == 4
@contextlib.contextmanager
def modify_config(tox_ini_path):
"""Context manager that allows modifying the given Tox config file
A statement like::
with prepare_config(projdir) as config:
will make `config` a ConfigParser instance that is saved at the end
of the `with` block.
"""
config = configparser.ConfigParser()
config.read(tox_ini_path)
yield config
with open(tox_ini_path, "w") as tox_ini_file:
config.write(tox_ini_file)
@functools.lru_cache(maxsize=8)
def is_available(python):
try:
subprocess.run((python, "--version"))
except FileNotFoundError:
return False
return True
@functools.lru_cache()
def drop_unsupported_pythons(envlist):
return envlist[len("py36,py37,"):] if TOX4 else envlist
@functools.lru_cache()
def envs_from_tox_ini():
cp = ConfigParser()
cp.read(FIXTURES_DIR / "tox.ini")
envlist = drop_unsupported_pythons(cp["tox"]["envlist"])
return envlist.split(",")
def tox_footer(envs=None, spaces=8):
if envs is None:
envs = envs_from_tox_ini()
elif isinstance(envs, str):
envs = [envs]
default_indent = " " * spaces
if TOX4:
result = ""
else:
result = "___________________________________ summary ____________________________________\n"
for i, env in enumerate(envs):
if TOX4:
# Skip indentation for the first line
indent = default_indent if i > 0 else ""
result += f"{indent} {env}: OK\n"
else:
result += f"{default_indent} {env}: commands succeeded\n"
result += f"{default_indent} congratulations :)"
return result
def prep_tox_output(output):
"""Remove time info from tox output"""
result = re.sub(r" \((\d+\.\d+|\d+) seconds\)", "", output)
result = re.sub(r" ✔ in (\d+\.\d+|\d+) seconds", "", result)
return result
needs_all_pythons = pytest.mark.skipif(
not all((is_available(f"python3.{x}") for x in range(6, 12))),
reason="This test needs all pythons from 3.6 to 3.11 available in $PATH",
)
tox-current-env-0.0.16/tox.ini 0000664 0000000 0000000 00000000614 14764326555 0016213 0 ustar 00root root 0000000 0000000 [tox]
# This information is repeated in .github/workflows/main.yaml
# (see https://github.com/fedora-python/tox-github-action/issues/8)
envlist = py36-tox3,{py37,py38,py39,py310,py311,py312,py313}-tox{3,4},py314-tox4
[testenv]
extras =
tests
deps=
tox3: tox < 4
tox3: virtualenv < 20.22
tox4: tox >=4.0.15,< 5
commands =
pytest -v {posargs} tests
[pytest]
addopts = -nauto