File: test_warnings.py

package info (click to toggle)
python-scipy 1.1.0-7
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 93,828 kB
  • sloc: python: 156,854; ansic: 82,925; fortran: 80,777; cpp: 7,505; makefile: 427; sh: 294
file content (128 lines) | stat: -rw-r--r-- 4,227 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""
Tests which scan for certain occurrences in the code, they may not find
all of these occurrences but should catch almost all. This file was adapted
from numpy.
"""


from __future__ import division, absolute_import, print_function

import os
import sys
import scipy

import pytest


if sys.version_info >= (3, 4):
    from pathlib import Path
    import ast
    import tokenize

    class ParseCall(ast.NodeVisitor):
        def __init__(self):
            self.ls = []

        def visit_Attribute(self, node):
            ast.NodeVisitor.generic_visit(self, node)
            self.ls.append(node.attr)

        def visit_Name(self, node):
            self.ls.append(node.id)

    class FindFuncs(ast.NodeVisitor):
        def __init__(self, filename):
            super().__init__()
            self.__filename = filename
            self.bad_filters = []
            self.bad_stacklevels = []

        def visit_Call(self, node):
            p = ParseCall()
            p.visit(node.func)
            ast.NodeVisitor.generic_visit(self, node)

            if p.ls[-1] == 'simplefilter' or p.ls[-1] == 'filterwarnings':
                if node.args[0].s == "ignore":
                    self.bad_filters.append(
                        "{}:{}".format(self.__filename, node.lineno))

            if p.ls[-1] == 'warn' and (
                    len(p.ls) == 1 or p.ls[-2] == 'warnings'):

                if self.__filename == "_lib/tests/test_warnings.py":
                    # This file
                    return

                # See if stacklevel exists:
                if len(node.args) == 3:
                    return
                args = {kw.arg for kw in node.keywords}
                if "stacklevel" not in args:
                    self.bad_stacklevels.append(
                        "{}:{}".format(self.__filename, node.lineno))


@pytest.fixture(scope="session")
def warning_calls():
    # combined "ignore" and stacklevel error
    base = Path(scipy.__file__).parent

    bad_filters = []
    bad_stacklevels = []

    for path in base.rglob("*.py"):
        # use tokenize to auto-detect encoding on systems where no
        # default encoding is defined (e.g. LANG='C')
        with tokenize.open(str(path)) as file:
            tree = ast.parse(file.read(), filename=str(path))
            finder = FindFuncs(path.relative_to(base))
            finder.visit(tree)
            bad_filters.extend(finder.bad_filters)
            bad_stacklevels.extend(finder.bad_stacklevels)

    return bad_filters, bad_stacklevels


@pytest.mark.slow
@pytest.mark.skipif(sys.version_info < (3, 4), reason="needs Python >= 3.4")
def test_warning_calls_filters(warning_calls):
    bad_filters, bad_stacklevels = warning_calls

    # There is still one missing occurrence in optimize.py,
    # this is one that should be fixed and this removed then.
    bad_filters = [item for item in bad_filters
                   if 'optimize.py' not in item]
    # The filterwarnings calls in sparse are needed.
    bad_filters = [item for item in bad_filters
                   if os.path.join('sparse', '__init__.py') not in item
                   and os.path.join('sparse', 'sputils.py') not in item]

    if bad_filters:
        raise AssertionError(
            "warning ignore filter should not be used, instead, use\n"
            "scipy._lib._numpy_compat.suppress_warnings (in tests only);\n"
            "found in:\n    {}".format(
                "\n    ".join(bad_filters)))


@pytest.mark.slow
@pytest.mark.skipif(sys.version_info < (3, 4), reason="needs Python >= 3.4")
@pytest.mark.xfail(reason="stacklevels currently missing")
def test_warning_calls_stacklevels(warning_calls):
    bad_filters, bad_stacklevels = warning_calls

    msg = ""

    if bad_filters:
        msg += ("warning ignore filter should not be used, instead, use\n"
                "scipy._lib._numpy_compat.suppress_warnings (in tests only);\n"
                "found in:\n    {}".format("\n    ".join(bad_filters)))
        msg += "\n\n"

    if bad_stacklevels:
        msg += "warnings should have an appropriate stacklevel:\n    {}".format(
                "\n    ".join(bad_stacklevels))

    if msg:
        raise AssertionError(msg)