From f5825e964c42eba3dc92e38b10f6839c5a402089 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 4 Jul 2024 08:00:42 -0500 Subject: [PATCH] test: add 'nodb' test runner ensure things work as expected if sqlalchemy is not installed --- tests/db/test_conf.py | 203 +++++++++++++++++++++--------------------- tests/db/test_sess.py | 83 ++++++++--------- tests/test_app.py | 16 +++- tests/test_conf.py | 34 ++++++- tox.ini | 5 +- 5 files changed, 193 insertions(+), 148 deletions(-) diff --git a/tests/db/test_conf.py b/tests/db/test_conf.py index 6d67dff..8c62a31 100644 --- a/tests/db/test_conf.py +++ b/tests/db/test_conf.py @@ -5,128 +5,131 @@ import shutil import tempfile from unittest import TestCase -import sqlalchemy as sa -from sqlalchemy import orm -from sqlalchemy.engine import Engine -from sqlalchemy.pool import NullPool - -from wuttjamaican.db import conf from wuttjamaican.conf import WuttaConfig +try: + import sqlalchemy as sa + from sqlalchemy import orm + from sqlalchemy.engine import Engine + from sqlalchemy.pool import NullPool + from wuttjamaican.db import conf +except ImportError: + pass +else: -class TestGetEngines(TestCase): + class TestGetEngines(TestCase): - def setUp(self): - self.tempdir = tempfile.mkdtemp() + def setUp(self): + self.tempdir = tempfile.mkdtemp() - def tearDown(self): - shutil.rmtree(self.tempdir) + def tearDown(self): + shutil.rmtree(self.tempdir) - def write_file(self, filename, content): - path = os.path.join(self.tempdir, filename) - with open(path, 'wt') as f: - f.write(content) - return path + def write_file(self, filename, content): + path = os.path.join(self.tempdir, filename) + with open(path, 'wt') as f: + f.write(content) + return path - def test_no_default(self): - myfile = self.write_file('my.conf', '') - config = WuttaConfig([myfile]) - self.assertEqual(conf.get_engines(config, 'wuttadb'), {}) + def test_no_default(self): + myfile = self.write_file('my.conf', '') + config = WuttaConfig([myfile]) + self.assertEqual(conf.get_engines(config, 'wuttadb'), {}) - def test_default(self): - myfile = self.write_file('my.conf', """\ -[wuttadb] -default.url = sqlite:// -""") - config = WuttaConfig([myfile]) - result = conf.get_engines(config, 'wuttadb') - self.assertEqual(len(result), 1) - self.assertIn('default', result) - engine = result['default'] - self.assertEqual(engine.dialect.name, 'sqlite') + def test_default(self): + myfile = self.write_file('my.conf', """\ + [wuttadb] + default.url = sqlite:// + """) + config = WuttaConfig([myfile]) + result = conf.get_engines(config, 'wuttadb') + self.assertEqual(len(result), 1) + self.assertIn('default', result) + engine = result['default'] + self.assertEqual(engine.dialect.name, 'sqlite') - def test_default_fallback(self): - myfile = self.write_file('my.conf', """\ -[wuttadb] -sqlalchemy.url = sqlite:// -""") - config = WuttaConfig([myfile]) - result = conf.get_engines(config, 'wuttadb') - self.assertEqual(len(result), 1) - self.assertIn('default', result) - engine = result['default'] - self.assertEqual(engine.dialect.name, 'sqlite') + def test_default_fallback(self): + myfile = self.write_file('my.conf', """\ + [wuttadb] + sqlalchemy.url = sqlite:// + """) + config = WuttaConfig([myfile]) + result = conf.get_engines(config, 'wuttadb') + self.assertEqual(len(result), 1) + self.assertIn('default', result) + engine = result['default'] + self.assertEqual(engine.dialect.name, 'sqlite') - def test_other(self): - myfile = self.write_file('my.conf', """\ -[otherdb] -keys = first, second -first.url = sqlite:// -second.url = sqlite:// -""") - config = WuttaConfig([myfile]) - result = conf.get_engines(config, 'otherdb') - self.assertEqual(len(result), 2) - self.assertIn('first', result) - self.assertIn('second', result) + def test_other(self): + myfile = self.write_file('my.conf', """\ + [otherdb] + keys = first, second + first.url = sqlite:// + second.url = sqlite:// + """) + config = WuttaConfig([myfile]) + result = conf.get_engines(config, 'otherdb') + self.assertEqual(len(result), 2) + self.assertIn('first', result) + self.assertIn('second', result) -class TestGetSetting(TestCase): + class TestGetSetting(TestCase): - def setUp(self): - Session = orm.sessionmaker() - engine = sa.create_engine('sqlite://') - self.session = Session(bind=engine) - self.session.execute(sa.text(""" - create table setting ( - name varchar(255) primary key, - value text - ); - """)) + def setUp(self): + Session = orm.sessionmaker() + engine = sa.create_engine('sqlite://') + self.session = Session(bind=engine) + self.session.execute(sa.text(""" + create table setting ( + name varchar(255) primary key, + value text + ); + """)) - def tearDown(self): - self.session.close() + def tearDown(self): + self.session.close() - def test_basic_value(self): - self.session.execute(sa.text("insert into setting values ('foo', 'bar');")) - value = conf.get_setting(self.session, 'foo') - self.assertEqual(value, 'bar') + def test_basic_value(self): + self.session.execute(sa.text("insert into setting values ('foo', 'bar');")) + value = conf.get_setting(self.session, 'foo') + self.assertEqual(value, 'bar') - def test_missing_value(self): - value = conf.get_setting(self.session, 'foo') - self.assertIsNone(value) + def test_missing_value(self): + value = conf.get_setting(self.session, 'foo') + self.assertIsNone(value) -class TestMakeEngineFromConfig(TestCase): + class TestMakeEngineFromConfig(TestCase): - def test_basic(self): - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - }) - self.assertIsInstance(engine, Engine) + def test_basic(self): + engine = conf.make_engine_from_config({ + 'sqlalchemy.url': 'sqlite://', + }) + self.assertIsInstance(engine, Engine) - def test_poolclass(self): + def test_poolclass(self): - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - }) - self.assertNotIsInstance(engine.pool, NullPool) + engine = conf.make_engine_from_config({ + 'sqlalchemy.url': 'sqlite://', + }) + self.assertNotIsInstance(engine.pool, NullPool) - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - 'sqlalchemy.poolclass': 'sqlalchemy.pool:NullPool', - }) - self.assertIsInstance(engine.pool, NullPool) + engine = conf.make_engine_from_config({ + 'sqlalchemy.url': 'sqlite://', + 'sqlalchemy.poolclass': 'sqlalchemy.pool:NullPool', + }) + self.assertIsInstance(engine.pool, NullPool) - def test_pool_pre_ping(self): + def test_pool_pre_ping(self): - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - }) - self.assertFalse(engine.pool._pre_ping) + engine = conf.make_engine_from_config({ + 'sqlalchemy.url': 'sqlite://', + }) + self.assertFalse(engine.pool._pre_ping) - engine = conf.make_engine_from_config({ - 'sqlalchemy.url': 'sqlite://', - 'sqlalchemy.pool_pre_ping': 'true', - }) - self.assertTrue(engine.pool._pre_ping) + engine = conf.make_engine_from_config({ + 'sqlalchemy.url': 'sqlite://', + 'sqlalchemy.pool_pre_ping': 'true', + }) + self.assertTrue(engine.pool._pre_ping) diff --git a/tests/db/test_sess.py b/tests/db/test_sess.py index d03305b..0ace2e4 100644 --- a/tests/db/test_sess.py +++ b/tests/db/test_sess.py @@ -3,52 +3,55 @@ from unittest import TestCase from unittest.mock import MagicMock -import sqlalchemy as sa -from sqlalchemy import orm - -from wuttjamaican.db import sess from wuttjamaican.conf import WuttaConfig +try: + import sqlalchemy as sa + from sqlalchemy import orm + from wuttjamaican.db import sess +except ImportError: + pass +else: -class TestShortSession(TestCase): + class TestShortSession(TestCase): - def test_none(self): - with sess.short_session() as s: - self.assertIsInstance(s, sess.Session.class_) + def test_none(self): + with sess.short_session() as s: + self.assertIsInstance(s, sess.Session.class_) - def test_factory(self): - TestSession = orm.sessionmaker() - with sess.short_session(factory=TestSession) as s: - self.assertIsInstance(s, TestSession.class_) + def test_factory(self): + TestSession = orm.sessionmaker() + with sess.short_session(factory=TestSession) as s: + self.assertIsInstance(s, TestSession.class_) - def test_instance(self): - # nb. nothing really happens if we provide the session instance - session = MagicMock() - with sess.short_session(session=session) as s: - pass - session.commit.assert_not_called() - session.close.assert_not_called() + def test_instance(self): + # nb. nothing really happens if we provide the session instance + session = MagicMock() + with sess.short_session(session=session) as s: + pass + session.commit.assert_not_called() + session.close.assert_not_called() - def test_config(self): - config = MagicMock() - TestSession = orm.sessionmaker() - config.get_app.return_value.make_session = TestSession - # nb. config may be first arg (or kwarg) - with sess.short_session(config) as s: - self.assertIsInstance(s, TestSession.class_) + def test_config(self): + config = MagicMock() + TestSession = orm.sessionmaker() + config.get_app.return_value.make_session = TestSession + # nb. config may be first arg (or kwarg) + with sess.short_session(config) as s: + self.assertIsInstance(s, TestSession.class_) - def test_without_commit(self): - session = MagicMock() - TestSession = MagicMock(return_value=session) - with sess.short_session(factory=TestSession, commit=False) as s: - pass - session.commit.assert_not_called() - session.close.assert_called_once_with() + def test_without_commit(self): + session = MagicMock() + TestSession = MagicMock(return_value=session) + with sess.short_session(factory=TestSession, commit=False) as s: + pass + session.commit.assert_not_called() + session.close.assert_called_once_with() - def test_with_commit(self): - session = MagicMock() - TestSession = MagicMock(return_value=session) - with sess.short_session(factory=TestSession, commit=True) as s: - pass - session.commit.assert_called_once_with() - session.close.assert_called_once_with() + def test_with_commit(self): + session = MagicMock() + TestSession = MagicMock(return_value=session) + with sess.short_session(factory=TestSession, commit=True) as s: + pass + session.commit.assert_called_once_with() + session.close.assert_called_once_with() diff --git a/tests/test_app.py b/tests/test_app.py index a00c4b8..d31c563 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -7,10 +7,9 @@ import warnings from unittest import TestCase from unittest.mock import patch, MagicMock -import sqlalchemy as sa -from sqlalchemy import orm +import pytest -from wuttjamaican import app, db +from wuttjamaican import app from wuttjamaican.conf import WuttaConfig @@ -46,6 +45,11 @@ class TestAppHandler(TestCase): shutil.rmtree(tempdir) def test_make_session(self): + try: + from wuttjamaican import db + except ImportError: + pytest.skip("test is not relevant without sqlalchemy") + session = self.app.make_session() self.assertIsInstance(session, db.Session.class_) @@ -60,6 +64,12 @@ class TestAppHandler(TestCase): foo='bar', factory=self.app.make_session) def test_get_setting(self): + try: + import sqlalchemy as sa + from sqlalchemy import orm + except ImportError: + pytest.skip("test is not relevant without sqlalchemy") + Session = orm.sessionmaker() engine = sa.create_engine('sqlite://') session = Session(bind=engine) diff --git a/tests/test_conf.py b/tests/test_conf.py index 5de11b2..c310981 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -5,12 +5,10 @@ import os from unittest import TestCase from unittest.mock import patch, MagicMock -import sqlalchemy as sa +import pytest from wuttjamaican import conf from wuttjamaican.exc import ConfigurationError -from wuttjamaican.db import Session -from wuttjamaican.db.conf import make_engine_from_config from wuttjamaican.app import AppHandler from wuttjamaican.testing import FileConfigTestCase @@ -133,6 +131,13 @@ require = %(here)s/first.conf self.assertEqual(config.get('foo'), 'bar') def test_constructor_db_flags(self): + try: + # nb. we don't need this import but the test will not + # behave correctly unless the lib is installed + import sqlalchemy + except ImportError: + pytest.skip("test is not relevant without sqlalchemy") + myfile = self.write_file('my.conf', """\ [wutta.config] usedb = true @@ -155,6 +160,12 @@ preferdb = true self.assertTrue(config.preferdb) def test_constructor_db_not_supported(self): + try: + # nb. we don't need this import but the test will not + # behave correctly unless the lib is installed + import sqlalchemy + except ImportError: + pytest.skip("test is not relevant without sqlalchemy") # flags are off by default config = conf.WuttaConfig() @@ -269,6 +280,12 @@ configure_logging = true self.assertEqual(config.get('foo', default='bar'), 'bar') def test_get_from_db(self): + try: + import sqlalchemy as sa + from wuttjamaican.db import Session + except ImportError: + pytest.skip("test is not relevant without sqlalchemy") + # minimal config, but at least it needs db cxn info config = conf.WuttaConfig(defaults={'wutta.db.default.url': 'sqlite://'}) @@ -317,6 +334,11 @@ configure_logging = true self.assertIn("makin stuff up", str(error)) def test_get_preferdb(self): + try: + import sqlalchemy as sa + from wuttjamaican.db import Session + except ImportError: + pytest.skip("test is not relevant without sqlalchemy") # start out with a default value config = conf.WuttaConfig(defaults={'wutta.db.default.url': 'sqlite://', @@ -403,6 +425,10 @@ configure_logging = true self.assertIsInstance(app, CustomAppHandler) def test_get_engine_maker(self): + try: + from wuttjamaican.db.conf import make_engine_from_config + except ImportError: + pytest.skip("test is not relevant without sqlalchemy") # default func config = conf.WuttaConfig() @@ -421,7 +447,7 @@ class CustomAppHandler(AppHandler): pass -def custom_make_engine_from_config(*args, **kwargs): +def custom_make_engine_from_config(): pass diff --git a/tox.ini b/tox.ini index f4c2c2e..eb50f04 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] -envlist = py36, py37, py38, py39, py310, py311 +envlist = py36, py37, py38, py39, py310, py311, nodb # TODO: can remove this when we drop py36 support # nb. need this for testing older python versions @@ -15,6 +15,9 @@ commands = pytest {posargs} # nb. newer coverage is causing segfault for this one, so must avoid that deps = coverage<6.5 +[testenv:nodb] +extras = tests + [testenv:coverage] basepython = python3.11 extras = db,tests