Merge lp:~lifeless/launchpad/oops into lp:launchpad/db-devel

Proposed by Robert Collins
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 9753
Proposed branch: lp:~lifeless/launchpad/oops
Merge into: lp:launchpad/db-devel
Diff against target: 407 lines (+127/-31)
11 files modified
lib/canonical/launchpad/doc/librarian.txt (+19/-0)
lib/canonical/launchpad/webapp/errorlog.py (+5/-1)
lib/canonical/librarian/client.py (+26/-7)
lib/canonical/librarian/ftests/test_client.py (+3/-2)
lib/lp/services/mail/sendmail.py (+20/-6)
lib/lp/services/mail/tests/test_sendmail.py (+0/-4)
lib/lp/services/memcache/client.py (+26/-1)
lib/lp/services/memcache/tests/test_memcache_client.py (+19/-6)
lib/lp/services/timeline/tests/test_timedaction.py (+4/-2)
lib/lp/services/timeline/timedaction.py (+1/-1)
lib/lp/testing/tests/test_testcase.py (+4/-1)
To merge this branch: bzr merge lp:~lifeless/launchpad/oops
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Tim Penhey (community) release-critical Approve
Graham Binns release-critical Pending
Review via email: mp+34635@code.launchpad.net

Commit message

Change TimedAction.logTuple to really match the oops expectations.

Description of the change

Change TimedAction.logTuple to really match the oops expectations.

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

=== modified file 'lib/lp/services/timeline/tests/test_timedaction.py'
--- lib/lp/services/timeline/tests/test_timedaction.py 2010-09-03 01:09:38 +0000
+++ lib/lp/services/timeline/tests/test_timedaction.py 2010-09-05 05:07:57 +0000
@@ -41,8 +41,10 @@
         action.duration = datetime.timedelta(0, 0, 0, 4)
         log_tuple = action.logTuple()
         self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))
+ # The first element is the start offset in ms.
         self.assertAlmostEqual(2, log_tuple[0])
- self.assertAlmostEqual(4, log_tuple[1])
+ # The second element is the end offset in ms.
+ self.assertAlmostEqual(6, log_tuple[1])
         self.assertEqual("foo", log_tuple[2])
         self.assertEqual("bar", log_tuple[3])

@@ -56,6 +58,6 @@
         log_tuple = action.logTuple()
         self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))
         self.assertAlmostEqual(2, log_tuple[0])
- self.assertAlmostEqual(999999, log_tuple[1])
+ self.assertAlmostEqual(1000001, log_tuple[1])
         self.assertEqual("foo", log_tuple[2])
         self.assertEqual("bar", log_tuple[3])

=== modified file 'lib/lp/services/timeline/timedaction.py'
--- lib/lp/services/timeline/timedaction.py 2010-09-03 01:09:38 +0000
+++ lib/lp/services/timeline/timedaction.py 2010-09-05 05:07:57 +0000
@@ -57,7 +57,7 @@
             length = 999999
         else:
             length = self._td_to_ms(self.duration)
- return (offset, length, self.category, self.detail)
+ return (offset, offset + length, self.category, self.detail)

     def _td_to_ms(self, td):
         """Tweak on a backport from python 2.7"""

