Skip to content

Commit

Permalink
Add ResourcedToStreamDecorator test result decorator for testresource…
Browse files Browse the repository at this point in the history
…s integration (#243)

This new decorator implements the TestResult protocol extension supported by test resources. For example, tt makes it possible to easily have resource-related events streamed to subunit.
  • Loading branch information
freeekanayaka committed Apr 11, 2017
1 parent bb7c7ec commit 01d4a9b
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 8 deletions.
6 changes: 6 additions & 0 deletions NEWS
Expand Up @@ -6,6 +6,12 @@ Changes and improvements to testtools_, grouped by release.
NEXT
~~~~

Improvements
------------

* New ``ResourcedToStreamDecorator`` for tracking lifecycle events of
test resources, and possibly integrate with subunit.

2.2.0
~~~~~

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Expand Up @@ -12,6 +12,7 @@ classifier =
[extras]
test =
testscenarios
testresources
unittest2>=1.1.0

[files]
Expand Down
2 changes: 2 additions & 0 deletions testtools/__init__.py
Expand Up @@ -18,6 +18,7 @@
'MultiTestResult',
'PlaceHolder',
'run_test_with',
'ResourcedToStreamDecorator',
'Tagger',
'TestCase',
'TestCommand',
Expand Down Expand Up @@ -85,6 +86,7 @@
ExtendedToOriginalDecorator,
ExtendedToStreamDecorator,
MultiTestResult,
ResourcedToStreamDecorator,
StreamFailFast,
StreamResult,
StreamResultRouter,
Expand Down
2 changes: 2 additions & 0 deletions testtools/testresult/__init__.py
Expand Up @@ -7,6 +7,7 @@
'ExtendedToOriginalDecorator',
'ExtendedToStreamDecorator',
'MultiTestResult',
'ResourcedToStreamDecorator',
'StreamFailFast',
'StreamResult',
'StreamResultRouter',
Expand All @@ -30,6 +31,7 @@
ExtendedToOriginalDecorator,
ExtendedToStreamDecorator,
MultiTestResult,
ResourcedToStreamDecorator,
StreamFailFast,
StreamResult,
StreamResultRouter,
Expand Down
21 changes: 15 additions & 6 deletions testtools/testresult/doubles.py
Expand Up @@ -2,6 +2,10 @@

"""Doubles of test result objects, useful for testing unittest code."""

from collections import namedtuple

from testtools.tags import TagContext

__all__ = [
'Python26TestResult',
'Python27TestResult',
Expand All @@ -11,9 +15,6 @@
]


from testtools.tags import TagContext


class LoggingBase(object):
"""Basic support for logging of results."""

Expand Down Expand Up @@ -219,6 +220,14 @@ def status(self, test_id=None, test_status=None, test_tags=None,
runnable=True, file_name=None, file_bytes=None, eof=False,
mime_type=None, route_code=None, timestamp=None):
self._events.append(
('status', test_id, test_status, test_tags,
runnable, file_name, file_bytes, eof, mime_type, route_code,
timestamp))
_StatusEvent(
'status', test_id, test_status, test_tags, runnable,
file_name, file_bytes, eof, mime_type, route_code,
timestamp))


# Convenience for easier access to status fields
_StatusEvent = namedtuple(
"_Event", [
"name", "test_id", "test_status", "test_tags", "runnable", "file_name",
"file_bytes", "eof", "mime_type", "route_code", "timestamp"])
53 changes: 53 additions & 0 deletions testtools/testresult/real.py
Expand Up @@ -6,6 +6,7 @@
'ExtendedToOriginalDecorator',
'ExtendedToStreamDecorator',
'MultiTestResult',
'ResourcedToStreamDecorator',
'StreamFailFast',
'StreamResult',
'StreamSummary',
Expand Down Expand Up @@ -1688,6 +1689,58 @@ def wasSuccessful(self):
return super(ExtendedToStreamDecorator, self).wasSuccessful()


class ResourcedToStreamDecorator(ExtendedToStreamDecorator):
"""Report ``testresources``-related activity to StreamResult objects.
Implement the resource lifecycle TestResult protocol extension supported
by the ``testresources.TestResourceManager`` class. At each stage of a
resource's lifecycle, a stream event with relevant details will be
emitted.
Each stream event will have its test_id field set to the resource manager's
identifier (see ``testresources.TestResourceManager.id()``) plus the method
being executed (either 'make' or 'clean').
The test_status will be either 'inprogress' or 'success'.
The runnable flag will be set to False.
"""

def startMakeResource(self, resource):
self._convertResourceLifecycle(resource, 'make', 'start')

