Merge ~pelpsi/launchpad:snap-component-integration into launchpad:master
- Git
- lp:~pelpsi/launchpad
- snap-component-integration
- Merge into master
Status: | Merged |
---|---|
Approved by: | Simone Pelosi |
Approved revision: | 21ff0a40b83935aee230066ffa104dff5802cd4c |
Merged at revision: | 9176ff43d948b8a895c2b2689721dbb16415e9c9 |
Proposed branch: | ~pelpsi/launchpad:snap-component-integration |
Merge into: | launchpad:master |
Diff against target: |
527 lines (+343/-15) 4 files modified
lib/lp/snappy/interfaces/snapbuildjob.py (+12/-1) lib/lp/snappy/model/snapbuildjob.py (+56/-1) lib/lp/snappy/model/snapstoreclient.py (+7/-3) lib/lp/snappy/tests/test_snapbuildjob.py (+268/-10) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Ines Almeida | Abstain | ||
Review via email: mp+461063@code.launchpad.net |
Commit message
Add Snap Component support
Reference: LP142
Snap Components are processed and uploaded to the
snapcraft storage before pushing the Snap to the store.
Once every component is updated on the storage we can push
the Snap to the store.
push` function changed to support the new `components` parameter.
Description of the change
Simone Pelosi (pelpsi) wrote : | # |
Thank you Ines for your precious review! I addressed your comments and I replied to the ones that I didn't addressed with the reason :)
William Grant (wgrant) : | # |
Ines Almeida (ines-almeida) wrote : | # |
Thanks for addressing my comments! Only an extra question.
I will mark my review as "abstain" because I will be off for a few days and don't want to block this MP from being merged if it gets approved.
Simone Pelosi (pelpsi) wrote : | # |
Thank you for your precious reviews, I firstly addressed some of your comments here, and I'm working to remove snapcraft.yaml logic to search for components as suggested by William. I will use the build files to retrieve them.
I also had a chat with Przemysław, and he clarified that snap_name cannot contain `+` nor `_`, the same as component_name. So it should be easier to extract the component name in that context `<snap_
William Grant (wgrant) : | # |
Preview Diff
1 | diff --git a/lib/lp/snappy/interfaces/snapbuildjob.py b/lib/lp/snappy/interfaces/snapbuildjob.py |
2 | index 12e4b8b..90a8d37 100644 |
3 | --- a/lib/lp/snappy/interfaces/snapbuildjob.py |
4 | +++ b/lib/lp/snappy/interfaces/snapbuildjob.py |
5 | @@ -13,7 +13,7 @@ __all__ = [ |
6 | from lazr.restful.fields import Reference |
7 | from zope.interface import Attribute, Interface |
8 | from zope.interface.interfaces import IObjectEvent |
9 | -from zope.schema import Int, TextLine |
10 | +from zope.schema import Dict, Int, TextLine |
11 | |
12 | from lp import _ |
13 | from lp.services.job.interfaces.job import IJob, IJobSource, IRunnableJob |
14 | @@ -68,6 +68,17 @@ class ISnapStoreUploadJob(IRunnableJob): |
15 | readonly=True, |
16 | ) |
17 | |
18 | + components_ids = Dict( |
19 | + title=_( |
20 | + "The IDs returned by the store when uploading snap components." |
21 | + "The key is the component name and the value is the related id." |
22 | + ), |
23 | + key_type=TextLine(), |
24 | + value_type=TextLine(), |
25 | + required=False, |
26 | + readonly=True, |
27 | + ) |
28 | + |
29 | status_url = TextLine( |
30 | title=_("The URL on the store to get the status of this build"), |
31 | required=False, |
32 | diff --git a/lib/lp/snappy/model/snapbuildjob.py b/lib/lp/snappy/model/snapbuildjob.py |
33 | index 539fb1c..9a73f64 100644 |
34 | --- a/lib/lp/snappy/model/snapbuildjob.py |
35 | +++ b/lib/lp/snappy/model/snapbuildjob.py |
36 | @@ -244,6 +244,18 @@ class SnapStoreUploadJob(SnapBuildJobDerived): |
37 | self.snapbuild.store_upload_metadata["upload_id"] = upload_id |
38 | |
39 | @property |
40 | + def components_ids(self): |
41 | + """See `ISnapStoreUploadJob`.""" |
42 | + return self.store_metadata.get("components_ids") |
43 | + |
44 | + @components_ids.setter |
45 | + def components_ids(self, components_ids): |
46 | + """See `ISnapStoreUploadJob`.""" |
47 | + if self.snapbuild.store_upload_metadata is None: |
48 | + self.snapbuild.store_upload_metadata = {} |
49 | + self.snapbuild.store_upload_metadata["components_ids"] = components_ids |
50 | + |
51 | + @property |
52 | def status_url(self): |
53 | """See `ISnapStoreUploadJob`.""" |
54 | return self.store_metadata.get("status_url") |
55 | @@ -333,6 +345,22 @@ class SnapStoreUploadJob(SnapBuildJobDerived): |
56 | pass |
57 | return timedelta(minutes=1) |
58 | |
59 | + @staticmethod |
60 | + def _extract_component_name(filename): |
61 | + """Extract component name from built component filename |
62 | + |
63 | + The built filename has the following pattern: |
64 | + <snap_name>+<component_name>_<component_revision>.comp. |
65 | + <snap_name> and <component_name> cannot contain `+` nor `_` |
66 | + by desing. |
67 | + """ |
68 | + start = filename.find("+") |
69 | + end = filename.find("_") |
70 | + if start == -1 or end == -1: |
71 | + return filename |
72 | + else: |
73 | + return filename[start + 1 : end] |
74 | + |
75 | def run(self): |
76 | """See `IRunnableJob`.""" |
77 | client = getUtility(ISnapStoreClient) |
78 | @@ -350,16 +378,43 @@ class SnapStoreUploadJob(SnapBuildJobDerived): |
79 | # Nothing to do. |
80 | self.error_message = None |
81 | return |
82 | + |
83 | + # Get components if any |
84 | + components = [] |
85 | + components_ids = {} |
86 | + for _, lfa, _ in self.snapbuild.getFiles(): |
87 | + if lfa.filename.endswith(".comp"): |
88 | + comp_name = self._extract_component_name(lfa.filename) |
89 | + components_ids[comp_name] = None |
90 | + components.append(lfa) |
91 | + |
92 | + if "components_ids" not in self.store_metadata: |
93 | + self.components_ids = components_ids |
94 | if "upload_id" not in self.store_metadata: |
95 | self.upload_id = client.uploadFile(snap_lfa) |
96 | # We made progress, so reset attempt_count. |
97 | self.attempt_count = 1 |
98 | + |
99 | + # Process components |
100 | + for component in components: |
101 | + # if the id is None, we need to upload the component |
102 | + # because it means that that component was never uploaded. |
103 | + # Note that the id is returned directly from SnapStore API. |
104 | + comp_name = self._extract_component_name( |
105 | + component.filename |
106 | + ) |
107 | + if self.components_ids.get(comp_name) is None: |
108 | + self.components_ids[comp_name] = client.uploadFile( |
109 | + component |
110 | + ) |
111 | + self.attempt_count = 1 |
112 | if "status_url" not in self.store_metadata: |
113 | self.status_url = client.push( |
114 | - self.snapbuild, self.upload_id |
115 | + self.snapbuild, self.upload_id, self.components_ids |
116 | ) |
117 | # We made progress, so reset attempt_count. |
118 | self.attempt_count = 1 |
119 | + |
120 | if self.store_url is None: |
121 | # This is no longer strictly necessary as the store is |
122 | # handling releases via the release intent, but we |
123 | diff --git a/lib/lp/snappy/model/snapstoreclient.py b/lib/lp/snappy/model/snapstoreclient.py |
124 | index 536ba17..98d2753 100644 |
125 | --- a/lib/lp/snappy/model/snapstoreclient.py |
126 | +++ b/lib/lp/snappy/model/snapstoreclient.py |
127 | @@ -293,7 +293,7 @@ class SnapStoreClient: |
128 | lfa.close() |
129 | |
130 | @classmethod |
131 | - def _push(cls, snapbuild, upload_id): |
132 | + def _push(cls, snapbuild, upload_id, components=None): |
133 | """Create a new store upload based on the uploaded file.""" |
134 | snap = snapbuild.snap |
135 | assert snap.can_upload_to_store |
136 | @@ -305,6 +305,10 @@ class SnapStoreClient: |
137 | "series": snap.store_series.name, |
138 | "built_at": snapbuild.date_started.isoformat(), |
139 | } |
140 | + |
141 | + if components: |
142 | + data["components"] = components |
143 | + |
144 | # The security proxy is useless and breaks JSON serialisation. |
145 | channels = removeSecurityProxy(snap.store_channels) |
146 | if channels: |
147 | @@ -343,10 +347,10 @@ class SnapStoreClient: |
148 | raise cls._makeSnapStoreError(UploadFailedResponse, e) |
149 | |
150 | @classmethod |
151 | - def push(cls, snapbuild, upload_id): |
152 | + def push(cls, snapbuild, upload_id, components=None): |
153 | """See `ISnapStoreClient`.""" |
154 | return cls.refreshIfNecessary( |
155 | - snapbuild.snap, cls._push, snapbuild, upload_id |
156 | + snapbuild.snap, cls._push, snapbuild, upload_id, components |
157 | ) |
158 | |
159 | @classmethod |
160 | diff --git a/lib/lp/snappy/tests/test_snapbuildjob.py b/lib/lp/snappy/tests/test_snapbuildjob.py |
161 | index 7e69f3c..d5239ae 100644 |
162 | --- a/lib/lp/snappy/tests/test_snapbuildjob.py |
163 | +++ b/lib/lp/snappy/tests/test_snapbuildjob.py |
164 | @@ -18,6 +18,7 @@ from zope.interface import implementer |
165 | from zope.security.proxy import removeSecurityProxy |
166 | |
167 | from lp.buildmaster.enums import BuildStatus |
168 | +from lp.code.tests.helpers import BranchHostingFixture |
169 | from lp.services.config import config |
170 | from lp.services.database.interfaces import IStore |
171 | from lp.services.job.interfaces.job import JobStatus |
172 | @@ -61,10 +62,30 @@ def run_isolated_jobs(jobs): |
173 | transaction.abort() |
174 | |
175 | |
176 | +class FakeMethodWithMaxCalls(FakeMethod): |
177 | + """Method that throws error after a given number of calls""" |
178 | + |
179 | + def __init__(self, max_calls=-1): |
180 | + self.max_calls = max_calls |
181 | + super().__init__(result=1, failure=None) |
182 | + |
183 | + def __call__(self, *args, **kwargs): |
184 | + if self.call_count == self.max_calls: |
185 | + self.failure = UploadFailedResponse("Proxy error", can_retry=True) |
186 | + self.result = None |
187 | + else: |
188 | + self.result = 1 |
189 | + self.failure = None |
190 | + return super().__call__(*args, **kwargs) |
191 | + |
192 | + |
193 | @implementer(ISnapStoreClient) |
194 | class FakeSnapStoreClient: |
195 | - def __init__(self): |
196 | - self.uploadFile = FakeMethod() |
197 | + def __init__(self, max_calls=-1): |
198 | + if max_calls >= 0: |
199 | + self.uploadFile = FakeMethodWithMaxCalls(max_calls) |
200 | + else: |
201 | + self.uploadFile = FakeMethod() |
202 | self.push = FakeMethod() |
203 | self.checkStatus = FakeMethod() |
204 | |
205 | @@ -144,6 +165,49 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
206 | ) |
207 | return snapbuild |
208 | |
209 | + def makeSnapBuildWithComponents(self, num_components): |
210 | + # Make a build with a builder, components, and a webhook. |
211 | + snapcraft_yaml = """ |
212 | + name : test-snap, |
213 | + components: |
214 | + """ |
215 | + branch = self.factory.makeBranch() |
216 | + snap = self.factory.makeSnap(store_name="test-snap", branch=branch) |
217 | + snapbuild = self.factory.makeSnapBuild( |
218 | + snap=snap, builder=self.factory.makeBuilder() |
219 | + ) |
220 | + snapbuild.updateStatus(BuildStatus.FULLYBUILT) |
221 | + snap_lfa = self.factory.makeLibraryFileAlias( |
222 | + filename="test-snap_0_all.snap", content=b"dummy snap content" |
223 | + ) |
224 | + for i in range(num_components): |
225 | + self.factory.makeSnapFile( |
226 | + snapbuild=snapbuild, libraryfile=snap_lfa |
227 | + ) |
228 | + component_lfa = self.factory.makeLibraryFileAlias( |
229 | + filename="test-snap+somecomponent%s_0.comp" % i, |
230 | + content=b"component", |
231 | + ) |
232 | + self.factory.makeSnapFile( |
233 | + snapbuild=snapbuild, libraryfile=component_lfa |
234 | + ) |
235 | + snapcraft_yaml += ( |
236 | + """ |
237 | + somecomponent%s: |
238 | + description: test |
239 | + """ |
240 | + % i |
241 | + ) |
242 | + self.useFixture( |
243 | + BranchHostingFixture( |
244 | + blob=bytes(snapcraft_yaml, "utf-8"), |
245 | + ) |
246 | + ) |
247 | + self.factory.makeWebhook( |
248 | + target=snapbuild.snap, event_types=["snap:build:0.1"] |
249 | + ) |
250 | + return snapbuild |
251 | + |
252 | def assertWebhookDeliveries( |
253 | self, snapbuild, expected_store_upload_statuses, logger |
254 | ): |
255 | @@ -207,7 +271,101 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
256 | self.assertThat( |
257 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
258 | ) |
259 | - self.assertEqual([((snapbuild, 1), {})], client.push.calls) |
260 | + self.assertEqual([((snapbuild, 1, {}), {})], client.push.calls) |
261 | + self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
262 | + self.assertContentEqual([job], snapbuild.store_upload_jobs) |
263 | + self.assertEqual(self.store_url, job.store_url) |
264 | + self.assertEqual(1, job.store_revision) |
265 | + self.assertEqual(1, snapbuild.store_upload_revision) |
266 | + self.assertIsNone(job.error_message) |
267 | + self.assertEqual([], pop_notifications()) |
268 | + self.assertWebhookDeliveries( |
269 | + snapbuild, ["Pending", "Uploaded"], logger |
270 | + ) |
271 | + |
272 | + def test_run_with_component(self): |
273 | + # The job uploads the build to the storage with its component |
274 | + # and then pushes it to the store and records the store URL |
275 | + # and revision. |
276 | + logger = self.useFixture(FakeLogger()) |
277 | + snapbuild = self.makeSnapBuildWithComponents(1) |
278 | + self.assertContentEqual([], snapbuild.store_upload_jobs) |
279 | + job = SnapStoreUploadJob.create(snapbuild) |
280 | + client = FakeSnapStoreClient() |
281 | + client.uploadFile.result = 1 |
282 | + client.push.result = self.status_url |
283 | + client.checkStatus.result = (self.store_url, 1) |
284 | + self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) |
285 | + with dbuser(config.ISnapStoreUploadJobSource.dbuser): |
286 | + run_isolated_jobs([job]) |
287 | + |
288 | + # Check if uploadFile is called for snap and its component. |
289 | + self.assertThat( |
290 | + [client.uploadFile.calls[0]], FileUploaded("test-snap_0_all.snap") |
291 | + ) |
292 | + self.assertThat( |
293 | + [client.uploadFile.calls[1]], |
294 | + FileUploaded("test-snap+somecomponent0_0.comp"), |
295 | + ) |
296 | + self.assertEqual( |
297 | + [((snapbuild, 1, {"somecomponent0": 1}), {})], |
298 | + client.push.calls, |
299 | + ) |
300 | + self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
301 | + self.assertContentEqual([job], snapbuild.store_upload_jobs) |
302 | + self.assertEqual(self.store_url, job.store_url) |
303 | + self.assertEqual(1, job.store_revision) |
304 | + self.assertEqual(1, snapbuild.store_upload_revision) |
305 | + self.assertIsNone(job.error_message) |
306 | + self.assertEqual([], pop_notifications()) |
307 | + self.assertWebhookDeliveries( |
308 | + snapbuild, ["Pending", "Uploaded"], logger |
309 | + ) |
310 | + |
311 | + def test_run_with_multiple_components(self): |
312 | + # The job uploads the build to the storage with its components |
313 | + # and then pushes it to the store and records the store URL |
314 | + # and revision. |
315 | + logger = self.useFixture(FakeLogger()) |
316 | + snapbuild = self.makeSnapBuildWithComponents(2) |
317 | + self.assertContentEqual([], snapbuild.store_upload_jobs) |
318 | + job = SnapStoreUploadJob.create(snapbuild) |
319 | + client = FakeSnapStoreClient() |
320 | + client.uploadFile.result = 1 |
321 | + client.push.result = self.status_url |
322 | + client.checkStatus.result = (self.store_url, 1) |
323 | + self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) |
324 | + with dbuser(config.ISnapStoreUploadJobSource.dbuser): |
325 | + run_isolated_jobs([job]) |
326 | + |
327 | + # Check if uploadFile is called for snap and its components. |
328 | + self.assertThat( |
329 | + [client.uploadFile.calls[0]], FileUploaded("test-snap_0_all.snap") |
330 | + ) |
331 | + self.assertThat( |
332 | + [client.uploadFile.calls[1]], |
333 | + FileUploaded("test-snap+somecomponent0_0.comp"), |
334 | + ) |
335 | + self.assertThat( |
336 | + [client.uploadFile.calls[2]], |
337 | + FileUploaded("test-snap+somecomponent1_0.comp"), |
338 | + ) |
339 | + self.assertEqual( |
340 | + [ |
341 | + ( |
342 | + ( |
343 | + snapbuild, |
344 | + 1, |
345 | + { |
346 | + "somecomponent0": 1, |
347 | + "somecomponent1": 1, |
348 | + }, |
349 | + ), |
350 | + {}, |
351 | + ) |
352 | + ], |
353 | + client.push.calls, |
354 | + ) |
355 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
356 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
357 | self.assertEqual(self.store_url, job.store_url) |
358 | @@ -268,7 +426,7 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
359 | self.assertThat( |
360 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
361 | ) |
362 | - self.assertEqual([((snapbuild, 1), {})], client.push.calls) |
363 | + self.assertEqual([((snapbuild, 1, {}), {})], client.push.calls) |
364 | self.assertEqual([], client.checkStatus.calls) |
365 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
366 | self.assertIsNone(job.store_url) |
367 | @@ -313,6 +471,106 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
368 | snapbuild, ["Pending", "Failed to upload"], logger |
369 | ) |
370 | |
371 | + def test_run_502_retries_with_components(self): |
372 | + # A run that gets a 502 error from the store schedules itself to be |
373 | + # retried. |
374 | + logger = self.useFixture(FakeLogger()) |
375 | + snapbuild = self.makeSnapBuildWithComponents(2) |
376 | + self.assertContentEqual([], snapbuild.store_upload_jobs) |
377 | + job = SnapStoreUploadJob.create(snapbuild) |
378 | + |
379 | + # The error raises on the third uploadFile call. |
380 | + client = FakeSnapStoreClient(2) |
381 | + client.uploadFile.result = 1 |
382 | + self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) |
383 | + with dbuser(config.ISnapStoreUploadJobSource.dbuser): |
384 | + run_isolated_jobs([job]) |
385 | + self.assertThat( |
386 | + [client.uploadFile.calls[0]], FileUploaded("test-snap_0_all.snap") |
387 | + ) |
388 | + self.assertThat( |
389 | + [client.uploadFile.calls[1]], |
390 | + FileUploaded("test-snap+somecomponent0_0.comp"), |
391 | + ) |
392 | + self.assertThat( |
393 | + [client.uploadFile.calls[2]], |
394 | + FileUploaded("test-snap+somecomponent1_0.comp"), |
395 | + ) |
396 | + self.assertEqual([], client.push.calls) |
397 | + self.assertEqual([], client.checkStatus.calls) |
398 | + self.assertContentEqual([job], snapbuild.store_upload_jobs) |
399 | + |
400 | + # Component upload is incomplete. |
401 | + self.assertEqual( |
402 | + { |
403 | + "somecomponent0": 1, |
404 | + "somecomponent1": None, |
405 | + }, |
406 | + job.components_ids, |
407 | + ) |
408 | + self.assertIsNone(job.store_url) |
409 | + self.assertIsNone(job.store_revision) |
410 | + self.assertIsNone(snapbuild.store_upload_revision) |
411 | + self.assertIsNone(job.error_message) |
412 | + self.assertEqual([], pop_notifications()) |
413 | + self.assertEqual(JobStatus.WAITING, job.job.status) |
414 | + self.assertWebhookDeliveries(snapbuild, ["Pending"], logger) |
415 | + |
416 | + # Try again. The upload part of the job is retried, and this time |
417 | + # it succeeds. |
418 | + job.scheduled_start = None |
419 | + client.uploadFile.max_calls = -1 |
420 | + client.uploadFile.failure = None |
421 | + client.uploadFile.result = 1 |
422 | + client.push.result = self.status_url |
423 | + client.checkStatus.result = (self.store_url, 1) |
424 | + with dbuser(config.ISnapStoreUploadJobSource.dbuser): |
425 | + run_isolated_jobs([job]) |
426 | + |
427 | + # The last call should contains just the component that failed. |
428 | + self.assertEqual(len(client.uploadFile.calls), 4) |
429 | + self.assertThat( |
430 | + [client.uploadFile.calls[3]], |
431 | + FileUploaded("test-snap+somecomponent1_0.comp"), |
432 | + ) |
433 | + |
434 | + self.assertEqual( |
435 | + { |
436 | + "somecomponent0": 1, |
437 | + "somecomponent1": 1, |
438 | + }, |
439 | + job.components_ids, |
440 | + ) |
441 | + |
442 | + # After that the snap is uploaded to the store. |
443 | + self.assertEqual( |
444 | + [ |
445 | + ( |
446 | + ( |
447 | + snapbuild, |
448 | + 1, |
449 | + { |
450 | + "somecomponent0": 1, |
451 | + "somecomponent1": 1, |
452 | + }, |
453 | + ), |
454 | + {}, |
455 | + ) |
456 | + ], |
457 | + client.push.calls, |
458 | + ) |
459 | + self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
460 | + self.assertContentEqual([job], snapbuild.store_upload_jobs) |
461 | + self.assertEqual(self.store_url, job.store_url) |
462 | + self.assertEqual(1, job.store_revision) |
463 | + self.assertEqual(1, snapbuild.store_upload_revision) |
464 | + self.assertIsNone(job.error_message) |
465 | + self.assertEqual([], pop_notifications()) |
466 | + self.assertEqual(JobStatus.COMPLETED, job.job.status) |
467 | + self.assertWebhookDeliveries( |
468 | + snapbuild, ["Pending", "Uploaded"], logger |
469 | + ) |
470 | + |
471 | def test_run_502_retries(self): |
472 | # A run that gets a 502 error from the store schedules itself to be |
473 | # retried. |
474 | @@ -353,7 +611,7 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
475 | self.assertThat( |
476 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
477 | ) |
478 | - self.assertEqual([((snapbuild, 1), {})], client.push.calls) |
479 | + self.assertEqual([((snapbuild, 1, {}), {})], client.push.calls) |
480 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
481 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
482 | self.assertEqual(self.store_url, job.store_url) |
483 | @@ -388,7 +646,7 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
484 | self.assertThat( |
485 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
486 | ) |
487 | - self.assertEqual([((snapbuild, 1), {})], client.push.calls) |
488 | + self.assertEqual([((snapbuild, 1, {}), {})], client.push.calls) |
489 | self.assertEqual([], client.checkStatus.calls) |
490 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
491 | self.assertIsNone(job.store_url) |
492 | @@ -524,7 +782,7 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
493 | self.assertThat( |
494 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
495 | ) |
496 | - self.assertEqual([((snapbuild, 2), {})], client.push.calls) |
497 | + self.assertEqual([((snapbuild, 2, {}), {})], client.push.calls) |
498 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
499 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
500 | self.assertIsNone(job.store_url) |
501 | @@ -586,7 +844,7 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
502 | self.assertThat( |
503 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
504 | ) |
505 | - self.assertEqual([((snapbuild, 2), {})], client.push.calls) |
506 | + self.assertEqual([((snapbuild, 2, {}), {})], client.push.calls) |
507 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
508 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
509 | self.assertIsNone(job.store_url) |
510 | @@ -655,7 +913,7 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
511 | self.assertThat( |
512 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
513 | ) |
514 | - self.assertEqual([((snapbuild, 1), {})], client.push.calls) |
515 | + self.assertEqual([((snapbuild, 1, {}), {})], client.push.calls) |
516 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
517 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
518 | self.assertIsNone(job.store_url) |
519 | @@ -684,7 +942,7 @@ class TestSnapStoreUploadJob(TestCaseWithFactory): |
520 | self.assertThat( |
521 | client.uploadFile.calls, FileUploaded("test-snap.snap") |
522 | ) |
523 | - self.assertEqual([((snapbuild, 1), {})], client.push.calls) |
524 | + self.assertEqual([((snapbuild, 1, {}), {})], client.push.calls) |
525 | self.assertEqual([((self.status_url,), {})], client.checkStatus.calls) |
526 | self.assertContentEqual([job], snapbuild.store_upload_jobs) |
527 | self.assertEqual(self.store_url, job.store_url) |
Great work!
I added some questions and some comments that need addressing.
Apologies for taking so long to review this MP