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

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~jtv/launchpad/bug-507681
Merge into: lp:launchpad
Diff against target: 566 lines (+288/-51)
9 files modified
configs/testrunner/launchpad-lazr.conf (+3/-0)
lib/canonical/config/schema-lazr.conf (+4/-0)
lib/lp/code/interfaces/branchjob.py (+24/-0)
lib/lp/code/model/branchjob.py (+24/-41)
lib/lp/codehosting/scanner/branch_scanner.py (+3/-1)
lib/lp/codehosting/scanner/bzrsync.py (+11/-4)
lib/lp/translations/interfaces/translationtemplatesbuildjob.py (+10/-0)
lib/lp/translations/model/translationtemplatesbuildjob.py (+45/-1)
lib/lp/translations/tests/test_translationtemplatesbuildjob.py (+164/-4)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-507681
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Review via email: mp+19531@code.launchpad.net

Commit message

Generate TranslationTemplatesBuildJobs.

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

= Bug 507681 =

This is the main work bug 507681, which in turn is part of the effort to generate translation templates on the build farm, based on source in a bzr tree.

This branch gets the branch scanner to create TranslationTemplateBuildJobs for branch changes that may require new translation templates to be generated.

You'll see occasional reference to what we call "pottery." Pottery is a Translations module that deals with detecting various types of translation setup in a source code branch (we only support certain intltool setups at the moment) and generating templates from them.

Going through the diff step by step:

configs/testrunner/launchpad-lazr.conf
lib/canonical/config/schema-lazr.conf

A new configuration item, rosetta.generate_templates, enables or disables the generation of these jobs. It's off by default, to avoid interfering with Soyuz operation unnecessarily before these new jobs become useful. However the feature is enabled on the test runner.

lib/lp/code/interfaces/branchjob.py
lib/lp/code/model/branchjob.py

Two of the IRosettaUploadJobSource utility methods were published in its interface. I changed their names to follow our naming style, and (after consultation with Henning) hopefully made the docstrings a bit clearer.

These two methods are used to control importing of templates and POFiles from a branch into a productseries' translations. Normally those imports are triggered by a change to the branch, but the user can also request a one-time import on the productseries.

For the one-time imports, a parameter force_translations_upload is set to True. When it's set, the importer ignores whether a productseries attached to the branch wants automatic translation imports from it and just uploads anyway.

This is strictly speaking a bug; it does the wrong thing when multiple productseries use the same branch. I isolated the brokenness a tiny bit by not passing on the force_translations_upload parameter to the "are there any productseries using this branch" method in the utility. Since force_translations_upload is an option on the productseries using the branch, it's safe to skip the check for productseries when the parameter is True.

In findProductSeries (née _find_product_series), I unified the two alternative queries to express that there is really only a very small difference.

lib/lp/codehosting/scanner/branch_scanner.py
lib/lp/codehosting/scanner/bzrsync.py

Here is where I registered an extra hook that runs when changes are pushed to a branch. This is similar to what other branch jobs already do; the real logic is in the ITranslationTemplatesBuildJobSource utility for ease of testing.

lib/lp/translations/interfaces/translationtemplatesbuildjob.py
lib/lp/translations/model/translationtemplatesbuildjob.py

A bit more logic to see whether Launchpad should try to generate templates for a branch. For now we decided not to introduce a separate setting for this; we'll simply try to generate templates for each branch that has translation series importing templates from it.

Except that pottery has a function to check whether it's worth trying this. It makes no sense to bother a build-farm slave if a branch c...

Read more...

Revision history for this message
Paul Hummer (rockstar) wrote :

I think this branch is quite good. I just have one piece of input, and that's a recent change to the branch scanner. I made the change, and anyone outside the code team probably wasn't aware, so it's probably good you changed.

branch_scanner.py is about to get deleted. We no longer use that codepath, and instead, use the BranchScanJob in lib/lp/code/model/branchjob.py - The event subscription stuff you currently have in branch_scanner should actually go there.

I promise this time I'll take a look tomorrow at the changes, and not let this branch go out as far as the last one (which I feel pretty sheepish about).

