Skip to content

Commit

Permalink
BACKPORT: Introduction of Config.invocation_args
Browse files Browse the repository at this point in the history
  • Loading branch information
fermezz committed Mar 8, 2020
1 parent f606fef commit fa20313
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 9 deletions.
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
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()``.
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.

**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:
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

0 comments on commit fa20313

Please sign in to comment.