@@ -641,3 +641,160 @@ def pytest_configure():
641
641
assert "INTERNALERROR" not in result .stderr .str ()
642
642
warning = recwarn .pop ()
643
643
assert str (warning .message ) == "from pytest_configure"
644
+
645
+
646
+ class TestStackLevel :
647
+ @pytest .fixture
648
+ def capwarn (self , testdir ):
649
+ class CapturedWarnings :
650
+ captured = []
651
+
652
+ @classmethod
653
+ def pytest_warning_captured (cls , warning_message , when , item , location ):
654
+ cls .captured .append ((warning_message , location ))
655
+
656
+ testdir .plugins = [CapturedWarnings ()]
657
+
658
+ return CapturedWarnings
659
+
660
+ def test_issue4445_rewrite (self , testdir , capwarn ):
661
+ """#4445: Make sure the warning points to a reasonable location
662
+ See origin of _issue_warning_captured at: _pytest.assertion.rewrite.py:241
663
+ """
664
+ testdir .makepyfile (some_mod = "" )
665
+ conftest = testdir .makeconftest (
666
+ """
667
+ import some_mod
668
+ import pytest
669
+
670
+ pytest.register_assert_rewrite("some_mod")
671
+ """
672
+ )
673
+ testdir .parseconfig ()
674
+
675
+ # with stacklevel=5 the warning originates from register_assert_rewrite
676
+ # function in the created conftest.py
677
+ assert len (capwarn .captured ) == 1
678
+ warning , location = capwarn .captured .pop ()
679
+ file , lineno , func = location
680
+
681
+ assert "Module already imported" in str (warning .message )
682
+ assert file == str (conftest )
683
+ assert func == "<module>" # the above conftest.py
684
+ assert lineno == 4
685
+
686
+ def test_issue4445_preparse (self , testdir , capwarn ):
687
+ """#4445: Make sure the warning points to a reasonable location
688
+ See origin of _issue_warning_captured at: _pytest.config.__init__.py:910
689
+ """
690
+ testdir .makeconftest (
691
+ """
692
+ import nothing
693
+ """
694
+ )
695
+ testdir .parseconfig ("--help" )
696
+
697
+ # with stacklevel=2 the warning should originate from config._preparse and is
698
+ # thrown by an errorneous conftest.py
699
+ assert len (capwarn .captured ) == 1
700
+ warning , location = capwarn .captured .pop ()
701
+ file , _ , func = location
702
+
703
+ assert "could not load initial conftests" in str (warning .message )
704
+ assert "config{sep}__init__.py" .format (sep = os .sep ) in file
705
+ assert func == "_preparse"
706
+
707
+ def test_issue4445_import_plugin (self , testdir , capwarn ):
708
+ """#4445: Make sure the warning points to a reasonable location
709
+ See origin of _issue_warning_captured at: _pytest.config.__init__.py:585
710
+ """
711
+ testdir .makepyfile (
712
+ some_plugin = """
713
+ import pytest
714
+ pytest.skip("thing", allow_module_level=True)
715
+ """
716
+ )
717
+ testdir .syspathinsert ()
718
+ testdir .parseconfig ("-p" , "some_plugin" )
719
+
720
+ # with stacklevel=2 the warning should originate from
721
+ # config.PytestPluginManager.import_plugin is thrown by a skipped plugin
722
+
723
+ # During config parsing the the pluginargs are checked in a while loop
724
+ # that as a result of the argument count runs import_plugin twice, hence
725
+ # two identical warnings are captured (is this intentional?).
726
+ assert len (capwarn .captured ) == 2
727
+ warning , location = capwarn .captured .pop ()
728
+ file , _ , func = location
729
+
730
+ assert "skipped plugin 'some_plugin': thing" in str (warning .message )
731
+ assert "config{sep}__init__.py" .format (sep = os .sep ) in file
732
+ assert func == "import_plugin"
733
+
734
+ def test_issue4445_resultlog (self , testdir , capwarn ):
735
+ """#4445: Make sure the warning points to a reasonable location
736
+ See origin of _issue_warning_captured at: _pytest.resultlog.py:35
737
+ """
738
+ testdir .makepyfile (
739
+ """
740
+ def test_dummy():
741
+ pass
742
+ """
743
+ )
744
+ # Use parseconfigure() because the warning in resultlog.py is triggered in
745
+ # the pytest_configure hook
746
+ testdir .parseconfigure (
747
+ "--result-log={dir}" .format (dir = testdir .tmpdir .join ("result.log" ))
748
+ )
749
+
750
+ # with stacklevel=2 the warning originates from resultlog.pytest_configure
751
+ # and is thrown when --result-log is used
752
+ warning , location = capwarn .captured .pop ()
753
+ file , _ , func = location
754
+
755
+ assert "--result-log is deprecated" in str (warning .message )
756
+ assert "resultlog.py" in file
757
+ assert func == "pytest_configure"
758
+
759
+ def test_issue4445_cacheprovider_set (self , testdir , capwarn ):
760
+ """#4445: Make sure the warning points to a reasonable location
761
+ See origin of _issue_warning_captured at: _pytest.cacheprovider.py:59
762
+ """
763
+ testdir .tmpdir .join (".pytest_cache" ).write ("something wrong" )
764
+ testdir .runpytest (plugins = [capwarn ()])
765
+
766
+ # with stacklevel=3 the warning originates from one stacklevel above
767
+ # _issue_warning_captured in cacheprovider.Cache.set and is thrown
768
+ # when there are errors during cache folder creation
769
+
770
+ # set is called twice (in module stepwise and in cacheprovider) so emits
771
+ # two warnings when there are errors during cache folder creation. (is this intentional?)
772
+ assert len (capwarn .captured ) == 2
773
+ warning , location = capwarn .captured .pop ()
774
+ file , lineno , func = location
775
+
776
+ assert "could not create cache path" in str (warning .message )
777
+ assert "cacheprovider.py" in file
778
+ assert func == "set"
779
+
780
+ def test_issue4445_issue5928_mark_generator (self , testdir ):
781
+ """#4445 and #5928: Make sure the warning from an unknown mark points to
782
+ the test file where this mark is used.
783
+ """
784
+ testfile = testdir .makepyfile (
785
+ """
786
+ import pytest
787
+
788
+ @pytest.mark.unknown
789
+ def test_it():
790
+ pass
791
+ """
792
+ )
793
+ result = testdir .runpytest_subprocess ()
794
+ # with stacklevel=2 the warning should originate from the above created test file
795
+ result .stdout .fnmatch_lines_random (
796
+ [
797
+ "*{testfile}:3*" .format (testfile = str (testfile )),
798
+ "*Unknown pytest.mark.unknown*" ,
799
+ ]
800
+ )
0 commit comments