3
0
Fork 0

First commit, basic config (with db) and app handler

this has 100% test coverage and i intend to keep it that way.  api
docs have a good start but still need narrative.  several more things
must be added before i can seriously consider incorporating into
rattail but this seemed a good save point
This commit is contained in:
Lance Edgar 2023-10-28 17:48:37 -05:00
commit 5c3c42d6b3
36 changed files with 3322 additions and 0 deletions

0
tests/__init__.py Normal file
View file

0
tests/db/__init__.py Normal file
View file

132
tests/db/test_conf.py Normal file
View file

@ -0,0 +1,132 @@
# -*- coding: utf-8; -*-
import os
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
class TestEngineFromConfig(TestCase):
def test_basic(self):
engine = conf.engine_from_config({
'sqlalchemy.url': 'sqlite://',
})
self.assertIsInstance(engine, Engine)
def test_poolclass(self):
engine = conf.engine_from_config({
'sqlalchemy.url': 'sqlite://',
})
self.assertNotIsInstance(engine.pool, NullPool)
engine = conf.engine_from_config({
'sqlalchemy.url': 'sqlite://',
'sqlalchemy.poolclass': 'sqlalchemy.pool:NullPool',
})
self.assertIsInstance(engine.pool, NullPool)
def test_pool_pre_ping(self):
engine = conf.engine_from_config({
'sqlalchemy.url': 'sqlite://',
})
self.assertFalse(engine.pool._pre_ping)
engine = conf.engine_from_config({
'sqlalchemy.url': 'sqlite://',
'sqlalchemy.pool_pre_ping': 'true',
})
self.assertTrue(engine.pool._pre_ping)
class TestGetEngines(TestCase):
def setUp(self):
self.tempdir = tempfile.mkdtemp()
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 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_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)
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 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_missing_value(self):
value = conf.get_setting(self.session, 'foo')
self.assertIsNone(value)

54
tests/db/test_sess.py Normal file
View file

@ -0,0 +1,54 @@
# -*- coding: utf-8; -*-
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
class TestShortSession(TestCase):
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_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_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()

53
tests/test_app.py Normal file
View file

@ -0,0 +1,53 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
from unittest.mock import patch, MagicMock
import sqlalchemy as sa
from sqlalchemy import orm
from wuttjamaican import app, db
class TestAppHandler(TestCase):
def setUp(self):
self.config = MagicMock()
self.app = app.AppHandler(self.config)
def test_init(self):
self.assertIs(self.app.config, self.config)
self.assertEqual(self.app.handlers, {})
def test_make_session(self):
session = self.app.make_session()
self.assertIsInstance(session, db.Session.class_)
def test_short_session(self):
short_session = MagicMock()
mockdb = MagicMock(short_session=short_session)
with patch.dict('sys.modules', **{'wuttjamaican.db': mockdb}):
with self.app.short_session(foo='bar') as s:
short_session.assert_called_once_with(
foo='bar', factory=self.app.make_session)
def test_get_setting(self):
Session = orm.sessionmaker()
engine = sa.create_engine('sqlite://')
session = Session(bind=engine)
session.execute(sa.text("""
create table setting (
name varchar(255) primary key,
value text
);
"""))
session.commit()
value = self.app.get_setting(session, 'foo')
self.assertIsNone(value)
session.execute(sa.text("insert into setting values ('foo', 'bar');"))
value = self.app.get_setting(session, 'foo')
self.assertEqual(value, 'bar')

579
tests/test_conf.py Normal file
View file

