Merge lp:~allenap/txlongpoll/oops-amqp into lp:txlongpoll

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
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().

Revision history for this message
Raphaël Badin (rvb) wrote :

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:
s/test_with_all_the_things/test_with_all_the_params
s/test_with_some_of_the_things/test_with_some_of_the_params
;)

review: Approve
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__)

Subscribers

People subscribed via source and target branches

to all changes: