Skip to content

Conversation

@CuriousLearner
Copy link
Member

@CuriousLearner CuriousLearner commented Nov 16, 2025

Enhanced the _find_lineno method in doctest to correctly identify and report line numbers for doctests defined in __test__ dictionaries when formatted as triple-quoted strings. The implementation finds unique lines in the test string and matches them in the source file to calculate the correct starting line number.

Previously, doctest would report "line None" for __test__ dictionary strings, making it difficult to debug failing tests.

Fixes #69113

Lib/doctest.py Outdated
# contains any unique line.
for offset, line in enumerate(obj.splitlines(keepends=True)):
if source_lines.count(line) == 1:
return source_lines.index(line) - offset
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding this correctly, it would be more precise to say "at least one line that is unique in the file". It also still has the limitation that it finds them only if they are in triple quoted string format (as opposed to for example the admittedly unlikely form of '>>>test\nresult\n')

My version would find a test even it there wasn't a unique line, but is (probably?) less efficient. I'm not sure how likely it is for the "no unique line" case to happen, but with multiple doctests I have the feeling the answer is not 0.

Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right - this approach requires at least one line unique in the entire file. Your multi-line matching approach is more robust since it only needs the sequence of lines to be unique, not any individual line. If >>> x = 1 appears multiple times but the subsequent lines differ, yours would still find the correct location, while this one would fail. The current implementation falls back to None gracefully in that case, but your approach would handle more real-world scenarios.

Would you prefer we use your multi-line matching instead, or perhaps a hybrid that tries the simple approach first and falls back to multi-line matching?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The no unique line cases can be low, but we can't treat them as zero, so I'm leaning towards a hybrid approach or building upon your implementation to cover edge cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, a hybrid approach sounds good.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bitdancer can you please take another look here?

@CuriousLearner CuriousLearner force-pushed the fix-gh-69113 branch 2 times, most recently from 4b6628a to 55e987b Compare December 1, 2025 00:39
Copy link
Member

@bitdancer bitdancer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Just some style comments.

Lib/doctest.py Outdated
# Skip the first line (may be on same line as opening quotes)
# and any blank lines to find a meaningful line to match
start_index = 1
while start_index < len(obj_lines) and not obj_lines[start_index].strip():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines should be wrapped <80.

I also don't find the extra blank lines around the 'skip' block helpful, but I'm not going to object if you leave them.

self.assertEqual(len(include_empty_finder.find(mod)), 1)
self.assertEqual(len(exclude_empty_finder.find(mod)), 0)

def test_lineno_of_test_dict_strings(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, lines should be wrapped < 80. The existing code is mostly consistent about that outside of the places where the doctest text has to be unwrapped.


sys.path.insert(0, tmpdir)
try:
# Import the module
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these comments just restate the code and so don't really add anything. Comments like that tend to rot faster than the code.


# Assert that we found the test and it has a valid line number
self.assertIsNotNone(test_dict_test, "__test__ dict test not found")
# The line number should not be None - this is what the bug fix addresses
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment, however, is helpful ;) You could also mention the bug number here.

self.assertIsNotNone(test_dict_test, "__test__ dict test not found")
# The line number should not be None - this is what the bug fix addresses
self.assertIsNotNone(test_dict_test.lineno,
"Line number should not be None for __test__ dict strings")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think modern style is to put a carriage return after the ( and indent all the arguments four spaces from the start of the line, one argument per line.

@bedevere-app
Copy link

bedevere-app bot commented Dec 5, 2025

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Enhanced the _find_lineno method in doctest to correctly identify and
report line numbers for doctests defined in __test__ dictionaries when
formatted as triple-quoted strings.

Finds a non-blank line in the test string and matches it in the source
file, verifying subsequent lines also match to handle duplicate lines.

Previously, doctest would report "line None" for __test__ dictionary
strings, making it difficult to debug failing tests.

Co-Authored-By: Jurjen N.E. Bos <[email protected]>
Co-Authored-By: R. David Murray <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow doctest to find line number of __test__ strings if formatted as a triple quoted string.

2 participants