1
0
mirror of https://github.com/fedora-python/tox-current-env.git synced 2024-12-24 09:06:15 +01:00

Add the --print-extras-to(-file) option

This commit is contained in:
Miro Hrončok 2020-09-11 15:20:03 +02:00
parent 6feb98352a
commit 6dd32b21ce
5 changed files with 241 additions and 17 deletions

View File

@ -5,7 +5,7 @@ tox-current-env
`tox <https://tox.readthedocs.io/>`_ plugin to run tests in current Python environment
---------------------------------------------------------------------------------------
The ``tox-current-env`` plugin adds two options:
The ``tox-current-env`` plugin adds these options:
``tox --current-env``
Runs the tox testenv's ``commands`` in the current Python environment
@ -15,11 +15,21 @@ The ``tox-current-env`` plugin adds two options:
(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 the above.
Instead of running any ``commands``, simply prints the
`declared dependencies <https://tox.readthedocs.io/en/latest/config.html#conf-deps>`_
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 <https://tox.readthedocs.io/en/latest/config.html#conf-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.
It is possible to use the two 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.
@ -82,7 +92,10 @@ and ``pip``-installing locally:
Usage
-----
When the plugin is installed, use ``tox`` with ``--current-env`` or ``--print-deps-to`` and all the other options as usual. Assuming your ``tox`` is installed on Python 3.7:
When the plugin is installed,
use ``tox`` with ``--current-env``, ``--print-deps-to`` or ``--print-extras-to``
and all the other options as usual.
Assuming your ``tox`` is installed on Python 3.7:
.. code-block:: console
@ -125,6 +138,21 @@ To get list of test dependencies, run:
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 :)
Caveats, warnings and limitations
---------------------------------
@ -159,8 +187,8 @@ 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`` or ``tox --print-deps-to`` runs
and regular ``tox`` runs (without the flag).
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,
@ -183,7 +211,7 @@ Read `the documentation for passing environment variables to tox
Other limitations and known bugs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``installed:`` line in the output of ``tox --print-deps-to`` shows irrelevant output
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 <https://docs.python.org/3/using/cmdline.html>`_ used in the shebang of ``tox``,

View File

@ -38,6 +38,16 @@ def tox_addoption(parser):
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)",
)
@tox.hookimpl
@ -55,11 +65,17 @@ def tox_configure(config):
"--print-deps-only cannot be used together "
+ "with --print-deps-to"
)
if config.option.current_env or config.option.print_deps_to:
if config.option.current_env or config.option.print_deps_to or config.option.print_extras_to:
config.skipsdist = True
for testenv in config.envconfigs:
config.envconfigs[testenv].whitelist_externals = "*"
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
@ -97,16 +113,17 @@ def rm_venv(venv):
def unsupported_raise(config, venv):
if config.option.recreate:
return
regular = not (config.option.current_env or config.option.print_deps_to)
regular = not (config.option.current_env or config.option.print_deps_to or config.option.print_extras_to)
if regular and is_current_env_link(venv):
if hasattr(tox.hookspecs, "tox_cleanup"):
raise tox.exception.ConfigError(
"Looks like previous --current-env or --print-deps-to tox run didn't finish the cleanup. "
"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 or --print-deps-to tox run is not supported without --recreate (-r)."
"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(
@ -119,7 +136,7 @@ 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:
if config.option.print_deps_to or config.option.print_extras_to:
if is_any_env(venv):
# We don't need anything
return True
@ -130,7 +147,7 @@ def tox_testenv_create(venv, action):
# 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-to only
# 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)
@ -178,16 +195,30 @@ def tox_testenv_install_deps(venv, action):
@tox.hookimpl
def tox_runtest(venv, redirect):
"""If --print-deps-to, prints deps instead of running tests"""
"""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(
*venv.get_resolved_dependencies(),
sep="\n",
file=config.option.print_deps_to,
)
return True
ret = True
if config.option.print_extras_to:
print(
*venv.envconfig.extras,
sep="\n",
file=config.option.print_extras_to,
)
ret = True
return ret
@tox.hookimpl

View File

@ -1,3 +1,9 @@
from setuptools import setup
setup(name="test")
setup(
name="test",
extras_require={
"dev": [],
"full": [],
}
)

View File

@ -5,5 +5,8 @@ envlist = py36,py37,py38,py39
deps =
six
py
extras =
dev
full
commands =
python -c 'import os, sys; print(os.path.realpath(sys.exec_prefix), "is the exec_prefix")'

View File

@ -36,6 +36,12 @@ def print_deps_stdout_arg(request):
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
def tox(*args, quiet=True, **kwargs):
kwargs.setdefault("encoding", "utf-8")
kwargs.setdefault("stdout", subprocess.PIPE)
@ -121,6 +127,21 @@ def test_print_deps(toxenv, print_deps_stdout_arg):
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
def test_print_extras(toxenv, print_extras_stdout_arg):
result = tox("-e", toxenv, print_extras_stdout_arg)
expected = textwrap.dedent(
f"""
dev
full
___________________________________ summary ____________________________________
{toxenv}: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
def test_print_deps_only_deprecated(toxenv):
result = tox(
@ -157,6 +178,29 @@ def test_allenvs_print_deps(print_deps_stdout_arg):
assert result.stdout == expected
def test_allenvs_print_extras(print_extras_stdout_arg):
result = tox(print_extras_stdout_arg)
expected = textwrap.dedent(
"""
dev
full
dev
full
dev
full
dev
full
___________________________________ summary ____________________________________
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
def test_print_deps_to_file(toxenv, tmp_path):
depspath = tmp_path / "deps"
@ -172,6 +216,21 @@ def test_print_deps_to_file(toxenv, tmp_path):
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
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"""
___________________________________ summary ____________________________________
{toxenv}: commands succeeded
congratulations :)
"""
).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"
@ -190,6 +249,24 @@ def test_allenvs_print_deps_to_file(tmp_path, option):
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"] * 4
expected = textwrap.dedent(
"""
___________________________________ summary ____________________________________
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
def test_allenvs_print_deps_to_existing_file(tmp_path):
depspath = tmp_path / "deps"
depspath.write_text("nada")
@ -200,6 +277,75 @@ def test_allenvs_print_deps_to_existing_file(tmp_path):
assert "py" in lines
def test_allenvs_print_extras_to_existing_file(tmp_path):
extraspath = tmp_path / "extras"
extraspath.write_text("nada")
result = 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",
@ -319,6 +465,16 @@ def test_current_after_print_deps(print_deps_stdout_arg):
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