1# -*- coding: utf-8 -*-
2from __future__ import absolute_import
3from __future__ import division
4from __future__ import print_function
5
6import os
7import sys
8import textwrap
9import types
10
11import attr
12import py
13import six
14
15import pytest
16from _pytest.compat import importlib_metadata
17from _pytest.main import EXIT_NOTESTSCOLLECTED
18from _pytest.main import EXIT_USAGEERROR
19from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
20
21
22def prepend_pythonpath(*dirs):
23    cur = os.getenv("PYTHONPATH")
24    if cur:
25        dirs += (cur,)
26    return os.pathsep.join(str(p) for p in dirs)
27
28
29class TestGeneralUsage(object):
30    def test_config_error(self, testdir):
31        testdir.copy_example("conftest_usageerror/conftest.py")
32        result = testdir.runpytest(testdir.tmpdir)
33        assert result.ret == EXIT_USAGEERROR
34        result.stderr.fnmatch_lines(["*ERROR: hello"])
35        result.stdout.fnmatch_lines(["*pytest_unconfigure_called"])
36
37    def test_root_conftest_syntax_error(self, testdir):
38        testdir.makepyfile(conftest="raise SyntaxError\n")
39        result = testdir.runpytest()
40        result.stderr.fnmatch_lines(["*raise SyntaxError*"])
41        assert result.ret != 0
42
43    def test_early_hook_error_issue38_1(self, testdir):
44        testdir.makeconftest(
45            """
46            def pytest_sessionstart():
47                0 / 0
48        """
49        )
50        result = testdir.runpytest(testdir.tmpdir)
51        assert result.ret != 0
52        # tracestyle is native by default for hook failures
53        result.stdout.fnmatch_lines(
54            ["*INTERNALERROR*File*conftest.py*line 2*", "*0 / 0*"]
55        )
56        result = testdir.runpytest(testdir.tmpdir, "--fulltrace")
57        assert result.ret != 0
58        # tracestyle is native by default for hook failures
59        result.stdout.fnmatch_lines(
60            ["*INTERNALERROR*def pytest_sessionstart():*", "*INTERNALERROR*0 / 0*"]
61        )
62
63    def test_early_hook_configure_error_issue38(self, testdir):
64        testdir.makeconftest(
65            """
66            def pytest_configure():
67                0 / 0
68        """
69        )
70        result = testdir.runpytest(testdir.tmpdir)
71        assert result.ret != 0
72        # here we get it on stderr
73        result.stderr.fnmatch_lines(
74            ["*INTERNALERROR*File*conftest.py*line 2*", "*0 / 0*"]
75        )
76
77    def test_file_not_found(self, testdir):
78        result = testdir.runpytest("asd")
79        assert result.ret != 0
80        result.stderr.fnmatch_lines(["ERROR: file not found*asd"])
81
82    def test_file_not_found_unconfigure_issue143(self, testdir):
83        testdir.makeconftest(
84            """
85            def pytest_configure():
86                print("---configure")
87            def pytest_unconfigure():
88                print("---unconfigure")
89        """
90        )
91        result = testdir.runpytest("-s", "asd")
92        assert result.ret == 4  # EXIT_USAGEERROR
93        result.stderr.fnmatch_lines(["ERROR: file not found*asd"])
94        result.stdout.fnmatch_lines(["*---configure", "*---unconfigure"])
95
96    def test_config_preparse_plugin_option(self, testdir):
97        testdir.makepyfile(
98            pytest_xyz="""
99            def pytest_addoption(parser):
100                parser.addoption("--xyz", dest="xyz", action="store")
101        """
102        )
103        testdir.makepyfile(
104            test_one="""
105            def test_option(pytestconfig):
106                assert pytestconfig.option.xyz == "123"
107        """
108        )
109        result = testdir.runpytest("-p", "pytest_xyz", "--xyz=123", syspathinsert=True)
110        assert result.ret == 0
111        result.stdout.fnmatch_lines(["*1 passed*"])
112
113    @pytest.mark.parametrize("load_cov_early", [True, False])
114    def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
115        testdir.makepyfile(mytestplugin1_module="")
116        testdir.makepyfile(mytestplugin2_module="")
117        testdir.makepyfile(mycov_module="")
118        testdir.syspathinsert()
119
120        loaded = []
121
122        @attr.s
123        class DummyEntryPoint(object):
124            name = attr.ib()
125            module = attr.ib()
126            group = "pytest11"
127
128            def load(self):
129                __import__(self.module)
130                loaded.append(self.name)
131                return sys.modules[self.module]
132
133        entry_points = [
134            DummyEntryPoint("myplugin1", "mytestplugin1_module"),
135            DummyEntryPoint("myplugin2", "mytestplugin2_module"),
136            DummyEntryPoint("mycov", "mycov_module"),
137        ]
138
139        @attr.s
140        class DummyDist(object):
141            entry_points = attr.ib()
142            files = ()
143
144        def my_dists():
145            return (DummyDist(entry_points),)
146
147        monkeypatch.setattr(importlib_metadata, "distributions", my_dists)
148        params = ("-p", "mycov") if load_cov_early else ()
149        testdir.runpytest_inprocess(*params)
150        if load_cov_early:
151            assert loaded == ["mycov", "myplugin1", "myplugin2"]
152        else:
153            assert loaded == ["myplugin1", "myplugin2", "mycov"]
154
155    def test_assertion_magic(self, testdir):
156        p = testdir.makepyfile(
157            """
158            def test_this():
159                x = 0
160                assert x
161        """
162        )
163        result = testdir.runpytest(p)
164        result.stdout.fnmatch_lines([">       assert x", "E       assert 0"])
165        assert result.ret == 1
166
167    def test_nested_import_error(self, testdir):
168        p = testdir.makepyfile(
169            """
170                import import_fails
171                def test_this():
172                    assert import_fails.a == 1
173        """
174        )
175        testdir.makepyfile(import_fails="import does_not_work")
176        result = testdir.runpytest(p)
177        result.stdout.fnmatch_lines(
178            [
179                # XXX on jython this fails:  ">   import import_fails",
180                "ImportError while importing test module*",
181                "*No module named *does_not_work*",
182            ]
183        )
184        assert result.ret == 2
185
186    def test_not_collectable_arguments(self, testdir):
187        p1 = testdir.makepyfile("")
188        p2 = testdir.makefile(".pyc", "123")
189        result = testdir.runpytest(p1, p2)
190        assert result.ret
191        result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)])
192
193    @pytest.mark.filterwarnings("default")
194    def test_better_reporting_on_conftest_load_failure(self, testdir, request):
195        """Show a user-friendly traceback on conftest import failures (#486, #3332)"""
196        testdir.makepyfile("")
197        testdir.makeconftest(
198            """
199            def foo():
200                import qwerty
201            foo()
202        """
203        )
204        result = testdir.runpytest("--help")
205        result.stdout.fnmatch_lines(
206            """
207            *--version*
208            *warning*conftest.py*
209        """
210        )
211        result = testdir.runpytest()
212        dirname = request.node.name + "0"
213        exc_name = (
214            "ModuleNotFoundError" if sys.version_info >= (3, 6) else "ImportError"
215        )
216        result.stderr.fnmatch_lines(
217            [
218                "ImportError while loading conftest '*{sep}{dirname}{sep}conftest.py'.".format(
219                    dirname=dirname, sep=os.sep
220                ),
221                "conftest.py:3: in <module>",
222                "    foo()",
223                "conftest.py:2: in foo",
224                "    import qwerty",
225                "E   {}: No module named {q}qwerty{q}".format(
226                    exc_name, q="" if six.PY2 else "'"
227                ),
228            ]
229        )
230
231    def test_early_skip(self, testdir):
232        testdir.mkdir("xyz")
233        testdir.makeconftest(
234            """
235            import pytest
236            def pytest_collect_directory():
237                pytest.skip("early")
238        """
239        )
240        result = testdir.runpytest()
241        assert result.ret == EXIT_NOTESTSCOLLECTED
242        result.stdout.fnmatch_lines(["*1 skip*"])
243
244    def test_issue88_initial_file_multinodes(self, testdir):
245        testdir.copy_example("issue88_initial_file_multinodes")
246        p = testdir.makepyfile("def test_hello(): pass")
247        result = testdir.runpytest(p, "--collect-only")
248        result.stdout.fnmatch_lines(["*MyFile*test_issue88*", "*Module*test_issue88*"])
249
250    def test_issue93_initialnode_importing_capturing(self, testdir):
251        testdir.makeconftest(
252            """
253            import sys
254            print("should not be seen")
255            sys.stderr.write("stder42\\n")
256        """
257        )
258        result = testdir.runpytest()
259        assert result.ret == EXIT_NOTESTSCOLLECTED
260        assert "should not be seen" not in result.stdout.str()
261        assert "stderr42" not in result.stderr.str()
262
263    def test_conftest_printing_shows_if_error(self, testdir):
264        testdir.makeconftest(
265            """
266            print("should be seen")
267            assert 0
268        """
269        )
270        result = testdir.runpytest()
271        assert result.ret != 0
272        assert "should be seen" in result.stdout.str()
273
274    @pytest.mark.skipif(
275        not hasattr(py.path.local, "mksymlinkto"),
276        reason="symlink not available on this platform",
277    )
278    def test_chdir(self, testdir):
279        testdir.tmpdir.join("py").mksymlinkto(py._pydir)
280        p = testdir.tmpdir.join("main.py")
281        p.write(
282            textwrap.dedent(
283                """\
284                import sys, os
285                sys.path.insert(0, '')
286                import py
287                print(py.__file__)
288                print(py.__path__)
289                os.chdir(os.path.dirname(os.getcwd()))
290                print(py.log)
291                """
292            )
293        )
294        result = testdir.runpython(p)
295        assert not result.ret
296
297    def test_issue109_sibling_conftests_not_loaded(self, testdir):
298        sub1 = testdir.mkdir("sub1")
299        sub2 = testdir.mkdir("sub2")
300        sub1.join("conftest.py").write("assert 0")
301        result = testdir.runpytest(sub2)
302        assert result.ret == EXIT_NOTESTSCOLLECTED
303        sub2.ensure("__init__.py")
304        p = sub2.ensure("test_hello.py")
305        result = testdir.runpytest(p)
306        assert result.ret == EXIT_NOTESTSCOLLECTED
307        result = testdir.runpytest(sub1)
308        assert result.ret == EXIT_USAGEERROR
309
310    def test_directory_skipped(self, testdir):
311        testdir.makeconftest(
312            """
313            import pytest
314            def pytest_ignore_collect():
315                pytest.skip("intentional")
316        """
317        )
318        testdir.makepyfile("def test_hello(): pass")
319        result = testdir.runpytest()
320        assert result.ret == EXIT_NOTESTSCOLLECTED
321        result.stdout.fnmatch_lines(["*1 skipped*"])
322
323    def test_multiple_items_per_collector_byid(self, testdir):
324        c = testdir.makeconftest(
325            """
326            import pytest
327            class MyItem(pytest.Item):
328                def runtest(self):
329                    pass
330            class MyCollector(pytest.File):
331                def collect(self):
332                    return [MyItem(name="xyz", parent=self)]
333            def pytest_collect_file(path, parent):
334                if path.basename.startswith("conftest"):
335                    return MyCollector(path, parent)
336        """
337        )
338        result = testdir.runpytest(c.basename + "::" + "xyz")
339        assert result.ret == 0
340        result.stdout.fnmatch_lines(["*1 pass*"])
341
342    def test_skip_on_generated_funcarg_id(self, testdir):
343        testdir.makeconftest(
344            """
345            import pytest
346            def pytest_generate_tests(metafunc):
347                metafunc.parametrize('x', [3], ids=['hello-123'])
348            def pytest_runtest_setup(item):
349                print(item.keywords)
350                if 'hello-123' in item.keywords:
351                    pytest.skip("hello")
352                assert 0
353        """
354        )
355        p = testdir.makepyfile("""def test_func(x): pass""")
356        res = testdir.runpytest(p, SHOW_PYTEST_WARNINGS_ARG)
357        assert res.ret == 0
358        res.stdout.fnmatch_lines(["*1 skipped*"])
359
360    def test_direct_addressing_selects(self, testdir):
361        p = testdir.makepyfile(
362            """
363            def pytest_generate_tests(metafunc):
364                metafunc.parametrize('i', [1, 2], ids=["1", "2"])
365            def test_func(i):
366                pass
367        """
368        )
369        res = testdir.runpytest(
370            p.basename + "::" + "test_func[1]", SHOW_PYTEST_WARNINGS_ARG
371        )
372        assert res.ret == 0
373        res.stdout.fnmatch_lines(["*1 passed*"])
374
375    def test_direct_addressing_notfound(self, testdir):
376        p = testdir.makepyfile(
377            """
378            def test_func():
379                pass
380        """
381        )
382        res = testdir.runpytest(p.basename + "::" + "test_notfound")
383        assert res.ret
384        res.stderr.fnmatch_lines(["*ERROR*not found*"])
385
386    def test_docstring_on_hookspec(self):
387        from _pytest import hookspec
388
389        for name, value in vars(hookspec).items():
390            if name.startswith("pytest_"):
391                assert value.__doc__, "no docstring for %s" % name
392
393    def test_initialization_error_issue49(self, testdir):
394        testdir.makeconftest(
395            """
396            def pytest_configure():
397                x
398        """
399        )
400        result = testdir.runpytest()
401        assert result.ret == 3  # internal error
402        result.stderr.fnmatch_lines(["INTERNAL*pytest_configure*", "INTERNAL*x*"])
403        assert "sessionstarttime" not in result.stderr.str()
404
405    @pytest.mark.parametrize("lookfor", ["test_fun.py::test_a"])
406    def test_issue134_report_error_when_collecting_member(self, testdir, lookfor):
407        testdir.makepyfile(
408            test_fun="""
409            def test_a():
410                pass
411            def"""
412        )
413        result = testdir.runpytest(lookfor)
414        result.stdout.fnmatch_lines(["*SyntaxError*"])
415        if "::" in lookfor:
416            result.stderr.fnmatch_lines(["*ERROR*"])
417            assert result.ret == 4  # usage error only if item not found
418
419    def test_report_all_failed_collections_initargs(self, testdir):
420        testdir.makeconftest(
421            """
422            from _pytest.main import EXIT_USAGEERROR
423
424            def pytest_sessionfinish(exitstatus):
425                assert exitstatus == EXIT_USAGEERROR
426                print("pytest_sessionfinish_called")
427            """
428        )
429        testdir.makepyfile(test_a="def", test_b="def")
430        result = testdir.runpytest("test_a.py::a", "test_b.py::b")
431        result.stderr.fnmatch_lines(["*ERROR*test_a.py::a*", "*ERROR*test_b.py::b*"])
432        result.stdout.fnmatch_lines(["pytest_sessionfinish_called"])
433        assert result.ret == EXIT_USAGEERROR
434
435    @pytest.mark.usefixtures("recwarn")
436    def test_namespace_import_doesnt_confuse_import_hook(self, testdir):
437        """
438        Ref #383. Python 3.3's namespace package messed with our import hooks
439        Importing a module that didn't exist, even if the ImportError was
440        gracefully handled, would make our test crash.
441
442        Use recwarn here to silence this warning in Python 2.7:
443            ImportWarning: Not importing directory '...\not_a_package': missing __init__.py
444        """
445        testdir.mkdir("not_a_package")
446        p = testdir.makepyfile(
447            """
448            try:
449                from not_a_package import doesnt_exist
450            except ImportError:
451                # We handle the import error gracefully here
452                pass
453
454            def test_whatever():
455                pass
456        """
457        )
458        res = testdir.runpytest(p.basename)
459        assert res.ret == 0
460
461    def test_unknown_option(self, testdir):
462        result = testdir.runpytest("--qwlkej")
463        result.stderr.fnmatch_lines(
464            """
465            *unrecognized*
466        """
467        )
468
469    def test_getsourcelines_error_issue553(self, testdir, monkeypatch):
470        monkeypatch.setattr("inspect.getsourcelines", None)
471        p = testdir.makepyfile(
472            """
473            def raise_error(obj):
474                raise IOError('source code not available')
475
476            import inspect
477            inspect.getsourcelines = raise_error
478
479            def test_foo(invalid_fixture):
480                pass
481        """
482        )
483        res = testdir.runpytest(p)
484        res.stdout.fnmatch_lines(
485            ["*source code not available*", "E*fixture 'invalid_fixture' not found"]
486        )
487
488    def test_plugins_given_as_strings(self, tmpdir, monkeypatch, _sys_snapshot):
489        """test that str values passed to main() as `plugins` arg
490        are interpreted as module names to be imported and registered.
491        #855.
492        """
493        with pytest.raises(ImportError) as excinfo:
494            pytest.main([str(tmpdir)], plugins=["invalid.module"])
495        assert "invalid" in str(excinfo.value)
496
497        p = tmpdir.join("test_test_plugins_given_as_strings.py")
498        p.write("def test_foo(): pass")
499        mod = types.ModuleType("myplugin")
500        monkeypatch.setitem(sys.modules, "myplugin", mod)
501        assert pytest.main(args=[str(tmpdir)], plugins=["myplugin"]) == 0
502
503    def test_parametrized_with_bytes_regex(self, testdir):
504        p = testdir.makepyfile(
505            """
506            import re
507            import pytest
508            @pytest.mark.parametrize('r', [re.compile(b'foo')])
509            def test_stuff(r):
510                pass
511        """
512        )
513        res = testdir.runpytest(p)
514        res.stdout.fnmatch_lines(["*1 passed*"])
515
516    def test_parametrized_with_null_bytes(self, testdir):
517        """Test parametrization with values that contain null bytes and unicode characters (#2644, #2957)"""
518        p = testdir.makepyfile(
519            u"""
520            # encoding: UTF-8
521            import pytest
522
523            @pytest.mark.parametrize("data", [b"\\x00", "\\x00", u'ação'])
524            def test_foo(data):
525                assert data
526        """
527        )
528        res = testdir.runpytest(p)
529        res.assert_outcomes(passed=3)
530
531
532class TestInvocationVariants(object):
533    def test_earlyinit(self, testdir):
534        p = testdir.makepyfile(
535            """
536            import pytest
537            assert hasattr(pytest, 'mark')
538        """
539        )
540        result = testdir.runpython(p)
541        assert result.ret == 0
542
543    @pytest.mark.xfail("sys.platform.startswith('java')")
544    def test_pydoc(self, testdir):
545        for name in ("py.test", "pytest"):
546            result = testdir.runpython_c("import {};help({})".format(name, name))
547            assert result.ret == 0
548            s = result.stdout.str()
549            assert "MarkGenerator" in s
550
551    def test_import_star_py_dot_test(self, testdir):
552        p = testdir.makepyfile(
553            """
554            from py.test import *
555            #collect
556            #cmdline
557            #Item
558            # assert collect.Item is Item
559            # assert collect.Collector is Collector
560            main
561            skip
562            xfail
563        """
564        )
565        result = testdir.runpython(p)
566        assert result.ret == 0
567
568    def test_import_star_pytest(self, testdir):
569        p = testdir.makepyfile(
570            """
571            from pytest import *
572            #Item
573            #File
574            main
575            skip
576            xfail
577        """
578        )
579        result = testdir.runpython(p)
580        assert result.ret == 0
581
582    def test_double_pytestcmdline(self, testdir):
583        p = testdir.makepyfile(
584            run="""
585            import pytest
586            pytest.main()
587            pytest.main()
588        """
589        )
590        testdir.makepyfile(
591            """
592            def test_hello():
593                pass
594        """
595        )
596        result = testdir.runpython(p)
597        result.stdout.fnmatch_lines(["*1 passed*", "*1 passed*"])
598
599    def test_python_minus_m_invocation_ok(self, testdir):
600        p1 = testdir.makepyfile("def test_hello(): pass")
601        res = testdir.run(sys.executable, "-m", "pytest", str(p1))
602        assert res.ret == 0
603
604    def test_python_minus_m_invocation_fail(self, testdir):
605        p1 = testdir.makepyfile("def test_fail(): 0/0")
606        res = testdir.run(sys.executable, "-m", "pytest", str(p1))
607        assert res.ret == 1
608
609    def test_python_pytest_package(self, testdir):
610        p1 = testdir.makepyfile("def test_pass(): pass")
611        res = testdir.run(sys.executable, "-m", "pytest", str(p1))
612        assert res.ret == 0
613        res.stdout.fnmatch_lines(["*1 passed*"])
614
615    def test_equivalence_pytest_pytest(self):
616        assert pytest.main == py.test.cmdline.main
617
618    def test_invoke_with_invalid_type(self, capsys):
619        with pytest.raises(
620            TypeError, match="expected to be a list or tuple of strings, got: '-h'"
621        ):
622            pytest.main("-h")
623
624    def test_invoke_with_path(self, tmpdir, capsys):
625        retcode = pytest.main(tmpdir)
626        assert retcode == EXIT_NOTESTSCOLLECTED
627        out, err = capsys.readouterr()
628
629    def test_invoke_plugin_api(self, testdir, capsys):
630        class MyPlugin(object):
631            def pytest_addoption(self, parser):
632                parser.addoption("--myopt")
633
634        pytest.main(["-h"], plugins=[MyPlugin()])
635        out, err = capsys.readouterr()
636        assert "--myopt" in out
637
638    def test_pyargs_importerror(self, testdir, monkeypatch):
639        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False)
640        path = testdir.mkpydir("tpkg")
641        path.join("test_hello.py").write("raise ImportError")
642
643        result = testdir.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True)
644        assert result.ret != 0
645
646        result.stdout.fnmatch_lines(["collected*0*items*/*1*errors"])
647
648    def test_cmdline_python_package(self, testdir, monkeypatch):
649        import warnings
650
651        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False)
652        path = testdir.mkpydir("tpkg")
653        path.join("test_hello.py").write("def test_hello(): pass")
654        path.join("test_world.py").write("def test_world(): pass")
655        result = testdir.runpytest("--pyargs", "tpkg")
656        assert result.ret == 0
657        result.stdout.fnmatch_lines(["*2 passed*"])
658        result = testdir.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True)
659        assert result.ret == 0
660        result.stdout.fnmatch_lines(["*1 passed*"])
661
662        empty_package = testdir.mkpydir("empty_package")
663        monkeypatch.setenv("PYTHONPATH", str(empty_package), prepend=os.pathsep)
664        # the path which is not a package raises a warning on pypy;
665        # no idea why only pypy and not normal python warn about it here
666        with warnings.catch_warnings():
667            warnings.simplefilter("ignore", ImportWarning)
668            result = testdir.runpytest("--pyargs", ".")
669        assert result.ret == 0
670        result.stdout.fnmatch_lines(["*2 passed*"])
671
672        monkeypatch.setenv("PYTHONPATH", str(testdir), prepend=os.pathsep)
673        result = testdir.runpytest("--pyargs", "tpkg.test_missing", syspathinsert=True)
674        assert result.ret != 0
675        result.stderr.fnmatch_lines(["*not*found*test_missing*"])
676
677    def test_cmdline_python_namespace_package(self, testdir, monkeypatch):
678        """
679        test --pyargs option with namespace packages (#1567)
680
681        Ref: https://packaging.python.org/guides/packaging-namespace-packages/
682        """
683        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
684
685        search_path = []
686        for dirname in "hello", "world":
687            d = testdir.mkdir(dirname)
688            search_path.append(d)
689            ns = d.mkdir("ns_pkg")
690            ns.join("__init__.py").write(
691                "__import__('pkg_resources').declare_namespace(__name__)"
692            )
693            lib = ns.mkdir(dirname)
694            lib.ensure("__init__.py")
695            lib.join("test_{}.py".format(dirname)).write(
696                "def test_{}(): pass\ndef test_other():pass".format(dirname)
697            )
698
699        # The structure of the test directory is now:
700        # .
701        # ├── hello
702        # │   └── ns_pkg
703        # │       ├── __init__.py
704        # │       └── hello
705        # │           ├── __init__.py
706        # │           └── test_hello.py
707        # └── world
708        #     └── ns_pkg
709        #         ├── __init__.py
710        #         └── world
711        #             ├── __init__.py
712        #             └── test_world.py
713
714        # NOTE: the different/reversed ordering is intentional here.
715        monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path))
716        for p in search_path:
717            monkeypatch.syspath_prepend(p)
718
719        # mixed module and filenames:
720        monkeypatch.chdir("world")
721        result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
722        assert result.ret == 0
723        result.stdout.fnmatch_lines(
724            [
725                "test_hello.py::test_hello*PASSED*",
726                "test_hello.py::test_other*PASSED*",
727                "ns_pkg/world/test_world.py::test_world*PASSED*",
728                "ns_pkg/world/test_world.py::test_other*PASSED*",
729                "*4 passed in*",
730            ]
731        )
732
733        # specify tests within a module
734        testdir.chdir()
735        result = testdir.runpytest(
736            "--pyargs", "-v", "ns_pkg.world.test_world::test_other"
737        )
738        assert result.ret == 0
739        result.stdout.fnmatch_lines(
740            ["*test_world.py::test_other*PASSED*", "*1 passed*"]
741        )
742
743    def test_invoke_test_and_doctestmodules(self, testdir):
744        p = testdir.makepyfile(
745            """
746            def test():
747                pass
748        """
749        )
750        result = testdir.runpytest(str(p) + "::test", "--doctest-modules")
751        result.stdout.fnmatch_lines(["*1 passed*"])
752
753    @pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks")
754    def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
755        """
756        test --pyargs option with packages with path containing symlink can
757        have conftest.py in their package (#2985)
758        """
759        # dummy check that we can actually create symlinks: on Windows `os.symlink` is available,
760        # but normal users require special admin privileges to create symlinks.
761        if sys.platform == "win32":
762            try:
763                os.symlink(
764                    str(testdir.tmpdir.ensure("tmpfile")),
765                    str(testdir.tmpdir.join("tmpfile2")),
766                )
767            except OSError as e:
768                pytest.skip(six.text_type(e.args[0]))
769        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
770
771        dirname = "lib"
772        d = testdir.mkdir(dirname)
773        foo = d.mkdir("foo")
774        foo.ensure("__init__.py")
775        lib = foo.mkdir("bar")
776        lib.ensure("__init__.py")
777        lib.join("test_bar.py").write(
778            "def test_bar(): pass\ndef test_other(a_fixture):pass"
779        )
780        lib.join("conftest.py").write(
781            "import pytest\n@pytest.fixture\ndef a_fixture():pass"
782        )
783
784        d_local = testdir.mkdir("local")
785        symlink_location = os.path.join(str(d_local), "lib")
786        if six.PY2:
787            os.symlink(str(d), symlink_location)
788        else:
789            os.symlink(str(d), symlink_location, target_is_directory=True)
790
791        # The structure of the test directory is now:
792        # .
793        # ├── local
794        # │   └── lib -> ../lib
795        # └── lib
796        #     └── foo
797        #         ├── __init__.py
798        #         └── bar
799        #             ├── __init__.py
800        #             ├── conftest.py
801        #             └── test_bar.py
802
803        # NOTE: the different/reversed ordering is intentional here.
804        search_path = ["lib", os.path.join("local", "lib")]
805        monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path))
806        for p in search_path:
807            monkeypatch.syspath_prepend(p)
808
809        # module picked up in symlink-ed directory:
810        # It picks up local/lib/foo/bar (symlink) via sys.path.
811        result = testdir.runpytest("--pyargs", "-v", "foo.bar")
812        testdir.chdir()
813        assert result.ret == 0
814        if hasattr(py.path.local, "mksymlinkto"):
815            result.stdout.fnmatch_lines(
816                [
817                    "lib/foo/bar/test_bar.py::test_bar PASSED*",
818                    "lib/foo/bar/test_bar.py::test_other PASSED*",
819                    "*2 passed*",
820                ]
821            )
822        else:
823            result.stdout.fnmatch_lines(
824                [
825                    "*lib/foo/bar/test_bar.py::test_bar PASSED*",
826                    "*lib/foo/bar/test_bar.py::test_other PASSED*",
827                    "*2 passed*",
828                ]
829            )
830
831    def test_cmdline_python_package_not_exists(self, testdir):
832        result = testdir.runpytest("--pyargs", "tpkgwhatv")
833        assert result.ret
834        result.stderr.fnmatch_lines(["ERROR*file*or*package*not*found*"])
835
836    @pytest.mark.xfail(reason="decide: feature or bug")
837    def test_noclass_discovery_if_not_testcase(self, testdir):
838        testpath = testdir.makepyfile(
839            """
840            import unittest
841            class TestHello(object):
842                def test_hello(self):
843                    assert self.attr
844
845            class RealTest(unittest.TestCase, TestHello):
846                attr = 42
847        """
848        )
849        reprec = testdir.inline_run(testpath)
850        reprec.assertoutcome(passed=1)
851
852    def test_doctest_id(self, testdir):
853        testdir.makefile(
854            ".txt",
855            """
856            >>> x=3
857            >>> x
858            4
859        """,
860        )
861        result = testdir.runpytest("-rf")
862        lines = result.stdout.str().splitlines()
863        for line in lines:
864            if line.startswith(("FAIL ", "FAILED ")):
865                _fail, _sep, testid = line.partition(" ")
866                break
867        result = testdir.runpytest(testid, "-rf")
868        result.stdout.fnmatch_lines(
869            ["FAILED test_doctest_id.txt::test_doctest_id.txt", "*1 failed*"]
870        )
871
872    def test_core_backward_compatibility(self):
873        """Test backward compatibility for get_plugin_manager function. See #787."""
874        import _pytest.config
875
876        assert (
877            type(_pytest.config.get_plugin_manager())
878            is _pytest.config.PytestPluginManager
879        )
880
881    def test_has_plugin(self, request):
882        """Test hasplugin function of the plugin manager (#932)."""
883        assert request.config.pluginmanager.hasplugin("python")
884
885
886class TestDurations(object):
887    source = """
888        import time
889        frag = 0.002
890        def test_something():
891            pass
892        def test_2():
893            time.sleep(frag*5)
894        def test_1():
895            time.sleep(frag)
896        def test_3():
897            time.sleep(frag*10)
898    """
899
900    def test_calls(self, testdir):
901        testdir.makepyfile(self.source)
902        result = testdir.runpytest("--durations=10")
903        assert result.ret == 0
904        result.stdout.fnmatch_lines_random(
905            ["*durations*", "*call*test_3*", "*call*test_2*"]
906        )
907        result.stdout.fnmatch_lines(
908            ["(0.00 durations hidden.  Use -vv to show these durations.)"]
909        )
910
911    def test_calls_show_2(self, testdir):
912        testdir.makepyfile(self.source)
913        result = testdir.runpytest("--durations=2")
914        assert result.ret == 0
915        lines = result.stdout.get_lines_after("*slowest*durations*")
916        assert "4 passed" in lines[2]
917
918    def test_calls_showall(self, testdir):
919        testdir.makepyfile(self.source)
920        result = testdir.runpytest("--durations=0")
921        assert result.ret == 0
922        for x in "23":
923            for y in ("call",):  # 'setup', 'call', 'teardown':
924                for line in result.stdout.lines:
925                    if ("test_%s" % x) in line and y in line:
926                        break
927                else:
928                    raise AssertionError("not found {} {}".format(x, y))
929
930    def test_calls_showall_verbose(self, testdir):
931        testdir.makepyfile(self.source)
932        result = testdir.runpytest("--durations=0", "-vv")
933        assert result.ret == 0
934        for x in "123":
935            for y in ("call",):  # 'setup', 'call', 'teardown':
936                for line in result.stdout.lines:
937                    if ("test_%s" % x) in line and y in line:
938                        break
939                else:
940                    raise AssertionError("not found {} {}".format(x, y))
941
942    def test_with_deselected(self, testdir):
943        testdir.makepyfile(self.source)
944        result = testdir.runpytest("--durations=2", "-k test_2")
945        assert result.ret == 0
946        result.stdout.fnmatch_lines(["*durations*", "*call*test_2*"])
947
948    def test_with_failing_collection(self, testdir):
949        testdir.makepyfile(self.source)
950        testdir.makepyfile(test_collecterror="""xyz""")
951        result = testdir.runpytest("--durations=2", "-k test_1")
952        assert result.ret == 2
953        result.stdout.fnmatch_lines(["*Interrupted: 1 errors during collection*"])
954        # Collection errors abort test execution, therefore no duration is
955        # output
956        assert "duration" not in result.stdout.str()
957
958    def test_with_not(self, testdir):
959        testdir.makepyfile(self.source)
960        result = testdir.runpytest("-k not 1")
961        assert result.ret == 0
962
963
964class TestDurationWithFixture(object):
965    source = """
966        import pytest
967        import time
968        frag = 0.01
969
970        @pytest.fixture
971        def setup_fixt():
972            time.sleep(frag)
973
974        def test_1(setup_fixt):
975            time.sleep(frag)
976    """
977
978    def test_setup_function(self, testdir):
979        testdir.makepyfile(self.source)
980        result = testdir.runpytest("--durations=10")
981        assert result.ret == 0
982
983        result.stdout.fnmatch_lines_random(
984            """
985            *durations*
986            * setup *test_1*
987            * call *test_1*
988        """
989        )
990
991
992def test_zipimport_hook(testdir, tmpdir):
993    """Test package loader is being used correctly (see #1837)."""
994    zipapp = pytest.importorskip("zipapp")
995    testdir.tmpdir.join("app").ensure(dir=1)
996    testdir.makepyfile(
997        **{
998            "app/foo.py": """
999            import pytest
1000            def main():
1001                pytest.main(['--pyarg', 'foo'])
1002        """
1003        }
1004    )
1005    target = tmpdir.join("foo.zip")
1006    zipapp.create_archive(str(testdir.tmpdir.join("app")), str(target), main="foo:main")
1007    result = testdir.runpython(target)
1008    assert result.ret == 0
1009    result.stderr.fnmatch_lines(["*not found*foo*"])
1010    assert "INTERNALERROR>" not in result.stdout.str()
1011
1012
1013def test_import_plugin_unicode_name(testdir):
1014    testdir.makepyfile(myplugin="")
1015    testdir.makepyfile(
1016        """
1017        def test(): pass
1018    """
1019    )
1020    testdir.makeconftest(
1021        """
1022        pytest_plugins = [u'myplugin']
1023    """
1024    )
1025    r = testdir.runpytest()
1026    assert r.ret == 0
1027
1028
1029def test_pytest_plugins_as_module(testdir):
1030    """Do not raise an error if pytest_plugins attribute is a module (#3899)"""
1031    testdir.makepyfile(
1032        **{
1033            "__init__.py": "",
1034            "pytest_plugins.py": "",
1035            "conftest.py": "from . import pytest_plugins",
1036            "test_foo.py": "def test(): pass",
1037        }
1038    )
1039    result = testdir.runpytest()
1040    result.stdout.fnmatch_lines(["* 1 passed in *"])
1041
1042
1043def test_deferred_hook_checking(testdir):
1044    """
1045    Check hooks as late as possible (#1821).
1046    """
1047    testdir.syspathinsert()
1048    testdir.makepyfile(
1049        **{
1050            "plugin.py": """
1051        class Hooks(object):
1052            def pytest_my_hook(self, config):
1053                pass
1054
1055        def pytest_configure(config):
1056            config.pluginmanager.add_hookspecs(Hooks)
1057        """,
1058            "conftest.py": """
1059            pytest_plugins = ['plugin']
1060            def pytest_my_hook(config):
1061                return 40
1062        """,
1063            "test_foo.py": """
1064            def test(request):
1065                assert request.config.hook.pytest_my_hook(config=request.config) == [40]
1066        """,
1067        }
1068    )
1069    result = testdir.runpytest()
1070    result.stdout.fnmatch_lines(["* 1 passed *"])
1071
1072
1073def test_fixture_values_leak(testdir):
1074    """Ensure that fixture objects are properly destroyed by the garbage collector at the end of their expected
1075    life-times (#2981).
1076    """
1077    testdir.makepyfile(
1078        """
1079        import attr
1080        import gc
1081        import pytest
1082        import weakref
1083
1084        @attr.s
1085        class SomeObj(object):
1086            name = attr.ib()
1087
1088        fix_of_test1_ref = None
1089        session_ref = None
1090
1091        @pytest.fixture(scope='session')
1092        def session_fix():
1093            global session_ref
1094            obj = SomeObj(name='session-fixture')
1095            session_ref = weakref.ref(obj)
1096            return obj
1097
1098        @pytest.fixture
1099        def fix(session_fix):
1100            global fix_of_test1_ref
1101            obj = SomeObj(name='local-fixture')
1102            fix_of_test1_ref = weakref.ref(obj)
1103            return obj
1104
1105        def test1(fix):
1106            assert fix_of_test1_ref() is fix
1107
1108        def test2():
1109            gc.collect()
1110            # fixture "fix" created during test1 must have been destroyed by now
1111            assert fix_of_test1_ref() is None
1112    """
1113    )
1114    result = testdir.runpytest()
1115    result.stdout.fnmatch_lines(["* 2 passed *"])
1116
1117
1118def test_fixture_order_respects_scope(testdir):
1119    """Ensure that fixtures are created according to scope order, regression test for #2405
1120    """
1121    testdir.makepyfile(
1122        """
1123        import pytest
1124
1125        data = {}
1126
1127        @pytest.fixture(scope='module')
1128        def clean_data():
1129            data.clear()
1130
1131        @pytest.fixture(autouse=True)
1132        def add_data():
1133            data.update(value=True)
1134
1135        @pytest.mark.usefixtures('clean_data')
1136        def test_value():
1137            assert data.get('value')
1138    """
1139    )
1140    result = testdir.runpytest()
1141    assert result.ret == 0
1142
1143
1144def test_frame_leak_on_failing_test(testdir):
1145    """pytest would leak garbage referencing the frames of tests that failed that could never be reclaimed (#2798)
1146
1147    Unfortunately it was not possible to remove the actual circles because most of them
1148    are made of traceback objects which cannot be weakly referenced. Those objects at least
1149    can be eventually claimed by the garbage collector.
1150    """
1151    testdir.makepyfile(
1152        """
1153        import gc
1154        import weakref
1155
1156        class Obj:
1157            pass
1158
1159        ref = None
1160
1161        def test1():
1162            obj = Obj()
1163            global ref
1164            ref = weakref.ref(obj)
1165            assert 0
1166
1167        def test2():
1168            gc.collect()
1169            assert ref() is None
1170    """
1171    )
1172    result = testdir.runpytest_subprocess()
1173    result.stdout.fnmatch_lines(["*1 failed, 1 passed in*"])
1174
1175
1176def test_fixture_mock_integration(testdir):
1177    """Test that decorators applied to fixture are left working (#3774)"""
1178    p = testdir.copy_example("acceptance/fixture_mock_integration.py")
1179    result = testdir.runpytest(p)
1180    result.stdout.fnmatch_lines(["*1 passed*"])
1181
1182
1183def test_usage_error_code(testdir):
1184    result = testdir.runpytest("-unknown-option-")
1185    assert result.ret == EXIT_USAGEERROR
1186
1187
1188@pytest.mark.skipif(
1189    sys.version_info[:2] < (3, 5), reason="async def syntax python 3.5+ only"
1190)
1191@pytest.mark.filterwarnings("default")
1192def test_warn_on_async_function(testdir):
1193    testdir.makepyfile(
1194        test_async="""
1195        async def test_1():
1196            pass
1197        async def test_2():
1198            pass
1199    """
1200    )
1201    result = testdir.runpytest()
1202    result.stdout.fnmatch_lines(
1203        [
1204            "test_async.py::test_1",
1205            "test_async.py::test_2",
1206            "*Coroutine functions are not natively supported*",
1207            "*2 skipped, 2 warnings in*",
1208        ]
1209    )
1210    # ensure our warning message appears only once
1211    assert (
1212        result.stdout.str().count("Coroutine functions are not natively supported") == 1
1213    )
1214