Merge lp:~allenap/txlongpoll/oops-amqp into lp:txlongpoll
- oops-amqp
- Merge into trunk
Proposed by
Gavin Panella
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | 103 |
Merged at revision: | 79 |
Proposed branch: | lp:~allenap/txlongpoll/oops-amqp |
Merge into: | lp:txlongpoll |
Prerequisite: | lp:~allenap/txlongpoll/bootstrap-without-net |
Diff against target: |
695 lines (+447/-145) 11 files modified
.bzrignore (+1/-0) .testr.conf (+5/-0) Makefile (+3/-3) NEWS (+11/-0) README (+8/-0) buildout.cfg (+3/-4) setup.py (+2/-0) test (+2/-0) twisted/plugins/txlongpoll.py (+3/-138) txlongpoll/plugin.py (+172/-0) txlongpoll/tests/test_plugin.py (+237/-0) |
To merge this branch: | bzr merge lp:~allenap/txlongpoll/oops-amqp |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+81504@code.launchpad.net |
Commit message
Report OOPSes via AMQP.
Description of the change
This builds on Rob's work and adds some simple tests for the plugin code.
To post a comment you must log in.
lp:~allenap/txlongpoll/oops-amqp
updated
- 97. By Gavin Panella
-
Simplify options_diff().
lp:~allenap/txlongpoll/oops-amqp
updated
- 98. By Gavin Panella
-
Convenience test script.
- 99. By Gavin Panella
-
Split up test_parse_
minimal_ options( ) as suggested by rvb. - 100. By Gavin Panella
-
Simplify test_parse_
int_options( ). - 101. By Gavin Panella
-
Remove humour from test names :)
- 102. By Gavin Panella
-
Add testrepository support.
- 103. By Gavin Panella
-
Create the txlongpoll.log in a temp dir.
Revision history for this message
Gavin Panella (allenap) wrote : | # |
Raphaël, I've made all the excellent changes you suggested. I've also
added testrepository support, a convenience script for testing, and
fixed a situation where a test was leaving a log file in the tree, but
these are fairly trivial so r=me for my own changes.
Thanks for the review!
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2011-06-28 14:52:07 +0000 |
3 | +++ .bzrignore 2011-11-08 11:40:27 +0000 |
4 | @@ -11,3 +11,4 @@ |
5 | ./parts |
6 | ./tags |
7 | dropin.cache |
8 | +./.testrepository |
9 | |
10 | === added file '.testr.conf' |
11 | --- .testr.conf 1970-01-01 00:00:00 +0000 |
12 | +++ .testr.conf 2011-11-08 11:40:27 +0000 |
13 | @@ -0,0 +1,5 @@ |
14 | +[DEFAULT] |
15 | +test_command = bin/testpy -m subunit/run -- discover $LISTOPT $IDOPTION |
16 | +test_id_option = --load-list $IDFILE |
17 | +# The "ignore" value below works around a bug in subunit. |
18 | +test_list_option = --list=ignore |
19 | |
20 | === modified file 'Makefile' |
21 | --- Makefile 2011-11-07 11:28:33 +0000 |
22 | +++ Makefile 2011-11-08 11:40:27 +0000 |
23 | @@ -22,8 +22,8 @@ |
24 | $(BUILDOUT) |
25 | |
26 | |
27 | -check: bin/test |
28 | - bin/test -vv |
29 | +check: bin/testpy |
30 | + bin/testpy -m subunit/run -- discover |
31 | |
32 | |
33 | dist: $(BUILDOUT_BIN) |
34 | @@ -58,7 +58,7 @@ |
35 | $(BUILDOUT) install runtime |
36 | |
37 | |
38 | -bin/test: $(BUILDOUT_BIN) $(BUILDOUT_CFG) setup.py |
39 | +bin/testpy: $(BUILDOUT_BIN) $(BUILDOUT_CFG) setup.py |
40 | $(BUILDOUT) install test |
41 | |
42 | |
43 | |
44 | === added file 'NEWS' |
45 | --- NEWS 1970-01-01 00:00:00 +0000 |
46 | +++ NEWS 2011-11-08 11:40:27 +0000 |
47 | @@ -0,0 +1,11 @@ |
48 | +txlongpoll NEWS |
49 | ++++++++++++++++ |
50 | + |
51 | +Changes and improvements to txlongpoll, grouped by release. |
52 | + |
53 | +NEXT |
54 | +---- |
55 | + |
56 | +* OOPS reports can be reported directly over AMQP if the oops-exchange option |
57 | + is given. The oops-prefix option has been renamed to oops-reporter to fit in |
58 | + with the long term terminology oops has adopted. (Robert Collins) |
59 | |
60 | === modified file 'README' |
61 | --- README 2011-11-07 12:03:18 +0000 |
62 | +++ README 2011-11-08 11:40:27 +0000 |
63 | @@ -46,6 +46,14 @@ |
64 | |
65 | will just do the first part. |
66 | |
67 | +There is testrepository <https://launchpad.net/testrepository> |
68 | +support, so you can also do the following: |
69 | + |
70 | + $ testr init |
71 | + $ testr run |
72 | + |
73 | +This is probably the best way to develop txlongpoll. |
74 | + |
75 | |
76 | Running |
77 | ------- |
78 | |
79 | === modified file 'buildout.cfg' |
80 | --- buildout.cfg 2011-11-04 20:09:20 +0000 |
81 | +++ buildout.cfg 2011-11-08 11:40:27 +0000 |
82 | @@ -20,13 +20,12 @@ |
83 | [runtime] |
84 | recipe = zc.recipe.egg:scripts |
85 | eggs = txlongpoll |
86 | -entry-points = twistd=twisted.scripts.twistd:run |
87 | -interpreter = py |
88 | +entry-points = twistd=twisted.scripts.twistd:run |
89 | |
90 | [test] |
91 | -recipe = zc.recipe.testrunner |
92 | +recipe = zc.recipe.egg:scripts |
93 | eggs = txlongpoll [test] |
94 | -defaults = '--tests-pattern ^tests --exit-with-status'.split() |
95 | +interpreter = testpy |
96 | |
97 | [tags] |
98 | recipe = z3c.recipe.tag:tags |
99 | |
100 | === modified file 'setup.py' |
101 | --- setup.py 2011-11-04 16:51:11 +0000 |
102 | +++ setup.py 2011-11-08 11:40:27 +0000 |
103 | @@ -18,6 +18,7 @@ |
104 | zip_safe=False, |
105 | description='Long polling HTTP frontend for AMQP', |
106 | install_requires=[ |
107 | + 'oops_amqp', |
108 | 'oops_datedir_repo', |
109 | 'oops_twisted >= 0.0.3', |
110 | 'setproctitle', |
111 | @@ -30,5 +31,6 @@ |
112 | 'rabbitfixture', |
113 | 'testresources >= 0.2.4_r58', |
114 | 'testtools', |
115 | + # 'subunit', # Not easy-installable. |
116 | ], |
117 | )) |
118 | |
119 | === added file 'test' |
120 | --- test 1970-01-01 00:00:00 +0000 |
121 | +++ test 2011-11-08 11:40:27 +0000 |
122 | @@ -0,0 +1,2 @@ |
123 | +#!/usr/bin/env bash |
124 | +exec "$(dirname "$0")/bin/testpy" -m subunit/run -- "$@" |
125 | |
126 | === modified file 'twisted/plugins/txlongpoll.py' |
127 | --- twisted/plugins/txlongpoll.py 2011-10-06 20:15:29 +0000 |
128 | +++ twisted/plugins/txlongpoll.py 2011-11-08 11:40:27 +0000 |
129 | @@ -3,144 +3,9 @@ |
130 | |
131 | from __future__ import absolute_import |
132 | |
133 | -import signal |
134 | -import sys |
135 | - |
136 | -from oops_datedir_repo import DateDirRepo |
137 | -from oops_twisted import ( |
138 | - Config as oops_config, |
139 | - defer_publisher, |
140 | - OOPSObserver, |
141 | - ) |
142 | -import setproctitle |
143 | -from twisted.application.internet import ( |
144 | - TCPClient, |
145 | - TCPServer, |
146 | - ) |
147 | -from twisted.application.service import ( |
148 | - IServiceMaker, |
149 | - MultiService, |
150 | - ) |
151 | -from twisted.internet import reactor |
152 | -from twisted.plugin import IPlugin |
153 | -from twisted.python import ( |
154 | - log, |
155 | - usage, |
156 | - ) |
157 | -from twisted.python.log import ( |
158 | - addObserver, |
159 | - FileLogObserver, |
160 | - ) |
161 | -from twisted.python.logfile import LogFile |
162 | -from twisted.web.server import Site |
163 | -from txlongpoll.client import AMQFactory |
164 | -from txlongpoll.frontend import ( |
165 | - FrontEndAjax, |
166 | - QueueManager, |
167 | - ) |
168 | -from zope.interface import implements |
169 | - |
170 | - |
171 | -def getRotatableLogFileObserver(filename): |
172 | - """Setup a L{LogFile} for the given application.""" |
173 | - if filename != '-': |
174 | - logfile = LogFile.fromFullPath( |
175 | - filename, rotateLength=None, defaultMode=0644) |
176 | - |
177 | - def signal_handler(*args): |
178 | - reactor.callFromThread(logfile.reopen) |
179 | - |
180 | - signal.signal(signal.SIGUSR1, signal_handler) |
181 | - else: |
182 | - logfile = sys.stdout |
183 | - |
184 | - return FileLogObserver(logfile) |
185 | - |
186 | - |
187 | -def setUpOopsHandler(options, logfile): |
188 | - """Add OOPS handling based on the passed command line options.""" |
189 | - config = oops_config() |
190 | - |
191 | - # Add the oops publisher that writes files in the configured place |
192 | - # if the command line option was set. |
193 | - if options["oops-dir"]: |
194 | - repo = DateDirRepo(options["oops-dir"], options["oops-prefix"]) |
195 | - config.publishers.append(defer_publisher(repo.publish)) |
196 | - |
197 | - observer = OOPSObserver(config, logfile.emit) |
198 | - addObserver(observer.emit) |
199 | - |
200 | - |
201 | -class Options(usage.Options): |
202 | - optParameters = [ |
203 | - ["logfile", "l", "txlongpoll.log", "Logfile name."], |
204 | - ["brokerport", "p", 5672, "Broker port"], |
205 | - ["brokerhost", "h", '127.0.0.1', "Broker host"], |
206 | - ["brokeruser", "u", None, "Broker user"], |
207 | - ["brokerpassword", "a", None, "Broker password"], |
208 | - ["brokervhost", "v", '/', "Broker vhost"], |
209 | - ["frontendport", "f", None, "Frontend port"], |
210 | - ["prefix", "x", None, "Queue prefix"], |
211 | - ["oops-dir", "r", None, "Where to write OOPS reports"], |
212 | - ["oops-prefix", "o", "LONGPOLL", "String prefix for OOPS IDs"], |
213 | - ] |
214 | - |
215 | - def postOptions(self): |
216 | - for man_arg in ('frontendport', 'brokeruser', 'brokerpassword'): |
217 | - if not self[man_arg]: |
218 | - raise usage.UsageError("--%s must be specified." % man_arg) |
219 | - for int_arg in ('brokerport', 'frontendport'): |
220 | - try: |
221 | - self[int_arg] = int(self[int_arg]) |
222 | - except (TypeError, ValueError): |
223 | - raise usage.UsageError("--%s must be an integer." % int_arg) |
224 | - |
225 | - |
226 | -class AMQServiceMaker(object): |
227 | - """Create an asynchronous frontend server for AMQP.""" |
228 | - |
229 | - implements(IServiceMaker, IPlugin) |
230 | - |
231 | - options = Options |
232 | - |
233 | - def __init__(self, name, description): |
234 | - self.tapname = name |
235 | - self.description = description |
236 | - |
237 | - def makeService(self, options): |
238 | - """Construct a TCPServer and TCPClient.""" |
239 | - setproctitle.setproctitle( |
240 | - "txlongpoll: accepting connections on %s" % |
241 | - options["frontendport"]) |
242 | - |
243 | - logfile = getRotatableLogFileObserver(options["logfile"]) |
244 | - setUpOopsHandler(options, logfile) |
245 | - |
246 | - broker_port = options["brokerport"] |
247 | - broker_host = options["brokerhost"] |
248 | - broker_user = options["brokeruser"] |
249 | - broker_password = options["brokerpassword"] |
250 | - broker_vhost = options["brokervhost"] |
251 | - frontend_port = options["frontendport"] |
252 | - prefix = options["prefix"] |
253 | - |
254 | - manager = QueueManager(prefix) |
255 | - factory = AMQFactory( |
256 | - broker_user, broker_password, broker_vhost, manager.connected, |
257 | - manager.disconnected, |
258 | - lambda (connector, reason): log.err(reason, "Connection failed")) |
259 | - resource = FrontEndAjax(manager) |
260 | - |
261 | - client_service = TCPClient(broker_host, broker_port, factory) |
262 | - server_service = TCPServer(frontend_port, Site(resource)) |
263 | - services = MultiService() |
264 | - services.addService(client_service) |
265 | - services.addService(server_service) |
266 | - |
267 | - return services |
268 | - |
269 | - |
270 | -# Now construct objects which *provide* the relevant interfaces. The name of |
271 | +from txlongpoll.plugin import AMQServiceMaker |
272 | + |
273 | +# Construct objects which *provide* the relevant interfaces. The name of |
274 | # these variables is irrelevant, as long as there are *some* names bound to |
275 | # providers of IPlugin and IServiceMaker. |
276 | |
277 | |
278 | === added file 'txlongpoll/plugin.py' |
279 | --- txlongpoll/plugin.py 1970-01-01 00:00:00 +0000 |
280 | +++ txlongpoll/plugin.py 2011-11-08 11:40:27 +0000 |
281 | @@ -0,0 +1,172 @@ |
282 | +# Copyright 2005-2011 Canonical Ltd. This software is licensed under |
283 | +# the GNU Affero General Public License version 3 (see the file LICENSE). |
284 | + |
285 | +__all__ = [ |
286 | + "AMQServiceMaker", |
287 | + ] |
288 | + |
289 | +from functools import partial |
290 | +import signal |
291 | +import sys |
292 | + |
293 | +from amqplib import client_0_8 as amqp |
294 | +import oops |
295 | +from oops_amqp import Publisher |
296 | +from oops_datedir_repo import DateDirRepo |
297 | +from oops_twisted import ( |
298 | + Config as oops_config, |
299 | + defer_publisher, |
300 | + OOPSObserver, |
301 | + ) |
302 | +import setproctitle |
303 | +from twisted.application.internet import ( |
304 | + TCPClient, |
305 | + TCPServer, |
306 | + ) |
307 | +from twisted.application.service import ( |
308 | + IServiceMaker, |
309 | + MultiService, |
310 | + ) |
311 | +from twisted.internet import reactor |
312 | +from twisted.plugin import IPlugin |
313 | +from twisted.python import ( |
314 | + log, |
315 | + usage, |
316 | + ) |
317 | +from twisted.python.log import ( |
318 | + addObserver, |
319 | + FileLogObserver, |
320 | + ) |
321 | +from twisted.python.logfile import LogFile |
322 | +from twisted.web.server import Site |
323 | +from txlongpoll.client import AMQFactory |
324 | +from txlongpoll.frontend import ( |
325 | + FrontEndAjax, |
326 | + QueueManager, |
327 | + ) |
328 | +from zope.interface import implements |
329 | + |
330 | + |
331 | +def getRotatableLogFileObserver(filename): |
332 | + """Setup a L{LogFile} for the given application.""" |
333 | + if filename != '-': |
334 | + logfile = LogFile.fromFullPath( |
335 | + filename, rotateLength=None, defaultMode=0644) |
336 | + def signal_handler(sig, frame): |
337 | + reactor.callFromThread(logfile.reopen) |
338 | + signal.signal(signal.SIGUSR1, signal_handler) |
339 | + else: |
340 | + logfile = sys.stdout |
341 | + return FileLogObserver(logfile) |
342 | + |
343 | + |
344 | +def setUpOOPSHandler(options, logfile): |
345 | + """Add OOPS handling based on the passed command line options.""" |
346 | + config = oops_config() |
347 | + |
348 | + # Add the oops publisher that writes files in the configured place |
349 | + # if the command line option was set. |
350 | + |
351 | + if options["oops-exchange"]: |
352 | + oops_exchange = options["oops-exchange"] |
353 | + oops_key = options["oops-routingkey"] or "" |
354 | + host = options["brokerhost"] |
355 | + if options["brokerport"]: |
356 | + host = "%s:%s" % (host, options["brokerport"]) |
357 | + rabbit_connect = partial( |
358 | + amqp.Connection, host=host, |
359 | + userid=options["brokeruser"], |
360 | + password=options["brokerpassword"], |
361 | + virtual_host=options["brokervhost"]) |
362 | + amqp_publisher = Publisher( |
363 | + rabbit_connect, oops_exchange, oops_key) |
364 | + config.publishers.append(defer_publisher(amqp_publisher)) |
365 | + |
366 | + if options["oops-dir"]: |
367 | + repo = DateDirRepo(options["oops-dir"]) |
368 | + config.publishers.append( |
369 | + defer_publisher(oops.publish_new_only(repo.publish))) |
370 | + |
371 | + if options["oops-reporter"]: |
372 | + config.template['reporter'] = options["oops-reporter"] |
373 | + |
374 | + observer = OOPSObserver(config, logfile.emit) |
375 | + addObserver(observer.emit) |
376 | + return observer |
377 | + |
378 | + |
379 | +class Options(usage.Options): |
380 | + |
381 | + optParameters = [ |
382 | + ["logfile", "l", "txlongpoll.log", "Logfile name."], |
383 | + ["brokerport", "p", 5672, "Broker port"], |
384 | + ["brokerhost", "h", '127.0.0.1', "Broker host"], |
385 | + ["brokeruser", "u", None, "Broker user"], |
386 | + ["brokerpassword", "a", None, "Broker password"], |
387 | + ["brokervhost", "v", '/', "Broker vhost"], |
388 | + ["frontendport", "f", None, "Frontend port"], |
389 | + ["prefix", "x", None, "Queue prefix"], |
390 | + ["oops-dir", "r", None, "Where to write OOPS reports"], |
391 | + ["oops-reporter", "o", "LONGPOLL", "String identifying this service."], |
392 | + ["oops-exchange", None, None, "AMQP Exchange to send OOPS reports to."], |
393 | + ["oops-routingkey", None, None, "Routing key for AMQP OOPSes."], |
394 | + ] |
395 | + |
396 | + def postOptions(self): |
397 | + for man_arg in ('frontendport', 'brokeruser', 'brokerpassword'): |
398 | + if not self[man_arg]: |
399 | + raise usage.UsageError("--%s must be specified." % man_arg) |
400 | + for int_arg in ('brokerport', 'frontendport'): |
401 | + try: |
402 | + self[int_arg] = int(self[int_arg]) |
403 | + except (TypeError, ValueError): |
404 | + raise usage.UsageError("--%s must be an integer." % int_arg) |
405 | + if not self["oops-reporter"] and ( |
406 | + self["oops-exchange"] or self["oops-dir"]): |
407 | + raise usage.UsageError( |
408 | + "A reporter must be supplied to identify reports " |
409 | + "from this service from other OOPS reports.") |
410 | + |
411 | + |
412 | +class AMQServiceMaker(object): |
413 | + """Create an asynchronous frontend server for AMQP.""" |
414 | + |
415 | + implements(IServiceMaker, IPlugin) |
416 | + |
417 | + options = Options |
418 | + |
419 | + def __init__(self, name, description): |
420 | + self.tapname = name |
421 | + self.description = description |
422 | + |
423 | + def makeService(self, options): |
424 | + """Construct a TCPServer and TCPClient.""" |
425 | + setproctitle.setproctitle( |
426 | + "txlongpoll: accepting connections on %s" % |
427 | + options["frontendport"]) |
428 | + |
429 | + logfile = getRotatableLogFileObserver(options["logfile"]) |
430 | + setUpOOPSHandler(options, logfile) |
431 | + |
432 | + broker_port = options["brokerport"] |
433 | + broker_host = options["brokerhost"] |
434 | + broker_user = options["brokeruser"] |
435 | + broker_password = options["brokerpassword"] |
436 | + broker_vhost = options["brokervhost"] |
437 | + frontend_port = options["frontendport"] |
438 | + prefix = options["prefix"] |
439 | + |
440 | + manager = QueueManager(prefix) |
441 | + factory = AMQFactory( |
442 | + broker_user, broker_password, broker_vhost, manager.connected, |
443 | + manager.disconnected, |
444 | + lambda (connector, reason): log.err(reason, "Connection failed")) |
445 | + resource = FrontEndAjax(manager) |
446 | + |
447 | + client_service = TCPClient(broker_host, broker_port, factory) |
448 | + server_service = TCPServer(frontend_port, Site(resource)) |
449 | + services = MultiService() |
450 | + services.addService(client_service) |
451 | + services.addService(server_service) |
452 | + |
453 | + return services |
454 | |
455 | === added file 'txlongpoll/tests/test_plugin.py' |
456 | --- txlongpoll/tests/test_plugin.py 1970-01-01 00:00:00 +0000 |
457 | +++ txlongpoll/tests/test_plugin.py 2011-11-08 11:40:27 +0000 |
458 | @@ -0,0 +1,237 @@ |
459 | +# Copyright 2005-2011 Canonical Ltd. This software is licensed under the |
460 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
461 | + |
462 | +from cStringIO import StringIO |
463 | +from functools import partial |
464 | +import os |
465 | +from unittest import defaultTestLoader |
466 | + |
467 | +from fixtures import TempDir |
468 | +from oops_twisted import OOPSObserver |
469 | +from subunit import IsolatedTestCase |
470 | +from testtools import TestCase |
471 | +from testtools.content import ( |
472 | + Content, |
473 | + UTF8_TEXT, |
474 | + ) |
475 | +from testtools.matchers import ( |
476 | + MatchesException, |
477 | + Raises, |
478 | + ) |
479 | +from twisted.application.service import MultiService |
480 | +from twisted.python.log import ( |
481 | + FileLogObserver, |
482 | + theLogPublisher, |
483 | + ) |
484 | +from twisted.python.usage import UsageError |
485 | +from txlongpoll.plugin import ( |
486 | + AMQServiceMaker, |
487 | + Options, |
488 | + setUpOOPSHandler, |
489 | + ) |
490 | + |
491 | + |
492 | +class TestOptions(TestCase): |
493 | + """Tests for `txlongpoll.plugin.Options`.""" |
494 | + |
495 | + def test_defaults(self): |
496 | + options = Options() |
497 | + expected = { |
498 | + "brokerhost": "127.0.0.1", |
499 | + "brokerpassword": None, |
500 | + "brokerport": 5672, |
501 | + "brokeruser": None, |
502 | + "brokervhost": "/", |
503 | + "frontendport": None, |
504 | + "logfile": "txlongpoll.log", |
505 | + "oops-dir": None, |
506 | + "oops-exchange": None, |
507 | + "oops-reporter": "LONGPOLL", |
508 | + "oops-routingkey": None, |
509 | + "prefix": None, |
510 | + } |
511 | + self.assertEqual(expected, options.defaults) |
512 | + |
513 | + def check_exception(self, options, message, *arguments): |
514 | + # Check that a UsageError is raised when parsing options. |
515 | + self.assertThat( |
516 | + partial(options.parseOptions, arguments), |
517 | + Raises(MatchesException(UsageError, message))) |
518 | + |
519 | + def test_option_frontendport_required(self): |
520 | + options = Options() |
521 | + self.check_exception( |
522 | + options, |
523 | + "--frontendport must be specified") |
524 | + |
525 | + def test_option_brokeruser_required(self): |
526 | + options = Options() |
527 | + self.check_exception( |
528 | + options, |
529 | + "--brokeruser must be specified", |
530 | + "--frontendport", "1234") |
531 | + |
532 | + def test_option_brokerpassword_required(self): |
533 | + options = Options() |
534 | + self.check_exception( |
535 | + options, |
536 | + "--brokerpassword must be specified", |
537 | + "--brokeruser", "Bob", |
538 | + "--frontendport", "1234") |
539 | + |
540 | + def test_parse_minimal_options(self): |
541 | + options = Options() |
542 | + # The minimal set of options that must be provided. |
543 | + arguments = [ |
544 | + "--brokerpassword", "Hoskins", |
545 | + "--brokeruser", "Bob", |
546 | + "--frontendport", "1234", |
547 | + ] |
548 | + options.parseOptions(arguments) # No error. |
549 | + |
550 | + def test_parse_int_options(self): |
551 | + # Some options are converted to ints. |
552 | + options = Options() |
553 | + arguments = [ |
554 | + "--brokerpassword", "Hoskins", |
555 | + "--brokerport", "4321", |
556 | + "--brokeruser", "Bob", |
557 | + "--frontendport", "1234", |
558 | + ] |
559 | + options.parseOptions(arguments) |
560 | + self.assertEqual(4321, options["brokerport"]) |
561 | + self.assertEqual(1234, options["frontendport"]) |
562 | + |
563 | + def test_parse_broken_int_options(self): |
564 | + # An error is raised if the integer options do not contain integers. |
565 | + options = Options() |
566 | + arguments = [ |
567 | + "--brokerpassword", "Hoskins", |
568 | + "--brokerport", "Jr.", |
569 | + "--brokeruser", "Bob", |
570 | + ] |
571 | + self.assertRaises( |
572 | + UsageError, options.parseOptions, arguments) |
573 | + |
574 | + def test_oops_exchange_without_reporter(self): |
575 | + # It is an error to omit the OOPS reporter if exchange is specified. |
576 | + options = Options() |
577 | + arguments = [ |
578 | + "--brokerpassword", "Hoskins", |
579 | + "--brokeruser", "Bob", |
580 | + "--frontendport", "1234", |
581 | + "--oops-exchange", "something", |
582 | + "--oops-reporter", "", |
583 | + ] |
584 | + expected = MatchesException( |
585 | + UsageError, "A reporter must be supplied") |
586 | + self.assertThat( |
587 | + partial(options.parseOptions, arguments), |
588 | + Raises(expected)) |
589 | + |
590 | + def test_oops_dir_without_reporter(self): |
591 | + # It is an error to omit the OOPS reporter if directory is specified. |
592 | + options = Options() |
593 | + arguments = [ |
594 | + "--brokerpassword", "Hoskins", |
595 | + "--brokeruser", "Bob", |
596 | + "--frontendport", "1234", |
597 | + "--oops-dir", "/some/where", |
598 | + "--oops-reporter", "", |
599 | + ] |
600 | + expected = MatchesException( |
601 | + UsageError, "A reporter must be supplied") |
602 | + self.assertThat( |
603 | + partial(options.parseOptions, arguments), |
604 | + Raises(expected)) |
605 | + |
606 | + |
607 | +class TestSetUpOOPSHandler(TestCase): |
608 | + """Tests for `txlongpoll.plugin.setUpOOPSHandler`.""" |
609 | + |
610 | + def setUp(self): |
611 | + super(TestSetUpOOPSHandler, self).setUp() |
612 | + self.observers = theLogPublisher.observers[:] |
613 | + self.logfile = StringIO() |
614 | + self.addDetail("log", Content(UTF8_TEXT, self.logfile.getvalue)) |
615 | + self.log = FileLogObserver(self.logfile) |
616 | + |
617 | + def tearDown(self): |
618 | + super(TestSetUpOOPSHandler, self).tearDown() |
619 | + theLogPublisher.observers[:] = self.observers |
620 | + |
621 | + def makeObserver(self, settings): |
622 | + options = Options() |
623 | + options["brokerpassword"] = "Hoskins" |
624 | + options["brokeruser"] = "Bob" |
625 | + options["frontendport"] = 1234 |
626 | + options.update(settings) |
627 | + observer = setUpOOPSHandler(options, self.log) |
628 | + return options, observer |
629 | + |
630 | + def test_minimal(self): |
631 | + options, observer = self.makeObserver({}) |
632 | + self.assertIsInstance(observer, OOPSObserver) |
633 | + self.assertEqual([], observer.config.publishers) |
634 | + self.assertEqual( |
635 | + {"reporter": options.defaults["oops-reporter"]}, |
636 | + observer.config.template) |
637 | + |
638 | + def test_with_all_params(self): |
639 | + settings = { |
640 | + "oops-exchange": "Frank", |
641 | + "oops-reporter": "Sidebottom", |
642 | + "oops-dir": self.useFixture(TempDir()).path, |
643 | + } |
644 | + options, observer = self.makeObserver(settings) |
645 | + self.assertIsInstance(observer, OOPSObserver) |
646 | + self.assertEqual(2, len(observer.config.publishers)) |
647 | + self.assertEqual( |
648 | + {"reporter": "Sidebottom"}, |
649 | + observer.config.template) |
650 | + |
651 | + def test_with_some_params(self): |
652 | + settings = { |
653 | + "oops-exchange": "Frank", |
654 | + "oops-reporter": "Sidebottom", |
655 | + } |
656 | + options, observer = self.makeObserver(settings) |
657 | + self.assertIsInstance(observer, OOPSObserver) |
658 | + self.assertEqual(1, len(observer.config.publishers)) |
659 | + self.assertEqual( |
660 | + {"reporter": "Sidebottom"}, |
661 | + observer.config.template) |
662 | + |
663 | + |
664 | +class TestAMQServiceMaker(IsolatedTestCase, TestCase): |
665 | + """Tests for `txlongpoll.plugin.AMQServiceMaker`.""" |
666 | + |
667 | + def test_init(self): |
668 | + service_maker = AMQServiceMaker("Harry", "Hill") |
669 | + self.assertEqual("Harry", service_maker.tapname) |
670 | + self.assertEqual("Hill", service_maker.description) |
671 | + |
672 | + def makeOptions(self, settings): |
673 | + options = Options() |
674 | + options["brokerpassword"] = "Hoskins" |
675 | + options["brokeruser"] = "Bob" |
676 | + options["frontendport"] = 1234 |
677 | + options.update(settings) |
678 | + return options |
679 | + |
680 | + def test_makeService(self): |
681 | + logfile = os.path.join( |
682 | + self.useFixture(TempDir()).path, "txlongpoll.log") |
683 | + options = self.makeOptions({"logfile": logfile}) |
684 | + service_maker = AMQServiceMaker("Harry", "Hill") |
685 | + service = service_maker.makeService(options) |
686 | + self.assertIsInstance(service, MultiService) |
687 | + self.assertEqual(2, len(service.services)) |
688 | + client_service, server_service = service.services |
689 | + self.assertEqual(options["brokerhost"], client_service.args[0]) |
690 | + self.assertEqual(options["brokerport"], client_service.args[1]) |
691 | + self.assertEqual(options["frontendport"], server_service.args[0]) |
692 | + |
693 | + |
694 | +def test_suite(): |
695 | + return defaultTestLoader.loadTestsFromName(__name__) |
This branch looks good.
[0]
Maybe the tests would be more clear if they tested one thing each like this: http:// paste.ubuntu. com/731893/ (warning: I've not even run that code ;) ). What do you think?
[1]
In test_parse_ int_options, why do you test the option values parsed *and* the default options, why not a simple test that the option value is what it should be (like 1234 for the frontend port)? (you already have a test for the default option values)
[2]
Maybe: with_all_ the_things/ test_with_ all_the_ params with_some_ of_the_ things/ test_with_ some_of_ the_params
s/test_
s/test_
;)