Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate create sub resources #93

Merged
merged 28 commits into from Oct 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fe8e53d
automated create sub resources
UnDarkle Sep 12, 2018
5121f47
added ut
UnDarkle Sep 13, 2018
0ffc827
Merge branch 'master' into pretty_sub_resources
UnDarkle Sep 13, 2018
a6c7f18
fix ut attempt
UnDarkle Sep 13, 2018
3a82967
Merge branch 'pretty_sub_resources' of https://github.com/gregoil/rot…
UnDarkle Sep 13, 2018
d38b665
fix import loop
UnDarkle Sep 13, 2018
6e37a00
fixed ut
UnDarkle Sep 13, 2018
c6bcbdb
really fix ut
UnDarkle Sep 13, 2018
1a18122
try fix ut again
UnDarkle Sep 13, 2018
4c464c7
fixed get sub data in ut
UnDarkle Sep 13, 2018
f991c40
final ut fix before review
UnDarkle Sep 13, 2018
1f90134
used placeholder
UnDarkle Sep 20, 2018
af4f303
Merge branch 'master' into pretty_sub_resources
UnDarkle Sep 20, 2018
4089b17
Merge branch 'master' into pretty_sub_resources
UnDarkle Oct 16, 2018
d7bebc4
used metaclass to create placeholders
UnDarkle Oct 16, 2018
71d7ff0
fixed by tox
UnDarkle Oct 16, 2018
7a91c8d
fixed by run some
UnDarkle Oct 16, 2018
de9adf9
used class getattr instead
UnDarkle Oct 16, 2018
f6dd3e3
fixed by tox
UnDarkle Oct 16, 2018
0cb19e8
another try
UnDarkle Oct 16, 2018
65b7953
should be working now
UnDarkle Oct 16, 2018
20530e2
fixed up ut a bit
UnDarkle Oct 16, 2018
f2d8121
created a different ut for service
UnDarkle Oct 16, 2018
47de033
fixed by tox
UnDarkle Oct 16, 2018
3aa50ef
fixed ut run
osherdp Oct 16, 2018
02aaa92
removed empty line
osherdp Oct 16, 2018
f0b79f9
fixed by review
UnDarkle Oct 16, 2018
6505ba9
Merge remote-tracking branch 'origin/pretty_sub_resources' into prett…
UnDarkle Oct 16, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.2.2"
__version__ = "4.3.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 in (object, type):
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 @@ -14,6 +14,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 @@ -102,23 +103,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 @@ -131,7 +115,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
6 changes: 3 additions & 3 deletions src/rotest/management/__init__.py
@@ -1,3 +1,3 @@
from models import ResourceData
from base_resource import BaseResource
from client.manager import ClientResourceManager
from models import ResourceData
from base_resource import BaseResource
from client.manager import ClientResourceManager
33 changes: 29 additions & 4 deletions src/rotest/management/base_resource.py
Expand Up @@ -8,9 +8,12 @@

from ipdbugger import debug
from attrdict import AttrDict
from django.db.models.fields.related import \
ReverseSingleRelatedObjectDescriptor

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, DataPointer


class ConvertToKwargsMeta(type):
Expand Down Expand Up @@ -77,8 +80,9 @@ def __init__(self, data=None, **kwargs):

if data is not None:
self.data = data
for field_name, field_value in self.data.get_fields().iteritems():
setattr(self, field_name, field_value)
if isinstance(data, ResourceData):
for field_name, field_value in data.get_fields().iteritems():
setattr(self, field_name, field_value)

else:
self.data = AttrDict()
Expand All @@ -95,13 +99,34 @@ 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__
actual_kwargs = sub_placeholder.kwargs.copy()
for key, value in sub_placeholder.kwargs.iteritems():
if isinstance(value, ReverseSingleRelatedObjectDescriptor):
actual_kwargs[key] = getattr(self.data, value.field.name)

elif isinstance(value, DataPointer):
actual_kwargs[key] = getattr(self.data, value.field_name)

sub_resource = sub_class(**actual_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
20 changes: 10 additions & 10 deletions src/rotest/management/models/__init__.py
@@ -1,10 +1,10 @@
"""Define Rotest's common models.

The Django infrastructure expects a models.py file containing all the models
definitions for each application. This folder is a workaround used in order
to separate the different common application models into different files.
"""
# pylint: disable=unused-import
from rotest.management.models.resource_data import ResourceData
from rotest.management.models.ut_models import \
DemoResourceData, DemoComplexResourceData
"""Define Rotest's common models.
The Django infrastructure expects a models.py file containing all the models
definitions for each application. This folder is a workaround used in order
to separate the different common application models into different files.
"""
# pylint: disable=unused-import
from rotest.management.models.resource_data import ResourceData
from rotest.management.models.ut_models import \
DemoResourceData, DemoComplexResourceData
21 changes: 20 additions & 1 deletion src/rotest/management/models/resource_data.py
Expand Up @@ -9,6 +9,8 @@
from datetime import datetime

from django.db import models
from django.utils import six
from django.db.models.base import ModelBase
from django.contrib.auth import models as auth_models
from django.core.exceptions import ObjectDoesNotExist, ValidationError

Expand All @@ -17,7 +19,24 @@
from rotest.common.django_utils import get_sub_model, linked_unicode


class ResourceData(models.Model):
class DataPointer(object):
"""Pointer to a field in the resource's data."""
def __init__(self, field_name):
self.field_name = field_name


class DataBase(ModelBase):
"""Metaclass that creates data pointers for django fields."""
def __getattr__(cls, key):
if hasattr(cls, '_meta') and \
key in (field.name for field in cls._meta.fields):

return DataPointer(key)

raise AttributeError(key)


class ResourceData(six.with_metaclass(DataBase, models.Model)):
"""Represent a container for a resource's global data.

Inheriting resource datas may add more fields, specific to the resource.
Expand Down