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