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