Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BACKPORT: Introduction of Config.invocation_args #6870

Merged
merged 1 commit into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Evan Kepner
Fabien Zarifian
Fabio Zadrozny
Feng Ma
Fernando Mezzabotta Rey
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this the thing to do when back-porting things. If it's not, let me know! Just following the steps-to-contribute 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is definitely the right thing! 👍

Florian Bruhin
Floris Bruynooghe
Gabriel Reis
Expand Down
1 change: 1 addition & 0 deletions changelog/6870.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a remark here:

Suggested change
New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
Remark: while this is technically a new feature and according to our `policy <https://docs.pytest.org/en/latest/py27-py34-deprecation.html#what-goes-into-4-6-x-releases>`_ it should not have been backported, we have opened an exception in this particular case because it fixes a serious interaction with ``pytest-xdist``, so it can also be considered a bugfix.

@asottile what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 makes it much more clear that this is one-off and not a greenlight to resume features on 4.6

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remark added, it makes a lot of sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh just noticed this was not added in the end.

No worries, I will update directly on #7199. 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I committed it locally and never pushed 🤦. Sorry about that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem 😁

4.6.10 has been released btw! 👍

4 changes: 2 additions & 2 deletions doc/en/py27-py34-deprecation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ are available on PyPI.

While pytest ``5.0`` will be the new mainstream and development version, until **January 2020**
the pytest core team plans to make bug-fix releases of the pytest ``4.6`` series by
back-porting patches to the ``4.6-maintenance`` branch that affect Python 2 users.
back-porting patches to the ``4.6.x`` branch that affect Python 2 users.
Copy link
Author

@fermezz fermezz Mar 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is not an actual 4.6-maintenance branch, I changed it for what I believe is correct. This should probably be in master as well. I can make that happen if I get confirmation that that's ok.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to port to master, we have changed that significantly already: https://docs.pytest.org/en/latest/py27-py34-deprecation.html


**After 2020**, the core team will no longer actively backport patches, but the ``4.6-maintenance``
**After 2020**, the core team will no longer actively backport patches, but the ``4.6.x``
branch will continue to exist so the community itself can contribute patches. The core team will
be happy to accept those patches and make new ``4.6`` releases **until mid-2020**.

Expand Down
64 changes: 57 additions & 7 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import types
import warnings

import attr
import py
import six
from packaging.version import Version
Expand All @@ -35,6 +36,7 @@
from _pytest.compat import safe_str
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.pathlib import Path
from _pytest.warning_types import PytestConfigWarning

hookimpl = HookimplMarker("pytest")
Expand Down Expand Up @@ -154,10 +156,15 @@ def directory_arg(path, optname):
builtin_plugins.add("pytester")


def get_config(args=None):
def get_config(args=None, plugins=None):
# subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager()
config = Config(pluginmanager)
config = Config(
pluginmanager,
invocation_params=Config.InvocationParams(
args=args, plugins=plugins, dir=Path().resolve()
),
)

if args is not None:
# Handle any "-p no:plugin" args.
Expand Down Expand Up @@ -190,7 +197,7 @@ def _prepareconfig(args=None, plugins=None):
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
raise TypeError(msg.format(args, type(args)))

config = get_config(args)
config = get_config(args, plugins)
pluginmanager = config.pluginmanager
try:
if plugins:
Expand Down Expand Up @@ -686,13 +693,52 @@ def _iter_rewritable_modules(package_files):


class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
"""
Access to configuration values, pluginmanager and plugin hooks.

:ivar PytestPluginManager pluginmanager: the plugin manager handles plugin registration and hook invocation.

:ivar argparse.Namespace option: access to command line option as attributes.

:ivar InvocationParams invocation_params:

Object containing the parameters regarding the ``pytest.main``
invocation.
Contains the followinig read-only attributes:
* ``args``: list of command-line arguments as passed to ``pytest.main()``.
* ``plugins``: list of extra plugins, might be None
* ``dir``: directory where ``pytest.main()`` was invoked from.
"""

@attr.s(frozen=True)
class InvocationParams(object):
"""Holds parameters passed during ``pytest.main()``

.. note::

Currently the environment variable PYTEST_ADDOPTS is also handled by
pytest implicitly, not being part of the invocation.

Plugins accessing ``InvocationParams`` must be aware of that.
"""

args = attr.ib()
plugins = attr.ib()
dir = attr.ib()

def __init__(self, pluginmanager, invocation_params=None, *args):
from .argparsing import Parser, FILE_OR_DIR

if invocation_params is None:
invocation_params = self.InvocationParams(
args=(), plugins=None, dir=Path().resolve()
)

def __init__(self, pluginmanager):
#: access to command line option as attributes.
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
self.option = argparse.Namespace()
from .argparsing import Parser, FILE_OR_DIR

self.invocation_params = invocation_params

_a = FILE_OR_DIR
self._parser = Parser(
Expand All @@ -709,9 +755,13 @@ def __init__(self, pluginmanager):
self._cleanup = []
self.pluginmanager.register(self, "pytestconfig")
self._configured = False
self.invocation_dir = py.path.local()
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))

@property
def invocation_dir(self):
"""Backward compatibility"""
return py.path.local(str(self.invocation_params.dir))

def add_cleanup(self, func):
""" Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure)."""
Expand Down
24 changes: 24 additions & 0 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from _pytest.main import EXIT_OK
from _pytest.main import EXIT_TESTSFAILED
from _pytest.main import EXIT_USAGEERROR
from _pytest.pathlib import Path


class TestParseIni(object):
Expand Down Expand Up @@ -1222,6 +1223,29 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir):
assert result.ret == EXIT_USAGEERROR


def test_invocation_args(testdir):
"""Ensure that Config.invocation_* arguments are correctly defined"""

class DummyPlugin(object):
pass

p = testdir.makepyfile("def test(): pass")
plugin = DummyPlugin()
rec = testdir.inline_run(p, "-v", plugins=[plugin])
calls = rec.getcalls("pytest_runtest_protocol")
assert len(calls) == 1
call = calls[0]
config = call.item.config

assert config.invocation_params.args == [p, "-v"]
assert config.invocation_params.dir == Path(str(testdir.tmpdir))

plugins = config.invocation_params.plugins
assert len(plugins) == 2
assert plugins[0] is plugin
assert type(plugins[1]).__name__ == "Collect" # installed by testdir.inline_run()


@pytest.mark.parametrize(
"plugin",
[
Expand Down