1
# Copyright 2011 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Tests for `CreateDistroSeriesIndexesJob`."""
13
from zope.component import getUtility
14
from zope.security.proxy import removeSecurityProxy
16
from canonical.config import config
17
from canonical.launchpad.interfaces.lpstorm import IMasterStore
18
from canonical.launchpad.webapp.testing import verifyObject
19
from canonical.testing.layers import LaunchpadZopelessLayer
20
from lp.archivepublisher.config import getPubConfig
21
from lp.archivepublisher.interfaces.createdistroseriesindexesjob import (
22
ICreateDistroSeriesIndexesJobSource,
24
from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
25
from lp.archivepublisher.model.createdistroseriesindexesjob import (
26
FEATURE_FLAG_ENABLE_MODULE,
27
CreateDistroSeriesIndexesJob,
29
from lp.registry.interfaces.pocket import pocketsuffix
30
from lp.services.job.model.job import Job
31
from lp.services.features.testing import FeatureFixture
32
from lp.services.job.interfaces.job import (
36
from lp.services.job.runner import JobCronScript
37
from lp.services.log.logger import DevNullLogger
38
from lp.services.mail import stub
39
from lp.services.mail.sendmail import format_address_for_person
40
from lp.services.utils import file_exists
41
from lp.soyuz.enums import ArchivePurpose
42
from lp.soyuz.interfaces.distributionjob import IDistributionJob
43
from lp.soyuz.model.distributionjob import DistributionJob
44
from lp.testing import TestCaseWithFactory
45
from lp.testing.fakemethod import FakeMethod
46
from lp.testing.mail_helpers import run_mail_jobs
49
def silence_publisher_logger():
50
"""Silence the logger that `run_publisher` creates."""
51
getLogger("publish-distro").setLevel(FATAL)
54
class TestCreateDistroSeriesIndexesJobSource(TestCaseWithFactory):
57
layer = LaunchpadZopelessLayer
60
super(TestCreateDistroSeriesIndexesJobSource, self).setUp()
61
self.useFixture(FeatureFixture({FEATURE_FLAG_ENABLE_MODULE: u'on'}))
63
def removePublisherConfig(self, distribution):
64
"""Strip `distribution` of its publisher configuration."""
65
publisher_config = getUtility(IPublisherConfigSet).getByDistribution(
67
IMasterStore(publisher_config).remove(publisher_config)
69
def test_baseline(self):
70
# The utility conforms to the interfaces it claims to implement.
71
jobsource = getUtility(ICreateDistroSeriesIndexesJobSource)
73
verifyObject(ICreateDistroSeriesIndexesJobSource, jobsource))
75
def test_creates_job_for_distro_with_publisher_config(self):
76
# The utility can create a job if the distribution has a
77
# publisher configuration.
78
distroseries = self.factory.makeDistroSeries()
79
jobset = getUtility(ICreateDistroSeriesIndexesJobSource)
80
job = jobset.makeFor(distroseries)
81
self.assertIsInstance(job, CreateDistroSeriesIndexesJob)
83
def test_does_not_create_job_for_distro_without_publisher_config(self):
84
# If the distribution has no publisher configuration, the
85
# utility creates no job for it.
86
distroseries = self.factory.makeDistroSeries()
87
self.removePublisherConfig(distroseries.distribution)
88
jobset = getUtility(ICreateDistroSeriesIndexesJobSource)
89
job = jobset.makeFor(distroseries)
90
self.assertIs(None, job)
92
def test_feature_flag_disables_feature(self):
93
# The creation of jobs is controlled by a feature flag.
94
self.useFixture(FeatureFixture({FEATURE_FLAG_ENABLE_MODULE: u''}))
95
jobset = getUtility(ICreateDistroSeriesIndexesJobSource)
96
self.assertIs(None, jobset.makeFor(self.factory.makeDistroSeries()))
99
class HorribleFailure(Exception):
100
"""A sample error for testing purposes."""
103
class TestCreateDistroSeriesIndexesJob(TestCaseWithFactory):
104
"""Test job class."""
106
layer = LaunchpadZopelessLayer
109
super(TestCreateDistroSeriesIndexesJob, self).setUp()
110
self.useFixture(FeatureFixture({FEATURE_FLAG_ENABLE_MODULE: u'on'}))
112
def getJobSource(self):
113
"""Shorthand for getting at the job-source utility."""
114
return getUtility(ICreateDistroSeriesIndexesJobSource)
116
def makeJob(self, distroseries=None):
117
"""Create an `CreateDistroSeriesIndexesJob`."""
118
if distroseries is None:
119
distroseries = self.factory.makeDistroSeries()
120
return self.getJobSource().makeFor(distroseries)
122
def getDistsRoot(self, distribution):
123
"""Get distsroot directory for `distribution`."""
124
archive = removeSecurityProxy(distribution.main_archive)
125
pub_config = getPubConfig(archive)
126
return pub_config.distsroot
128
def makeDistsDirs(self, distroseries):
129
"""Create dists directories in `distsroot` for `distroseries`."""
130
distsroot = self.getDistsRoot(distroseries.distribution)
131
base = os.path.join(distsroot, distroseries.name)
132
for suffix in pocketsuffix.itervalues():
133
os.makedirs(base + suffix)
135
def makeCredibleJob(self):
136
"""Create a job with fixtures required for running it."""
137
silence_publisher_logger()
138
distro = self.factory.makeDistribution(
139
publish_root_dir=unicode(self.makeTemporaryDirectory()))
140
distroseries = self.factory.makeDistroSeries(distribution=distro)
141
self.makeDistsDirs(distroseries)
142
return self.makeJob(distroseries)
144
def becomeArchivePublisher(self):
145
"""Become the archive publisher database user (and clean up later)."""
146
self.becomeDbUser(config.archivepublisher.dbuser)
147
self.addCleanup(self.becomeDbUser, 'launchpad')
149
def getSuites(self, distroseries):
150
"""Get the list of suites for `distroseries`."""
152
distroseries.name + suffix
153
for suffix in pocketsuffix.itervalues()]
155
def test_baseline(self):
156
# The job class conforms to the interfaces it claims to implement.
158
self.assertTrue(verifyObject(IRunnableJob, job))
159
self.assertTrue(verifyObject(IDistributionJob, job))
161
def test_getSuites_identifies_distroseries_suites(self):
162
# getSuites lists all suites in the distroseries.
164
self.assertContentEqual(
165
self.getSuites(job.distroseries),
166
removeSecurityProxy(job).getSuites())
168
def test_getSuites_ignores_suites_for_other_distroseries(self):
169
# getSuites does not list suites in the distribution that do not
170
# belong to the right distroseries.
172
self.assertContentEqual(
173
self.getSuites(job.distroseries),
174
removeSecurityProxy(job).getSuites())
176
def test_job_runs_publish_distro_for_main(self):
177
# The job always runs publish_distro for the distribution's main
180
naked_job = removeSecurityProxy(job)
181
naked_job.runPublishDistro = FakeMethod()
183
args, kwargs = naked_job.runPublishDistro.calls[-1]
184
self.assertEqual((), args)
186
def test_job_runs_publish_distro_for_partner_if_present(self):
187
# If the distribution has a partner archive, the job will run
188
# publish_distro for it. This differs from the run for the main
189
# archive in that publish_distro receives the --partner option.
190
distroseries = self.factory.makeDistroSeries()
191
self.factory.makeArchive(
192
distribution=distroseries.distribution,
193
purpose=ArchivePurpose.PARTNER)
194
job = self.makeJob(distroseries)
195
naked_job = removeSecurityProxy(job)
196
naked_job.runPublishDistro = FakeMethod()
200
[args for args, kwargs in naked_job.runPublishDistro.calls])
202
def test_job_does_not_run_publish_distro_for_partner_if_not_present(self):
203
# If the distribution does not have a partner archive,
204
# publish_distro is not run for the partner archive.
206
naked_job = removeSecurityProxy(job)
207
naked_job.runPublishDistro = FakeMethod()
209
self.assertEqual(1, naked_job.runPublishDistro.call_count)
211
def test_job_notifies_if_successful(self):
212
# Once the indexes have been created, the job calls its
213
# notifySuccess method to let stakeholders know that they may
214
# proceed with their release process.
216
naked_job = removeSecurityProxy(job)
217
naked_job.runPublishDistro = FakeMethod()
218
naked_job.notifySuccess = FakeMethod()
220
self.assertEqual(1, naked_job.notifySuccess.call_count)
222
def test_failure_notifies_recipients(self):
223
# Failure notices are sent to the addresses returned by
226
removeSecurityProxy(job).getMailRecipients = FakeMethod(
227
result=["foo@example.com"])
228
job.notifyUserError(HorribleFailure("Boom!"))
230
sender, recipients, body = stub.test_emails.pop()
231
self.assertIn("foo@example.com", recipients)
233
def test_success_notifies_recipients(self):
234
# Success notices are sent to the addresses returned by
237
naked_job = removeSecurityProxy(job)
238
naked_job.getMailRecipients = FakeMethod(result=["bar@example.com"])
239
naked_job.notifySuccess()
241
sender, recipients, body = stub.test_emails.pop()
242
self.assertIn("bar@example.com", recipients)
244
def test_notifySuccess_sends_email(self):
245
# notifySuccess sends out a success notice by email.
247
removeSecurityProxy(job).notifySuccess()
249
sender, recipients, body = stub.test_emails.pop()
250
self.assertIn("success", body)
252
def test_release_manager_gets_notified(self):
253
# The release manager gets notified. This role is represented
254
# by the driver for the distroseries.
255
distroseries = self.factory.makeDistroSeries()
256
distroseries.driver = self.factory.makePerson()
257
job = self.makeJob(distroseries)
259
format_address_for_person(distroseries.driver),
260
removeSecurityProxy(job).getMailRecipients())
262
def test_distribution_owner_gets_notified_if_no_release_manager(self):
263
# If no release manager is available, the distribution owners
265
distroseries = self.factory.makeDistroSeries()
266
distroseries.driver = None
267
job = self.makeJob(distroseries)
269
format_address_for_person(distroseries.distribution.owner),
270
removeSecurityProxy(job).getMailRecipients())
272
def test_destroySelf_destroys_job(self):
274
job_id = removeSecurityProxy(job).job.id
275
self.becomeArchivePublisher()
277
store = IMasterStore(Job)
278
self.assertIs(None, store.find(Job, Job.id == job_id).one())
280
def test_destroySelf_destroys_DistributionJob(self):
283
self.becomeArchivePublisher()
285
store = IMasterStore(DistributionJob)
288
store.find(DistributionJob, DistributionJob.id == job_id).one())
290
def test_run_does_the_job(self):
291
# The job runs publish_distro and generates the expected output
293
job = self.makeCredibleJob()
294
self.becomeArchivePublisher()
296
distsroot = self.getDistsRoot(job.distribution)
297
output = os.path.join(distsroot, job.distroseries.name, "Release")
298
self.assertTrue(file_exists(output))
300
def test_job_runner_runs_jobs(self):
301
# The generic job runner can set itself up to run these jobs.
302
job = self.makeCredibleJob()
303
script = JobCronScript(
304
test_args=["create_distroseries_indexes"],
305
commandline_config=True)
306
script.logger = DevNullLogger()
309
JobStatus.COMPLETED, removeSecurityProxy(job).context.job.status)