summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/testing/__init__.py1
-rw-r--r--lib/sqlalchemy/testing/config.py8
-rw-r--r--lib/sqlalchemy/testing/exclusions.py23
-rw-r--r--lib/sqlalchemy/testing/plugin/plugin_base.py123
-rw-r--r--lib/sqlalchemy/testing/plugin/pytestplugin.py52
-rw-r--r--lib/sqlalchemy/testing/requirements.py5
6 files changed, 103 insertions, 109 deletions
diff --git a/lib/sqlalchemy/testing/__init__.py b/lib/sqlalchemy/testing/__init__.py
index fd6ddf593..4253aa61b 100644
--- a/lib/sqlalchemy/testing/__init__.py
+++ b/lib/sqlalchemy/testing/__init__.py
@@ -42,6 +42,7 @@ from .assertions import not_in
from .assertions import not_in_
from .assertions import startswith_
from .assertions import uses_deprecated
+from .config import add_to_marker
from .config import async_test
from .config import combinations
from .config import combinations_list
diff --git a/lib/sqlalchemy/testing/config.py b/lib/sqlalchemy/testing/config.py
index f326c124d..268a56421 100644
--- a/lib/sqlalchemy/testing/config.py
+++ b/lib/sqlalchemy/testing/config.py
@@ -106,6 +106,14 @@ def mark_base_test_class():
return _fixture_functions.mark_base_test_class()
+class _AddToMarker:
+ def __getattr__(self, attr):
+ return getattr(_fixture_functions.add_to_marker, attr)
+
+
+add_to_marker = _AddToMarker()
+
+
class Config:
def __init__(self, db, db_opts, options, file_config):
self._set_name(db)
diff --git a/lib/sqlalchemy/testing/exclusions.py b/lib/sqlalchemy/testing/exclusions.py
index b92d6859f..b51f6e57c 100644
--- a/lib/sqlalchemy/testing/exclusions.py
+++ b/lib/sqlalchemy/testing/exclusions.py
@@ -35,7 +35,6 @@ class compound:
def __init__(self):
self.fails = set()
self.skips = set()
- self.tags = set()
def __add__(self, other):
return self.add(other)
@@ -44,25 +43,22 @@ class compound:
rule = compound()
rule.skips.update(self.skips)
rule.skips.update(self.fails)
- rule.tags.update(self.tags)
return rule
def add(self, *others):
copy = compound()
copy.fails.update(self.fails)
copy.skips.update(self.skips)
- copy.tags.update(self.tags)
+
for other in others:
copy.fails.update(other.fails)
copy.skips.update(other.skips)
- copy.tags.update(other.tags)
return copy
def not_(self):
copy = compound()
copy.fails.update(NotPredicate(fail) for fail in self.fails)
copy.skips.update(NotPredicate(skip) for skip in self.skips)
- copy.tags.update(self.tags)
return copy
@property
@@ -83,16 +79,9 @@ class compound:
if predicate(config)
]
- def include_test(self, include_tags, exclude_tags):
- return bool(
- not self.tags.intersection(exclude_tags)
- and (not include_tags or self.tags.intersection(include_tags))
- )
-
def _extend(self, other):
self.skips.update(other.skips)
self.fails.update(other.fails)
- self.tags.update(other.tags)
def __call__(self, fn):
if hasattr(fn, "_sa_exclusion_extend"):
@@ -166,16 +155,6 @@ class compound:
)
-def requires_tag(tagname):
- return tags([tagname])
-
-
-def tags(tagnames):
- comp = compound()
- comp.tags.update(tagnames)
- return comp
-
-
def only_if(predicate, reason=None):
predicate = _as_predicate(predicate)
return skip_if(NotPredicate(predicate), reason)
diff --git a/lib/sqlalchemy/testing/plugin/plugin_base.py b/lib/sqlalchemy/testing/plugin/plugin_base.py
index 5a4bfe3a6..0b4451b3c 100644
--- a/lib/sqlalchemy/testing/plugin/plugin_base.py
+++ b/lib/sqlalchemy/testing/plugin/plugin_base.py
@@ -106,21 +106,28 @@ def setup_options(make_option):
)
make_option(
"--backend-only",
- action="store_true",
- dest="backend_only",
- help="Run only tests marked with __backend__ or __sparse_backend__",
+ action="callback",
+ zeroarg_callback=_set_tag_include("backend"),
+ help=(
+ "Run only tests marked with __backend__ or __sparse_backend__; "
+ "this is now equivalent to the pytest -m backend mark expression"
+ ),
)
make_option(
"--nomemory",
- action="store_true",
- dest="nomemory",
- help="Don't run memory profiling tests",
+ action="callback",
+ zeroarg_callback=_set_tag_exclude("memory_intensive"),
+ help="Don't run memory profiling tests; "
+ "this is now equivalent to the pytest -m 'not memory_intensive' "
+ "mark expression",
)
make_option(
"--notimingintensive",
- action="store_true",
- dest="notimingintensive",
- help="Don't run timing intensive tests",
+ action="callback",
+ zeroarg_callback=_set_tag_exclude("timing_intensive"),
+ help="Don't run timing intensive tests; "
+ "this is now equivalent to the pytest -m 'not timing_intensive' "
+ "mark expression",
)
make_option(
"--profile-sort",
@@ -171,26 +178,20 @@ def setup_options(make_option):
help="requirements class for testing, overrides setup.cfg",
)
make_option(
- "--with-cdecimal",
- action="store_true",
- dest="cdecimal",
- default=False,
- help="Monkeypatch the cdecimal library into Python 'decimal' "
- "for all tests",
- )
- make_option(
"--include-tag",
action="callback",
callback=_include_tag,
type=str,
- help="Include tests with tag <tag>",
+ help="Include tests with tag <tag>; "
+ "legacy, use pytest -m 'tag' instead",
)
make_option(
"--exclude-tag",
action="callback",
callback=_exclude_tag,
type=str,
- help="Exclude tests with tag <tag>",
+ help="Exclude tests with tag <tag>; "
+ "legacy, use pytest -m 'not tag' instead",
)
make_option(
"--write-profiles",
@@ -240,15 +241,9 @@ def memoize_important_follower_config(dict_):
This invokes in the parent process after normal config is set up.
- This is necessary as pytest seems to not be using forking, so we
- start with nothing in memory, *but* it isn't running our argparse
- callables, so we have to just copy all of that over.
+ Hook is currently not used.
"""
- dict_["memoized_config"] = {
- "include_tags": include_tags,
- "exclude_tags": exclude_tags,
- }
def restore_important_follower_config(dict_):
@@ -256,10 +251,9 @@ def restore_important_follower_config(dict_):
This invokes in the follower process.
+ Hook is currently not used.
+
"""
- global include_tags, exclude_tags
- include_tags.update(dict_["memoized_config"]["include_tags"])
- exclude_tags.update(dict_["memoized_config"]["exclude_tags"])
def read_config():
@@ -322,6 +316,20 @@ def _requirements_opt(opt_str, value, parser):
_setup_requirements(value)
+def _set_tag_include(tag):
+ def _do_include_tag(opt_str, value, parser):
+ _include_tag(opt_str, tag, parser)
+
+ return _do_include_tag
+
+
+def _set_tag_exclude(tag):
+ def _do_exclude_tag(opt_str, value, parser):
+ _exclude_tag(opt_str, tag, parser)
+
+ return _do_exclude_tag
+
+
def _exclude_tag(opt_str, value, parser):
exclude_tags.add(value.replace("-", "_"))
@@ -350,26 +358,6 @@ def _setup_options(opt, file_config):
options = opt
-@pre
-def _set_nomemory(opt, file_config):
- if opt.nomemory:
- exclude_tags.add("memory_intensive")
-
-
-@pre
-def _set_notimingintensive(opt, file_config):
- if opt.notimingintensive:
- exclude_tags.add("timing_intensive")
-
-
-@pre
-def _monkeypatch_cdecimal(options, file_config):
- if options.cdecimal:
- import cdecimal
-
- sys.modules["decimal"] = cdecimal
-
-
@post
def __ensure_cext(opt, file_config):
if os.environ.get("REQUIRE_SQLALCHEMY_CEXT", "0") == "1":
@@ -515,13 +503,6 @@ def want_class(name, cls):
return False
elif name.startswith("_"):
return False
- elif (
- config.options.backend_only
- and not getattr(cls, "__backend__", False)
- and not getattr(cls, "__sparse_backend__", False)
- and not getattr(cls, "__only_on__", False)
- ):
- return False
else:
return True
@@ -531,33 +512,13 @@ def want_method(cls, fn):
return False
elif fn.__module__ is None:
return False
- elif include_tags:
- return (
- hasattr(cls, "__tags__")
- and exclusions.tags(cls.__tags__).include_test(
- include_tags, exclude_tags
- )
- ) or (
- hasattr(fn, "_sa_exclusion_extend")
- and fn._sa_exclusion_extend.include_test(
- include_tags, exclude_tags
- )
- )
- elif exclude_tags and hasattr(cls, "__tags__"):
- return exclusions.tags(cls.__tags__).include_test(
- include_tags, exclude_tags
- )
- elif exclude_tags and hasattr(fn, "_sa_exclusion_extend"):
- return fn._sa_exclusion_extend.include_test(include_tags, exclude_tags)
else:
return True
-def generate_sub_tests(cls, module):
- if getattr(cls, "__backend__", False) or getattr(
- cls, "__sparse_backend__", False
- ):
- sparse = getattr(cls, "__sparse_backend__", False)
+def generate_sub_tests(cls, module, markers):
+ if "backend" in markers or "sparse_backend" in markers:
+ sparse = "sparse_backend" in markers
for cfg in _possible_configs_for_cls(cls, sparse=sparse):
orig_name = cls.__name__
@@ -780,6 +741,10 @@ class FixtureFunctions(abc.ABC):
def mark_base_test_class(self):
raise NotImplementedError()
+ @abc.abstractproperty
+ def add_to_marker(self):
+ raise NotImplementedError()
+
_fixture_fn_class = None
diff --git a/lib/sqlalchemy/testing/plugin/pytestplugin.py b/lib/sqlalchemy/testing/plugin/pytestplugin.py
index 2ae6730bb..363a73ecc 100644
--- a/lib/sqlalchemy/testing/plugin/pytestplugin.py
+++ b/lib/sqlalchemy/testing/plugin/pytestplugin.py
@@ -69,6 +69,17 @@ def pytest_addoption(parser):
def pytest_configure(config):
+ if plugin_base.exclude_tags or plugin_base.include_tags:
+ if config.option.markexpr:
+ raise ValueError(
+ "Can't combine explicit pytest marks with legacy options "
+ "such as --backend-only, --exclude-tags, etc. "
+ )
+ config.option.markexpr = " and ".join(
+ list(plugin_base.include_tags)
+ + [f"not {tag}" for tag in plugin_base.exclude_tags]
+ )
+
if config.pluginmanager.hasplugin("xdist"):
config.pluginmanager.register(XDistHooks())
@@ -206,18 +217,43 @@ def pytest_collection_modifyitems(session, config, items):
def setup_test_classes():
for test_class in test_classes:
+
+ # transfer legacy __backend__ and __sparse_backend__ symbols
+ # to be markers
+ add_markers = set()
+ if getattr(test_class.cls, "__backend__", False) or getattr(
+ test_class.cls, "__only_on__", False
+ ):
+ add_markers = {"backend"}
+ elif getattr(test_class.cls, "__sparse_backend__", False):
+ add_markers = {"sparse_backend"}
+ else:
+ add_markers = frozenset()
+
+ existing_markers = {
+ mark.name for mark in test_class.iter_markers()
+ }
+ add_markers = add_markers - existing_markers
+ all_markers = existing_markers.union(add_markers)
+
+ for marker in add_markers:
+ test_class.add_marker(marker)
+
for sub_cls in plugin_base.generate_sub_tests(
- test_class.cls, test_class.module
+ test_class.cls, test_class.module, all_markers
):
if sub_cls is not test_class.cls:
per_cls_dict = rebuilt_items[test_class.cls]
module = test_class.getparent(pytest.Module)
- for fn in collect(
- pytest.Class.from_parent(
- name=sub_cls.__name__, parent=module
- )
- ):
+
+ new_cls = pytest.Class.from_parent(
+ name=sub_cls.__name__, parent=module
+ )
+ for marker in add_markers:
+ new_cls.add_marker(marker)
+
+ for fn in collect(new_cls):
per_cls_dict[fn.name].append(fn)
# class requirements will sometimes need to access the DB to check
@@ -573,6 +609,10 @@ class PytestFixtureFunctions(plugin_base.FixtureFunctions):
def skip_test_exception(self, *arg, **kw):
return pytest.skip.Exception(*arg, **kw)
+ @property
+ def add_to_marker(self):
+ return pytest.mark
+
def mark_base_test_class(self):
return pytest.mark.usefixtures(
"setup_class_methods", "setup_test_methods"
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 6af2687a9..410ab26ed 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -17,6 +17,7 @@ to provide specific inclusion/exclusions.
import platform
+from . import config
from . import exclusions
from . import only_on
from .. import create_engine
@@ -1295,11 +1296,11 @@ class SuiteRequirements(Requirements):
@property
def timing_intensive(self):
- return exclusions.requires_tag("timing_intensive")
+ return config.add_to_marker.timing_intensive
@property
def memory_intensive(self):
- return exclusions.requires_tag("memory_intensive")
+ return config.add_to_marker.memory_intensive
@property
def threading_with_mock(self):