Skip to content

Commit 602cd5e

Browse files
authored
Introduce Config.invocation_params (#5564)
Introduce Config.invocation_params
2 parents 3173815 + 7a82285 commit 602cd5e

File tree

3 files changed

+83
-10
lines changed

3 files changed

+83
-10
lines changed

changelog/5564.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.

src/_pytest/config/__init__.py

+58-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import types
99
import warnings
1010
from functools import lru_cache
11+
from pathlib import Path
1112

13+
import attr
1214
import py
1315
from packaging.version import Version
1416
from pluggy import HookimplMarker
@@ -148,10 +150,15 @@ def directory_arg(path, optname):
148150
builtin_plugins.add("pytester")
149151

150152

151-
def get_config(args=None):
153+
def get_config(args=None, plugins=None):
152154
# subsequent calls to main will create a fresh instance
153155
pluginmanager = PytestPluginManager()
154-
config = Config(pluginmanager)
156+
config = Config(
157+
pluginmanager,
158+
invocation_params=Config.InvocationParams(
159+
args=args, plugins=plugins, dir=Path().resolve()
160+
),
161+
)
155162

156163
if args is not None:
157164
# Handle any "-p no:plugin" args.
@@ -184,7 +191,7 @@ def _prepareconfig(args=None, plugins=None):
184191
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
185192
raise TypeError(msg.format(args, type(args)))
186193

187-
config = get_config(args)
194+
config = get_config(args, plugins)
188195
pluginmanager = config.pluginmanager
189196
try:
190197
if plugins:
@@ -613,20 +620,57 @@ def _iter_rewritable_modules(package_files):
613620

614621

615622
class Config:
616-
""" access to configuration values, pluginmanager and plugin hooks. """
623+
"""
624+
Access to configuration values, pluginmanager and plugin hooks.
617625
618-
def __init__(self, pluginmanager):
619-
#: access to command line option as attributes.
620-
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
621-
self.option = argparse.Namespace()
626+
:ivar PytestPluginManager pluginmanager: the plugin manager handles plugin registration and hook invocation.
627+
628+
:ivar argparse.Namespace option: access to command line option as attributes.
629+
630+
:ivar InvocationParams invocation_params:
631+
632+
Object containing the parameters regarding the ``pytest.main``
633+
invocation.
634+
635+
Contains the followinig read-only attributes:
636+
637+
* ``args``: list of command-line arguments as passed to ``pytest.main()``.
638+
* ``plugins``: list of extra plugins, might be None
639+
* ``dir``: directory where ``pytest.main()`` was invoked from.
640+
"""
641+
642+
@attr.s(frozen=True)
643+
class InvocationParams:
644+
"""Holds parameters passed during ``pytest.main()``
645+
646+
.. note::
647+
648+
Currently the environment variable PYTEST_ADDOPTS is also handled by
649+
pytest implicitly, not being part of the invocation.
650+
651+
Plugins accessing ``InvocationParams`` must be aware of that.
652+
"""
653+
654+
args = attr.ib()
655+
plugins = attr.ib()
656+
dir = attr.ib()
657+
658+
def __init__(self, pluginmanager, *, invocation_params=None):
622659
from .argparsing import Parser, FILE_OR_DIR
623660

661+
if invocation_params is None:
662+
invocation_params = self.InvocationParams(
663+
args=(), plugins=None, dir=Path().resolve()
664+
)
665+
666+
self.option = argparse.Namespace()
667+
self.invocation_params = invocation_params
668+
624669
_a = FILE_OR_DIR
625670
self._parser = Parser(
626671
usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a),
627672
processopt=self._processopt,
628673
)
629-
#: a pluginmanager instance
630674
self.pluginmanager = pluginmanager
631675
self.trace = self.pluginmanager.trace.root.get("config")
632676
self.hook = self.pluginmanager.hook
@@ -636,9 +680,13 @@ def __init__(self, pluginmanager):
636680
self._cleanup = []
637681
self.pluginmanager.register(self, "pytestconfig")
638682
self._configured = False
639-
self.invocation_dir = py.path.local()
640683
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
641684

685+
@property
686+
def invocation_dir(self):
687+
"""Backward compatibility"""
688+
return py.path.local(str(self.invocation_params.dir))
689+
642690
def add_cleanup(self, func):
643691
""" Add a function to be called when the config object gets out of
644692
use (usually coninciding with pytest_unconfigure)."""

testing/test_config.py

+24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import sys
33
import textwrap
4+
from pathlib import Path
45

56
import _pytest._code
67
import pytest
@@ -1199,6 +1200,29 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir):
11991200
assert result.ret == ExitCode.USAGE_ERROR
12001201

12011202

1203+
def test_invocation_args(testdir):
1204+
"""Ensure that Config.invocation_* arguments are correctly defined"""
1205+
1206+
class DummyPlugin:
1207+
pass
1208+
1209+
p = testdir.makepyfile("def test(): pass")
1210+
plugin = DummyPlugin()
1211+
rec = testdir.inline_run(p, "-v", plugins=[plugin])
1212+
calls = rec.getcalls("pytest_runtest_protocol")
1213+
assert len(calls) == 1
1214+
call = calls[0]
1215+
config = call.item.config
1216+
1217+
assert config.invocation_params.args == [p, "-v"]
1218+
assert config.invocation_params.dir == Path(str(testdir.tmpdir))
1219+
1220+
plugins = config.invocation_params.plugins
1221+
assert len(plugins) == 2
1222+
assert plugins[0] is plugin
1223+
assert type(plugins[1]).__name__ == "Collect" # installed by testdir.inline_run()
1224+
1225+
12021226
@pytest.mark.parametrize(
12031227
"plugin",
12041228
[

0 commit comments

Comments
 (0)