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

Introduce Config.invocation_params #5564

Merged
merged 4 commits into from
Jul 10, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/5564.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()``.
68 changes: 58 additions & 10 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import types
import warnings
from functools import lru_cache
from pathlib import Path

import attr
import py
from packaging.version import Version
from pluggy import HookimplMarker
Expand Down Expand Up @@ -147,10 +149,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 @@ -183,7 +190,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 @@ -608,20 +615,57 @@ def _iter_rewritable_modules(package_files):


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

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()
: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:
"""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):
from .argparsing import Parser, FILE_OR_DIR

if invocation_params is None:
nicoddemus marked this conversation as resolved.
Show resolved Hide resolved
invocation_params = self.InvocationParams(
args=(), plugins=None, dir=Path().resolve()
)

self.option = argparse.Namespace()
self.invocation_params = invocation_params

_a = FILE_OR_DIR
self._parser = Parser(
usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a),
processopt=self._processopt,
)
#: a pluginmanager instance
self.pluginmanager = pluginmanager
self.trace = self.pluginmanager.trace.root.get("config")
self.hook = self.pluginmanager.hook
Expand All @@ -631,9 +675,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
@@ -1,5 +1,6 @@
import sys
import textwrap
from pathlib import Path

import _pytest._code
import pytest
Expand Down Expand Up @@ -1198,6 +1199,29 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir):
assert result.ret == ExitCode.USAGE_ERROR


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