Merge lp:~sil2100/ubuntu-release-upgrader/snap-size-estimation into lp:ubuntu-release-upgrader
- snap-size-estimation
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 3280 |
Proposed branch: | lp:~sil2100/ubuntu-release-upgrader/snap-size-estimation |
Merge into: | lp:ubuntu-release-upgrader |
Diff against target: |
440 lines (+321/-38) 3 files modified
DistUpgrade/DistUpgradeCache.py (+2/-0) DistUpgrade/DistUpgradeQuirks.py (+115/-38) tests/test_quirks.py (+204/-0) |
To merge this branch: | bzr merge lp:~sil2100/ubuntu-release-upgrader/snap-size-estimation |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brian Murray | Needs Fixing | ||
Review via email: mp+371120@code.launchpad.net |
Commit message
Modify DistUpgradeQuirks to calculate extra space needed for snaps that need to be installed during upgrade (replacing old debs) and add that to estimates done in checkFreeSpace().
Description of the change
Modify DistUpgradeQuirks to calculate extra space needed for snaps that need to be installed during upgrade (replacing old debs) and add that to estimates done in checkFreeSpace().
This branch is now ready for an initial review. I had a few design bikeshedding issues while working on this, so I'm still not entirely happy with the end result. For instance, I don't like how it looks with the consecutive quirks assuming the previous ones set up everything for them - but that seemed to be optimal.
I added all the estimation as a quirk, alongside of the existing ones for replacing debs with snaps. Seemed to be 'consistent' with the current approach. In cases where quirks are not ran, the extra_snap_space is 0 so nothing funny is happening. I also added some very basic unit testing for the new methods added. They're not very pretty, mostly because a lot of the functionality had to be mocked as it required network access, which we don't want to rely on in unit testing.
For fetching the size, I try to be as accurate as possible - which is why I am performing raw API calls to the snapstore as per their recommendation. I could not use snap_info as we need to get the size of the snap for the specific branch (stable/ubuntu-x.x) - and only a fake snap_refresh allows that (which needs the snap-id specified). At first I wanted to be consistent and just use `snap info`, but as said - the command didn't seem to allow specifying the branch that needed to be checked.
I'm open to any propositions. This is my first contribution to u-r-u so apologies if the coding style is not adequate. I still need to test it in practice, which is why I'd prefer if we didn't merge it yet before more testing.
Łukasz Zemczak (sil2100) wrote : | # |
Brian Murray (brian-murray) wrote : | # |
Looking at DistUpgradeQuir
Brian Murray (brian-murray) wrote : | # |
I think it'd also be good if DistUpgradeCache.py were to log that the not enough free space error in /var was due to the fact that extra_snap_space was size X.
Brian Murray (brian-murray) wrote : | # |
Thanks for working on this! You'll find some specific comments in line.
- 3280. By Łukasz Zemczak
-
Fixes as per Brian's review: do not create a PreCalcDistUpgrade quirk (no needed), fix typos.
Łukasz Zemczak (sil2100) wrote : | # |
Thanks for the review! I will address all the issues you have outlined, with comments to a few of them (not including those inline as the diff will change and it's always a bit more annoying to switch to the old revision to see the comments):
1) Regarding PreCalcDistUpgrade: actually I see that I can just do the same in PostInitialUpdate, not sure why those weeks ago I didn't find that one appropriate. I just know any of the later ones would not do, as it has to happen early in the upgrade process. One plus side of having a PreCalcDistUpgrade was that it all felt a bit more consistent, as the size calculation was done right before the dist-upgrade was 'calculated'. I changed it to be done as part of PostInitialUpdate now which is also fine, but the only downside is that now very early users might be seeing 'Calculating snap size requirements', which might seem a bit out-of-place. On the other hand, that doesn't actually matter much I'd say!
2) Regarding changing the 'installing ...' strings as you requested - won't that break translations? I mean, I'm usually very reluctant in changing translatable strings without a valid reason. Guess this particular change won't be backported to bionic though, so maybe it's actually safe. Anyway, I guess I'd recommend changing those in a separate MP/commit.
3) Regarding Snap-Device-Series: for now this is a constant that I think can just stay hard-coded. One might think it's related to the core base (16, 18 etc.) - that would be logical, right? But actually it's not. I was confused about that as well, but the snapd team told me this series is actually unrelated to the snap base but is instead like a snap-specific standard version which should be only bumped when the standard changes, which for now is still constant. So this argument is '16' for both core16, core18 and possibly the upcoming core20.
4) Regarding the count of 5 in the test for replacing debs with snaps: I guess it feels correct, because basically currently as well as previously when determining which debs need to be exchanged with snaps, at the stage of when the quirk is running there was never any logic to check if we're replacing a real deb or not. So for instance 'gtk-common-
I can only assume that the upgrader actually checks if the packages in forced_obsoletes are actual packages that need acting on somewhere later on.
5) Regarding the new dependencies and adding those to debian/control: I think no new dependencies need to be added. Both the json and urllib modules are part of the python standard library, so they basically come from the libpython3.*-stdlib etc. packages, and those are explicit dependencies of python3.*. So I guess we should be safe here, right?
Łukasz Zemczak (sil2100) wrote : | # |
Was also looking into making DistUpgradeCache mentioning the not enough space being because of extra_snap_space, but from what I see the way it's written right now feels very generic and not sure if I should change that. But I'll look into it a bit more.
Brian Murray (brian-murray) wrote : | # |
> Thanks for the review! I will address all the issues you have outlined, with
> comments to a few of them (not including those inline as the diff will change
> and it's always a bit more annoying to switch to the old revision to see the
> comments):
>
> 1) Regarding PreCalcDistUpgrade: actually I see that I can just do the same in
> PostInitialUpdate, not sure why those weeks ago I didn't find that one
> appropriate. I just know any of the later ones would not do, as it has to
> happen early in the upgrade process. One plus side of having a
> PreCalcDistUpgrade was that it all felt a bit more consistent, as the size
> calculation was done right before the dist-upgrade was 'calculated'. I changed
> it to be done as part of PostInitialUpdate now which is also fine, but the
> only downside is that now very early users might be seeing 'Calculating snap
> size requirements', which might seem a bit out-of-place. On the other hand,
> that doesn't actually matter much I'd say!
Great, thanks.
> 2) Regarding changing the 'installing ...' strings as you requested - won't
> that break translations? I mean, I'm usually very reluctant in changing
> translatable strings without a valid reason. Guess this particular change
> won't be backported to bionic though, so maybe it's actually safe. Anyway, I
> guess I'd recommend changing those in a separate MP/commit.
That's fair and not a big deal really.
> 3) Regarding Snap-Device-Series: for now this is a constant that I think can
> just stay hard-coded. One might think it's related to the core base (16, 18
> etc.) - that would be logical, right? But actually it's not. I was confused
> about that as well, but the snapd team told me this series is actually
> unrelated to the snap base but is instead like a snap-specific standard
> version which should be only bumped when the standard changes, which for now
> is still constant. So this argument is '16' for both core16, core18 and
> possibly the upcoming core20.
Alright, thanks for explaining this!
> 4) Regarding the count of 5 in the test for replacing debs with snaps: I guess
> it feels correct, because basically currently as well as previously when
> determining which debs need to be exchanged with snaps, at the stage of when
> the quirk is running there was never any logic to check if we're replacing a
> real deb or not. So for instance 'gtk-common-
> package, in the case when its snap was not installed on the system, would
> anyway mark it for installation and put it into forced_obsoletes. The logic
> here didn't change. What the unit test checks is only if the 'refresh' snaps
> are refreshed and if the 'install' snaps are installed (and, per the existing
> logic, added to the forced_obsoletes).
> I can only assume that the upgrader actually checks if the packages in
> forced_obsoletes are actual packages that need acting on somewhere later on.
That makes sense, maybe its worth adding a comment about why it is 5 though?
> 5) Regarding the new dependencies and adding those to debian/control: I think
> no new dependencies need to be added. Both the json and urllib modules are
> part of the python standard library, so the...
Brian Murray (brian-murray) wrote : | # |
This look good to me now, but as I mentioned on irc I'd expect the size calculation to fail for the core18 snap because there is no stable/ubuntu-19.10 channel for core18. It'd be good to test that this is really failing before fixing bug 1841225.
- 3281. By Łukasz Zemczak
-
Add a better comment regarding the forced_obsoletes check in quirks.
Łukasz Zemczak (sil2100) wrote : | # |
I did some testing on a VM and real upgrade scenarios and with this branch and the core18-stable follow up one, everything seems to work as expected. Let me merge both in this case.
Preview Diff
1 | === modified file 'DistUpgrade/DistUpgradeCache.py' |
2 | --- DistUpgrade/DistUpgradeCache.py 2019-06-21 17:46:27 +0000 |
3 | +++ DistUpgrade/DistUpgradeCache.py 2019-08-30 14:54:30 +0000 |
4 | @@ -1157,6 +1157,8 @@ |
5 | # sum up space requirements |
6 | for (dir, size) in [(archivedir, self.required_download), |
7 | ("/usr", self.additional_required_space), |
8 | + # this is only >0 for the deb-to-snap quirks |
9 | + ("/var", self.quirks.extra_snap_space), |
10 | # plus 50M safety buffer in /usr |
11 | ("/usr", 50*1024*1024), |
12 | ("/boot", space_in_boot), |
13 | |
14 | === modified file 'DistUpgrade/DistUpgradeQuirks.py' |
15 | --- DistUpgrade/DistUpgradeQuirks.py 2019-06-24 18:16:09 +0000 |
16 | +++ DistUpgrade/DistUpgradeQuirks.py 2019-08-30 14:54:30 +0000 |
17 | @@ -49,8 +49,12 @@ |
18 | self.uname = Popen(["uname", "-r"], stdout=PIPE, |
19 | universal_newlines=True).communicate()[0].strip() |
20 | self.arch = get_arch() |
21 | + self.extra_snap_space = 0 |
22 | self._poke = None |
23 | self._snapstore_reachable = False |
24 | + self._snap_list = None |
25 | + self._from_version = None |
26 | + self._to_version = None |
27 | |
28 | # the quirk function have the name: |
29 | # $Name (e.g. PostUpgrade) |
30 | @@ -118,6 +122,11 @@ |
31 | if cache['ubuntu-desktop'].is_installed and \ |
32 | cache['snapd'].is_installed: |
33 | self._checkStoreConnectivity() |
34 | + # If the snap store is accessible, at the same time calculate the |
35 | + # extra size needed by to-be-installed snaps. This also prepares |
36 | + # the snaps-to-install list for the actual upgrade. |
37 | + if self._snapstore_reachable: |
38 | + self._calculateSnapSizeRequirements() |
39 | |
40 | def eoanPostUpgrade(self): |
41 | logging.debug("running Quirks.eoanPostUpgrade") |
42 | @@ -128,7 +137,7 @@ |
43 | return |
44 | if cache['ubuntu-desktop'].is_installed and \ |
45 | cache['snapd'].is_installed and \ |
46 | - self._snapstore_reachable: |
47 | + self._snap_list: |
48 | self._replaceDebsWithSnaps() |
49 | |
50 | # individual quirks handler when the dpkg run is finished --------- |
51 | @@ -439,51 +448,65 @@ |
52 | if not res: |
53 | self.controller.abort() |
54 | |
55 | + def _calculateSnapSizeRequirements(self): |
56 | + import json |
57 | + import urllib.request |
58 | + from urllib.error import URLError |
59 | + |
60 | + # first fetch the list of snap-deb replacements that will be needed |
61 | + # and store them for future reference, along with other data we'll |
62 | + # need in the process |
63 | + self._prepare_snap_replacement_data() |
64 | + # now perform direct API calls to the store, requesting size |
65 | + # information for each of the snaps needing installation |
66 | + self._view.updateStatus(_("Calculating snap size requirements")) |
67 | + for snap, snap_object in self._snap_list.items(): |
68 | + if snap_object['command'] != 'install': |
69 | + continue |
70 | + action = { |
71 | + "instance-key": "upgrade-size-check", |
72 | + "action": "download", |
73 | + "snap-id": snap_object['snap-id'], |
74 | + "channel": "stable/ubuntu-%s" % self._to_version, |
75 | + } |
76 | + data = { |
77 | + "context": [], |
78 | + "actions": [action], |
79 | + } |
80 | + req = urllib.request.Request( |
81 | + url='https://api.snapcraft.io/v2/snaps/refresh', |
82 | + data=bytes(json.dumps(data), encoding='utf-8')) |
83 | + req.add_header('Snap-Device-Series', '16') |
84 | + req.add_header('Content-type', 'application/json') |
85 | + req.add_header('Snap-Device-Architecture', self.arch) |
86 | + try: |
87 | + response = urllib.request.urlopen(req).read() |
88 | + info = json.loads(response) |
89 | + size = int(info['results'][0]['snap']['download']['size']) |
90 | + except (KeyError, URLError, ValueError): |
91 | + logging.debug("Failed fetching size of snap %s" % snap) |
92 | + continue |
93 | + self.extra_snap_space += size |
94 | + |
95 | def _replaceDebsWithSnaps(self): |
96 | - di = distro_info.UbuntuDistroInfo() |
97 | - try: |
98 | - fromVersion = \ |
99 | - di.version('%s' % self.controller.fromDist).split()[0] |
100 | - toVersion = di.version('%s' % self.controller.toDist).split()[0] |
101 | - # Ubuntu 18.04's python3-distro-info does not have version |
102 | - except AttributeError: |
103 | - fromVersion = next((r.version for r in di.get_all("object") |
104 | - if r.series == self.controller.fromDist), |
105 | - self.controller.fromDist).split()[0] |
106 | - toVersion = next((r.version for r in di.get_all("object") |
107 | - if r.series == self.controller.toDist), |
108 | - self.controller.toDist).split()[0] |
109 | """ install a snap and mark its corresponding package for removal """ |
110 | # gtk-common-themes isn't a package name but is this risky? |
111 | - snaps = ['core18', 'gnome-3-28-1804', 'gtk-common-themes', |
112 | - 'gnome-calculator', 'gnome-characters', 'gnome-logs', |
113 | - 'gnome-system-monitor'] |
114 | - self._view.updateStatus(_("Checking for installed snaps")) |
115 | - for snap in snaps: |
116 | - # check to see if the snap is already installed |
117 | - snap_info = subprocess.Popen(["snap", "info", snap], |
118 | - universal_newlines=True, |
119 | - stdout=subprocess.PIPE).communicate() |
120 | - self._view.processEvents() |
121 | - if re.search("^installed: ", snap_info[0], re.MULTILINE): |
122 | - logging.debug("Snap %s is installed" % snap) |
123 | - # its not tracking the release channel so don't refresh |
124 | - if not re.search("^tracking:.*ubuntu-%s" % fromVersion, |
125 | - snap_info[0], re.MULTILINE): |
126 | - logging.debug("Snap %s is not tracking the release channel" |
127 | - % snap) |
128 | - continue |
129 | - command = 'refresh' |
130 | + self._view.updateStatus(_("Processing snap replacements")) |
131 | + # _snap_list should be populated by the earlier |
132 | + # _calculateSnapSizeRequirements call. |
133 | + for snap, snap_object in self._snap_list.items(): |
134 | + command = snap_object['command'] |
135 | + if command == 'refresh': |
136 | self._view.updateStatus(_("refreshing snap %s" % snap)) |
137 | else: |
138 | - command = 'install' |
139 | self._view.updateStatus(_("installing snap %s" % snap)) |
140 | try: |
141 | self._view.processEvents() |
142 | - proc = subprocess.run(["snap", command, "--channel", |
143 | - "stable/ubuntu-%s" % toVersion, snap], |
144 | - stdout=subprocess.PIPE, |
145 | - check=True) |
146 | + proc = subprocess.run( |
147 | + ["snap", command, "--channel", |
148 | + "stable/ubuntu-%s" % self._to_version, snap], |
149 | + stdout=subprocess.PIPE, |
150 | + check=True) |
151 | self._view.processEvents() |
152 | except subprocess.CalledProcessError: |
153 | logging.debug("%s of snap %s failed" % (command, snap)) |
154 | @@ -767,3 +790,57 @@ |
155 | msg += " enabling it just for the upgrade" |
156 | logging.warning(msg) |
157 | apt.apt_pkg.config.set("Apt::Install-Recommends", "1") |
158 | + |
159 | + def _prepare_snap_replacement_data(self): |
160 | + """ Helper function fetching all required info for the deb-to-snap |
161 | + migration: version strings for upgrade (from and to) and the list |
162 | + of snaps (with actions). |
163 | + """ |
164 | + di = distro_info.UbuntuDistroInfo() |
165 | + try: |
166 | + self._from_version = \ |
167 | + di.version('%s' % self.controller.fromDist).split()[0] |
168 | + self._to_version = \ |
169 | + di.version('%s' % self.controller.toDist).split()[0] |
170 | + # Ubuntu 18.04's python3-distro-info does not have version |
171 | + except AttributeError: |
172 | + self._from_version = next( |
173 | + (r.version for r in di.get_all("object") |
174 | + if r.series == self.controller.fromDist), |
175 | + self.controller.fromDist).split()[0] |
176 | + self._to_version = next( |
177 | + (r.version for r in di.get_all("object") |
178 | + if r.series == self.controller.toDist), |
179 | + self.controller.toDist).split()[0] |
180 | + self._snap_list = {} |
181 | + # gtk-common-themes isn't a package name but is this risky? |
182 | + snaps = ['core18', 'gnome-3-28-1804', 'gtk-common-themes', |
183 | + 'gnome-calculator', 'gnome-characters', 'gnome-logs', |
184 | + 'gnome-system-monitor'] |
185 | + self._view.updateStatus(_("Checking for installed snaps")) |
186 | + for snap in snaps: |
187 | + snap_object = {} |
188 | + # check to see if the snap is already installed |
189 | + snap_info = subprocess.Popen(["snap", "info", snap], |
190 | + universal_newlines=True, |
191 | + stdout=subprocess.PIPE).communicate() |
192 | + self._view.processEvents() |
193 | + if re.search("^installed: ", snap_info[0], re.MULTILINE): |
194 | + logging.debug("Snap %s is installed" % snap) |
195 | + # its not tracking the release channel so don't refresh |
196 | + if not re.search("^tracking:.*ubuntu-%s" % self._from_version, |
197 | + snap_info[0], re.MULTILINE): |
198 | + logging.debug("Snap %s is not tracking the release channel" |
199 | + % snap) |
200 | + continue |
201 | + snap_object['command'] = 'refresh' |
202 | + else: |
203 | + match = re.search("snap-id:\s*(\w*)", snap_info[0]) |
204 | + if not match: |
205 | + logging.debug("Could not parse snap-id for the %s snap" |
206 | + % snap) |
207 | + continue |
208 | + snap_object['command'] = 'install' |
209 | + snap_object['snap-id'] = match[1] |
210 | + self._snap_list[snap] = snap_object |
211 | + return self._snap_list |
212 | |
213 | === modified file 'tests/test_quirks.py' |
214 | --- tests/test_quirks.py 2018-08-31 22:46:45 +0000 |
215 | +++ tests/test_quirks.py 2019-08-30 14:54:30 +0000 |
216 | @@ -9,6 +9,7 @@ |
217 | import unittest |
218 | import shutil |
219 | import tempfile |
220 | +import json |
221 | |
222 | from DistUpgrade.DistUpgradeQuirks import DistUpgradeQuirks |
223 | |
224 | @@ -24,6 +25,104 @@ |
225 | pass |
226 | |
227 | |
228 | +class MockPopenSnap(): |
229 | + def __init__(self, cmd, universal_newlines=True, |
230 | + stdout=None): |
231 | + self.command = cmd |
232 | + |
233 | + def communicate(self): |
234 | + snap_name = self.command[2] |
235 | + if (snap_name == 'gnome-logs' or |
236 | + snap_name == 'gnome-system-monitor'): |
237 | + return [""" |
238 | +name: test-snap |
239 | +summary: Test Snap |
240 | +publisher: Canonical |
241 | +license: unset |
242 | +description: Some description |
243 | +commands: |
244 | + - gnome-calculator |
245 | +snap-id: 1234 |
246 | +tracking: stable/ubuntu-19.04 |
247 | +refresh-date: 2019-04-11 |
248 | +channels: |
249 | + stable: 3.32.1 2019-04-10 (406) 4MB - |
250 | + candidate: 3.32.2 2019-06-26 (433) 4MB - |
251 | + beta: 3.33.89 2019-08-06 (459) 4MB - |
252 | + edge: 3.33.90 2019-08-06 (460) 4MB - |
253 | +installed: 3.32.1 (406) 4MB - |
254 | +"""] |
255 | + else: |
256 | + return [""" |
257 | +name: test-snap |
258 | +summary: Test Snap |
259 | +publisher: Canonical |
260 | +license: unset |
261 | +description: Some description |
262 | +commands: |
263 | + - gnome-calculator |
264 | +snap-id: 1234 |
265 | +refresh-date: 2019-04-11 |
266 | +channels: |
267 | + stable: 3.32.1 2019-04-10 (406) 4MB - |
268 | + candidate: 3.32.2 2019-06-26 (433) 4MB - |
269 | + beta: 3.33.89 2019-08-06 (459) 4MB - |
270 | + edge: 3.33.90 2019-08-06 (460) 4MB - |
271 | +"""] |
272 | + |
273 | + |
274 | +def mock_urlopen_snap(req): |
275 | + result = """{{ |
276 | + "error-list": [], |
277 | + "results": [ |
278 | + {{ |
279 | + "effective-channel": "stable", |
280 | + "instance-key": "test", |
281 | + "name": "{name}", |
282 | + "released-at": "2019-04-10T18:54:15.717357+00:00", |
283 | + "result": "download", |
284 | + "snap": {{ |
285 | + "created-at": "2019-04-09T17:09:29.941588+00:00", |
286 | + "download": {{ |
287 | + "deltas": [], |
288 | + "size": {size}, |
289 | + "url": "SNAPURL" |
290 | + }}, |
291 | + "license": "GPL-3.0+", |
292 | + "name": "{name}", |
293 | + "prices": {{ }}, |
294 | + "publisher": {{ |
295 | + "display-name": "Canonical", |
296 | + "id": "canonical", |
297 | + "username": "canonical", |
298 | + "validation": "verified" |
299 | + }}, |
300 | + "revision": 406, |
301 | + "snap-id": "{snap_id}", |
302 | + "summary": "GNOME Calculator", |
303 | + "title": "GNOME Calculator", |
304 | + "type": "app", |
305 | + "version": "3.32.1" |
306 | + }}, |
307 | + "snap-id": "{snap_id}" |
308 | + }} |
309 | + ] |
310 | +}} |
311 | +""" |
312 | + test_snaps = { |
313 | + '1': ("gnome-calculator", 4218880), |
314 | + '2': ("test-snap", 2000000) |
315 | + } |
316 | + json_data = json.loads(req.data) |
317 | + snap_id = json_data['actions'][0]['snap-id'] |
318 | + name = test_snaps[snap_id][0] |
319 | + size = test_snaps[snap_id][1] |
320 | + response_mock = mock.Mock() |
321 | + response_mock.read.return_value = result.format( |
322 | + name=name, snap_id=snap_id, size=size) |
323 | + return response_mock |
324 | + |
325 | + |
326 | def make_mock_pkg(name, is_installed, candidate_rec): |
327 | mock_pkg = mock.Mock() |
328 | mock_pkg.name = name |
329 | @@ -229,6 +328,111 @@ |
330 | self.assertEqual(pkgname, "linux-generic-lts-quantal") |
331 | |
332 | |
333 | +class TestSnapQuirks(unittest.TestCase): |
334 | + |
335 | + @mock.patch("subprocess.Popen", MockPopenSnap) |
336 | + def test_prepare_snap_replacement_data(self): |
337 | + # Prepare the state for testing |
338 | + controller = mock.Mock() |
339 | + controller.fromDist = 'disco' |
340 | + controller.toDist = 'eoan' |
341 | + config = mock.Mock() |
342 | + q = DistUpgradeQuirks(controller, config) |
343 | + # Call method under test |
344 | + q._prepare_snap_replacement_data() |
345 | + self.assertEqual(q._from_version, '19.04') |
346 | + self.assertEqual(q._to_version, '19.10') |
347 | + # Check if the right snaps have been detected as installed and |
348 | + # needing refresh and which ones need installation |
349 | + self.assertDictEqual( |
350 | + q._snap_list, |
351 | + {'core18': {'command': 'install', 'snap-id': '1234'}, |
352 | + 'gnome-3-28-1804': {'command': 'install', 'snap-id': '1234'}, |
353 | + 'gtk-common-themes': {'command': 'install', 'snap-id': '1234'}, |
354 | + 'gnome-calculator': {'command': 'install', 'snap-id': '1234'}, |
355 | + 'gnome-characters': {'command': 'install', 'snap-id': '1234'}, |
356 | + 'gnome-logs': {'command': 'refresh'}, |
357 | + 'gnome-system-monitor': {'command': 'refresh'}}) |
358 | + |
359 | + @mock.patch("DistUpgrade.DistUpgradeQuirks.get_arch") |
360 | + @mock.patch("urllib.request.urlopen") |
361 | + def test_calculate_snap_size_requirements(self, urlopen, arch): |
362 | + # Prepare the state for testing |
363 | + arch.return_value = 'amd64' |
364 | + controller = mock.Mock() |
365 | + config = mock.Mock() |
366 | + q = DistUpgradeQuirks(controller, config) |
367 | + # We mock out _prepare_snap_replacement_data(), as this is tested |
368 | + # separately. |
369 | + q._prepare_snap_replacement_data = mock.Mock() |
370 | + q._snap_list = { |
371 | + 'test-snap': {'command': 'install', 'snap-id': '2'}, |
372 | + 'gnome-calculator': {'command': 'install', 'snap-id': '1'}, |
373 | + 'gnome-system-monitor': {'command': 'refresh'} |
374 | + } |
375 | + q._to_version = "19.10" |
376 | + # Mock out urlopen in such a way that we get a mocked response based |
377 | + # on the parameters given but also allow us to check call arguments |
378 | + # etc. |
379 | + urlopen.side_effect = mock_urlopen_snap |
380 | + # Call method under test |
381 | + q._calculateSnapSizeRequirements() |
382 | + # Check if the size was calculated correctly |
383 | + self.assertEqual(q.extra_snap_space, 6218880) |
384 | + # Check if we only sent queries for the two command: install snaps |
385 | + self.assertEqual(urlopen.call_count, 2) |
386 | + # Make sure each call had the right headers and parameters |
387 | + for call in urlopen.call_args_list: |
388 | + req = call[0][0] |
389 | + self.assertIn(b"stable/ubuntu-19.10", req.data) |
390 | + self.assertDictEqual( |
391 | + req.headers, |
392 | + {'Snap-device-series': '16', |
393 | + 'Content-type': 'application/json', |
394 | + 'Snap-device-architecture': 'amd64'}) |
395 | + |
396 | + @mock.patch("subprocess.run") |
397 | + def test_replace_debs_with_snaps(self, run_mock): |
398 | + controller = mock.Mock() |
399 | + config = mock.Mock() |
400 | + q = DistUpgradeQuirks(controller, config) |
401 | + q._snap_list = { |
402 | + 'core18': {'command': 'install', 'snap-id': '1234'}, |
403 | + 'gnome-3-28-1804': {'command': 'install', 'snap-id': '1234'}, |
404 | + 'gtk-common-themes': {'command': 'install', 'snap-id': '1234'}, |
405 | + 'gnome-calculator': {'command': 'install', 'snap-id': '1234'}, |
406 | + 'gnome-characters': {'command': 'install', 'snap-id': '1234'}, |
407 | + 'gnome-logs': {'command': 'refresh'}, |
408 | + 'gnome-system-monitor': {'command': 'refresh'} |
409 | + } |
410 | + q._to_version = "19.10" |
411 | + q._replaceDebsWithSnaps() |
412 | + # Make sure all snaps have been handled |
413 | + self.assertEqual(run_mock.call_count, 7) |
414 | + snaps_refreshed = set() |
415 | + snaps_installed = set() |
416 | + # Check if all the snaps that needed to be installed were installed |
417 | + # and those that needed a refresh - refreshed |
418 | + for call in run_mock.call_args_list: |
419 | + args = call[0][0] |
420 | + if args[1] == 'install': |
421 | + snaps_installed.add(args[4]) |
422 | + else: |
423 | + snaps_refreshed.add(args[4]) |
424 | + self.assertSetEqual( |
425 | + snaps_refreshed, |
426 | + {'gnome-logs', 'gnome-system-monitor'}) |
427 | + self.assertSetEqual( |
428 | + snaps_installed, |
429 | + {'core18', 'gnome-3-28-1804', 'gtk-common-themes', |
430 | + 'gnome-calculator', 'gnome-characters'}) |
431 | + # Make sure we marked the replaced ones for removal |
432 | + # Here we only check if the right number of 'packages' has been |
433 | + # added to the forced_obsoletes list - not all of those packages are |
434 | + # actual deb packages that will have to be removed during the upgrade |
435 | + self.assertEqual(controller.forced_obsoletes.append.call_count, 5) |
436 | + |
437 | + |
438 | if __name__ == "__main__": |
439 | import logging |
440 | logging.basicConfig(level=logging.DEBUG) |
From the stylistic nitpicks for instance, I really had a strong urge to just call _prepare_ snap_replacemen t_data( ) in both _calculateSnapS izeRequirements () and _replaceDebsWit hSnaps( ), and simply make it to exit early if self._snap_list is already non-empty. But then it would be non-optimal, since we know _replaceDebsWit hSnaps will never be called without _calculateSnapS izeRequirements running first. But eh, I somehow couldn't make this look pretty, sorry...