review: Needs Fixing (code)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

I've added the event handler to branchjob, but left the obsolete code as I had it so there's some pointless duplication. Better than courting disaster if things evolve further before the obsolete code is culled.

Is it okay now?

Revision history for this message
Paul Hummer (rockstar) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configs/testrunner/launchpad-lazr.conf'
2--- configs/testrunner/launchpad-lazr.conf 2010-01-29 17:15:31 +0000
3+++ configs/testrunner/launchpad-lazr.conf 2010-02-19 09:01:18 +0000
4@@ -193,6 +193,9 @@
5 error_dir: /var/tmp/poimport.test
6 oops_prefix: TPOI
7
8+[rosetta]
9+generate_templates: True
10+
11 [rosettabranches]
12 oops_prefix: TRSBR
13 error_dir: /var/tmp/rosettabranches.test
14
15=== modified file 'lib/canonical/config/schema-lazr.conf'
16--- lib/canonical/config/schema-lazr.conf 2010-01-22 04:01:17 +0000
17+++ lib/canonical/config/schema-lazr.conf 2010-02-19 09:01:18 +0000
18@@ -1578,6 +1578,10 @@
19 # datatype: integer
20 translate_pages_max_batch_size: 50
21
22+# Generate templates using the buildfarm?
23+# datatype: boolean
24+generate_templates: False
25+
26 [rosetta_pofile_stats]
27 # In daily runs of pofile statistics update, check for
28 # POFiles that have been updated in the last how many days.
29
30=== modified file 'lib/lp/code/interfaces/branchjob.py'
31--- lib/lp/code/interfaces/branchjob.py 2010-01-14 23:33:30 +0000
32+++ lib/lp/code/interfaces/branchjob.py 2010-02-19 09:01:18 +0000
33@@ -180,6 +180,30 @@
34 given) whose status is neither "complete" nor "failed."
35 """
36
37+ def providesTranslationFiles(branch):
38+ """Is anyone importing translation files from this branch?
39+
40+ This is used to check if any product series is related to the branch
41+ in order to decide if a job needs to be created.
42+
43+ :param branch: The `IBranch` that is being scanned.
44+ :return: Boolean.
45+ """
46+
47+ def findProductSeries(branch, force_translations_upload=False):
48+ """Find `ProductSeries` that import translation files from branch.
49+
50+ :param branch: The `IBranch` that is being scanned.
51+ :param force_translations_upload: If True, return all
52+ `ProductSeries` attached to this branch regardless of their
53+ import mode settings.
54+ :return: a list of `IProductSeries`.
55+ """
56+ # XXX JeroenVermeulen 2010-01-12 bug=521095:
57+ # force_translations_upload was meant to ignore import settings
58+ # for one specific ProductSeries attached to the branch, not any
59+ # ProductSeries attached to the branch.
60+
61
62 class IReclaimBranchSpaceJob(IRunnableJob):
63 """A job to delete a branch from disk after its been deleted from the db.
64
65=== modified file 'lib/lp/code/model/branchjob.py'
66--- lib/lp/code/model/branchjob.py 2010-02-17 00:39:08 +0000
67+++ lib/lp/code/model/branchjob.py 2010-02-19 09:01:18 +0000
68@@ -1,4 +1,4 @@
69-# Copyright 2009 Canonical Ltd. This software is licensed under the
70+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
71 # GNU Affero General Public License version 3 (see the file LICENSE).
72
73 __all__ = [
74@@ -53,8 +53,8 @@
75 from lp.codehosting.scanner.fixture import (
76 Fixtures, ServerFixture, make_zope_event_fixture)
77 from lp.codehosting.scanner.bzrsync import (
78- BzrSync, schedule_diff_updates, schedule_translation_upload
79-)
80+ BzrSync, schedule_diff_updates, schedule_translation_templates_build,
81+ schedule_translation_upload)
82 from lp.codehosting.vfs import (branch_id_to_path, get_multi_server,
83 get_scanner_server)
84 from lp.services.job.model.job import Job
85@@ -296,10 +296,12 @@
86 mergedetection.auto_merge_branches,
87 mergedetection.auto_merge_proposals,
88 schedule_diff_updates,
89+ schedule_translation_templates_build,
90 schedule_translation_upload,
91 ]
92- fixture = Fixtures(
93- [ServerFixture(cls.server), make_zope_event_fixture(*event_handlers)])
94+ fixture = Fixtures([
95+ ServerFixture(cls.server),
96+ make_zope_event_fixture(*event_handlers)])
97 fixture.setUp()
98 yield
99 fixture.tearDown()
100@@ -729,41 +731,22 @@
101 return self.metadata['force_translations_upload']
102
103 @classmethod
104- def _get_any_product_series(cls, branch, force_translations_upload):
105- """Find an affected product series.
106-
107- This is used to check if any product series is related to the branch
108- in order to decide if a job needs to be created.
109-
110- :param branch: The IBranch that is being scanned.
111- :param force_translations_upload: Flag to override the settings in the
112- product series and upload all translation files.
113- :returns: a list of IProductSeries objects.
114- """
115- return cls._find_product_series(branch,
116- force_translations_upload).any()
117+ def providesTranslationFiles(cls, branch):
118+ """See `IRosettaUploadJobSource`."""
119+ return not cls.findProductSeries(branch).is_empty()
120
121 @staticmethod
122- def _find_product_series(branch, force_translations_upload):
123- """Find affected product series.
124-
125- :param branch: The IBranch that is being scanned.
126- :param force_translations_upload: Flag to override the settings in the
127- product series and upload all translation files.
128- :returns: a list of IProductSeries objects.
129- """
130+ def findProductSeries(branch, force_translations_upload=False):
131+ """See `IRosettaUploadJobSource`."""
132 store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
133- if force_translations_upload:
134- productseries = store.find(
135- (ProductSeries),
136- ProductSeries.branch == branch)
137- else:
138- productseries = store.find(
139- (ProductSeries),
140- ProductSeries.branch == branch,
141- ProductSeries.translations_autoimport_mode !=
142- TranslationsBranchImportMode.NO_IMPORT)
143- return productseries
144+
145+ conditions = [ProductSeries.branch == branch]
146+ if not force_translations_upload:
147+ import_mode = ProductSeries.translations_autoimport_mode
148+ conditions.append(
149+ import_mode != TranslationsBranchImportMode.NO_IMPORT)
150+
151+ return store.find(ProductSeries, And(*conditions))
152
153 @classmethod
154 def create(cls, branch, from_revision_id,
155@@ -771,11 +754,11 @@
156 """See `IRosettaUploadJobSource`."""
157 if branch is None:
158 return None
159+
160 if from_revision_id is None:
161 from_revision_id = NULL_REVISION
162- productseries = cls._get_any_product_series(branch,
163- force_translations_upload)
164- if productseries is not None:
165+
166+ if force_translations_upload or cls.providesTranslationFiles(branch):
167 metadata = cls.getMetadata(from_revision_id,
168 force_translations_upload)
169 branch_job = BranchJob(
170@@ -909,7 +892,7 @@
171 self._init_translation_file_lists()
172 # Get the product series that are connected to this branch and
173 # that want to upload translations.
174- productseries = self._find_product_series(
175+ productseries = self.findProductSeries(
176 self.branch, self.force_translations_upload)
177 translation_import_queue = getUtility(ITranslationImportQueue)
178 for series in productseries:
179
180=== modified file 'lib/lp/codehosting/scanner/branch_scanner.py'
181--- lib/lp/codehosting/scanner/branch_scanner.py 2010-01-21 22:10:38 +0000
182+++ lib/lp/codehosting/scanner/branch_scanner.py 2010-02-19 09:01:18 +0000
183@@ -21,7 +21,8 @@
184 from lp.codehosting.vfs import get_scanner_server
185 from lp.codehosting.scanner import buglinks, email, mergedetection
186 from lp.codehosting.scanner.bzrsync import (
187- BzrSync, schedule_diff_updates, schedule_translation_upload)
188+ BzrSync, schedule_diff_updates, schedule_translation_templates_build,
189+ schedule_translation_upload)
190 from lp.codehosting.scanner.fixture import (
191 Fixtures, make_zope_event_fixture, run_with_fixture, ServerFixture)
192 from canonical.launchpad.webapp import canonical_url, errorlog
193@@ -97,6 +98,7 @@
194 mergedetection.auto_merge_branches,
195 mergedetection.auto_merge_proposals,
196 schedule_diff_updates,
197+ schedule_translation_templates_build,
198 schedule_translation_upload,
199 ]
200 server = get_scanner_server()
201
202=== modified file 'lib/lp/codehosting/scanner/bzrsync.py'
203--- lib/lp/codehosting/scanner/bzrsync.py 2010-01-07 04:48:10 +0000
204+++ lib/lp/codehosting/scanner/bzrsync.py 2010-02-19 09:01:18 +0000
205@@ -1,6 +1,6 @@
206 #!/usr/bin/python
207 #
208-# Copyright 2009 Canonical Ltd. This software is licensed under the
209+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
210 # GNU Affero General Public License version 3 (see the file LICENSE).
211
212 """Import version control metadata from a Bazaar branch into the database."""
213@@ -10,12 +10,13 @@
214 __all__ = [
215 "BzrSync",
216 'schedule_diff_updates',
217+ 'schedule_translation_templates_build',
218 'schedule_translation_upload',
219 ]
220
221 import logging
222-
223 import pytz
224+import transaction
225
226 from zope.component import adapter, getUtility
227 from zope.event import notify
228@@ -27,8 +28,6 @@
229
230 from lazr.uri import URI
231
232-import transaction
233-
234 from lp.codehosting import iter_list_chunks
235 from lp.codehosting.puller.worker import BranchMirrorer
236 from lp.codehosting.scanner import events
237@@ -37,6 +36,8 @@
238 from lp.code.interfaces.branchjob import IRosettaUploadJobSource
239 from lp.code.interfaces.branchrevision import IBranchRevisionSet
240 from lp.code.interfaces.revision import IRevisionSet
241+from lp.translations.interfaces.translationtemplatesbuildjob import (
242+ ITranslationTemplatesBuildJobSource)
243
244 UTC = pytz.timezone('UTC')
245
246@@ -358,5 +359,11 @@
247
248
249 @adapter(events.TipChanged)
250+def schedule_translation_templates_build(tip_changed):
251+ utility = getUtility(ITranslationTemplatesBuildJobSource)
252+ utility.scheduleTranslationTemplatesBuild(tip_changed.db_branch)
253+
254+
255+@adapter(events.TipChanged)
256 def schedule_diff_updates(tip_changed):
257 tip_changed.db_branch.scheduleDiffUpdates()
258
259=== modified file 'lib/lp/translations/interfaces/translationtemplatesbuildjob.py'
260--- lib/lp/translations/interfaces/translationtemplatesbuildjob.py 2010-01-12 23:37:32 +0000
261+++ lib/lp/translations/interfaces/translationtemplatesbuildjob.py 2010-02-19 09:01:18 +0000
262@@ -15,6 +15,13 @@
263 class ITranslationTemplatesBuildJobSource(Interface):
264 """Container for `TranslationTemplatesBuildJob`s."""
265
266+ def generatesTemplates(branch):
267+ """Can this branch usefully generate translation templates?
268+
269+ If yes, then use `create` to schedule a build-farm job to
270+ generate the templates based on the source code in the branch.
271+ """
272+
273 def create(branch):
274 """Create new `TranslationTemplatesBuildJob`.
275
276@@ -24,3 +31,6 @@
277 generate templates for.
278 :return: A new `TranslationTemplatesBuildJob`.
279 """
280+
281+ def scheduleTranslationTemplatesBuild(branch):
282+ """Schedule a translation templates build job, if appropriate."""
283
284=== modified file 'lib/lp/translations/model/translationtemplatesbuildjob.py'
285--- lib/lp/translations/model/translationtemplatesbuildjob.py 2010-01-15 01:20:20 +0000
286+++ lib/lp/translations/model/translationtemplatesbuildjob.py 2010-02-19 09:01:18 +0000
287@@ -11,6 +11,9 @@
288
289 from zope.component import getUtility
290 from zope.interface import classProvides, implements
291+from zope.security.proxy import removeSecurityProxy
292+
293+from canonical.config import config
294
295 from canonical.launchpad.webapp.interfaces import (
296 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR)
297@@ -18,11 +21,12 @@
298 from lp.buildmaster.interfaces.buildfarmjob import (
299 BuildFarmJobType, IBuildFarmJob, ISpecificBuildFarmJobClass)
300 from lp.buildmaster.model.buildfarmjob import BuildFarmJob
301-from lp.code.interfaces.branchjob import IBranchJob
302+from lp.code.interfaces.branchjob import IBranchJob, IRosettaUploadJobSource
303 from lp.code.model.branchjob import BranchJob, BranchJobDerived, BranchJobType
304 from lp.soyuz.model.buildqueue import BuildQueue
305 from lp.translations.interfaces.translationtemplatesbuildjob import (
306 ITranslationTemplatesBuildJobSource)
307+from lp.translations.pottery.detect_intltool import is_intltool_structure
308
309
310 class TranslationTemplatesBuildJob(BranchJobDerived, BuildFarmJob):
311@@ -61,6 +65,35 @@
312 return '%s translation templates build' % self.branch.bzr_identity
313
314 @classmethod
315+ def _hasPotteryCompatibleSetup(cls, branch):
316+ """Does `branch` look as if pottery can generate templates for it?
317+
318+ :param branch: A `Branch` object.
319+ """
320+ bzr_branch = removeSecurityProxy(branch).getBzrBranch()
321+ return is_intltool_structure(bzr_branch.basis_tree())
322+
323+ @classmethod
324+ def generatesTemplates(cls, branch):
325+ """See `ITranslationTemplatesBuildJobSource`."""
326+ if branch.private:
327+ # We don't support generating template from private branches
328+ # at the moment.
329+ return False
330+
331+ utility = getUtility(IRosettaUploadJobSource)
332+ if not utility.providesTranslationFiles(branch):
333+ # Nobody asked for templates generated from this branch.
334+ return False
335+
336+ if not cls._hasPotteryCompatibleSetup(branch):
337+ # Nothing we could do with this branch if we wanted to.
338+ return False
339+
340+ # Yay! We made it.
341+ return True
342+
343+ @classmethod
344 def create(cls, branch):
345 """See `ITranslationTemplatesBuildJobSource`."""
346 store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
347@@ -82,6 +115,17 @@
348 return specific_job
349
350 @classmethod
351+ def scheduleTranslationTemplatesBuild(cls, branch):
352+ """See `ITranslationTemplatesBuildJobSource`."""
353+ if not config.rosetta.generate_templates:
354+ # This feature is disabled by default.
355+ return
356+
357+ if cls.generatesTemplates(branch):
358+ # This branch is used for generating templates.
359+ cls.create(branch)
360+
361+ @classmethod
362 def getByJob(cls, job):
363 """See `ISpecificBuildFarmJobClass`."""
364 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
365
366=== modified file 'lib/lp/translations/tests/test_translationtemplatesbuildjob.py'
367--- lib/lp/translations/tests/test_translationtemplatesbuildjob.py 2010-02-11 19:11:11 +0000
368+++ lib/lp/translations/tests/test_translationtemplatesbuildjob.py 2010-02-19 09:01:18 +0000
369@@ -8,17 +8,26 @@
370 from zope.component import getUtility
371 from zope.security.proxy import removeSecurityProxy
372
373+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
374+from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
375 from canonical.launchpad.webapp.testing import verifyObject
376-from canonical.testing import ZopelessDatabaseLayer
377+from canonical.launchpad.webapp.interfaces import (
378+ DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE)
379+from canonical.testing import LaunchpadZopelessLayer, ZopelessDatabaseLayer
380
381 from lp.testing import TestCaseWithFactory
382
383 from lp.buildmaster.interfaces.buildfarmjob import (
384 IBuildFarmJob, ISpecificBuildFarmJobClass)
385+from lp.buildmaster.interfaces.buildfarmjobbehavior import (
386+ IBuildFarmJobBehavior)
387 from lp.code.interfaces.branchjob import IBranchJob
388+from lp.code.model.branchjob import BranchJob
389 from lp.services.job.model.job import Job
390 from lp.soyuz.interfaces.buildqueue import IBuildQueueSet
391 from lp.soyuz.model.buildqueue import BuildQueue
392+from lp.translations.interfaces.translations import (
393+ TranslationsBranchImportMode)
394 from lp.translations.interfaces.translationtemplatesbuildjob import (
395 ITranslationTemplatesBuildJobSource)
396 from lp.translations.model.translationtemplatesbuildjob import (
397@@ -47,9 +56,7 @@
398 verifyObject(IBranchJob, self.specific_job)
399 verifyObject(IBuildFarmJob, self.specific_job)
400
401- # The class also implements a utility and
402- # ISpecificBuildFarmJobClass.
403- verifyObject(ITranslationTemplatesBuildJobSource, self.jobset)
404+ # The class also implements ISpecificBuildFarmJobClass.
405 verifyObject(ISpecificBuildFarmJobClass, TranslationTemplatesBuildJob)
406
407 # Each of these jobs knows the branch it will operate on.
408@@ -98,5 +105,158 @@
409 self.assertEqual(1000, self.specific_job.score())
410
411
412+class FakeTranslationTemplatesJobSource(TranslationTemplatesBuildJob):
413+ """Fake utility class.
414+
415+ Allows overriding of _hasPotteryCompatibleSetup.
416+
417+ How do you fake a utility that is implemented as a class, not a
418+ factory? By inheriting from `TranslationTemplatesJob`, this class
419+ "copies" the utility. But you can make it fake the utility's
420+ behavior by setting an attribute of the class (not an object!) at
421+ the beginning of every test.
422+ """
423+ # Fake _hasPotteryCompatibleSetup, and if so, make it give what
424+ # answer?
425+ fake_pottery_compatibility = None
426+
427+ @classmethod
428+ def _hasPotteryCompatibleSetup(cls, branch):
429+ if cls.fake_pottery_compatibility is None:
430+ # No fake compatibility setting call the real method.
431+ return TranslationTemplatesBuildJob._hasPotteryCompatibleSetup(
432+ branch)
433+ else:
434+ # Fake pottery compatibility.
435+ return cls.fake_pottery_compatibility
436+
437+
438+class TestTranslationTemplatesBuildJobSource(TestCaseWithFactory):
439+ """Test `TranslationTemplatesBuildJobSource`."""
440+
441+ layer = LaunchpadZopelessLayer
442+
443+ def setUp(self):
444+ super(TestTranslationTemplatesBuildJobSource, self).setUp()
445+ self.jobsource = FakeTranslationTemplatesJobSource
446+ self.jobsource.fake_pottery_compabitility = None
447+
448+ def tearDown(self):
449+ self._fakePotteryCompatibleSetup(compatible=None)
450+ super(TestTranslationTemplatesBuildJobSource, self).tearDown()
451+
452+ def _makeTranslationBranch(self, fake_pottery_compatible=None):
453+ """Create a branch that provides translations for a productseries."""
454+ if fake_pottery_compatible is None:
455+ self.useBzrBranches()
456+ branch, tree = self.create_branch_and_tree()
457+ else:
458+ branch = self.factory.makeAnyBranch()
459+ product = removeSecurityProxy(branch.product)
460+ trunk = product.getSeries('trunk')
461+ trunk.branch = branch
462+ trunk.translations_autoimport_mode = (
463+ TranslationsBranchImportMode.IMPORT_TEMPLATES)
464+
465+ self._fakePotteryCompatibleSetup(fake_pottery_compatible)
466+
467+ return branch
468+
469+ def _fakePotteryCompatibleSetup(self, compatible=True):
470+ """Mock up branch compatibility check.
471+
472+ :param compatible: Whether the mock check should say that
473+ branches have a pottery-compatible setup, or that they
474+ don't.
475+ """
476+ self.jobsource.fake_pottery_compatibility = compatible
477+
478+ def test_baseline(self):
479+ utility = getUtility(ITranslationTemplatesBuildJobSource)
480+ verifyObject(ITranslationTemplatesBuildJobSource, utility)
481+
482+ def test_generatesTemplates(self):
483+ # A branch "generates templates" if it is a translation branch
484+ # for a productseries that imports templates from it; is not
485+ # private; and has a pottery compatible setup.
486+ # For convenience we fake the pottery compatibility here.
487+ branch = self._makeTranslationBranch(fake_pottery_compatible=True)
488+ self.assertTrue(self.jobsource.generatesTemplates(branch))
489+
490+ def test_not_pottery_compatible(self):
491+ # If pottery does not see any files it can work with in the
492+ # branch, generatesTemplates returns False.
493+ branch = self._makeTranslationBranch()
494+ self.assertFalse(self.jobsource.generatesTemplates(branch))
495+
496+ def test_branch_not_used(self):
497+ # We don't generate templates branches not attached to series.
498+ branch = self._makeTranslationBranch(fake_pottery_compatible=True)
499+
500+ trunk = branch.product.getSeries('trunk')
501+ removeSecurityProxy(trunk).branch = None
502+
503+ self.assertFalse(self.jobsource.generatesTemplates(branch))
504+
505+ def test_not_importing_templates(self):
506+ # We don't generate templates when imports are disabled.
507+ branch = self._makeTranslationBranch(fake_pottery_compatible=True)
508+
509+ trunk = branch.product.getSeries('trunk')
510+ removeSecurityProxy(trunk).translations_autoimport_mode = (
511+ TranslationsBranchImportMode.NO_IMPORT)
512+
513+ self.assertFalse(self.jobsource.generatesTemplates(branch))
514+
515+ def test_private_branch(self):
516+ # We don't generate templates for private branches.
517+ branch = self._makeTranslationBranch(fake_pottery_compatible=True)
518+ removeSecurityProxy(branch).private = True
519+ self.assertFalse(self.jobsource.generatesTemplates(branch))
520+
521+ def test_scheduleTranslationTemplatesBuild(self):
522+ # If the feature is enabled, scheduleTranslationTemplatesBuild
523+ # will schedule a templates build whenever a change is pushed to
524+ # a branch that generates templates.
525+ branch = self._makeTranslationBranch(fake_pottery_compatible=True)
526+
527+ self.jobsource.scheduleTranslationTemplatesBuild(branch)
528+
529+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
530+ branchjobs = list(store.find(BranchJob, BranchJob.branch == branch))
531+ self.assertEqual(1, len(branchjobs))
532+ self.assertEqual(branch, branchjobs[0].branch)
533+
534+
535+class TestTranslationTemplatesBuildBehavior(TestCaseWithFactory):
536+ """Test `TranslationTemplatesBuildBehavior`."""
537+
538+ layer = ZopelessDatabaseLayer
539+
540+ def setUp(self):
541+ super(TestTranslationTemplatesBuildBehavior, self).setUp()
542+ self.jobset = getUtility(ITranslationTemplatesBuildJobSource)
543+ self.branch = self.factory.makeBranch()
544+ self.specific_job = self.jobset.create(self.branch)
545+ self.behavior = IBuildFarmJobBehavior(self.specific_job)
546+
547+ def test_getChroot(self):
548+ # _getChroot produces the current chroot for the current Ubuntu
549+ # release, on the nominated architecture for
550+ # architecture-independent builds.
551+ ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
552+ current_ubuntu = ubuntu.currentseries
553+ distroarchseries = current_ubuntu.nominatedarchindep
554+
555+ # Set an arbitrary chroot file.
556+ fake_chroot_file = getUtility(ILibraryFileAliasSet)[1]
557+ distroarchseries.addOrUpdateChroot(fake_chroot_file)
558+
559+ chroot = self.behavior._getChroot()
560+
561+ self.assertNotEqual(None, chroot)
562+ self.assertEqual(fake_chroot_file, chroot)
563+
564+
565 def test_suite():
566 return TestLoader().loadTestsFromName(__name__)