async def test_finish_commit(raise_exception, tmp_path): """ Tests that the session is automatically committed if and only if the context was not exited with an exception. """ db_path = tmp_path / "test.db" engine = create_engine(f"sqlite:///{db_path}", poolclass=NullPool) with engine.begin() as connection: connection.execute(text("CREATE TABLE foo (id INTEGER PRIMARY KEY)")) component = SQLAlchemyComponent(url={ "drivername": "sqlite", "database": str(db_path) }, ) with ExitStack() as stack: async with Context() as ctx: await component.start(ctx) session = ctx.require_resource(Session) session.execute(text("INSERT INTO foo (id) VALUES(3)")) if raise_exception: stack.enter_context(pytest.raises(Exception, match="dummy")) raise Exception("dummy") rows = connection.execute(text("SELECT * FROM foo")).fetchall() assert len(rows) == (0 if raise_exception else 1)
async def test_resources(): mailer = SMTPMailer(tls_context='contextresource') context = Context() sslcontext = ssl.create_default_context() context.add_resource(sslcontext, 'contextresource') await mailer.start(context) assert mailer.tls_context is sslcontext
async def test_component_multiple(caplog): caplog.set_level(logging.INFO, logger='asphalt.mailer.component') component = MailerComponent( mailers={ 'smtp': { 'backend': 'smtp', 'context_attr': 'mailer1' }, 'sendmail': { 'backend': 'sendmail', 'context_attr': 'mailer2' } }) async with Context() as ctx: await component.start(ctx) assert isinstance(ctx.mailer1, Mailer) assert isinstance(ctx.mailer2, Mailer) records = [ record for record in caplog.records if record.name == 'asphalt.mailer.component' ] records.sort(key=lambda r: r.message) assert len(records) == 4 assert records[0].message.startswith( "Configured mailer (sendmail / ctx.mailer2; class=SendmailMailer)") assert records[1].message.startswith( "Configured mailer (smtp / ctx.mailer1; class=SMTPMailer)") assert records[2].message.startswith('Mailer (sendmail) stopped') assert records[3].message.startswith('Mailer (smtp) stopped')
async def test_get_resources_include_parents(self, context): subcontext = Context(context) resource1 = context.publish_resource(6, 'int1') resource2 = subcontext.publish_resource(8, 'int2') resource3 = context.publish_resource('foo', 'str') assert subcontext.get_resources() == [resource1, resource2, resource3] assert subcontext.get_resources(include_parents=False) == [resource2]
def test_get_parent_attribute(self, context): """ Test that accessing a nonexistent attribute on a context retrieves the value from parent. """ child_context = Context(context) context.a = 2 assert child_context.a == 2
def mailer(event_loop, unused_tcp_port, client_tls_context, smtp_server, tls): mailer = SMTPMailer(port=unused_tcp_port, timeout=1, tls=tls, tls_context=client_tls_context) with Context() as ctx: event_loop.run_until_complete(mailer.start(ctx)) yield mailer
async def test_default_config(): component = SerializationComponent(backend='json') async with Context() as ctx: await component.start(ctx) resource = ctx.require_resource(Serializer) assert isinstance(resource, JSONSerializer) assert ctx.json is resource
def test_run_return_5(self, event_loop): class DummyCLIComponent(CLIApplicationComponent): async def run(self, ctx: Context): return 5 component = DummyCLIComponent() event_loop.run_until_complete(component.start(Context())) exc = pytest.raises(SystemExit, event_loop.run_forever) assert exc.value.code == 5
async def test_multiple_engines(): component = SQLAlchemyComponent(engines={'db1': {}, 'db2': {}}, url='sqlite:///:memory:') async with Context() as ctx: await component.start(ctx) engine1 = ctx.require_resource(Engine, 'db1') engine2 = ctx.require_resource(Engine, 'db2') assert ctx.db1.bind is engine1 assert ctx.db2.bind is engine2
async def test_bind(): """Test that a Connection can be passed as "bind" in place of "url".""" engine = create_engine('sqlite:///:memory:') connection = engine.connect() component = SQLAlchemyComponent(bind=connection) async with Context() as ctx: await component.start(ctx) assert ctx.require_resource(Engine) is engine assert ctx.sql.bind is connection
async def test_memory_leak(): """Test that creating a session in a context does not leak memory.""" component = SQLAlchemyComponent(url='sqlite:///:memory:') async with Context() as ctx: await component.start(ctx) assert isinstance(ctx.sql, Session) del ctx gc.collect() # needed on PyPy assert next((x for x in gc.get_objects() if isinstance(x, Context)), None) is None
async def test_component_start_async(): """Test that the component creates all the expected (asynchronous) resources.""" url = URL.create("sqlite+aiosqlite", database=":memory:") component = SQLAlchemyComponent(url=url) async with Context() as ctx: await component.start(ctx) ctx.require_resource(AsyncEngine) ctx.require_resource(sessionmaker) ctx.require_resource(AsyncSession)
async def test_component_start(poolclass): """Test that the component creates all the expected resources.""" component = SQLAlchemyComponent(url='sqlite:///:memory:', poolclass=poolclass) async with Context() as ctx: await component.start(ctx) engine = ctx.require_resource(Engine) ctx.require_resource(sessionmaker) assert ctx.sql is ctx.require_resource(Session) assert ctx.sql.bind is engine
async def test_request_resource_parent_add(self, context, event_loop): """ Test that publishing a resource to the parent context will satisfy a resource request in a child context. """ child_context = Context(context) task = event_loop.create_task(child_context.request_resource(int)) context.publish_resource(6) resource = await task assert resource == 6
async def test_bind_async(): """Test that a Connection can be passed as "bind" in place of "url".""" engine = create_async_engine("sqlite+aiosqlite:///:memory:") connection = await engine.connect() component = SQLAlchemyComponent(bind=connection) async with Context() as ctx: await component.start(ctx) assert ctx.require_resource(AsyncEngine) is engine assert ctx.require_resource(AsyncSession).bind is connection await connection.close()
async def test_single_client(event_loop): ctx = Context() component = WAMPComponent(ssl='default', serializer='default') ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) serializer = JSONSerializer() ctx.publish_resource(serializer, types=[Serializer]) ctx.publish_resource(ssl_context) await component.start(ctx) assert isinstance(ctx.wamp, WAMPClient) assert ctx.wamp.ssl is ssl_context assert ctx.wamp.serializer is serializer
def test_run_return_invalid_type(self, event_loop): class DummyCLIComponent(CLIApplicationComponent): async def run(self, ctx: Context): return 'foo' component = DummyCLIComponent() event_loop.run_until_complete(component.start(Context())) with pytest.warns(UserWarning) as record: exc = pytest.raises(SystemExit, event_loop.run_forever) assert exc.value.code == 1 assert len(record) == 1 assert str(record[0].message) == 'run() must return an integer or None, not str'
def test_run_return_invalid_value(self, event_loop): class DummyCLIComponent(CLIApplicationComponent): async def run(self, ctx: Context): return 128 component = DummyCLIComponent() event_loop.run_until_complete(component.start(Context())) with pytest.warns(UserWarning) as record: exc = pytest.raises(SystemExit, event_loop.run_forever) assert exc.value.code == 1 assert len(record) == 1 assert str(record[0].message) == 'exit code out of range: 128'
def test_event_context(): parent = Context() session_details = SessionDetails('default', 5) event_details = EventDetails(publication=15, publisher=8, publisher_authid='user', publisher_authrole='role', topic='topic') context = EventContext(parent, session_details, event_details) assert context.session_id == 5 assert context.publisher_session_id == 8 assert context.publisher_auth_id == 'user' assert context.publisher_auth_role == 'role' assert context.publication_id == 15 assert context.topic == 'topic' assert context.enc_algo is None
async def test_component_start(): component = SerializationComponent(serializers={ 'json': {'encoder_options': {'allow_nan': False}}, 'msgpack': {'unpacker_options': {'encoding': 'iso-8859-1'}}, 'pickle': {'protocol': 3}, 'yaml': {'safe': False} }) async with Context() as ctx: await component.start(ctx) assert ctx.json assert ctx.msgpack assert ctx.pickle assert ctx.yaml
async def test_null_configs(): component = SerializationComponent(serializers={ 'json': None, 'msgpack': None, 'pickle': None, 'yaml': None }) async with Context() as ctx: await component.start(ctx) assert ctx.json assert ctx.msgpack assert ctx.pickle assert ctx.yaml
async def test_close_twice_async(asyncpg_url): """Test that closing a session releases connection resources, but remains usable.""" component = SQLAlchemyComponent(url=asyncpg_url) async with Context() as ctx: await component.start(ctx) session = ctx.require_resource(AsyncSession) pool = session.bind.pool await session.execute(text("SELECT 1")) assert pool.checkedout() == 1 await session.close() assert pool.checkedout() == 0 await session.execute(text("SELECT 1")) assert pool.checkedout() == 1 assert pool.checkedout() == 0
async def test_component_single(caplog, backend): caplog.set_level(logging.INFO, logger='asphalt.mailer.component') component = MailerComponent(backend=backend) async with Context() as ctx: await component.start(ctx) assert isinstance(ctx.mailer, Mailer) records = [ record for record in caplog.records if record.name == 'asphalt.mailer.component' ] records.sort(key=lambda r: r.message) assert len(records) == 2 assert records[0].message.startswith( "Configured mailer (default / ctx.mailer; class=%s)" % ctx.mailer.__class__.__name__) assert records[1].message.startswith('Mailer (default) stopped')
async def test_context_teardown(self, expected_exc): @context_teardown async def start(ctx: Context): nonlocal phase, received_exception phase = 'started' exc = yield phase = 'finished' received_exception = exc phase = received_exception = None context = Context() await start(context) assert phase == 'started' await context.close(expected_exc) assert phase == 'finished' assert received_exception == expected_exc
def test_call_context(): def progress(arg): pass parent = Context() session_details = SessionDetails('default', 5) call_details = CallDetails(progress=progress, caller=8, caller_authid='user', caller_authrole='role', procedure='procedurename') context = CallContext(parent, session_details, call_details) assert context.session_id == 5 assert context.caller_session_id == 8 assert context.caller_auth_id == 'user' assert context.caller_auth_role == 'role' assert context.procedure == 'procedurename' assert context.enc_algo is None assert context.progress is progress
async def test_single_renderer(): async with Context() as ctx: ctx.add_resource("åäö") component = TemplatingComponent(backend="jinja2", options={"package_name": "tests"}) await component.start(ctx) renderer = ctx.require_resource(TemplateRenderer) assert isinstance(renderer, TemplateRendererProxy) assert type(renderer.environment).__name__ == "Environment" assert (renderer.render("jinja2_context.html", str=str) == """\ <div> This is a sample Test variable: åäö </div>""") assert (renderer.render_string( "This is testvar: {{ ctx.require_resource(str) }}", str=str) == "This is testvar: åäö")
async def test_finish_commit(raise_exception, executor, commit_executor, tmpdir): """ Tests that the session is automatically committed if and only if the context was not exited with an exception. """ db_path = tmpdir.join('test.db') engine = create_engine('sqlite:///%s' % db_path, poolclass=NullPool) engine.execute('CREATE TABLE foo (id INTEGER PRIMARY KEY)') component = SQLAlchemyComponent( url={'drivername': 'sqlite', 'database': str(db_path)}, commit_executor=executor if commit_executor == 'instance' else commit_executor) ctx = Context() ctx.add_resource(executor, types=[Executor]) await component.start(ctx) ctx.sql.execute('INSERT INTO foo (id) VALUES(3)') await ctx.close(Exception('dummy') if raise_exception else None) rows = engine.execute('SELECT * FROM foo').fetchall() assert len(rows) == (0 if raise_exception else 1)
async def test_ready_callback(asynchronous): def ready_callback(engine, factory): nonlocal engine2, factory2 engine2 = engine factory2 = factory async def ready_callback_async(engine, factory): nonlocal engine2, factory2 engine2 = engine factory2 = factory engine2 = factory2 = None callback = ready_callback_async if asynchronous else ready_callback component = SQLAlchemyComponent(url='sqlite:///:memory:', ready_callback=callback) async with Context() as ctx: await component.start(ctx) engine = ctx.require_resource(Engine) factory = ctx.require_resource(sessionmaker) assert engine is engine2 assert factory is factory2
async def test_session_event_async(request, asyncpg_url, psycopg2_url): """Test that creating a session in a context does not leak memory.""" listener_session: Session listener_thread: Thread def listener(session: Session) -> None: nonlocal listener_session, listener_thread try: async_session = get_resource(AsyncSession) except NoCurrentContext: return if async_session and session is async_session.sync_session: listener_session = session listener_thread = current_thread() listen(Session, "before_commit", listener) request.addfinalizer(lambda: remove(Session, "before_commit", listener)) component = SQLAlchemyComponent(url=asyncpg_url) async with Context() as ctx: await component.start(ctx) dbsession = ctx.require_resource(AsyncSession) await dbsession.run_sync( lambda session: Person.metadata.create_all(session.bind)) dbsession.add(Person(name="Test person")) assert listener_session is dbsession.sync_session assert listener_thread is current_thread() engine = create_engine(psycopg2_url) with Session(engine) as sess: sess.add(Person(name="Test person 2")) sess.commit() engine.dispose() assert listener_session is dbsession.sync_session assert listener_thread is current_thread()
async def test_session_event_sync(psycopg2_url): """Test that creating a session in a context does not leak memory.""" listener_session: Session listener_thread: Thread def listener(session: Session) -> None: nonlocal listener_session, listener_thread current_context() listener_session = session listener_thread = current_thread() component = SQLAlchemyComponent(url=psycopg2_url) async with Context() as ctx: await component.start(ctx) Person.metadata.create_all(ctx.require_resource(Engine)) session_factory = ctx.require_resource(sessionmaker) listen(session_factory, "before_commit", listener) dbsession = ctx.require_resource(Session) dbsession.add(Person(name="Test person")) assert listener_session is dbsession assert listener_thread != current_thread()