1
0
mirror of https://github.com/fedora-python/tox-current-env.git synced 2024-12-25 01:26:13 +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 `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`` ``tox --current-env``
Runs the tox testenv's ``commands`` in the current Python environment 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). (if ``tox`` is invoked from an Python 3.7 environment, any non 3.7 testenv will fail).
``tox --print-deps-to=FILE`` ``tox --print-deps-to=FILE``
Instead of running any ``commands``, Instead of running any ``commands``, simply prints the
simply prints the declared dependencies in ``deps`` to the specified ``FILE``. `declared dependencies <https://tox.readthedocs.io/en/latest/config.html#conf-deps>`_
This is useful for preparing the current environment for the above. 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. 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. 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. Any deviation from this behavior is considered a bug.
@ -82,7 +92,10 @@ and ``pip``-installing locally:
Usage 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 .. code-block:: console
@ -125,6 +138,21 @@ To get list of test dependencies, run:
py37: commands succeeded py37: commands succeeded
congratulations :) 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 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 Tox caches the virtualenvs it creates, and doesn't distinguish between
regular virtualenvs and ``--current-env``. regular virtualenvs and ``--current-env``.
Don't mix ``tox --current-env`` or ``tox --print-deps-to`` runs Don't mix ``tox --current-env``, ``tox --print-deps-to`` or ``tox --print-extras-to``
and regular ``tox`` runs (without the flag). 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. 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, 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 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). (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``, 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 " help="Don't run tests, only print the dependencies to the given file "
+ "(use `-` for stdout)", + "(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 @tox.hookimpl
@ -55,11 +65,17 @@ def tox_configure(config):
"--print-deps-only cannot be used together " "--print-deps-only cannot be used together "
+ "with --print-deps-to" + "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 config.skipsdist = True
for testenv in config.envconfigs: for testenv in config.envconfigs:
config.envconfigs[testenv].whitelist_externals = "*" 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 return config
@ -97,16 +113,17 @@ def rm_venv(venv):
def unsupported_raise(config, venv): def unsupported_raise(config, venv):
if config.option.recreate: if config.option.recreate:
return 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 regular and is_current_env_link(venv):
if hasattr(tox.hookspecs, "tox_cleanup"): if hasattr(tox.hookspecs, "tox_cleanup"):
raise tox.exception.ConfigError( 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." "Run tox run with --recreate (-r) or manually remove the environment in .tox."
) )
else: else:
raise tox.exception.ConfigError( 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): elif config.option.current_env and is_proper_venv(venv):
raise tox.exception.ConfigError( raise tox.exception.ConfigError(
@ -119,7 +136,7 @@ def tox_testenv_create(venv, action):
"""We create a fake virtualenv with just the symbolic link""" """We create a fake virtualenv with just the symbolic link"""
config = venv.envconfig.config config = venv.envconfig.config
create_fake_env = check_version = config.option.current_env 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): if is_any_env(venv):
# We don't need anything # We don't need anything
return True return True
@ -130,7 +147,7 @@ def tox_testenv_create(venv, action):
# because it's cheaper, faster and won't install stuff # because it's cheaper, faster and won't install stuff
create_fake_env = True create_fake_env = True
if check_version: 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 version_info = venv.envconfig.python_info.version_info
if version_info is None: if version_info is None:
raise tox.exception.InterpreterNotFound(venv.envconfig.basepython) raise tox.exception.InterpreterNotFound(venv.envconfig.basepython)
@ -178,16 +195,30 @@ def tox_testenv_install_deps(venv, action):
@tox.hookimpl @tox.hookimpl
def tox_runtest(venv, redirect): 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 config = venv.envconfig.config
unsupported_raise(config, venv) unsupported_raise(config, venv)
ret = None
if config.option.print_deps_to: if config.option.print_deps_to:
print( print(
*venv.get_resolved_dependencies(), *venv.get_resolved_dependencies(),
sep="\n", sep="\n",
file=config.option.print_deps_to, 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 @tox.hookimpl

View File

@ -1,3 +1,9 @@
from setuptools import setup 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 = deps =
six six
py py
extras =
dev
full
commands = commands =
python -c 'import os, sys; print(os.path.realpath(sys.exec_prefix), "is the exec_prefix")' 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 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): def tox(*args, quiet=True, **kwargs):
kwargs.setdefault("encoding", "utf-8") kwargs.setdefault("encoding", "utf-8")
kwargs.setdefault("stdout", subprocess.PIPE) kwargs.setdefault("stdout", subprocess.PIPE)
@ -121,6 +127,21 @@ def test_print_deps(toxenv, print_deps_stdout_arg):
assert result.stdout == expected 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"]) @pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
def test_print_deps_only_deprecated(toxenv): def test_print_deps_only_deprecated(toxenv):
result = tox( result = tox(
@ -157,6 +178,29 @@ def test_allenvs_print_deps(print_deps_stdout_arg):
assert result.stdout == expected 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"]) @pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
def test_print_deps_to_file(toxenv, tmp_path): def test_print_deps_to_file(toxenv, tmp_path):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
@ -172,6 +216,21 @@ def test_print_deps_to_file(toxenv, tmp_path):
assert result.stdout == expected 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')) @pytest.mark.parametrize('option', ('--print-deps-to', '--print-deps-to-file'))
def test_allenvs_print_deps_to_file(tmp_path, option): def test_allenvs_print_deps_to_file(tmp_path, option):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
@ -190,6 +249,24 @@ def test_allenvs_print_deps_to_file(tmp_path, option):
assert result.stdout == expected 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): def test_allenvs_print_deps_to_existing_file(tmp_path):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
depspath.write_text("nada") depspath.write_text("nada")
@ -200,6 +277,75 @@ def test_allenvs_print_deps_to_existing_file(tmp_path):
assert "py" in lines 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(): def test_print_deps_only_print_deps_to_file_are_mutually_exclusive():
result = tox( result = tox(
"-e", "-e",
@ -319,6 +465,16 @@ def test_current_after_print_deps(print_deps_stdout_arg):
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG 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): def test_regular_recreate_after_print_deps(print_deps_stdout_arg):
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg) result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
assert "bin/python" not in result.stdout assert "bin/python" not in result.stdout