1from __future__ import absolute_import, division, print_function
2from textwrap import dedent
3
4import _pytest._code
5import py
6import pytest
7from _pytest.config import PytestPluginManager
8from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
9
10
11@pytest.fixture(scope="module", params=["global", "inpackage"])
12def basedir(request, tmpdir_factory):
13    from _pytest.tmpdir import tmpdir
14
15    tmpdir = tmpdir(request, tmpdir_factory)
16    tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
17    tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
18    if request.param == "inpackage":
19        tmpdir.ensure("adir/__init__.py")
20        tmpdir.ensure("adir/b/__init__.py")
21    return tmpdir
22
23
24def ConftestWithSetinitial(path):
25    conftest = PytestPluginManager()
26    conftest_setinitial(conftest, [path])
27    return conftest
28
29
30def conftest_setinitial(conftest, args, confcutdir=None):
31
32    class Namespace(object):
33
34        def __init__(self):
35            self.file_or_dir = args
36            self.confcutdir = str(confcutdir)
37            self.noconftest = False
38
39    conftest._set_initial_conftests(Namespace())
40
41
42class TestConftestValueAccessGlobal(object):
43
44    def test_basic_init(self, basedir):
45        conftest = PytestPluginManager()
46        p = basedir.join("adir")
47        assert conftest._rget_with_confmod("a", p)[1] == 1
48
49    def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
50        conftest = PytestPluginManager()
51        len(conftest._path2confmods)
52        conftest._getconftestmodules(basedir)
53        snap1 = len(conftest._path2confmods)
54        # assert len(conftest._path2confmods) == snap1 + 1
55        conftest._getconftestmodules(basedir.join("adir"))
56        assert len(conftest._path2confmods) == snap1 + 1
57        conftest._getconftestmodules(basedir.join("b"))
58        assert len(conftest._path2confmods) == snap1 + 2
59
60    def test_value_access_not_existing(self, basedir):
61        conftest = ConftestWithSetinitial(basedir)
62        with pytest.raises(KeyError):
63            conftest._rget_with_confmod("a", basedir)
64
65    def test_value_access_by_path(self, basedir):
66        conftest = ConftestWithSetinitial(basedir)
67        adir = basedir.join("adir")
68        assert conftest._rget_with_confmod("a", adir)[1] == 1
69        assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
70
71    def test_value_access_with_confmod(self, basedir):
72        startdir = basedir.join("adir", "b")
73        startdir.ensure("xx", dir=True)
74        conftest = ConftestWithSetinitial(startdir)
75        mod, value = conftest._rget_with_confmod("a", startdir)
76        assert value == 1.5
77        path = py.path.local(mod.__file__)
78        assert path.dirpath() == basedir.join("adir", "b")
79        assert path.purebasename.startswith("conftest")
80
81
82def test_conftest_in_nonpkg_with_init(tmpdir):
83    tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3")
84    tmpdir.ensure("adir-1.0/b/conftest.py").write("b=2 ; a = 1.5")
85    tmpdir.ensure("adir-1.0/b/__init__.py")
86    tmpdir.ensure("adir-1.0/__init__.py")
87    ConftestWithSetinitial(tmpdir.join("adir-1.0", "b"))
88
89
90def test_doubledash_considered(testdir):
91    conf = testdir.mkdir("--option")
92    conf.ensure("conftest.py")
93    conftest = PytestPluginManager()
94    conftest_setinitial(conftest, [conf.basename, conf.basename])
95    values = conftest._getconftestmodules(conf)
96    assert len(values) == 1
97
98
99def test_issue151_load_all_conftests(testdir):
100    names = "code proj src".split()
101    for name in names:
102        p = testdir.mkdir(name)
103        p.ensure("conftest.py")
104
105    conftest = PytestPluginManager()
106    conftest_setinitial(conftest, names)
107    d = list(conftest._conftestpath2mod.values())
108    assert len(d) == len(names)
109
110
111def test_conftest_global_import(testdir):
112    testdir.makeconftest("x=3")
113    p = testdir.makepyfile(
114        """
115        import py, pytest
116        from _pytest.config import PytestPluginManager
117        conf = PytestPluginManager()
118        mod = conf._importconftest(py.path.local("conftest.py"))
119        assert mod.x == 3
120        import conftest
121        assert conftest is mod, (conftest, mod)
122        subconf = py.path.local().ensure("sub", "conftest.py")
123        subconf.write("y=4")
124        mod2 = conf._importconftest(subconf)
125        assert mod != mod2
126        assert mod2.y == 4
127        import conftest
128        assert conftest is mod2, (conftest, mod)
129    """
130    )
131    res = testdir.runpython(p)
132    assert res.ret == 0
133
134
135def test_conftestcutdir(testdir):
136    conf = testdir.makeconftest("")
137    p = testdir.mkdir("x")
138    conftest = PytestPluginManager()
139    conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
140    values = conftest._getconftestmodules(p)
141    assert len(values) == 0
142    values = conftest._getconftestmodules(conf.dirpath())
143    assert len(values) == 0
144    assert conf not in conftest._conftestpath2mod
145    # but we can still import a conftest directly
146    conftest._importconftest(conf)
147    values = conftest._getconftestmodules(conf.dirpath())
148    assert values[0].__file__.startswith(str(conf))
149    # and all sub paths get updated properly
150    values = conftest._getconftestmodules(p)
151    assert len(values) == 1
152    assert values[0].__file__.startswith(str(conf))
153
154
155def test_conftestcutdir_inplace_considered(testdir):
156    conf = testdir.makeconftest("")
157    conftest = PytestPluginManager()
158    conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
159    values = conftest._getconftestmodules(conf.dirpath())
160    assert len(values) == 1
161    assert values[0].__file__.startswith(str(conf))
162
163
164@pytest.mark.parametrize("name", "test tests whatever .dotdir".split())
165def test_setinitial_conftest_subdirs(testdir, name):
166    sub = testdir.mkdir(name)
167    subconftest = sub.ensure("conftest.py")
168    conftest = PytestPluginManager()
169    conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
170    if name not in ("whatever", ".dotdir"):
171        assert subconftest in conftest._conftestpath2mod
172        assert len(conftest._conftestpath2mod) == 1
173    else:
174        assert subconftest not in conftest._conftestpath2mod
175        assert len(conftest._conftestpath2mod) == 0
176
177
178def test_conftest_confcutdir(testdir):
179    testdir.makeconftest("assert 0")
180    x = testdir.mkdir("x")
181    x.join("conftest.py").write(
182        _pytest._code.Source(
183            """
184        def pytest_addoption(parser):
185            parser.addoption("--xyz", action="store_true")
186    """
187        )
188    )
189    result = testdir.runpytest("-h", "--confcutdir=%s" % x, x)
190    result.stdout.fnmatch_lines(["*--xyz*"])
191    assert "warning: could not load initial" not in result.stdout.str()
192
193
194def test_no_conftest(testdir):
195    testdir.makeconftest("assert 0")
196    result = testdir.runpytest("--noconftest")
197    assert result.ret == EXIT_NOTESTSCOLLECTED
198
199    result = testdir.runpytest()
200    assert result.ret == EXIT_USAGEERROR
201
202
203def test_conftest_existing_resultlog(testdir):
204    x = testdir.mkdir("tests")
205    x.join("conftest.py").write(
206        _pytest._code.Source(
207            """
208        def pytest_addoption(parser):
209            parser.addoption("--xyz", action="store_true")
210    """
211        )
212    )
213    testdir.makefile(ext=".log", result="")  # Writes result.log
214    result = testdir.runpytest("-h", "--resultlog", "result.log")
215    result.stdout.fnmatch_lines(["*--xyz*"])
216
217
218def test_conftest_existing_junitxml(testdir):
219    x = testdir.mkdir("tests")
220    x.join("conftest.py").write(
221        _pytest._code.Source(
222            """
223        def pytest_addoption(parser):
224            parser.addoption("--xyz", action="store_true")
225    """
226        )
227    )
228    testdir.makefile(ext=".xml", junit="")  # Writes junit.xml
229    result = testdir.runpytest("-h", "--junitxml", "junit.xml")
230    result.stdout.fnmatch_lines(["*--xyz*"])
231
232
233def test_conftest_import_order(testdir, monkeypatch):
234    ct1 = testdir.makeconftest("")
235    sub = testdir.mkdir("sub")
236    ct2 = sub.join("conftest.py")
237    ct2.write("")
238
239    def impct(p):
240        return p
241
242    conftest = PytestPluginManager()
243    conftest._confcutdir = testdir.tmpdir
244    monkeypatch.setattr(conftest, "_importconftest", impct)
245    assert conftest._getconftestmodules(sub) == [ct1, ct2]
246
247
248def test_fixture_dependency(testdir, monkeypatch):
249    ct1 = testdir.makeconftest("")
250    ct1 = testdir.makepyfile("__init__.py")
251    ct1.write("")
252    sub = testdir.mkdir("sub")
253    sub.join("__init__.py").write("")
254    sub.join("conftest.py").write(
255        dedent(
256            """
257        import pytest
258
259        @pytest.fixture
260        def not_needed():
261            assert False, "Should not be called!"
262
263        @pytest.fixture
264        def foo():
265            assert False, "Should not be called!"
266
267        @pytest.fixture
268        def bar(foo):
269            return 'bar'
270    """
271        )
272    )
273    subsub = sub.mkdir("subsub")
274    subsub.join("__init__.py").write("")
275    subsub.join("test_bar.py").write(
276        dedent(
277            """
278        import pytest
279
280        @pytest.fixture
281        def bar():
282            return 'sub bar'
283
284        def test_event_fixture(bar):
285            assert bar == 'sub bar'
286    """
287        )
288    )
289    result = testdir.runpytest("sub")
290    result.stdout.fnmatch_lines(["*1 passed*"])
291
292
293def test_conftest_found_with_double_dash(testdir):
294    sub = testdir.mkdir("sub")
295    sub.join("conftest.py").write(
296        dedent(
297            """
298        def pytest_addoption(parser):
299            parser.addoption("--hello-world", action="store_true")
300    """
301        )
302    )
303    p = sub.join("test_hello.py")
304    p.write("def test_hello(): pass")
305    result = testdir.runpytest(str(p) + "::test_hello", "-h")
306    result.stdout.fnmatch_lines(
307        """
308        *--hello-world*
309    """
310    )
311
312
313class TestConftestVisibility(object):
314
315    def _setup_tree(self, testdir):  # for issue616
316        # example mostly taken from:
317        # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html
318        runner = testdir.mkdir("empty")
319        package = testdir.mkdir("package")
320
321        package.join("conftest.py").write(
322            dedent(
323                """\
324            import pytest
325            @pytest.fixture
326            def fxtr():
327                return "from-package"
328        """
329            )
330        )
331        package.join("test_pkgroot.py").write(
332            dedent(
333                """\
334            def test_pkgroot(fxtr):
335                assert fxtr == "from-package"
336        """
337            )
338        )
339
340        swc = package.mkdir("swc")
341        swc.join("__init__.py").ensure()
342        swc.join("conftest.py").write(
343            dedent(
344                """\
345            import pytest
346            @pytest.fixture
347            def fxtr():
348                return "from-swc"
349        """
350            )
351        )
352        swc.join("test_with_conftest.py").write(
353            dedent(
354                """\
355            def test_with_conftest(fxtr):
356                assert fxtr == "from-swc"
357
358        """
359            )
360        )
361
362        snc = package.mkdir("snc")
363        snc.join("__init__.py").ensure()
364        snc.join("test_no_conftest.py").write(
365            dedent(
366                """\
367            def test_no_conftest(fxtr):
368                assert fxtr == "from-package"   # No local conftest.py, so should
369                                                # use value from parent dir's
370
371        """
372            )
373        )
374        print("created directory structure:")
375        for x in testdir.tmpdir.visit():
376            print("   " + x.relto(testdir.tmpdir))
377
378        return {"runner": runner, "package": package, "swc": swc, "snc": snc}
379
380    # N.B.: "swc" stands for "subdir with conftest.py"
381    #       "snc" stands for "subdir no [i.e. without] conftest.py"
382    @pytest.mark.parametrize(
383        "chdir,testarg,expect_ntests_passed",
384        [
385            # Effective target: package/..
386            ("runner", "..", 3),
387            ("package", "..", 3),
388            ("swc", "../..", 3),
389            ("snc", "../..", 3),
390            # Effective target: package
391            ("runner", "../package", 3),
392            ("package", ".", 3),
393            ("swc", "..", 3),
394            ("snc", "..", 3),
395            # Effective target: package/swc
396            ("runner", "../package/swc", 1),
397            ("package", "./swc", 1),
398            ("swc", ".", 1),
399            ("snc", "../swc", 1),
400            # Effective target: package/snc
401            ("runner", "../package/snc", 1),
402            ("package", "./snc", 1),
403            ("swc", "../snc", 1),
404            ("snc", ".", 1),
405        ],
406    )
407    @pytest.mark.issue616
408    def test_parsefactories_relative_node_ids(
409        self, testdir, chdir, testarg, expect_ntests_passed
410    ):
411        dirs = self._setup_tree(testdir)
412        print("pytest run in cwd: %s" % (dirs[chdir].relto(testdir.tmpdir)))
413        print("pytestarg        : %s" % (testarg))
414        print("expected pass    : %s" % (expect_ntests_passed))
415        with dirs[chdir].as_cwd():
416            reprec = testdir.inline_run(testarg, "-q", "--traceconfig")
417            reprec.assertoutcome(passed=expect_ntests_passed)
418
419
420@pytest.mark.parametrize(
421    "confcutdir,passed,error", [(".", 2, 0), ("src", 1, 1), (None, 1, 1)]
422)
423def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error):
424    """Test that conftest files are detected only up to an ini file, unless
425    an explicit --confcutdir option is given.
426    """
427    root = testdir.tmpdir
428    src = root.join("src").ensure(dir=1)
429    src.join("pytest.ini").write("[pytest]")
430    src.join("conftest.py").write(
431        _pytest._code.Source(
432            """
433        import pytest
434        @pytest.fixture
435        def fix1(): pass
436    """
437        )
438    )
439    src.join("test_foo.py").write(
440        _pytest._code.Source(
441            """
442        def test_1(fix1):
443            pass
444        def test_2(out_of_reach):
445            pass
446    """
447        )
448    )
449    root.join("conftest.py").write(
450        _pytest._code.Source(
451            """
452        import pytest
453        @pytest.fixture
454        def out_of_reach(): pass
455    """
456        )
457    )
458
459    args = [str(src)]
460    if confcutdir:
461        args = ["--confcutdir=%s" % root.join(confcutdir)]
462    result = testdir.runpytest(*args)
463    match = ""
464    if passed:
465        match += "*%d passed*" % passed
466    if error:
467        match += "*%d error*" % error
468    result.stdout.fnmatch_lines(match)
469
470
471def test_issue1073_conftest_special_objects(testdir):
472    testdir.makeconftest(
473        """
474        class DontTouchMe(object):
475            def __getattr__(self, x):
476                raise Exception('cant touch me')
477
478        x = DontTouchMe()
479    """
480    )
481    testdir.makepyfile(
482        """
483        def test_some():
484            pass
485    """
486    )
487    res = testdir.runpytest()
488    assert res.ret == 0
489
490
491def test_conftest_exception_handling(testdir):
492    testdir.makeconftest(
493        """
494        raise ValueError()
495    """
496    )
497    testdir.makepyfile(
498        """
499        def test_some():
500            pass
501    """
502    )
503    res = testdir.runpytest()
504    assert res.ret == 4
505    assert "raise ValueError()" in [line.strip() for line in res.errlines]
506
507
508def test_hook_proxy(testdir):
509    """Session's gethookproxy() would cache conftests incorrectly (#2016).
510    It was decided to remove the cache altogether.
511    """
512    testdir.makepyfile(
513        **{
514            "root/demo-0/test_foo1.py": "def test1(): pass",
515            "root/demo-a/test_foo2.py": "def test1(): pass",
516            "root/demo-a/conftest.py": """
517            def pytest_ignore_collect(path, config):
518                return True
519            """,
520            "root/demo-b/test_foo3.py": "def test1(): pass",
521            "root/demo-c/test_foo4.py": "def test1(): pass",
522        }
523    )
524    result = testdir.runpytest()
525    result.stdout.fnmatch_lines(
526        ["*test_foo1.py*", "*test_foo3.py*", "*test_foo4.py*", "*3 passed*"]
527    )
528
529
530def test_required_option_help(testdir):
531    testdir.makeconftest("assert 0")
532    x = testdir.mkdir("x")
533    x.join("conftest.py").write(
534        _pytest._code.Source(
535            """
536        def pytest_addoption(parser):
537            parser.addoption("--xyz", action="store_true", required=True)
538    """
539        )
540    )
541    result = testdir.runpytest("-h", x)
542    assert "argument --xyz is required" not in result.stdout.str()
543    assert "general:" in result.stdout.str()
544