From 5e097970df24a47a5944f29a6dca1eb1219b5b8e Mon Sep 17 00:00:00 2001
From: Nikolay Kondratyev <kondratyev.nv@gmail.com>
Date: Mon, 28 Oct 2019 00:28:21 +0300
Subject: [PATCH] Fix line detection for properties in doctest tests

Co-Authored-By: Daniel Hahler <github@thequod.de>
---
 changelog/6082.bugfix.rst |  1 +
 src/_pytest/doctest.py    | 10 ++++++
 testing/test_doctest.py   | 65 ++++++++++++++++++++++++++++++++++++---
 3 files changed, 71 insertions(+), 5 deletions(-)
 create mode 100644 changelog/6082.bugfix.rst

diff --git a/changelog/6082.bugfix.rst b/changelog/6082.bugfix.rst
new file mode 100644
index 000000000..24d389257
--- /dev/null
+++ b/changelog/6082.bugfix.rst
@@ -0,0 +1 @@
+Fix line detection for doctest samples inside ``property`` docstrings, as a workaround to `bpo-17446 <https://bugs.python.org/issue17446>`__.
diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py
index f7d96257e..c81f0bfd4 100644
--- a/src/_pytest/doctest.py
+++ b/src/_pytest/doctest.py
@@ -435,6 +435,16 @@ class DoctestModule(pytest.Module):
             https://bugs.python.org/issue25532
             """
 
+            def _find_lineno(self, obj, source_lines):
+                """
+                Doctest code does not take into account `@property`, this is a hackish way to fix it.
+
+                https://bugs.python.org/issue17446
+                """
+                if isinstance(obj, property):
+                    obj = getattr(obj, "fget", obj)
+                return doctest.DocTestFinder._find_lineno(self, obj, source_lines)
+
             def _find(self, tests, obj, name, module, source_lines, globs, seen):
                 if _is_mocked(obj):
                     return
diff --git a/testing/test_doctest.py b/testing/test_doctest.py
index 79095e3e7..bd214e3de 100644
--- a/testing/test_doctest.py
+++ b/testing/test_doctest.py
@@ -287,13 +287,68 @@ class TestDoctests:
             )
         )
         result = testdir.runpytest("--doctest-modules")
+        result.stdout.fnmatch_lines(
+            ["*hello*", "006*>>> 1/0*", "*UNEXPECTED*ZeroDivision*", "*1 failed*"]
+        )
+
+    def test_doctest_linedata_on_property(self, testdir):
+        testdir.makepyfile(
+            """
+            class Sample(object):
+                @property
+                def some_property(self):
+                    '''
+                    >>> Sample().some_property
+                    'another thing'
+                    '''
+                    return 'something'
+            """
+        )
+        result = testdir.runpytest("--doctest-modules")
         result.stdout.fnmatch_lines(
             [
-                "*hello*",
-                "*EXAMPLE LOCATION UNKNOWN, not showing all tests of that example*",
-                "*1/0*",
-                "*UNEXPECTED*ZeroDivision*",
-                "*1 failed*",
+                "*= FAILURES =*",
+                "*_ [[]doctest[]] test_doctest_linedata_on_property.Sample.some_property _*",
+                "004 ",
+                "005         >>> Sample().some_property",
+                "Expected:",
+                "    'another thing'",
+                "Got:",
+                "    'something'",
+                "",
+                "*/test_doctest_linedata_on_property.py:5: DocTestFailure",
+                "*= 1 failed in *",
+            ]
+        )
+
+    def test_doctest_no_linedata_on_overriden_property(self, testdir):
+        testdir.makepyfile(
+            """
+            class Sample(object):
+                @property
+                def some_property(self):
+                    '''
+                    >>> Sample().some_property
+                    'another thing'
+                    '''
+                    return 'something'
+                some_property = property(some_property.__get__, None, None, some_property.__doc__)
+            """
+        )
+        result = testdir.runpytest("--doctest-modules")
+        result.stdout.fnmatch_lines(
+            [
+                "*= FAILURES =*",
+                "*_ [[]doctest[]] test_doctest_no_linedata_on_overriden_property.Sample.some_property _*",
+                "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example",
+                "[?][?][?] >>> Sample().some_property",
+                "Expected:",
+                "    'another thing'",
+                "Got:",
+                "    'something'",
+                "",
+                "*/test_doctest_no_linedata_on_overriden_property.py:None: DocTestFailure",
+                "*= 1 failed in *",
             ]
         )