1import os
2import sys
3from unittest import mock
4
5import pytest
6from _pytest.config import ExitCode
7from _pytest.mark import MarkGenerator as Mark
8from _pytest.mark.structures import EMPTY_PARAMETERSET_OPTION
9from _pytest.nodes import Collector
10from _pytest.nodes import Node
11
12
13class TestMark:
14    @pytest.mark.parametrize("attr", ["mark", "param"])
15    @pytest.mark.parametrize("modulename", ["py.test", "pytest"])
16    def test_pytest_exists_in_namespace_all(self, attr: str, modulename: str) -> None:
17        module = sys.modules[modulename]
18        assert attr in module.__all__  # type: ignore
19
20    def test_pytest_mark_notcallable(self) -> None:
21        mark = Mark()
22        with pytest.raises(TypeError):
23            mark()  # type: ignore[operator]
24
25    def test_mark_with_param(self):
26        def some_function(abc):
27            pass
28
29        class SomeClass:
30            pass
31
32        assert pytest.mark.foo(some_function) is some_function
33        marked_with_args = pytest.mark.foo.with_args(some_function)
34        assert marked_with_args is not some_function  # type: ignore[comparison-overlap]
35
36        assert pytest.mark.foo(SomeClass) is SomeClass
37        assert pytest.mark.foo.with_args(SomeClass) is not SomeClass  # type: ignore[comparison-overlap]
38
39    def test_pytest_mark_name_starts_with_underscore(self):
40        mark = Mark()
41        with pytest.raises(AttributeError):
42            mark._some_name
43
44
45def test_marked_class_run_twice(testdir):
46    """Test fails file is run twice that contains marked class.
47    See issue#683.
48    """
49    py_file = testdir.makepyfile(
50        """
51    import pytest
52    @pytest.mark.parametrize('abc', [1, 2, 3])
53    class Test1(object):
54        def test_1(self, abc):
55            assert abc in [1, 2, 3]
56    """
57    )
58    file_name = os.path.basename(py_file.strpath)
59    rec = testdir.inline_run(file_name, file_name)
60    rec.assertoutcome(passed=6)
61
62
63def test_ini_markers(testdir):
64    testdir.makeini(
65        """
66        [pytest]
67        markers =
68            a1: this is a webtest marker
69            a2: this is a smoke marker
70    """
71    )
72    testdir.makepyfile(
73        """
74        def test_markers(pytestconfig):
75            markers = pytestconfig.getini("markers")
76            print(markers)
77            assert len(markers) >= 2
78            assert markers[0].startswith("a1:")
79            assert markers[1].startswith("a2:")
80    """
81    )
82    rec = testdir.inline_run()
83    rec.assertoutcome(passed=1)
84
85
86def test_markers_option(testdir):
87    testdir.makeini(
88        """
89        [pytest]
90        markers =
91            a1: this is a webtest marker
92            a1some: another marker
93            nodescription
94    """
95    )
96    result = testdir.runpytest("--markers")
97    result.stdout.fnmatch_lines(
98        ["*a1*this is a webtest*", "*a1some*another marker", "*nodescription*"]
99    )
100
101
102def test_ini_markers_whitespace(testdir):
103    testdir.makeini(
104        """
105        [pytest]
106        markers =
107            a1 : this is a whitespace marker
108    """
109    )
110    testdir.makepyfile(
111        """
112        import pytest
113
114        @pytest.mark.a1
115        def test_markers():
116            assert True
117    """
118    )
119    rec = testdir.inline_run("--strict-markers", "-m", "a1")
120    rec.assertoutcome(passed=1)
121
122
123def test_marker_without_description(testdir):
124    testdir.makefile(
125        ".cfg",
126        setup="""
127        [tool:pytest]
128        markers=slow
129    """,
130    )
131    testdir.makeconftest(
132        """
133        import pytest
134        pytest.mark.xfail('FAIL')
135    """
136    )
137    ftdir = testdir.mkdir("ft1_dummy")
138    testdir.tmpdir.join("conftest.py").move(ftdir.join("conftest.py"))
139    rec = testdir.runpytest("--strict-markers")
140    rec.assert_outcomes()
141
142
143def test_markers_option_with_plugin_in_current_dir(testdir):
144    testdir.makeconftest('pytest_plugins = "flip_flop"')
145    testdir.makepyfile(
146        flip_flop="""\
147        def pytest_configure(config):
148            config.addinivalue_line("markers", "flip:flop")
149
150        def pytest_generate_tests(metafunc):
151            try:
152                mark = metafunc.function.flipper
153            except AttributeError:
154                return
155            metafunc.parametrize("x", (10, 20))"""
156    )
157    testdir.makepyfile(
158        """\
159        import pytest
160        @pytest.mark.flipper
161        def test_example(x):
162            assert x"""
163    )
164
165    result = testdir.runpytest("--markers")
166    result.stdout.fnmatch_lines(["*flip*flop*"])
167
168
169def test_mark_on_pseudo_function(testdir):
170    testdir.makepyfile(
171        """
172        import pytest
173
174        @pytest.mark.r(lambda x: 0/0)
175        def test_hello():
176            pass
177    """
178    )
179    reprec = testdir.inline_run()
180    reprec.assertoutcome(passed=1)
181
182
183@pytest.mark.parametrize("option_name", ["--strict-markers", "--strict"])
184def test_strict_prohibits_unregistered_markers(testdir, option_name):
185    testdir.makepyfile(
186        """
187        import pytest
188        @pytest.mark.unregisteredmark
189        def test_hello():
190            pass
191    """
192    )
193    result = testdir.runpytest(option_name)
194    assert result.ret != 0
195    result.stdout.fnmatch_lines(
196        ["'unregisteredmark' not found in `markers` configuration option"]
197    )
198
199
200@pytest.mark.parametrize(
201    ("expr", "expected_passed"),
202    [
203        ("xyz", ["test_one"]),
204        ("(((  xyz))  )", ["test_one"]),
205        ("not not xyz", ["test_one"]),
206        ("xyz and xyz2", []),
207        ("xyz2", ["test_two"]),
208        ("xyz or xyz2", ["test_one", "test_two"]),
209    ],
210)
211def test_mark_option(expr: str, expected_passed: str, testdir) -> None:
212    testdir.makepyfile(
213        """
214        import pytest
215        @pytest.mark.xyz
216        def test_one():
217            pass
218        @pytest.mark.xyz2
219        def test_two():
220            pass
221    """
222    )
223    rec = testdir.inline_run("-m", expr)
224    passed, skipped, fail = rec.listoutcomes()
225    passed = [x.nodeid.split("::")[-1] for x in passed]
226    assert passed == expected_passed
227
228
229@pytest.mark.parametrize(
230    ("expr", "expected_passed"),
231    [("interface", ["test_interface"]), ("not interface", ["test_nointer"])],
232)
233def test_mark_option_custom(expr: str, expected_passed: str, testdir) -> None:
234    testdir.makeconftest(
235        """
236        import pytest
237        def pytest_collection_modifyitems(items):
238            for item in items:
239                if "interface" in item.nodeid:
240                    item.add_marker(pytest.mark.interface)
241    """
242    )
243    testdir.makepyfile(
244        """
245        def test_interface():
246            pass
247        def test_nointer():
248            pass
249    """
250    )
251    rec = testdir.inline_run("-m", expr)
252    passed, skipped, fail = rec.listoutcomes()
253    passed = [x.nodeid.split("::")[-1] for x in passed]
254    assert passed == expected_passed
255
256
257@pytest.mark.parametrize(
258    ("expr", "expected_passed"),
259    [
260        ("interface", ["test_interface"]),
261        ("not interface", ["test_nointer", "test_pass", "test_1", "test_2"]),
262        ("pass", ["test_pass"]),
263        ("not pass", ["test_interface", "test_nointer", "test_1", "test_2"]),
264        ("not not not (pass)", ["test_interface", "test_nointer", "test_1", "test_2"]),
265        ("1 or 2", ["test_1", "test_2"]),
266        ("not (1 or 2)", ["test_interface", "test_nointer", "test_pass"]),
267    ],
268)
269def test_keyword_option_custom(expr: str, expected_passed: str, testdir) -> None:
270    testdir.makepyfile(
271        """
272        def test_interface():
273            pass
274        def test_nointer():
275            pass
276        def test_pass():
277            pass
278        def test_1():
279            pass
280        def test_2():
281            pass
282    """
283    )
284    rec = testdir.inline_run("-k", expr)
285    passed, skipped, fail = rec.listoutcomes()
286    passed = [x.nodeid.split("::")[-1] for x in passed]
287    assert passed == expected_passed
288
289
290def test_keyword_option_considers_mark(testdir):
291    testdir.copy_example("marks/marks_considered_keywords")
292    rec = testdir.inline_run("-k", "foo")
293    passed = rec.listoutcomes()[0]
294    assert len(passed) == 1
295
296
297@pytest.mark.parametrize(
298    ("expr", "expected_passed"),
299    [
300        ("None", ["test_func[None]"]),
301        ("[1.3]", ["test_func[1.3]"]),
302        ("2-3", ["test_func[2-3]"]),
303    ],
304)
305def test_keyword_option_parametrize(expr: str, expected_passed: str, testdir) -> None:
306    testdir.makepyfile(
307        """
308        import pytest
309        @pytest.mark.parametrize("arg", [None, 1.3, "2-3"])
310        def test_func(arg):
311            pass
312    """
313    )
314    rec = testdir.inline_run("-k", expr)
315    passed, skipped, fail = rec.listoutcomes()
316    passed = [x.nodeid.split("::")[-1] for x in passed]
317    assert passed == expected_passed
318
319
320def test_parametrize_with_module(testdir):
321    testdir.makepyfile(
322        """
323        import pytest
324        @pytest.mark.parametrize("arg", [pytest,])
325        def test_func(arg):
326            pass
327    """
328    )
329    rec = testdir.inline_run()
330    passed, skipped, fail = rec.listoutcomes()
331    expected_id = "test_func[" + pytest.__name__ + "]"
332    assert passed[0].nodeid.split("::")[-1] == expected_id
333
334
335@pytest.mark.parametrize(
336    ("expr", "expected_error"),
337    [
338        (
339            "foo or",
340            "at column 7: expected not OR left parenthesis OR identifier; got end of input",
341        ),
342        (
343            "foo or or",
344            "at column 8: expected not OR left parenthesis OR identifier; got or",
345        ),
346        ("(foo", "at column 5: expected right parenthesis; got end of input",),
347        ("foo bar", "at column 5: expected end of input; got identifier",),
348        (
349            "or or",
350            "at column 1: expected not OR left parenthesis OR identifier; got or",
351        ),
352        (
353            "not or",
354            "at column 5: expected not OR left parenthesis OR identifier; got or",
355        ),
356    ],
357)
358def test_keyword_option_wrong_arguments(
359    expr: str, expected_error: str, testdir, capsys
360) -> None:
361    testdir.makepyfile(
362        """
363            def test_func(arg):
364                pass
365        """
366    )
367    testdir.inline_run("-k", expr)
368    err = capsys.readouterr().err
369    assert expected_error in err
370
371
372def test_parametrized_collected_from_command_line(testdir):
373    """Parametrized test not collected if test named specified in command
374    line issue#649."""
375    py_file = testdir.makepyfile(
376        """
377        import pytest
378        @pytest.mark.parametrize("arg", [None, 1.3, "2-3"])
379        def test_func(arg):
380            pass
381    """
382    )
383    file_name = os.path.basename(py_file.strpath)
384    rec = testdir.inline_run(file_name + "::" + "test_func")
385    rec.assertoutcome(passed=3)
386
387
388def test_parametrized_collect_with_wrong_args(testdir):
389    """Test collect parametrized func with wrong number of args."""
390    py_file = testdir.makepyfile(
391        """
392        import pytest
393
394        @pytest.mark.parametrize('foo, bar', [(1, 2, 3)])
395        def test_func(foo, bar):
396            pass
397    """
398    )
399
400    result = testdir.runpytest(py_file)
401    result.stdout.fnmatch_lines(
402        [
403            'test_parametrized_collect_with_wrong_args.py::test_func: in "parametrize" the number of names (2):',
404            "  ['foo', 'bar']",
405            "must be equal to the number of values (3):",
406            "  (1, 2, 3)",
407        ]
408    )
409
410
411def test_parametrized_with_kwargs(testdir):
412    """Test collect parametrized func with wrong number of args."""
413    py_file = testdir.makepyfile(
414        """
415        import pytest
416
417        @pytest.fixture(params=[1,2])
418        def a(request):
419            return request.param
420
421        @pytest.mark.parametrize(argnames='b', argvalues=[1, 2])
422        def test_func(a, b):
423            pass
424    """
425    )
426
427    result = testdir.runpytest(py_file)
428    assert result.ret == 0
429
430
431def test_parametrize_iterator(testdir):
432    """`parametrize` should work with generators (#5354)."""
433    py_file = testdir.makepyfile(
434        """\
435        import pytest
436
437        def gen():
438            yield 1
439            yield 2
440            yield 3
441
442        @pytest.mark.parametrize('a', gen())
443        def test(a):
444            assert a >= 1
445        """
446    )
447    result = testdir.runpytest(py_file)
448    assert result.ret == 0
449    # should not skip any tests
450    result.stdout.fnmatch_lines(["*3 passed*"])
451
452
453class TestFunctional:
454    def test_merging_markers_deep(self, testdir):
455        # issue 199 - propagate markers into nested classes
456        p = testdir.makepyfile(
457            """
458            import pytest
459            class TestA(object):
460                pytestmark = pytest.mark.a
461                def test_b(self):
462                    assert True
463                class TestC(object):
464                    # this one didn't get marked
465                    def test_d(self):
466                        assert True
467        """
468        )
469        items, rec = testdir.inline_genitems(p)
470        for item in items:
471            print(item, item.keywords)
472            assert [x for x in item.iter_markers() if x.name == "a"]
473
474    def test_mark_decorator_subclass_does_not_propagate_to_base(self, testdir):
475        p = testdir.makepyfile(
476            """
477            import pytest
478
479            @pytest.mark.a
480            class Base(object): pass
481
482            @pytest.mark.b
483            class Test1(Base):
484                def test_foo(self): pass
485
486            class Test2(Base):
487                def test_bar(self): pass
488        """
489        )
490        items, rec = testdir.inline_genitems(p)
491        self.assert_markers(items, test_foo=("a", "b"), test_bar=("a",))
492
493    def test_mark_should_not_pass_to_siebling_class(self, testdir):
494        """#568"""
495        p = testdir.makepyfile(
496            """
497            import pytest
498
499            class TestBase(object):
500                def test_foo(self):
501                    pass
502
503            @pytest.mark.b
504            class TestSub(TestBase):
505                pass
506
507
508            class TestOtherSub(TestBase):
509                pass
510
511        """
512        )
513        items, rec = testdir.inline_genitems(p)
514        base_item, sub_item, sub_item_other = items
515        print(items, [x.nodeid for x in items])
516        # new api segregates
517        assert not list(base_item.iter_markers(name="b"))
518        assert not list(sub_item_other.iter_markers(name="b"))
519        assert list(sub_item.iter_markers(name="b"))
520
521    def test_mark_decorator_baseclasses_merged(self, testdir):
522        p = testdir.makepyfile(
523            """
524            import pytest
525
526            @pytest.mark.a
527            class Base(object): pass
528
529            @pytest.mark.b
530            class Base2(Base): pass
531
532            @pytest.mark.c
533            class Test1(Base2):
534                def test_foo(self): pass
535
536            class Test2(Base2):
537                @pytest.mark.d
538                def test_bar(self): pass
539        """
540        )
541        items, rec = testdir.inline_genitems(p)
542        self.assert_markers(items, test_foo=("a", "b", "c"), test_bar=("a", "b", "d"))
543
544    def test_mark_closest(self, testdir):
545        p = testdir.makepyfile(
546            """
547            import pytest
548
549            @pytest.mark.c(location="class")
550            class Test:
551                @pytest.mark.c(location="function")
552                def test_has_own(self):
553                    pass
554
555                def test_has_inherited(self):
556                    pass
557
558        """
559        )
560        items, rec = testdir.inline_genitems(p)
561        has_own, has_inherited = items
562        assert has_own.get_closest_marker("c").kwargs == {"location": "function"}
563        assert has_inherited.get_closest_marker("c").kwargs == {"location": "class"}
564        assert has_own.get_closest_marker("missing") is None
565
566    def test_mark_with_wrong_marker(self, testdir):
567        reprec = testdir.inline_runsource(
568            """
569                import pytest
570                class pytestmark(object):
571                    pass
572                def test_func():
573                    pass
574        """
575        )
576        values = reprec.getfailedcollections()
577        assert len(values) == 1
578        assert "TypeError" in str(values[0].longrepr)
579
580    def test_mark_dynamically_in_funcarg(self, testdir):
581        testdir.makeconftest(
582            """
583            import pytest
584            @pytest.fixture
585            def arg(request):
586                request.applymarker(pytest.mark.hello)
587            def pytest_terminal_summary(terminalreporter):
588                values = terminalreporter.stats['passed']
589                terminalreporter._tw.line("keyword: %s" % values[0].keywords)
590        """
591        )
592        testdir.makepyfile(
593            """
594            def test_func(arg):
595                pass
596        """
597        )
598        result = testdir.runpytest()
599        result.stdout.fnmatch_lines(["keyword: *hello*"])
600
601    def test_no_marker_match_on_unmarked_names(self, testdir):
602        p = testdir.makepyfile(
603            """
604            import pytest
605            @pytest.mark.shouldmatch
606            def test_marked():
607                assert 1
608
609            def test_unmarked():
610                assert 1
611        """
612        )
613        reprec = testdir.inline_run("-m", "test_unmarked", p)
614        passed, skipped, failed = reprec.listoutcomes()
615        assert len(passed) + len(skipped) + len(failed) == 0
616        dlist = reprec.getcalls("pytest_deselected")
617        deselected_tests = dlist[0].items
618        assert len(deselected_tests) == 2
619
620    def test_keywords_at_node_level(self, testdir):
621        testdir.makepyfile(
622            """
623            import pytest
624            @pytest.fixture(scope="session", autouse=True)
625            def some(request):
626                request.keywords["hello"] = 42
627                assert "world" not in request.keywords
628
629            @pytest.fixture(scope="function", autouse=True)
630            def funcsetup(request):
631                assert "world" in request.keywords
632                assert "hello" in  request.keywords
633
634            @pytest.mark.world
635            def test_function():
636                pass
637        """
638        )
639        reprec = testdir.inline_run()
640        reprec.assertoutcome(passed=1)
641
642    def test_keyword_added_for_session(self, testdir):
643        testdir.makeconftest(
644            """
645            import pytest
646            def pytest_collection_modifyitems(session):
647                session.add_marker("mark1")
648                session.add_marker(pytest.mark.mark2)
649                session.add_marker(pytest.mark.mark3)
650                pytest.raises(ValueError, lambda:
651                        session.add_marker(10))
652        """
653        )
654        testdir.makepyfile(
655            """
656            def test_some(request):
657                assert "mark1" in request.keywords
658                assert "mark2" in request.keywords
659                assert "mark3" in request.keywords
660                assert 10 not in request.keywords
661                marker = request.node.get_closest_marker("mark1")
662                assert marker.name == "mark1"
663                assert marker.args == ()
664                assert marker.kwargs == {}
665        """
666        )
667        reprec = testdir.inline_run("-m", "mark1")
668        reprec.assertoutcome(passed=1)
669
670    def assert_markers(self, items, **expected):
671        """Assert that given items have expected marker names applied to them.
672        expected should be a dict of (item name -> seq of expected marker names).
673
674        Note: this could be moved to ``testdir`` if proven to be useful
675        to other modules.
676        """
677        items = {x.name: x for x in items}
678        for name, expected_markers in expected.items():
679            markers = {m.name for m in items[name].iter_markers()}
680            assert markers == set(expected_markers)
681
682    @pytest.mark.filterwarnings("ignore")
683    def test_mark_from_parameters(self, testdir):
684        """#1540"""
685        testdir.makepyfile(
686            """
687            import pytest
688
689            pytestmark = pytest.mark.skipif(True, reason='skip all')
690
691            # skipifs inside fixture params
692            params = [pytest.mark.skipif(False, reason='dont skip')('parameter')]
693
694
695            @pytest.fixture(params=params)
696            def parameter(request):
697                return request.param
698
699
700            def test_1(parameter):
701                assert True
702        """
703        )
704        reprec = testdir.inline_run()
705        reprec.assertoutcome(skipped=1)
706
707    def test_reevaluate_dynamic_expr(self, testdir):
708        """#7360"""
709        py_file1 = testdir.makepyfile(
710            test_reevaluate_dynamic_expr1="""
711            import pytest
712
713            skip = True
714
715            @pytest.mark.skipif("skip")
716            def test_should_skip():
717                assert True
718        """
719        )
720        py_file2 = testdir.makepyfile(
721            test_reevaluate_dynamic_expr2="""
722            import pytest
723
724            skip = False
725
726            @pytest.mark.skipif("skip")
727            def test_should_not_skip():
728                assert True
729        """
730        )
731
732        file_name1 = os.path.basename(py_file1.strpath)
733        file_name2 = os.path.basename(py_file2.strpath)
734        reprec = testdir.inline_run(file_name1, file_name2)
735        reprec.assertoutcome(passed=1, skipped=1)
736
737
738class TestKeywordSelection:
739    def test_select_simple(self, testdir):
740        file_test = testdir.makepyfile(
741            """
742            def test_one():
743                assert 0
744            class TestClass(object):
745                def test_method_one(self):
746                    assert 42 == 43
747        """
748        )
749
750        def check(keyword, name):
751            reprec = testdir.inline_run("-s", "-k", keyword, file_test)
752            passed, skipped, failed = reprec.listoutcomes()
753            assert len(failed) == 1
754            assert failed[0].nodeid.split("::")[-1] == name
755            assert len(reprec.getcalls("pytest_deselected")) == 1
756
757        for keyword in ["test_one", "est_on"]:
758            check(keyword, "test_one")
759        check("TestClass and test", "test_method_one")
760
761    @pytest.mark.parametrize(
762        "keyword",
763        [
764            "xxx",
765            "xxx and test_2",
766            "TestClass",
767            "xxx and not test_1",
768            "TestClass and test_2",
769            "xxx and TestClass and test_2",
770        ],
771    )
772    def test_select_extra_keywords(self, testdir, keyword):
773        p = testdir.makepyfile(
774            test_select="""
775            def test_1():
776                pass
777            class TestClass(object):
778                def test_2(self):
779                    pass
780        """
781        )
782        testdir.makepyfile(
783            conftest="""
784            import pytest
785            @pytest.hookimpl(hookwrapper=True)
786            def pytest_pycollect_makeitem(name):
787                outcome = yield
788                if name == "TestClass":
789                    item = outcome.get_result()
790                    item.extra_keyword_matches.add("xxx")
791        """
792        )
793        reprec = testdir.inline_run(p.dirpath(), "-s", "-k", keyword)
794        print("keyword", repr(keyword))
795        passed, skipped, failed = reprec.listoutcomes()
796        assert len(passed) == 1
797        assert passed[0].nodeid.endswith("test_2")
798        dlist = reprec.getcalls("pytest_deselected")
799        assert len(dlist) == 1
800        assert dlist[0].items[0].name == "test_1"
801
802    def test_select_starton(self, testdir):
803        threepass = testdir.makepyfile(
804            test_threepass="""
805            def test_one(): assert 1
806            def test_two(): assert 1
807            def test_three(): assert 1
808        """
809        )
810        reprec = testdir.inline_run("-k", "test_two:", threepass)
811        passed, skipped, failed = reprec.listoutcomes()
812        assert len(passed) == 2
813        assert not failed
814        dlist = reprec.getcalls("pytest_deselected")
815        assert len(dlist) == 1
816        item = dlist[0].items[0]
817        assert item.name == "test_one"
818
819    def test_keyword_extra(self, testdir):
820        p = testdir.makepyfile(
821            """
822           def test_one():
823               assert 0
824           test_one.mykeyword = True
825        """
826        )
827        reprec = testdir.inline_run("-k", "mykeyword", p)
828        passed, skipped, failed = reprec.countoutcomes()
829        assert failed == 1
830
831    @pytest.mark.xfail
832    def test_keyword_extra_dash(self, testdir):
833        p = testdir.makepyfile(
834            """
835           def test_one():
836               assert 0
837           test_one.mykeyword = True
838        """
839        )
840        # with argparse the argument to an option cannot
841        # start with '-'
842        reprec = testdir.inline_run("-k", "-mykeyword", p)
843        passed, skipped, failed = reprec.countoutcomes()
844        assert passed + skipped + failed == 0
845
846    @pytest.mark.parametrize(
847        "keyword", ["__", "+", ".."],
848    )
849    def test_no_magic_values(self, testdir, keyword: str) -> None:
850        """Make sure the tests do not match on magic values,
851        no double underscored values, like '__dict__' and '+'.
852        """
853        p = testdir.makepyfile(
854            """
855            def test_one(): assert 1
856        """
857        )
858
859        reprec = testdir.inline_run("-k", keyword, p)
860        passed, skipped, failed = reprec.countoutcomes()
861        dlist = reprec.getcalls("pytest_deselected")
862        assert passed + skipped + failed == 0
863        deselected_tests = dlist[0].items
864        assert len(deselected_tests) == 1
865
866    def test_no_match_directories_outside_the_suite(self, testdir):
867        """`-k` should not match against directories containing the test suite (#7040)."""
868        test_contents = """
869            def test_aaa(): pass
870            def test_ddd(): pass
871        """
872        testdir.makepyfile(
873            **{"ddd/tests/__init__.py": "", "ddd/tests/test_foo.py": test_contents}
874        )
875
876        def get_collected_names(*args):
877            _, rec = testdir.inline_genitems(*args)
878            calls = rec.getcalls("pytest_collection_finish")
879            assert len(calls) == 1
880            return [x.name for x in calls[0].session.items]
881
882        # sanity check: collect both tests in normal runs
883        assert get_collected_names() == ["test_aaa", "test_ddd"]
884
885        # do not collect anything based on names outside the collection tree
886        assert get_collected_names("-k", testdir.tmpdir.basename) == []
887
888        # "-k ddd" should only collect "test_ddd", but not
889        # 'test_aaa' just because one of its parent directories is named "ddd";
890        # this was matched previously because Package.name would contain the full path
891        # to the package
892        assert get_collected_names("-k", "ddd") == ["test_ddd"]
893
894
895class TestMarkDecorator:
896    @pytest.mark.parametrize(
897        "lhs, rhs, expected",
898        [
899            (pytest.mark.foo(), pytest.mark.foo(), True),
900            (pytest.mark.foo(), pytest.mark.bar(), False),
901            (pytest.mark.foo(), "bar", False),
902            ("foo", pytest.mark.bar(), False),
903        ],
904    )
905    def test__eq__(self, lhs, rhs, expected):
906        assert (lhs == rhs) == expected
907
908    def test_aliases(self) -> None:
909        md = pytest.mark.foo(1, "2", three=3)
910        assert md.name == "foo"
911        assert md.args == (1, "2")
912        assert md.kwargs == {"three": 3}
913
914
915@pytest.mark.parametrize("mark", [None, "", "skip", "xfail"])
916def test_parameterset_for_parametrize_marks(testdir, mark):
917    if mark is not None:
918        testdir.makeini(
919            """
920        [pytest]
921        {}={}
922        """.format(
923                EMPTY_PARAMETERSET_OPTION, mark
924            )
925        )
926
927    config = testdir.parseconfig()
928    from _pytest.mark import pytest_configure, get_empty_parameterset_mark
929
930    pytest_configure(config)
931    result_mark = get_empty_parameterset_mark(config, ["a"], all)
932    if mark in (None, ""):
933        # normalize to the requested name
934        mark = "skip"
935    assert result_mark.name == mark
936    assert result_mark.kwargs["reason"].startswith("got empty parameter set ")
937    if mark == "xfail":
938        assert result_mark.kwargs.get("run") is False
939
940
941def test_parameterset_for_fail_at_collect(testdir):
942    testdir.makeini(
943        """
944    [pytest]
945    {}=fail_at_collect
946    """.format(
947            EMPTY_PARAMETERSET_OPTION
948        )
949    )
950
951    config = testdir.parseconfig()
952    from _pytest.mark import pytest_configure, get_empty_parameterset_mark
953
954    pytest_configure(config)
955
956    with pytest.raises(
957        Collector.CollectError,
958        match=r"Empty parameter set in 'pytest_configure' at line \d\d+",
959    ):
960        get_empty_parameterset_mark(config, ["a"], pytest_configure)
961
962    p1 = testdir.makepyfile(
963        """
964        import pytest
965
966        @pytest.mark.parametrize("empty", [])
967        def test():
968            pass
969        """
970    )
971    result = testdir.runpytest(str(p1))
972    result.stdout.fnmatch_lines(
973        [
974            "collected 0 items / 1 error",
975            "* ERROR collecting test_parameterset_for_fail_at_collect.py *",
976            "Empty parameter set in 'test' at line 3",
977            "*= 1 error in *",
978        ]
979    )
980    assert result.ret == ExitCode.INTERRUPTED
981
982
983def test_parameterset_for_parametrize_bad_markname(testdir):
984    with pytest.raises(pytest.UsageError):
985        test_parameterset_for_parametrize_marks(testdir, "bad")
986
987
988def test_mark_expressions_no_smear(testdir):
989    testdir.makepyfile(
990        """
991        import pytest
992
993        class BaseTests(object):
994            def test_something(self):
995                pass
996
997        @pytest.mark.FOO
998        class TestFooClass(BaseTests):
999            pass
1000
1001        @pytest.mark.BAR
1002        class TestBarClass(BaseTests):
1003            pass
1004    """
1005    )
1006
1007    reprec = testdir.inline_run("-m", "FOO")
1008    passed, skipped, failed = reprec.countoutcomes()
1009    dlist = reprec.getcalls("pytest_deselected")
1010    assert passed == 1
1011    assert skipped == failed == 0
1012    deselected_tests = dlist[0].items
1013    assert len(deselected_tests) == 1
1014
1015    # todo: fixed
1016    # keywords smear - expected behaviour
1017    # reprec_keywords = testdir.inline_run("-k", "FOO")
1018    # passed_k, skipped_k, failed_k = reprec_keywords.countoutcomes()
1019    # assert passed_k == 2
1020    # assert skipped_k == failed_k == 0
1021
1022
1023def test_addmarker_order():
1024    session = mock.Mock()
1025    session.own_markers = []
1026    session.parent = None
1027    session.nodeid = ""
1028    node = Node.from_parent(session, name="Test")
1029    node.add_marker("foo")
1030    node.add_marker("bar")
1031    node.add_marker("baz", append=False)
1032    extracted = [x.name for x in node.iter_markers()]
1033    assert extracted == ["baz", "foo", "bar"]
1034
1035
1036@pytest.mark.filterwarnings("ignore")
1037def test_markers_from_parametrize(testdir):
1038    """#3605"""
1039    testdir.makepyfile(
1040        """
1041        import pytest
1042
1043        first_custom_mark = pytest.mark.custom_marker
1044        custom_mark = pytest.mark.custom_mark
1045        @pytest.fixture(autouse=True)
1046        def trigger(request):
1047            custom_mark = list(request.node.iter_markers('custom_mark'))
1048            print("Custom mark %s" % custom_mark)
1049
1050        @custom_mark("custom mark non parametrized")
1051        def test_custom_mark_non_parametrized():
1052            print("Hey from test")
1053
1054        @pytest.mark.parametrize(
1055            "obj_type",
1056            [
1057                first_custom_mark("first custom mark")("template"),
1058                pytest.param( # Think this should be recommended way?
1059                    "disk",
1060                    marks=custom_mark('custom mark1')
1061                ),
1062                custom_mark("custom mark2")("vm"),  # Tried also this
1063            ]
1064        )
1065        def test_custom_mark_parametrized(obj_type):
1066            print("obj_type is:", obj_type)
1067    """
1068    )
1069
1070    result = testdir.runpytest()
1071    result.assert_outcomes(passed=4)
1072
1073
1074def test_pytest_param_id_requires_string() -> None:
1075    with pytest.raises(TypeError) as excinfo:
1076        pytest.param(id=True)  # type: ignore[arg-type]
1077    (msg,) = excinfo.value.args
1078    assert msg == "Expected id to be a string, got <class 'bool'>: True"
1079
1080
1081@pytest.mark.parametrize("s", (None, "hello world"))
1082def test_pytest_param_id_allows_none_or_string(s):
1083    assert pytest.param(id=s)
1084
1085
1086@pytest.mark.parametrize("expr", ("NOT internal_err", "NOT (internal_err)", "bogus/"))
1087def test_marker_expr_eval_failure_handling(testdir, expr):
1088    foo = testdir.makepyfile(
1089        """
1090        import pytest
1091
1092        @pytest.mark.internal_err
1093        def test_foo():
1094            pass
1095        """
1096    )
1097    expected = "ERROR: Wrong expression passed to '-m': {}: *".format(expr)
1098    result = testdir.runpytest(foo, "-m", expr)
1099    result.stderr.fnmatch_lines([expected])
1100    assert result.ret == ExitCode.USAGE_ERROR
1101