def stopMakeResource(self, resource):
self._convertResourceLifecycle(resource, 'make', 'stop')

def startCleanResource(self, resource):
self._convertResourceLifecycle(resource, 'clean', 'start')

def stopCleanResource(self, resource):
self._convertResourceLifecycle(resource, 'clean', 'stop')

def _convertResourceLifecycle(self, resource, method, phase):
"""Convert a resource lifecycle report to a stream event."""

# If the resource implements the TestResourceManager.id() API, let's
# use it, otherwise fallback to the class name.
if safe_hasattr(resource, "id"):
resource_id = resource.id()
else:
resource_id = "%s.%s" % (
resource.__class__.__module__, resource.__class__.__name__)

test_id = '%s.%s' % (resource_id, method)

if phase == 'start':
test_status = 'inprogress'
else:
test_status = 'success'

self.status(
test_id=test_id, test_status=test_status, runnable=False,
timestamp=self._now())


class StreamToExtendedDecorator(StreamResult):
"""Convert StreamResult API calls into ExtendedTestResult calls.
Expand Down
77 changes: 75 additions & 2 deletions testtools/tests/test_testresult.py
Expand Up @@ -15,17 +15,19 @@
import tempfile
import threading
from unittest import TestSuite

from extras import safe_hasattr, try_imports
from extras import safe_hasattr, try_imports, try_import

Queue = try_imports(['Queue.Queue', 'queue.Queue'])

testresources = try_import('testresources')

from testtools import (
CopyStreamResult,
ExtendedToOriginalDecorator,
ExtendedToStreamDecorator,
MultiTestResult,
PlaceHolder,
ResourcedToStreamDecorator,
StreamFailFast,
StreamResult,
StreamResultRouter,
Expand Down Expand Up @@ -590,6 +592,12 @@ def _make_result(self):
return ExtendedToStreamDecorator(StreamResult())


class TestResourcedToStreamDecoratorContract(TestCase, TestStreamResultContract):

def _make_result(self):
return ResourcedToStreamDecorator(StreamResult())


class TestStreamSummaryResultContract(TestCase, TestStreamResultContract):

def _make_result(self):
Expand Down Expand Up @@ -932,6 +940,71 @@ def test_empty_detail_status_correct(self):
('stopTestRun',)], log._events)


class TestResourcedToStreamDecorator(TestCase):

def setUp(self):
super(TestResourcedToStreamDecorator, self).setUp()
if testresources is None:
self.skipTest('Need testresources')

def test_startMakeResource(self):
log = LoggingStreamResult()
result = ResourcedToStreamDecorator(log)
timestamp = datetime.datetime.utcfromtimestamp(3.476)
result.startTestRun()
result.time(timestamp)
resource = testresources.TestResourceManager()
result.startMakeResource(resource)
[_, event] = log._events
self.assertEqual(
'testresources.TestResourceManager.make', event.test_id)
self.assertEqual('inprogress', event.test_status)
self.assertFalse(event.runnable)
self.assertEqual(timestamp, event.timestamp)

def test_startMakeResource_with_custom_id_method(self):
log = LoggingStreamResult()
result = ResourcedToStreamDecorator(log)
resource = testresources.TestResourceManager()
resource.id = lambda: 'nice.resource'
result.startTestRun()
result.startMakeResource(resource)
self.assertEqual('nice.resource.make', log._events[1].test_id)

def test_stopMakeResource(self):
log = LoggingStreamResult()
result = ResourcedToStreamDecorator(log)
resource = testresources.TestResourceManager()
result.startTestRun()
result.stopMakeResource(resource)
[_, event] = log._events
self.assertEqual(
'testresources.TestResourceManager.make', event.test_id)
self.assertEqual('success', event.test_status)

def test_startCleanResource(self):
log = LoggingStreamResult()
result = ResourcedToStreamDecorator(log)
resource = testresources.TestResourceManager()
result.startTestRun()
result.startCleanResource(resource)
[_, event] = log._events
self.assertEqual(
'testresources.TestResourceManager.clean', event.test_id)
self.assertEqual('inprogress', event.test_status)

def test_stopCleanResource(self):
log = LoggingStreamResult()
result = ResourcedToStreamDecorator(log)
resource = testresources.TestResourceManager()
result.startTestRun()
result.stopCleanResource(resource)
[_, event] = log._events
self.assertEqual(
'testresources.TestResourceManager.clean', event.test_id)
self.assertEqual('success', event.test_status)


class TestStreamFailFast(TestCase):

def test_inprogress(self):
Expand Down

0 comments on commit 01d4a9b

Please sign in to comment.