1import contextlib
2import functools
3import importlib
4import re
5import sys
6import warnings
7
8
9def import_deprecated(name):
10    """Import *name* while suppressing DeprecationWarning."""
11    with warnings.catch_warnings():
12        warnings.simplefilter('ignore', category=DeprecationWarning)
13        return importlib.import_module(name)
14
15
16def check_syntax_warning(testcase, statement, errtext='',
17                         *, lineno=1, offset=None):
18    # Test also that a warning is emitted only once.
19    from test.support import check_syntax_error
20    with warnings.catch_warnings(record=True) as warns:
21        warnings.simplefilter('always', SyntaxWarning)
22        compile(statement, '<testcase>', 'exec')
23    testcase.assertEqual(len(warns), 1, warns)
24
25    warn, = warns
26    testcase.assertTrue(issubclass(warn.category, SyntaxWarning),
27                        warn.category)
28    if errtext:
29        testcase.assertRegex(str(warn.message), errtext)
30    testcase.assertEqual(warn.filename, '<testcase>')
31    testcase.assertIsNotNone(warn.lineno)
32    if lineno is not None:
33        testcase.assertEqual(warn.lineno, lineno)
34
35    # SyntaxWarning should be converted to SyntaxError when raised,
36    # since the latter contains more information and provides better
37    # error report.
38    with warnings.catch_warnings(record=True) as warns:
39        warnings.simplefilter('error', SyntaxWarning)
40        check_syntax_error(testcase, statement, errtext,
41                           lineno=lineno, offset=offset)
42    # No warnings are leaked when a SyntaxError is raised.
43    testcase.assertEqual(warns, [])
44
45
46def ignore_warnings(*, category):
47    """Decorator to suppress warnings.
48
49    Use of context managers to hide warnings make diffs
50    more noisy and tools like 'git blame' less useful.
51    """
52    def decorator(test):
53        @functools.wraps(test)
54        def wrapper(self, *args, **kwargs):
55            with warnings.catch_warnings():
56                warnings.simplefilter('ignore', category=category)
57                return test(self, *args, **kwargs)
58        return wrapper
59    return decorator
60
61
62class WarningsRecorder(object):
63    """Convenience wrapper for the warnings list returned on
64       entry to the warnings.catch_warnings() context manager.
65    """
66    def __init__(self, warnings_list):
67        self._warnings = warnings_list
68        self._last = 0
69
70    def __getattr__(self, attr):
71        if len(self._warnings) > self._last:
72            return getattr(self._warnings[-1], attr)
73        elif attr in warnings.WarningMessage._WARNING_DETAILS:
74            return None
75        raise AttributeError("%r has no attribute %r" % (self, attr))
76
77    @property
78    def warnings(self):
79        return self._warnings[self._last:]
80
81    def reset(self):
82        self._last = len(self._warnings)
83
84
85@contextlib.contextmanager
86def check_warnings(*filters, **kwargs):
87    """Context manager to silence warnings.
88
89    Accept 2-tuples as positional arguments:
90        ("message regexp", WarningCategory)
91
92    Optional argument:
93     - if 'quiet' is True, it does not fail if a filter catches nothing
94        (default True without argument,
95         default False if some filters are defined)
96
97    Without argument, it defaults to:
98        check_warnings(("", Warning), quiet=True)
99    """
100    quiet = kwargs.get('quiet')
101    if not filters:
102        filters = (("", Warning),)
103        # Preserve backward compatibility
104        if quiet is None:
105            quiet = True
106    return _filterwarnings(filters, quiet)
107
108
109@contextlib.contextmanager
110def check_no_warnings(testcase, message='', category=Warning, force_gc=False):
111    """Context manager to check that no warnings are emitted.
112
113    This context manager enables a given warning within its scope
114    and checks that no warnings are emitted even with that warning
115    enabled.
116
117    If force_gc is True, a garbage collection is attempted before checking
118    for warnings. This may help to catch warnings emitted when objects
119    are deleted, such as ResourceWarning.
120
121    Other keyword arguments are passed to warnings.filterwarnings().
122    """
123    from test.support import gc_collect
124    with warnings.catch_warnings(record=True) as warns:
125        warnings.filterwarnings('always',
126                                message=message,
127                                category=category)
128        yield
129        if force_gc:
130            gc_collect()
131    testcase.assertEqual(warns, [])
132
133
134@contextlib.contextmanager
135def check_no_resource_warning(testcase):
136    """Context manager to check that no ResourceWarning is emitted.
137
138    Usage:
139
140        with check_no_resource_warning(self):
141            f = open(...)
142            ...
143            del f
144
145    You must remove the object which may emit ResourceWarning before
146    the end of the context manager.
147    """
148    with check_no_warnings(testcase, category=ResourceWarning, force_gc=True):
149        yield
150
151
152def _filterwarnings(filters, quiet=False):
153    """Catch the warnings, then check if all the expected
154    warnings have been raised and re-raise unexpected warnings.
155    If 'quiet' is True, only re-raise the unexpected warnings.
156    """
157    # Clear the warning registry of the calling module
158    # in order to re-raise the warnings.
159    frame = sys._getframe(2)
160    registry = frame.f_globals.get('__warningregistry__')
161    if registry:
162        registry.clear()
163    with warnings.catch_warnings(record=True) as w:
164        # Set filter "always" to record all warnings.  Because
165        # test_warnings swap the module, we need to look up in
166        # the sys.modules dictionary.
167        sys.modules['warnings'].simplefilter("always")
168        yield WarningsRecorder(w)
169    # Filter the recorded warnings
170    reraise = list(w)
171    missing = []
172    for msg, cat in filters:
173        seen = False
174        for w in reraise[:]:
175            warning = w.message
176            # Filter out the matching messages
177            if (re.match(msg, str(warning), re.I) and
178                issubclass(warning.__class__, cat)):
179                seen = True
180                reraise.remove(w)
181        if not seen and not quiet:
182            # This filter caught nothing
183            missing.append((msg, cat.__name__))
184    if reraise:
185        raise AssertionError("unhandled warning %s" % reraise[0])
186    if missing:
187        raise AssertionError("filter (%r, %s) did not catch any warning" %
188                             missing[0])
189
190
191@contextlib.contextmanager
192def save_restore_warnings_filters():
193    old_filters = warnings.filters[:]
194    try:
195        yield
196    finally:
197        warnings.filters[:] = old_filters
198
199
200def _warn_about_deprecation():
201    warnings.warn(
202        "This is used in test_support test to ensure"
203        " support.ignore_deprecations_from() works as expected."
204        " You should not be seeing this.",
205        DeprecationWarning,
206        stacklevel=0,
207    )
208