switching to high quality piper tts and added label translations
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
"""py.test hacks to support XFAIL/XPASS"""
|
||||
|
||||
import platform
|
||||
import sys
|
||||
import re
|
||||
import functools
|
||||
import os
|
||||
import contextlib
|
||||
import warnings
|
||||
import inspect
|
||||
import pathlib
|
||||
from typing import Any, Callable
|
||||
|
||||
from sympy.utilities.exceptions import SymPyDeprecationWarning
|
||||
# Imported here for backwards compatibility. Note: do not import this from
|
||||
# here in library code (importing sympy.pytest in library code will break the
|
||||
# pytest integration).
|
||||
from sympy.utilities.exceptions import ignore_warnings # noqa:F401
|
||||
|
||||
ON_CI = os.getenv('CI', None) == "true"
|
||||
|
||||
try:
|
||||
import pytest
|
||||
USE_PYTEST = getattr(sys, '_running_pytest', False)
|
||||
except ImportError:
|
||||
USE_PYTEST = False
|
||||
|
||||
IS_WASM: bool = sys.platform == 'emscripten' or platform.machine() in ["wasm32", "wasm64"]
|
||||
|
||||
raises: Callable[[Any, Any], Any]
|
||||
XFAIL: Callable[[Any], Any]
|
||||
skip: Callable[[Any], Any]
|
||||
SKIP: Callable[[Any], Any]
|
||||
slow: Callable[[Any], Any]
|
||||
tooslow: Callable[[Any], Any]
|
||||
nocache_fail: Callable[[Any], Any]
|
||||
|
||||
|
||||
if USE_PYTEST:
|
||||
raises = pytest.raises
|
||||
skip = pytest.skip
|
||||
XFAIL = pytest.mark.xfail
|
||||
SKIP = pytest.mark.skip
|
||||
slow = pytest.mark.slow
|
||||
tooslow = pytest.mark.tooslow
|
||||
nocache_fail = pytest.mark.nocache_fail
|
||||
from _pytest.outcomes import Failed
|
||||
|
||||
else:
|
||||
# Not using pytest so define the things that would have been imported from
|
||||
# there.
|
||||
|
||||
# _pytest._code.code.ExceptionInfo
|
||||
class ExceptionInfo:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return "<ExceptionInfo {!r}>".format(self.value)
|
||||
|
||||
|
||||
def raises(expectedException, code=None):
|
||||
"""
|
||||
Tests that ``code`` raises the exception ``expectedException``.
|
||||
|
||||
``code`` may be a callable, such as a lambda expression or function
|
||||
name.
|
||||
|
||||
If ``code`` is not given or None, ``raises`` will return a context
|
||||
manager for use in ``with`` statements; the code to execute then
|
||||
comes from the scope of the ``with``.
|
||||
|
||||
``raises()`` does nothing if the callable raises the expected exception,
|
||||
otherwise it raises an AssertionError.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.testing.pytest import raises
|
||||
|
||||
>>> raises(ZeroDivisionError, lambda: 1/0)
|
||||
<ExceptionInfo ZeroDivisionError(...)>
|
||||
>>> raises(ZeroDivisionError, lambda: 1/2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Failed: DID NOT RAISE
|
||||
|
||||
>>> with raises(ZeroDivisionError):
|
||||
... n = 1/0
|
||||
>>> with raises(ZeroDivisionError):
|
||||
... n = 1/2
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Failed: DID NOT RAISE
|
||||
|
||||
Note that you cannot test multiple statements via
|
||||
``with raises``:
|
||||
|
||||
>>> with raises(ZeroDivisionError):
|
||||
... n = 1/0 # will execute and raise, aborting the ``with``
|
||||
... n = 9999/0 # never executed
|
||||
|
||||
This is just what ``with`` is supposed to do: abort the
|
||||
contained statement sequence at the first exception and let
|
||||
the context manager deal with the exception.
|
||||
|
||||
To test multiple statements, you'll need a separate ``with``
|
||||
for each:
|
||||
|
||||
>>> with raises(ZeroDivisionError):
|
||||
... n = 1/0 # will execute and raise
|
||||
>>> with raises(ZeroDivisionError):
|
||||
... n = 9999/0 # will also execute and raise
|
||||
|
||||
"""
|
||||
if code is None:
|
||||
return RaisesContext(expectedException)
|
||||
elif callable(code):
|
||||
try:
|
||||
code()
|
||||
except expectedException as e:
|
||||
return ExceptionInfo(e)
|
||||
raise Failed("DID NOT RAISE")
|
||||
elif isinstance(code, str):
|
||||
raise TypeError(
|
||||
'\'raises(xxx, "code")\' has been phased out; '
|
||||
'change \'raises(xxx, "expression")\' '
|
||||
'to \'raises(xxx, lambda: expression)\', '
|
||||
'\'raises(xxx, "statement")\' '
|
||||
'to \'with raises(xxx): statement\'')
|
||||
else:
|
||||
raise TypeError(
|
||||
'raises() expects a callable for the 2nd argument.')
|
||||
|
||||
class RaisesContext:
|
||||
def __init__(self, expectedException):
|
||||
self.expectedException = expectedException
|
||||
|
||||
def __enter__(self):
|
||||
return None
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if exc_type is None:
|
||||
raise Failed("DID NOT RAISE")
|
||||
return issubclass(exc_type, self.expectedException)
|
||||
|
||||
class XFail(Exception):
|
||||
pass
|
||||
|
||||
class XPass(Exception):
|
||||
pass
|
||||
|
||||
class Skipped(Exception):
|
||||
pass
|
||||
|
||||
class Failed(Exception): # type: ignore
|
||||
pass
|
||||
|
||||
def XFAIL(func):
|
||||
def wrapper():
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
message = str(e)
|
||||
if message != "Timeout":
|
||||
raise XFail(func.__name__)
|
||||
else:
|
||||
raise Skipped("Timeout")
|
||||
raise XPass(func.__name__)
|
||||
|
||||
wrapper = functools.update_wrapper(wrapper, func)
|
||||
return wrapper
|
||||
|
||||
def skip(str):
|
||||
raise Skipped(str)
|
||||
|
||||
def SKIP(reason):
|
||||
"""Similar to ``skip()``, but this is a decorator. """
|
||||
def wrapper(func):
|
||||
def func_wrapper():
|
||||
raise Skipped(reason)
|
||||
|
||||
func_wrapper = functools.update_wrapper(func_wrapper, func)
|
||||
return func_wrapper
|
||||
|
||||
return wrapper
|
||||
|
||||
def slow(func):
|
||||
func._slow = True
|
||||
|
||||
def func_wrapper():
|
||||
func()
|
||||
|
||||
func_wrapper = functools.update_wrapper(func_wrapper, func)
|
||||
func_wrapper.__wrapped__ = func
|
||||
return func_wrapper
|
||||
|
||||
def tooslow(func):
|
||||
func._slow = True
|
||||
func._tooslow = True
|
||||
|
||||
def func_wrapper():
|
||||
skip("Too slow")
|
||||
|
||||
func_wrapper = functools.update_wrapper(func_wrapper, func)
|
||||
func_wrapper.__wrapped__ = func
|
||||
return func_wrapper
|
||||
|
||||
def nocache_fail(func):
|
||||
"Dummy decorator for marking tests that fail when cache is disabled"
|
||||
return func
|
||||
|
||||
@contextlib.contextmanager
|
||||
def warns(warningcls, *, match='', test_stacklevel=True):
|
||||
'''
|
||||
Like raises but tests that warnings are emitted.
|
||||
|
||||
>>> from sympy.testing.pytest import warns
|
||||
>>> import warnings
|
||||
|
||||
>>> with warns(UserWarning):
|
||||
... warnings.warn('deprecated', UserWarning, stacklevel=2)
|
||||
|
||||
>>> with warns(UserWarning):
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Failed: DID NOT WARN. No warnings of type UserWarning\
|
||||
was emitted. The list of emitted warnings is: [].
|
||||
|
||||
``test_stacklevel`` makes it check that the ``stacklevel`` parameter to
|
||||
``warn()`` is set so that the warning shows the user line of code (the
|
||||
code under the warns() context manager). Set this to False if this is
|
||||
ambiguous or if the context manager does not test the direct user code
|
||||
that emits the warning.
|
||||
|
||||
If the warning is a ``SymPyDeprecationWarning``, this additionally tests
|
||||
that the ``active_deprecations_target`` is a real target in the
|
||||
``active-deprecations.md`` file.
|
||||
|
||||
'''
|
||||
# Absorbs all warnings in warnrec
|
||||
with warnings.catch_warnings(record=True) as warnrec:
|
||||
# Any warning other than the one we are looking for is an error
|
||||
warnings.simplefilter("error")
|
||||
warnings.filterwarnings("always", category=warningcls)
|
||||
# Now run the test
|
||||
yield warnrec
|
||||
|
||||
# Raise if expected warning not found
|
||||
if not any(issubclass(w.category, warningcls) for w in warnrec):
|
||||
msg = ('Failed: DID NOT WARN.'
|
||||
' No warnings of type %s was emitted.'
|
||||
' The list of emitted warnings is: %s.'
|
||||
) % (warningcls, [w.message for w in warnrec])
|
||||
raise Failed(msg)
|
||||
|
||||
# We don't include the match in the filter above because it would then
|
||||
# fall to the error filter, so we instead manually check that it matches
|
||||
# here
|
||||
for w in warnrec:
|
||||
# Should always be true due to the filters above
|
||||
assert issubclass(w.category, warningcls)
|
||||
if not re.compile(match, re.IGNORECASE).match(str(w.message)):
|
||||
raise Failed(f"Failed: WRONG MESSAGE. A warning with of the correct category ({warningcls.__name__}) was issued, but it did not match the given match regex ({match!r})")
|
||||
|
||||
if test_stacklevel:
|
||||
for f in inspect.stack():
|
||||
thisfile = f.filename
|
||||
file = os.path.split(thisfile)[1]
|
||||
if file.startswith('test_'):
|
||||
break
|
||||
elif file == 'doctest.py':
|
||||
# skip the stacklevel testing in the doctests of this
|
||||
# function
|
||||
return
|
||||
else:
|
||||
raise RuntimeError("Could not find the file for the given warning to test the stacklevel")
|
||||
for w in warnrec:
|
||||
if w.filename != thisfile:
|
||||
msg = f'''\
|
||||
Failed: Warning has the wrong stacklevel. The warning stacklevel needs to be
|
||||
set so that the line of code shown in the warning message is user code that
|
||||
calls the deprecated code (the current stacklevel is showing code from
|
||||
{w.filename} (line {w.lineno}), expected {thisfile})'''.replace('\n', ' ')
|
||||
raise Failed(msg)
|
||||
|
||||
if warningcls == SymPyDeprecationWarning:
|
||||
this_file = pathlib.Path(__file__)
|
||||
active_deprecations_file = (this_file.parent.parent.parent / 'doc' /
|
||||
'src' / 'explanation' /
|
||||
'active-deprecations.md')
|
||||
if not active_deprecations_file.exists():
|
||||
# We can only test that the active_deprecations_target works if we are
|
||||
# in the git repo.
|
||||
return
|
||||
targets = []
|
||||
for w in warnrec:
|
||||
targets.append(w.message.active_deprecations_target)
|
||||
text = pathlib.Path(active_deprecations_file).read_text(encoding="utf-8")
|
||||
for target in targets:
|
||||
if f'({target})=' not in text:
|
||||
raise Failed(f"The active deprecations target {target!r} does not appear to be a valid target in the active-deprecations.md file ({active_deprecations_file}).")
|
||||
|
||||
def _both_exp_pow(func):
|
||||
"""
|
||||
Decorator used to run the test twice: the first time `e^x` is represented
|
||||
as ``Pow(E, x)``, the second time as ``exp(x)`` (exponential object is not
|
||||
a power).
|
||||
|
||||
This is a temporary trick helping to manage the elimination of the class
|
||||
``exp`` in favor of a replacement by ``Pow(E, ...)``.
|
||||
"""
|
||||
from sympy.core.parameters import _exp_is_pow
|
||||
|
||||
def func_wrap():
|
||||
with _exp_is_pow(True):
|
||||
func()
|
||||
with _exp_is_pow(False):
|
||||
func()
|
||||
|
||||
wrapper = functools.update_wrapper(func_wrap, func)
|
||||
return wrapper
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def warns_deprecated_sympy():
|
||||
'''
|
||||
Shorthand for ``warns(SymPyDeprecationWarning)``
|
||||
|
||||
This is the recommended way to test that ``SymPyDeprecationWarning`` is
|
||||
emitted for deprecated features in SymPy. To test for other warnings use
|
||||
``warns``. To suppress warnings without asserting that they are emitted
|
||||
use ``ignore_warnings``.
|
||||
|
||||
.. note::
|
||||
|
||||
``warns_deprecated_sympy()`` is only intended for internal use in the
|
||||
SymPy test suite to test that a deprecation warning triggers properly.
|
||||
All other code in the SymPy codebase, including documentation examples,
|
||||
should not use deprecated behavior.
|
||||
|
||||
If you are a user of SymPy and you want to disable
|
||||
SymPyDeprecationWarnings, use ``warnings`` filters (see
|
||||
:ref:`silencing-sympy-deprecation-warnings`).
|
||||
|
||||
>>> from sympy.testing.pytest import warns_deprecated_sympy
|
||||
>>> from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
>>> with warns_deprecated_sympy():
|
||||
... sympy_deprecation_warning("Don't use",
|
||||
... deprecated_since_version="1.0",
|
||||
... active_deprecations_target="active-deprecations")
|
||||
|
||||
>>> with warns_deprecated_sympy():
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Failed: DID NOT WARN. No warnings of type \
|
||||
SymPyDeprecationWarning was emitted. The list of emitted warnings is: [].
|
||||
|
||||
.. note::
|
||||
|
||||
Sometimes the stacklevel test will fail because the same warning is
|
||||
emitted multiple times. In this case, you can use
|
||||
:func:`sympy.utilities.exceptions.ignore_warnings` in the code to
|
||||
prevent the ``SymPyDeprecationWarning`` from being emitted again
|
||||
recursively. In rare cases it is impossible to have a consistent
|
||||
``stacklevel`` for deprecation warnings because different ways of
|
||||
calling a function will produce different call stacks.. In those cases,
|
||||
use ``warns(SymPyDeprecationWarning)`` instead.
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.utilities.exceptions.SymPyDeprecationWarning
|
||||
sympy.utilities.exceptions.sympy_deprecation_warning
|
||||
sympy.utilities.decorator.deprecated
|
||||
|
||||
'''
|
||||
with warns(SymPyDeprecationWarning):
|
||||
yield
|
||||
|
||||
|
||||
def skip_under_pyodide(message):
|
||||
"""Decorator to skip a test if running under Pyodide/WASM."""
|
||||
def decorator(test_func):
|
||||
@functools.wraps(test_func)
|
||||
def test_wrapper():
|
||||
if IS_WASM:
|
||||
skip(message)
|
||||
return test_func()
|
||||
return test_wrapper
|
||||
return decorator
|
||||
Reference in New Issue
Block a user