Files
pytest2/testing/test_pluginmanager.py
Bruno Oliveira 93d2ccbfb7 Point to stable docs instead of latest
Now that our master might contain new features, it is best to point
users to the stable docs rather than the latest
2020-07-07 07:45:27 -03:00

386 lines
14 KiB
Python

import os
import sys
import types
from typing import List
import pytest
from _pytest.config import ExitCode
from _pytest.config import PytestPluginManager
from _pytest.config.exceptions import UsageError
from _pytest.main import Session
@pytest.fixture
def pytestpm() -> PytestPluginManager:
return PytestPluginManager()
class TestPytestPluginInteractions:
def test_addhooks_conftestplugin(self, testdir, _config_for_test):
testdir.makepyfile(
newhooks="""
def pytest_myhook(xyz):
"new hook"
"""
)
conf = testdir.makeconftest(
"""
import newhooks
def pytest_addhooks(pluginmanager):
pluginmanager.add_hookspecs(newhooks)
def pytest_myhook(xyz):
return xyz + 1
"""
)
config = _config_for_test
pm = config.pluginmanager
pm.hook.pytest_addhooks.call_historic(
kwargs=dict(pluginmanager=config.pluginmanager)
)
config.pluginmanager._importconftest(conf, importmode="prepend")
# print(config.pluginmanager.get_plugins())
res = config.hook.pytest_myhook(xyz=10)
assert res == [11]
def test_addhooks_nohooks(self, testdir):
testdir.makeconftest(
"""
import sys
def pytest_addhooks(pluginmanager):
pluginmanager.add_hookspecs(sys)
"""
)
res = testdir.runpytest()
assert res.ret != 0
res.stderr.fnmatch_lines(["*did not find*sys*"])
def test_do_option_postinitialize(self, testdir):
config = testdir.parseconfigure()
assert not hasattr(config.option, "test123")
p = testdir.makepyfile(
"""
def pytest_addoption(parser):
parser.addoption('--test123', action="store_true",
default=True)
"""
)
config.pluginmanager._importconftest(p, importmode="prepend")
assert config.option.test123
def test_configure(self, testdir):
config = testdir.parseconfig()
values = []
class A:
def pytest_configure(self):
values.append(self)
config.pluginmanager.register(A())
assert len(values) == 0
config._do_configure()
assert len(values) == 1
config.pluginmanager.register(A()) # leads to a configured() plugin
assert len(values) == 2
assert values[0] != values[1]
config._ensure_unconfigure()
config.pluginmanager.register(A())
assert len(values) == 2
def test_hook_tracing(self, _config_for_test) -> None:
pytestpm = _config_for_test.pluginmanager # fully initialized with plugins
saveindent = []
class api1:
def pytest_plugin_registered(self):
saveindent.append(pytestpm.trace.root.indent)
class api2:
def pytest_plugin_registered(self):
saveindent.append(pytestpm.trace.root.indent)
raise ValueError()
values = [] # type: List[str]
pytestpm.trace.root.setwriter(values.append)
undo = pytestpm.enable_tracing()
try:
indent = pytestpm.trace.root.indent
p = api1()
pytestpm.register(p)
assert pytestpm.trace.root.indent == indent
assert len(values) >= 2
assert "pytest_plugin_registered" in values[0]
assert "finish" in values[1]
values[:] = []
with pytest.raises(ValueError):
pytestpm.register(api2())
assert pytestpm.trace.root.indent == indent
assert saveindent[0] > indent
finally:
undo()
def test_hook_proxy(self, testdir):
"""Test the gethookproxy function(#2016)"""
config = testdir.parseconfig()
session = Session.from_config(config)
testdir.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""})
conftest1 = testdir.tmpdir.join("tests/conftest.py")
conftest2 = testdir.tmpdir.join("tests/subdir/conftest.py")
config.pluginmanager._importconftest(conftest1, importmode="prepend")
ihook_a = session.gethookproxy(testdir.tmpdir.join("tests"))
assert ihook_a is not None
config.pluginmanager._importconftest(conftest2, importmode="prepend")
ihook_b = session.gethookproxy(testdir.tmpdir.join("tests"))
assert ihook_a is not ihook_b
def test_hook_with_addoption(self, testdir):
"""Test that hooks can be used in a call to pytest_addoption"""
testdir.makepyfile(
newhooks="""
import pytest
@pytest.hookspec(firstresult=True)
def pytest_default_value():
pass
"""
)
testdir.makepyfile(
myplugin="""
import newhooks
def pytest_addhooks(pluginmanager):
pluginmanager.add_hookspecs(newhooks)
def pytest_addoption(parser, pluginmanager):
default_value = pluginmanager.hook.pytest_default_value()
parser.addoption("--config", help="Config, defaults to %(default)s", default=default_value)
"""
)
testdir.makeconftest(
"""
pytest_plugins=("myplugin",)
def pytest_default_value():
return "default_value"
"""
)
res = testdir.runpytest("--help")
res.stdout.fnmatch_lines(["*--config=CONFIG*default_value*"])
def test_default_markers(testdir):
result = testdir.runpytest("--markers")
result.stdout.fnmatch_lines(["*tryfirst*first*", "*trylast*last*"])
def test_importplugin_error_message(testdir, pytestpm):
"""Don't hide import errors when importing plugins and provide
an easy to debug message.
See #375 and #1998.
"""
testdir.syspathinsert(testdir.tmpdir)
testdir.makepyfile(
qwe="""\
def test_traceback():
raise ImportError('Not possible to import: ☺')
test_traceback()
"""
)
with pytest.raises(ImportError) as excinfo:
pytestpm.import_plugin("qwe")
assert str(excinfo.value).endswith(
'Error importing plugin "qwe": Not possible to import: ☺'
)
assert "in test_traceback" in str(excinfo.traceback[-1])
class TestPytestPluginManager:
def test_register_imported_modules(self):
pm = PytestPluginManager()
mod = types.ModuleType("x.y.pytest_hello")
pm.register(mod)
assert pm.is_registered(mod)
values = pm.get_plugins()
assert mod in values
pytest.raises(ValueError, pm.register, mod)
pytest.raises(ValueError, lambda: pm.register(mod))
# assert not pm.is_registered(mod2)
assert pm.get_plugins() == values
def test_canonical_import(self, monkeypatch):
mod = types.ModuleType("pytest_xyz")
monkeypatch.setitem(sys.modules, "pytest_xyz", mod)
pm = PytestPluginManager()
pm.import_plugin("pytest_xyz")
assert pm.get_plugin("pytest_xyz") == mod
assert pm.is_registered(mod)
def test_consider_module(self, testdir, pytestpm: PytestPluginManager) -> None:
testdir.syspathinsert()
testdir.makepyfile(pytest_p1="#")
testdir.makepyfile(pytest_p2="#")
mod = types.ModuleType("temp")
mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"]
pytestpm.consider_module(mod)
assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
def test_consider_module_import_module(self, testdir, _config_for_test) -> None:
pytestpm = _config_for_test.pluginmanager
mod = types.ModuleType("x")
mod.__dict__["pytest_plugins"] = "pytest_a"
aplugin = testdir.makepyfile(pytest_a="#")
reprec = testdir.make_hook_recorder(pytestpm)
testdir.syspathinsert(aplugin.dirpath())
pytestpm.consider_module(mod)
call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
assert call.plugin.__name__ == "pytest_a"
# check that it is not registered twice
pytestpm.consider_module(mod)
values = reprec.getcalls("pytest_plugin_registered")
assert len(values) == 1
def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
monkeypatch.setenv("PYTEST_PLUGINS", "nonexisting", prepend=",")
with pytest.raises(ImportError):
pytestpm.consider_env()
@pytest.mark.filterwarnings("always")
def test_plugin_skip(self, testdir, monkeypatch):
p = testdir.makepyfile(
skipping1="""
import pytest
pytest.skip("hello", allow_module_level=True)
"""
)
p.copy(p.dirpath("skipping2.py"))
monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
result = testdir.runpytest("-p", "skipping1", syspathinsert=True)
assert result.ret == ExitCode.NO_TESTS_COLLECTED
result.stdout.fnmatch_lines(
["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"]
)
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
testdir.syspathinsert()
testdir.makepyfile(xy123="#")
monkeypatch.setitem(os.environ, "PYTEST_PLUGINS", "xy123")
l1 = len(pytestpm.get_plugins())
pytestpm.consider_env()
l2 = len(pytestpm.get_plugins())
assert l2 == l1 + 1
assert pytestpm.get_plugin("xy123")
pytestpm.consider_env()
l3 = len(pytestpm.get_plugins())
assert l2 == l3
def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
testdir.makepyfile(pytest_x500="#")
p = testdir.makepyfile(
"""
import pytest
def test_hello(pytestconfig):
plugin = pytestconfig.pluginmanager.get_plugin('pytest_x500')
assert plugin is not None
"""
)
monkeypatch.setenv("PYTEST_PLUGINS", "pytest_x500", prepend=",")
result = testdir.runpytest(p, syspathinsert=True)
assert result.ret == 0
result.stdout.fnmatch_lines(["*1 passed*"])
def test_import_plugin_importname(self, testdir, pytestpm):
pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y")
testdir.syspathinsert()
pluginname = "pytest_hello"
testdir.makepyfile(**{pluginname: ""})
pytestpm.import_plugin("pytest_hello")
len1 = len(pytestpm.get_plugins())
pytestpm.import_plugin("pytest_hello")
len2 = len(pytestpm.get_plugins())
assert len1 == len2
plugin1 = pytestpm.get_plugin("pytest_hello")
assert plugin1.__name__.endswith("pytest_hello")
plugin2 = pytestpm.get_plugin("pytest_hello")
assert plugin2 is plugin1
def test_import_plugin_dotted_name(self, testdir, pytestpm):
pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y")
testdir.syspathinsert()
testdir.mkpydir("pkg").join("plug.py").write("x=3")
pluginname = "pkg.plug"
pytestpm.import_plugin(pluginname)
mod = pytestpm.get_plugin("pkg.plug")
assert mod.x == 3
def test_consider_conftest_deps(self, testdir, pytestpm):
mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
with pytest.raises(ImportError):
pytestpm.consider_conftest(mod)
class TestPytestPluginManagerBootstrapming:
def test_preparse_args(self, pytestpm):
pytest.raises(
ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"])
)
# Handles -p without space (#3532).
with pytest.raises(ImportError) as excinfo:
pytestpm.consider_preparse(["-phello123"])
assert '"hello123"' in excinfo.value.args[0]
pytestpm.consider_preparse(["-pno:hello123"])
# Handles -p without following arg (when used without argparse).
pytestpm.consider_preparse(["-p"])
with pytest.raises(UsageError, match="^plugin main cannot be disabled$"):
pytestpm.consider_preparse(["-p", "no:main"])
def test_plugin_prevent_register(self, pytestpm):
pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
l1 = pytestpm.get_plugins()
pytestpm.register(42, name="abc")
l2 = pytestpm.get_plugins()
assert len(l2) == len(l1)
assert 42 not in l2
def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
pytestpm.register(42, name="abc")
l1 = pytestpm.get_plugins()
assert 42 in l1
pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
l2 = pytestpm.get_plugins()
assert 42 not in l2
def test_plugin_prevent_register_stepwise_on_cacheprovider_unregister(
self, pytestpm
):
""" From PR #4304 : The only way to unregister a module is documented at
the end of https://docs.pytest.org/en/stable/plugins.html.
When unregister cacheprovider, then unregister stepwise too
"""
pytestpm.register(42, name="cacheprovider")
pytestpm.register(43, name="stepwise")
l1 = pytestpm.get_plugins()
assert 42 in l1
assert 43 in l1
pytestpm.consider_preparse(["xyz", "-p", "no:cacheprovider"])
l2 = pytestpm.get_plugins()
assert 42 not in l2
assert 43 not in l2
def test_blocked_plugin_can_be_used(self, pytestpm):
pytestpm.consider_preparse(["xyz", "-p", "no:abc", "-p", "abc"])
assert pytestpm.has_plugin("abc")
assert not pytestpm.is_blocked("abc")
assert not pytestpm.is_blocked("pytest_abc")