diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-01-21 18:46:37 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-01-22 19:17:10 -0500 |
| commit | de0b4db838e26fe61953c7765f35d5b7be581646 (patch) | |
| tree | 89f9816c86dbf9eaec0406b6a9b8585e9b645a6c /lib/sqlalchemy/testing | |
| parent | d46a4c0326bd2e697794514b920e6727d5153324 (diff) | |
| download | sqlalchemy-de0b4db838e26fe61953c7765f35d5b7be581646.tar.gz | |
dont use exception catches for warnings; modernize xdist detection
Improvements to the test suite's integration with pytest such that the
"warnings" plugin, if manually enabled, will not interfere with the test
suite, such that third parties can enable the warnings plugin or make use
of the ``-W`` parameter and SQLAlchemy's test suite will continue to pass.
Additionally, modernized the detection of the "pytest-xdist" plugin so that
plugins can be globally disabled using PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
without breaking the test suite if xdist were still installed. Warning
filters that promote deprecation warnings to errors are now localized to
SQLAlchemy-specific warnings, or within SQLAlchemy-specific sources for
general Python deprecation warnings, so that non-SQLAlchemy deprecation
warnings emitted from pytest plugins should also not impact the test suite.
Fixes: #7599
Change-Id: Ibcf09af25228d39ee5a943fda82d8a9302433726
Diffstat (limited to 'lib/sqlalchemy/testing')
| -rw-r--r-- | lib/sqlalchemy/testing/__init__.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/assertions.py | 59 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/plugin/pytestplugin.py | 19 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/warnings.py | 32 |
4 files changed, 78 insertions, 34 deletions
diff --git a/lib/sqlalchemy/testing/__init__.py b/lib/sqlalchemy/testing/__init__.py index 87208d3f4..fd6ddf593 100644 --- a/lib/sqlalchemy/testing/__init__.py +++ b/lib/sqlalchemy/testing/__init__.py @@ -12,6 +12,8 @@ from .assertions import assert_raises from .assertions import assert_raises_context_ok from .assertions import assert_raises_message from .assertions import assert_raises_message_context_ok +from .assertions import assert_warns +from .assertions import assert_warns_message from .assertions import AssertsCompiledSQL from .assertions import AssertsExecutionResults from .assertions import ComparesTables diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index f268b6fc3..2a00f1c14 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -139,13 +139,15 @@ def _expect_warnings( exc_cls, messages, regex=True, + search_msg=False, assert_=True, raise_on_any_unexpected=False, + squelch_other_warnings=False, ): global _FILTERS, _SEEN, _EXC_CLS - if regex: + if regex or search_msg: filters = [re.compile(msg, re.I | re.S) for msg in messages] else: filters = list(messages) @@ -183,19 +185,23 @@ def _expect_warnings( exception = None if not exception or not issubclass(exception, _EXC_CLS): - return real_warn(msg, *arg, **kw) + if not squelch_other_warnings: + return real_warn(msg, *arg, **kw) if not filters and not raise_on_any_unexpected: return for filter_ in filters: - if (regex and filter_.match(msg)) or ( - not regex and filter_ == msg + if ( + (search_msg and filter_.search(msg)) + or (regex and filter_.match(msg)) + or (not regex and filter_ == msg) ): seen.discard(filter_) break else: - real_warn(msg, *arg, **kw) + if not squelch_other_warnings: + real_warn(msg, *arg, **kw) with mock.patch("warnings.warn", our_warn): try: @@ -343,6 +349,40 @@ def assert_raises_message(except_cls, msg, callable_, *args, **kwargs): ) +def assert_warns(except_cls, callable_, *args, **kwargs): + """legacy adapter function for functions that were previously using + assert_raises with SAWarning or similar. + + has some workarounds to accommodate the fact that the callable completes + with this approach rather than stopping at the exception raise. + + + """ + with _expect_warnings(except_cls, [".*"], squelch_other_warnings=True): + return callable_(*args, **kwargs) + + +def assert_warns_message(except_cls, msg, callable_, *args, **kwargs): + """legacy adapter function for functions that were previously using + assert_raises with SAWarning or similar. + + has some workarounds to accommodate the fact that the callable completes + with this approach rather than stopping at the exception raise. + + Also uses regex.search() to match the given message to the error string + rather than regex.match(). + + """ + with _expect_warnings( + except_cls, + [msg], + search_msg=True, + regex=False, + squelch_other_warnings=True, + ): + return callable_(*args, **kwargs) + + def assert_raises_message_context_ok( except_cls, msg, callable_, *args, **kwargs ): @@ -364,6 +404,15 @@ class _ErrorContainer: @contextlib.contextmanager def _expect_raises(except_cls, msg=None, check_context=False): + if ( + isinstance(except_cls, type) + and issubclass(except_cls, Warning) + or isinstance(except_cls, Warning) + ): + raise TypeError( + "Use expect_warnings for warnings, not " + "expect_raises / assert_raises" + ) ec = _ErrorContainer() if check_context: are_we_already_in_a_traceback = sys.exc_info()[0] diff --git a/lib/sqlalchemy/testing/plugin/pytestplugin.py b/lib/sqlalchemy/testing/plugin/pytestplugin.py index 7a62ad008..2ae6730bb 100644 --- a/lib/sqlalchemy/testing/plugin/pytestplugin.py +++ b/lib/sqlalchemy/testing/plugin/pytestplugin.py @@ -13,16 +13,10 @@ import itertools import operator import os import re +import uuid import pytest -try: - import xdist # noqa - - has_xdist = True -except ImportError: - has_xdist = False - def pytest_addoption(parser): group = parser.getgroup("sqlalchemy") @@ -75,6 +69,9 @@ def pytest_addoption(parser): def pytest_configure(config): + if config.pluginmanager.hasplugin("xdist"): + config.pluginmanager.register(XDistHooks()) + if hasattr(config, "workerinput"): plugin_base.restore_important_follower_config(config.workerinput) plugin_base.configure_follower(config.workerinput["follower_ident"]) @@ -148,10 +145,8 @@ def pytest_collection_finish(session): collect_types.init_types_collection(filter_filename=_filter) -if has_xdist: - import uuid - - def pytest_configure_node(node): +class XDistHooks: + def pytest_configure_node(self, node): from sqlalchemy.testing import provision from sqlalchemy.testing import asyncio @@ -166,7 +161,7 @@ if has_xdist: provision.create_follower_db, node.workerinput["follower_ident"] ) - def pytest_testnodedown(node, error): + def pytest_testnodedown(self, node, error): from sqlalchemy.testing import provision from sqlalchemy.testing import asyncio diff --git a/lib/sqlalchemy/testing/warnings.py b/lib/sqlalchemy/testing/warnings.py index 34b23d675..1c2039602 100644 --- a/lib/sqlalchemy/testing/warnings.py +++ b/lib/sqlalchemy/testing/warnings.py @@ -11,8 +11,13 @@ from .. import exc as sa_exc from ..util.langhelpers import _warnings_warn -class SATestSuiteWarning(sa_exc.SAWarning): - """warning for a condition detected during tests that is non-fatal""" +class SATestSuiteWarning(Warning): + """warning for a condition detected during tests that is non-fatal + + Currently outside of SAWarning so that we can work around tools like + Alembic doing the wrong thing with warnings. + + """ def warn_test_suite(message): @@ -22,28 +27,21 @@ def warn_test_suite(message): def setup_filters(): """Set global warning behavior for the test suite.""" + # TODO: at this point we can use the normal pytest warnings plugin, + # if we decide the test suite can be linked to pytest only + + origin = r"^(?:test|sqlalchemy)\..*" + warnings.filterwarnings( "ignore", category=sa_exc.SAPendingDeprecationWarning ) warnings.filterwarnings("error", category=sa_exc.SADeprecationWarning) warnings.filterwarnings("error", category=sa_exc.SAWarning) - warnings.filterwarnings("always", category=SATestSuiteWarning) - # some selected deprecations... - warnings.filterwarnings("error", category=DeprecationWarning) - warnings.filterwarnings( - "ignore", category=DeprecationWarning, message=r".*StopIteration" - ) - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=r".*inspect.get.*argspec", - ) + warnings.filterwarnings("always", category=SATestSuiteWarning) warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message="The loop argument is deprecated", + "error", category=DeprecationWarning, module=origin ) try: @@ -52,7 +50,7 @@ def setup_filters(): pass else: warnings.filterwarnings( - "once", category=pytest.PytestDeprecationWarning + "once", category=pytest.PytestDeprecationWarning, module=origin ) |
