Merge lp:~stevenk/launchpad/dsdj-runner into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 12649
Proposed branch: lp:~stevenk/launchpad/dsdj-runner
Merge into: lp:launchpad
Diff against target: 676 lines (+417/-58) (has conflicts)
11 files modified
configs/development/launchpad-lazr.conf (+4/-0)
cronscripts/distroseriesdifference_job.py (+37/-0)
database/schema/security.cfg (+15/-0)
lib/canonical/config/schema-lazr.conf (+13/-0)
lib/lp/code/model/branchcollection.py (+25/-9)
lib/lp/soyuz/configure.zcml (+6/-5)
lib/lp/soyuz/interfaces/distributionjob.py (+19/-0)
lib/lp/soyuz/interfaces/distroseriesdifferencejob.py (+0/-24)
lib/lp/soyuz/model/distroseriesdifferencejob.py (+29/-10)
lib/lp/soyuz/model/publishing.py (+6/-1)
lib/lp/soyuz/tests/test_distroseriesdifferencejob.py (+263/-9)
Text conflict in lib/lp/code/model/branchcollection.py
To merge this branch: bzr merge lp:~stevenk/launchpad/dsdj-runner
Reviewer Review Type Date Requested Status
Henning Eggers (community) code Approve
Review via email: mp+54159@code.launchpad.net

Commit message

[r=henninge][bug=739997] Implement the job runner for DSDJs.

Description of the change

Add "the business end" to DistroSeriesDifferenceJob is the short description.

The longer description is implement a run() method for DSDJ, add a *lot* of tests for it, create a database user, add relevant permissions for it, implement a cronscript that will run said jobs, also create DSDJs when an SPPH is marked for deletion, fix some bugs I noticed during implementation and perform some clean up.

