Merge lp:~cjwatson/launchpad/snap-build-created-webhook into lp:launchpad
- snap-build-created-webhook
- Merge into devel
Proposed by
Colin Watson
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 18771 | ||||
Proposed branch: | lp:~cjwatson/launchpad/snap-build-created-webhook | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
498 lines (+152/-30) 12 files modified
lib/lp/snappy/configure.zcml (+4/-0) lib/lp/snappy/interfaces/snap.py (+6/-2) lib/lp/snappy/interfaces/snapbuild.py (+9/-1) lib/lp/snappy/interfaces/snapjob.py (+8/-1) lib/lp/snappy/model/snap.py (+7/-4) lib/lp/snappy/model/snapbuild.py (+13/-3) lib/lp/snappy/model/snapjob.py (+6/-1) lib/lp/snappy/subscribers/snapbuild.py (+12/-6) lib/lp/snappy/tests/test_snap.py (+73/-4) lib/lp/snappy/tests/test_snapbuild.py (+3/-0) lib/lp/snappy/tests/test_snapbuildjob.py (+3/-1) lib/lp/snappy/tests/test_snapjob.py (+8/-7) |
||||
To merge this branch: | bzr merge lp:~cjwatson/launchpad/snap-build-created-webhook | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+352306@code.launchpad.net |
Commit message
Issue snap:build:0.1 webhook delivery when creating a snap build, and include a link to the build request in the payload.
Description of the change
I need this as part of converting build.snapcraft.io over to the new snap.requestBuilds interface; it needs to be able to issue a build request, keep a note of the reason it did so, and then retrieve that when it receives a webhook delivery indicating that the build has been created. (The alternatives are to have BSI's request-builds endpoint poll until the builds are created, which is obviously horrible, or to lose the build annotations.)
This requires https:/
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/snappy/configure.zcml' |
2 | --- lib/lp/snappy/configure.zcml 2018-06-15 13:21:14 +0000 |
3 | +++ lib/lp/snappy/configure.zcml 2018-08-03 14:22:02 +0000 |
4 | @@ -63,6 +63,10 @@ |
5 | </class> |
6 | <subscriber |
7 | for="lp.snappy.interfaces.snapbuild.ISnapBuild |
8 | + lazr.lifecycle.interfaces.IObjectCreatedEvent" |
9 | + handler="lp.snappy.subscribers.snapbuild.snap_build_created" /> |
10 | + <subscriber |
11 | + for="lp.snappy.interfaces.snapbuild.ISnapBuild |
12 | lp.snappy.interfaces.snapbuild.ISnapBuildStatusChangedEvent" |
13 | handler="lp.snappy.subscribers.snapbuild.snap_build_status_changed" /> |
14 | |
15 | |
16 | === modified file 'lib/lp/snappy/interfaces/snap.py' |
17 | --- lib/lp/snappy/interfaces/snap.py 2018-06-18 22:08:58 +0000 |
18 | +++ lib/lp/snappy/interfaces/snap.py 2018-08-03 14:22:02 +0000 |
19 | @@ -365,7 +365,7 @@ |
20 | @export_factory_operation(Interface, []) |
21 | @operation_for_version("devel") |
22 | def requestBuild(requester, archive, distro_arch_series, pocket, |
23 | - channels=None): |
24 | + channels=None, build_request=None): |
25 | """Request that the snap package be built. |
26 | |
27 | :param requester: The person requesting the build. |
28 | @@ -374,6 +374,8 @@ |
29 | :param pocket: The pocket that should be targeted. |
30 | :param channels: A dictionary mapping snap names to channels to use |
31 | for this build. |
32 | + :param build_request: The `ISnapBuildRequest` job being processed, |
33 | + if any. |
34 | :return: `ISnapBuild`. |
35 | """ |
36 | |
37 | @@ -407,7 +409,7 @@ |
38 | |
39 | def requestBuildsFromJob(requester, archive, pocket, channels=None, |
40 | allow_failures=False, fetch_snapcraft_yaml=True, |
41 | - logger=None): |
42 | + build_request=None, logger=None): |
43 | """Synchronous part of `Snap.requestBuilds`. |
44 | |
45 | Request that the snap package be built for relevant architectures. |
46 | @@ -424,6 +426,8 @@ |
47 | appropriate branch or repository and use it to decide which |
48 | builds to request; if False, fall back to building for all |
49 | supported architectures. |
50 | + :param build_request: The `ISnapBuildRequest` job being processed, |
51 | + if any. |
52 | :param logger: An optional logger. |
53 | :return: A sequence of `ISnapBuild` instances. |
54 | """ |
55 | |
56 | === modified file 'lib/lp/snappy/interfaces/snapbuild.py' |
57 | --- lib/lp/snappy/interfaces/snapbuild.py 2018-03-22 16:48:16 +0000 |
58 | +++ lib/lp/snappy/interfaces/snapbuild.py 2018-08-03 14:22:02 +0000 |
59 | @@ -52,7 +52,10 @@ |
60 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
61 | from lp.services.database.constants import DEFAULT |
62 | from lp.services.librarian.interfaces import ILibraryFileAlias |
63 | -from lp.snappy.interfaces.snap import ISnap |
64 | +from lp.snappy.interfaces.snap import ( |
65 | + ISnap, |
66 | + ISnapBuildRequest, |
67 | + ) |
68 | from lp.soyuz.interfaces.archive import IArchive |
69 | from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries |
70 | |
71 | @@ -122,6 +125,11 @@ |
72 | class ISnapBuildView(IPackageBuild): |
73 | """`ISnapBuild` attributes that require launchpad.View permission.""" |
74 | |
75 | + build_request = Reference( |
76 | + ISnapBuildRequest, |
77 | + title=_("The build request that caused this build to be created."), |
78 | + required=False, readonly=True) |
79 | + |
80 | requester = exported(Reference( |
81 | IPerson, |
82 | title=_("The person who requested this build."), |
83 | |
84 | === modified file 'lib/lp/snappy/interfaces/snapjob.py' |
85 | --- lib/lp/snappy/interfaces/snapjob.py 2018-06-15 13:21:14 +0000 |
86 | +++ lib/lp/snappy/interfaces/snapjob.py 2018-08-03 14:22:02 +0000 |
87 | @@ -32,7 +32,10 @@ |
88 | IJobSource, |
89 | IRunnableJob, |
90 | ) |
91 | -from lp.snappy.interfaces.snap import ISnap |
92 | +from lp.snappy.interfaces.snap import ( |
93 | + ISnap, |
94 | + ISnapBuildRequest, |
95 | + ) |
96 | from lp.snappy.interfaces.snapbuild import ISnapBuild |
97 | from lp.soyuz.interfaces.archive import IArchive |
98 | |
99 | @@ -78,6 +81,10 @@ |
100 | title=_("Error message resulting from running this job."), |
101 | required=False, readonly=True) |
102 | |
103 | + build_request = Reference( |
104 | + title=_("The build request corresponding to this job."), |
105 | + schema=ISnapBuildRequest, required=True, readonly=True) |
106 | + |
107 | builds = List( |
108 | title=_("The builds created by this request."), |
109 | value_type=Reference(schema=ISnapBuild), required=True, readonly=True) |
110 | |
111 | === modified file 'lib/lp/snappy/model/snap.py' |
112 | --- lib/lp/snappy/model/snap.py 2018-07-30 09:07:30 +0000 |
113 | +++ lib/lp/snappy/model/snap.py 2018-08-03 14:22:02 +0000 |
114 | @@ -14,6 +14,7 @@ |
115 | from operator import attrgetter |
116 | from urlparse import urlsplit |
117 | |
118 | +from lazr.lifecycle.event import ObjectCreatedEvent |
119 | from pymacaroons import Macaroon |
120 | import pytz |
121 | from storm.expr import ( |
122 | @@ -39,6 +40,7 @@ |
123 | getAdapter, |
124 | getUtility, |
125 | ) |
126 | +from zope.event import notify |
127 | from zope.interface import implementer |
128 | from zope.security.interfaces import Unauthorized |
129 | from zope.security.proxy import removeSecurityProxy |
130 | @@ -521,7 +523,7 @@ |
131 | raise SnapBuildArchiveOwnerMismatch() |
132 | |
133 | def requestBuild(self, requester, archive, distro_arch_series, pocket, |
134 | - channels=None): |
135 | + channels=None, build_request=None): |
136 | """See `ISnap`.""" |
137 | self._checkRequestBuild(requester, archive) |
138 | if distro_arch_series not in self.getAllowedArchitectures(): |
139 | @@ -540,8 +542,9 @@ |
140 | |
141 | build = getUtility(ISnapBuildSet).new( |
142 | requester, self, archive, distro_arch_series, pocket, |
143 | - channels=channels) |
144 | + channels=channels, build_request=build_request) |
145 | build.queueBuild() |
146 | + notify(ObjectCreatedEvent(build, user=requester)) |
147 | return build |
148 | |
149 | def requestBuilds(self, requester, archive, pocket, channels=None): |
150 | @@ -553,7 +556,7 @@ |
151 | |
152 | def requestBuildsFromJob(self, requester, archive, pocket, channels=None, |
153 | allow_failures=False, fetch_snapcraft_yaml=True, |
154 | - logger=None): |
155 | + build_request=None, logger=None): |
156 | """See `ISnap`.""" |
157 | if fetch_snapcraft_yaml: |
158 | try: |
159 | @@ -585,7 +588,7 @@ |
160 | try: |
161 | build = self.requestBuild( |
162 | requester, archive, supported_arches[arch], pocket, |
163 | - channels) |
164 | + channels, build_request=build_request) |
165 | if logger is not None: |
166 | logger.debug( |
167 | " - %s/%s/%s: Build requested.", |
168 | |
169 | === modified file 'lib/lp/snappy/model/snapbuild.py' |
170 | --- lib/lp/snappy/model/snapbuild.py 2018-03-22 16:48:16 +0000 |
171 | +++ lib/lp/snappy/model/snapbuild.py 2018-08-03 14:22:02 +0000 |
172 | @@ -128,6 +128,8 @@ |
173 | build_farm_job_id = Int(name='build_farm_job', allow_none=False) |
174 | build_farm_job = Reference(build_farm_job_id, 'BuildFarmJob.id') |
175 | |
176 | + build_request_id = Int(name='build_request', allow_none=True) |
177 | + |
178 | requester_id = Int(name='requester', allow_none=False) |
179 | requester = Reference(requester_id, 'Person.id') |
180 | |
181 | @@ -175,7 +177,7 @@ |
182 | |
183 | def __init__(self, build_farm_job, requester, snap, archive, |
184 | distro_arch_series, pocket, channels, processor, virtualized, |
185 | - date_created): |
186 | + date_created, build_request=None): |
187 | """Construct a `SnapBuild`.""" |
188 | super(SnapBuild, self).__init__() |
189 | self.build_farm_job = build_farm_job |
190 | @@ -188,9 +190,17 @@ |
191 | self.processor = processor |
192 | self.virtualized = virtualized |
193 | self.date_created = date_created |
194 | + if build_request is not None: |
195 | + self.build_request_id = build_request.id |
196 | self.status = BuildStatus.NEEDSBUILD |
197 | |
198 | @property |
199 | + def build_request(self): |
200 | + """See `ISnapBuild`.""" |
201 | + if self.build_request_id is not None: |
202 | + return self.snap.getBuildRequest(self.build_request_id) |
203 | + |
204 | + @property |
205 | def is_private(self): |
206 | """See `IBuildFarmJob`.""" |
207 | return ( |
208 | @@ -488,7 +498,7 @@ |
209 | class SnapBuildSet(SpecificBuildFarmJobSourceMixin): |
210 | |
211 | def new(self, requester, snap, archive, distro_arch_series, pocket, |
212 | - channels=None, date_created=DEFAULT): |
213 | + channels=None, date_created=DEFAULT, build_request=None): |
214 | """See `ISnapBuildSet`.""" |
215 | store = IMasterStore(SnapBuild) |
216 | build_farm_job = getUtility(IBuildFarmJobSource).new( |
217 | @@ -499,7 +509,7 @@ |
218 | pocket, channels, distro_arch_series.processor, |
219 | not distro_arch_series.processor.supports_nonvirtualized |
220 | or snap.require_virtualized or archive.require_virtualized, |
221 | - date_created) |
222 | + date_created, build_request=build_request) |
223 | store.add(snapbuild) |
224 | return snapbuild |
225 | |
226 | |
227 | === modified file 'lib/lp/snappy/model/snapjob.py' |
228 | --- lib/lp/snappy/model/snapjob.py 2018-06-15 13:21:14 +0000 |
229 | +++ lib/lp/snappy/model/snapjob.py 2018-08-03 14:22:02 +0000 |
230 | @@ -243,6 +243,11 @@ |
231 | self.metadata["error_message"] = message |
232 | |
233 | @property |
234 | + def build_request(self): |
235 | + """See `ISnapRequestBuildsJob`.""" |
236 | + return self.snap.getBuildRequest(self.job.id) |
237 | + |
238 | + @property |
239 | def builds(self): |
240 | """See `ISnapRequestBuildsJob`.""" |
241 | build_ids = self.metadata.get("builds") |
242 | @@ -272,7 +277,7 @@ |
243 | try: |
244 | self.builds = self.snap.requestBuildsFromJob( |
245 | requester, archive, self.pocket, channels=self.channels, |
246 | - logger=log) |
247 | + build_request=self.build_request, logger=log) |
248 | self.error_message = None |
249 | except self.retry_error_types: |
250 | raise |
251 | |
252 | === modified file 'lib/lp/snappy/subscribers/snapbuild.py' |
253 | --- lib/lp/snappy/subscribers/snapbuild.py 2017-03-20 00:03:52 +0000 |
254 | +++ lib/lp/snappy/subscribers/snapbuild.py 2018-08-03 14:22:02 +0000 |
255 | @@ -1,4 +1,4 @@ |
256 | -# Copyright 2016-2017 Canonical Ltd. This software is licensed under the |
257 | +# Copyright 2016-2018 Canonical Ltd. This software is licensed under the |
258 | # GNU Affero General Public License version 3 (see the file LICENSE). |
259 | |
260 | """Event subscribers for snap builds.""" |
261 | @@ -19,21 +19,27 @@ |
262 | from lp.snappy.interfaces.snapbuildjob import ISnapStoreUploadJobSource |
263 | |
264 | |
265 | -def _trigger_snap_build_webhook(snapbuild): |
266 | +def _trigger_snap_build_webhook(snapbuild, action): |
267 | if getFeatureFlag(SNAP_WEBHOOKS_FEATURE_FLAG): |
268 | payload = { |
269 | "snap_build": canonical_url(snapbuild, force_local_path=True), |
270 | - "action": "status-changed", |
271 | + "action": action, |
272 | } |
273 | payload.update(compose_webhook_payload( |
274 | - ISnapBuild, snapbuild, ["snap", "status", "store_upload_status"])) |
275 | + ISnapBuild, snapbuild, |
276 | + ["snap", "build_request", "status", "store_upload_status"])) |
277 | getUtility(IWebhookSet).trigger( |
278 | snapbuild.snap, "snap:build:0.1", payload) |
279 | |
280 | |
281 | +def snap_build_created(snapbuild, event): |
282 | + """Trigger events when a new snap package build is created.""" |
283 | + _trigger_snap_build_webhook(snapbuild, "created") |
284 | + |
285 | + |
286 | def snap_build_status_changed(snapbuild, event): |
287 | """Trigger events when snap package build statuses change.""" |
288 | - _trigger_snap_build_webhook(snapbuild) |
289 | + _trigger_snap_build_webhook(snapbuild, "status-changed") |
290 | |
291 | if (snapbuild.snap.can_upload_to_store and snapbuild.snap.store_upload and |
292 | snapbuild.status == BuildStatus.FULLYBUILT): |
293 | @@ -42,4 +48,4 @@ |
294 | |
295 | def snap_build_store_upload_status_changed(snapbuild, event): |
296 | """Trigger events when snap package build store upload statuses change.""" |
297 | - _trigger_snap_build_webhook(snapbuild) |
298 | + _trigger_snap_build_webhook(snapbuild, "status-changed") |
299 | |
300 | === modified file 'lib/lp/snappy/tests/test_snap.py' |
301 | --- lib/lp/snappy/tests/test_snap.py 2018-07-30 09:07:30 +0000 |
302 | +++ lib/lp/snappy/tests/test_snap.py 2018-08-03 14:22:02 +0000 |
303 | @@ -76,6 +76,7 @@ |
304 | from lp.services.propertycache import clear_property_cache |
305 | from lp.services.timeout import default_timeout |
306 | from lp.services.webapp.interfaces import OAuthPermission |
307 | +from lp.services.webapp.publisher import canonical_url |
308 | from lp.snappy.interfaces.snap import ( |
309 | BadSnapSearchContext, |
310 | CannotFetchSnapcraftYaml, |
311 | @@ -368,6 +369,38 @@ |
312 | snap.owner, snap.distro_series.main_archive, distroarchseries, |
313 | PackagePublishingPocket.UPDATES) |
314 | |
315 | + def test_requestBuild_triggers_webhooks(self): |
316 | + # Requesting a build triggers webhooks. |
317 | + processor = self.factory.makeProcessor(supports_virtualized=True) |
318 | + distroarchseries = self.makeBuildableDistroArchSeries( |
319 | + processor=processor) |
320 | + snap = self.factory.makeSnap( |
321 | + distroseries=distroarchseries.distroseries, processors=[processor]) |
322 | + hook = self.factory.makeWebhook( |
323 | + target=snap, event_types=["snap:build:0.1"]) |
324 | + build = snap.requestBuild( |
325 | + snap.owner, snap.distro_series.main_archive, distroarchseries, |
326 | + PackagePublishingPocket.UPDATES) |
327 | + expected_payload = { |
328 | + "snap_build": Equals(canonical_url(build, force_local_path=True)), |
329 | + "action": Equals("created"), |
330 | + "snap": Equals(canonical_url(snap, force_local_path=True)), |
331 | + "build_request": Is(None), |
332 | + "status": Equals("Needs building"), |
333 | + "store_upload_status": Equals("Unscheduled"), |
334 | + } |
335 | + with person_logged_in(snap.owner): |
336 | + delivery = hook.deliveries.one() |
337 | + self.assertThat( |
338 | + delivery, MatchesStructure( |
339 | + event_type=Equals("snap:build:0.1"), |
340 | + payload=MatchesDict(expected_payload))) |
341 | + with dbuser(config.IWebhookDeliveryJobSource.dbuser): |
342 | + self.assertEqual( |
343 | + "<WebhookDeliveryJob for webhook %d on %r>" % ( |
344 | + hook.id, hook.target), |
345 | + repr(delivery)) |
346 | + |
347 | def test_requestBuilds(self): |
348 | # requestBuilds schedules a job and returns a corresponding |
349 | # SnapBuildRequest. |
350 | @@ -437,7 +470,8 @@ |
351 | with person_logged_in(job.requester): |
352 | builds = job.snap.requestBuildsFromJob( |
353 | job.requester, job.archive, job.pocket, |
354 | - removeSecurityProxy(job.channels)) |
355 | + removeSecurityProxy(job.channels), |
356 | + build_request=job.build_request) |
357 | self.assertRequestedBuildsMatch(builds, job, ["sparc"]) |
358 | |
359 | def test_requestBuildsFromJob_no_explicit_architectures(self): |
360 | @@ -449,10 +483,11 @@ |
361 | with person_logged_in(job.requester): |
362 | builds = job.snap.requestBuildsFromJob( |
363 | job.requester, job.archive, job.pocket, |
364 | - removeSecurityProxy(job.channels)) |
365 | + removeSecurityProxy(job.channels), |
366 | + build_request=job.build_request) |
367 | self.assertRequestedBuildsMatch(builds, job, ["mips64el", "riscv64"]) |
368 | |
369 | - def test_requestBuilds_unsupported_remote(self): |
370 | + def test_requestBuildsFromJob_unsupported_remote(self): |
371 | # If the snap is based on an external Git repository from which we |
372 | # don't support fetching blobs, requestBuildsFromJob falls back to |
373 | # requesting builds for all configured architectures. |
374 | @@ -463,9 +498,43 @@ |
375 | with person_logged_in(job.requester): |
376 | builds = job.snap.requestBuildsFromJob( |
377 | job.requester, job.archive, job.pocket, |
378 | - removeSecurityProxy(job.channels)) |
379 | + removeSecurityProxy(job.channels), |
380 | + build_request=job.build_request) |
381 | self.assertRequestedBuildsMatch(builds, job, ["mips64el", "riscv64"]) |
382 | |
383 | + def test_requestBuildsFromJob_triggers_webhooks(self): |
384 | + # requestBuildsFromJob triggers webhooks, and the payload includes a |
385 | + # link to the build request. |
386 | + self.useFixture(GitHostingFixture(blob=dedent("""\ |
387 | + architectures: |
388 | + - build-on: avr |
389 | + - build-on: riscv64 |
390 | + """))) |
391 | + job = self.makeRequestBuildsJob(["avr", "riscv64", "sparc"]) |
392 | + hook = self.factory.makeWebhook( |
393 | + target=job.snap, event_types=["snap:build:0.1"]) |
394 | + with person_logged_in(job.requester): |
395 | + builds = job.snap.requestBuildsFromJob( |
396 | + job.requester, job.archive, job.pocket, |
397 | + removeSecurityProxy(job.channels), |
398 | + build_request=job.build_request) |
399 | + self.assertEqual(2, len(builds)) |
400 | + self.assertThat(hook.deliveries, MatchesSetwise(*( |
401 | + MatchesStructure( |
402 | + event_type=Equals("snap:build:0.1"), |
403 | + payload=MatchesDict({ |
404 | + "snap_build": Equals(canonical_url( |
405 | + build, force_local_path=True)), |
406 | + "action": Equals("created"), |
407 | + "snap": Equals(canonical_url( |
408 | + job.snap, force_local_path=True)), |
409 | + "build_request": Equals(canonical_url( |
410 | + job.build_request, force_local_path=True)), |
411 | + "status": Equals("Needs building"), |
412 | + "store_upload_status": Equals("Unscheduled"), |
413 | + })) |
414 | + for build in builds))) |
415 | + |
416 | def test_requestAutoBuilds(self): |
417 | # requestAutoBuilds creates new builds for all configured |
418 | # architectures with appropriate parameters. |
419 | |
420 | === modified file 'lib/lp/snappy/tests/test_snapbuild.py' |
421 | --- lib/lp/snappy/tests/test_snapbuild.py 2018-02-08 12:47:44 +0000 |
422 | +++ lib/lp/snappy/tests/test_snapbuild.py 2018-08-03 14:22:02 +0000 |
423 | @@ -20,6 +20,7 @@ |
424 | import pytz |
425 | from testtools.matchers import ( |
426 | Equals, |
427 | + Is, |
428 | MatchesDict, |
429 | MatchesStructure, |
430 | ) |
431 | @@ -241,6 +242,7 @@ |
432 | "action": Equals("status-changed"), |
433 | "snap": Equals( |
434 | canonical_url(self.build.snap, force_local_path=True)), |
435 | + "build_request": Is(None), |
436 | "status": Equals("Successfully built"), |
437 | "store_upload_status": Equals("Unscheduled"), |
438 | } |
439 | @@ -516,6 +518,7 @@ |
440 | "action": Equals("status-changed"), |
441 | "snap": Equals( |
442 | canonical_url(self.build.snap, force_local_path=True)), |
443 | + "build_request": Is(None), |
444 | "status": Equals("Successfully built"), |
445 | "store_upload_status": Equals("Pending"), |
446 | } |
447 | |
448 | === modified file 'lib/lp/snappy/tests/test_snapbuildjob.py' |
449 | --- lib/lp/snappy/tests/test_snapbuildjob.py 2018-02-19 17:48:22 +0000 |
450 | +++ lib/lp/snappy/tests/test_snapbuildjob.py 2018-08-03 14:22:02 +0000 |
451 | @@ -1,4 +1,4 @@ |
452 | -# Copyright 2016-2017 Canonical Ltd. This software is licensed under the |
453 | +# Copyright 2016-2018 Canonical Ltd. This software is licensed under the |
454 | # GNU Affero General Public License version 3 (see the file LICENSE). |
455 | |
456 | """Tests for snap build jobs.""" |
457 | @@ -12,6 +12,7 @@ |
458 | from fixtures import FakeLogger |
459 | from testtools.matchers import ( |
460 | Equals, |
461 | + Is, |
462 | MatchesDict, |
463 | MatchesListwise, |
464 | MatchesStructure, |
465 | @@ -125,6 +126,7 @@ |
466 | "action": Equals("status-changed"), |
467 | "snap": Equals( |
468 | canonical_url(snapbuild.snap, force_local_path=True)), |
469 | + "build_request": Is(None), |
470 | "status": Equals("Successfully built"), |
471 | "store_upload_status": Equals(expected), |
472 | } for expected in expected_store_upload_statuses] |
473 | |
474 | === modified file 'lib/lp/snappy/tests/test_snapjob.py' |
475 | --- lib/lp/snappy/tests/test_snapjob.py 2018-06-15 12:54:27 +0000 |
476 | +++ lib/lp/snappy/tests/test_snapjob.py 2018-08-03 14:22:02 +0000 |
477 | @@ -113,13 +113,14 @@ |
478 | job=MatchesStructure.byEquality(status=JobStatus.COMPLETED), |
479 | error_message=Is(None), |
480 | builds=AfterPreprocessing(set, MatchesSetwise(*[ |
481 | - MatchesStructure.byEquality( |
482 | - requester=snap.registrant, |
483 | - snap=snap, |
484 | - archive=distroseries.main_archive, |
485 | - distro_arch_series=distroseries[arch], |
486 | - pocket=PackagePublishingPocket.RELEASE, |
487 | - channels={"core": "stable"}) |
488 | + MatchesStructure( |
489 | + build_request=MatchesStructure.byEquality(id=job.job.id), |
490 | + requester=Equals(snap.registrant), |
491 | + snap=Equals(snap), |
492 | + archive=Equals(distroseries.main_archive), |
493 | + distro_arch_series=Equals(distroseries[arch]), |
494 | + pocket=Equals(PackagePublishingPocket.RELEASE), |
495 | + channels=Equals({"core": "stable"})) |
496 | for arch in ("avr2001", "x32")])))) |
497 | |
498 | def test_run_failed(self): |