Merge lp:~jtv/launchpad/bug-499404 into lp:launchpad
- bug-499404
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~jtv/launchpad/bug-499404 |
Merge into: | lp:launchpad |
Diff against target: |
444 lines (+374/-6) 7 files modified
lib/lp/buildmaster/interfaces/buildfarmjob.py (+6/-6) lib/lp/code/model/branchjob.py (+6/-0) lib/lp/translations/configure.zcml (+29/-0) lib/lp/translations/interfaces/translationtemplatesbuildjob.py (+37/-0) lib/lp/translations/model/translationtemplatesbuildbehavior.py (+79/-0) lib/lp/translations/model/translationtemplatesbuildjob.py (+85/-0) lib/lp/translations/tests/test_translationtemplatesbuildjob.py (+132/-0) |
To merge this branch: | bzr merge lp:~jtv/launchpad/bug-499404 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | code | Approve | |
Review via email: mp+17120@code.launchpad.net |
Commit message
IBuildFarmJob type for generating translation templates.
Description of the change
Jeroen T. Vermeulen (jtv) wrote : | # |
Julian Edwards (julian-edwards) wrote : | # |
Hi Jeroen! This is a great start, thanks also for cleaning up the docstrings. I'll just re-iterate here what we discussed in person for future reference:
* You need a verifyObject on the ITranslationTem
* Remove the XXX on line 171 next to build_type.
* The fault handling in dispatchBuildTo
* _getChroot is not returning a chroot! I can see what you were starting to do though, returning the nominatedarchindep arch chroot for the series in question is the right thing.
* test_getChroot should poke a dummy file into the librarian for the chroot for the pertinent distroarchseries. It should then assert that the one that _getChroot returns is the same file.
* There's no test for score().
Cheers
J
Jeroen T. Vermeulen (jtv) wrote : | # |
All done. Filed bug 506243.
Julian Edwards (julian-edwards) wrote : | # |
Land it!
Preview Diff
1 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py' |
2 | --- lib/lp/buildmaster/interfaces/buildfarmjob.py 2009-12-24 14:18:35 +0000 |
3 | +++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-01-12 03:38:15 +0000 |
4 | @@ -46,24 +46,24 @@ |
5 | Build a package from a bazaar branch and a recipe. |
6 | """) |
7 | |
8 | - TRANSLATION = DBItem(4, """ |
9 | - TranslationJob |
10 | + TRANSLATIONTEMPLATESBUILD = DBItem(4, """ |
11 | + TranslationTemplatesBuildJob |
12 | |
13 | - Perform a translation job. |
14 | + Generate translation templates from a bazaar branch. |
15 | """) |
16 | |
17 | |
18 | class IBuildFarmJob(Interface): |
19 | - """Operations that Soyuz build farm jobs must implement.""" |
20 | + """Operations that jobs for the build farm must implement.""" |
21 | |
22 | def score(): |
23 | """Calculate a job score appropriate for the job type in question.""" |
24 | |
25 | def getLogFileName(): |
26 | - """The preferred file name for the log of this Soyuz job.""" |
27 | + """The preferred file name for this job's log.""" |
28 | |
29 | def getName(): |
30 | - """An appropriate name for this Soyuz job.""" |
31 | + """An appropriate name for this job.""" |
32 | |
33 | def jobStarted(): |
34 | """'Job started' life cycle event, handle as appropriate.""" |
35 | |
36 | === modified file 'lib/lp/code/model/branchjob.py' |
37 | --- lib/lp/code/model/branchjob.py 2010-01-07 04:57:13 +0000 |
38 | +++ lib/lp/code/model/branchjob.py 2010-01-12 03:38:15 +0000 |
39 | @@ -119,6 +119,12 @@ |
40 | from disk. |
41 | """) |
42 | |
43 | + TRANSLATION_TEMPLATES_BUILD = DBItem(6, """ |
44 | + Generate translation templates |
45 | + |
46 | + This job generates translations templates from a source branch. |
47 | + """) |
48 | + |
49 | |
50 | class BranchJob(SQLBase): |
51 | """Base class for jobs related to branches.""" |
52 | |
53 | === modified file 'lib/lp/translations/configure.zcml' |
54 | --- lib/lp/translations/configure.zcml 2009-12-28 22:58:18 +0000 |
55 | +++ lib/lp/translations/configure.zcml 2010-01-12 03:38:15 +0000 |
56 | @@ -572,6 +572,35 @@ |
57 | interface="lp.translations.interfaces.poexportrequest.IPOExportRequestSet"/> |
58 | </securedutility> |
59 | |
60 | + <!-- TranslationTemplateBuildJob --> |
61 | + <class |
62 | + class="lp.translations.model.translationtemplatesbuildjob.TranslationTemplatesBuildJob"> |
63 | + <allow |
64 | + interface="lp.translations.interfaces.translationtemplatesbuildjob.ITranslationTemplatesBuildJob"/> |
65 | + </class> |
66 | + <securedutility |
67 | + component="lp.translations.model.translationtemplatesbuildjob.TranslationTemplatesBuildJob" |
68 | + provides="lp.translations.interfaces.translationtemplatesbuildjob.ITranslationTemplatesBuildJobSource"> |
69 | + <allow interface="lp.translations.interfaces.translationtemplatesbuildjob.ITranslationTemplatesBuildJobSource"/> |
70 | + </securedutility> |
71 | + <utility |
72 | + component="lp.translations.model.translationtemplatesbuildjob.TranslationTemplatesBuildJob" |
73 | + provides="lp.translations.interfaces.translationtemplatesbuildjob.ITranslationTemplatesBuildJob" |
74 | + name="TRANSLATIONTEMPLATESBUILD"/> |
75 | + |
76 | + <!-- TranslationTemplateBuildBehavior --> |
77 | + <class |
78 | + class="lp.translations.model.translationtemplatesbuildbehavior.TranslationTemplatesBuildBehavior"> |
79 | + <allow |
80 | + interface="lp.buildmaster.interfaces.buildfarmjobbehavior.IBuildFarmJobBehavior"/> |
81 | + </class> |
82 | + |
83 | + <adapter |
84 | + provides="lp.buildmaster.interfaces.buildfarmjobbehavior.IBuildFarmJobBehavior" |
85 | + for="lp.translations.model.translationtemplatesbuildjob.TranslationTemplatesBuildJob" |
86 | + factory="lp.translations.model.translationtemplatesbuildbehavior.TranslationTemplatesBuildBehavior" |
87 | + /> |
88 | + |
89 | <webservice:register module="lp.translations.interfaces.webservice" /> |
90 | |
91 | </configure> |
92 | |
93 | === added file 'lib/lp/translations/interfaces/translationtemplatesbuildjob.py' |
94 | --- lib/lp/translations/interfaces/translationtemplatesbuildjob.py 1970-01-01 00:00:00 +0000 |
95 | +++ lib/lp/translations/interfaces/translationtemplatesbuildjob.py 2010-01-12 03:38:15 +0000 |
96 | @@ -0,0 +1,37 @@ |
97 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
98 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
99 | + |
100 | +# pylint: disable-msg=E0213 |
101 | + |
102 | +__metaclass__ = type |
103 | + |
104 | +__all__ = [ |
105 | + 'ITranslationTemplatesBuildJob', |
106 | + 'ITranslationTemplatesBuildJobSource', |
107 | + ] |
108 | + |
109 | +from zope.interface import Interface |
110 | + |
111 | +from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
112 | +from lp.code.interfaces.branchjob import IBranchJob |
113 | + |
114 | + |
115 | +class ITranslationTemplatesBuildJob(IBranchJob, IBuildFarmJob): |
116 | + """Build-farm job type for generating translation templates.""" |
117 | + |
118 | + |
119 | +class ITranslationTemplatesBuildJobSource(Interface): |
120 | + """Container for `ITranslationTemplatesBuildJob`s.""" |
121 | + |
122 | + def create(branch): |
123 | + """Create new `ITranslationTemplatesBuildJob`. |
124 | + |
125 | + Also creates the matching `IBuildQueue` and `IJob`. |
126 | + |
127 | + :param branch: A `Branch` that this job will check out and |
128 | + generate templates for. |
129 | + :return: A new `ITranslationTemplatesBuildJob`. |
130 | + """ |
131 | + |
132 | + def getForJob(job): |
133 | + """Find `ITranslationTemplatesBuildJob` matching given `Job`.""" |
134 | |
135 | === added file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py' |
136 | --- lib/lp/translations/model/translationtemplatesbuildbehavior.py 1970-01-01 00:00:00 +0000 |
137 | +++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-01-12 03:38:15 +0000 |
138 | @@ -0,0 +1,79 @@ |
139 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
140 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
141 | + |
142 | +"""An `IBuildFarmJobBehavior` for `TranslationTemplatesBuildJob`. |
143 | + |
144 | +Dispatches translation template build jobs to build-farm slaves. |
145 | +""" |
146 | + |
147 | +__metaclass__ = type |
148 | +__all__ = [ |
149 | + 'TranslationTemplatesBuildBehavior', |
150 | + ] |
151 | + |
152 | +import socket |
153 | +import xmlrpclib |
154 | + |
155 | +from zope.component import getUtility |
156 | +from zope.interface import implements |
157 | + |
158 | +from canonical.launchpad.interfaces import ILaunchpadCelebrities |
159 | + |
160 | +from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
161 | + IBuildFarmJobBehavior) |
162 | +from lp.buildmaster.model.buildfarmjobbehavior import ( |
163 | + BuildFarmJobBehaviorBase) |
164 | +from lp.soyuz.interfaces.builder import BuildSlaveFailure |
165 | +from lp.translations.interfaces.translationtemplatesbuildjob import ( |
166 | + ITranslationTemplatesBuildJobSource) |
167 | + |
168 | + |
169 | +class TranslationTemplatesBuildBehavior(BuildFarmJobBehaviorBase): |
170 | + """Dispatches `TranslationTemplateBuildJob`s to slaves.""" |
171 | + implements(IBuildFarmJobBehavior) |
172 | + |
173 | + # Identify the type of job to the slave. |
174 | + build_type = 'translation-templates' |
175 | + |
176 | + def dispatchBuildToSlave(self, build_queue_item, logger): |
177 | + """See `IBuildFarmJobBehavior`.""" |
178 | + # XXX JeroenVermeulen 2009-12-24 bug=500110: This method is not |
179 | + # covered by tests yet. Either unify it with Soyuz code into a |
180 | + # generalised method, or test it. |
181 | + templatesbuildjob = self._findTranslationTemplatesBuildJob( |
182 | + build_queue_item) |
183 | + chroot = self._getChroot() |
184 | + chroot_sha1 = chroot.content.sha1 |
185 | + self._builder.cacheFileOnSlave(logger, chroot) |
186 | + buildid = templatesbuildjob.getName() |
187 | + |
188 | + args = { 'branch_url': build_queue_item.branch.url } |
189 | + filemap = {} |
190 | + |
191 | + try: |
192 | + status, info = self._builder.slave.build( |
193 | + buildid, self.build_type, chroot_sha1, filemap, args) |
194 | + except xmlrpclib.Fault, info: |
195 | + # Mark builder as 'failed'. |
196 | + logger.debug( |
197 | + "Disabling builder: %s" % self._builder.url, exc_info=1) |
198 | + self._builder.failbuilder( |
199 | + "Exception (%s) when setting up to new job" % info) |
200 | + raise BuildSlaveFailure |
201 | + except socket.error, info: |
202 | + error_message = "Exception (%s) when setting up new job" % info |
203 | + self._builder.handleTimeout(logger, error_message) |
204 | + raise BuildSlaveFailure |
205 | + |
206 | + def _getChroot(self): |
207 | + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
208 | + return ubuntu.currentseries.nominatedarchindep.getChroot() |
209 | + |
210 | + def _findTranslationTemplatesBuildJob(self, build_queue_item): |
211 | + """Find the `TranslationTemplatesBuildJob` for a job. |
212 | + |
213 | + :param build_queue_item: A `BuildQueue` entry. |
214 | + :return: The matching `TranslationTemplatesBuildJob`. |
215 | + """ |
216 | + jobsource = getUtility(ITranslationTemplatesBuildJobSource) |
217 | + return jobsource.getForJob(build_queue_item.job) |
218 | |
219 | === added file 'lib/lp/translations/model/translationtemplatesbuildjob.py' |
220 | --- lib/lp/translations/model/translationtemplatesbuildjob.py 1970-01-01 00:00:00 +0000 |
221 | +++ lib/lp/translations/model/translationtemplatesbuildjob.py 2010-01-12 03:38:15 +0000 |
222 | @@ -0,0 +1,85 @@ |
223 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
224 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
225 | + |
226 | +__metaclass__ = type |
227 | +__all__ = [ |
228 | + 'TranslationTemplatesBuildJob', |
229 | + ] |
230 | + |
231 | +import re |
232 | +from datetime import timedelta |
233 | + |
234 | +from zope.component import getUtility |
235 | +from zope.interface import classProvides, implements |
236 | + |
237 | +from canonical.launchpad.webapp.interfaces import ( |
238 | + DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR) |
239 | + |
240 | +from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType |
241 | +from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
242 | +from lp.code.model.branchjob import BranchJob, BranchJobDerived, BranchJobType |
243 | +from lp.soyuz.model.buildqueue import BuildQueue |
244 | +from lp.translations.interfaces.translationtemplatesbuildjob import ( |
245 | + ITranslationTemplatesBuildJob, ITranslationTemplatesBuildJobSource) |
246 | + |
247 | + |
248 | +class TranslationTemplatesBuildJob(BranchJobDerived, BuildFarmJob): |
249 | + """An `IBuildFarmJob` implementation that generates templates. |
250 | + |
251 | + Implementation-wise, this is actually a `BranchJob`. |
252 | + """ |
253 | + implements(ITranslationTemplatesBuildJob) |
254 | + classProvides(ITranslationTemplatesBuildJobSource) |
255 | + |
256 | + duration_estimate = timedelta(seconds=10) |
257 | + |
258 | + unsafe_chars = '[^a-zA-Z0-9_+-]' |
259 | + |
260 | + def __init__(self, branch_job): |
261 | + super(TranslationTemplatesBuildJob, self).__init__(branch_job) |
262 | + |
263 | + def score(self): |
264 | + """See `IBuildFarmJob`.""" |
265 | + # Hard-code score for now; anything other than 1000 is probably |
266 | + # inappropriate. |
267 | + return 1000 |
268 | + |
269 | + def getLogFileName(self): |
270 | + """See `IBuildFarmJob`.""" |
271 | + sanitized_name = re.sub(self.unsafe_chars, '_', self.getName()) |
272 | + return "translationtemplates_%s" % sanitized_name |
273 | + |
274 | + def getName(self): |
275 | + """See `IBuildFarmJob`.""" |
276 | + return '%s-%d' % (self.branch.name, self.job.id) |
277 | + |
278 | + @classmethod |
279 | + def create(cls, branch): |
280 | + """See `ITranslationTemplatesBuildJobSource`.""" |
281 | + store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR) |
282 | + |
283 | + # We don't have any JSON metadata for this BranchJob type. |
284 | + metadata = {} |
285 | + branch_job = BranchJob( |
286 | + branch, BranchJobType.TRANSLATION_TEMPLATES_BUILD, metadata) |
287 | + store.add(branch_job) |
288 | + specific_job = TranslationTemplatesBuildJob(branch_job) |
289 | + |
290 | + duration_estimate = cls.duration_estimate |
291 | + build_queue_entry = BuildQueue( |
292 | + estimated_duration=duration_estimate, |
293 | + job_type=BuildFarmJobType.TRANSLATIONTEMPLATESBUILD, |
294 | + job=specific_job.job.id) |
295 | + store.add(build_queue_entry) |
296 | + |
297 | + return specific_job |
298 | + |
299 | + @classmethod |
300 | + def getForJob(cls, job): |
301 | + """See `ITranslationTemplatesBuildJobSource`.""" |
302 | + store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
303 | + branch_job = store.find(BranchJob, BranchJob.job == job).one() |
304 | + if branch_job is None: |
305 | + return None |
306 | + else: |
307 | + return cls(branch_job) |
308 | |
309 | === added file 'lib/lp/translations/tests/test_translationtemplatesbuildjob.py' |
310 | --- lib/lp/translations/tests/test_translationtemplatesbuildjob.py 1970-01-01 00:00:00 +0000 |
311 | +++ lib/lp/translations/tests/test_translationtemplatesbuildjob.py 2010-01-12 03:38:15 +0000 |
312 | @@ -0,0 +1,132 @@ |
313 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
314 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
315 | + |
316 | +__metaclass__ = type |
317 | + |
318 | +from unittest import TestLoader |
319 | + |
320 | +from zope.component import getUtility |
321 | +from zope.security.proxy import removeSecurityProxy |
322 | + |
323 | +from canonical.launchpad.interfaces import ( |
324 | + ILaunchpadCelebrities, ILibraryFileAliasSet) |
325 | +from canonical.launchpad.webapp.testing import verifyObject |
326 | +from canonical.testing import ZopelessDatabaseLayer |
327 | + |
328 | +from lp.testing import TestCaseWithFactory |
329 | + |
330 | +from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
331 | +from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
332 | + IBuildFarmJobBehavior) |
333 | +from lp.services.job.model.job import Job |
334 | +from lp.soyuz.interfaces.buildqueue import IBuildQueueSet |
335 | +from lp.soyuz.model.buildqueue import BuildQueue |
336 | +from lp.translations.interfaces.translationtemplatesbuildjob import ( |
337 | + ITranslationTemplatesBuildJob, ITranslationTemplatesBuildJobSource) |
338 | + |
339 | + |
340 | +def get_job_id(job): |
341 | + """Peek inside a `Job` and retrieve its id.""" |
342 | + return removeSecurityProxy(job).id |
343 | + |
344 | + |
345 | +class TestTranslationTemplatesBuildJob(TestCaseWithFactory): |
346 | + """Test `TranslationTemplatesBuildJob`.""" |
347 | + |
348 | + layer = ZopelessDatabaseLayer |
349 | + |
350 | + def setUp(self): |
351 | + super(TestTranslationTemplatesBuildJob, self).setUp() |
352 | + self.jobset = getUtility(ITranslationTemplatesBuildJobSource) |
353 | + self.branch = self.factory.makeBranch() |
354 | + self.specific_job = self.jobset.create(self.branch) |
355 | + |
356 | + def test_new_TranslationTemplatesBuildJob(self): |
357 | + # TranslationTemplateBuildJob implements IBuildFarmJob and |
358 | + # ITranslationTemplatesBuildJob. |
359 | + verifyObject(IBuildFarmJob, self.specific_job) |
360 | + verifyObject(ITranslationTemplatesBuildJob, self.specific_job) |
361 | + |
362 | + # The class also implements a utility. |
363 | + verifyObject(ITranslationTemplatesBuildJobSource, self.jobset) |
364 | + |
365 | + # Each of these jobs knows the branch it will operate on. |
366 | + self.assertEqual(self.branch, self.specific_job.branch) |
367 | + |
368 | + def test_has_Job(self): |
369 | + # Associated with each TranslationTemplateBuildJob is a Job. |
370 | + base_job = self.specific_job.job |
371 | + self.assertIsInstance(base_job, Job) |
372 | + |
373 | + # From a Job, the TranslationTemplatesBuildJobSource can find the |
374 | + # TranslationTemplatesBuildJob back for us. |
375 | + specific_job_for_base_job = removeSecurityProxy( |
376 | + self.jobset.getForJob(base_job)) |
377 | + self.assertEqual(self.specific_job, specific_job_for_base_job) |
378 | + |
379 | + def test_has_BuildQueue(self): |
380 | + # There's also a BuildQueue item associated with the job. |
381 | + queueset = getUtility(IBuildQueueSet) |
382 | + job_id = get_job_id(self.specific_job.job) |
383 | + buildqueue = queueset.get(job_id) |
384 | + |
385 | + self.assertIsInstance(buildqueue, BuildQueue) |
386 | + self.assertEqual(job_id, get_job_id(buildqueue.job)) |
387 | + |
388 | + def test_getName(self): |
389 | + # Each job gets a unique name. |
390 | + other_job = self.jobset.create(self.branch) |
391 | + self.assertNotEqual(self.specific_job.getName(), other_job.getName()) |
392 | + |
393 | + def test_getLogFileName(self): |
394 | + # Each job has a unique log file name. |
395 | + other_job = self.jobset.create(self.branch) |
396 | + self.assertNotEqual( |
397 | + self.specific_job.getLogFileName(), other_job.getLogFileName()) |
398 | + |
399 | + def test_score(self): |
400 | + # For now, these jobs always score themselves at 1,000. In the |
401 | + # future however the scoring system is to be revisited. |
402 | + self.assertEqual(1000, self.specific_job.score()) |
403 | + |
404 | + |
405 | +class TestTranslationTemplatesBuildBehavior(TestCaseWithFactory): |
406 | + """Test `TranslationTemplatesBuildBehavior`.""" |
407 | + |
408 | + layer = ZopelessDatabaseLayer |
409 | + |
410 | + def setUp(self): |
411 | + super(TestTranslationTemplatesBuildBehavior, self).setUp() |
412 | + self.jobset = getUtility(ITranslationTemplatesBuildJobSource) |
413 | + self.branch = self.factory.makeBranch() |
414 | + self.specific_job = self.jobset.create(self.branch) |
415 | + self.behavior = IBuildFarmJobBehavior(self.specific_job) |
416 | + |
417 | + def test_getChroot(self): |
418 | + # _getChroot produces the current chroot for the current Ubuntu |
419 | + # release, on the nominated architecture for |
420 | + # architecture-independent builds. |
421 | + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
422 | + current_ubuntu = ubuntu.currentseries |
423 | + distroarchseries = current_ubuntu.nominatedarchindep |
424 | + |
425 | + # Set an arbitrary chroot file. |
426 | + fake_chroot_file = getUtility(ILibraryFileAliasSet)[1] |
427 | + distroarchseries.addOrUpdateChroot(fake_chroot_file) |
428 | + |
429 | + chroot = self.behavior._getChroot() |
430 | + |
431 | + self.assertNotEqual(None, chroot) |
432 | + self.assertEqual(fake_chroot_file, chroot) |
433 | + |
434 | + def test_findTranslationTemplatesBuildJob(self): |
435 | + job = self.specific_job.job |
436 | + job_id = removeSecurityProxy(job).id |
437 | + buildqueue = getUtility(IBuildQueueSet).get(job_id) |
438 | + specific_job_for_buildqueue = removeSecurityProxy( |
439 | + self.behavior._findTranslationTemplatesBuildJob(buildqueue)) |
440 | + self.assertEqual(self.specific_job, specific_job_for_buildqueue) |
441 | + |
442 | + |
443 | +def test_suite(): |
444 | + return TestLoader().loadTestsFromName(__name__) |
= Bug 499404 =
This implements some new classes that will be needed for Rosetta to run jobs on the generalised build farm. The job in question is generating translation templates based on a source-code branch, and will require a lot more work before it goes operational.
Generalisation of the build farm is ongoing work, so its APIs etc. are not complete yet. What you find here is:
1. An implementation of IBuildFarmJob—a new class representing a job that can be dispatched to the build farm. Called TranslationTemp latesBuildJob, it's a derivative of BranchJob found in the Code tree.
2. A BuildFarmJobSource utility—to produce and navigate jobs of the new type. This follows the pattern of the BranchJobSource utilities from the Code hierarchy. It also creates a BuildQueue record for each job it creates; these records are matched to the records of the new type in that they both refer to the same Job record.
3. A new IBuildFarmJobBe havior implementation for dispatching the new job type to the build farm, with an adapter associating it with TranslationTemp latesBuildJob. More may be needed there in the future.
This branch does not test how the Behavior class dispatches jobs to builders; I don't think that's tested for the existing classes either and I'm not sure that part of the build-farm generalization is quite ready for use yet.
To test: latesbuild
{{{
./bin/test -vv -t translationtemp
}}}
No lint.
Jeroen