test_warnings.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """
  2. Tests which scan for certain occurrences in the code, they may not find
  3. all of these occurrences but should catch almost all. This file was adapted
  4. from numpy.
  5. """
  6. from __future__ import division, absolute_import, print_function
  7. import os
  8. import sys
  9. import scipy
  10. import pytest
  11. if sys.version_info >= (3, 4):
  12. from pathlib import Path
  13. import ast
  14. import tokenize
  15. class ParseCall(ast.NodeVisitor):
  16. def __init__(self):
  17. self.ls = []
  18. def visit_Attribute(self, node):
  19. ast.NodeVisitor.generic_visit(self, node)
  20. self.ls.append(node.attr)
  21. def visit_Name(self, node):
  22. self.ls.append(node.id)
  23. class FindFuncs(ast.NodeVisitor):
  24. def __init__(self, filename):
  25. super().__init__()
  26. self.__filename = filename
  27. self.bad_filters = []
  28. self.bad_stacklevels = []
  29. def visit_Call(self, node):
  30. p = ParseCall()
  31. p.visit(node.func)
  32. ast.NodeVisitor.generic_visit(self, node)
  33. if p.ls[-1] == 'simplefilter' or p.ls[-1] == 'filterwarnings':
  34. if node.args[0].s == "ignore":
  35. self.bad_filters.append(
  36. "{}:{}".format(self.__filename, node.lineno))
  37. if p.ls[-1] == 'warn' and (
  38. len(p.ls) == 1 or p.ls[-2] == 'warnings'):
  39. if self.__filename == "_lib/tests/test_warnings.py":
  40. # This file
  41. return
  42. # See if stacklevel exists:
  43. if len(node.args) == 3:
  44. return
  45. args = {kw.arg for kw in node.keywords}
  46. if "stacklevel" not in args:
  47. self.bad_stacklevels.append(
  48. "{}:{}".format(self.__filename, node.lineno))
  49. @pytest.fixture(scope="session")
  50. def warning_calls():
  51. # combined "ignore" and stacklevel error
  52. base = Path(scipy.__file__).parent
  53. bad_filters = []
  54. bad_stacklevels = []
  55. for path in base.rglob("*.py"):
  56. # use tokenize to auto-detect encoding on systems where no
  57. # default encoding is defined (e.g. LANG='C')
  58. with tokenize.open(str(path)) as file:
  59. tree = ast.parse(file.read(), filename=str(path))
  60. finder = FindFuncs(path.relative_to(base))
  61. finder.visit(tree)
  62. bad_filters.extend(finder.bad_filters)
  63. bad_stacklevels.extend(finder.bad_stacklevels)
  64. return bad_filters, bad_stacklevels
  65. @pytest.mark.slow
  66. @pytest.mark.skipif(sys.version_info < (3, 4), reason="needs Python >= 3.4")
  67. def test_warning_calls_filters(warning_calls):
  68. bad_filters, bad_stacklevels = warning_calls
  69. # There is still one simplefilter occurrence in optimize.py that could be removed.
  70. bad_filters = [item for item in bad_filters
  71. if 'optimize.py' not in item]
  72. # The filterwarnings call in sparse/__init__.py is needed.
  73. bad_filters = [item for item in bad_filters
  74. if os.path.join('sparse', '__init__.py') not in item]
  75. if bad_filters:
  76. raise AssertionError(
  77. "warning ignore filter should not be used, instead, use\n"
  78. "scipy._lib._numpy_compat.suppress_warnings (in tests only);\n"
  79. "found in:\n {}".format(
  80. "\n ".join(bad_filters)))
  81. @pytest.mark.slow
  82. @pytest.mark.skipif(sys.version_info < (3, 4), reason="needs Python >= 3.4")
  83. @pytest.mark.xfail(reason="stacklevels currently missing")
  84. def test_warning_calls_stacklevels(warning_calls):
  85. bad_filters, bad_stacklevels = warning_calls
  86. msg = ""
  87. if bad_filters:
  88. msg += ("warning ignore filter should not be used, instead, use\n"
  89. "scipy._lib._numpy_compat.suppress_warnings (in tests only);\n"
  90. "found in:\n {}".format("\n ".join(bad_filters)))
  91. msg += "\n\n"
  92. if bad_stacklevels:
  93. msg += "warnings should have an appropriate stacklevel:\n {}".format(
  94. "\n ".join(bad_stacklevels))
  95. if msg:
  96. raise AssertionError(msg)