Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions python_files/tests/unittestadapter/.data/doctest_patched_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
Patched doctest module.
This module's doctests will be patched to have proper IDs.

>>> 2 + 2
4
"""


def example_function():
"""
Example function with doctest.

>>> example_function()
'works'
"""
return "works"
7 changes: 7 additions & 0 deletions python_files/tests/unittestadapter/.data/doctest_standard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Standard doctest module that should be blocked.
This has a simple doctest with short ID.

>>> 2 + 2
4
"""
50 changes: 50 additions & 0 deletions python_files/tests/unittestadapter/.data/test_doctest_patched.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
"""Test file with patched doctest integration that should work."""

import unittest
import doctest
import sys
import doctest_patched_module


# Patch DocTestCase to modify test IDs to be compatible with the extension
original_init = doctest.DocTestCase.__init__


def patched_init(self, test, optionflags=0, setUp=None, tearDown=None, checker=None):
"""Patch to modify doctest names to have proper hierarchy."""
if hasattr(test, 'name'):
# Get module name
module_hierarchy = test.name.split('.')
module_name = module_hierarchy[0] if module_hierarchy else 'unknown'

# Reconstruct with proper formatting to have enough components
# Format: module.file.class.function
if test.filename.endswith('.py'):
file_base = test.filename.split('/')[-1].replace('.py', '')
test_name = test.name.split('.')[-1] if '.' in test.name else test.name
# Create a properly formatted ID with enough components
test.name = f"{module_name}.{file_base}._DocTests.{test_name}"

# Call original init
original_init(self, test, optionflags, setUp, tearDown, checker)


# Apply the patch
doctest.DocTestCase.__init__ = patched_init


def load_tests(loader, tests, ignore):
"""
Standard hook for unittest to load tests.
This uses patched doctest to create compatible test IDs.
"""
tests.addTests(doctest.DocTestSuite(doctest_patched_module))
return tests


# Clean up the patch after loading
def tearDownModule():
"""Restore original DocTestCase.__init__"""
doctest.DocTestCase.__init__ = original_init
16 changes: 16 additions & 0 deletions python_files/tests/unittestadapter/.data/test_doctest_standard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
"""Test file with standard doctest integration that should be blocked."""

import unittest
import doctest
import doctest_standard


def load_tests(loader, tests, ignore):
"""
Standard hook for unittest to load tests.
This uses standard doctest without any patching.
"""
tests.addTests(doctest.DocTestSuite(doctest_standard))
return tests
45 changes: 45 additions & 0 deletions python_files/tests/unittestadapter/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,48 @@ def test_build_empty_tree() -> None:
assert tests is not None
assert tests.get("children") == []
assert not errors


def test_doctest_standard_blocked() -> None:
"""Standard doctests with short IDs should be skipped with an error message."""
start_dir = os.fsdecode(TEST_DATA_PATH)
pattern = "test_doctest_standard*"

loader = unittest.TestLoader()
suite = loader.discover(start_dir, pattern)
tests, errors = build_test_tree(suite, start_dir)

# Should return a tree but with no test children (since doctests are skipped)
assert tests is not None
# Check that we got an error about doctests not being supported
assert len(errors) > 0
assert "Skipping doctest as it is not supported for the extension" in errors[0]


def test_doctest_patched_works() -> None:
"""Patched doctests with properly formatted IDs should be processed normally."""
start_dir = os.fsdecode(TEST_DATA_PATH)
pattern = "test_doctest_patched*"

loader = unittest.TestLoader()
suite = loader.discover(start_dir, pattern)
tests, errors = build_test_tree(suite, start_dir)

# Should successfully build a tree with the patched doctest
assert tests is not None
# The patched doctests should have proper IDs and be included
# We should find at least one test child (the doctests that were patched)
def count_tests(node):
"""Recursively count test nodes."""
if node.get("type_") == "test":
return 1
count = 0
for child in node.get("children", []):
count += count_tests(child)
return count

test_count = count_tests(tests)
# We expect at least the module doctest and function doctest
assert test_count > 0, "Patched doctests should be included in the tree"
# Should not have doctest-related errors since they're properly formatted
assert not any("doctest" in str(e).lower() for e in errors)
13 changes: 7 additions & 6 deletions python_files/unittestadapter/pvsc_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,6 @@ def build_test_tree(
root = build_test_node(top_level_directory, directory_path.name, TestNodeTypeEnum.folder)

for test_case in get_test_case(suite):
if isinstance(test_case, doctest.DocTestCase):
print(
"Skipping doctest as it is not supported for the extension. Test case: ", test_case
)
error = ["Skipping doctest as it is not supported for the extension."]
continue
test_id = test_case.id()
if test_id.startswith("unittest.loader._FailedTest"):
error.append(str(test_case._exception)) # type: ignore # noqa: SLF001
Expand All @@ -221,6 +215,13 @@ def build_test_tree(
else:
# Get the static test path components: filename, class name and function name.
components = test_id.split(".")
# Check if this is a doctest with insufficient components that would cause unpacking to fail
if len(components) < 3 and isinstance(test_case, doctest.DocTestCase):
print(
"Skipping doctest as it is not supported for the extension. Test case: ", test_case
)
error = ["Skipping doctest as it is not supported for the extension."]
continue
*folders, filename, class_name, function_name = components
py_filename = f"{filename}.py"

Expand Down
Loading