Skip to content

Commit e2f103f

Browse files
blhsingclaude
andcommitted
Run test_difflib against both pure-Python and C implementations
When the _difflib C accelerator is built, programmatically generate a parallel ``*_PurePython`` TestCase for each existing test class so the same suite covers both implementations. Pure-Python coverage is obtained by patching ``difflib.SequenceMatcher`` to ``_pydifflib.SequenceMatcher`` in setUp / restoring it in tearDown; internal helpers like ``unified_diff`` and ``ndiff`` resolve ``SequenceMatcher`` on ``difflib`` at call time, so patching the module attribute covers the whole pipeline. This mirrors the dual-implementation test pattern used by test_decimal (C* / Py* class pairs) without requiring every existing test method to be parameterised. ``test_html_diff`` also gets a single-line fix: it depended on ``HtmlDiff._default_prefix`` starting at 0, which only held because it ran first. Resetting the counter at the top of the test makes it order-independent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 39a7304 commit e2f103f

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

Lib/test/test_difflib.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import difflib
2+
import _pydifflib
23
from test import support
34
from test.support import findfile, force_colorized
45
from test.support.import_helper import ensure_lazy_imports
@@ -7,6 +8,28 @@
78
import sys
89

910

11+
# Tests below reference ``difflib.SequenceMatcher``. By default that is the
12+
# C-accelerated subclass (when ``_difflib`` is available) or the pure-Python
13+
# class otherwise. The mixin below temporarily swaps it to the pure-Python
14+
# class from ``_pydifflib`` so the same test suite covers both implementations
15+
# whenever the accelerator is built.
16+
_PySequenceMatcher = _pydifflib.SequenceMatcher
17+
_has_c_accelerator = difflib.SequenceMatcher is not _PySequenceMatcher
18+
19+
20+
class _PyImplMixin:
21+
"""Run a TestCase with ``difflib.SequenceMatcher`` patched to pure Python."""
22+
23+
def setUp(self):
24+
super().setUp()
25+
self._orig_SequenceMatcher = difflib.SequenceMatcher
26+
difflib.SequenceMatcher = _PySequenceMatcher
27+
28+
def tearDown(self):
29+
difflib.SequenceMatcher = self._orig_SequenceMatcher
30+
super().tearDown()
31+
32+
1033
class TestWithAscii(unittest.TestCase):
1134
def test_one_insert(self):
1235
sm = difflib.SequenceMatcher(None, 'b' * 100, 'a' + 'b' * 100)
@@ -201,6 +224,10 @@ class TestSFpatches(unittest.TestCase):
201224

202225
def test_html_diff(self):
203226
# Check SF patch 914575 for generating HTML differences
227+
# Reset the global ``HtmlDiff._default_prefix`` counter so that
228+
# generated element IDs are stable when this test runs twice
229+
# (e.g. once per implementation; see _PyImplMixin below).
230+
difflib.HtmlDiff._default_prefix = 0
204231
f1a = ((patch914575_from1 + '123\n'*10)*3)
205232
t1a = (patch914575_to1 + '123\n'*10)*3
206233
f1b = '456\n'*10 + f1a
@@ -657,5 +684,31 @@ def load_tests(loader, tests, pattern):
657684
return tests
658685

659686

687+
# When the C accelerator is present, generate a parallel ``*_PurePython``
688+
# class for each TestCase above so the same tests run against the pure-Python
689+
# implementation as well. Tests that probe import behaviour (LazyImportTest)
690+
# or are inherently implementation-specific are skipped.
691+
def _generate_pure_python_variants():
692+
if not _has_c_accelerator:
693+
return
694+
skip = {"LazyImportTest"}
695+
module = sys.modules[__name__]
696+
for name in list(vars(module)):
697+
cls = getattr(module, name)
698+
if (isinstance(cls, type)
699+
and issubclass(cls, unittest.TestCase)
700+
and cls is not unittest.TestCase
701+
and not name.endswith("_PurePython")
702+
and name not in skip):
703+
new_name = name + "_PurePython"
704+
new_cls = type(new_name, (_PyImplMixin, cls), {})
705+
setattr(module, new_name, new_cls)
706+
707+
708+
_generate_pure_python_variants()
709+
710+
711+
712+
660713
if __name__ == '__main__':
661714
unittest.main()

0 commit comments

Comments
 (0)