Merge lp:~jtv/launchpad/bug-507681 into lp:launchpad
- bug-507681
- 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-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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Hummer (community) | code | Approve | |
Review via email: mp+19531@code.launchpad.net |
Commit message
Generate TranslationTemp
Description of the change
Jeroen T. Vermeulen (jtv) wrote : | # |
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/
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).
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?
Paul Hummer (rockstar) : | # |
Preview Diff
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__) |
= 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 TranslationTemp lateBuildJobs 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 config/ schema- lazr.conf
lib/canonical/
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 code/model/ branchjob. py
lib/lp/
Two of the IRosettaUploadJ obSource 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_translati ons_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_translati ons_upload parameter to the "are there any productseries using this branch" method in the utility. Since force_translati ons_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 codehosting/ scanner/ bzrsync. py
lib/lp/
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 ITranslationTem platesBuildJobS ource utility for ease of testing.
lib/lp/ translations/ interfaces/ translationtemp latesbuildjob. py translations/ model/translati ontemplatesbuil djob.py
lib/lp/
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...