Merge lp:~lifeless/subunit/time-support into lp:~subunit/subunit/trunk
- time-support
- Merge into 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 |
Related bugs: |
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.
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
- 75. By Robert Collins
-
Add subunit.
test_results. HookedTestResul tDecorator. - 76. By Robert Collins
-
Add TestProtocolCli
ent.time( ). - 77. By Robert Collins
-
Add AutoTimingTestR
esultDecorator.
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): |
This teaches the python support to export time data to the TestResult.