Skip to content

Commit

Permalink
Merge pull request #46 from gregoil/resources_requests_sugar
Browse files Browse the repository at this point in the history
Resources requests sugar
  • Loading branch information
gregoil committed Apr 9, 2018
2 parents b94f98f + 1075b3f commit 3c8aa31
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 24 deletions.
8 changes: 3 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ Now, an example for a test:
.. code-block:: python
from rotest.core.runner import main
from rotest.core.case import TestCase, request
from rotest.core.case import TestCase
class SimpleCalculationTest(TestCase):
resources = [request("calculator", Calculator)]
calculator = Calculator()
def test_simple_calculation(self):
self.assertEqual(self.calculator.calculate("1+2"), 3)
Expand All @@ -93,9 +93,7 @@ Now, an example for a test:
main(SimpleCalculationTest)
The test can include the `setUp` and `tearDown` methods of `unittest` as
well, and it differs only in the request for resources. The request includes
the target member name, the requested class and might include more
parameters for finding the suitable resource.
well, and it differs only in the request for resources.

Following, those are the options exposed when running the test:

Expand Down
6 changes: 2 additions & 4 deletions docs/advanced/blocks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ Example
class DemoFlow(TestFlow):
"""Demo test-flow."""
resources = (request('resource1', SomeResourceClass,
some_limitation=LIMITATION),)
resource1 = SomeResourceClass(some_limitation=LIMITATION)
common = {'input2': INPUT_VALUE}
Expand Down Expand Up @@ -297,8 +296,7 @@ The functions gets the following arguments:
class DemoFlow(TestFlow):
"""Demo test-flow."""
resources = (request('resource1', SomeResourceClass,
some_limitation=LIMITATION),)
resource1 = SomeResourceClass(some_limitation=LIMITATION)
blocks = (DemoBlock1,
DemoBlock2,
Expand Down
4 changes: 2 additions & 2 deletions docs/cli_options/client_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,8 @@ As an example, let's suppose we have the following test:
.. code-block:: python
class SomeTest(TestCase):
resources = [request("res1", Resource1),
request("res2", Resource2)]
res1 = Resource1()
res2 = Resource2()
def test(self):
...
Expand Down
4 changes: 2 additions & 2 deletions docs/intro/using_resources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,13 @@ following content:
.. code-block:: python
from rotest.core.runner import main
from rotest.core.case import TestCase, request
from rotest.core.case import TestCase
from resources.calculator import Calculator
class AddTest(TestCase):
resources = [request("calc", Calculator)]
calc = Calculator()
def test_add(self):
result = self.calc.calculate("1 + 1")
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

setup(
name='rotest',
version="2.7.6",
version="2.8.0",
description="Resource oriented testing framework",
long_description=open("README.rst").read(),
license="MIT",
Expand Down
28 changes: 24 additions & 4 deletions src/rotest/core/abstract_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from attrdict import AttrDict

from rotest.common.config import ROTEST_WORK_DIR
from rotest.management.base_resource import BaseResource
from rotest.management.client.manager import ResourceRequest
from rotest.management.client.manager import ClientResourceManager

Expand Down Expand Up @@ -88,6 +89,28 @@ def __init__(self, indexer=count(), methodName='runTest',
self._is_client_local = False
self.resource_manager = resource_manager

@classmethod
def get_resource_requests(cls):
"""Return a list of all the resource requests this test makes.
Resource requests can be done both by overriding the class's
'resources' field and by declaring class fields that point to a
BaseResource instance.
Returns:
list. resource requests of the test class.
"""
all_requests = list(cls.resources)
for field_name in cls.__dict__:
if not field_name.startswith("_"):
field = getattr(cls, field_name)
if isinstance(field, BaseResource):
all_requests.append(request(field_name,
field.__class__,
**field.kwargs))

return all_requests

def create_resource_manager(self):
"""Create a new resource manager client instance.
Expand Down Expand Up @@ -128,8 +151,7 @@ def add_resources(self, resources):
for name, resource in resources.iteritems():
setattr(self, name, resource)

def request_resources(self, resources_to_request, use_previous=False,
is_global=False):
def request_resources(self, resources_to_request, use_previous=False):
"""Lock the requested resources and prepare them for the test.
Lock the required resources using the resource manager, then assign
Expand All @@ -141,8 +163,6 @@ def request_resources(self, resources_to_request, use_previous=False,
resources_to_request (list): list of resource requests to lock.
use_previous (bool): whether to use previously locked resources and
release the unused ones.
is_global (bool): whether to inject the resources to the parent and
sibling components or not.
"""
if len(resources_to_request) == 0:
# No resources to requested
Expand Down
4 changes: 3 additions & 1 deletion src/rotest/core/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ def setup_method_wrapper(*args, **kwargs):
if skip_reason is not None:
self.skipTest(skip_reason)

self.request_resources(self.resources, use_previous=True)
self.request_resources(self.get_resource_requests(),
use_previous=True)

try:
setup_method(*args, **kwargs)
self.result.setupFinished(self)
Expand Down
2 changes: 1 addition & 1 deletion src/rotest/core/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def _validate_inputs(self, extra_inputs=[]):
Raises:
AttributeError: not all inputs were passed to the block.
"""
fields = [request.name for request in self.resources]
fields = [request.name for request in self.get_resource_requests()]
fields.extend(extra_inputs)
for block in self:
block._validate_inputs(fields)
Expand Down
3 changes: 2 additions & 1 deletion src/rotest/core/flow_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ def setup_method_wrapper(*args, **kwargs):
self.skipTest(self.PREVIOUS_FAILED_MESSAGE)

try:
self.request_resources(self.resources, use_previous=True)
self.request_resources(self.get_resource_requests(),
use_previous=True)

except Exception as err:
if isinstance(err, ServerError):
Expand Down
1 change: 1 addition & 0 deletions src/rotest/management/base_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class BaseResource(object):
def __init__(self, data=None, **kwargs):
# We use core_log as default logger in case
# that resource is used outside case.
self.kwargs = kwargs
self.logger = core_log

if data is not None:
Expand Down
51 changes: 51 additions & 0 deletions tests/core/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ class TempSuccessCase(SuccessCase):
resources = (request('test_resource', DemoResource, name=RESOURCE_NAME),)


class TempComplexRequestCase(SuccessCase):
"""Inherit class and override resources requests."""
__test__ = False

resources = (request('res1', DemoResource, name='available_resource1'),)
res2 = DemoResource(name='available_resource2')


class TempDynamicResourceLockingCase(DynamicResourceLockingCase):
"""Inherit class and override resources requests."""
__test__ = False
Expand Down Expand Up @@ -170,6 +178,49 @@ def test_success_case_run(self):

self.validate_resource(test_resource)

def test_complex_resource_request(self):
"""Test a TestCase with all the ways to request resources.
* Way 1 - overriding 'resources'.
* Way 2 - declaring fields with BaseResource instance.
"""
case = self._run_case(TempComplexRequestCase)

self.assertTrue(self.result.wasSuccessful(),
'Case failed when it should have succeeded')

# === Validate case data object ===
self.assertTrue(case.data.success)

test_resources = case.all_resources
locked_names = []
for resource in test_resources.values():
self.assertTrue(isinstance(resource, DemoResource),
"Got wrong resource %r for the request" % resource)

test_resource = DemoResourceData.objects.get(name=resource.name)
self.validate_resource(test_resource)
locked_names.append(resource.name)

self.assertEqual(len(test_resources), 2,
"Unexpected number of resources, expected %r got %r" %
(2, len(test_resources)))

for request_name in ['res1', 'res2']:
self.assertTrue(request_name in test_resources,
"Test didn't request a resource for %s" %
request_name)

self.assertTrue(request_name in case.__dict__,
"Test doesn't contain field named %s" %
request_name)

self.assertIn('available_resource1', locked_names,
"Resource request using 'resources' ignored kwargs")

self.assertIn('available_resource2', locked_names,
"Resource request using class field ignored kwargs")

def test_dynamic_resources_locking(self):
"""Test that cases can dynamically lock resources.
Expand Down
13 changes: 10 additions & 3 deletions tests/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,13 @@ def _lock_resources(self, descriptors, timeout=None):

try:
available_resources = data_type.objects.filter(
is_usable=True, **descriptor.properties)
is_usable=True, **descriptor.properties)

prev_locks = [prev.name for prev in resources]
available_resources = [resource
for resource in available_resources
if resource.name not in prev_locks]

if len(available_resources) == 0:
raise ResourceDoesNotExistError()

Expand All @@ -252,8 +258,9 @@ def _release_resources(self, resources):
if resource in self.locked_resources:
self.locked_resources.remove(resource)

[resource.data.save() for resource in resources
if resource.DATA_CLASS is not None]
for resource in resources:
if resource.DATA_CLASS is not None:
resource.data.save()

def query_resources(self, descriptor):
"""Query the content of the server's DB.
Expand Down

0 comments on commit 3c8aa31

Please sign in to comment.