1# encoding: UTF-8
2from __future__ import absolute_import, division, print_function
3import pytest
4import os
5import re
6import sys
7import types
8
9from _pytest.config import get_config, PytestPluginManager
10from _pytest.main import EXIT_NOTESTSCOLLECTED, Session
11
12
13@pytest.fixture
14def pytestpm():
15    return PytestPluginManager()
16
17
18class TestPytestPluginInteractions(object):
19
20    def test_addhooks_conftestplugin(self, testdir):
21        testdir.makepyfile(
22            newhooks="""
23            def pytest_myhook(xyz):
24                "new hook"
25        """
26        )
27        conf = testdir.makeconftest(
28            """
29            import sys ; sys.path.insert(0, '.')
30            import newhooks
31            def pytest_addhooks(pluginmanager):
32                pluginmanager.addhooks(newhooks)
33            def pytest_myhook(xyz):
34                return xyz + 1
35        """
36        )
37        config = get_config()
38        pm = config.pluginmanager
39        pm.hook.pytest_addhooks.call_historic(
40            kwargs=dict(pluginmanager=config.pluginmanager)
41        )
42        config.pluginmanager._importconftest(conf)
43        # print(config.pluginmanager.get_plugins())
44        res = config.hook.pytest_myhook(xyz=10)
45        assert res == [11]
46
47    def test_addhooks_nohooks(self, testdir):
48        testdir.makeconftest(
49            """
50            import sys
51            def pytest_addhooks(pluginmanager):
52                pluginmanager.addhooks(sys)
53        """
54        )
55        res = testdir.runpytest()
56        assert res.ret != 0
57        res.stderr.fnmatch_lines(["*did not find*sys*"])
58
59    def test_namespace_early_from_import(self, testdir):
60        p = testdir.makepyfile(
61            """
62            from pytest import Item
63            from pytest import Item as Item2
64            assert Item is Item2
65        """
66        )
67        result = testdir.runpython(p)
68        assert result.ret == 0
69
70    def test_do_ext_namespace(self, testdir):
71        testdir.makeconftest(
72            """
73            def pytest_namespace():
74                return {'hello': 'world'}
75        """
76        )
77        p = testdir.makepyfile(
78            """
79            from pytest import hello
80            import pytest
81            def test_hello():
82                assert hello == "world"
83                assert 'hello' in pytest.__all__
84        """
85        )
86        reprec = testdir.inline_run(p)
87        reprec.assertoutcome(passed=1)
88
89    def test_do_option_postinitialize(self, testdir):
90        config = testdir.parseconfigure()
91        assert not hasattr(config.option, "test123")
92        p = testdir.makepyfile(
93            """
94            def pytest_addoption(parser):
95                parser.addoption('--test123', action="store_true",
96                    default=True)
97        """
98        )
99        config.pluginmanager._importconftest(p)
100        assert config.option.test123
101
102    def test_configure(self, testdir):
103        config = testdir.parseconfig()
104        values = []
105
106        class A(object):
107
108            def pytest_configure(self, config):
109                values.append(self)
110
111        config.pluginmanager.register(A())
112        assert len(values) == 0
113        config._do_configure()
114        assert len(values) == 1
115        config.pluginmanager.register(A())  # leads to a configured() plugin
116        assert len(values) == 2
117        assert values[0] != values[1]
118
119        config._ensure_unconfigure()
120        config.pluginmanager.register(A())
121        assert len(values) == 2
122
123    def test_hook_tracing(self):
124        pytestpm = get_config().pluginmanager  # fully initialized with plugins
125        saveindent = []
126
127        class api1(object):
128
129            def pytest_plugin_registered(self):
130                saveindent.append(pytestpm.trace.root.indent)
131
132        class api2(object):
133
134            def pytest_plugin_registered(self):
135                saveindent.append(pytestpm.trace.root.indent)
136                raise ValueError()
137
138        values = []
139        pytestpm.trace.root.setwriter(values.append)
140        undo = pytestpm.enable_tracing()
141        try:
142            indent = pytestpm.trace.root.indent
143            p = api1()
144            pytestpm.register(p)
145            assert pytestpm.trace.root.indent == indent
146            assert len(values) >= 2
147            assert "pytest_plugin_registered" in values[0]
148            assert "finish" in values[1]
149
150            values[:] = []
151            with pytest.raises(ValueError):
152                pytestpm.register(api2())
153            assert pytestpm.trace.root.indent == indent
154            assert saveindent[0] > indent
155        finally:
156            undo()
157
158    def test_hook_proxy(self, testdir):
159        """Test the gethookproxy function(#2016)"""
160        config = testdir.parseconfig()
161        session = Session(config)
162        testdir.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""})
163
164        conftest1 = testdir.tmpdir.join("tests/conftest.py")
165        conftest2 = testdir.tmpdir.join("tests/subdir/conftest.py")
166
167        config.pluginmanager._importconftest(conftest1)
168        ihook_a = session.gethookproxy(testdir.tmpdir.join("tests"))
169        assert ihook_a is not None
170        config.pluginmanager._importconftest(conftest2)
171        ihook_b = session.gethookproxy(testdir.tmpdir.join("tests"))
172        assert ihook_a is not ihook_b
173
174    def test_warn_on_deprecated_addhooks(self, pytestpm):
175        warnings = []
176
177        class get_warnings(object):
178
179            def pytest_logwarning(self, code, fslocation, message, nodeid):
180                warnings.append(message)
181
182        class Plugin(object):
183
184            def pytest_testhook():
185                pass
186
187        pytestpm.register(get_warnings())
188        before = list(warnings)
189        pytestpm.addhooks(Plugin())
190        assert len(warnings) == len(before) + 1
191        assert "deprecated" in warnings[-1]
192
193
194def test_namespace_has_default_and_env_plugins(testdir):
195    p = testdir.makepyfile(
196        """
197        import pytest
198        pytest.mark
199    """
200    )
201    result = testdir.runpython(p)
202    assert result.ret == 0
203
204
205def test_default_markers(testdir):
206    result = testdir.runpytest("--markers")
207    result.stdout.fnmatch_lines(["*tryfirst*first*", "*trylast*last*"])
208
209
210def test_importplugin_error_message(testdir, pytestpm):
211    """Don't hide import errors when importing plugins and provide
212    an easy to debug message.
213
214    See #375 and #1998.
215    """
216    testdir.syspathinsert(testdir.tmpdir)
217    testdir.makepyfile(
218        qwe="""
219        # encoding: UTF-8
220        def test_traceback():
221            raise ImportError(u'Not possible to import: ☺')
222        test_traceback()
223    """
224    )
225    with pytest.raises(ImportError) as excinfo:
226        pytestpm.import_plugin("qwe")
227
228    expected_message = '.*Error importing plugin "qwe": Not possible to import: .'
229    expected_traceback = ".*in test_traceback"
230    assert re.match(expected_message, str(excinfo.value))
231    assert re.match(expected_traceback, str(excinfo.traceback[-1]))
232
233
234class TestPytestPluginManager(object):
235
236    def test_register_imported_modules(self):
237        pm = PytestPluginManager()
238        mod = types.ModuleType("x.y.pytest_hello")
239        pm.register(mod)
240        assert pm.is_registered(mod)
241        values = pm.get_plugins()
242        assert mod in values
243        pytest.raises(ValueError, "pm.register(mod)")
244        pytest.raises(ValueError, lambda: pm.register(mod))
245        # assert not pm.is_registered(mod2)
246        assert pm.get_plugins() == values
247
248    def test_canonical_import(self, monkeypatch):
249        mod = types.ModuleType("pytest_xyz")
250        monkeypatch.setitem(sys.modules, "pytest_xyz", mod)
251        pm = PytestPluginManager()
252        pm.import_plugin("pytest_xyz")
253        assert pm.get_plugin("pytest_xyz") == mod
254        assert pm.is_registered(mod)
255
256    def test_consider_module(self, testdir, pytestpm):
257        testdir.syspathinsert()
258        testdir.makepyfile(pytest_p1="#")
259        testdir.makepyfile(pytest_p2="#")
260        mod = types.ModuleType("temp")
261        mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
262        pytestpm.consider_module(mod)
263        assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
264        assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
265
266    def test_consider_module_import_module(self, testdir):
267        pytestpm = get_config().pluginmanager
268        mod = types.ModuleType("x")
269        mod.pytest_plugins = "pytest_a"
270        aplugin = testdir.makepyfile(pytest_a="#")
271        reprec = testdir.make_hook_recorder(pytestpm)
272        # syspath.prepend(aplugin.dirpath())
273        sys.path.insert(0, str(aplugin.dirpath()))
274        pytestpm.consider_module(mod)
275        call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
276        assert call.plugin.__name__ == "pytest_a"
277
278        # check that it is not registered twice
279        pytestpm.consider_module(mod)
280        values = reprec.getcalls("pytest_plugin_registered")
281        assert len(values) == 1
282
283    def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
284        monkeypatch.setenv("PYTEST_PLUGINS", "nonexisting", prepend=",")
285        with pytest.raises(ImportError):
286            pytestpm.consider_env()
287
288    def test_plugin_skip(self, testdir, monkeypatch):
289        p = testdir.makepyfile(
290            skipping1="""
291            import pytest
292            pytest.skip("hello")
293        """
294        )
295        p.copy(p.dirpath("skipping2.py"))
296        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
297        result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
298        assert result.ret == EXIT_NOTESTSCOLLECTED
299        result.stdout.fnmatch_lines(
300            ["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"]
301        )
302
303    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
304        testdir.syspathinsert()
305        testdir.makepyfile(xy123="#")
306        monkeypatch.setitem(os.environ, "PYTEST_PLUGINS", "xy123")
307        l1 = len(pytestpm.get_plugins())
308        pytestpm.consider_env()
309        l2 = len(pytestpm.get_plugins())
310        assert l2 == l1 + 1
311        assert pytestpm.get_plugin("xy123")
312        pytestpm.consider_env()
313        l3 = len(pytestpm.get_plugins())
314        assert l2 == l3
315
316    def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
317        testdir.makepyfile(pytest_x500="#")
318        p = testdir.makepyfile(
319            """
320            import pytest
321            def test_hello(pytestconfig):
322                plugin = pytestconfig.pluginmanager.get_plugin('pytest_x500')
323                assert plugin is not None
324        """
325        )
326        monkeypatch.setenv("PYTEST_PLUGINS", "pytest_x500", prepend=",")
327        result = testdir.runpytest(p, syspathinsert=True)
328        assert result.ret == 0
329        result.stdout.fnmatch_lines(["*1 passed*"])
330
331    def test_import_plugin_importname(self, testdir, pytestpm):
332        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
333        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
334
335        testdir.syspathinsert()
336        pluginname = "pytest_hello"
337        testdir.makepyfile(**{pluginname: ""})
338        pytestpm.import_plugin("pytest_hello")
339        len1 = len(pytestpm.get_plugins())
340        pytestpm.import_plugin("pytest_hello")
341        len2 = len(pytestpm.get_plugins())
342        assert len1 == len2
343        plugin1 = pytestpm.get_plugin("pytest_hello")
344        assert plugin1.__name__.endswith("pytest_hello")
345        plugin2 = pytestpm.get_plugin("pytest_hello")
346        assert plugin2 is plugin1
347
348    def test_import_plugin_dotted_name(self, testdir, pytestpm):
349        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
350        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
351
352        testdir.syspathinsert()
353        testdir.mkpydir("pkg").join("plug.py").write("x=3")
354        pluginname = "pkg.plug"
355        pytestpm.import_plugin(pluginname)
356        mod = pytestpm.get_plugin("pkg.plug")
357        assert mod.x == 3
358
359    def test_consider_conftest_deps(self, testdir, pytestpm):
360        mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
361        with pytest.raises(ImportError):
362            pytestpm.consider_conftest(mod)
363
364
365class TestPytestPluginManagerBootstrapming(object):
366
367    def test_preparse_args(self, pytestpm):
368        pytest.raises(
369            ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"])
370        )
371
372    def test_plugin_prevent_register(self, pytestpm):
373        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
374        l1 = pytestpm.get_plugins()
375        pytestpm.register(42, name="abc")
376        l2 = pytestpm.get_plugins()
377        assert len(l2) == len(l1)
378        assert 42 not in l2
379
380    def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
381        pytestpm.register(42, name="abc")
382        l1 = pytestpm.get_plugins()
383        assert 42 in l1
384        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
385        l2 = pytestpm.get_plugins()
386        assert 42 not in l2
387