New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
unittest assertSequenceEqual can lead to Difflib.compare() crashing on mostly different sequences #65452
Comments
|
When difflib.compare() is used on two moderately large sequences with little or no common elements, a RuntimeError: maximum recursion depth exceeded occurs. This error became apparent when testing another bug (see: bpo-19217) in the AssertEquals() method of the unit test library. A sample program to reproduce this issue in 3.4 is attached. To repo in 2.7 remove the list() wrapper from the range call. |
|
An obvious fix for the recursion limit error is to convert the .compare recursion to iteration with a stack (which I could try to do), but I don't know if the deep recursion is expected in this case, or is a bug. Tim? |
|
It appears to devolve into linear recursion in this case, one per each item in one of the sequences being searched for a match, so even using a stack seems wrong as it'd still be linear (though it would prevent the recursion depth problem). The mutual _fancy_replace + _fancy_helper linear recursion comes from http://hg.python.org/cpython/file/604b74f9a07d/Lib/difflib.py#l1021 |
|
Comparison can certainly trigger a recursion error if the sequences contain no (or few) matching non-junk elements, but contain many "almost matching" elements. If the sequences have lengths M and N, recursion can go as deep as 2*min(M, N) then. Now in the test case, we have two lists of integers. Difflib has no idea what "almost match" might mean for integers. But difflib isn't passed two lists of integers. Instead unittest appears to be converting the input lists to giant strings, then splitting the giant strings on whitespace (or just linefeeds?), and then feeding the resulting lists of substrings to difflib. That doesn't make much sense to me, but so it goes. There are no matches in the two lists of strings, so difflib starts looking for "close matches", and there are a lot of these. At first it decides "[1," and "[100," aren't close enough, but " 10," and " 101," are close enough. That's used as a synch point, and then there's recursion to match the sublists before and after the synch point. Then " 12," and " 102," are close enough, so that pair is used as the next synch point, and another layer of 2-sided recursion. Etc. Whether someone wants to rip the recursion out of _fancy_replace and _fancy_helper is up to them. I wouldn't bother, if this unittest-created problem is the only reported instance. Comparing strings seems a poor idea from the start (there's no guarantee in general, e.g., that A != B implies str(A) != str(B) or repr(A) != repr(B)), and difflib isn't good in any case at comparing sequences with few matching elements (e.g., remove the recursion and it will still take time at best cubic in the common length of the sequences - would it really help to change "a failing unittest bombs with RecursionError" to "a failing unittest seems to take forever"?). I'd suggest instead that unittest, say, locate the first pair of non-equal elements itself, and display that along with a few elements of context on either side. Or something ;-) Something worst-case linear-time, and using != directly on sequence elements (not on strings derived from the sequence elements). |
|
that seems reasonable. unittest's assertSequenceEqual is using this to attempt to display a useful error message as to what the delta was; it should try harder to avoid difflib corner cases. At the very least, unittest should recover from a difflib failure and report a test failure without the possibly nicer message. |
|
I am seeing something similar in difflib.HtmlDiff.make_file() under Python 3.4.3 (windows 7). Do I need to file a separate bug report? File "H:\test\test.py", line 522, in print_differ (repeated many times) File "C:\Python34\lib\difflib.py", line 1791, in _split_line |
|
While this gets fixed, can you provide a workaround? or recommend another library? |
|
workaround idea? https://pypi.org/project/diff-match-patch/ exists and seems maybe maintained (updated in 2020), though I personally haven't used it. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: