summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFree Ekanayaka <free@ekanayaka.io>2016-11-26 19:36:49 +0000
committerFree Ekanayaka <free@ekanayaka.io>2016-11-26 20:10:25 +0000
commit2632169bf97511e366a82ec190ad2e7427d2f879 (patch)
tree36a5841e68738ccaee5df0388e1f9f706dcc18f3
parentbfc88a45a3d94b4b030f9210471b2f700d294f15 (diff)
downloadtesttools-resourced-to-stream-decorator.tar.gz
Add ResourcedToStreamDecorator test result decorator for testresources integrationresourced-to-stream-decorator
-rw-r--r--NEWS6
-rw-r--r--setup.cfg1
-rw-r--r--testtools/__init__.py2
-rw-r--r--testtools/testresult/__init__.py2
-rw-r--r--testtools/testresult/doubles.py21
-rw-r--r--testtools/testresult/real.py53
-rw-r--r--testtools/tests/test_testresult.py77
7 files changed, 154 insertions, 8 deletions
diff --git a/NEWS b/NEWS
index e459a98..07c6fef 100644
--- a/NEWS
+++ b/NEWS
@@ -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
~~~~~
diff --git a/setup.cfg b/setup.cfg
index c1d6b51..2ad0da5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -12,6 +12,7 @@ classifier =
[extras]
test =
testscenarios
+ testresources
unittest2>=1.1.0
[files]
diff --git a/testtools/__init__.py b/testtools/__init__.py
index d6c15d2..5d6193a 100644
--- a/testtools/__init__.py
+++ b/testtools/__init__.py
@@ -18,6 +18,7 @@ __all__ = [
'MultiTestResult',
'PlaceHolder',
'run_test_with',
+ 'ResourcedToStreamDecorator',
'Tagger',
'TestCase',
'TestCommand',
@@ -85,6 +86,7 @@ else:
ExtendedToOriginalDecorator,
ExtendedToStreamDecorator,
MultiTestResult,
+ ResourcedToStreamDecorator,
StreamFailFast,
StreamResult,
StreamResultRouter,
diff --git a/testtools/testresult/__init__.py b/testtools/testresult/__init__.py
index 5bf8f9c..83d8cc7 100644
--- a/testtools/testresult/__init__.py
+++ b/testtools/testresult/__init__.py
@@ -7,6 +7,7 @@ __all__ = [
'ExtendedToOriginalDecorator',
'ExtendedToStreamDecorator',
'MultiTestResult',
+ 'ResourcedToStreamDecorator',
'StreamFailFast',
'StreamResult',
'StreamResultRouter',
@@ -30,6 +31,7 @@ from testtools.testresult.real import (
ExtendedToOriginalDecorator,
ExtendedToStreamDecorator,
MultiTestResult,
+ ResourcedToStreamDecorator,
StreamFailFast,
StreamResult,
StreamResultRouter,
diff --git a/testtools/testresult/doubles.py b/testtools/testresult/doubles.py
index 3f1bf53..e37a5d4 100644
--- a/testtools/testresult/doubles.py
+++ b/testtools/testresult/doubles.py
@@ -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',
@@ -11,9 +15,6 @@ __all__ = [
]
-from testtools.tags import TagContext
-
-
class LoggingBase(object):
"""Basic support for logging of results."""
@@ -219,6 +220,14 @@ class StreamResult(LoggingBase):
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"])
diff --git a/testtools/testresult/real.py b/testtools/testresult/real.py
index 9330bd8..b74a163 100644
--- a/testtools/testresult/real.py
+++ b/testtools/testresult/real.py
@@ -6,6 +6,7 @@ __all__ = [
'ExtendedToOriginalDecorator',
'ExtendedToStreamDecorator',
'MultiTestResult',
+ 'ResourcedToStreamDecorator',
'StreamFailFast',
'StreamResult',
'StreamSummary',
@@ -1688,6 +1689,58 @@ class ExtendedToStreamDecorator(CopyStreamResult, StreamSummary, TestControl):
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.
diff --git a/testtools/tests/test_testresult.py b/testtools/tests/test_testresult.py
index 2126c49..bea18c4 100644
--- a/testtools/tests/test_testresult.py
+++ b/testtools/tests/test_testresult.py
@@ -15,17 +15,19 @@ import sys
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,
@@ -590,6 +592,12 @@ class TestExtendedToStreamDecoratorContract(TestCase, TestStreamResultContract):
return ExtendedToStreamDecorator(StreamResult())
+class TestResourcedToStreamDecoratorContract(TestCase, TestStreamResultContract):
+
+ def _make_result(self):
+ return ResourcedToStreamDecorator(StreamResult())
+
+
class TestStreamSummaryResultContract(TestCase, TestStreamResultContract):
def _make_result(self):
@@ -920,6 +928,71 @@ class TestExtendedToStreamDecorator(TestCase):
('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):