Merge lp:~lifeless/subunit/time-support into lp:~subunit/subunit/trunk

Proposed by Robert Collins
Status: Superseded
Proposed branch: lp:~lifeless/subunit/time-support
Merge into: lp:~subunit/subunit/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~lifeless/subunit/time-support
Reviewer Review Type Date Requested Status
Subunit Developers Pending
Review via email: mp+9042@code.launchpad.net

This proposal has been superseded by a proposal from 2009-07-22.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

This teaches the python support to export time data to the TestResult.

lp:~lifeless/subunit/time-support updated
75. By Robert Collins

Add subunit.test_results.HookedTestResultDecorator.

76. By Robert Collins

Add TestProtocolClient.time().

77. By Robert Collins

Add AutoTimingTestResultDecorator.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile.am'
2--- Makefile.am 2009-06-12 01:45:53 +0000
3+++ Makefile.am 2009-07-20 10:08:11 +0000
4@@ -51,6 +51,7 @@
5
6 pkgpython_PYTHON = \
7 python/subunit/__init__.py \
8+ python/subunit/iso8601.py \
9 python/subunit/run.py
10
11 lib_LTLIBRARIES = libsubunit.la
12
13=== modified file 'NEWS'
14--- NEWS 2009-07-20 07:11:54 +0000
15+++ NEWS 2009-07-20 08:13:55 +0000
16@@ -14,6 +14,9 @@
17
18 API CHANGES:
19
20+ * When a time: directive is encountered in a subunit stream, the python
21+ bindings now call the ``time(seconds)`` method on ``TestResult``.
22+
23 INTERNALS:
24
25 * ExecTestCase supports passing arguments to test scripts.
26
27=== modified file 'README'
28--- README 2009-07-18 01:28:43 +0000
29+++ README 2009-07-20 10:08:11 +0000
30@@ -17,6 +17,10 @@
31 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32
33
34+ subunit reuses iso8601 by Michael Twomey, distributed under an MIT style
35+ licence - see python/iso8601/LICENSE for details.
36+
37+
38 Subunit
39 -------
40
41@@ -44,7 +48,7 @@
42 * tap2subunit - convert perl's TestAnythingProtocol to subunit.
43 * subunit2pyunit - convert a subunit stream to pyunit test results.
44 * subunit-filter - filter out tests from a subunit stream.
45- * subunit-ls - list the tests present in a subunit stream.
46+ * subunit-ls - list info about tests present in a subunit stream.
47 * subunit-stats - generate a summary of a subunit stream.
48 * subunit-tags - add or remove tags from a stream.
49
50@@ -123,6 +127,12 @@
51 # needed and report to your result object.
52 suite.run(result)
53
54+subunit includes extensions to the python ``TestResult`` protocol. The
55+``time(a_datetime)`` method is called (if present) when a ``time:``
56+directive is encountered in a subunit stream. This is used to tell a TestResult
57+about the time that events in the stream occured at, to allow reconstructing
58+test timing from a stream.
59+
60 Finally, subunit.run is a convenience wrapper to run a python test suite via
61 the command line, reporting via subunit::
62
63@@ -215,8 +225,8 @@
64 In Python, tags are assigned to the .tags attribute on the RemoteTest objects
65 created by the TestProtocolServer.
66
67-The time element acts as a clock event - it sets the time for all future events.
68-Currently this is not exposed at the python API layer.
69+The time element acts as a clock event - it sets the time for all future
70+events. The value should be a valid ISO8601 time.
71
72 The skip result is used to indicate a test that was found by the runner but not
73 fully executed due to some policy or dependency issue. This is represented in
74
75=== modified file 'filters/subunit-ls'
76--- filters/subunit-ls 2009-03-08 20:34:19 +0000
77+++ filters/subunit-ls 2009-07-20 10:12:42 +0000
78@@ -19,6 +19,7 @@
79
80 """List tests in a subunit stream."""
81
82+from optparse import OptionParser
83 import sys
84 import unittest
85
86@@ -26,32 +27,60 @@
87
88 class TestIdPrintingResult(unittest.TestResult):
89
90- def __init__(self, stream):
91+ def __init__(self, stream, show_times=False):
92 """Create a FilterResult object outputting to stream."""
93 unittest.TestResult.__init__(self)
94 self._stream = stream
95 self.failed_tests = 0
96+ self.__time = 0
97+ self.show_times = show_times
98+ self._test = None
99+ self._test_duration = 0
100
101 def addError(self, test, err):
102 self.failed_tests += 1
103- self.reportTest(test)
104+ self._test = test
105
106 def addFailure(self, test, err):
107 self.failed_tests += 1
108- self.reportTest(test)
109+ self._test = test
110
111 def addSuccess(self, test):
112- self.reportTest(test)
113-
114- def reportTest(self, test):
115- self._stream.write(test.id() + '\n')
116+ self._test = test
117+
118+ def reportTest(self, test, duration):
119+ if self.show_times:
120+ seconds = duration.seconds
121+ seconds += duration.days * 3600 * 24
122+ seconds += duration.microseconds / 1000000.0
123+ self._stream.write(test.id() + ' %0.3f\n' % seconds)
124+ else:
125+ self._stream.write(test.id() + '\n')
126+
127+ def startTest(self, test):
128+ self._start_time = self._time()
129+
130+ def stopTest(self, test):
131+ test_duration = self._time() - self._start_time
132+ self.reportTest(self._test, test_duration)
133+
134+ def time(self, time):
135+ self.__time = time
136+
137+ def _time(self):
138+ return self.__time
139
140 def wasSuccessful(self):
141 "Tells whether or not this result was a success"
142 return self.failed_tests == 0
143
144
145-result = TestIdPrintingResult(sys.stdout)
146+parser = OptionParser(description=__doc__)
147+parser.add_option("--times", action="store_true",
148+ help="list the time each test took (requires a timestamped stream)",
149+ default=False)
150+(options, args) = parser.parse_args()
151+result = TestIdPrintingResult(sys.stdout, options.times)
152 test = ProtocolTestCase(sys.stdin)
153 test.run(result)
154 if result.wasSuccessful():
155
156=== added directory 'python/iso8601'
157=== added file 'python/iso8601/LICENSE'
158--- python/iso8601/LICENSE 1970-01-01 00:00:00 +0000
159+++ python/iso8601/LICENSE 2009-07-20 10:08:11 +0000
160@@ -0,0 +1,20 @@
161+Copyright (c) 2007 Michael Twomey
162+
163+Permission is hereby granted, free of charge, to any person obtaining a
164+copy of this software and associated documentation files (the
165+"Software"), to deal in the Software without restriction, including
166+without limitation the rights to use, copy, modify, merge, publish,
167+distribute, sublicense, and/or sell copies of the Software, and to
168+permit persons to whom the Software is furnished to do so, subject to
169+the following conditions:
170+
171+The above copyright notice and this permission notice shall be included
172+in all copies or substantial portions of the Software.
173+
174+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
175+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
176+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
177+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
178+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
179+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
180+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
181
182=== added file 'python/iso8601/README'
183--- python/iso8601/README 1970-01-01 00:00:00 +0000
184+++ python/iso8601/README 2009-07-20 10:08:11 +0000
185@@ -0,0 +1,26 @@
186+A simple package to deal with ISO 8601 date time formats.
187+
188+ISO 8601 defines a neutral, unambiguous date string format, which also
189+has the property of sorting naturally.
190+
191+e.g. YYYY-MM-DDTHH:MM:SSZ or 2007-01-25T12:00:00Z
192+
193+Currently this covers only the most common date formats encountered, not
194+all of ISO 8601 is handled.
195+
196+Currently the following formats are handled:
197+
198+* 2006-01-01T00:00:00Z
199+* 2006-01-01T00:00:00[+-]00:00
200+
201+I'll add more as I encounter them in my day to day life. Patches with
202+new formats and tests will be gratefully accepted of course :)
203+
204+References:
205+
206+* http://www.cl.cam.ac.uk/~mgk25/iso-time.html - simple overview
207+
208+* http://hydracen.com/dx/iso8601.htm - more detailed enumeration of
209+ valid formats.
210+
211+See the LICENSE file for the license this package is released under.
212
213=== added file 'python/iso8601/README.subunit'
214--- python/iso8601/README.subunit 1970-01-01 00:00:00 +0000
215+++ python/iso8601/README.subunit 2009-07-20 10:08:11 +0000
216@@ -0,0 +1,5 @@
217+This is a [slightly rearranged] import of http://pypi.python.org/pypi/iso8601/
218+version 0.1.4. The OS X hidden files have been stripped, and the package
219+turned into a single module, to simplify installation. The remainder of the
220+source distribution is included in the subunit source tree at python/iso8601
221+for reference.
222
223=== added file 'python/iso8601/setup.py'
224--- python/iso8601/setup.py 1970-01-01 00:00:00 +0000
225+++ python/iso8601/setup.py 2009-07-20 10:08:11 +0000
226@@ -0,0 +1,58 @@
227+try:
228+ from setuptools import setup
229+except ImportError:
230+ from distutils import setup
231+
232+long_description="""Simple module to parse ISO 8601 dates
233+
234+This module parses the most common forms of ISO 8601 date strings (e.g.
235+2007-01-14T20:34:22+00:00) into datetime objects.
236+
237+>>> import iso8601
238+>>> iso8601.parse_date("2007-01-25T12:00:00Z")
239+datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>)
240+>>>
241+
242+Changes
243+=======
244+
245+0.1.4
246+-----
247+
248+* The default_timezone argument wasn't being passed through correctly,
249+ UTC was being used in every case. Fixes issue 10.
250+
251+0.1.3
252+-----
253+
254+* Fixed the microsecond handling, the generated microsecond values were
255+ way too small. Fixes issue 9.
256+
257+0.1.2
258+-----
259+
260+* Adding ParseError to __all__ in iso8601 module, allows people to import it.
261+ Addresses issue 7.
262+* Be a little more flexible when dealing with dates without leading zeroes.
263+ This violates the spec a little, but handles more dates as seen in the
264+ field. Addresses issue 6.
265+* Allow date/time separators other than T.
266+
267+0.1.1
268+-----
269+
270+* When parsing dates without a timezone the specified default is used. If no
271+ default is specified then UTC is used. Addresses issue 4.
272+"""
273+
274+setup(
275+ name="iso8601",
276+ version="0.1.4",
277+ description=long_description.split("\n")[0],
278+ long_description=long_description,
279+ author="Michael Twomey",
280+ author_email="micktwomey+iso8601@gmail.com",
281+ url="http://code.google.com/p/pyiso8601/",
282+ packages=["iso8601"],
283+ license="MIT",
284+)
285
286=== added file 'python/iso8601/test_iso8601.py'
287--- python/iso8601/test_iso8601.py 1970-01-01 00:00:00 +0000
288+++ python/iso8601/test_iso8601.py 2009-07-20 10:08:11 +0000
289@@ -0,0 +1,111 @@
290+import iso8601
291+
292+def test_iso8601_regex():
293+ assert iso8601.ISO8601_REGEX.match("2006-10-11T00:14:33Z")
294+
295+def test_timezone_regex():
296+ assert iso8601.TIMEZONE_REGEX.match("+01:00")
297+ assert iso8601.TIMEZONE_REGEX.match("+00:00")
298+ assert iso8601.TIMEZONE_REGEX.match("+01:20")
299+ assert iso8601.TIMEZONE_REGEX.match("-01:00")
300+
301+def test_parse_date():
302+ d = iso8601.parse_date("2006-10-20T15:34:56Z")
303+ assert d.year == 2006
304+ assert d.month == 10
305+ assert d.day == 20
306+ assert d.hour == 15
307+ assert d.minute == 34
308+ assert d.second == 56
309+ assert d.tzinfo == iso8601.UTC
310+
311+def test_parse_date_fraction():
312+ d = iso8601.parse_date("2006-10-20T15:34:56.123Z")
313+ assert d.year == 2006
314+ assert d.month == 10
315+ assert d.day == 20
316+ assert d.hour == 15
317+ assert d.minute == 34
318+ assert d.second == 56
319+ assert d.microsecond == 123000
320+ assert d.tzinfo == iso8601.UTC
321+
322+def test_parse_date_fraction_2():
323+ """From bug 6
324+
325+ """
326+ d = iso8601.parse_date("2007-5-7T11:43:55.328Z'")
327+ assert d.year == 2007
328+ assert d.month == 5
329+ assert d.day == 7
330+ assert d.hour == 11
331+ assert d.minute == 43
332+ assert d.second == 55
333+ assert d.microsecond == 328000
334+ assert d.tzinfo == iso8601.UTC
335+
336+def test_parse_date_tz():
337+ d = iso8601.parse_date("2006-10-20T15:34:56.123+02:30")
338+ assert d.year == 2006
339+ assert d.month == 10
340+ assert d.day == 20
341+ assert d.hour == 15
342+ assert d.minute == 34
343+ assert d.second == 56
344+ assert d.microsecond == 123000
345+ assert d.tzinfo.tzname(None) == "+02:30"
346+ offset = d.tzinfo.utcoffset(None)
347+ assert offset.days == 0
348+ assert offset.seconds == 60 * 60 * 2.5
349+
350+def test_parse_invalid_date():
351+ try:
352+ iso8601.parse_date(None)
353+ except iso8601.ParseError:
354+ pass
355+ else:
356+ assert 1 == 2
357+
358+def test_parse_invalid_date2():
359+ try:
360+ iso8601.parse_date("23")
361+ except iso8601.ParseError:
362+ pass
363+ else:
364+ assert 1 == 2
365+
366+def test_parse_no_timezone():
367+ """issue 4 - Handle datetime string without timezone
368+
369+ This tests what happens when you parse a date with no timezone. While not
370+ strictly correct this is quite common. I'll assume UTC for the time zone
371+ in this case.
372+ """
373+ d = iso8601.parse_date("2007-01-01T08:00:00")
374+ assert d.year == 2007
375+ assert d.month == 1
376+ assert d.day == 1
377+ assert d.hour == 8
378+ assert d.minute == 0
379+ assert d.second == 0
380+ assert d.microsecond == 0
381+ assert d.tzinfo == iso8601.UTC
382+
383+def test_parse_no_timezone_different_default():
384+ tz = iso8601.FixedOffset(2, 0, "test offset")
385+ d = iso8601.parse_date("2007-01-01T08:00:00", default_timezone=tz)
386+ assert d.tzinfo == tz
387+
388+def test_space_separator():
389+ """Handle a separator other than T
390+
391+ """
392+ d = iso8601.parse_date("2007-06-23 06:40:34.00Z")
393+ assert d.year == 2007
394+ assert d.month == 6
395+ assert d.day == 23
396+ assert d.hour == 6
397+ assert d.minute == 40
398+ assert d.second == 34
399+ assert d.microsecond == 0
400+ assert d.tzinfo == iso8601.UTC
401
402=== modified file 'python/subunit/__init__.py'
403--- python/subunit/__init__.py 2009-07-18 03:43:05 +0000
404+++ python/subunit/__init__.py 2009-07-20 10:08:11 +0000
405@@ -17,13 +17,17 @@
406 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
407 #
408
409+import datetime
410 import os
411+import re
412 from StringIO import StringIO
413 import subprocess
414 import sys
415-import re
416 import unittest
417
418+import iso8601
419+
420+
421 def test_suite():
422 import subunit.tests
423 return subunit.tests.test_suite()
424@@ -207,6 +211,13 @@
425 update_tags.update(new_tags)
426 update_tags.difference_update(gone_tags)
427
428+ def _handleTime(self, offset, line):
429+ # Accept it, but do not do anything with it yet.
430+ event_time = iso8601.parse_date(line[offset:-1])
431+ time_method = getattr(self.client, 'time', None)
432+ if callable(time_method):
433+ time_method(event_time)
434+
435 def lineReceived(self, line):
436 """Call the appropriate local method for the received line."""
437 if line == "]\n":
438@@ -236,8 +247,7 @@
439 elif cmd in ('tags',):
440 self._handleTags(offset, line)
441 elif cmd in ('time',):
442- # Accept it, but do not do anything with it yet.
443- pass
444+ self._handleTime(offset, line)
445 elif cmd == 'xfail':
446 self._addExpectedFail(offset, line)
447 else:
448
449=== added file 'python/subunit/iso8601.py'
450--- python/subunit/iso8601.py 1970-01-01 00:00:00 +0000
451+++ python/subunit/iso8601.py 2009-07-20 10:08:11 +0000
452@@ -0,0 +1,123 @@
453+# Copyright (c) 2007 Michael Twomey
454+#
455+# Permission is hereby granted, free of charge, to any person obtaining a
456+# copy of this software and associated documentation files (the
457+# "Software"), to deal in the Software without restriction, including
458+# without limitation the rights to use, copy, modify, merge, publish,
459+# distribute, sublicense, and/or sell copies of the Software, and to
460+# permit persons to whom the Software is furnished to do so, subject to
461+# the following conditions:
462+#
463+# The above copyright notice and this permission notice shall be included
464+# in all copies or substantial portions of the Software.
465+#
466+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
467+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
468+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
469+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
470+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
471+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
472+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
473+
474+"""ISO 8601 date time string parsing
475+
476+Basic usage:
477+>>> import iso8601
478+>>> iso8601.parse_date("2007-01-25T12:00:00Z")
479+datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>)
480+>>>
481+
482+"""
483+
484+from datetime import datetime, timedelta, tzinfo
485+import re
486+
487+__all__ = ["parse_date", "ParseError"]
488+
489+# Adapted from http://delete.me.uk/2005/03/iso8601.html
490+ISO8601_REGEX = re.compile(r"(?P<year>[0-9]{4})(-(?P<month>[0-9]{1,2})(-(?P<day>[0-9]{1,2})"
491+ r"((?P<separator>.)(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?"
492+ r"(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
493+)
494+TIMEZONE_REGEX = re.compile("(?P<prefix>[+-])(?P<hours>[0-9]{2}).(?P<minutes>[0-9]{2})")
495+
496+class ParseError(Exception):
497+ """Raised when there is a problem parsing a date string"""
498+
499+# Yoinked from python docs
500+ZERO = timedelta(0)
501+class Utc(tzinfo):
502+ """UTC
503+
504+ """
505+ def utcoffset(self, dt):
506+ return ZERO
507+
508+ def tzname(self, dt):
509+ return "UTC"
510+
511+ def dst(self, dt):
512+ return ZERO
513+UTC = Utc()
514+
515+class FixedOffset(tzinfo):
516+ """Fixed offset in hours and minutes from UTC
517+
518+ """
519+ def __init__(self, offset_hours, offset_minutes, name):
520+ self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
521+ self.__name = name
522+
523+ def utcoffset(self, dt):
524+ return self.__offset
525+
526+ def tzname(self, dt):
527+ return self.__name
528+
529+ def dst(self, dt):
530+ return ZERO
531+
532+ def __repr__(self):
533+ return "<FixedOffset %r>" % self.__name
534+
535+def parse_timezone(tzstring, default_timezone=UTC):
536+ """Parses ISO 8601 time zone specs into tzinfo offsets
537+
538+ """
539+ if tzstring == "Z":
540+ return default_timezone
541+ # This isn't strictly correct, but it's common to encounter dates without
542+ # timezones so I'll assume the default (which defaults to UTC).
543+ # Addresses issue 4.
544+ if tzstring is None:
545+ return default_timezone
546+ m = TIMEZONE_REGEX.match(tzstring)
547+ prefix, hours, minutes = m.groups()
548+ hours, minutes = int(hours), int(minutes)
549+ if prefix == "-":
550+ hours = -hours
551+ minutes = -minutes
552+ return FixedOffset(hours, minutes, tzstring)
553+
554+def parse_date(datestring, default_timezone=UTC):
555+ """Parses ISO 8601 dates into datetime objects
556+
557+ The timezone is parsed from the date string. However it is quite common to
558+ have dates without a timezone (not strictly correct). In this case the
559+ default timezone specified in default_timezone is used. This is UTC by
560+ default.
561+ """
562+ if not isinstance(datestring, basestring):
563+ raise ParseError("Expecting a string %r" % datestring)
564+ m = ISO8601_REGEX.match(datestring)
565+ if not m:
566+ raise ParseError("Unable to parse date string %r" % datestring)
567+ groups = m.groupdict()
568+ tz = parse_timezone(groups["timezone"], default_timezone=default_timezone)
569+ if groups["fraction"] is None:
570+ groups["fraction"] = 0
571+ else:
572+ groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6)
573+ return datetime(int(groups["year"]), int(groups["month"]), int(groups["day"]),
574+ int(groups["hour"]), int(groups["minute"]), int(groups["second"]),
575+ int(groups["fraction"]), tz)
576
577=== modified file 'python/subunit/tests/test_test_protocol.py'
578--- python/subunit/tests/test_test_protocol.py 2009-07-18 03:43:05 +0000
579+++ python/subunit/tests/test_test_protocol.py 2009-07-20 10:08:11 +0000
580@@ -17,46 +17,49 @@
581 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
582 #
583
584+import datetime
585 import unittest
586 from StringIO import StringIO
587 import os
588 import subunit
589 import sys
590-import time
591-
592-try:
593- class MockTestProtocolServerClient(object):
594- """A mock protocol server client to test callbacks."""
595-
596- def __init__(self):
597- self.end_calls = []
598- self.error_calls = []
599- self.failure_calls = []
600- self.skip_calls = []
601- self.start_calls = []
602- self.success_calls = []
603- super(MockTestProtocolServerClient, self).__init__()
604-
605- def addError(self, test, error):
606- self.error_calls.append((test, error))
607-
608- def addFailure(self, test, error):
609- self.failure_calls.append((test, error))
610-
611- def addSkip(self, test, reason):
612- self.skip_calls.append((test, reason))
613-
614- def addSuccess(self, test):
615- self.success_calls.append(test)
616-
617- def stopTest(self, test):
618- self.end_calls.append(test)
619-
620- def startTest(self, test):
621- self.start_calls.append(test)
622-
623-except AttributeError:
624- MockTestProtocolServer = None
625+
626+import subunit.iso8601 as iso8601
627+
628+
629+class MockTestProtocolServerClient(object):
630+ """A mock protocol server client to test callbacks."""
631+
632+ def __init__(self):
633+ self.end_calls = []
634+ self.error_calls = []
635+ self.failure_calls = []
636+ self.skip_calls = []
637+ self.start_calls = []
638+ self.success_calls = []
639+ self._time = None
640+ super(MockTestProtocolServerClient, self).__init__()
641+
642+ def addError(self, test, error):
643+ self.error_calls.append((test, error))
644+
645+ def addFailure(self, test, error):
646+ self.failure_calls.append((test, error))
647+
648+ def addSkip(self, test, reason):
649+ self.skip_calls.append((test, reason))
650+
651+ def addSuccess(self, test):
652+ self.success_calls.append(test)
653+
654+ def stopTest(self, test):
655+ self.end_calls.append(test)
656+
657+ def startTest(self, test):
658+ self.start_calls.append(test)
659+
660+ def time(self, time):
661+ self._time = time
662
663
664 class TestMockTestProtocolServer(unittest.TestCase):
665@@ -763,15 +766,23 @@
666 class TestTestProtocolServerStreamTime(unittest.TestCase):
667 """Test managing time information at the protocol level."""
668
669- def setUp(self):
670- self.client = MockTestProtocolServerClient()
671+ def test_time_accepted_stdlib(self):
672+ self.result = unittest.TestResult()
673 self.stream = StringIO()
674- self.protocol = subunit.TestProtocolServer(self.client,
675+ self.protocol = subunit.TestProtocolServer(self.result,
676 stream=self.stream)
677+ self.protocol.lineReceived("time: 2001-12-12 12:59:59Z\n")
678+ self.assertEqual("", self.stream.getvalue())
679
680- def test_time_accepted(self):
681+ def test_time_accepted_extended(self):
682+ self.result = MockTestProtocolServerClient()
683+ self.stream = StringIO()
684+ self.protocol = subunit.TestProtocolServer(self.result,
685+ stream=self.stream)
686 self.protocol.lineReceived("time: 2001-12-12 12:59:59Z\n")
687 self.assertEqual("", self.stream.getvalue())
688+ self.assertEqual(datetime.datetime(2001, 12, 12, 12, 59, 59, 0,
689+ iso8601.Utc()), self.result._time)
690
691
692 class TestRemotedTestCase(unittest.TestCase):

Subscribers

People subscribed via source and target branches