test_docstrings.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import difflib
  2. import cytoolz
  3. from cytoolz import curry, identity, keyfilter, valfilter, merge_with
  4. from cytoolz.utils import raises
  5. from dev_skip_test import dev_skip_test
  6. # `cytoolz` functions for which "# doctest: +SKIP" were added.
  7. # This may have been done because the error message may not exactly match.
  8. # The skipped tests should be added below with results and explanations.
  9. skipped_doctests = ['get_in']
  10. @curry
  11. def isfrommod(modname, func):
  12. mod = getattr(func, '__module__', '') or ''
  13. return mod.startswith(modname) or 'toolz.functoolz.curry' in str(type(func))
  14. def convertdoc(doc):
  15. """ Convert docstring from `toolz` to `cytoolz`."""
  16. if hasattr(doc, '__doc__'):
  17. doc = doc.__doc__
  18. doc = doc.replace('toolz', 'cytoolz')
  19. doc = doc.replace('dictcytoolz', 'dicttoolz')
  20. doc = doc.replace('funccytoolz', 'functoolz')
  21. doc = doc.replace('itercytoolz', 'itertoolz')
  22. doc = doc.replace('cytoolz.readthedocs', 'toolz.readthedocs')
  23. return doc
  24. @dev_skip_test
  25. def test_docstrings_uptodate():
  26. import toolz
  27. differ = difflib.Differ()
  28. # only consider items created in both `toolz` and `cytoolz`
  29. toolz_dict = valfilter(isfrommod('toolz'), toolz.__dict__)
  30. cytoolz_dict = valfilter(isfrommod('cytoolz'), cytoolz.__dict__)
  31. # only test functions that have docstrings defined in `toolz`
  32. toolz_dict = valfilter(lambda x: getattr(x, '__doc__', ''), toolz_dict)
  33. # full API coverage should be tested elsewhere
  34. toolz_dict = keyfilter(lambda x: x in cytoolz_dict, toolz_dict)
  35. cytoolz_dict = keyfilter(lambda x: x in toolz_dict, cytoolz_dict)
  36. d = merge_with(identity, toolz_dict, cytoolz_dict)
  37. for key, (toolz_func, cytoolz_func) in d.items():
  38. # only check if the new doctstring *contains* the expected docstring
  39. # in Python < 3.13 the second line is indented, in 3.13+
  40. # it is not, strip all lines to fudge it
  41. toolz_doc = "\n".join((line.strip() for line in convertdoc(toolz_func).splitlines()))
  42. cytoolz_doc = "\n".join((line.strip() for line in cytoolz_func.__doc__.splitlines()))
  43. if toolz_doc not in cytoolz_doc:
  44. diff = list(differ.compare(toolz_doc.splitlines(),
  45. cytoolz_doc.splitlines()))
  46. fulldiff = list(diff)
  47. # remove additional lines at the beginning
  48. while diff and diff[0].startswith('+'):
  49. diff.pop(0)
  50. # remove additional lines at the end
  51. while diff and diff[-1].startswith('+'):
  52. diff.pop()
  53. def checkbad(line):
  54. return (line.startswith('+') and
  55. not ('# doctest: +SKIP' in line and
  56. key in skipped_doctests))
  57. if any(map(checkbad, diff)):
  58. assert False, 'Error: cytoolz.%s has a bad docstring:\n%s\n' % (
  59. key, '\n'.join(fulldiff))
  60. def test_get_in_doctest():
  61. # Original doctest:
  62. # >>> get_in(['y'], {}, no_default=True)
  63. # Traceback (most recent call last):
  64. # ...
  65. # KeyError: 'y'
  66. # cytoolz result:
  67. # KeyError:
  68. raises(KeyError, lambda: cytoolz.get_in(['y'], {}, no_default=True))