Revision history for this message
Tim Penhey (thumper) :
review: Approve (release-critical)
Revision history for this message
Jeroen T. Vermeulen (jtv) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/doc/librarian.txt'
--- lib/canonical/launchpad/doc/librarian.txt 2009-11-24 15:36:44 +0000
+++ lib/canonical/launchpad/doc/librarian.txt 2010-09-05 05:22:30 +0000
@@ -183,6 +183,25 @@
183 >>> re.search(r'^http://localhost:58000/\d+/text.txt$', url) is not None183 >>> re.search(r'^http://localhost:58000/\d+/text.txt$', url) is not None
184 True184 True
185185
186Librarian reads are logged in the request timeline.
187
188 >>> from canonical.lazr.utils import get_current_browser_request
189 >>> from lp.services.timeline.requesttimeline import get_request_timeline
190 >>> request = get_current_browser_request()
191 >>> timeline = get_request_timeline(request)
192 >>> f = client.getFileByAlias(aid)
193 >>> action = timeline.actions[-1]
194 >>> action.category
195 'librarian-connection'
196 >>> action.detail.endswith('/text.txt')
197 True
198 >>> _unused = f.read()
199 >>> action = timeline.actions[-1]
200 >>> action.category
201 'librarian-read'
202 >>> action.detail.endswith('/text.txt')
203 True
204
186At this level we can also reverse the transactional semantics by using the205At this level we can also reverse the transactional semantics by using the
187remoteAddFile instead of the addFile method. In this case, the database206remoteAddFile instead of the addFile method. In this case, the database
188rows are added by the Librarian, which means that the file is downloadable207rows are added by the Librarian, which means that the file is downloadable
189208
=== modified file 'lib/canonical/launchpad/webapp/errorlog.py'
--- lib/canonical/launchpad/webapp/errorlog.py 2010-09-03 11:09:57 +0000
+++ lib/canonical/launchpad/webapp/errorlog.py 2010-09-05 05:22:30 +0000
@@ -85,6 +85,9 @@
85 'representation of an object')85 'representation of an object')
86 value = '<unprintable %s object>' % (86 value = '<unprintable %s object>' % (
87 str(type(obj).__name__))87 str(type(obj).__name__))
88 # Some str() calls return unicode objects.
89 if isinstance(value, unicode):
90 return _safestr(value)
88 # encode non-ASCII characters91 # encode non-ASCII characters
89 value = value.replace('\\', '\\\\')92 value = value.replace('\\', '\\\\')
90 value = re.sub(r'[\x80-\xff]',93 value = re.sub(r'[\x80-\xff]',
@@ -163,6 +166,7 @@
163 return '<ErrorReport %s %s: %s>' % (self.id, self.type, self.value)166 return '<ErrorReport %s %s: %s>' % (self.id, self.type, self.value)
164167
165 def get_chunks(self):168 def get_chunks(self):
169 """Returns a list of bytestrings making up the oops disk content."""
166 chunks = []170 chunks = []
167 chunks.append('Oops-Id: %s\n' % _normalise_whitespace(self.id))171 chunks.append('Oops-Id: %s\n' % _normalise_whitespace(self.id))
168 chunks.append(172 chunks.append(
@@ -171,7 +175,7 @@
171 'Exception-Value: %s\n' % _normalise_whitespace(self.value))175 'Exception-Value: %s\n' % _normalise_whitespace(self.value))
172 chunks.append('Date: %s\n' % self.time.isoformat())176 chunks.append('Date: %s\n' % self.time.isoformat())
173 chunks.append('Page-Id: %s\n' % _normalise_whitespace(self.pageid))177 chunks.append('Page-Id: %s\n' % _normalise_whitespace(self.pageid))
174 chunks.append('Branch: %s\n' % self.branch_nick)178 chunks.append('Branch: %s\n' % _safestr(self.branch_nick))
175 chunks.append('Revision: %s\n' % self.revno)179 chunks.append('Revision: %s\n' % self.revno)
176 chunks.append('User: %s\n' % _normalise_whitespace(self.username))180 chunks.append('User: %s\n' % _normalise_whitespace(self.username))
177 chunks.append('URL: %s\n' % _normalise_whitespace(self.url))181 chunks.append('URL: %s\n' % _normalise_whitespace(self.url))
178182
=== modified file 'lib/canonical/librarian/client.py'
--- lib/canonical/librarian/client.py 2010-08-17 21:05:47 +0000
+++ lib/canonical/librarian/client.py 2010-09-05 05:22:30 +0000
@@ -33,6 +33,8 @@
33from canonical.librarian.interfaces import (33from canonical.librarian.interfaces import (
34 DownloadFailed, ILibrarianClient, IRestrictedLibrarianClient,34 DownloadFailed, ILibrarianClient, IRestrictedLibrarianClient,
35 LIBRARIAN_SERVER_DEFAULT_TIMEOUT, LibrarianServerError, UploadFailed)35 LIBRARIAN_SERVER_DEFAULT_TIMEOUT, LibrarianServerError, UploadFailed)
36from canonical.lazr.utils import get_current_browser_request
37from lp.services.timeline.requesttimeline import get_request_timeline
3638
3739
38def url_path_quote(filename):40def url_path_quote(filename):
@@ -246,16 +248,23 @@
246248
247249
248class _File:250class _File:
249 """A wrapper around a file like object that has security assertions"""251 """A File wrapper which uses the timeline and has security assertions."""
250252
251 def __init__(self, file):253 def __init__(self, file, url):
252 self.file = file254 self.file = file
255 self.url = url
253256
254 def read(self, chunksize=None):257 def read(self, chunksize=None):
255 if chunksize is None:258 request = get_current_browser_request()
256 return self.file.read()259 timeline = get_request_timeline(request)
257 else:260 action = timeline.start("librarian-read", self.url)
258 return self.file.read(chunksize)261 try:
262 if chunksize is None:
263 return self.file.read()
264 else:
265 return self.file.read(chunksize)
266 finally:
267 action.finish()
259268
260 def close(self):269 def close(self):
261 return self.file.close()270 return self.file.close()
@@ -345,9 +354,19 @@
345 # File has been deleted354 # File has been deleted
346 return None355 return None
347 try_until = time.time() + timeout356 try_until = time.time() + timeout
357 request = get_current_browser_request()
358 timeline = get_request_timeline(request)
359 action = timeline.start("librarian-connection", url)
360 try:
361 return self._connect_read(url, try_until, aliasID)
362 finally:
363 action.finish()
364
365 def _connect_read(self, url, try_until, aliasID):
366 """Helper for getFileByAlias."""
348 while 1:367 while 1:
349 try:368 try:
350 return _File(urllib2.urlopen(url))369 return _File(urllib2.urlopen(url), url)
351 except urllib2.URLError, error:370 except urllib2.URLError, error:
352 # 404 errors indicate a data inconsistency: more than one371 # 404 errors indicate a data inconsistency: more than one
353 # attempt to open the file is pointless.372 # attempt to open the file is pointless.
354373
=== modified file 'lib/canonical/librarian/ftests/test_client.py'
--- lib/canonical/librarian/ftests/test_client.py 2009-11-24 15:36:44 +0000
+++ lib/canonical/librarian/ftests/test_client.py 2010-09-05 05:22:30 +0000
@@ -32,7 +32,7 @@
3232
3333
34def make_mock_file(error, max_raise):34def make_mock_file(error, max_raise):
35 """Return a surrogate for clinet._File.35 """Return a surrogate for client._File.
3636
37 The surrogate function raises error when called for the first37 The surrogate function raises error when called for the first
38 max_raise times.38 max_raise times.
@@ -44,7 +44,7 @@
44 'num_calls': 0,44 'num_calls': 0,
45 }45 }
4646
47 def mock_file(url):47 def mock_file(url_file, url):
48 if file_status['num_calls'] < file_status['max_raise']:48 if file_status['num_calls'] < file_status['max_raise']:
49 file_status['num_calls'] += 149 file_status['num_calls'] += 1
50 raise file_status['error']50 raise file_status['error']
@@ -52,6 +52,7 @@
5252
53 return mock_file53 return mock_file
5454
55
55class LibrarianClientTestCase(unittest.TestCase):56class LibrarianClientTestCase(unittest.TestCase):
56 layer = LaunchpadFunctionalLayer57 layer = LaunchpadFunctionalLayer
5758
5859
=== modified file 'lib/lp/services/mail/sendmail.py'
--- lib/lp/services/mail/sendmail.py 2010-08-20 20:31:18 +0000
+++ lib/lp/services/mail/sendmail.py 2010-09-05 05:22:30 +0000
@@ -1,8 +1,7 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""4"""The One True Way to send mail from the Launchpad application.
5The One True Way to send mail from the Launchpad application.
65
7Uses zope.sendmail.interfaces.IMailer, so you can subscribe to6Uses zope.sendmail.interfaces.IMailer, so you can subscribe to
8IMailSentEvent or IMailErrorEvent to record status.7IMailSentEvent or IMailErrorEvent to record status.
@@ -24,7 +23,6 @@
24 'sendmail',23 'sendmail',
25 'simple_sendmail',24 'simple_sendmail',
26 'simple_sendmail_from_person',25 'simple_sendmail_from_person',
27 'raw_sendmail',
28 'validate_message',26 'validate_message',
29 ]27 ]
3028
@@ -45,6 +43,7 @@
45import hashlib43import hashlib
46from smtplib import SMTP44from smtplib import SMTP
4745
46from lazr.restful.utils import get_current_browser_request
48from zope.app import zapi47from zope.app import zapi
49from zope.security.proxy import isinstance as zisinstance48from zope.security.proxy import isinstance as zisinstance
50from zope.sendmail.interfaces import IMailDelivery49from zope.sendmail.interfaces import IMailDelivery
@@ -54,6 +53,7 @@
54from canonical.launchpad.helpers import is_ascii_only53from canonical.launchpad.helpers import is_ascii_only
55from canonical.lp import isZopeless54from canonical.lp import isZopeless
56from lp.services.mail.stub import TestMailer55from lp.services.mail.stub import TestMailer
56from lp.services.timeline.requesttimeline import get_request_timeline
5757
58# email package by default ends up encoding UTF-8 messages using base64,58# email package by default ends up encoding UTF-8 messages using base64,
59# which sucks as they look like spam to stupid spam filters. We define59# which sucks as they look like spam to stupid spam filters. We define
@@ -414,10 +414,12 @@
414 message['X-Launchpad-Hash'] = hash.hexdigest()414 message['X-Launchpad-Hash'] = hash.hexdigest()
415415
416 raw_message = message.as_string()416 raw_message = message.as_string()
417 message_detail = message['Subject']
417 if isZopeless():418 if isZopeless():
418 # Zopeless email sending is not unit tested, and won't be.419 # Zopeless email sending is not unit tested, and won't be.
419 # The zopeless specific stuff is pretty simple though so this420 # The zopeless specific stuff is pretty simple though so this
420 # should be fine.421 # should be fine.
422 # TODO: Store a timeline action for zopeless mail.
421423
422 if config.instance_name == 'testrunner':424 if config.instance_name == 'testrunner':
423 # when running in the testing environment, store emails425 # when running in the testing environment, store emails
@@ -443,14 +445,17 @@
443 # The "MAIL FROM" is set to the bounce address, to behave in a way445 # The "MAIL FROM" is set to the bounce address, to behave in a way
444 # similar to mailing list software.446 # similar to mailing list software.
445 return raw_sendmail(447 return raw_sendmail(
446 config.canonical.bounce_address, to_addrs, raw_message)448 config.canonical.bounce_address,
449 to_addrs,
450 raw_message,
451 message_detail)
447452
448453
449def get_msgid():454def get_msgid():
450 return make_msgid('launchpad')455 return make_msgid('launchpad')
451456
452457
453def raw_sendmail(from_addr, to_addrs, raw_message):458def raw_sendmail(from_addr, to_addrs, raw_message, message_detail):
454 """Send a raw RFC8222 email message.459 """Send a raw RFC8222 email message.
455460
456 All headers and encoding should already be done, as the message is461 All headers and encoding should already be done, as the message is
@@ -461,12 +466,21 @@
461466
462 Returns the message-id.467 Returns the message-id.
463468
469 :param message_detail: Information about the message to include in the
470 request timeline.
464 """471 """
472 # Note that raw_sendail has no tests, unit or otherwise.
465 assert not isinstance(to_addrs, basestring), 'to_addrs must be a sequence'473 assert not isinstance(to_addrs, basestring), 'to_addrs must be a sequence'
466 assert isinstance(raw_message, str), 'Not a plain string'474 assert isinstance(raw_message, str), 'Not a plain string'
467 assert raw_message.decode('ascii'), 'Not ASCII - badly encoded message'475 assert raw_message.decode('ascii'), 'Not ASCII - badly encoded message'
468 mailer = zapi.getUtility(IMailDelivery, 'Mail')476 mailer = zapi.getUtility(IMailDelivery, 'Mail')
469 return mailer.send(from_addr, to_addrs, raw_message)477 request = get_current_browser_request()
478 timeline = get_request_timeline(request)
479 action = timeline.start("sendmail", message_detail)
480 try:
481 return mailer.send(from_addr, to_addrs, raw_message)
482 finally:
483 action.finish()
470484
471485
472if __name__ == '__main__':486if __name__ == '__main__':
473487
=== modified file 'lib/lp/services/mail/tests/test_sendmail.py'
--- lib/lp/services/mail/tests/test_sendmail.py 2010-08-20 20:31:18 +0000
+++ lib/lp/services/mail/tests/test_sendmail.py 2010-09-05 05:22:30 +0000
@@ -243,7 +243,3 @@
243 suite.addTest(DocTestSuite('lp.services.mail.sendmail'))243 suite.addTest(DocTestSuite('lp.services.mail.sendmail'))
244 suite.addTests(unittest.TestLoader().loadTestsFromName(__name__))244 suite.addTests(unittest.TestLoader().loadTestsFromName(__name__))
245 return suite245 return suite
246
247
248if __name__ == '__main__':
249 unittest.main(defaultTest='test_suite')
250246
=== modified file 'lib/lp/services/memcache/client.py'
--- lib/lp/services/memcache/client.py 2010-08-20 20:31:18 +0000
+++ lib/lp/services/memcache/client.py 2010-09-05 05:22:30 +0000
@@ -8,9 +8,11 @@
88
9import re9import re
1010
11from lazr.restful.utils import get_current_browser_request
11import memcache12import memcache
1213
13from canonical.config import config14from canonical.config import config
15from lp.services.timeline.requesttimeline import get_request_timeline
1416
1517
16def memcache_client_factory():18def memcache_client_factory():
@@ -20,4 +22,27 @@
20 r'\((.+?),(\d+)\)', config.memcache.servers)]22 r'\((.+?),(\d+)\)', config.memcache.servers)]
21 assert len(servers) > 0, "Invalid memcached server list %r" % (23 assert len(servers) > 0, "Invalid memcached server list %r" % (
22 config.memcache.addresses,)24 config.memcache.addresses,)
23 return memcache.Client(servers)25 return TimelineRecordingClient(servers)
26
27
28class TimelineRecordingClient(memcache.Client):
29
30 def __get_timeline_action(self, suffix, key):
31 request = get_current_browser_request()
32 timeline = get_request_timeline(request)
33 return timeline.start("memcache-%s" % suffix, key)
34
35 def get(self, key):
36 action = self.__get_timeline_action("get", key)
37 try:
38 return memcache.Client.get(self, key)
39 finally:
40 action.finish()
41
42 def set(self, key, value, time=0, min_compress_len=0):
43 action = self.__get_timeline_action("set", key)
44 try:
45 return memcache.Client.set(self, key, value, time=time,
46 min_compress_len=min_compress_len)
47 finally:
48 action.finish()
2449
=== modified file 'lib/lp/services/memcache/tests/test_memcache_client.py'
--- lib/lp/services/memcache/tests/test_memcache_client.py 2010-01-07 06:47:46 +0000
+++ lib/lp/services/memcache/tests/test_memcache_client.py 2010-09-05 05:22:30 +0000
@@ -5,16 +5,17 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import unittest8from lazr.restful.utils import get_current_browser_request
9
10from zope.component import getUtility9from zope.component import getUtility
1110
12from canonical.testing.layers import LaunchpadZopelessLayer11from canonical.testing.layers import LaunchpadZopelessLayer
13from lp.services.memcache.interfaces import IMemcacheClient12from lp.services.memcache.interfaces import IMemcacheClient
13from lp.services.timeline.requesttimeline import get_request_timeline
14from lp.testing import TestCase14from lp.testing import TestCase
1515
1616
17class MemcacheClientTestCase(TestCase):17class MemcacheClientTestCase(TestCase):
18
18 layer = LaunchpadZopelessLayer19 layer = LaunchpadZopelessLayer
1920
20 def setUp(self):21 def setUp(self):
@@ -36,7 +37,19 @@
36 self.client.MemcachedKeyCharacterError,37 self.client.MemcachedKeyCharacterError,
37 self.client.set, 'key with spaces', 'some value')38 self.client.set, 'key with spaces', 'some value')
3839
3940 def test_set_recorded_to_timeline(self):
40def test_suite():41 request = get_current_browser_request()
41 return unittest.TestLoader().loadTestsFromName(__name__)42 timeline = get_request_timeline(request)
4243 self.client.set('foo', 'bar')
44 action = timeline.actions[-1]
45 self.assertEqual('memcache-set', action.category)
46 self.assertEqual('foo', action.detail)
47
48 def test_get_recorded_to_timeline(self):
49 request = get_current_browser_request()
50 timeline = get_request_timeline(request)
51 self.client.set('foo', 'bar')
52 self.client.get('foo')
53 action = timeline.actions[-1]
54 self.assertEqual('memcache-get', action.category)
55 self.assertEqual('foo', action.detail)
4356
=== modified file 'lib/lp/services/timeline/tests/test_timedaction.py'
--- lib/lp/services/timeline/tests/test_timedaction.py 2010-09-03 01:09:38 +0000
+++ lib/lp/services/timeline/tests/test_timedaction.py 2010-09-05 05:22:30 +0000
@@ -41,8 +41,10 @@
41 action.duration = datetime.timedelta(0, 0, 0, 4)41 action.duration = datetime.timedelta(0, 0, 0, 4)
42 log_tuple = action.logTuple()42 log_tuple = action.logTuple()
43 self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))43 self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))
44 # The first element is the start offset in ms.
44 self.assertAlmostEqual(2, log_tuple[0])45 self.assertAlmostEqual(2, log_tuple[0])
45 self.assertAlmostEqual(4, log_tuple[1])46 # The second element is the end offset in ms.
47 self.assertAlmostEqual(6, log_tuple[1])
46 self.assertEqual("foo", log_tuple[2])48 self.assertEqual("foo", log_tuple[2])
47 self.assertEqual("bar", log_tuple[3])49 self.assertEqual("bar", log_tuple[3])
4850
@@ -56,6 +58,6 @@
56 log_tuple = action.logTuple()58 log_tuple = action.logTuple()
57 self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))59 self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))
58 self.assertAlmostEqual(2, log_tuple[0])60 self.assertAlmostEqual(2, log_tuple[0])
59 self.assertAlmostEqual(999999, log_tuple[1])61 self.assertAlmostEqual(1000001, log_tuple[1])
60 self.assertEqual("foo", log_tuple[2])62 self.assertEqual("foo", log_tuple[2])
61 self.assertEqual("bar", log_tuple[3])63 self.assertEqual("bar", log_tuple[3])
6264
=== modified file 'lib/lp/services/timeline/timedaction.py'
--- lib/lp/services/timeline/timedaction.py 2010-09-03 01:09:38 +0000
+++ lib/lp/services/timeline/timedaction.py 2010-09-05 05:22:30 +0000
@@ -57,7 +57,7 @@
57 length = 99999957 length = 999999
58 else:58 else:
59 length = self._td_to_ms(self.duration)59 length = self._td_to_ms(self.duration)
60 return (offset, length, self.category, self.detail)60 return (offset, offset + length, self.category, self.detail)
6161
62 def _td_to_ms(self, td):62 def _td_to_ms(self, td):
63 """Tweak on a backport from python 2.7"""63 """Tweak on a backport from python 2.7"""
6464
=== modified file 'lib/lp/testing/tests/test_testcase.py'
--- lib/lp/testing/tests/test_testcase.py 2010-08-20 20:31:18 +0000
+++ lib/lp/testing/tests/test_testcase.py 2010-09-05 05:22:30 +0000
@@ -91,8 +91,11 @@
91 # getLastOopsReport does, and doing so changes whether the91 # getLastOopsReport does, and doing so changes whether the
92 # timezone is in the timestamp.92 # timezone is in the timestamp.
93 content = StringIO()93 content = StringIO()
94 content.writelines(self.getDetails()['oops-0'].iter_text())94 content.writelines(self.getDetails()['oops-0'].iter_bytes())
95 content.seek(0)95 content.seek(0)
96 # Safety net: ensure that no autocasts have occured even on Python 2.6
97 # which is slightly better.
98 self.assertIsInstance(content.getvalue(), str)
96 from_details = errorlog.ErrorReport.read(content)99 from_details = errorlog.ErrorReport.read(content)
97 self.assertEqual(100 self.assertEqual(
98 oops.get_chunks(),101 oops.get_chunks(),

Subscribers

People subscribed via source and target branches

to status/vote changes: