diff options
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/testing/__init__.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/config.py | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/exclusions.py | 23 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/plugin/plugin_base.py | 123 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/plugin/pytestplugin.py | 52 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/requirements.py | 5 |
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): |
