Discover test case method dependencies using load_object function
Refactor fixture manager to avoid reverse control pattern. test cases method dependency on fixtures is now obtained from default parameter values discovered from test case methods signatures. Change-Id: I4eb04189adb9aab27923b167910f32c49c33d8e0
This commit is contained in:
parent
075ea47c20
commit
7f30a885f0
|
@ -20,11 +20,15 @@ from tobiko.common.managers import loader as loader_manager
|
|||
load_object = loader_manager.load_object
|
||||
load_module = loader_manager.load_module
|
||||
|
||||
discover_testcases = testcase_manager.discover_testcases
|
||||
|
||||
Fixture = fixture_manager.Fixture
|
||||
|
||||
get_fixture = fixture_manager.FIXTURES.get
|
||||
create_fixture = fixture_manager.FIXTURES.create
|
||||
delete_fixture = fixture_manager.FIXTURES.delete
|
||||
|
||||
discover_testcases = testcase_manager.discover_testcases
|
||||
fixture = fixture_manager.fixture
|
||||
is_fixture = fixture_manager.is_fixture
|
||||
get_fixture = fixture_manager.get_fixture
|
||||
create_fixture = fixture_manager.create_fixture
|
||||
delete_fixture = fixture_manager.delete_fixture
|
||||
get_required_fixtures = fixture_manager.get_required_fixtures
|
||||
discover_required_fixtures = fixture_manager.discover_required_fixtures
|
||||
create_fixtures = fixture_manager.create_fixtures
|
||||
delete_fixtures = fixture_manager.delete_fixtures
|
||||
|
|
|
@ -13,24 +13,61 @@
|
|||
# under the License.
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import abc
|
||||
import contextlib
|
||||
import inspect
|
||||
|
||||
import six
|
||||
|
||||
import tobiko
|
||||
|
||||
def get_fixture_name(obj):
|
||||
if isinstance(obj, six.string_types):
|
||||
return obj
|
||||
|
||||
elif (isinstance(obj, six.class_types) and
|
||||
issubclass(obj, Fixture)):
|
||||
return obj.fixture_name
|
||||
def fixture(obj):
|
||||
obj.__tobiko_fixture__ = True
|
||||
return object
|
||||
|
||||
msg = "{!r} is not a string type or a subclass of {!s}".format(
|
||||
obj, Fixture)
|
||||
raise TypeError(msg)
|
||||
|
||||
def is_fixture(obj):
|
||||
return getattr(obj, '__tobiko_fixture__', False)
|
||||
|
||||
|
||||
def get_fixture(obj, manager=None):
|
||||
manager = manager or FIXTURES
|
||||
return manager.get_fixture(obj)
|
||||
|
||||
|
||||
def create_fixture(obj, manager=None):
|
||||
manager = manager or FIXTURES
|
||||
return manager.create_fixture(obj)
|
||||
|
||||
|
||||
def delete_fixture(obj, manager=None):
|
||||
manager = manager or FIXTURES
|
||||
return manager.delete_fixture(obj)
|
||||
|
||||
|
||||
def get_required_fixtures(obj, manager=None):
|
||||
manager = manager or FIXTURES
|
||||
return manager.get_required_fixtures(obj)
|
||||
|
||||
|
||||
def discover_required_fixtures(objects, manager=None):
|
||||
manager = manager or FIXTURES
|
||||
return sorted(set(manager.discover_required_fixtures(objects, manager)))
|
||||
|
||||
|
||||
def create_fixtures(objects, manager=None):
|
||||
manager = manager or FIXTURES
|
||||
for _fixture in discover_required_fixtures(objects=objects,
|
||||
manager=manager):
|
||||
manager.create_fixture(_fixture)
|
||||
|
||||
|
||||
def delete_fixtures(objects, manager=None):
|
||||
manager = manager or FIXTURES
|
||||
for _fixture in discover_required_fixtures(objects=objects,
|
||||
manager=manager):
|
||||
return manager.delete_fixture(_fixture)
|
||||
|
||||
|
||||
class FixtureManager(object):
|
||||
|
@ -38,60 +75,159 @@ class FixtureManager(object):
|
|||
def __init__(self):
|
||||
self.fixtures = {}
|
||||
|
||||
def set(self, name, cls):
|
||||
if not issubclass(cls, Fixture):
|
||||
msg = "{!r} is not a subclass of {!s}".format(cls, Fixture)
|
||||
raise TypeError(msg)
|
||||
fixture = cls()
|
||||
actual_fixture = self.fixtures.setdefault(name, fixture)
|
||||
if actual_fixture is not fixture:
|
||||
msg = "Fixture with named {!r} already registered: {!r}".format(
|
||||
name, actual_fixture)
|
||||
raise ValueError(msg)
|
||||
return fixture
|
||||
def get_fixture(self, obj):
|
||||
name = get_object_name(obj)
|
||||
_fixture = self.fixtures.get(name)
|
||||
if _fixture is None:
|
||||
_fixture = self.init_fixture(name=name, obj=obj)
|
||||
assert isinstance(_fixture, Fixture)
|
||||
self.fixtures[name] = _fixture
|
||||
return _fixture
|
||||
|
||||
def get(self, cls_or_name):
|
||||
name = get_fixture_name(cls_or_name)
|
||||
fixture = self.fixtures.get(name)
|
||||
if fixture is None:
|
||||
raise ValueError('Invalid fixture name: {!r}'.format(name))
|
||||
return fixture
|
||||
def create_fixture(self, obj):
|
||||
return self.get_fixture(obj).create_fixture()
|
||||
|
||||
def create(self, cls_or_name):
|
||||
fixture = self.get(cls_or_name)
|
||||
fixture.create_fixture()
|
||||
return fixture
|
||||
def delete_fixture(self, obj):
|
||||
return self.get_fixture(obj).delete_fixture()
|
||||
|
||||
def delete(self, cls_or_name):
|
||||
fixture = self.get(cls_or_name)
|
||||
fixture.delete_fixture()
|
||||
return fixture
|
||||
def init_fixture(self, obj, name):
|
||||
if isinstance(obj, six.string_types):
|
||||
if name != obj:
|
||||
msg = ("Fixture name mismatch: "
|
||||
"{!r} != {!r}").format(name, obj.fixture_name)
|
||||
raise ValueError(msg)
|
||||
obj = tobiko.load_object(name)
|
||||
|
||||
if isinstance(obj, Fixture):
|
||||
if name != obj.fixture_name:
|
||||
msg = ("Fixture name mismatch: "
|
||||
"{!r} != {!r}").format(name, obj.fixture_name)
|
||||
raise ValueError(msg)
|
||||
return obj
|
||||
elif inspect.isclass(obj):
|
||||
if issubclass(obj, Fixture):
|
||||
return obj(fixture_name=name)
|
||||
elif inspect.isgeneratorfunction(obj):
|
||||
return ContextFixture(
|
||||
fixture_name=name, context=contextlib.contextmanager(obj))
|
||||
elif inspect.isfunction(obj):
|
||||
return FunctionFixture(fixture_name=name, function=obj)
|
||||
raise TypeError("Invalid fixture object type: {!r}".format(object))
|
||||
|
||||
def get_required_fixtures(self, obj):
|
||||
return sorted(set(self.discover_required_fixtures([obj])))
|
||||
|
||||
def discover_required_fixtures(self, objects):
|
||||
objects = list(objects)
|
||||
while objects:
|
||||
obj = objects.pop()
|
||||
if isinstance(obj, six.string_types):
|
||||
object_id = obj
|
||||
obj = tobiko.load_object(object_id)
|
||||
else:
|
||||
object_id = get_object_name(obj)
|
||||
|
||||
if is_fixture(obj):
|
||||
yield object_id
|
||||
|
||||
elif inspect.isfunction(obj) or inspect.ismethod(obj):
|
||||
for default in get_default_param_values(obj):
|
||||
if is_fixture(default):
|
||||
yield get_object_name(default)
|
||||
|
||||
if inspect.ismodule(obj):
|
||||
members = [obj for _, obj in inspect.getmembers(obj)
|
||||
if (inspect.isfunction(obj) or
|
||||
inspect.isclass(obj))]
|
||||
objects.extend(members)
|
||||
|
||||
elif inspect.isclass(obj):
|
||||
members = [obj for _, obj in inspect.getmembers(obj)
|
||||
if (inspect.isfunction(obj) or
|
||||
inspect.ismethod(obj))]
|
||||
objects.extend(members)
|
||||
|
||||
|
||||
FIXTURES = FixtureManager()
|
||||
|
||||
|
||||
class FixtureMeta(abc.ABCMeta):
|
||||
|
||||
def __new__(cls, name, bases, members): # pylint: disable=arguments-differ
|
||||
fixture_class = super(FixtureMeta, cls).__new__(cls, name, bases,
|
||||
members)
|
||||
if not inspect.isabstract(fixture_class):
|
||||
fixture_name = getattr(fixture_class, 'fixture_name', None)
|
||||
if fixture_name is None:
|
||||
fixture_class.fixture_name = fixture_name = (
|
||||
fixture_class.__module__ + '.' +
|
||||
fixture_class.__name__)
|
||||
FIXTURES.set(fixture_name, fixture_class)
|
||||
return fixture_class
|
||||
|
||||
|
||||
@six.add_metaclass(FixtureMeta)
|
||||
class Fixture(object):
|
||||
|
||||
__tobiko_fixture__ = True
|
||||
|
||||
def __init__(self, fixture_name):
|
||||
self.fixture_name = fixture_name
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_fixture(self):
|
||||
pass
|
||||
|
||||
def delete_fixture(self):
|
||||
pass
|
||||
|
||||
|
||||
class FunctionFixture(Fixture):
|
||||
|
||||
def __init__(self, fixture_name, function):
|
||||
super(FunctionFixture, self).__init__(fixture_name=fixture_name)
|
||||
assert callable(function)
|
||||
self.function = function
|
||||
|
||||
def create_fixture(self):
|
||||
return self.function()
|
||||
|
||||
|
||||
class ContextFixture(Fixture):
|
||||
|
||||
def __init__(self, fixture_name, context):
|
||||
super(ContextFixture, self).__init__(fixture_name=fixture_name)
|
||||
self.context = context
|
||||
|
||||
def create_fixture(self):
|
||||
return self.context.__enter__()
|
||||
|
||||
def delete_fixture(self):
|
||||
return self.context.__exit__(None, None, None)
|
||||
|
||||
|
||||
def get_object_name(obj):
|
||||
if isinstance(obj, six.string_types):
|
||||
return obj
|
||||
|
||||
if is_fixture(obj):
|
||||
name = getattr(obj, 'fixture_name', None)
|
||||
if name:
|
||||
return name
|
||||
|
||||
name = getattr(obj, '__qualname__', None)
|
||||
if name:
|
||||
return obj.__module__ + '.' + name
|
||||
|
||||
module = inspect.getmodule(obj).__name__
|
||||
if inspect.isclass(obj):
|
||||
return module + '.' + obj.__name__
|
||||
|
||||
parent_class = getattr(obj, 'im_class', None)
|
||||
if parent_class:
|
||||
return module + parent_class.__name__ + '.' + obj.__name__
|
||||
|
||||
if inspect.isfunction(obj):
|
||||
return module + '.' + obj.func_name
|
||||
|
||||
msg = "Unable to get fixture name from object {!r}".format(obj)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
||||
def get_default_param_values(obj):
|
||||
if hasattr(inspect, 'signature'):
|
||||
try:
|
||||
signature = inspect.signature(obj)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
return [param.default
|
||||
for param in signature.parameters.values()]
|
||||
|
||||
# Use old deprecated function 'getargspec'
|
||||
return list(inspect.getargspec(obj).defaults or # pylint: disable=W1505
|
||||
tuple())
|
||||
|
|
|
@ -19,17 +19,20 @@ from tobiko.tests import base
|
|||
|
||||
class TestFixture(tobiko.Fixture):
|
||||
|
||||
def __init__(self):
|
||||
created = False
|
||||
deleted = False
|
||||
|
||||
def reset(self):
|
||||
self.created = False
|
||||
self.deleted = False
|
||||
|
||||
reset = __init__
|
||||
|
||||
def create_fixture(self):
|
||||
self.created = True
|
||||
return 'created'
|
||||
|
||||
def delete_fixture(self):
|
||||
self.deleted = True
|
||||
return 'deleted'
|
||||
|
||||
|
||||
class FixtureTypeTest(base.TobikoTest):
|
||||
|
@ -71,10 +74,10 @@ class FixtureTypeTest(base.TobikoTest):
|
|||
self._test_create_fixture(self.fixture_type)
|
||||
|
||||
def _test_create_fixture(self, obj):
|
||||
fixture = tobiko.create_fixture(obj)
|
||||
self.assertIs(self.fixture, fixture)
|
||||
self.assertTrue(fixture.created)
|
||||
self.assertFalse(fixture.deleted)
|
||||
result = tobiko.create_fixture(obj)
|
||||
self.assertEqual('created', result)
|
||||
self.assertTrue(self.fixture.created)
|
||||
self.assertFalse(self.fixture.deleted)
|
||||
|
||||
def test_delete_fixture_by_name(self):
|
||||
self._test_delete_fixture(self.fixture_name)
|
||||
|
@ -83,7 +86,17 @@ class FixtureTypeTest(base.TobikoTest):
|
|||
self._test_delete_fixture(self.fixture_type)
|
||||
|
||||
def _test_delete_fixture(self, obj=TestFixture):
|
||||
fixture = tobiko.delete_fixture(obj)
|
||||
self.assertIs(self.fixture, fixture)
|
||||
self.assertFalse(fixture.created)
|
||||
self.assertTrue(fixture.deleted)
|
||||
result = tobiko.delete_fixture(obj)
|
||||
self.assertEqual('deleted', result)
|
||||
self.assertFalse(self.fixture.created)
|
||||
self.assertTrue(self.fixture.deleted)
|
||||
|
||||
def test_get_required_fixtures_from_method_by_type(
|
||||
self, _required_fixture=TestFixture):
|
||||
result = tobiko.get_required_fixtures(self.id())
|
||||
self.assertEqual([self.fixture_name], result)
|
||||
|
||||
def test_get_required_fixtures_from_test_class(
|
||||
self, _required_fixture=TestFixture):
|
||||
result = tobiko.get_required_fixtures(FixtureTypeTest)
|
||||
self.assertEqual([self.fixture_name], result)
|
||||
|
|
Loading…
Reference in New Issue