@ -0,0 +1,579 @@
# -*- coding: utf-8; -*-
import configparser
import os
import shutil
import tempfile
from unittest import TestCase
from unittest.mock import patch, MagicMock
import sqlalchemy as sa
from wuttjamaican import conf
from wuttjamaican.exc import ConfigurationError
from wuttjamaican.db import Session
class TestWuttaConfig(TestCase):
def setUp(self):
self.tempdir = tempfile.mkdtemp()
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 test_contstructor_basic(self):
config = conf.WuttaConfig()
self.assertEqual(config.appname, 'wutta')
self.assertEqual(config.files_read, [])
def test_constructor_valid_files(self):
myfile = self.write_file('my.conf', '')
config = conf.WuttaConfig(files=[myfile])
self.assertEqual(len(config.files_read), 1)
self.assertEqual(config.files_read[0], myfile)
def test_constructor_missing_files(self):
invalid = os.path.join(self.tempdir, 'invalid.conf')
self.assertRaises(FileNotFoundError, conf.WuttaConfig, files=[invalid])
def test_constructor_required_files_are_present(self):
first = self.write_file('first.conf', """\
[foo]
bar = 1
baz = A
""")
second = self.write_file('second.conf', """\
[wutta.config]
require = %(here)s/first.conf
[foo]
baz = B
""")
config = conf.WuttaConfig(files=[second])
self.assertEqual(len(config.files_read), 2)
# nb. files_read listing is in order of "priority" which is
# same the as order in which files were initially read
self.assertEqual(config.files_read[0], second)
self.assertEqual(config.files_read[1], first)
self.assertEqual(config.get('foo.bar'), '1')
self.assertEqual(config.get('foo.baz'), 'B')
def test_constructor_required_files_are_missing(self):
second = self.write_file('second.conf', """\
[wutta.config]
require = %(here)s/first.conf
[foo]
baz = B
""")
self.assertRaises(FileNotFoundError, conf.WuttaConfig, files=[second])
def test_constructor_included_files_are_present(self):
first = self.write_file('first.conf', """\
[foo]
bar = 1
baz = A
""")
second = self.write_file('second.conf', """\
[wutta.config]
include = %(here)s/first.conf
[foo]
baz = B
""")
config = conf.WuttaConfig(files=[second])
self.assertEqual(len(config.files_read), 2)
# nb. files_read listing is in order of "priority" which is
# same the as order in which files were initially read
self.assertEqual(config.files_read[0], second)
self.assertEqual(config.files_read[1], first)
self.assertEqual(config.get('foo.bar'), '1')
self.assertEqual(config.get('foo.baz'), 'B')
def test_constructor_included_files_are_missing(self):
second = self.write_file('second.conf', """\
[wutta.config]
include = %(here)s/first.conf
[foo]
baz = B
""")
config = conf.WuttaConfig(files=[second])
self.assertEqual(len(config.files_read), 1)
self.assertEqual(config.files_read[0], second)
self.assertIsNone(config.get('foo.bar'))
self.assertEqual(config.get('foo.baz'), 'B')
def test_constructor_defaults(self):
config = conf.WuttaConfig()
self.assertEqual(config.defaults, {})
self.assertIsNone(config.get('foo'))
config = conf.WuttaConfig(defaults={'foo': 'bar'})
self.assertEqual(config.defaults, {'foo': 'bar'})
self.assertEqual(config.get('foo'), 'bar')
def test_constructor_db_flags(self):
myfile = self.write_file('my.conf', """\
[wutta.config]
usedb = true
preferdb = true
""")
# flags are off by default
config = conf.WuttaConfig()
self.assertFalse(config.usedb)
self.assertFalse(config.preferdb)
# but may override via constructor
config = conf.WuttaConfig(usedb=True, preferdb=True)
self.assertTrue(config.usedb)
self.assertTrue(config.preferdb)
# and also may override via config file
config = conf.WuttaConfig(files=[myfile])
self.assertTrue(config.usedb)
self.assertTrue(config.preferdb)
def test_constructor_db_not_supported(self):
# flags are off by default
config = conf.WuttaConfig()
self.assertFalse(config.usedb)
self.assertFalse(config.preferdb)
# but caller may enable the flags (if sqlalchemy available)
config = conf.WuttaConfig(usedb=True, preferdb=True)
self.assertTrue(config.usedb)
self.assertTrue(config.preferdb)
# but db flags are force-disabled if sqlalchemy not available,
# regardless of flag values caller provides...
orig_import = __import__
def mock_import(name, *args, **kwargs):
if name == 'db':
raise ImportError
return orig_import(name, *args, **kwargs)
with patch('builtins.__import__', side_effect=mock_import):
config = conf.WuttaConfig(usedb=True, preferdb=True)
self.assertFalse(config.usedb)
self.assertFalse(config.preferdb)
def test_constructor_may_configure_logging(self):
myfile = self.write_file('my.conf', """\
[wutta.config]
configure_logging = true
""")
with patch.object(conf.WuttaConfig, '_configure_logging') as method:
# no logging config by default
config = conf.WuttaConfig()
method.assert_not_called()
# but may override via constructor
method.reset_mock()
config = conf.WuttaConfig(configure_logging=True)
method.assert_called_once()
# and also may override via config file
method.reset_mock()
config = conf.WuttaConfig(files=[myfile])
method.assert_called_once()
def test_constructor_configures_logging(self):
myfile = self.write_file('my.conf', """\
[wutta]
timezone.default = America/Chicago
[wutta.config]
configure_logging = true
""")
with patch('wuttjamaican.conf.logging') as logging:
# basic constructor attempts logging config
config = conf.WuttaConfig(configure_logging=True)
logging.config.fileConfig.assert_called_once()
# if logging config fails, error is *not* raised
logging.config.fileConfig.reset_mock()
logging.config.fileConfig.side_effect = configparser.NoSectionError('logging')
config = conf.WuttaConfig(configure_logging=True)
logging.config.fileConfig.assert_called_once()
# and it works if we specify config file
logging.config.fileConfig.reset_mock()
config = conf.WuttaConfig(files=[myfile])
logging.config.fileConfig.assert_called_once()
def test_setdefault(self):
config = conf.WuttaConfig()
# value is empty by default
self.assertIsNone(config.get('foo'))
# but we can change that by setting default
config.setdefault('foo', 'bar')
self.assertEqual(config.get('foo'), 'bar')
# also, value is returned when we set default
self.assertIsNone(config.get('baz'))
self.assertEqual(config.setdefault('baz', 'blarg'), 'blarg')
def test_get_require_with_default(self):
config = conf.WuttaConfig()
self.assertRaises(ValueError, config.get, 'foo', require=True, default='bar')
def test_get_require_missing(self):
config = conf.WuttaConfig()
self.assertRaises(ConfigurationError, config.get, 'foo', require=True)
def test_get_with_default(self):
config = conf.WuttaConfig()
# nb. returns None if no default specified
self.assertIsNone(config.get('foo'))
self.assertEqual(config.get('foo', default='bar'), 'bar')
def test_get_from_db(self):
# minimal config, but at least it needs db cxn info
config = conf.WuttaConfig(defaults={'wutta.db.default.url': 'sqlite://'})
session = Session()
# setup table for testing
session.execute(sa.text("""
create table setting (
name varchar(255) primary key,
value text
);
"""))
session.commit()
# setting not yet defined
self.assertIsNone(config.get_from_db('foo'))
# insert setting value to db
session.execute(sa.text("insert into setting values ('foo', 'bar')"))
session.commit()
# now setting returns a value
self.assertEqual(config.get_from_db('foo'), 'bar')
# also works if we provide the session
self.assertEqual(config.get_from_db('foo', session=session), 'bar')
session.close()
def test_get_default(self):
config = conf.WuttaConfig()
self.assertIsNone(config.get('foo'))
self.assertEqual(config.get('foo', default='bar'), 'bar')
def test_get_require(self):
config = conf.WuttaConfig()
self.assertIsNone(config.get('foo'))
self.assertRaises(ConfigurationError, config.get, 'foo', require=True)
def test_get_require_message(self):
config = conf.WuttaConfig()
self.assertIsNone(config.get('foo'))
try:
config.get('foo', require=True, message="makin stuff up")
except ConfigurationError as error:
self.assertIn("makin stuff up", str(error))
def test_get_preferdb(self):
# start out with a default value
config = conf.WuttaConfig(defaults={'wutta.db.default.url': 'sqlite://',
'foo': 'bar'})
self.assertEqual(config.get('foo'), 'bar')
session = Session()
# setup table for testing
session.execute(sa.text("""
create table setting (
name varchar(255) primary key,
value text
);
"""))
session.execute(sa.text("insert into setting values ('foo', 'baz')"))
session.commit()
# we did not specify usedb=True, so original default is still returned
self.assertFalse(config.usedb)
self.assertEqual(config.get('foo'), 'bar')
# usedb but no preferdb means original default is still returned
self.assertEqual(config.get('foo', usedb=True), 'bar')
# but preferdb should mean newer db value is returned
self.assertEqual(config.get('foo', usedb=True, preferdb=True), 'baz')
# try a different key to ensure db fallback works if no default present
session.execute(sa.text("insert into setting values ('blarg', 'blitz')"))
session.commit()
self.assertIsNone(config.get('blarg'))
self.assertEqual(config.get('blarg', usedb=True), 'blitz')
session.close()
def test_require(self):
config = conf.WuttaConfig()
self.assertRaises(ConfigurationError, config.require, 'foo')
class TestGenericDefaultFiles(TestCase):
def test_linux(self):
files = conf.generic_default_files('wuttatest')
self.assertIsInstance(files, list)
self.assertTrue(len(files) > 1)
self.assertIn('/etc/wuttatest.conf', files)
def test_win32(self):
win32com = MagicMock()
win32com.shell.SHGetSpecialFolderPath.return_value = r'C:' + os.sep
with patch.dict('sys.modules', **{'win32com.shell': win32com}):
with patch('wuttjamaican.conf.sys', platform='win32'):
files = conf.generic_default_files('wuttatest')
self.assertIsInstance(files, list)
self.assertTrue(len(files) > 1)
self.assertIn(os.path.join('C:', 'wuttatest.conf'), files)
def test_win32_broken(self):
orig_import = __import__
def mock_import(name, *args, **kwargs):
if name == 'win32com.shell':
raise ImportError
return orig_import(name, *args, **kwargs)
with patch('builtins.__import__', side_effect=mock_import):
with patch('wuttjamaican.conf.sys', platform='win32'):
files = conf.generic_default_files('wuttatest')
self.assertIsInstance(files, list)
self.assertEqual(len(files), 0)
class TestMakeConfig(TestCase):
# nb. we use appname='wuttatest' in this suite to avoid any
# "valid" default config files, env vars etc. which may be present
# on the dev machine
def setUp(self):
self.tempdir = tempfile.mkdtemp()
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 test_generic_default_files(self):
generic = self.write_file('generic.conf', '')
with patch('wuttjamaican.conf.generic_default_files') as generic_default_files:
with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig:
# generic files are used if nothing is specified
generic_default_files.return_value = [generic]
config = conf.make_config(appname='wuttatest')
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([generic], appname='wuttatest',
usedb=None, preferdb=None)
# make sure empty defaults works too
generic_default_files.reset_mock()
generic_default_files.return_value = []
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest')
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([], appname='wuttatest',
usedb=None, preferdb=None)
def test_specify_default_files(self):
generic = self.write_file('generic.conf', '')
myfile = self.write_file('my.conf', '')
with patch('wuttjamaican.conf.generic_default_files') as generic_default_files:
with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig:
# generic defaults are used if nothing specified
generic_default_files.return_value = [generic]
config = conf.make_config(appname='wuttatest')
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([generic], appname='wuttatest',
usedb=None, preferdb=None)
# can specify single default file
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest', default_files=myfile)
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with([myfile], appname='wuttatest',
usedb=None, preferdb=None)
# can specify default files as list
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest', default_files=[myfile])
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with([myfile], appname='wuttatest',
usedb=None, preferdb=None)
# can specify default files as callable
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest', default_files=lambda appname: [myfile])
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with([myfile], appname='wuttatest',
usedb=None, preferdb=None)
def test_specify_plus_files(self):
generic = self.write_file('generic.conf', '')
myfile = self.write_file('my.conf', '')
with patch('wuttjamaican.conf.generic_default_files') as generic_default_files:
with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig:
generic_default_files.return_value = [generic]
# no plus files by default
config = conf.make_config(appname='wuttatest')
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([generic], appname='wuttatest',
usedb=None, preferdb=None)
# can specify single plus file
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest', plus_files=myfile)
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([generic, myfile], appname='wuttatest',
usedb=None, preferdb=None)
# can specify plus files as list
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest', plus_files=[myfile])
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([generic, myfile], appname='wuttatest',
usedb=None, preferdb=None)
# can specify plus files via env
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest',
env={'WUTTATEST_CONFIG_PLUS_FILES': myfile})
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([generic, myfile], appname='wuttatest',
usedb=None, preferdb=None)
def test_specify_primary_files(self):
generic = self.write_file('generic.conf', '')
myfile = self.write_file('my.conf', '')
with patch('wuttjamaican.conf.generic_default_files') as generic_default_files:
with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig:
generic_default_files.return_value = [generic]
# generic files by default
config = conf.make_config(appname='wuttatest')
generic_default_files.assert_called_once_with('wuttatest')
WuttaConfig.assert_called_once_with([generic], appname='wuttatest',
usedb=None, preferdb=None)
# can specify single primary file (nb. no default files)
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(myfile, appname='wuttatest')
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with([myfile], appname='wuttatest',
usedb=None, preferdb=None)
# can specify primary files as list
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config([myfile], appname='wuttatest')
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with([myfile], appname='wuttatest',
usedb=None, preferdb=None)
# can specify primary files via env
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest',
env={'WUTTATEST_CONFIG_FILES': myfile})
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with([myfile], appname='wuttatest',
usedb=None, preferdb=None)
def test_extensions(self):
generic = self.write_file('generic.conf', '')
myfile = self.write_file('my.conf', '')
with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig:
with patch('wuttjamaican.conf.load_entry_points') as load_entry_points:
# no entry points loaded if extend=False
config = conf.make_config(appname='wuttatest', extend=False)
WuttaConfig.assert_called_once_with([], appname='wuttatest',
usedb=None, preferdb=None)
load_entry_points.assert_not_called()
# confirm entry points for default appname
load_entry_points.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wutta')
WuttaConfig.assert_called_once_with([], appname='wutta',
usedb=None, preferdb=None)
load_entry_points.assert_called_once_with('wutta.config.extensions')
# confirm entry points for custom appname
load_entry_points.reset_mock()
WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest')
WuttaConfig.assert_called_once_with([], appname='wuttatest',
usedb=None, preferdb=None)
load_entry_points.assert_called_once_with('wuttatest.config.extensions')
# confirm extensions are invoked
load_entry_points.reset_mock()
foo_obj = MagicMock()
foo_cls = MagicMock(return_value=foo_obj)
load_entry_points.return_value = {'foo': foo_cls}
WuttaConfig.reset_mock()
testconfig = MagicMock()
WuttaConfig.return_value = testconfig
config = conf.make_config(appname='wuttatest')
WuttaConfig.assert_called_once_with([], appname='wuttatest',
usedb=None, preferdb=None)
load_entry_points.assert_called_once_with('wuttatest.config.extensions')
foo_cls.assert_called_once_with()
foo_obj.configure.assert_called_once_with(testconfig)

