Skip to content

Add new INI config key 'required_plugins' that defines a list of plugins that must be present for a run of pytest #7330

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

Merged
merged 12 commits into from
Jun 12, 2020
Merged
3 changes: 3 additions & 0 deletions changelog/7305.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
A new INI key `require_plugins` has been added that allows the user to specify a list of plugins required for pytest to run.

The `--strict-config` flag can be used to treat these warnings as errors.
12 changes: 12 additions & 0 deletions doc/en/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,18 @@ passed multiple times. The expected format is ``name=value``. For example::
See :ref:`change naming conventions` for more detailed examples.


.. confval:: require_plugins

A space separated list of plugins that must be present for pytest to run.
If any one of the plugins is not found, emit a warning.
If pytest is run with ``--strict-config`` exceptions are raised in place of warnings.

.. code-block:: ini

[pytest]
require_plugins = pluginA pluginB pluginC


.. confval:: testpaths


Expand Down
28 changes: 22 additions & 6 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,12 @@ def _initini(self, args: Sequence[str]) -> None:
self._parser.extra_info["inifile"] = self.inifile
self._parser.addini("addopts", "extra command line options", "args")
self._parser.addini("minversion", "minimally required pytest version")
self._parser.addini(
"require_plugins",
"plugins that must be present for pytest to run",
type="args",
default=[],
)
self._override_ini = ns.override_ini or ()

def _consider_importhook(self, args: Sequence[str]) -> None:
Expand Down Expand Up @@ -1035,7 +1041,8 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None:
self.known_args_namespace = ns = self._parser.parse_known_args(
args, namespace=copy.copy(self.option)
)
self._validatekeys()
self._validate_keys()
self._validate_plugins()
if self.known_args_namespace.confcutdir is None and self.inifile:
confcutdir = py.path.local(self.inifile).dirname
self.known_args_namespace.confcutdir = confcutdir
Expand Down Expand Up @@ -1078,12 +1085,21 @@ def _checkversion(self):
)
)

def _validatekeys(self):
def _validate_keys(self) -> None:
for key in sorted(self._get_unknown_ini_keys()):
message = "Unknown config ini key: {}\n".format(key)
if self.known_args_namespace.strict_config:
fail(message, pytrace=False)
sys.stderr.write("WARNING: {}".format(message))
self._emit_warning_or_fail("Unknown config ini key: {}\n".format(key))

def _validate_plugins(self) -> None:
for plugin in self.getini("require_plugins"):
if not self.pluginmanager.hasplugin(plugin):
self._emit_warning_or_fail(
"Missing required plugin: {}\n".format(plugin)
)

def _emit_warning_or_fail(self, message: str) -> None:
if self.known_args_namespace.strict_config:
fail(message, pytrace=False)
sys.stderr.write("WARNING: {}".format(message))

def _get_unknown_ini_keys(self) -> List[str]:
parser_inicfg = self._parser._inidict
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def pytest_addoption(parser: Parser) -> None:
group._addoption(
"--strict-config",
action="store_true",
help="invalid ini keys for the `pytest` section of the configuration file raise errors.",
help="any warnings encountered while parsing the `pytest` section of the configuration file raise errors.",
)
group._addoption(
"--strict-markers",
Expand Down
71 changes: 71 additions & 0 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,77 @@ def test_invalid_ini_keys(
with pytest.raises(pytest.fail.Exception, match=exception_text):
testdir.runpytest("--strict-config")

@pytest.mark.parametrize(
"ini_file_text, stderr_output, exception_text",
[
(
"""
[pytest]
require_plugins = fakePlugin1 fakePlugin2
""",
[
"WARNING: Missing required plugin: fakePlugin1",
"WARNING: Missing required plugin: fakePlugin2",
],
"Missing required plugin: fakePlugin1",
),
(
"""
[pytest]
require_plugins = a monkeypatch z
""",
[
"WARNING: Missing required plugin: a",
"WARNING: Missing required plugin: z",
],
"Missing required plugin: a",
),
(
"""
[pytest]
require_plugins = a monkeypatch z
addopts = -p no:monkeypatch
""",
[
"WARNING: Missing required plugin: a",
"WARNING: Missing required plugin: monkeypatch",
"WARNING: Missing required plugin: z",
],
"Missing required plugin: a",
),
(
"""
[some_other_header]
require_plugins = wont be triggered
[pytest]
minversion = 5.0.0
""",
[],
"",
),
(
"""
[pytest]
minversion = 5.0.0
""",
[],
"",
),
],
)
def test_missing_required_plugins(
self, testdir, ini_file_text, stderr_output, exception_text
):
testdir.tmpdir.join("pytest.ini").write(textwrap.dedent(ini_file_text))
testdir.parseconfig()

result = testdir.runpytest()
result.stderr.fnmatch_lines(stderr_output)

if stderr_output:
with pytest.raises(pytest.fail.Exception, match=exception_text):
testdir.runpytest("--strict-config")


class TestConfigCmdlineParsing:
def test_parsing_again_fails(self, testdir):
Expand Down