(Some of the clean up wasn't strictly necessary, but I was the first implementer of DistributionJob, so I still think things should be 'just so'.)

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (4.3 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Steven,
thank you for this branch. It looks all very well done and thoroughly tested.
That's good. ;-)

I have two issues:
- - You keep using IMasterStore when I think IStore would be totally sufficient.
Any particular reason for that?
- - There is a helper method called canonical.launchpad.scripts.tests.run_script
that encapsulates a lot of what you are doing manually.

 review approve code

Cheers,
Henning

Am 21.03.2011 08:49, schrieb Steve Kowalik:
> === modified file 'lib/lp/soyuz/tests/test_distroseriesdifferencejob.py'
> --- lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-03-11 13:35:48 +0000
> +++ lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-03-21 07:49:35 +0000
> @@ -5,28 +5,43 @@
>
> __metaclass__ = type
>
> +import os
> +import subprocess
> +import sys

New line here between "system" and "3rd party" imports. But maybe that section
is going away anyway ...

> import transaction
> from psycopg2 import ProgrammingError
> from zope.component import getUtility
> from zope.interface.verify import verifyObject
>
> +from canonical.config import config
> +from canonical.launchpad.interfaces.lpstorm import IMasterStore
> from canonical.testing.layers import (
> LaunchpadZopelessLayer,
> ZopelessDatabaseLayer,
> )
> +from lp.registry.enum import (
> + DistroSeriesDifferenceStatus,
> + DistroSeriesDifferenceType,
> + )
> +from lp.registry.model.distroseriesdifference import DistroSeriesDifference
> from lp.services.features.testing import FeatureFixture
> from lp.services.job.interfaces.job import JobStatus
> -from lp.soyuz.interfaces.distroseriesdifferencejob import (
> +from lp.soyuz.enums import PackagePublishingStatus
> +from lp.soyuz.interfaces.distributionjob import (
> IDistroSeriesDifferenceJobSource,
> )
> from lp.soyuz.model.distroseriesdifferencejob import (
> create_job,
> + DistroSeriesDifferenceJob,
> FEATURE_FLAG_ENABLE_MODULE,
> find_waiting_jobs,
> make_metadata,
> may_require_job,
> )
> -from lp.testing import TestCaseWithFactory
> +from lp.testing import (
> + person_logged_in,
> + TestCaseWithFactory,
> + )
>
>
> class TestDistroSeriesDifferenceJobSource(TestCaseWithFactory):
[...]
> @@ -163,6 +187,246 @@
> self.getJobSource().createForPackagePublication(distroseries, package)
> self.assertContentEqual([], find_waiting_jobs(distroseries, package))
>
> + def test_cronscript(self):
> + derived_series = self.makeDerivedDistroSeries()
> + package = self.factory.makeSourcePackageName()
> + self.getJobSource().createForPackagePublication(
> + derived_series, package)
> + transaction.commit() # The cronscript is a different process.
> + script = os.path.join(
> + config.root, 'cronscripts', 'distroseriesdifference_job.py')
> + args = [sys.executable, script, '-v']
> + process = subprocess.Popen(
> + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
> + stdout, stderr = process.communicate()

So, this whole section could be:
       return_code, stdout...

Read more...

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configs/development/launchpad-lazr.conf'
2--- configs/development/launchpad-lazr.conf 2011-02-25 19:27:05 +0000
3+++ configs/development/launchpad-lazr.conf 2011-03-23 01:14:43 +0000
4@@ -115,6 +115,10 @@
5 timeout: 10
6 cdimage_file_list_url: file:lib/canonical/launchpad/doc/ubuntu-releases.testdata
7
8+[distroseriesdifferencejob]
9+oops_prefix: DSDJ
10+error_dir: /var/tmp/soyuz.test
11+
12 [error_reports]
13 oops_prefix: X
14 error_dir: /var/tmp/lperr
15
16=== added file 'cronscripts/distroseriesdifference_job.py'
17--- cronscripts/distroseriesdifference_job.py 1970-01-01 00:00:00 +0000
18+++ cronscripts/distroseriesdifference_job.py 2011-03-23 01:14:43 +0000
19@@ -0,0 +1,37 @@
20+#!/usr/bin/python -S
21+#
22+# Copyright 2010 Canonical Ltd. This software is licensed under the
23+# GNU Affero General Public License version 3 (see the file LICENSE).
24+
25+"""Process DistroSeriesDifferences."""
26+
27+__metaclass__ = type
28+
29+import _pythonpath
30+
31+from lp.services.features import getFeatureFlag
32+from lp.services.job.runner import JobCronScript
33+from lp.soyuz.model.distroseriesdifferencejob import (
34+ FEATURE_FLAG_ENABLE_MODULE,
35+ )
36+from lp.soyuz.interfaces.distributionjob import (
37+ IDistroSeriesDifferenceJobSource,
38+ )
39+
40+
41+class RunDistroSeriesDifferenceJob(JobCronScript):
42+ """Run DistroSeriesDifferenceJob jobs."""
43+
44+ config_name = 'distroseriesdifferencejob'
45+ source_interface = IDistroSeriesDifferenceJobSource
46+
47+ def main(self):
48+ if not getFeatureFlag(FEATURE_FLAG_ENABLE_MODULE):
49+ self.logger.info("Feature flag is not enabled.")
50+ return
51+ super(RunDistroSeriesDifferenceJob, self).main()
52+
53+
54+if __name__ == '__main__':
55+ script = RunDistroSeriesDifferenceJob()
56+ script.lock_and_run()
57
58=== modified file 'database/schema/security.cfg'
59--- database/schema/security.cfg 2011-03-18 03:56:36 +0000
60+++ database/schema/security.cfg 2011-03-23 01:14:43 +0000
61@@ -1059,6 +1059,21 @@
62 public.sourcepackagerelease = SELECT
63 public.sourcepackagereleasefile = SELECT, INSERT, UPDATE
64
65+[distroseriesdifferencejob]
66+type=user
67+groups=script
68+public.archive = SELECT
69+public.distribution = SELECT
70+public.distributionjob = SELECT
71+public.distroseries = SELECT
72+public.distroseriesdifference = SELECT, INSERT, UPDATE
73+public.job = SELECT, UPDATE
74+public.libraryfilealias = SELECT
75+public.libraryfilecontent = SELECT
76+public.sourcepackagename = SELECT
77+public.sourcepackagepublishinghistory = SELECT
78+public.sourcepackagerelease = SELECT
79+
80 [write]
81 type=group
82 # Full access except for tables that are exclusively updated by
83
84=== modified file 'lib/canonical/config/schema-lazr.conf'
85--- lib/canonical/config/schema-lazr.conf 2011-03-07 20:49:03 +0000
86+++ lib/canonical/config/schema-lazr.conf 2011-03-23 01:14:43 +0000
87@@ -728,6 +728,19 @@
88 cdimage_file_list_url: http://releases.ubuntu.com/.manifest
89
90
91+[distroseriesdifferencejob]
92+dbuser: distroseriesdifferencejob
93+
94+# See [error_reports].
95+error_dir: none
96+
97+# See [error_reports].
98+oops_prefix: none
99+
100+# See [error_reports].
101+copy_to_zlog: false
102+
103+
104 [error_reports]
105 # A prefix for "OOPS" codes for this process instance.
106 # This is used to allow storing the reports from different
107
108=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
109=== modified file 'lib/lp/code/model/branchcollection.py'
110--- lib/lp/code/model/branchcollection.py 2011-03-22 02:14:15 +0000
111+++ lib/lp/code/model/branchcollection.py 2011-03-23 01:14:43 +0000
112@@ -203,15 +203,31 @@
113 cache = caches[link.branchID]
114 cache._associatedSuiteSourcePackages.append(
115 link.suite_sourcepackage)
116- load_related(Product, rows, ['productID'])
117- # So far have only needed the persons for their canonical_url - no
118- # need for validity etc in the /branches API call.
119- load_related(Person, rows,
120- ['ownerID', 'registrantID', 'reviewerID'])
121- for code_import in IStore(CodeImport).find(
122- CodeImport, CodeImport.branchID.is_in(branch_ids)):
123- cache = caches[code_import.branchID]
124- cache.code_import = code_import
125+<<<<<<< TREE
126+ load_related(Product, rows, ['productID'])
127+ # So far have only needed the persons for their canonical_url - no
128+ # need for validity etc in the /branches API call.
129+ load_related(Person, rows,
130+ ['ownerID', 'registrantID', 'reviewerID'])
131+ for code_import in IStore(CodeImport).find(
132+ CodeImport, CodeImport.branchID.is_in(branch_ids)):
133+ cache = caches[code_import.branchID]
134+ cache.code_import = code_import
135+=======
136+ load_related(Product, rows, ['productID'])
137+ # So far have only needed the persons for their canonical_url - no
138+ # need for validity etc in the /branches API call.
139+ load_related(Person, rows,
140+ ['ownerID', 'registrantID', 'reviewerID'])
141+ # Cache all branches as having no code imports to prevent fruitless
142+ # lookups on the ones we don't find.
143+ for cache in caches.values():
144+ cache.code_import = None
145+ for code_import in IStore(CodeImport).find(
146+ CodeImport, CodeImport.branchID.is_in(branch_ids)):
147+ cache = caches[code_import.branchID]
148+ cache.code_import = code_import
149+>>>>>>> MERGE-SOURCE
150 return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
151
152 def getMergeProposals(self, statuses=None, for_branches=None,
153
154=== modified file 'lib/lp/soyuz/configure.zcml'
155--- lib/lp/soyuz/configure.zcml 2011-03-10 14:05:51 +0000
156+++ lib/lp/soyuz/configure.zcml 2011-03-23 01:14:43 +0000
157@@ -901,14 +901,15 @@
158 </class>
159
160 <!-- DistroSeriesDifferenceJobSource -->
161+ <securedutility
162+ component="lp.soyuz.model.distroseriesdifferencejob.DistroSeriesDifferenceJob"
163+ provides="lp.soyuz.interfaces.distributionjob.IDistroSeriesDifferenceJobSource">
164+ <allow interface="lp.soyuz.interfaces.distributionjob.IDistroSeriesDifferenceJobSource"/>
165+ </securedutility>
166 <class class="lp.soyuz.model.distroseriesdifferencejob.DistroSeriesDifferenceJob">
167+ <allow interface="lp.soyuz.interfaces.distributionjob.IDistroSeriesDifferenceJob" />
168 <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
169 </class>
170- <securedutility
171- component="lp.soyuz.model.distroseriesdifferencejob.DistroSeriesDifferenceJob"
172- provides="lp.soyuz.interfaces.distroseriesdifferencejob.IDistroSeriesDifferenceJobSource">
173- <allow interface="lp.soyuz.interfaces.distroseriesdifferencejob.IDistroSeriesDifferenceJobSource"/>
174- </securedutility>
175
176 <!-- SyncPackageJobSource -->
177 <securedutility
178
179=== modified file 'lib/lp/soyuz/interfaces/distributionjob.py'
180--- lib/lp/soyuz/interfaces/distributionjob.py 2011-03-10 14:05:51 +0000
181+++ lib/lp/soyuz/interfaces/distributionjob.py 2011-03-23 01:14:43 +0000
182@@ -6,6 +6,8 @@
183 __all__ = [
184 "DistributionJobType",
185 "IDistributionJob",
186+ "IDistroSeriesDifferenceJob",
187+ "IDistroSeriesDifferenceJobSource",
188 "IInitialiseDistroSeriesJob",
189 "IInitialiseDistroSeriesJobSource",
190 "ISyncPackageJob",
191@@ -133,3 +135,20 @@
192 include_binaries = Bool(
193 title=_("Copy binaries"),
194 required=False, readonly=True)
195+
196+
197+class IDistroSeriesDifferenceJob(IRunnableJob):
198+ """A Job that performs actions related to DSDs."""
199+
200+
201+class IDistroSeriesDifferenceJobSource(IJobSource):
202+ """An `IJob` for creating `DistroSeriesDifference`s."""
203+
204+ def createForPackagePublication(distroseries, sourcepackagename):
205+ """Create jobs as appropriate for a given status publication.
206+
207+ :param distroseries: A `DistroSeries` that is assumed to be
208+ derived from another one.
209+ :param sourcepackagename: A `SourcePackageName` that is being
210+ published in `distroseries`.
211+ """
212
213=== removed file 'lib/lp/soyuz/interfaces/distroseriesdifferencejob.py'
214--- lib/lp/soyuz/interfaces/distroseriesdifferencejob.py 2011-03-10 14:05:51 +0000
215+++ lib/lp/soyuz/interfaces/distroseriesdifferencejob.py 1970-01-01 00:00:00 +0000
216@@ -1,24 +0,0 @@
217-# Copyright 2011 Canonical Ltd. This software is licensed under the
218-# GNU Affero General Public License version 3 (see the file LICENSE).
219-
220-"""`IDistroSeriesDifferenceJob`."""
221-
222-__metaclass__ = type
223-__all__ = [
224- 'IDistroSeriesDifferenceJobSource',
225- ]
226-
227-from lp.services.job.interfaces.job import IJobSource
228-
229-
230-class IDistroSeriesDifferenceJobSource(IJobSource):
231- """An `IJob` for creating `DistroSeriesDifference`s."""
232-
233- def createForPackagePublication(distroseries, sourcepackagename):
234- """Create jobs as appropriate for a given status publication.
235-
236- :param distroseries: A `DistroSeries` that is assumed to be
237- derived from another one.
238- :param sourcepackagename: A `SourcePackageName` that is being
239- published in `distroseries`.
240- """
241
242=== modified file 'lib/lp/soyuz/model/distroseriesdifferencejob.py'
243--- lib/lp/soyuz/model/distroseriesdifferencejob.py 2011-03-11 13:35:48 +0000
244+++ lib/lp/soyuz/model/distroseriesdifferencejob.py 2011-03-23 01:14:43 +0000
245@@ -8,25 +8,29 @@
246 'DistroSeriesDifferenceJob',
247 ]
248
249+from zope.component import getUtility
250 from zope.interface import (
251 classProvides,
252 implements,
253 )
254
255 from canonical.launchpad.interfaces.lpstorm import IMasterStore
256+from lp.registry.interfaces.distroseriesdifference import (
257+ IDistroSeriesDifferenceSource,
258+ )
259+from lp.registry.model.distroseriesdifference import DistroSeriesDifference
260+from lp.registry.model.sourcepackagename import SourcePackageName
261 from lp.services.features import getFeatureFlag
262 from lp.services.job.model.job import Job
263 from lp.soyuz.interfaces.distributionjob import (
264 DistributionJobType,
265- IDistributionJob,
266+ IDistroSeriesDifferenceJob,
267+ IDistroSeriesDifferenceJobSource,
268 )
269 from lp.soyuz.model.distributionjob import (
270 DistributionJob,
271 DistributionJobDerived,
272 )
273-from lp.soyuz.interfaces.distroseriesdifferencejob import (
274- IDistroSeriesDifferenceJobSource,
275- )
276
277
278 FEATURE_FLAG_ENABLE_MODULE = u"soyuz.derived_series_jobs.enabled"
279@@ -98,7 +102,7 @@
280 class DistroSeriesDifferenceJob(DistributionJobDerived):
281 """A `Job` type for creating/updating `DistroSeriesDifference`s."""
282
283- implements(IDistributionJob)
284+ implements(IDistroSeriesDifferenceJob)
285 classProvides(IDistroSeriesDifferenceJobSource)
286
287 class_job_type = DistributionJobType.DISTROSERIESDIFFERENCE
288@@ -108,12 +112,27 @@
289 """See `IDistroSeriesDifferenceJobSource`."""
290 if not getFeatureFlag(FEATURE_FLAG_ENABLE_MODULE):
291 return
292- children = distroseries.getDerivedSeries()
293- parent = distroseries.parent_series
294- for relative in list(children) + [parent]:
295+ jobs = []
296+ children = list(distroseries.getDerivedSeries())
297+ for relative in children + [distroseries]:
298 if may_require_job(relative, sourcepackagename):
299- create_job(relative, sourcepackagename)
300+ jobs.append(create_job(relative, sourcepackagename))
301+ return jobs
302+
303+ @property
304+ def sourcepackagename(self):
305+ return SourcePackageName.get(self.metadata['sourcepackagename'])
306
307 def run(self):
308 """See `IRunnableJob`."""
309-# TODO: Implement the business end.
310+ store = IMasterStore(DistroSeriesDifference)
311+ ds_diff = store.find(
312+ DistroSeriesDifference,
313+ DistroSeriesDifference.derived_series == self.distroseries,
314+ DistroSeriesDifference.source_package_name ==
315+ self.sourcepackagename).one()
316+ if ds_diff is None:
317+ ds_diff = getUtility(IDistroSeriesDifferenceSource).new(
318+ self.distroseries, self.sourcepackagename)
319+ else:
320+ ds_diff.update()
321
322=== modified file 'lib/lp/soyuz/model/publishing.py'
323--- lib/lp/soyuz/model/publishing.py 2011-03-10 14:05:51 +0000
324+++ lib/lp/soyuz/model/publishing.py 2011-03-23 01:14:43 +0000
325@@ -85,7 +85,7 @@
326 BuildSetStatus,
327 IBinaryPackageBuildSet,
328 )
329-from lp.soyuz.interfaces.distroseriesdifferencejob import (
330+from lp.soyuz.interfaces.distributionjob import (
331 IDistroSeriesDifferenceJobSource,
332 )
333 from lp.soyuz.interfaces.publishing import (
334@@ -336,6 +336,11 @@
335 self.datesuperseded = UTC_NOW
336 self.removed_by = removed_by
337 self.removal_comment = removal_comment
338+ if ISourcePackagePublishingHistory.providedBy(self):
339+ dsd_job_source = getUtility(IDistroSeriesDifferenceJobSource)
340+ dsd_job_source.createForPackagePublication(
341+ self.distroseries,
342+ self.sourcepackagerelease.sourcepackagename)
343
344 def requestObsolescence(self):
345 """See `IArchivePublisher`."""
346
347=== modified file 'lib/lp/soyuz/tests/test_distroseriesdifferencejob.py'
348--- lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-03-11 13:35:48 +0000
349+++ lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-03-23 01:14:43 +0000
350@@ -10,17 +10,26 @@
351 from zope.component import getUtility
352 from zope.interface.verify import verifyObject
353
354+from canonical.launchpad.interfaces.lpstorm import IMasterStore
355+from canonical.launchpad.scripts.tests import run_script
356 from canonical.testing.layers import (
357 LaunchpadZopelessLayer,
358 ZopelessDatabaseLayer,
359 )
360+from lp.registry.enum import (
361+ DistroSeriesDifferenceStatus,
362+ DistroSeriesDifferenceType,
363+ )
364+from lp.registry.model.distroseriesdifference import DistroSeriesDifference
365 from lp.services.features.testing import FeatureFixture
366 from lp.services.job.interfaces.job import JobStatus
367-from lp.soyuz.interfaces.distroseriesdifferencejob import (
368+from lp.soyuz.enums import PackagePublishingStatus
369+from lp.soyuz.interfaces.distributionjob import (
370 IDistroSeriesDifferenceJobSource,
371 )
372 from lp.soyuz.model.distroseriesdifferencejob import (
373 create_job,
374+ DistroSeriesDifferenceJob,
375 FEATURE_FLAG_ENABLE_MODULE,
376 find_waiting_jobs,
377 make_metadata,
378@@ -110,7 +119,7 @@
379 sourcepackage = self.factory.makeSourcePackage()
380 distroseries, sourcepackagename = (
381 sourcepackage.distroseries, sourcepackage.distroseries)
382- job = create_job(distroseries, sourcepackagename)
383+ create_job(distroseries, sourcepackagename)
384 other_series = self.factory.makeDistroSeries()
385 self.assertContentEqual(
386 [], find_waiting_jobs(other_series, sourcepackagename))
387@@ -119,7 +128,7 @@
388 sourcepackage = self.factory.makeSourcePackage()
389 distroseries, sourcepackagename = (
390 sourcepackage.distroseries, sourcepackage.distroseries)
391- job = create_job(distroseries, sourcepackagename)
392+ create_job(distroseries, sourcepackagename)
393 other_spn = self.factory.makeSourcePackageName()
394 self.assertContentEqual(
395 [], find_waiting_jobs(distroseries, other_spn))
396@@ -136,22 +145,31 @@
397 self.assertContentEqual(
398 [], find_waiting_jobs(distroseries, sourcepackagename))
399
400- def test_createForPackagedPublication_creates_job_for_parent_series(self):
401+ def test_createForPackagedPublication_creates_jobs_for_its_child(self):
402 derived_series = self.factory.makeDistroSeries(
403 parent_series=self.makeDerivedDistroSeries())
404 package = self.factory.makeSourcePackageName()
405+ # Create a job for the derived_series parent, which should create
406+ # two jobs. One for derived_series, and the other for its child.
407 self.getJobSource().createForPackagePublication(
408- derived_series, package)
409- jobs = list(find_waiting_jobs(derived_series.parent_series, package))
410- self.assertEqual(1, len(jobs))
411+ derived_series.parent_series, package)
412+ jobs = (list(
413+ find_waiting_jobs(derived_series.parent_series, package)) +
414+ list(find_waiting_jobs(derived_series, package)))
415+ self.assertEqual(2, len(jobs))
416 self.assertEqual(package.id, jobs[0].metadata['sourcepackagename'])
417+ self.assertEqual(package.id, jobs[1].metadata['sourcepackagename'])
418+ # Lastly, a job was not created for the grandparent.
419+ jobs = list(
420+ find_waiting_jobs(derived_series.parent_series.parent_series,
421+ package))
422+ self.assertEqual(0, len(jobs))
423
424 def test_createForPackagePublication_creates_job_for_derived_series(self):
425 derived_series = self.makeDerivedDistroSeries()
426- parent_series = derived_series.parent_series
427 package = self.factory.makeSourcePackageName()
428 self.getJobSource().createForPackagePublication(
429- parent_series, package)
430+ derived_series, package)
431 jobs = list(find_waiting_jobs(derived_series, package))
432 self.assertEqual(1, len(jobs))
433 self.assertEqual(package.id, jobs[0].metadata['sourcepackagename'])
434@@ -163,6 +181,242 @@
435 self.getJobSource().createForPackagePublication(distroseries, package)
436 self.assertContentEqual([], find_waiting_jobs(distroseries, package))
437
438+ def test_cronscript(self):
439+ derived_series = self.makeDerivedDistroSeries()
440+ package = self.factory.makeSourcePackageName()
441+ self.getJobSource().createForPackagePublication(
442+ derived_series, package)
443+ transaction.commit() # The cronscript is a different process.
444+ return_code, stdout, stderr = run_script(
445+ 'cronscripts/distroseriesdifference_job.py', ['-v'])
446+ # The cronscript ran how we expected it to.
447+ self.assertEqual(return_code, 0)
448+ self.assertIn(
449+ 'INFO Ran 1 DistroSeriesDifferenceJob jobs.', stderr)
450+ # And it did what we expected.
451+ jobs = list(find_waiting_jobs(derived_series, package))
452+ self.assertEqual(0, len(jobs))
453+ store = IMasterStore(DistroSeriesDifference)
454+ ds_diff = store.find(
455+ DistroSeriesDifference,
456+ DistroSeriesDifference.derived_series == derived_series,
457+ DistroSeriesDifference.source_package_name == package)
458+ self.assertEqual(1, ds_diff.count())
459+
460+ def test_job_runner_does_not_create_multiple_dsds(self):
461+ derived_series = self.makeDerivedDistroSeries()
462+ package = self.factory.makeSourcePackageName()
463+ job = self.getJobSource().createForPackagePublication(
464+ derived_series, package)
465+ job[0].start()
466+ job[0].run()
467+ job[0].job.complete() # So we can create another job.
468+ # The first job would have created a DSD for us.
469+ store = IMasterStore(DistroSeriesDifference)
470+ ds_diff = store.find(
471+ DistroSeriesDifference,
472+ DistroSeriesDifference.derived_series == derived_series,
473+ DistroSeriesDifference.source_package_name == package)
474+ self.assertEqual(1, ds_diff.count())
475+ # If we run the job again, it will not create another DSD.
476+ job = self.getJobSource().createForPackagePublication(
477+ derived_series, package)
478+ job[0].start()
479+ job[0].run()
480+ ds_diff = store.find(
481+ DistroSeriesDifference,
482+ DistroSeriesDifference.derived_series == derived_series,
483+ DistroSeriesDifference.source_package_name == package)
484+ self.assertEqual(1, ds_diff.count())
485+
486+class TestDistroSeriesDifferenceJobEndToEnd(TestCaseWithFactory):
487+
488+ layer = LaunchpadZopelessLayer
489+
490+ def setUp(self):
491+ super(TestDistroSeriesDifferenceJobEndToEnd, self).setUp()
492+ self.useFixture(FeatureFixture({FEATURE_FLAG_ENABLE_MODULE: u'on'}))
493+ self.store = IMasterStore(DistroSeriesDifference)
494+
495+ def getJobSource(self):
496+ return getUtility(IDistroSeriesDifferenceJobSource)
497+
498+ def makeDerivedDistroSeries(self):
499+ return self.factory.makeDistroSeries(
500+ parent_series=self.factory.makeDistroSeries())
501+
502+ def createPublication(self, source_package_name, versions, distroseries):
503+ changelog_lfa = self.factory.makeChangelog(
504+ source_package_name.name, versions)
505+ transaction.commit() # Yay, librarian.
506+ spr = self.factory.makeSourcePackageRelease(
507+ sourcepackagename=source_package_name, version=versions[0],
508+ changelog=changelog_lfa)
509+ return self.factory.makeSourcePackagePublishingHistory(
510+ sourcepackagerelease=spr, archive=distroseries.main_archive,
511+ distroseries=distroseries,
512+ status=PackagePublishingStatus.PUBLISHED)
513+
514+ def findDSD(self, derived_series, source_package_name):
515+ return self.store.find(
516+ DistroSeriesDifference,
517+ DistroSeriesDifference.derived_series == derived_series,
518+ DistroSeriesDifference.source_package_name ==
519+ source_package_name)
520+
521+ def runJob(self, job):
522+ transaction.commit() # Switching DB user performs an abort.
523+ self.layer.switchDbUser('distroseriesdifferencejob')
524+ dsdjob = DistroSeriesDifferenceJob(job)
525+ dsdjob.start()
526+ dsdjob.run()
527+ dsdjob.complete()
528+ transaction.commit() # Switching DB user performs an abort.
529+ self.layer.switchDbUser('launchpad')
530+
531+ def test_parent_gets_newer(self):
532+ # When a new source package is uploaded to the parent distroseries,
533+ # a job is created that updates the relevant DSD.
534+ derived_series = self.makeDerivedDistroSeries()
535+ source_package_name = self.factory.makeSourcePackageName()
536+ self.createPublication(
537+ source_package_name, ['1.0-1derived1', '1.0-1'], derived_series)
538+ self.createPublication(
539+ source_package_name, ['1.0-1'], derived_series.parent_series)
540+ # Creating the SPPHs has created jobs for us, so grab it off the
541+ # queue.
542+ jobs = find_waiting_jobs(derived_series, source_package_name)
543+ self.runJob(jobs[0])
544+ ds_diff = self.findDSD(derived_series, source_package_name)
545+ self.assertEqual(1, ds_diff.count())
546+ self.assertEqual('1.0-1', ds_diff[0].parent_source_version)
547+ self.assertEqual('1.0-1derived1', ds_diff[0].source_version)
548+ self.assertEqual('1.0-1', ds_diff[0].base_version)
549+ # Now create a 1.0-2 upload to the parent.
550+ self.createPublication(
551+ source_package_name, ['1.0-2', '1.0-1'],
552+ derived_series.parent_series)
553+ jobs = find_waiting_jobs(derived_series, source_package_name)
554+ self.runJob(jobs[0])
555+ # And the DSD we have a hold of will have updated.
556+ self.assertEqual('1.0-2', ds_diff[0].parent_source_version)
557+ self.assertEqual('1.0-1derived1', ds_diff[0].source_version)
558+ self.assertEqual('1.0-1', ds_diff[0].base_version)
559+
560+ def test_child_gets_newer(self):
561+ # When a new source is uploaded to the child distroseries, the DSD is
562+ # updated.
563+ derived_series = self.makeDerivedDistroSeries()
564+ source_package_name = self.factory.makeSourcePackageName()
565+ self.createPublication(
566+ source_package_name, ['1.0-1'], derived_series)
567+ self.createPublication(
568+ source_package_name, ['1.0-1'], derived_series.parent_series)
569+ jobs = find_waiting_jobs(derived_series, source_package_name)
570+ self.runJob(jobs[0])
571+ ds_diff = self.findDSD(derived_series, source_package_name)
572+ self.assertEqual(
573+ DistroSeriesDifferenceStatus.RESOLVED, ds_diff[0].status)
574+ self.createPublication(
575+ source_package_name, ['2.0-0derived1', '1.0-1'], derived_series)
576+ jobs = find_waiting_jobs(derived_series, source_package_name)
577+ self.runJob(jobs[0])
578+ self.assertEqual(
579+ DistroSeriesDifferenceStatus.NEEDS_ATTENTION, ds_diff[0].status)
580+ self.assertEqual('1.0-1', ds_diff[0].base_version)
581+
582+ def test_child_is_synced(self):
583+ # If the source package gets 'synced' to the child from the parent,
584+ # the job correctly updates the DSD.
585+ derived_series = self.makeDerivedDistroSeries()
586+ source_package_name = self.factory.makeSourcePackageName()
587+ self.createPublication(
588+ source_package_name, ['1.0-1derived1', '1.0-1'], derived_series)
589+ self.createPublication(
590+ source_package_name, ['1.0-2', '1.0-1'],
591+ derived_series.parent_series)
592+ jobs = find_waiting_jobs(derived_series, source_package_name)
593+ self.runJob(jobs[0])
594+ ds_diff = self.findDSD(derived_series, source_package_name)
595+ self.assertEqual('1.0-1', ds_diff[0].base_version)
596+ self.createPublication(
597+ source_package_name, ['1.0-2', '1.0-1'], derived_series)
598+ jobs = find_waiting_jobs(derived_series, source_package_name)
599+ self.runJob(jobs[0])
600+ self.assertEqual(
601+ DistroSeriesDifferenceStatus.RESOLVED, ds_diff[0].status)
602+
603+ def test_only_in_child(self):
604+ # If a source package only exists in the child distroseries, the DSD
605+ # is created with the right type.
606+ derived_series = self.makeDerivedDistroSeries()
607+ source_package_name = self.factory.makeSourcePackageName()
608+ self.createPublication(
609+ source_package_name, ['1.0-0derived1'], derived_series)
610+ jobs = find_waiting_jobs(derived_series, source_package_name)
611+ self.runJob(jobs[0])
612+ ds_diff = self.findDSD(derived_series, source_package_name)
613+ self.assertEqual(
614+ DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES,
615+ ds_diff[0].difference_type)
616+
617+ def test_only_in_parent(self):
618+ # If a source package only exists in the parent distroseries, the DSD
619+ # is created with the right type.
620+ derived_series = self.makeDerivedDistroSeries()
621+ source_package_name = self.factory.makeSourcePackageName()
622+ self.createPublication(
623+ source_package_name, ['1.0-1'],
624+ derived_series.parent_series)
625+ jobs = find_waiting_jobs(derived_series, source_package_name)
626+ self.runJob(jobs[0])
627+ ds_diff = self.findDSD(derived_series, source_package_name)
628+ self.assertEqual(
629+ DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES,
630+ ds_diff[0].difference_type)
631+
632+ def test_deleted_in_parent(self):
633+ # If a source package is deleted in the parent, a job is created, and
634+ # the DSD is updated correctly.
635+ derived_series = self.makeDerivedDistroSeries()
636+ source_package_name = self.factory.makeSourcePackageName()
637+ self.createPublication(
638+ source_package_name, ['1.0-1'], derived_series)
639+ spph = self.createPublication(
640+ source_package_name, ['1.0-1'], derived_series.parent_series)
641+ jobs = find_waiting_jobs(derived_series, source_package_name)
642+ self.runJob(jobs[0])
643+ ds_diff = self.findDSD(derived_series, source_package_name)
644+ self.assertEqual(
645+ DistroSeriesDifferenceStatus.RESOLVED, ds_diff[0].status)
646+ spph.requestDeletion(self.factory.makePerson())
647+ jobs = find_waiting_jobs(derived_series, source_package_name)
648+ self.runJob(jobs[0])
649+ self.assertEqual(
650+ DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES,
651+ ds_diff[0].difference_type)
652+
653+ def test_deleted_in_child(self):
654+ # If a source package is deleted in the child, a job is created, and
655+ # the DSD is updated correctly.
656+ derived_series = self.makeDerivedDistroSeries()
657+ source_package_name = self.factory.makeSourcePackageName()
658+ spph = self.createPublication(
659+ source_package_name, ['1.0-1'], derived_series)
660+ self.createPublication(
661+ source_package_name, ['1.0-1'], derived_series.parent_series)
662+ jobs = find_waiting_jobs(derived_series, source_package_name)
663+ self.runJob(jobs[0])
664+ ds_diff = self.findDSD(derived_series, source_package_name)
665+ self.assertEqual(
666+ DistroSeriesDifferenceStatus.RESOLVED, ds_diff[0].status)
667+ spph.requestDeletion(self.factory.makePerson())
668+ jobs = find_waiting_jobs(derived_series, source_package_name)
669+ self.runJob(jobs[0])
670+ self.assertEqual(
671+ DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES,
672+ ds_diff[0].difference_type)
673+
674
675 class TestDistroSeriesDifferenceJobPermissions(TestCaseWithFactory):
676 """Database permissions test for `DistroSeriesDifferenceJob`."""