216
tests/test_util.py Normal file
View file

@ -0,0 +1,216 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
from unittest.mock import patch, MagicMock
# nb. setuptools must be imported before distutils, else weird
# behavior may ensue within some of the tests below
import setuptools
from wuttjamaican import util
class TestLoadEntryPoints(TestCase):
def test_empty(self):
# empty set returned for unknown group
result = util.load_entry_points('this_should_never_exist!!!!!!')
self.assertEqual(result, {})
def test_basic(self):
# load some entry points which should "always" be present,
# even in a testing environment. basic sanity check
result = util.load_entry_points('console_scripts')
self.assertTrue(len(result) >= 1)
self.assertIn('pip', result)
def test_error(self):
entry_point = MagicMock()
entry_point.load.side_effect = NotImplementedError("just a testin")
entry_points = MagicMock()
entry_points.select.return_value = [entry_point]
importlib = MagicMock()
importlib.metadata.entry_points.return_value = entry_points
with patch.dict('sys.modules', **{'importlib': importlib}):
# empty set returned if errors suppressed
result = util.load_entry_points('wuttatest.thingers', ignore_errors=True)
self.assertEqual(result, {})
importlib.metadata.entry_points.assert_called_once_with()
entry_points.select.assert_called_once_with(group='wuttatest.thingers')
entry_point.load.assert_called_once_with()
# error is raised, if not suppressed
importlib.metadata.entry_points.reset_mock()
entry_points.select.reset_mock()
entry_point.load.reset_mock()
self.assertRaises(NotImplementedError, util.load_entry_points, 'wuttatest.thingers')
importlib.metadata.entry_points.assert_called_once_with()
entry_points.select.assert_called_once_with(group='wuttatest.thingers')
entry_point.load.assert_called_once_with()
def test_pkg_resources_empty(self):
orig_import = __import__
def mock_import(name, *args, **kwargs):
if name == 'importlib.metadata':
raise ImportError
return orig_import(name, *args, **kwargs)
with patch('builtins.__import__', side_effect=mock_import):
# empty set returned for unknown group
result = util.load_entry_points('this_should_never_exist!!!!!!')
self.assertEqual(result, {})
def test_pkg_resources_basic(self):
orig_import = __import__
def mock_import(name, *args, **kwargs):
if name == 'importlib.metadata':
raise ImportError
return orig_import(name, *args, **kwargs)
with patch('builtins.__import__', side_effect=mock_import):
# load some entry points which should "always" be present,
# even in a testing environment. basic sanity check
result = util.load_entry_points('console_scripts')
self.assertTrue(len(result) >= 1)
self.assertIn('pip', result)
def test_pkg_resources_error(self):
orig_import = __import__
entry_point = MagicMock()
entry_point.load.side_effect = NotImplementedError("just a testin")
iter_entry_points = MagicMock(return_value=[entry_point])
pkg_resources = MagicMock(iter_entry_points=iter_entry_points)
def mock_import(name, *args, **kwargs):
if name == 'importlib.metadata':
raise ImportError
return orig_import(name, *args, **kwargs)
with patch('builtins.__import__', side_effect=mock_import):
with patch.dict('sys.modules', **{'pkg_resources': pkg_resources}):
# empty set returned if errors suppressed
result = util.load_entry_points('wuttatest.thingers', ignore_errors=True)
self.assertEqual(result, {})
iter_entry_points.assert_called_once_with('wuttatest.thingers')
entry_point.load.assert_called_once_with()
# error is raised, if not suppressed
iter_entry_points.reset_mock()
entry_point.load.reset_mock()
self.assertRaises(NotImplementedError, util.load_entry_points, 'wuttatest.thingers')
iter_entry_points.assert_called_once_with('wuttatest.thingers')
entry_point.load.assert_called_once_with()
class TestLoadObject(TestCase):
def test_missing_spec(self):
self.assertRaises(ValueError, util.load_object, None)
def test_basic(self):
result = util.load_object('unittest:TestCase')
self.assertIs(result, TestCase)
class TestParseBool(TestCase):
def test_null(self):
self.assertIsNone(util.parse_bool(None))
def test_bool(self):
self.assertTrue(util.parse_bool(True))
self.assertFalse(util.parse_bool(False))
def test_string_true(self):
self.assertTrue(util.parse_bool('true'))
self.assertTrue(util.parse_bool('yes'))
self.assertTrue(util.parse_bool('y'))
self.assertTrue(util.parse_bool('on'))
self.assertTrue(util.parse_bool('1'))
def test_string_false(self):
self.assertFalse(util.parse_bool('false'))
self.assertFalse(util.parse_bool('no'))
self.assertFalse(util.parse_bool('n'))
self.assertFalse(util.parse_bool('off'))
self.assertFalse(util.parse_bool('0'))
# nb. assume false for unrecognized input
self.assertFalse(util.parse_bool('whatever-else'))
class TestParseList(TestCase):
def test_null(self):
value = util.parse_list(None)
self.assertIsInstance(value, list)
self.assertEqual(len(value), 0)
def test_single_value(self):
value = util.parse_list('foo')
self.assertEqual(len(value), 1)
self.assertEqual(value[0], 'foo')
def test_single_value_padded_by_spaces(self):
value = util.parse_list(' foo ')
self.assertEqual(len(value), 1)
self.assertEqual(value[0], 'foo')
def test_slash_is_not_a_separator(self):
value = util.parse_list('/dev/null')
self.assertEqual(len(value), 1)
self.assertEqual(value[0], '/dev/null')
def test_multiple_values_separated_by_whitespace(self):
value = util.parse_list('foo bar baz')
self.assertEqual(len(value), 3)
self.assertEqual(value[0], 'foo')
self.assertEqual(value[1], 'bar')
self.assertEqual(value[2], 'baz')
def test_multiple_values_separated_by_commas(self):
value = util.parse_list('foo,bar,baz')
self.assertEqual(len(value), 3)
self.assertEqual(value[0], 'foo')
self.assertEqual(value[1], 'bar')
self.assertEqual(value[2], 'baz')
def test_multiple_values_separated_by_whitespace_and_commas(self):
value = util.parse_list(' foo, bar baz')
self.assertEqual(len(value), 3)
self.assertEqual(value[0], 'foo')
self.assertEqual(value[1], 'bar')
self.assertEqual(value[2], 'baz')
def test_multiple_values_separated_by_whitespace_and_commas_with_some_quoting(self):
value = util.parse_list("""
foo
"C:\\some path\\with spaces\\and, a comma",
baz
""")
self.assertEqual(len(value), 3)
self.assertEqual(value[0], 'foo')
self.assertEqual(value[1], 'C:\\some path\\with spaces\\and, a comma')
self.assertEqual(value[2], 'baz')
def test_multiple_values_separated_by_whitespace_and_commas_with_single_quotes(self):
value = util.parse_list("""
foo
'C:\\some path\\with spaces\\and, a comma',
baz
""")
self.assertEqual(len(value), 3)
self.assertEqual(value[0], 'foo')
self.assertEqual(value[1], 'C:\\some path\\with spaces\\and, a comma')
self.assertEqual(value[2], 'baz')