Merge lp:~abentley/launchpad/celery-job-layer into lp:launchpad

Proposed by Aaron Bentley on 2012-04-12
Status: Merged
Approved by: Aaron Bentley on 2012-04-13
Approved revision: no longer in the source branch.
Merged at revision: 15098
Proposed branch: lp:~abentley/launchpad/celery-job-layer
Merge into: lp:launchpad
Prerequisite: lp:~abentley/launchpad/celery-everywhere-3
Diff against target: 557 lines (+135/-116)
7 files modified
lib/lp/code/model/tests/test_branch.py (+48/-54)
lib/lp/code/model/tests/test_branchjob.py (+12/-16)
lib/lp/code/model/tests/test_branchmergeproposaljobs.py (+9/-22)
lib/lp/codehosting/scanner/tests/test_email.py (+12/-17)
lib/lp/services/job/model/job.py (+8/-3)
lib/lp/services/job/tests/__init__.py (+9/-4)
lib/lp/testing/layers.py (+37/-0)
To merge this branch: bzr merge lp:~abentley/launchpad/celery-job-layer
Reviewer Review Type Date Requested Status
j.c.sackett (community) 2012-04-12 Approve on 2012-04-12
Review via email: mp+101806@code.launchpad.net

Commit Message

Run celeryd via a layer in tests.

Description of the Change

= Summary =
Run celeryd via a layer in tests.

== Proposed fix ==
None

== Pre-implementation notes ==
Robert confirmed that Layers are still our preferred means of reducing redundant startup/teardown costs.

== Implementation details ==
Implement CeleryJobLayer and CeleryBranchWriteJobLayer to provide celeryd instances for the corresponding queues.

Update all existing tests to use these layers

Extract block_on_job from various tests that retrieve the job's response and then wait.

lp.services.job.tests.celeryd only specifies parameters to lazr.jobrunner.tests.test_celerytask.running, and does not change its behaviour, so it can return running instead of being a contextmanager, itself.

== Tests ==
bin/test --layer=CeleryJobLayer
bin/test --layer=CeleryBranchWriteJobLayer

== Demo and Q/A ==
None.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/services/job/tests/test_runner.py
  lib/lp/codehosting/scanner/tests/test_mergedetection.py
  lib/lp/services/job/runner.py
  lib/lp/code/model/tests/test_branchmergeproposaljobs.py
  lib/lp/code/model/branchmergeproposal.py
  lib/lp/code/model/tests/test_branchjob.py
  lib/lp/services/job/tests/celery_helpers.py
  lib/lp/services/job/celeryjob.py
  lib/lp/codehosting/scanner/tests/test_email.py
  lib/lp/services/job/tests/test_job.py
  lib/lp/services/features/flags.py
  lib/lp/code/model/branchmergeproposaljob.py
  lib/lp/testing/layers.py
  lib/lp/services/job/celeryconfig.py
  lib/lp/services/job/model/job.py
  lib/lp/services/job/tests/__init__.py
  lib/lp/codehosting/scanner/email.py
  lib/lp/code/model/tests/test_branch.py
  lib/lp/code/model/branchjob.py

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

Aaron--

