def pytest_runtest_setup(item): from sqlalchemy.testing import asyncio if not isinstance(item, pytest.Function): return # pytest_runtest_setup runs *before* pytest fixtures with scope="class". # plugin_base.start_test_class_outside_fixtures may opt to raise SkipTest # for the whole class and has to run things that are across all current # databases, so we run this outside of the pytest fixture system altogether # and ensure asyncio greenlet if any engines are async global _current_class if _current_class is None: asyncio._maybe_async_provisioning( plugin_base.start_test_class_outside_fixtures, item.parent.parent.cls, ) _current_class = item.parent.parent def finalize(): global _current_class _current_class = None asyncio._maybe_async_provisioning( plugin_base.stop_test_class_outside_fixtures, item.parent.parent.cls, ) item.parent.parent.addfinalizer(finalize)
def pytest_collection_modifyitems(session, config, items): # look for all those classes that specify __backend__ and # expand them out into per-database test cases. # this is much easier to do within pytest_pycollect_makeitem, however # pytest is iterating through cls.__dict__ as makeitem is # called which causes a "dictionary changed size" error on py3k. # I'd submit a pullreq for them to turn it into a list first, but # it's to suit the rather odd use case here which is that we are adding # new classes to a module on the fly. from sqlalchemy.testing import asyncio rebuilt_items = collections.defaultdict( lambda: collections.defaultdict(list)) items[:] = [ item for item in items if isinstance(item.parent, pytest.Instance) and not item.parent.parent.name.startswith("_") ] test_classes = set(item.parent for item in items) def setup_test_classes(): for test_class in test_classes: for sub_cls in plugin_base.generate_sub_tests( test_class.cls, test_class.parent.module): if sub_cls is not test_class.cls: per_cls_dict = rebuilt_items[test_class.cls] # support pytest 5.4.0 and above pytest.Class.from_parent ctor = getattr(pytest.Class, "from_parent", pytest.Class) for inst in ctor( name=sub_cls.__name__, parent=test_class.parent.parent).collect(): for t in inst.collect(): per_cls_dict[t.name].append(t) # class requirements will sometimes need to access the DB to check # capabilities, so need to do this for async asyncio._maybe_async_provisioning(setup_test_classes) newitems = [] for item in items: if item.parent.cls in rebuilt_items: newitems.extend(rebuilt_items[item.parent.cls][item.name]) else: newitems.append(item) # seems like the functions attached to a test class aren't sorted already? # is that true and why's that? (when using unittest, they're sorted) items[:] = sorted( newitems, key=lambda item: ( item.parent.parent.parent.name, item.parent.parent.name, item.name, ), )
def pytest_testnodedown(node, error): from sqlalchemy.testing import provision from sqlalchemy.testing import asyncio asyncio._maybe_async_provisioning( provision.drop_follower_db, node.workerinput["follower_ident"] )
def finalize(): global _current_class, _current_report _current_class = None try: asyncio._maybe_async_provisioning( plugin_base.stop_test_class_outside_fixtures, item.parent.parent.cls, ) except Exception as e: # in case of an exception during teardown attach the original # error to the exception message, otherwise it will get lost if _current_report.failed: if not e.args: e.args = ("__Original test failure__:\n" + _current_report.longreprtext, ) elif e.args[-1] and isinstance(e.args[-1], string_types): args = list(e.args) args[-1] += ("\n__Original test failure__:\n" + _current_report.longreprtext) e.args = tuple(args) else: e.args += ( "__Original test failure__", _current_report.longreprtext, ) raise finally: _current_report = None
def finalize(): global _current_class _current_class = None asyncio._maybe_async_provisioning( plugin_base.stop_test_class_outside_fixtures, item.parent.parent.cls, )
def pytest_sessionfinish(session): from sqlalchemy.testing import asyncio asyncio._maybe_async_provisioning(plugin_base.final_process_cleanup) if session.config.option.dump_pyannotate: from pyannotate_runtime import collect_types collect_types.dump_stats(session.config.option.dump_pyannotate)
def pytest_runtest_setup(item): from sqlalchemy.testing import asyncio from sqlalchemy.util import string_types if not isinstance(item, pytest.Function): return # pytest_runtest_setup runs *before* pytest fixtures with scope="class". # plugin_base.start_test_class_outside_fixtures may opt to raise SkipTest # for the whole class and has to run things that are across all current # databases, so we run this outside of the pytest fixture system altogether # and ensure asyncio greenlet if any engines are async global _current_class if _current_class is None: asyncio._maybe_async_provisioning( plugin_base.start_test_class_outside_fixtures, item.parent.parent.cls, ) _current_class = item.parent.parent def finalize(): global _current_class, _current_report _current_class = None try: asyncio._maybe_async_provisioning( plugin_base.stop_test_class_outside_fixtures, item.parent.parent.cls, ) except Exception as e: # in case of an exception during teardown attach the original # error to the exception message, otherwise it will get lost if _current_report.failed: if not e.args: e.args = ( "__Original test failure__:\n" + _current_report.longreprtext, ) elif e.args[-1] and isinstance(e.args[-1], string_types): args = list(e.args) args[-1] += ( "\n__Original test failure__:\n" + _current_report.longreprtext ) e.args = tuple(args) else: e.args += ( "__Original test failure__", _current_report.longreprtext, ) raise finally: _current_report = None item.parent.parent.addfinalizer(finalize)
def pytest_configure_node(node): from sqlalchemy.testing import provision from sqlalchemy.testing import asyncio # the master for each node fills workerinput dictionary # which pytest-xdist will transfer to the subprocess plugin_base.memoize_important_follower_config(node.workerinput) node.workerinput["follower_ident"] = "test_%s" % uuid.uuid4().hex[0:12] asyncio._maybe_async_provisioning(provision.create_follower_db, node.workerinput["follower_ident"])
def pytest_runtest_teardown(item, nextitem): # runs inside of pytest function fixture scope # after test function runs from sqlalchemy.testing import asyncio asyncio._maybe_async(plugin_base.after_test, item) yield # this is now after all the fixture teardown have run, the class can be # finalized. Since pytest v7 this finalizer can no longer be added in # pytest_runtest_setup since the class has not yet been setup at that # time. # See https://github.com/pytest-dev/pytest/issues/9343 global _current_class, _current_report if _current_class is not None and ( # last test or a new class nextitem is None or nextitem.getparent(pytest.Class) is not _current_class ): _current_class = None try: asyncio._maybe_async_provisioning( plugin_base.stop_test_class_outside_fixtures, item.cls ) except Exception as e: # in case of an exception during teardown attach the original # error to the exception message, otherwise it will get lost if _current_report.failed: if not e.args: e.args = ( "__Original test failure__:\n" + _current_report.longreprtext, ) elif e.args[-1] and isinstance(e.args[-1], str): args = list(e.args) args[-1] += ( "\n__Original test failure__:\n" + _current_report.longreprtext ) e.args = tuple(args) else: e.args += ( "__Original test failure__", _current_report.longreprtext, ) raise finally: _current_report = None
def pytest_collection_modifyitems(session, config, items): # look for all those classes that specify __backend__ and # expand them out into per-database test cases. # this is much easier to do within pytest_pycollect_makeitem, however # pytest is iterating through cls.__dict__ as makeitem is # called which causes a "dictionary changed size" error on py3k. # I'd submit a pullreq for them to turn it into a list first, but # it's to suit the rather odd use case here which is that we are adding # new classes to a module on the fly. from sqlalchemy.testing import asyncio rebuilt_items = collections.defaultdict( lambda: collections.defaultdict(list) ) items[:] = [ item for item in items if item.getparent(pytest.Class) is not None and not item.getparent(pytest.Class).name.startswith("_") ] test_classes = set(item.getparent(pytest.Class) for item in items) def collect(element): for inst_or_fn in element.collect(): if isinstance(inst_or_fn, pytest.Collector): yield from collect(inst_or_fn) else: yield inst_or_fn 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, all_markers ): if sub_cls is not test_class.cls: per_cls_dict = rebuilt_items[test_class.cls] module = test_class.getparent(pytest.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 # capabilities, so need to do this for async asyncio._maybe_async_provisioning(setup_test_classes) newitems = [] for item in items: cls_ = item.cls if cls_ in rebuilt_items: newitems.extend(rebuilt_items[cls_][item.name]) else: newitems.append(item) # seems like the functions attached to a test class aren't sorted already? # is that true and why's that? (when using unittest, they're sorted) items[:] = sorted( newitems, key=lambda item: ( item.getparent(pytest.Module).name, item.getparent(pytest.Class).name, item.name, ), )
def class_setup(item): from sqlalchemy.testing import asyncio asyncio._maybe_async_provisioning(plugin_base.start_test_class, item.cls)