Merge lp:~lifeless/launchpad/oops into lp:launchpad/db-devel
- oops
- Merge into 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 | ||||||||||||
Related bugs: |
|
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.
Description of the change
Change TimedAction.
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
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
1 | === modified file 'lib/canonical/launchpad/doc/librarian.txt' | |||
2 | --- lib/canonical/launchpad/doc/librarian.txt 2009-11-24 15:36:44 +0000 | |||
3 | +++ lib/canonical/launchpad/doc/librarian.txt 2010-09-05 05:22:30 +0000 | |||
4 | @@ -183,6 +183,25 @@ | |||
5 | 183 | >>> re.search(r'^http://localhost:58000/\d+/text.txt$', url) is not None | 183 | >>> re.search(r'^http://localhost:58000/\d+/text.txt$', url) is not None |
6 | 184 | True | 184 | True |
7 | 185 | 185 | ||
8 | 186 | Librarian reads are logged in the request timeline. | ||
9 | 187 | |||
10 | 188 | >>> from canonical.lazr.utils import get_current_browser_request | ||
11 | 189 | >>> from lp.services.timeline.requesttimeline import get_request_timeline | ||
12 | 190 | >>> request = get_current_browser_request() | ||
13 | 191 | >>> timeline = get_request_timeline(request) | ||
14 | 192 | >>> f = client.getFileByAlias(aid) | ||
15 | 193 | >>> action = timeline.actions[-1] | ||
16 | 194 | >>> action.category | ||
17 | 195 | 'librarian-connection' | ||
18 | 196 | >>> action.detail.endswith('/text.txt') | ||
19 | 197 | True | ||
20 | 198 | >>> _unused = f.read() | ||
21 | 199 | >>> action = timeline.actions[-1] | ||
22 | 200 | >>> action.category | ||
23 | 201 | 'librarian-read' | ||
24 | 202 | >>> action.detail.endswith('/text.txt') | ||
25 | 203 | True | ||
26 | 204 | |||
27 | 186 | At this level we can also reverse the transactional semantics by using the | 205 | At this level we can also reverse the transactional semantics by using the |
28 | 187 | remoteAddFile instead of the addFile method. In this case, the database | 206 | remoteAddFile instead of the addFile method. In this case, the database |
29 | 188 | rows are added by the Librarian, which means that the file is downloadable | 207 | rows are added by the Librarian, which means that the file is downloadable |
30 | 189 | 208 | ||
31 | === modified file 'lib/canonical/launchpad/webapp/errorlog.py' | |||
32 | --- lib/canonical/launchpad/webapp/errorlog.py 2010-09-03 11:09:57 +0000 | |||
33 | +++ lib/canonical/launchpad/webapp/errorlog.py 2010-09-05 05:22:30 +0000 | |||
34 | @@ -85,6 +85,9 @@ | |||
35 | 85 | 'representation of an object') | 85 | 'representation of an object') |
36 | 86 | value = '<unprintable %s object>' % ( | 86 | value = '<unprintable %s object>' % ( |
37 | 87 | str(type(obj).__name__)) | 87 | str(type(obj).__name__)) |
38 | 88 | # Some str() calls return unicode objects. | ||
39 | 89 | if isinstance(value, unicode): | ||
40 | 90 | return _safestr(value) | ||
41 | 88 | # encode non-ASCII characters | 91 | # encode non-ASCII characters |
42 | 89 | value = value.replace('\\', '\\\\') | 92 | value = value.replace('\\', '\\\\') |
43 | 90 | value = re.sub(r'[\x80-\xff]', | 93 | value = re.sub(r'[\x80-\xff]', |
44 | @@ -163,6 +166,7 @@ | |||
45 | 163 | return '<ErrorReport %s %s: %s>' % (self.id, self.type, self.value) | 166 | return '<ErrorReport %s %s: %s>' % (self.id, self.type, self.value) |
46 | 164 | 167 | ||
47 | 165 | def get_chunks(self): | 168 | def get_chunks(self): |
48 | 169 | """Returns a list of bytestrings making up the oops disk content.""" | ||
49 | 166 | chunks = [] | 170 | chunks = [] |
50 | 167 | chunks.append('Oops-Id: %s\n' % _normalise_whitespace(self.id)) | 171 | chunks.append('Oops-Id: %s\n' % _normalise_whitespace(self.id)) |
51 | 168 | chunks.append( | 172 | chunks.append( |
52 | @@ -171,7 +175,7 @@ | |||
53 | 171 | 'Exception-Value: %s\n' % _normalise_whitespace(self.value)) | 175 | 'Exception-Value: %s\n' % _normalise_whitespace(self.value)) |
54 | 172 | chunks.append('Date: %s\n' % self.time.isoformat()) | 176 | chunks.append('Date: %s\n' % self.time.isoformat()) |
55 | 173 | chunks.append('Page-Id: %s\n' % _normalise_whitespace(self.pageid)) | 177 | chunks.append('Page-Id: %s\n' % _normalise_whitespace(self.pageid)) |
57 | 174 | chunks.append('Branch: %s\n' % self.branch_nick) | 178 | chunks.append('Branch: %s\n' % _safestr(self.branch_nick)) |
58 | 175 | chunks.append('Revision: %s\n' % self.revno) | 179 | chunks.append('Revision: %s\n' % self.revno) |
59 | 176 | chunks.append('User: %s\n' % _normalise_whitespace(self.username)) | 180 | chunks.append('User: %s\n' % _normalise_whitespace(self.username)) |
60 | 177 | chunks.append('URL: %s\n' % _normalise_whitespace(self.url)) | 181 | chunks.append('URL: %s\n' % _normalise_whitespace(self.url)) |
61 | 178 | 182 | ||
62 | === modified file 'lib/canonical/librarian/client.py' | |||
63 | --- lib/canonical/librarian/client.py 2010-08-17 21:05:47 +0000 | |||
64 | +++ lib/canonical/librarian/client.py 2010-09-05 05:22:30 +0000 | |||
65 | @@ -33,6 +33,8 @@ | |||
66 | 33 | from canonical.librarian.interfaces import ( | 33 | from canonical.librarian.interfaces import ( |
67 | 34 | DownloadFailed, ILibrarianClient, IRestrictedLibrarianClient, | 34 | DownloadFailed, ILibrarianClient, IRestrictedLibrarianClient, |
68 | 35 | LIBRARIAN_SERVER_DEFAULT_TIMEOUT, LibrarianServerError, UploadFailed) | 35 | LIBRARIAN_SERVER_DEFAULT_TIMEOUT, LibrarianServerError, UploadFailed) |
69 | 36 | from canonical.lazr.utils import get_current_browser_request | ||
70 | 37 | from lp.services.timeline.requesttimeline import get_request_timeline | ||
71 | 36 | 38 | ||
72 | 37 | 39 | ||
73 | 38 | def url_path_quote(filename): | 40 | def url_path_quote(filename): |
74 | @@ -246,16 +248,23 @@ | |||
75 | 246 | 248 | ||
76 | 247 | 249 | ||
77 | 248 | class _File: | 250 | class _File: |
79 | 249 | """A wrapper around a file like object that has security assertions""" | 251 | """A File wrapper which uses the timeline and has security assertions.""" |
80 | 250 | 252 | ||
82 | 251 | def __init__(self, file): | 253 | def __init__(self, file, url): |
83 | 252 | self.file = file | 254 | self.file = file |
84 | 255 | self.url = url | ||
85 | 253 | 256 | ||
86 | 254 | def read(self, chunksize=None): | 257 | def read(self, chunksize=None): |
91 | 255 | if chunksize is None: | 258 | request = get_current_browser_request() |
92 | 256 | return self.file.read() | 259 | timeline = get_request_timeline(request) |
93 | 257 | else: | 260 | action = timeline.start("librarian-read", self.url) |
94 | 258 | return self.file.read(chunksize) | 261 | try: |
95 | 262 | if chunksize is None: | ||
96 | 263 | return self.file.read() | ||
97 | 264 | else: | ||
98 | 265 | return self.file.read(chunksize) | ||
99 | 266 | finally: | ||
100 | 267 | action.finish() | ||
101 | 259 | 268 | ||
102 | 260 | def close(self): | 269 | def close(self): |
103 | 261 | return self.file.close() | 270 | return self.file.close() |
104 | @@ -345,9 +354,19 @@ | |||
105 | 345 | # File has been deleted | 354 | # File has been deleted |
106 | 346 | return None | 355 | return None |
107 | 347 | try_until = time.time() + timeout | 356 | try_until = time.time() + timeout |
108 | 357 | request = get_current_browser_request() | ||
109 | 358 | timeline = get_request_timeline(request) | ||
110 | 359 | action = timeline.start("librarian-connection", url) | ||
111 | 360 | try: | ||
112 | 361 | return self._connect_read(url, try_until, aliasID) | ||
113 | 362 | finally: | ||
114 | 363 | action.finish() | ||
115 | 364 | |||
116 | 365 | def _connect_read(self, url, try_until, aliasID): | ||
117 | 366 | """Helper for getFileByAlias.""" | ||
118 | 348 | while 1: | 367 | while 1: |
119 | 349 | try: | 368 | try: |
121 | 350 | return _File(urllib2.urlopen(url)) | 369 | return _File(urllib2.urlopen(url), url) |
122 | 351 | except urllib2.URLError, error: | 370 | except urllib2.URLError, error: |
123 | 352 | # 404 errors indicate a data inconsistency: more than one | 371 | # 404 errors indicate a data inconsistency: more than one |
124 | 353 | # attempt to open the file is pointless. | 372 | # attempt to open the file is pointless. |
125 | 354 | 373 | ||
126 | === modified file 'lib/canonical/librarian/ftests/test_client.py' | |||
127 | --- lib/canonical/librarian/ftests/test_client.py 2009-11-24 15:36:44 +0000 | |||
128 | +++ lib/canonical/librarian/ftests/test_client.py 2010-09-05 05:22:30 +0000 | |||
129 | @@ -32,7 +32,7 @@ | |||
130 | 32 | 32 | ||
131 | 33 | 33 | ||
132 | 34 | def make_mock_file(error, max_raise): | 34 | def make_mock_file(error, max_raise): |
134 | 35 | """Return a surrogate for clinet._File. | 35 | """Return a surrogate for client._File. |
135 | 36 | 36 | ||
136 | 37 | The surrogate function raises error when called for the first | 37 | The surrogate function raises error when called for the first |
137 | 38 | max_raise times. | 38 | max_raise times. |
138 | @@ -44,7 +44,7 @@ | |||
139 | 44 | 'num_calls': 0, | 44 | 'num_calls': 0, |
140 | 45 | } | 45 | } |
141 | 46 | 46 | ||
143 | 47 | def mock_file(url): | 47 | def mock_file(url_file, url): |
144 | 48 | if file_status['num_calls'] < file_status['max_raise']: | 48 | if file_status['num_calls'] < file_status['max_raise']: |
145 | 49 | file_status['num_calls'] += 1 | 49 | file_status['num_calls'] += 1 |
146 | 50 | raise file_status['error'] | 50 | raise file_status['error'] |
147 | @@ -52,6 +52,7 @@ | |||
148 | 52 | 52 | ||
149 | 53 | return mock_file | 53 | return mock_file |
150 | 54 | 54 | ||
151 | 55 | |||
152 | 55 | class LibrarianClientTestCase(unittest.TestCase): | 56 | class LibrarianClientTestCase(unittest.TestCase): |
153 | 56 | layer = LaunchpadFunctionalLayer | 57 | layer = LaunchpadFunctionalLayer |
154 | 57 | 58 | ||
155 | 58 | 59 | ||
156 | === modified file 'lib/lp/services/mail/sendmail.py' | |||
157 | --- lib/lp/services/mail/sendmail.py 2010-08-20 20:31:18 +0000 | |||
158 | +++ lib/lp/services/mail/sendmail.py 2010-09-05 05:22:30 +0000 | |||
159 | @@ -1,8 +1,7 @@ | |||
160 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
161 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
162 | 3 | 3 | ||
165 | 4 | """ | 4 | """The One True Way to send mail from the Launchpad application. |
164 | 5 | The One True Way to send mail from the Launchpad application. | ||
166 | 6 | 5 | ||
167 | 7 | Uses zope.sendmail.interfaces.IMailer, so you can subscribe to | 6 | Uses zope.sendmail.interfaces.IMailer, so you can subscribe to |
168 | 8 | IMailSentEvent or IMailErrorEvent to record status. | 7 | IMailSentEvent or IMailErrorEvent to record status. |
169 | @@ -24,7 +23,6 @@ | |||
170 | 24 | 'sendmail', | 23 | 'sendmail', |
171 | 25 | 'simple_sendmail', | 24 | 'simple_sendmail', |
172 | 26 | 'simple_sendmail_from_person', | 25 | 'simple_sendmail_from_person', |
173 | 27 | 'raw_sendmail', | ||
174 | 28 | 'validate_message', | 26 | 'validate_message', |
175 | 29 | ] | 27 | ] |
176 | 30 | 28 | ||
177 | @@ -45,6 +43,7 @@ | |||
178 | 45 | import hashlib | 43 | import hashlib |
179 | 46 | from smtplib import SMTP | 44 | from smtplib import SMTP |
180 | 47 | 45 | ||
181 | 46 | from lazr.restful.utils import get_current_browser_request | ||
182 | 48 | from zope.app import zapi | 47 | from zope.app import zapi |
183 | 49 | from zope.security.proxy import isinstance as zisinstance | 48 | from zope.security.proxy import isinstance as zisinstance |
184 | 50 | from zope.sendmail.interfaces import IMailDelivery | 49 | from zope.sendmail.interfaces import IMailDelivery |
185 | @@ -54,6 +53,7 @@ | |||
186 | 54 | from canonical.launchpad.helpers import is_ascii_only | 53 | from canonical.launchpad.helpers import is_ascii_only |
187 | 55 | from canonical.lp import isZopeless | 54 | from canonical.lp import isZopeless |
188 | 56 | from lp.services.mail.stub import TestMailer | 55 | from lp.services.mail.stub import TestMailer |
189 | 56 | from lp.services.timeline.requesttimeline import get_request_timeline | ||
190 | 57 | 57 | ||
191 | 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, |
192 | 59 | # which sucks as they look like spam to stupid spam filters. We define | 59 | # which sucks as they look like spam to stupid spam filters. We define |
193 | @@ -414,10 +414,12 @@ | |||
194 | 414 | message['X-Launchpad-Hash'] = hash.hexdigest() | 414 | message['X-Launchpad-Hash'] = hash.hexdigest() |
195 | 415 | 415 | ||
196 | 416 | raw_message = message.as_string() | 416 | raw_message = message.as_string() |
197 | 417 | message_detail = message['Subject'] | ||
198 | 417 | if isZopeless(): | 418 | if isZopeless(): |
199 | 418 | # Zopeless email sending is not unit tested, and won't be. | 419 | # Zopeless email sending is not unit tested, and won't be. |
200 | 419 | # The zopeless specific stuff is pretty simple though so this | 420 | # The zopeless specific stuff is pretty simple though so this |
201 | 420 | # should be fine. | 421 | # should be fine. |
202 | 422 | # TODO: Store a timeline action for zopeless mail. | ||
203 | 421 | 423 | ||
204 | 422 | if config.instance_name == 'testrunner': | 424 | if config.instance_name == 'testrunner': |
205 | 423 | # when running in the testing environment, store emails | 425 | # when running in the testing environment, store emails |
206 | @@ -443,14 +445,17 @@ | |||
207 | 443 | # The "MAIL FROM" is set to the bounce address, to behave in a way | 445 | # The "MAIL FROM" is set to the bounce address, to behave in a way |
208 | 444 | # similar to mailing list software. | 446 | # similar to mailing list software. |
209 | 445 | return raw_sendmail( | 447 | return raw_sendmail( |
211 | 446 | config.canonical.bounce_address, to_addrs, raw_message) | 448 | config.canonical.bounce_address, |
212 | 449 | to_addrs, | ||
213 | 450 | raw_message, | ||
214 | 451 | message_detail) | ||
215 | 447 | 452 | ||
216 | 448 | 453 | ||
217 | 449 | def get_msgid(): | 454 | def get_msgid(): |
218 | 450 | return make_msgid('launchpad') | 455 | return make_msgid('launchpad') |
219 | 451 | 456 | ||
220 | 452 | 457 | ||
222 | 453 | def raw_sendmail(from_addr, to_addrs, raw_message): | 458 | def raw_sendmail(from_addr, to_addrs, raw_message, message_detail): |
223 | 454 | """Send a raw RFC8222 email message. | 459 | """Send a raw RFC8222 email message. |
224 | 455 | 460 | ||
225 | 456 | All headers and encoding should already be done, as the message is | 461 | All headers and encoding should already be done, as the message is |
226 | @@ -461,12 +466,21 @@ | |||
227 | 461 | 466 | ||
228 | 462 | Returns the message-id. | 467 | Returns the message-id. |
229 | 463 | 468 | ||
230 | 469 | :param message_detail: Information about the message to include in the | ||
231 | 470 | request timeline. | ||
232 | 464 | """ | 471 | """ |
233 | 472 | # Note that raw_sendail has no tests, unit or otherwise. | ||
234 | 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' |
235 | 466 | assert isinstance(raw_message, str), 'Not a plain string' | 474 | assert isinstance(raw_message, str), 'Not a plain string' |
236 | 467 | assert raw_message.decode('ascii'), 'Not ASCII - badly encoded message' | 475 | assert raw_message.decode('ascii'), 'Not ASCII - badly encoded message' |
237 | 468 | mailer = zapi.getUtility(IMailDelivery, 'Mail') | 476 | mailer = zapi.getUtility(IMailDelivery, 'Mail') |
239 | 469 | return mailer.send(from_addr, to_addrs, raw_message) | 477 | request = get_current_browser_request() |
240 | 478 | timeline = get_request_timeline(request) | ||
241 | 479 | action = timeline.start("sendmail", message_detail) | ||
242 | 480 | try: | ||
243 | 481 | return mailer.send(from_addr, to_addrs, raw_message) | ||
244 | 482 | finally: | ||
245 | 483 | action.finish() | ||
246 | 470 | 484 | ||
247 | 471 | 485 | ||
248 | 472 | if __name__ == '__main__': | 486 | if __name__ == '__main__': |
249 | 473 | 487 | ||
250 | === modified file 'lib/lp/services/mail/tests/test_sendmail.py' | |||
251 | --- lib/lp/services/mail/tests/test_sendmail.py 2010-08-20 20:31:18 +0000 | |||
252 | +++ lib/lp/services/mail/tests/test_sendmail.py 2010-09-05 05:22:30 +0000 | |||
253 | @@ -243,7 +243,3 @@ | |||
254 | 243 | suite.addTest(DocTestSuite('lp.services.mail.sendmail')) | 243 | suite.addTest(DocTestSuite('lp.services.mail.sendmail')) |
255 | 244 | suite.addTests(unittest.TestLoader().loadTestsFromName(__name__)) | 244 | suite.addTests(unittest.TestLoader().loadTestsFromName(__name__)) |
256 | 245 | return suite | 245 | return suite |
257 | 246 | |||
258 | 247 | |||
259 | 248 | if __name__ == '__main__': | ||
260 | 249 | unittest.main(defaultTest='test_suite') | ||
261 | 250 | 246 | ||
262 | === modified file 'lib/lp/services/memcache/client.py' | |||
263 | --- lib/lp/services/memcache/client.py 2010-08-20 20:31:18 +0000 | |||
264 | +++ lib/lp/services/memcache/client.py 2010-09-05 05:22:30 +0000 | |||
265 | @@ -8,9 +8,11 @@ | |||
266 | 8 | 8 | ||
267 | 9 | import re | 9 | import re |
268 | 10 | 10 | ||
269 | 11 | from lazr.restful.utils import get_current_browser_request | ||
270 | 11 | import memcache | 12 | import memcache |
271 | 12 | 13 | ||
272 | 13 | from canonical.config import config | 14 | from canonical.config import config |
273 | 15 | from lp.services.timeline.requesttimeline import get_request_timeline | ||
274 | 14 | 16 | ||
275 | 15 | 17 | ||
276 | 16 | def memcache_client_factory(): | 18 | def memcache_client_factory(): |
277 | @@ -20,4 +22,27 @@ | |||
278 | 20 | r'\((.+?),(\d+)\)', config.memcache.servers)] | 22 | r'\((.+?),(\d+)\)', config.memcache.servers)] |
279 | 21 | assert len(servers) > 0, "Invalid memcached server list %r" % ( | 23 | assert len(servers) > 0, "Invalid memcached server list %r" % ( |
280 | 22 | config.memcache.addresses,) | 24 | config.memcache.addresses,) |
282 | 23 | return memcache.Client(servers) | 25 | return TimelineRecordingClient(servers) |
283 | 26 | |||
284 | 27 | |||
285 | 28 | class TimelineRecordingClient(memcache.Client): | ||
286 | 29 | |||
287 | 30 | def __get_timeline_action(self, suffix, key): | ||
288 | 31 | request = get_current_browser_request() | ||
289 | 32 | timeline = get_request_timeline(request) | ||
290 | 33 | return timeline.start("memcache-%s" % suffix, key) | ||
291 | 34 | |||
292 | 35 | def get(self, key): | ||
293 | 36 | action = self.__get_timeline_action("get", key) | ||
294 | 37 | try: | ||
295 | 38 | return memcache.Client.get(self, key) | ||
296 | 39 | finally: | ||
297 | 40 | action.finish() | ||
298 | 41 | |||
299 | 42 | def set(self, key, value, time=0, min_compress_len=0): | ||
300 | 43 | action = self.__get_timeline_action("set", key) | ||
301 | 44 | try: | ||
302 | 45 | return memcache.Client.set(self, key, value, time=time, | ||
303 | 46 | min_compress_len=min_compress_len) | ||
304 | 47 | finally: | ||
305 | 48 | action.finish() | ||
306 | 24 | 49 | ||
307 | === modified file 'lib/lp/services/memcache/tests/test_memcache_client.py' | |||
308 | --- lib/lp/services/memcache/tests/test_memcache_client.py 2010-01-07 06:47:46 +0000 | |||
309 | +++ lib/lp/services/memcache/tests/test_memcache_client.py 2010-09-05 05:22:30 +0000 | |||
310 | @@ -5,16 +5,17 @@ | |||
311 | 5 | 5 | ||
312 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
313 | 7 | 7 | ||
316 | 8 | import unittest | 8 | from lazr.restful.utils import get_current_browser_request |
315 | 9 | |||
317 | 10 | from zope.component import getUtility | 9 | from zope.component import getUtility |
318 | 11 | 10 | ||
319 | 12 | from canonical.testing.layers import LaunchpadZopelessLayer | 11 | from canonical.testing.layers import LaunchpadZopelessLayer |
320 | 13 | from lp.services.memcache.interfaces import IMemcacheClient | 12 | from lp.services.memcache.interfaces import IMemcacheClient |
321 | 13 | from lp.services.timeline.requesttimeline import get_request_timeline | ||
322 | 14 | from lp.testing import TestCase | 14 | from lp.testing import TestCase |
323 | 15 | 15 | ||
324 | 16 | 16 | ||
325 | 17 | class MemcacheClientTestCase(TestCase): | 17 | class MemcacheClientTestCase(TestCase): |
326 | 18 | |||
327 | 18 | layer = LaunchpadZopelessLayer | 19 | layer = LaunchpadZopelessLayer |
328 | 19 | 20 | ||
329 | 20 | def setUp(self): | 21 | def setUp(self): |
330 | @@ -36,7 +37,19 @@ | |||
331 | 36 | self.client.MemcachedKeyCharacterError, | 37 | self.client.MemcachedKeyCharacterError, |
332 | 37 | self.client.set, 'key with spaces', 'some value') | 38 | self.client.set, 'key with spaces', 'some value') |
333 | 38 | 39 | ||
338 | 39 | 40 | def test_set_recorded_to_timeline(self): | |
339 | 40 | def test_suite(): | 41 | request = get_current_browser_request() |
340 | 41 | return unittest.TestLoader().loadTestsFromName(__name__) | 42 | timeline = get_request_timeline(request) |
341 | 42 | 43 | self.client.set('foo', 'bar') | |
342 | 44 | action = timeline.actions[-1] | ||
343 | 45 | self.assertEqual('memcache-set', action.category) | ||
344 | 46 | self.assertEqual('foo', action.detail) | ||
345 | 47 | |||
346 | 48 | def test_get_recorded_to_timeline(self): | ||
347 | 49 | request = get_current_browser_request() | ||
348 | 50 | timeline = get_request_timeline(request) | ||
349 | 51 | self.client.set('foo', 'bar') | ||
350 | 52 | self.client.get('foo') | ||
351 | 53 | action = timeline.actions[-1] | ||
352 | 54 | self.assertEqual('memcache-get', action.category) | ||
353 | 55 | self.assertEqual('foo', action.detail) | ||
354 | 43 | 56 | ||
355 | === modified file 'lib/lp/services/timeline/tests/test_timedaction.py' | |||
356 | --- lib/lp/services/timeline/tests/test_timedaction.py 2010-09-03 01:09:38 +0000 | |||
357 | +++ lib/lp/services/timeline/tests/test_timedaction.py 2010-09-05 05:22:30 +0000 | |||
358 | @@ -41,8 +41,10 @@ | |||
359 | 41 | action.duration = datetime.timedelta(0, 0, 0, 4) | 41 | action.duration = datetime.timedelta(0, 0, 0, 4) |
360 | 42 | log_tuple = action.logTuple() | 42 | log_tuple = action.logTuple() |
361 | 43 | self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,)) | 43 | self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,)) |
362 | 44 | # The first element is the start offset in ms. | ||
363 | 44 | self.assertAlmostEqual(2, log_tuple[0]) | 45 | self.assertAlmostEqual(2, log_tuple[0]) |
365 | 45 | self.assertAlmostEqual(4, log_tuple[1]) | 46 | # The second element is the end offset in ms. |
366 | 47 | self.assertAlmostEqual(6, log_tuple[1]) | ||
367 | 46 | self.assertEqual("foo", log_tuple[2]) | 48 | self.assertEqual("foo", log_tuple[2]) |
368 | 47 | self.assertEqual("bar", log_tuple[3]) | 49 | self.assertEqual("bar", log_tuple[3]) |
369 | 48 | 50 | ||
370 | @@ -56,6 +58,6 @@ | |||
371 | 56 | log_tuple = action.logTuple() | 58 | log_tuple = action.logTuple() |
372 | 57 | self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,)) | 59 | self.assertEqual(4, len(log_tuple), "!= 4 elements %s" % (log_tuple,)) |
373 | 58 | self.assertAlmostEqual(2, log_tuple[0]) | 60 | self.assertAlmostEqual(2, log_tuple[0]) |
375 | 59 | self.assertAlmostEqual(999999, log_tuple[1]) | 61 | self.assertAlmostEqual(1000001, log_tuple[1]) |
376 | 60 | self.assertEqual("foo", log_tuple[2]) | 62 | self.assertEqual("foo", log_tuple[2]) |
377 | 61 | self.assertEqual("bar", log_tuple[3]) | 63 | self.assertEqual("bar", log_tuple[3]) |
378 | 62 | 64 | ||
379 | === modified file 'lib/lp/services/timeline/timedaction.py' | |||
380 | --- lib/lp/services/timeline/timedaction.py 2010-09-03 01:09:38 +0000 | |||
381 | +++ lib/lp/services/timeline/timedaction.py 2010-09-05 05:22:30 +0000 | |||
382 | @@ -57,7 +57,7 @@ | |||
383 | 57 | length = 999999 | 57 | length = 999999 |
384 | 58 | else: | 58 | else: |
385 | 59 | length = self._td_to_ms(self.duration) | 59 | length = self._td_to_ms(self.duration) |
387 | 60 | return (offset, length, self.category, self.detail) | 60 | return (offset, offset + length, self.category, self.detail) |
388 | 61 | 61 | ||
389 | 62 | def _td_to_ms(self, td): | 62 | def _td_to_ms(self, td): |
390 | 63 | """Tweak on a backport from python 2.7""" | 63 | """Tweak on a backport from python 2.7""" |
391 | 64 | 64 | ||
392 | === modified file 'lib/lp/testing/tests/test_testcase.py' | |||
393 | --- lib/lp/testing/tests/test_testcase.py 2010-08-20 20:31:18 +0000 | |||
394 | +++ lib/lp/testing/tests/test_testcase.py 2010-09-05 05:22:30 +0000 | |||
395 | @@ -91,8 +91,11 @@ | |||
396 | 91 | # getLastOopsReport does, and doing so changes whether the | 91 | # getLastOopsReport does, and doing so changes whether the |
397 | 92 | # timezone is in the timestamp. | 92 | # timezone is in the timestamp. |
398 | 93 | content = StringIO() | 93 | content = StringIO() |
400 | 94 | content.writelines(self.getDetails()['oops-0'].iter_text()) | 94 | content.writelines(self.getDetails()['oops-0'].iter_bytes()) |
401 | 95 | content.seek(0) | 95 | content.seek(0) |
402 | 96 | # Safety net: ensure that no autocasts have occured even on Python 2.6 | ||
403 | 97 | # which is slightly better. | ||
404 | 98 | self.assertIsInstance(content.getvalue(), str) | ||
405 | 96 | from_details = errorlog.ErrorReport.read(content) | 99 | from_details = errorlog.ErrorReport.read(content) |
406 | 97 | self.assertEqual( | 100 | self.assertEqual( |
407 | 98 | oops.get_chunks(), | 101 | oops.get_chunks(), |
=== modified file 'lib/lp/ services/ timeline/ tests/test_ timedaction. py' services/ timeline/ tests/test_ timedaction. py 2010-09-03 01:09:38 +0000 services/ timeline/ tests/test_ timedaction. py 2010-09-05 05:07:57 +0000
action. duration = datetime. timedelta( 0, 0, 0, 4)
self. assertEqual( 4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))
self. assertAlmostEqu al(2, log_tuple[0]) stEqual( 4, log_tuple[1]) stEqual( 6, log_tuple[1])
self. assertEqual( "foo", log_tuple[2])
self. assertEqual( "bar", log_tuple[3])
--- lib/lp/
+++ lib/lp/
@@ -41,8 +41,10 @@
log_tuple = action.logTuple()
+ # The first element is the start offset in ms.
- self.assertAlmo
+ # The second element is the end offset in ms.
+ self.assertAlmo
@@ -56,6 +58,6 @@
self. assertEqual( 4, len(log_tuple), "!= 4 elements %s" % (log_tuple,))
self. assertAlmostEqu al(2, log_tuple[0]) stEqual( 999999, log_tuple[1]) stEqual( 1000001, log_tuple[1])
self. assertEqual( "foo", log_tuple[2])
self. assertEqual( "bar", log_tuple[3])
log_tuple = action.logTuple()
- self.assertAlmo
+ self.assertAlmo
=== modified file 'lib/lp/ services/ timeline/ timedaction. py' services/ timeline/ timedaction. py 2010-09-03 01:09:38 +0000 services/ timeline/ timedaction. py 2010-09-05 05:07:57 +0000 to_ms(self. duration)
--- lib/lp/
+++ lib/lp/
@@ -57,7 +57,7 @@
length = 999999
else:
length = self._td_
- 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"""