Merge lp:~jtv/launchpad/bug-507678 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Eleanor Berger
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~jtv/launchpad/bug-507678
Merge into: lp:launchpad
Diff against target: 715 lines (+431/-98)
9 files modified
lib/devscripts/ec2test/tests/test_ec2instance.py (+17/-59)
lib/lp/buildmaster/model/buildfarmjobbehavior.py (+15/-4)
lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py (+41/-0)
lib/lp/testing/factory.py (+15/-0)
lib/lp/testing/fakemethod.py (+51/-0)
lib/lp/testing/tests/test_fakemethod.py (+85/-0)
lib/lp/translations/model/translationtemplatesbuildbehavior.py (+50/-1)
lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py (+157/-0)
lib/lp/translations/tests/test_translationtemplatesbuildjob.py (+0/-34)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-507678
Reviewer Review Type Date Requested Status
Eleanor Berger (community) code Approve
Review via email: mp+19335@code.launchpad.net

Commit message

Retrieve results from translation-templates builds on the build farm.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (3.8 KiB)

= Bug 507678 =

This is a re-proposal; the original proposal was identical but accidentally targeted at db-devel.

Continuing work on making the buildfarm generate translation templates from source branches. Here I'm jumping through the hoops to let the IBuildFarmJobBehavior implementation that we have for this kind of build-farm job retrieve a tarball produced by the slave.

The default BuildFarmJobBehaviorBase code for this assumed that there's an IBuildBase object (basically a generalization of soyuz's Build) attached to a build-farm job. It didn't seem worth it to implement IBuildBase just for this (I tried), I now override the only BuildFarmJobBehaviorBase method that requires it. I had to extend the infrastructure a bit along the way.

An important thing to note is that these pieces are still coming together; they're not actually a functional whole yet. But with the ongoing work on build-farm generalization, it's important to get and keep them integrated before bit-rot sets in.

Going through the diff step by step:

lib/lp/buildmaster/model/buildfarmjobbehavior.py

I extracted a method for reading and checking build status from a slave status dict. That makes it easy for my behavior class to reuse it.

lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py

Tests the method I abstracted.

lib/lp/testing/factory.py

New factory method for the type of build-farm job that generates translation templates.

lib/lp/testing/fakemethod.py

Provides a generic stub for all sorts of functions and methods. I got tired of writing one-liners, injecting ad-hoc status variables into objects, and building one-off mock classes. Need to override a method in an object to return a dummy value or fail in a particular way? Just assign it to a FakeMethod.

Pylint had a truly silly complaint about this file, so I silenced it. There's a comment to the effect.

lib/lp/testing/tests/test_fakemethod.py

Unit-tests FakeMethod.

lib/lp/translations/model/translationtemplatesbuildbehavior.py

Grew some methods for retrieving a tarball of results from the build-farm slave. As yet we do not have the code in place that will generate the real templates.

lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py

Tests the extensions to the behavior class. The starting point for this was an existing unit test that I ripped out of the next file in the diff.

Interaction with slaves makes testing this stuff very hard. About the best I can do is mock up all the infrastructure, unit-test my part, and hope the other classes do their job. It's not perfect, but it's a lot more automated testing than a lot of the other buildfarm work gets.

For this test I had to strike a balance black-box and white-box approaches: obviously the test needs a lot of inside knowledge about the classes it's dealing with, but I didn't make it check the fidgety details of what values are being passed around where. Those could change easily, as work on the build farm is still ongoing. Moreover, I have no real requirements for them other than "they should work."

lib/lp/translations/tests/test_translationtemplatesbuildjob.py

Lost a test case to the file above. What there was got...

Read more...