This looks like a really good cleanup. I think the tests are much cleaner this way. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/model/tests/test_branch.py'
2--- lib/lp/code/model/tests/test_branch.py 2012-04-10 20:24:43 +0000
3+++ lib/lp/code/model/tests/test_branch.py 2012-04-13 19:33:20 +0000
4@@ -12,7 +12,6 @@
5 datetime,
6 timedelta,
7 )
8-import os
9
10 from bzrlib.branch import Branch
11 from bzrlib.bzrdir import BzrDir
12@@ -118,7 +117,7 @@
13 from lp.services.database.lpstorm import IStore
14 from lp.services.features.testing import FeatureFixture
15 from lp.services.job.tests import (
16- celeryd,
17+ block_on_job,
18 monitor_celery,
19 )
20 from lp.services.osutils import override_environ
21@@ -141,6 +140,8 @@
22 from lp.testing.factory import LaunchpadObjectFactory
23 from lp.testing.layers import (
24 AppServerLayer,
25+ CeleryBranchWriteJobLayer,
26+ CeleryJobLayer,
27 DatabaseFunctionalLayer,
28 LaunchpadFunctionalLayer,
29 LaunchpadZopelessLayer,
30@@ -153,8 +154,9 @@
31
32 def create_knit(test_case):
33 db_branch, tree = test_case.create_branch_and_tree(format='knit')
34- db_branch.branch_format = BranchFormat.BZR_BRANCH_5
35- db_branch.repository_format = RepositoryFormat.BZR_KNIT_1
36+ with person_logged_in(db_branch.owner):
37+ db_branch.branch_format = BranchFormat.BZR_BRANCH_5
38+ db_branch.repository_format = RepositoryFormat.BZR_KNIT_1
39 return db_branch, tree
40
41
42@@ -307,62 +309,75 @@
43
44 class TestBranchJobViaCelery(TestCaseWithFactory):
45
46- layer = ZopelessAppServerLayer
47+ layer = CeleryJobLayer
48
49 def test_branchChanged_via_celery(self):
50 """Running a job via Celery succeeds and emits expected output."""
51 # Delay importing anything that uses Celery until RabbitMQLayer is
52 # running, so that config.rabbitmq.host is defined when
53 # lp.services.job.celeryconfig is loaded.
54- from celery.exceptions import TimeoutError
55 self.useFixture(FeatureFixture({
56 'jobs.celery.enabled_classes': 'BranchScanJob'}))
57- with celeryd('job') as proc:
58- self.useBzrBranches()
59- db_branch, bzr_tree = self.create_branch_and_tree()
60- bzr_tree.commit(
61- 'First commit', rev_id='rev1', committer='me@example.org')
62+ self.useBzrBranches()
63+ db_branch, bzr_tree = self.create_branch_and_tree()
64+ bzr_tree.commit(
65+ 'First commit', rev_id='rev1', committer='me@example.org')
66+ with person_logged_in(db_branch.owner):
67 db_branch.branchChanged(None, 'rev1', None, None, None)
68- with monitor_celery() as responses:
69- transaction.commit()
70- try:
71- responses[-1].wait(30)
72- except TimeoutError:
73- pass
74- self.assertIn(
75- 'Updating branch scanner status: 1 revs', proc.stderr.read())
76+ with block_on_job():
77+ transaction.commit()
78 self.assertEqual(db_branch.revision_count, 1)
79
80 def test_branchChanged_via_celery_no_enabled(self):
81- """Running a job via Celery succeeds and emits expected output."""
82+ """With no feature flag, no task is created."""
83 self.useBzrBranches()
84 db_branch, bzr_tree = self.create_branch_and_tree()
85 bzr_tree.commit(
86 'First commit', rev_id='rev1', committer='me@example.org')
87- db_branch.branchChanged(None, 'rev1', None, None, None)
88+ with person_logged_in(db_branch.owner):
89+ db_branch.branchChanged(None, 'rev1', None, None, None)
90 with monitor_celery() as responses:
91 transaction.commit()
92 self.assertEqual([], responses)
93
94+
95+class TestBranchWriteJobViaCelery(TestCaseWithFactory):
96+
97+ layer = CeleryBranchWriteJobLayer
98+
99 def test_destroySelf_via_celery(self):
100 """Calling destroySelf causes Celery to delete the branch."""
101- from celery.exceptions import TimeoutError
102 self.useFixture(FeatureFixture({
103 'jobs.celery.enabled_classes': 'ReclaimBranchSpaceJob'}))
104- with celeryd('branch_write_job'):
105- self.useBzrBranches()
106- db_branch, tree = self.create_branch_and_tree()
107- branch_path = get_real_branch_path(db_branch.id)
108- self.assertThat(branch_path, PathExists())
109+ self.useBzrBranches()
110+ db_branch, tree = self.create_branch_and_tree()
111+ branch_path = get_real_branch_path(db_branch.id)
112+ self.assertThat(branch_path, PathExists())
113+ with person_logged_in(db_branch.owner):
114 db_branch.destroySelf()
115- with monitor_celery() as responses:
116- transaction.commit()
117- try:
118- responses[-1].wait(30)
119- except TimeoutError:
120- pass
121+ with block_on_job():
122+ transaction.commit()
123 self.assertThat(branch_path, Not(PathExists()))
124
125+ def test_requestUpgradeUsesCelery(self):
126+ self.useFixture(FeatureFixture({
127+ 'jobs.celery.enabled_classes': 'BranchUpgradeJob'}))
128+ self.useBzrBranches()
129+ db_branch, tree = create_knit(self)
130+ self.assertEqual(
131+ tree.branch.repository._format.get_format_string(),
132+ 'Bazaar-NG Knit Repository Format 1')
133+
134+ with person_logged_in(db_branch.owner):
135+ db_branch.requestUpgrade(db_branch.owner)
136+ with block_on_job():
137+ transaction.commit()
138+ new_branch = Branch.open(tree.branch.base)
139+ self.assertEqual(
140+ new_branch.repository._format.get_format_string(),
141+ 'Bazaar repository format 2a (needs bzr 1.16 or later)\n')
142+ self.assertFalse(db_branch.needs_upgrading)
143+
144
145 class TestBranchRevisionMethods(TestCaseWithFactory):
146 """Test the branch methods for adding and removing branch revisions."""
147@@ -816,27 +831,6 @@
148 jobs,
149 [job, ])
150
151- def test_requestUpgradeUsesCelery(self):
152- self.useFixture(FeatureFixture({
153- 'jobs.celery.enabled_classes': 'BranchUpgradeJob'}))
154- cwd = os.getcwd()
155- self.useBzrBranches()
156- db_branch, tree = create_knit(self)
157- self.assertEqual(
158- tree.branch.repository._format.get_format_string(),
159- 'Bazaar-NG Knit Repository Format 1')
160-
161- db_branch.requestUpgrade(db_branch.owner)
162- with monitor_celery() as responses:
163- transaction.commit()
164- with celeryd('branch_write_job', cwd):
165- responses[-1].wait(30)
166- new_branch = Branch.open(tree.branch.base)
167- self.assertEqual(
168- new_branch.repository._format.get_format_string(),
169- 'Bazaar repository format 2a (needs bzr 1.16 or later)\n')
170- self.assertFalse(db_branch.needs_upgrading)
171-
172 def test_requestUpgrade_no_upgrade_needed(self):
173 # If a branch doesn't need to be upgraded, requestUpgrade raises an
174 # AlreadyLatestFormat.
175
176=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
177--- lib/lp/code/model/tests/test_branchjob.py 2012-04-10 20:24:43 +0000
178+++ lib/lp/code/model/tests/test_branchjob.py 2012-04-13 19:33:20 +0000
179@@ -74,8 +74,7 @@
180 from lp.services.job.model.job import Job
181 from lp.services.job.runner import JobRunner
182 from lp.services.job.tests import (
183- celeryd,
184- monitor_celery,
185+ block_on_job,
186 )
187 from lp.services.osutils import override_environ
188 from lp.services.webapp import canonical_url
189@@ -88,7 +87,7 @@
190 switch_dbuser,
191 )
192 from lp.testing.layers import (
193- AppServerLayer,
194+ CeleryJobLayer,
195 DatabaseFunctionalLayer,
196 LaunchpadZopelessLayer,
197 )
198@@ -1234,11 +1233,10 @@
199
200 class TestViaCelery(TestCaseWithFactory):
201
202- layer = AppServerLayer
203+ layer = CeleryJobLayer
204
205 def test_RosettaUploadJob(self):
206 """Ensure RosettaUploadJob can run under Celery."""
207- self.useContext(celeryd('job'))
208 self.useBzrBranches(direct_database=True)
209 self.useFixture(FeatureFixture({
210 'jobs.celery.enabled_classes': 'BranchScanJob RosettaUploadJob'
211@@ -1247,19 +1245,17 @@
212 self.createBzrBranch(db_branch)
213 commit = DirectBranchCommit(db_branch, no_race_check=True)
214 commit.writeFile('foo.pot', 'gibberish')
215- with monitor_celery() as responses:
216- with person_logged_in(db_branch.owner):
217+ with person_logged_in(db_branch.owner):
218+ # wait for branch scan
219+ with block_on_job():
220 commit.commit('message')
221 transaction.commit()
222- # Wait for branch scan to complete.
223- responses[0].wait(30)
224- series = self.factory.makeProductSeries(branch=db_branch)
225- RosettaUploadJob.create(
226- commit.db_branch, NULL_REVISION,
227- force_translations_upload=True)
228- transaction.commit()
229- # Wait for RosettaUploadJob to complete
230- responses[1].wait(30)
231+ series = self.factory.makeProductSeries(branch=db_branch)
232+ with block_on_job():
233+ RosettaUploadJob.create(
234+ commit.db_branch, NULL_REVISION,
235+ force_translations_upload=True)
236+ transaction.commit()
237 queue = getUtility(ITranslationImportQueue)
238 entries = list(queue.getAllEntries(target=series))
239 self.assertEqual(len(entries), 1)
240
241=== modified file 'lib/lp/code/model/tests/test_branchmergeproposaljobs.py'
242--- lib/lp/code/model/tests/test_branchmergeproposaljobs.py 2012-04-13 19:33:19 +0000
243+++ lib/lp/code/model/tests/test_branchmergeproposaljobs.py 2012-04-13 19:33:20 +0000
244@@ -60,8 +60,7 @@
245 from lp.services.job.model.job import Job
246 from lp.services.job.runner import JobRunner
247 from lp.services.job.tests import (
248- celeryd,
249- monitor_celery,
250+ block_on_job,
251 pop_remote_notifications,
252 )
253 from lp.services.osutils import override_environ
254@@ -72,7 +71,7 @@
255 )
256 from lp.testing.dbuser import dbuser
257 from lp.testing.layers import (
258- AppServerLayer,
259+ CeleryJobLayer,
260 LaunchpadZopelessLayer,
261 )
262 from lp.testing.mail_helpers import pop_notifications
263@@ -595,79 +594,67 @@
264
265 class TestViaCelery(TestCaseWithFactory):
266
267- layer = AppServerLayer
268+ layer = CeleryJobLayer
269
270 def test_MergeProposalNeedsReviewEmailJob(self):
271 """MergeProposalNeedsReviewEmailJob runs under Celery."""
272 self.useFixture(FeatureFixture(
273 {'jobs.celery.enabled_classes':
274 'MergeProposalNeedsReviewEmailJob'}))
275- self.useContext(celeryd('job'))
276 bmp = self.factory.makeBranchMergeProposal()
277- with monitor_celery() as responses:
278+ with block_on_job():
279 MergeProposalNeedsReviewEmailJob.create(bmp)
280 transaction.commit()
281- responses[0].wait(30)
282 self.assertEqual(2, len(pop_remote_notifications()))
283
284 def test_UpdatePreviewDiffJob(self):
285 """UpdatePreviewDiffJob runs under Celery."""
286- self.useContext(celeryd('job'))
287 self.useBzrBranches(direct_database=True)
288 bmp = create_example_merge(self)[0]
289 self.factory.makeRevisionsForBranch(bmp.source_branch, count=1)
290 self.useFixture(FeatureFixture(
291 {'jobs.celery.enabled_classes': 'UpdatePreviewDiffJob'}))
292- with monitor_celery() as responses:
293+ with block_on_job():
294 UpdatePreviewDiffJob.create(bmp)
295 transaction.commit()
296- responses[0].wait(30)
297 self.assertIsNot(None, bmp.preview_diff)
298
299 def test_CodeReviewCommentEmailJob(self):
300 """CodeReviewCommentEmailJob runs under Celery."""
301 comment = self.factory.makeCodeReviewComment()
302- self.useContext(celeryd('job'))
303 self.useFixture(FeatureFixture(
304 {'jobs.celery.enabled_classes': 'CodeReviewCommentEmailJob'}))
305- with monitor_celery() as responses:
306+ with block_on_job():
307 CodeReviewCommentEmailJob.create(comment)
308 transaction.commit()
309- responses[0].wait(30)
310 self.assertEqual(2, len(pop_remote_notifications()))
311
312 def test_ReviewRequestedEmailJob(self):
313 """ReviewRequestedEmailJob runs under Celery."""
314 request = self.factory.makeCodeReviewVoteReference()
315- self.useContext(celeryd('job'))
316 self.useFixture(FeatureFixture(
317 {'jobs.celery.enabled_classes': 'ReviewRequestedEmailJob'}))
318- with monitor_celery() as responses:
319+ with block_on_job():
320 ReviewRequestedEmailJob.create(request)
321 transaction.commit()
322- responses[0].wait(30)
323 self.assertEqual(1, len(pop_remote_notifications()))
324
325 def test_MergeProposalUpdatedEmailJob(self):
326 """MergeProposalUpdatedEmailJob runs under Celery."""
327 bmp = self.factory.makeBranchMergeProposal()
328- self.useContext(celeryd('job'))
329 self.useFixture(FeatureFixture(
330 {'jobs.celery.enabled_classes': 'MergeProposalUpdatedEmailJob'}))
331- with monitor_celery() as responses:
332+ with block_on_job():
333 MergeProposalUpdatedEmailJob.create(
334 bmp, 'change', bmp.registrant)
335 transaction.commit()
336- responses[0].wait(30)
337 self.assertEqual(2, len(pop_remote_notifications()))
338
339 def test_GenerateIncrementalDiffJob(self):
340 """GenerateIncrementalDiffJob runs under Celery."""
341- self.useContext(celeryd('job'))
342 self.useFixture(FeatureFixture(
343 {'jobs.celery.enabled_classes': 'GenerateIncrementalDiffJob'}))
344- with monitor_celery() as responses:
345+ with block_on_job():
346 job = make_runnable_incremental_diff_job(self)
347 transaction.commit()
348- responses[0].wait(30)
349 self.assertEqual(JobStatus.COMPLETED, job.status)
350
351=== modified file 'lib/lp/codehosting/scanner/tests/test_email.py'
352--- lib/lp/codehosting/scanner/tests/test_email.py 2012-04-13 19:33:19 +0000
353+++ lib/lp/codehosting/scanner/tests/test_email.py 2012-04-13 19:33:20 +0000
354@@ -30,15 +30,14 @@
355 from lp.services.job.runner import JobRunner
356 from lp.services.mail import stub
357 from lp.services.job.tests import (
358- celeryd,
359- monitor_celery,
360+ block_on_job,
361 pop_remote_notifications,
362 )
363 from lp.testing import TestCaseWithFactory
364 from lp.testing.dbuser import switch_dbuser
365 from lp.testing.layers import (
366+ CeleryJobLayer,
367 LaunchpadZopelessLayer,
368- ZopelessAppServerLayer,
369 )
370
371
372@@ -162,12 +161,11 @@
373
374 class TestViaCelery(TestCaseWithFactory):
375
376- layer = ZopelessAppServerLayer
377+ layer = CeleryJobLayer
378
379 def prepare(self, job_name):
380 self.useFixture(FeatureFixture(
381 {'jobs.celery.enabled_classes': job_name}))
382- self.useContext(celeryd('job'))
383 self.useBzrBranches(direct_database=True)
384 db_branch, tree = self.create_branch_and_tree()
385 add_subscriber(db_branch)
386@@ -179,9 +177,8 @@
387 def test_empty_branch(self):
388 """RevisionMailJob for empty branches runs via Celery."""
389 db_branch, tree = self.prepare('RevisionMailJob')
390- with monitor_celery() as responses:
391+ with block_on_job():
392 BzrSync(db_branch).syncBranchAndClose(tree.branch)
393- responses[-1].wait(30)
394 self.assertEqual(1, len(pop_remote_notifications()))
395
396 def test_uncommit_branch(self):
397@@ -189,13 +186,12 @@
398 db_branch, tree = self.prepare('RevisionMailJob')
399 tree.commit('message')
400 bzr_sync = BzrSync(db_branch)
401- with monitor_celery() as responses:
402- bzr_sync.syncBranchAndClose(tree.branch)
403- responses[0].wait(30)
404- pop_remote_notifications()
405- uncommit(tree.branch)
406- bzr_sync.syncBranchAndClose(tree.branch)
407- responses[1].wait(30)
408+ with block_on_job():
409+ bzr_sync.syncBranchAndClose(tree.branch)
410+ pop_remote_notifications()
411+ uncommit(tree.branch)
412+ with block_on_job():
413+ bzr_sync.syncBranchAndClose(tree.branch)
414 self.assertEqual(1, len(pop_remote_notifications()))
415
416 def test_revisions_added(self):
417@@ -206,10 +202,9 @@
418 bzr_sync.syncBranchAndClose(tree.branch)
419 pop_remote_notifications()
420 tree.commit('message2')
421- with monitor_celery() as responses:
422+ with block_on_job():
423 bzr_sync.syncBranchAndClose(tree.branch)
424- responses[-1].wait(30)
425- self.assertEqual(1, len(pop_remote_notifications()))
426+ self.assertEqual(1, len(pop_remote_notifications()))
427
428
429 class TestScanBranches(TestCaseWithFactory):
430
431=== modified file 'lib/lp/services/job/model/job.py'
432--- lib/lp/services/job/model/job.py 2012-04-13 19:33:19 +0000
433+++ lib/lp/services/job/model/job.py 2012-04-13 19:33:20 +0000
434@@ -277,15 +277,20 @@
435
436 return base_job.makeDerived(), store
437
438+ @staticmethod
439+ def clearStore(store):
440+ transaction.abort()
441+ getUtility(IZStorm).remove(store)
442+ store.close()
443+
444 @classmethod
445 def switchDBUser(cls, job_id):
446 """Switch to the DB user associated with this Job ID."""
447+ cls.clearStore(IStore(Job))
448 derived, store = cls.getDerived(job_id)
449 dbconfig.override(
450 dbuser=derived.config.dbuser, isolation_level='read_committed')
451- transaction.abort()
452- getUtility(IZStorm).remove(store)
453- store.close()
454+ cls.clearStore(store)
455
456 @classmethod
457 def get(cls, job_id):
458
459=== modified file 'lib/lp/services/job/tests/__init__.py'
460--- lib/lp/services/job/tests/__init__.py 2012-04-13 19:33:19 +0000
461+++ lib/lp/services/job/tests/__init__.py 2012-04-13 19:33:20 +0000
462@@ -4,6 +4,7 @@
463 __metaclass__ = type
464
465 __all__ = [
466+ 'block_on_job',
467 'celeryd',
468 'monitor_celery',
469 'pop_remote_notifications',
470@@ -15,7 +16,6 @@
471 from lp.services.job.runner import BaseRunnableJob
472
473
474-@contextmanager
475 def celeryd(queue, cwd=None):
476 """Return a ContextManager for a celeryd instance.
477
478@@ -35,9 +35,7 @@
479 '--queues', queue,
480 '--include', 'lp.services.job.tests.celery_helpers',
481 )
482- with running('bin/celeryd', cmd_args, cwd=cwd) as proc:
483- # Wait for celeryd startup to complete.
484- yield proc
485+ return running('bin/celeryd', cmd_args, cwd=cwd)
486
487
488 @contextmanager
489@@ -52,6 +50,13 @@
490 BaseRunnableJob.celery_responses = old_responses
491
492
493+@contextmanager
494+def block_on_job():
495+ with monitor_celery() as responses:
496+ yield
497+ responses[-1].wait(30)
498+
499+
500 def pop_remote_notifications():
501 """Pop the notifications from a celeryd worker."""
502 from lp.services.job.tests.celery_helpers import pop_notifications
503
504=== modified file 'lib/lp/testing/layers.py'
505--- lib/lp/testing/layers.py 2012-03-22 12:13:15 +0000
506+++ lib/lp/testing/layers.py 2012-04-13 19:33:20 +0000
507@@ -111,6 +111,7 @@
508 ConfigUseFixture,
509 )
510 from lp.services.database.sqlbase import session_store
511+from lp.services.job.tests import celeryd
512 from lp.services.googlesearch.tests.googleserviceharness import (
513 GoogleServiceTestSetup,
514 )
515@@ -1859,6 +1860,42 @@
516 LayerProcessController.postTestInvariants()
517
518
519+class CeleryJobLayer(AppServerLayer):
520+ """Layer for tests that run jobs via Celery."""
521+
522+ celeryd = None
523+
524+ @classmethod
525+ @profiled
526+ def setUp(cls):
527+ cls.celeryd = celeryd('job')
528+ cls.celeryd.__enter__()
529+
530+ @classmethod
531+ @profiled
532+ def tearDown(cls):
533+ cls.celeryd.__exit__(None, None, None)
534+ cls.celeryd = None
535+
536+
537+class CeleryBranchWriteJobLayer(AppServerLayer):
538+ """Layer for tests that run jobs which write to branches via Celery."""
539+
540+ celeryd = None
541+
542+ @classmethod
543+ @profiled
544+ def setUp(cls):
545+ cls.celeryd = celeryd('branch_write_job')
546+ cls.celeryd.__enter__()
547+
548+ @classmethod
549+ @profiled
550+ def tearDown(cls):
551+ cls.celeryd.__exit__(None, None, None)
552+ cls.celeryd = None
553+
554+
555 class ZopelessAppServerLayer(LaunchpadZopelessLayer):
556 """Layer for tests that run in the zopeless environment with an appserver.
557 """