Skip to content

Commit

Permalink
Merge d38b665 into feaf3f2
Browse files Browse the repository at this point in the history
  • Loading branch information
UnDarkle committed Sep 13, 2018
2 parents feaf3f2 + d38b665 commit ab83a1b
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 47 deletions.
2 changes: 1 addition & 1 deletion setup.py
@@ -1,7 +1,7 @@
"""Setup file for handling packaging and distribution."""
from setuptools import setup, find_packages

__version__ = "4.0.2"
__version__ = "4.1.0"

result_handlers = [
"db = rotest.core.result.handlers.db_handler:DBHandler",
Expand Down
28 changes: 28 additions & 0 deletions src/rotest/common/utils.py
Expand Up @@ -72,3 +72,31 @@ def get_work_dir(base_dir, test_name, test_item):

os.makedirs(work_dir)
return work_dir


def get_class_fields(cls, field_type):
"""Get all fields of the class that inherit from the given type.
* This method searches also in the parent classes.
* Fields of sons override fields of parents.
* Ignores fields starting with '_'.
Args:
cls (type): class to search.
field_type (type): field type to find.
Yields:
tuple. all found (field name, field value).
"""
if cls is object:
return

for parent_class in cls.__bases__:
for (field_name, field) in get_class_fields(parent_class, field_type):
yield (field_name, field)

for field_name in cls.__dict__:
if not field_name.startswith("_"):
field = getattr(cls, field_name)
if isinstance(field, field_type):
yield (field_name, field)
20 changes: 2 additions & 18 deletions src/rotest/core/abstract_test.py
Expand Up @@ -13,6 +13,7 @@
from ipdbugger import debug
from attrdict import AttrDict

from rotest.common.utils import get_class_fields
from rotest.core.models.case_data import TestOutcome
from rotest.management.base_resource import BaseResource
from rotest.management.client.manager import ResourceRequest
Expand Down Expand Up @@ -101,23 +102,6 @@ def release_resource_loggers(self):
for resource in self.all_resources.itervalues():
resource.release_logger(self.logger)

@classmethod
def get_resource_requests_fields(cls):
"""Yield tuples of all the resource request fields of this test.
Yields:
tuple. (requests name, request field) tuples of the test class.
"""
checked_class = cls
while checked_class is not AbstractTest:
for field_name in checked_class.__dict__:
if not field_name.startswith("_"):
field = getattr(checked_class, field_name)
if isinstance(field, BaseResource):
yield (field_name, field)

checked_class = checked_class.__bases__[0]

@classmethod
def get_resource_requests(cls):
"""Return a list of all the resource requests this test makes.
Expand All @@ -130,7 +114,7 @@ def get_resource_requests(cls):
list. resource requests of the test class.
"""
all_requests = list(cls.resources)
for (field_name, field) in cls.get_resource_requests_fields():
for (field_name, field) in get_class_fields(cls, BaseResource):
new_request = request(field_name,
field.__class__,
**field.kwargs)
Expand Down
28 changes: 5 additions & 23 deletions src/rotest/core/block.py
Expand Up @@ -2,6 +2,7 @@
# pylint: disable=dangerous-default-value,too-many-arguments
from itertools import count

from rotest.common.utils import get_class_fields
from rotest.common.config import ROTEST_WORK_DIR
from rotest.core.flow_component import (AbstractFlowComponent, MODE_OPTIONAL,
MODE_FINALLY, MODE_CRITICAL,
Expand Down Expand Up @@ -105,40 +106,21 @@ def get_name(cls):

@classmethod
def get_inputs(cls):
"""Return a list of all the input instances of this block.
"""Return a dict of all the input instances of this block.
Returns:
dict. block's inputs (name: input placeholder instance).
"""
all_inputs = {}
checked_class = cls
while checked_class is not TestBlock:
all_inputs.update({key: value for (key, value) in
checked_class.__dict__.iteritems()
if (isinstance(value, BlockInput) and
key not in all_inputs)})

checked_class = checked_class.__bases__[0]

return all_inputs
return dict(get_class_fields(cls, BlockInput))

@classmethod
def get_outputs(cls):
"""Return a list of all the input instances of this block.
"""Return a dict of all the input instances of this block.
Returns:
dict. block's inputs (name: input placeholder instance).
"""
all_outputs = {}
checked_class = cls
while checked_class is not TestBlock:
all_outputs.update({key: value for (key, value) in
checked_class.__dict__.iteritems()
if isinstance(value, BlockOutput)})

checked_class = checked_class.__bases__[0]

return all_outputs
return dict(get_class_fields(cls, BlockOutput))

def _share_outputs(self):
"""Share all the declared outputs of the block."""
Expand Down
27 changes: 24 additions & 3 deletions src/rotest/management/base_resource.py
Expand Up @@ -10,7 +10,8 @@
from attrdict import AttrDict

from rotest.common import core_log
from rotest.common.utils import get_work_dir
from rotest.common.utils import get_work_dir, get_class_fields
from rotest.management.models.resource_data import ResourceData


class ConvertToKwargsMeta(type):
Expand Down Expand Up @@ -75,7 +76,7 @@ def __init__(self, data=None, **kwargs):
self.logger = core_log
self._prev_loggers = []

if data is not None:
if data is not None and isinstance(data, ResourceData):
self.data = data
for field_name, field_value in self.data.get_fields().iteritems():
setattr(self, field_name, field_value)
Expand All @@ -95,13 +96,33 @@ def __init__(self, data=None, **kwargs):
def create_sub_resources(self):
"""Create and return the sub resources if needed.
By default, this method searches for sub-resources declared as
class fields, where the 'data' attribute in the declaration points
to the name of the sub-resource's data field under the current's data.
Override and assign sub-resources to fields in the current resource,
using the 'data' object.
Returns:
iterable. sub-resources created.
"""
return ()
sub_resources = []
for sub_name, sub_placeholder in get_class_fields(self.__class__,
BaseResource):

sub_class = sub_placeholder.__class__
if sub_class.DATA_CLASS is None:
sub_data = None

else:
sub_data = getattr(self.data, sub_placeholder.data)

sub_resource = sub_class(data=sub_data, **sub_placeholder.kwargs)

setattr(self, sub_name, sub_resource)
sub_resources.append(sub_resource)

return sub_resources

def is_available(self, user_name=""):
"""Return whether resource is available for the given user.
Expand Down
167 changes: 165 additions & 2 deletions tests/management/test_resource_manager.py
Expand Up @@ -13,6 +13,7 @@
from django.contrib.auth.models import User
from swaggapi.api.builder.client import requester

from rotest.management import BaseResource
from rotest.management.common.utils import LOCALHOST
from rotest.management.client.manager import (ClientResourceManager,
ResourceRequest)
Expand Down Expand Up @@ -573,8 +574,8 @@ def test_lock_release_complex_resource(self):
"Sub-resource %r should be locked but "
"found available" % sub_resource.name)

resource_instace = DemoResource(data=resources.get())
self.client._release_resources(resources=[resource_instace])
resource_instance = DemoResource(data=resources.get())
self.client._release_resources(resources=[resource_instance])

resources = DemoComplexResourceData.objects.filter(
name=self.COMPLEX_NAME)
Expand Down Expand Up @@ -1260,3 +1261,165 @@ def test_threaded_initialize(self):
self.assertEqual(len(ThreadedResource.THREADS), 2,
"%d threads were created instead of 2" %
len(ThreadedResource.THREADS))

def test_lock_alter_complex_resource(self):
"""Lock complex resource with the default 'create_sub_resources'.
* Validates the DB initial state.
* Requests an existing complex resource, using resource client.
* Validates that 1 resource returned.
* Validates the name of the returned resource.
* Validates the type of the returned resource.
* Validates the amount of sub-resources it has.
* Validates the resource and its subs were locked and initialized.
* Releases the locked resource, using resource client.
* Validates the above resource and it sub-resources are now available.
"""
class AlterDemoComplexResource(BaseResource):
"""Fake complex resource class, used in resource manager tests."""
DATA_CLASS = DemoComplexResourceData
demo1 = DemoResource(data='demo1')
demo2 = DemoResource(data='demo2')

def initialize(self):
"""Turns on the initialization flag."""
super(AlterDemoComplexResource, self).initialize()
self.data.initialization_flag = True
self.data.save()

resources = DemoComplexResourceData.objects.filter(
name=self.COMPLEX_NAME)

resources_num = len(resources)
self.assertEqual(resources_num, 1, "Expected 1 complex "
"resource with name %r in DB, found %d"
% (self.COMPLEX_NAME, resources_num))

resource, = resources
self.assertTrue(resource.is_available(), "Expected available "
"complex resource with name %r in DB, found %d"
% (self.COMPLEX_NAME, resources_num))

request = ResourceRequest('res1', AlterDemoComplexResource,
name=self.COMPLEX_NAME)

resources = self.client.request_resources(requests=[request]).values()

resources_num = len(resources)
self.assertEquals(resources_num, 1, "Expected list with 1 "
"resource in it but found %d" % resources_num)

resource, = resources
self.assertEquals(resource.name, self.COMPLEX_NAME,
"Expected resource with name %r but got %r"
% (self.COMPLEX_NAME, resource.name))

self.assertIsInstance(resource, request.type,
"Expected resource of type %r, but got %r"
% (request.type.__name__,
resource.__class__.__name__))

self.assertEquals(len(list(resource.get_sub_resources())), 2,
"Expected to have 2 sub-resources, found %r"
% resource.get_sub_resources())

self.assertTrue(resource.data.initialization_flag,
"Resource %r should have been initialized" %
resource.name)

for sub_resource in resource.get_sub_resources():
self.assertFalse(sub_resource in AlterDemoComplexResource.__dict__,
"Sub-resource %r is still a placeholder" %
sub_resource.name)

self.assertFalse(sub_resource.is_available(),
"Sub-resource %r should be locked but "
"found available" % sub_resource.name)

self.assertIsInstance(sub_resource, DemoResource,
"Expected sub-resource of type %r, got %r"
% (DemoResource.__name__,
sub_resource.__class__.__name__))

self.assertTrue(sub_resource.data.initialization_flag,
"Sub-resource %r should have been initialized" %
sub_resource.name)

resources_data = request.type.DATA_CLASS.objects.filter(~Q(owner=""),
name=self.COMPLEX_NAME)

resources_num = len(resources_data)
self.assertEquals(resources_num, 1, "Expected 1 locked "
"resource with name %r in DB, found %d"
% (self.COMPLEX_NAME, resources_num))

self.client.release_resources(resources=[resource])

resources = DemoComplexResourceData.objects.filter(
name=self.COMPLEX_NAME)

resource, = resources
self.assertTrue(resource.is_available(), "Expected available "
"complex resource with name %r in DB, found %d"
% (self.COMPLEX_NAME, resources_num))

def test_lock_alter_complex_service(self):
"""Lock complex service with the default 'create_sub_resources'.
* Validates the DB initial state.
* Requests an existing complex resource, using resource client.
* Validates that 1 resource returned.
* Validates the name of the returned resource.
* Validates the type of the returned resource.
* Validates the amount of sub-resources it has.
* Validates the resource was initialized.
* Validates the above resource and it sub-resources were created.
* Releases the locked resource, using resource client.
"""
class AlterDemoComplexService(BaseResource):
"""Fake complex service class, used in resource manager tests."""
DATA_CLASS = None
demo1 = DemoService()
demo2 = DemoService()

initialized = False

def initialize(self):
"""Turns on the initialization flag."""
super(AlterDemoComplexService, self).initialize()
self.initialized = True

request = ResourceRequest('res1', AlterDemoComplexService,
name=self.COMPLEX_NAME)

resources = self.client.request_resources(requests=[request]).values()

resource, = resources
self.assertEquals(resource.name, self.COMPLEX_NAME,
"Expected resource with name %r but got %r"
% (self.COMPLEX_NAME, resource.name))

self.assertIsInstance(resource, request.type,
"Expected resource of type %r, but got %r"
% (request.type.__name__,
resource.__class__.__name__))

self.assertEquals(len(list(resource.get_sub_resources())), 2,
"Expected to have 2 sub-resources, found %r"
% resource.get_sub_resources())

self.assertTrue(resource.initialized,
"Resource %r should have been initialized" %
resource.name)

for sub_resource in resource.get_sub_resources():
self.assertFalse(sub_resource in AlterDemoComplexService.__dict__,
"Sub-resource %r is still a placeholder" %
sub_resource.name)

self.assertIsInstance(sub_resource, DemoService,
"Expected sub-resource of type %r, got %r"
% (DemoResource.__name__,
sub_resource.__class__.__name__))

self.client.release_resources(resources=[resource])

0 comments on commit ab83a1b

Please sign in to comment.