Revision history for this message
Eleanor Berger (intellectronica) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/devscripts/ec2test/tests/test_ec2instance.py'
2--- lib/devscripts/ec2test/tests/test_ec2instance.py 2009-11-26 21:32:25 +0000
3+++ lib/devscripts/ec2test/tests/test_ec2instance.py 2010-02-16 10:34:19 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8 # pylint: disable-msg=E0702
9
10@@ -8,57 +8,15 @@
11
12 from unittest import TestCase, TestLoader
13
14+from lp.testing.fakemethod import FakeMethod
15+
16 from devscripts.ec2test.instance import EC2Instance
17
18
19-class Stub:
20- """Generic recipient of invocations.
21-
22- Use this to:
23- - Stub methods that should do nothing.
24- - Inject failures into methods.
25- - Record whether a method is being called.
26- """
27- # XXX JeroenVermeulen 2009-11-26: This probably exists somewhere
28- # already. Or if not, maybe it should. But with a name that won't
29- # turn Stuart Bishop's IRC client into a disco simulator.
30-
31- call_count = 0
32-
33- def __init__(self, return_value=None, simulated_failure=None):
34- """Define what to do when this stub gets invoked.
35-
36- :param return_value: Something to return from the invocation.
37- :parma simulated_failure: Something to raise in the invocation.
38- """
39- assert return_value is None or simulated_failure is None, (
40- "Stub can raise an exception or return a value, but not both.")
41- self.return_value = return_value
42- self.simulated_failure = simulated_failure
43-
44- def __call__(self, *args, **kwargs):
45- """Catch a call.
46-
47- Records the fact that an invocation was made in
48- `call_count`.
49-
50- If you passed a `simulated_failure` to the constructor, that
51- object is raised.
52-
53- :return: The `return_value` you passed to the constructor.
54- """
55- self.call_count += 1
56-
57- if self.simulated_failure is not None:
58- raise self.simulated_failure
59-
60- return self.return_value
61-
62-
63 class FakeAccount:
64 """Helper for setting up an `EC2Instance` without EC2."""
65- acquire_private_key = Stub()
66- acquire_security_group = Stub()
67+ acquire_private_key = FakeMethod()
68+ acquire_security_group = FakeMethod()
69
70
71 class FakeOutput:
72@@ -72,8 +30,8 @@
73 state = 'running'
74 public_dns_name = 'fake-instance'
75
76- update = Stub()
77- stop = Stub()
78+ update = FakeMethod()
79+ stop = FakeMethod()
80 get_console_output = FakeOutput
81
82
83@@ -85,7 +43,7 @@
84
85 class FakeImage:
86 """Helper for setting up an `EC2Instance` without EC2."""
87- run = Stub(return_value=FakeReservation())
88+ run = FakeMethod(result=FakeReservation())
89
90
91 class FakeFailure(Exception):
92@@ -98,8 +56,8 @@
93 def _makeInstance(self):
94 """Set up an `EC2Instance`, with stubbing where needed.
95
96- `EC2Instance.shutdown` is replaced with a `Stub`, so check its
97- call_count to see whether it's been invoked.
98+ `EC2Instance.shutdown` is replaced with a `FakeMethod`, so check
99+ its call_count to see whether it's been invoked.
100 """
101 session_name = None
102 image = FakeImage()
103@@ -114,16 +72,16 @@
104 session_name, image, instance_type, demo_networks, account,
105 from_scratch, user_key, login)
106
107- instance.shutdown = Stub()
108- instance._report_traceback = Stub()
109- instance.log = Stub()
110+ instance.shutdown = FakeMethod()
111+ instance._report_traceback = FakeMethod()
112+ instance.log = FakeMethod()
113
114 return instance
115
116 def _runInstance(self, instance, runnee=None, headless=False):
117 """Set up and run an `EC2Instance` (but without EC2)."""
118 if runnee is None:
119- runnee = Stub()
120+ runnee = FakeMethod()
121
122 instance.set_up_and_run(False, not headless, runnee)
123
124@@ -133,7 +91,7 @@
125 # Not a very useful test, except it establishes the basic
126 # assumptions for the other tests.
127 instance = self._makeInstance()
128- runnee = Stub()
129+ runnee = FakeMethod()
130
131 self.assertEqual(0, runnee.call_count)
132 self.assertEqual(0, instance.shutdown.call_count)
133@@ -164,7 +122,7 @@
134 # If the test runner barfs, the instance swallows the exception
135 # and shuts down.
136 instance = self._makeInstance()
137- runnee = Stub(simulated_failure=FakeFailure("Headful barfage."))
138+ runnee = FakeMethod(failure=FakeFailure("Headful barfage."))
139
140 self._runInstance(instance, runnee=runnee, headless=False)
141
142@@ -174,7 +132,7 @@
143 # If the instance's test runner fails to set up for a headless
144 # run, the instance swallows the exception and shuts down.
145 instance = self._makeInstance()
146- runnee = Stub(simulated_failure=FakeFailure("Headless boom."))
147+ runnee = FakeMethod(failure=FakeFailure("Headless boom."))
148
149 self._runInstance(instance, runnee=runnee, headless=True)
150
151
152=== modified file 'lib/lp/buildmaster/model/buildfarmjobbehavior.py'
153--- lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-01-29 17:15:31 +0000
154+++ lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-02-16 10:34:19 +0000
155@@ -164,6 +164,20 @@
156 queueItem.job.fail()
157 queueItem.specific_job.jobAborted()
158
159+ def extractBuildStatus(self, slave_status):
160+ """Read build status name.
161+
162+ :param slave_status: build status dict as passed to the
163+ updateBuild_* methods.
164+ :return: the unqualified status name, e.g. "OK".
165+ """
166+ status_string = slave_status['build_status']
167+ lead_string = 'BuildStatus.'
168+ assert status_string.startswith(lead_string), (
169+ "Malformed status string: '%s'" % status_string)
170+
171+ return status_string[len(lead_string):]
172+
173 def updateBuild_WAITING(self, queueItem, slave_status, logtail, logger):
174 """Perform the actions needed for a slave in a WAITING state
175
176@@ -178,13 +192,10 @@
177 the uploader for processing.
178 """
179 librarian = getUtility(ILibrarianClient)
180- build_status = slave_status['build_status']
181+ build_status = self.extractBuildStatus(slave_status)
182
183 # XXX: dsilvers 2005-03-02: Confirm the builder has the right build?
184- assert build_status.startswith('BuildStatus.'), (
185- 'Malformed status string: %s' % build_status)
186
187- build_status = build_status[len('BuildStatus.'):]
188 queueItem.specific_job.build.handleStatus(
189 build_status, librarian, slave_status)
190
191
192=== added file 'lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py'
193--- lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 1970-01-01 00:00:00 +0000
194+++ lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2010-02-16 10:34:19 +0000
195@@ -0,0 +1,41 @@
196+# Copyright 2010 Canonical Ltd. This software is licensed under the
197+# GNU Affero General Public License version 3 (see the file LICENSE).
198+
199+"""Unit tests for BuildFarmJobBehaviorBase."""
200+
201+from unittest import TestCase, TestLoader
202+
203+from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase
204+from lp.soyuz.interfaces.build import BuildStatus
205+
206+
207+class FakeBuildFarmJob:
208+ """Dummy BuildFarmJob."""
209+ pass
210+
211+
212+class TestBuildFarmJobBehaviorBase(TestCase):
213+ """Test very small, basic bits of BuildFarmJobBehaviorBase."""
214+
215+ def setUp(self):
216+ self.behavior = BuildFarmJobBehaviorBase(FakeBuildFarmJob())
217+
218+ def test_extractBuildStatus_baseline(self):
219+ # extractBuildStatus picks the name of the build status out of a
220+ # dict describing the slave's status.
221+ slave_status = {'build_status': 'BuildStatus.BUILDING'}
222+ self.assertEqual(
223+ BuildStatus.BUILDING.name,
224+ self.behavior.extractBuildStatus(slave_status))
225+
226+ def test_extractBuildStatus_malformed(self):
227+ # extractBuildStatus errors out when the status string is not
228+ # of the form it expects.
229+ slave_status = {'build_status': 'BUILDING'}
230+ self.assertRaises(
231+ AssertionError,
232+ self.behavior.extractBuildStatus, slave_status)
233+
234+
235+def test_suite():
236+ return TestLoader().loadTestsFromName(__name__)
237
238=== modified file 'lib/lp/testing/factory.py'
239--- lib/lp/testing/factory.py 2010-02-10 23:14:56 +0000
240+++ lib/lp/testing/factory.py 2010-02-16 10:34:19 +0000
241@@ -132,6 +132,9 @@
242 from lp.soyuz.interfaces.packageset import IPackagesetSet
243 from lp.soyuz.model.buildqueue import BuildQueue
244 from lp.testing import run_with_login, time_counter
245+from lp.translations.interfaces.translationtemplatesbuildjob import (
246+ ITranslationTemplatesBuildJobSource)
247+
248
249 SPACE = ' '
250
251@@ -1681,6 +1684,18 @@
252 store.add(bq)
253 return bq
254
255+ def makeTranslationTemplatesBuildJob(self, branch=None):
256+ """Make a new `TranslationTemplatesBuildJob`.
257+
258+ :param branch: The branch that the job should be for. If none
259+ is given, one will be created.
260+ """
261+ if branch is None:
262+ branch = self.makeBranch()
263+
264+ jobset = getUtility(ITranslationTemplatesBuildJobSource)
265+ return jobset.create(branch)
266+
267 def makePOTemplate(self, productseries=None, distroseries=None,
268 sourcepackagename=None, owner=None, name=None,
269 translation_domain=None, path=None):
270
271=== added file 'lib/lp/testing/fakemethod.py'
272--- lib/lp/testing/fakemethod.py 1970-01-01 00:00:00 +0000
273+++ lib/lp/testing/fakemethod.py 2010-02-16 10:34:19 +0000
274@@ -0,0 +1,51 @@
275+# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
276+# GNU Affero General Public License version 3 (see the file LICENSE).
277+
278+# pylint: disable-msg=E0702
279+
280+
281+__metaclass__ = type
282+__all__ = [
283+ 'FakeMethod',
284+ ]
285+
286+
287+class FakeMethod:
288+ """Catch any function or method call, and record the fact.
289+
290+ Use this for easy stubbing. The call operator can return a fixed
291+ value, or raise a fixed exception object.
292+
293+ This is useful when unit-testing code that does things you don't
294+ want to integration-test, e.g. because it wants to talk to remote
295+ systems.
296+ """
297+
298+ # How many times has this fake method been called?
299+ call_count = 0
300+
301+ def __init__(self, result=None, failure=None):
302+ """Set up a fake function or method.
303+
304+ :param result: Value to return.
305+ :param failure: Exception to raise.
306+ """
307+ self.result = result
308+ self.failure = failure
309+
310+ def __call__(self, *args, **kwargs):
311+ """Catch an invocation to the method. Increment `call_count`.
312+
313+ Accepts any and all parameters. Raises the failure passed to
314+ the constructor, if any; otherwise, returns the result value
315+ passed to the constructor.
316+ """
317+ self.call_count += 1
318+
319+ if self.failure is None:
320+ return self.result
321+ else:
322+ # pylint thinks this raises None, which is clearly not
323+ # possible. That's why this test disables pylint message
324+ # E0702.
325+ raise self.failure
326
327=== added file 'lib/lp/testing/tests/test_fakemethod.py'
328--- lib/lp/testing/tests/test_fakemethod.py 1970-01-01 00:00:00 +0000
329+++ lib/lp/testing/tests/test_fakemethod.py 2010-02-16 10:34:19 +0000
330@@ -0,0 +1,85 @@
331+# Copyright 2010 Canonical Ltd. This software is licensed under the
332+# GNU Affero General Public License version 3 (see the file LICENSE).
333+
334+from unittest import TestCase, TestLoader
335+
336+from lp.testing.fakemethod import FakeMethod
337+
338+
339+class RealActualClass:
340+ """A class that's hard to test."""
341+
342+ doing_impossible_stuff = False
343+ doing_possible_stuff = False
344+
345+ def impossibleMethod(self):
346+ """A method that you can't afford to invoke in your test.
347+
348+ This is the method you're going to want to avoid calling. The
349+ way to do that is to replace this method with a fake method.
350+ """
351+ self.doing_impossible_stuff = True
352+ raise AssertionError("Trying to do impossible stuff.")
353+
354+ def testableMethod(self):
355+ """This part of the class logic that you do want to exercise."""
356+ self.doing_possible_stuff = True
357+
358+ def doComplicatedThings(self, argument):
359+ """This is the top-level method you want to test.
360+
361+ Unfortunately this invokes impossibleMethod, making it hard.
362+ """
363+ self.impossibleMethod()
364+ self.testableMethod()
365+ return argument
366+
367+
368+class CustomException(Exception):
369+ """Some specific error that you want raised."""
370+
371+
372+class TestFakeMethod(TestCase):
373+ def test_fakeMethod(self):
374+ # A class that you're testing can continue normally despite some
375+ # of its methods being stubbed.
376+ thing = RealActualClass()
377+ thing.impossibleMethod = FakeMethod()
378+
379+ result = thing.doComplicatedThings(99)
380+
381+ self.assertEqual(99, result)
382+ self.assertFalse(thing.doing_impossible_stuff)
383+ self.assertTrue(thing.doing_possible_stuff)
384+
385+ def test_raiseFailure(self):
386+ # A FakeMethod can raise an exception you specify.
387+ ouch = CustomException("Ouch!")
388+ func = FakeMethod(failure=ouch)
389+ self.assertRaises(CustomException, func)
390+
391+ def test_returnResult(self):
392+ # A FakeMethod can return a value you specify.
393+ value = "Fixed return value."
394+ func = FakeMethod(result=value)
395+ self.assertEqual(value, func())
396+
397+ def test_countCalls(self):
398+ # A FakeMethod counts the number of times it's been invoked.
399+ func = FakeMethod()
400+ for count in xrange(3):
401+ self.assertEqual(count, func.call_count)
402+ func()
403+ self.assertEqual(count + 1, func.call_count)
404+
405+ def test_takeArguments(self):
406+ # A FakeMethod invocation accepts any arguments it gets.
407+ func = FakeMethod()
408+ func()
409+ func(1)
410+ func(2, kwarg=3)
411+ self.assertEqual(3, func.call_count)
412+
413+
414+def test_suite():
415+ return TestLoader().loadTestsFromName(__name__)
416
417=== modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py'
418--- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-01-15 01:20:20 +0000
419+++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-02-16 10:34:19 +0000
420@@ -13,6 +13,7 @@
421
422 from zope.component import getUtility
423 from zope.interface import implements
424+from zope.security.proxy import removeSecurityProxy
425
426 from canonical.launchpad.interfaces import ILaunchpadCelebrities
427
428@@ -29,6 +30,9 @@
429 # Identify the type of job to the slave.
430 build_type = 'translation-templates'
431
432+ # Filename for the tarball of templates that the slave builds.
433+ templates_tarball_path = 'translation-templates.tar.gz'
434+
435 def dispatchBuildToSlave(self, build_queue_item, logger):
436 """See `IBuildFarmJobBehavior`."""
437 chroot = self._getChroot()
438@@ -39,7 +43,7 @@
439 args = { 'branch_url': self.buildfarmjob.branch.url }
440 filemap = {}
441
442- status, info = self._builder.slave.build(
443+ self._builder.slave.build(
444 buildid, self.build_type, chroot_sha1, filemap, args)
445
446 def _getChroot(self):
447@@ -49,3 +53,48 @@
448 def logStartBuild(self, logger):
449 """See `IBuildFarmJobBehavior`."""
450 logger.info("Starting templates build.")
451+
452+ def _readTarball(self, buildqueue, filemap, logger):
453+ """Read tarball with generated translation templates from slave."""
454+ slave_filename = filemap.get(self.templates_tarball_path)
455+ if slave_filename is None:
456+ logger.error("Did not find templates tarball in slave output.")
457+ return None
458+
459+ slave = removeSecurityProxy(buildqueue.builder.slave)
460+ return slave.getFile(slave_filename).read()
461+
462+ def _uploadTarball(self, branch, tarball, logger):
463+ """Upload tarball to productseries that want it."""
464+ # XXX JeroenVermeulen 2010-01-28 bug=507680: Find productseries
465+ # that want these templates, and upload to there.
466+
467+ def updateBuild_WAITING(self, queue_item, slave_status, logtail, logger):
468+ """Deal with a finished ("WAITING" state, perversely) build job.
469+
470+ Retrieves tarball and logs from the slave, then cleans up the
471+ slave so it's ready for a next job and destroys the queue item.
472+
473+ If this fails for whatever unforeseen reason, a future run will
474+ retry it.
475+ """
476+ build_status = self.extractBuildStatus(slave_status)
477+ build_id = slave_status['build_id']
478+ filemap = slave_status['filemap']
479+
480+ logger.debug("Templates generation job %s finished with status %s" % (
481+ build_id, build_status))
482+
483+ if build_status == 'OK':
484+ logger.debug("Processing templates build %s" % build_id)
485+ tarball = self._readTarball(queue_item, filemap, logger)
486+ if tarball is None:
487+ logger.error("Successful build %s produced no tarball." % (
488+ build_id))
489+ else:
490+ logger.debug("Uploading translation templates tarball.")
491+ self._uploadTarball(tarball, logger)
492+ logger.debug("Upload complete.")
493+
494+ queue_item.builder.cleanSlave()
495+ queue_item.destroySelf()
496
497=== added file 'lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py'
498--- lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 1970-01-01 00:00:00 +0000
499+++ lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2010-02-16 10:34:19 +0000
500@@ -0,0 +1,157 @@
501+# Copyright 2010 Canonical Ltd. This software is licensed under the
502+# GNU Affero General Public License version 3 (see the file LICENSE).
503+
504+"""Unit tests for TranslationTemplatesBuildBehavior."""
505+
506+import logging
507+from StringIO import StringIO
508+from unittest import TestLoader
509+
510+from zope.component import getUtility
511+from zope.security.proxy import removeSecurityProxy
512+
513+from canonical.testing import ZopelessDatabaseLayer
514+
515+from canonical.launchpad.interfaces import ILaunchpadCelebrities
516+from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
517+from lp.buildmaster.interfaces.buildfarmjobbehavior import (
518+ IBuildFarmJobBehavior)
519+from lp.soyuz.interfaces.build import BuildStatus
520+from lp.soyuz.interfaces.buildqueue import IBuildQueueSet
521+from lp.testing import TestCaseWithFactory
522+from lp.testing.fakemethod import FakeMethod
523+
524+
525+class FakeChrootContent:
526+ """Pretend chroot contents."""
527+ sha1 = "shasha"
528+
529+
530+class FakeChroot:
531+ """Pretend chroot."""
532+ def __init__(self, *args, **kwargs):
533+ """Constructor acts as a fake _getChroot."""
534+ self.content = FakeChrootContent()
535+
536+
537+class FakeSlave:
538+ """Pretend build slave."""
539+ def __init__(self, builderstatus):
540+ self.build_started = False
541+ self.status = {
542+ 'build_status': 'BuildStatus.%s' % builderstatus.name,
543+ }
544+
545+ self.cacheFile = FakeMethod()
546+ self.getFile = FakeMethod(result=StringIO("File from the slave."))
547+
548+ def build(self, buildid, build_type, chroot_sha1, filemap, args):
549+ """Pretend to start a build."""
550+ self.status['build_id'] = buildid
551+ self.status['filemap'] = filemap
552+ self.build_started = True
553+
554+
555+class FakeBuilder:
556+ """Pretend `Builder`."""
557+ def __init__(self, slave):
558+ self.slave = slave
559+ self.cleanSlave = FakeMethod()
560+
561+ def slaveStatus(self):
562+ return self.slave.status
563+
564+
565+class FakeBuildQueue:
566+ """Pretend `BuildQueue`."""
567+ def __init__(self, behavior):
568+ """Pretend to be a BuildQueue item for the given build behavior.
569+
570+ Copies its builder from the behavior object.
571+ """
572+ self.builder = behavior._builder
573+ self.destroySelf = FakeMethod()
574+
575+
576+class TestTranslationTemplatesBuildBehavior(TestCaseWithFactory):
577+ """Test `TranslationTemplatesBuildBehavior`."""
578+
579+ layer = ZopelessDatabaseLayer
580+
581+ def _makeBehavior(self):
582+ """Create a TranslationTemplatesBuildBehavior.
583+
584+ Anything that might communicate with build slaves and such
585+ (which we can't really do here) is mocked up.
586+ """
587+ specific_job = self.factory.makeTranslationTemplatesBuildJob()
588+ behavior = IBuildFarmJobBehavior(specific_job)
589+ slave = FakeSlave(BuildStatus.NEEDSBUILD)
590+ behavior._builder = FakeBuilder(slave)
591+ return behavior
592+
593+ def _getBuildQueueItem(self, behavior):
594+ """Get `BuildQueue` for an `IBuildFarmJobBehavior`."""
595+ job = removeSecurityProxy(behavior.buildfarmjob.job)
596+ return getUtility(IBuildQueueSet).getByJob(job.id)
597+
598+ def test_dispatchBuildToSlave(self):
599+ behavior = self._makeBehavior()
600+ behavior._getChroot = FakeChroot
601+ buildqueue_item = self._getBuildQueueItem(behavior)
602+
603+ behavior.dispatchBuildToSlave(buildqueue_item, logging)
604+
605+ self.assertTrue(behavior._builder.slave.build_started)
606+
607+ def test_getChroot(self):
608+ # _getChroot produces the current chroot for the current Ubuntu
609+ # release, on the nominated architecture for
610+ # architecture-independent builds.
611+ ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
612+ current_ubuntu = ubuntu.currentseries
613+ distroarchseries = current_ubuntu.nominatedarchindep
614+
615+ # Set an arbitrary chroot file.
616+ fake_chroot_file = getUtility(ILibraryFileAliasSet)[1]
617+ distroarchseries.addOrUpdateChroot(fake_chroot_file)
618+
619+ behavior = self._makeBehavior()
620+ chroot = behavior._getChroot()
621+
622+ self.assertNotEqual(None, chroot)
623+ self.assertEqual(fake_chroot_file, chroot)
624+
625+ def test_readTarball(self):
626+ behavior = self._makeBehavior()
627+ buildqueue = FakeBuildQueue(behavior)
628+ path = behavior.templates_tarball_path
629+ self.assertEqual(
630+ "File from the slave.",
631+ behavior._readTarball(buildqueue, {path: path}, logging))
632+
633+ def test_updateBuild_WAITING(self):
634+ behavior = self._makeBehavior()
635+ behavior._getChroot = FakeChroot
636+ behavior._uploadTarball = FakeMethod()
637+ queue_item = FakeBuildQueue(behavior)
638+ slave_status = behavior._builder.slave.status
639+ builder = behavior._builder
640+ slave = builder.slave
641+
642+ behavior.dispatchBuildToSlave(queue_item, logging)
643+
644+ self.assertEqual(0, queue_item.destroySelf.call_count)
645+ self.assertEqual(0, builder.cleanSlave.call_count)
646+ self.assertEqual(0, behavior._uploadTarball.call_count)
647+
648+ behavior.updateBuild_WAITING(queue_item, slave_status, None, logging)
649+
650+ self.assertEqual(1, queue_item.destroySelf.call_count)
651+ self.assertEqual(1, builder.cleanSlave.call_count)
652+ self.assertEqual(0, behavior._uploadTarball.call_count)
653+
654+
655+def test_suite():
656+ return TestLoader().loadTestsFromName(__name__)
657+
658
659=== modified file 'lib/lp/translations/tests/test_translationtemplatesbuildjob.py'
660--- lib/lp/translations/tests/test_translationtemplatesbuildjob.py 2010-01-15 01:20:20 +0000
661+++ lib/lp/translations/tests/test_translationtemplatesbuildjob.py 2010-02-16 10:34:19 +0000
662@@ -8,8 +8,6 @@
663 from zope.component import getUtility
664 from zope.security.proxy import removeSecurityProxy
665
666-from canonical.launchpad.interfaces import (
667- ILaunchpadCelebrities, ILibraryFileAliasSet)
668 from canonical.launchpad.webapp.testing import verifyObject
669 from canonical.testing import ZopelessDatabaseLayer
670
671@@ -17,8 +15,6 @@
672
673 from lp.buildmaster.interfaces.buildfarmjob import (
674 IBuildFarmJob, ISpecificBuildFarmJobClass)
675-from lp.buildmaster.interfaces.buildfarmjobbehavior import (
676- IBuildFarmJobBehavior)
677 from lp.code.interfaces.branchjob import IBranchJob
678 from lp.services.job.model.job import Job
679 from lp.soyuz.interfaces.buildqueue import IBuildQueueSet
680@@ -102,35 +98,5 @@
681 self.assertEqual(1000, self.specific_job.score())
682
683
684-class TestTranslationTemplatesBuildBehavior(TestCaseWithFactory):
685- """Test `TranslationTemplatesBuildBehavior`."""
686-
687- layer = ZopelessDatabaseLayer
688-
689- def setUp(self):
690- super(TestTranslationTemplatesBuildBehavior, self).setUp()
691- self.jobset = getUtility(ITranslationTemplatesBuildJobSource)
692- self.branch = self.factory.makeBranch()
693- self.specific_job = self.jobset.create(self.branch)
694- self.behavior = IBuildFarmJobBehavior(self.specific_job)
695-
696- def test_getChroot(self):
697- # _getChroot produces the current chroot for the current Ubuntu
698- # release, on the nominated architecture for
699- # architecture-independent builds.
700- ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
701- current_ubuntu = ubuntu.currentseries
702- distroarchseries = current_ubuntu.nominatedarchindep
703-
704- # Set an arbitrary chroot file.
705- fake_chroot_file = getUtility(ILibraryFileAliasSet)[1]
706- distroarchseries.addOrUpdateChroot(fake_chroot_file)
707-
708- chroot = self.behavior._getChroot()
709-
710- self.assertNotEqual(None, chroot)
711- self.assertEqual(fake_chroot_file, chroot)
712-
713-
714 def test_suite():
715 return TestLoader().loadTestsFromName(__name__)