Merge lp:~kyrofa/unity-scope-snappy/integration-tests_package-management into lp:~unity-api-team/unity-scope-snappy/trunk
- integration-tests_package-management
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Kyle Fazzari |
Approved revision: | 32 |
Merged at revision: | 31 |
Proposed branch: | lp:~kyrofa/unity-scope-snappy/integration-tests_package-management |
Merge into: | lp:~unity-api-team/unity-scope-snappy/trunk |
Prerequisite: | lp:~kyrofa/unity-scope-snappy/integration-tests_previews |
Diff against target: |
919 lines (+770/-40) 8 files modified
debian/control (+1/-0) package-management-daemon/daemon/webdm_package_manager.go (+2/-2) test/fakes/fake_webdm_server.py (+146/-38) test/fakes/package.py (+13/-0) test/fakes/test_fake_webdm_server.py (+172/-0) test/store/package_management_tasks.py (+193/-0) test/store/test_package_management.py (+141/-0) test/store/test_package_management_failures.py (+102/-0) |
To merge this branch: | bzr merge lp:~kyrofa/unity-scope-snappy/integration-tests_package-management |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Charles Kerr (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Needs Fixing | |
Xavi Garcia | Pending | ||
Review via email: mp+263968@code.launchpad.net |
Commit message
Add integration tests for package management tasks.
Description of the change
Add integration tests for package management tasks.
This is the final MP for the store scope integration tests.
Kyle Fazzari (kyrofa) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:29
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Charles Kerr (charlesk) wrote : | # |
No complaints here. Nice to see the progress hacks start falling away, and also the incidental copyediting (e.g. improved test error messages)
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2015-07-20 15:27:32 +0000 |
3 | +++ debian/control 2015-07-20 15:27:32 +0000 |
4 | @@ -12,6 +12,7 @@ |
5 | pkg-config, |
6 | python3, |
7 | python3-fixtures, |
8 | + python3-requests, |
9 | python3-scope-harness, |
10 | Standards-Version: 3.9.6 |
11 | Homepage: https://launchpad.net/unity-scope-snappy |
12 | |
13 | === modified file 'package-management-daemon/daemon/webdm_package_manager.go' |
14 | --- package-management-daemon/daemon/webdm_package_manager.go 2015-06-23 12:12:02 +0000 |
15 | +++ package-management-daemon/daemon/webdm_package_manager.go 2015-07-20 15:27:32 +0000 |
16 | @@ -82,7 +82,7 @@ |
17 | err := manager.packageManager.Install(packageId) |
18 | if err != nil { |
19 | return "", dbus.NewError("org.freedesktop.DBus.Error.Failed", |
20 | - []interface{}{fmt.Sprintf(`Unable to install package "%d": %s`, packageId, err)}) |
21 | + []interface{}{fmt.Sprintf(`Unable to install package "%s": %s`, packageId, err)}) |
22 | } |
23 | |
24 | operationId := manager.newOperationId() |
25 | @@ -106,7 +106,7 @@ |
26 | err := manager.packageManager.Uninstall(packageId) |
27 | if err != nil { |
28 | return "", dbus.NewError("org.freedesktop.DBus.Error.Failed", |
29 | - []interface{}{fmt.Sprintf(`Unable to uninstall package "%d": %s`, packageId, err)}) |
30 | + []interface{}{fmt.Sprintf(`Unable to uninstall package "%s": %s`, packageId, err)}) |
31 | } |
32 | |
33 | operationId := manager.newOperationId() |
34 | |
35 | === modified file 'test/fakes/fake_webdm_server.py' |
36 | --- test/fakes/fake_webdm_server.py 2015-07-06 19:26:07 +0000 |
37 | +++ test/fakes/fake_webdm_server.py 2015-07-20 15:27:32 +0000 |
38 | @@ -1,33 +1,31 @@ |
39 | import http.server |
40 | +import multiprocessing |
41 | import urllib |
42 | import json |
43 | |
44 | +# Local imports |
45 | +from .package import Package |
46 | + |
47 | +PACKAGE_LIST_PATH = "/api/v2/packages/" |
48 | + |
49 | +def finishOperation(status): |
50 | + if status == "installing": |
51 | + return "installed" |
52 | + elif status == "uninstalling": |
53 | + return "uninstalled" |
54 | + |
55 | + return status |
56 | + |
57 | +def undoOperation(status): |
58 | + if status == "installing": |
59 | + return "uninstalled" |
60 | + elif status == "uninstalling": |
61 | + return "installed" |
62 | + |
63 | + return status |
64 | + |
65 | class FakeWebdmServerHandler(http.server.BaseHTTPRequestHandler): |
66 | - _PACKAGE_LIST_PATH = "/api/v2/packages/" |
67 | - |
68 | - _PACKAGE1 = { |
69 | - "id": "package1.canonical", |
70 | - "name": "package1", |
71 | - "vendor": "Canonical", |
72 | - "version": "0.1", |
73 | - "description": "description1", |
74 | - "icon": "http://icon1", |
75 | - "type": "app", |
76 | - "status": "installed", |
77 | - "installed_size": 123456 |
78 | - } |
79 | - |
80 | - _PACKAGE2 = { |
81 | - "id": "package2.canonical", |
82 | - "name": "package2", |
83 | - "vendor": "Canonical", |
84 | - "version": "0.2", |
85 | - "description": "description2", |
86 | - "icon": "http://icon2", |
87 | - "type": "app", |
88 | - "status": "uninstalled", |
89 | - "download_size": 123456 |
90 | - } |
91 | + _PROGRESS_STEP = 50 |
92 | |
93 | def sendJson(self, code, json): |
94 | self.send_response(code) |
95 | @@ -35,6 +33,36 @@ |
96 | self.end_headers() |
97 | self.wfile.write(json.encode()) |
98 | |
99 | + def sendPackages(self, packages): |
100 | + jsonArray = [] |
101 | + for package in packages: |
102 | + jsonArray.append(package.__dict__) |
103 | + |
104 | + self.sendJson(200, json.dumps(jsonArray)) |
105 | + |
106 | + def findPackage(self, packageId): |
107 | + for index, package in enumerate(self.server.PACKAGES): |
108 | + if package.id == packageId: |
109 | + return (package, index) |
110 | + |
111 | + return (None, None) |
112 | + |
113 | + def continueOperation(self, package): |
114 | + if package.progress >= 100: |
115 | + if package.status == "installing": |
116 | + package.installed_size = package.download_size |
117 | + package.download_size = 0 |
118 | + elif package.status == "uninstalling": |
119 | + package.download_size = package.installed_size |
120 | + package.installed_size = 0 |
121 | + |
122 | + package.status = finishOperation(package.status) |
123 | + package.progress = 0 |
124 | + else: |
125 | + package.progress += self._PROGRESS_STEP |
126 | + |
127 | + return package |
128 | + |
129 | def do_GET(self): |
130 | parsedPath = urllib.parse.urlparse(self.path) |
131 | query = urllib.parse.parse_qs(parsedPath.query) |
132 | @@ -43,27 +71,107 @@ |
133 | if "installed_only" in query and query["installed_only"][0] == "true": |
134 | installedOnly = True |
135 | |
136 | - if parsedPath.path.startswith(self._PACKAGE_LIST_PATH): |
137 | - packageId = parsedPath.path[len(self._PACKAGE_LIST_PATH):] |
138 | + if parsedPath.path.startswith(PACKAGE_LIST_PATH): |
139 | + packageId = parsedPath.path[len(PACKAGE_LIST_PATH):] |
140 | |
141 | # If no package ID was provided, list packages instead |
142 | if len(packageId) == 0: |
143 | + packages = self.server.PACKAGES |
144 | + |
145 | if installedOnly: |
146 | - self.sendJson(200, json.dumps([self._PACKAGE1])) |
147 | - else: |
148 | - self.sendJson(200, json.dumps([self._PACKAGE1, self._PACKAGE2])) |
149 | - else: |
150 | - if packageId == "package1.canonical": |
151 | - self.sendJson(200, json.dumps(self._PACKAGE1)) |
152 | - elif packageId == "package2.canonical" and not installedOnly: |
153 | - self.sendJson(200, json.dumps(self._PACKAGE2)) |
154 | - else: |
155 | - self.send_error(404, "snappy package not found {}\n".format(packageId)) |
156 | + packages = [] |
157 | + for package in self.server.PACKAGES: |
158 | + if package.status == "installed": |
159 | + packages.append(package) |
160 | + |
161 | + self.sendPackages(packages) |
162 | + else: |
163 | + package, index = self.findPackage(packageId) |
164 | + if package != None: |
165 | + if (package.status == "installing" or |
166 | + package.status == "uninstalling"): |
167 | + package = self.continueOperation(package) |
168 | + self.server.PACKAGES[index] = package |
169 | + |
170 | + if not installedOnly or (package.status == "installed"): |
171 | + self.sendJson(200, json.dumps(package.__dict__)) |
172 | + return |
173 | + |
174 | + self.send_error(404, "snappy package not found {}\n".format(packageId)) |
175 | + |
176 | + return |
177 | + |
178 | + raise NotImplementedError(self.path) |
179 | + |
180 | + def do_PUT(self): |
181 | + parsedPath = urllib.parse.urlparse(self.path) |
182 | + query = urllib.parse.parse_qs(parsedPath.query) |
183 | + |
184 | + if parsedPath.path.startswith(PACKAGE_LIST_PATH): |
185 | + packageId = parsedPath.path[len(PACKAGE_LIST_PATH):] |
186 | + |
187 | + if len(packageId) > 0: |
188 | + package, index = self.findPackage(packageId) |
189 | + |
190 | + if package != None and not self.server.ignoreRequests: |
191 | + package.status = "installing" |
192 | + self.server.PACKAGES[index] = package |
193 | + |
194 | + self.sendJson(202, json.dumps("Accepted")) |
195 | + else: |
196 | + self.send_error(500, "PUT here makes no sense") |
197 | + |
198 | + return |
199 | + |
200 | + raise NotImplementedError(self.path) |
201 | + |
202 | + def do_DELETE(self): |
203 | + parsedPath = urllib.parse.urlparse(self.path) |
204 | + query = urllib.parse.parse_qs(parsedPath.query) |
205 | + |
206 | + if parsedPath.path.startswith(PACKAGE_LIST_PATH): |
207 | + packageId = parsedPath.path[len(PACKAGE_LIST_PATH):] |
208 | + |
209 | + if len(packageId) > 0: |
210 | + package, index = self.findPackage(packageId) |
211 | + if package != None and not self.server.ignoreRequests: |
212 | + package.status = "uninstalling" |
213 | + self.server.PACKAGES[index] = package |
214 | + |
215 | + self.sendJson(202, json.dumps("Accepted")) |
216 | + else: |
217 | + self.send_error(500, "DELETE here makes no sense") |
218 | |
219 | return |
220 | |
221 | raise NotImplementedError(self.path) |
222 | |
223 | class FakeWebdmServer(http.server.HTTPServer): |
224 | - def __init__(self, address): |
225 | + def __init__(self, address, ignoreRequests=False): |
226 | + self.ignoreRequests = ignoreRequests |
227 | + |
228 | + self.PACKAGES = [] |
229 | + self.PACKAGES.append(Package( |
230 | + id = "package1.canonical", |
231 | + name = "package1", |
232 | + vendor = "Canonical", |
233 | + version = "0.1", |
234 | + description = "description1", |
235 | + icon = "http://icon1", |
236 | + type = "app", |
237 | + status = "installed", |
238 | + installed_size = 123456 |
239 | + )) |
240 | + self.PACKAGES.append(Package( |
241 | + id = "package2.canonical", |
242 | + name = "package2", |
243 | + vendor = "Canonical", |
244 | + version = "0.2", |
245 | + description = "description2", |
246 | + icon = "http://icon2", |
247 | + type = "app", |
248 | + status = "uninstalled", |
249 | + download_size = 123456 |
250 | + )) |
251 | + |
252 | super().__init__(address, FakeWebdmServerHandler) |
253 | |
254 | === added file 'test/fakes/package.py' |
255 | --- test/fakes/package.py 1970-01-01 00:00:00 +0000 |
256 | +++ test/fakes/package.py 2015-07-20 15:27:32 +0000 |
257 | @@ -0,0 +1,13 @@ |
258 | +class Package: |
259 | + def __init__(self, id="", name="", vendor="", version="", description="", icon="", type="", status="", installed_size=0, download_size=0, progress=0): |
260 | + self.id = id |
261 | + self.name = name |
262 | + self.vendor = vendor |
263 | + self.version = version |
264 | + self.description = description |
265 | + self.icon = icon |
266 | + self.type = type |
267 | + self.status = status |
268 | + self.installed_size = installed_size |
269 | + self.download_size = download_size |
270 | + self.progress = progress |
271 | |
272 | === added file 'test/fakes/test_fake_webdm_server.py' |
273 | --- test/fakes/test_fake_webdm_server.py 1970-01-01 00:00:00 +0000 |
274 | +++ test/fakes/test_fake_webdm_server.py 2015-07-20 15:27:32 +0000 |
275 | @@ -0,0 +1,172 @@ |
276 | +#!/usr/bin/env python3 |
277 | + |
278 | +import testtools |
279 | +import fixtures |
280 | +import json |
281 | +import requests |
282 | + |
283 | +# Local imports |
284 | +import test.fakes.fake_webdm_server as fake |
285 | +from test.test_fixtures import ServerFixture |
286 | + |
287 | +class TestFakeWebdmServer(testtools.TestCase, fixtures.TestWithFixtures): |
288 | + """Test fake WebDM server to verify that it works as expected.""" |
289 | + |
290 | + def setUp(self): |
291 | + """Setup the test fixtures and run the store scope using the harness. |
292 | + """ |
293 | + |
294 | + super().setUp() |
295 | + |
296 | + server = ServerFixture(fake.FakeWebdmServer) |
297 | + self.useFixture(server) |
298 | + self.url = server.url[:-1] |
299 | + |
300 | + def testListStorePackages(self): |
301 | + """Test the package list API.""" |
302 | + |
303 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH) |
304 | + results = response.json() |
305 | + |
306 | + self.assertEqual(len(results), 2, |
307 | + "There should only be two packages") |
308 | + |
309 | + # Order is not enforced on the list, so we need to check it this |
310 | + # way. |
311 | + package1Found = False |
312 | + package2Found = False |
313 | + for package in results: |
314 | + if package["id"] == "package1.canonical": |
315 | + package1Found = True |
316 | + elif package["id"] == "package2.canonical": |
317 | + package2Found = True |
318 | + else: |
319 | + self.fail("Unexpected package: {}".format(package["id"])) |
320 | + |
321 | + if not package1Found: |
322 | + self.fail("Expected package with ID: \"package1.canonical\"") |
323 | + |
324 | + if not package2Found: |
325 | + self.fail("Expected package with ID: \"package2.canonical\"") |
326 | + |
327 | + def testListInstalledPackages(self): |
328 | + """Test the package list API with a filter for installed packages.""" |
329 | + |
330 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH, |
331 | + params={"installed_only": "true"}) |
332 | + results = response.json() |
333 | + |
334 | + self.assertEqual(len(results), 1, |
335 | + "There should be a single result") |
336 | + |
337 | + self.assertEqual(results[0]["id"], "package1.canonical", |
338 | + "Expected the installed package to be package1") |
339 | + |
340 | + def testQueryInstalled(self): |
341 | + """Test the query API with an installed package.""" |
342 | + |
343 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH + |
344 | + "package1.canonical") |
345 | + result = response.json() |
346 | + self.assertEqual(result["id"], "package1.canonical") |
347 | + |
348 | + def testQueryNotInstalled(self): |
349 | + """Test the query API with a package that is not installed.""" |
350 | + |
351 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH + |
352 | + "package2.canonical") |
353 | + result = response.json() |
354 | + self.assertEqual(result["id"], "package2.canonical") |
355 | + |
356 | + def testQueryInstalledAndInstalledOnly(self): |
357 | + """Test the query API with a filter for installed packages. |
358 | + |
359 | + Test with an installed package. |
360 | + """ |
361 | + |
362 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH + |
363 | + "package1.canonical", |
364 | + params={"installed_only": "true"}) |
365 | + result = response.json() |
366 | + self.assertEqual(result["id"], "package1.canonical") |
367 | + |
368 | + def testQueryNotInstalledAndInstalledOnly(self): |
369 | + """Test the query API with a filter for installed packages. |
370 | + |
371 | + Test with a package that is not installed. |
372 | + """ |
373 | + |
374 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH + |
375 | + "package2.canonical", |
376 | + params={"installed_only": "true"}) |
377 | + self.assertEqual(response.status_code, 404, |
378 | + "Requesting \"package2\" while also limiting the search to installed packages should result in not found") |
379 | + |
380 | + def testPackageInstallation(self): |
381 | + """Test the install API.""" |
382 | + |
383 | + self.installPackage("package2.canonical") |
384 | + |
385 | + def testPackageUninstallation(self): |
386 | + """Test the uninstall API.""" |
387 | + |
388 | + self.uninstallPackage("package1.canonical") |
389 | + |
390 | + def testPackageInstallUninstallReinstall(self): |
391 | + """Test a package install/uninstall/reinstall flow.""" |
392 | + |
393 | + # Install package. |
394 | + self.installPackage("package2.canonical") |
395 | + |
396 | + # Now uninstall the package. |
397 | + self.uninstallPackage("package2.canonical") |
398 | + |
399 | + # Finally, reinstall the package. |
400 | + self.installPackage("package2.canonical") |
401 | + |
402 | + def installPackage(self, packageId): |
403 | + """Install a package via the install API, making sure it succeeds. |
404 | + """ |
405 | + |
406 | + response = requests.put(self.url + fake.PACKAGE_LIST_PATH + packageId) |
407 | + self.assertEqual(response.status_code, 202) |
408 | + |
409 | + progress = 0 |
410 | + status = "" |
411 | + while True: |
412 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH + |
413 | + packageId) |
414 | + result = response.json() |
415 | + if result["status"] == "installed": |
416 | + break |
417 | + |
418 | + newProgress = result["progress"] |
419 | + |
420 | + if newProgress <= progress: |
421 | + self.fail("Progress was {}, but last call it was {}. It should be increasing with each call".format(newProgress, progress)) |
422 | + |
423 | + progress = newProgress |
424 | + |
425 | + def uninstallPackage(self, packageId): |
426 | + """Uninstall a package via the uninstall API, making sure it succeeds. |
427 | + """ |
428 | + |
429 | + response = requests.delete(self.url + fake.PACKAGE_LIST_PATH + |
430 | + packageId) |
431 | + self.assertEqual(response.status_code, 202) |
432 | + |
433 | + progress = 0 |
434 | + status = "" |
435 | + while True: |
436 | + response = requests.get(self.url + fake.PACKAGE_LIST_PATH + |
437 | + packageId) |
438 | + result = response.json() |
439 | + if result["status"] == "uninstalled": |
440 | + break |
441 | + |
442 | + newProgress = result["progress"] |
443 | + |
444 | + if newProgress <= progress: |
445 | + self.fail("Progress was {}, but last call it was {}. It should be increasing with each call".format(newProgress, progress)) |
446 | + |
447 | + progress = newProgress |
448 | |
449 | === modified file 'test/store/package_management_tasks.py' |
450 | --- test/store/package_management_tasks.py 2015-07-20 15:27:32 +0000 |
451 | +++ test/store/package_management_tasks.py 2015-07-20 15:27:32 +0000 |
452 | @@ -1,10 +1,95 @@ |
453 | #!/usr/bin/env python3 |
454 | |
455 | from scope_harness import (ScopeHarness, |
456 | + Parameters, |
457 | + PreviewView, |
458 | PreviewColumnMatcher, |
459 | PreviewMatcher, |
460 | PreviewWidgetMatcher) |
461 | |
462 | +def installPackage(test, categoryId, packageId): |
463 | + """Install a package via the scope. |
464 | + |
465 | + This involves communicating with the DBus daemon, which will begin |
466 | + the package installation and report progress back. |
467 | + """ |
468 | + |
469 | + test.view.browse_department("") |
470 | + |
471 | + # Go to the preview of the package. |
472 | + preview = test.view.category(categoryId).result("snappy:"+packageId).tap() |
473 | + test.assertIsInstance(preview, PreviewView) |
474 | + |
475 | + # Begin the installation. |
476 | + widgetFound = False |
477 | + for columnWidgets in preview.widgets: |
478 | + for widget in columnWidgets: |
479 | + if widget.type == "actions": |
480 | + preview = widget.trigger("install", "") |
481 | + widgetFound = True |
482 | + |
483 | + test.assertTrue(widgetFound, "Expected preview to include Install action.") |
484 | + |
485 | + return preview |
486 | + |
487 | +def uninstallPackage(test, categoryId, packageId): |
488 | + """Uninstall a package via the scope. |
489 | + |
490 | + This involves communicating with the DBus daemon, which will begin |
491 | + the package uninstallation and report progress back. |
492 | + """ |
493 | + |
494 | + test.view.browse_department("") |
495 | + |
496 | + # Go to the preview of the package. |
497 | + preview = test.view.category(categoryId).result("snappy:"+packageId).tap() |
498 | + test.assertIsInstance(preview, PreviewView) |
499 | + |
500 | + # Request the uninstallation (it will need to be confirmed) |
501 | + widgetFound = False |
502 | + for columnWidgets in preview.widgets: |
503 | + for widget in columnWidgets: |
504 | + if widget.type == "actions": |
505 | + preview = widget.trigger("uninstall", "") |
506 | + widgetFound = True |
507 | + |
508 | + test.assertTrue(widgetFound, |
509 | + "Expected preview to include Uninstall action.") |
510 | + |
511 | + return preview |
512 | + |
513 | +def confirmUninstallPackage(test, preview): |
514 | + """Confirm uninstallation request.""" |
515 | + |
516 | + # Begin the uninstallation. |
517 | + widgetFound = False |
518 | + for columnWidgets in preview.widgets: |
519 | + for widget in columnWidgets: |
520 | + if widget.type == "actions": |
521 | + preview = widget.trigger("uninstall_confirm", "") |
522 | + widgetFound = True |
523 | + |
524 | + test.assertTrue(widgetFound, |
525 | + "Expected preview to include Uninstall action.") |
526 | + |
527 | + return preview |
528 | + |
529 | +def cancelUninstallPackage(test, preview): |
530 | + """Cancel uninstallation request.""" |
531 | + |
532 | + # Cancel the uninstallation. |
533 | + widgetFound = False |
534 | + for columnWidgets in preview.widgets: |
535 | + for widget in columnWidgets: |
536 | + if widget.type == "actions": |
537 | + preview = widget.trigger("uninstall_cancel", "") |
538 | + widgetFound = True |
539 | + |
540 | + test.assertTrue(widgetFound, "Expected preview to include Cancel action.") |
541 | + |
542 | + return preview |
543 | + |
544 | + |
545 | def verifyInstalledPreview(test, preview, title, subtitle, mascot, |
546 | description, version, size): |
547 | """Verify the preview for an installed package.""" |
548 | @@ -52,6 +137,47 @@ |
549 | }))) |
550 | .match(preview.widgets)) |
551 | |
552 | +def verifyInstallingPreview(test, preview, title, subtitle, mascot, |
553 | + description, version, size, operationNumber=1): |
554 | + """Verify the preview for an installing package.""" |
555 | + |
556 | + test.assertMatchResult(PreviewColumnMatcher() |
557 | + .column(PreviewMatcher() |
558 | + .widget(PreviewWidgetMatcher("header") |
559 | + .type("header") |
560 | + .data({ |
561 | + "title": title, |
562 | + "subtitle": subtitle, |
563 | + "mascot": mascot, |
564 | + "attributes": [ |
565 | + {"value": "FREE"} |
566 | + ] |
567 | + })) |
568 | + .widget(PreviewWidgetMatcher("install") |
569 | + .type("progress") |
570 | + .data({ |
571 | + "source": { |
572 | + "dbus-name": "com.canonical.applications.WebdmPackageManager", |
573 | + "dbus-object": "/com/canonical/applications/WebdmPackageManager/operation/{}".format(operationNumber) |
574 | + } |
575 | + })) |
576 | + .widget(PreviewWidgetMatcher("summary") |
577 | + .type("text") |
578 | + .data({ |
579 | + "title": "Info", |
580 | + "text": description |
581 | + })) |
582 | + .widget(PreviewWidgetMatcher("updates_table") |
583 | + .type("table") |
584 | + .data({ |
585 | + "title": "Updates", |
586 | + "values": [ |
587 | + ["Version number", version], |
588 | + ["Size", size] |
589 | + ] |
590 | + }))) |
591 | + .match(preview.widgets)) |
592 | + |
593 | def verifyStorePreview(test, preview, title, subtitle, mascot, |
594 | description, version, size): |
595 | """Verify the preview for an in-store package (i.e. not installed).""" |
596 | @@ -94,3 +220,70 @@ |
597 | ] |
598 | }))) |
599 | .match(preview.widgets)) |
600 | + |
601 | +def verifyUninstallingPreview(test, preview, title, subtitle, mascot, |
602 | + description, version, size, operationNumber=1): |
603 | + """Verify the preview for an uninstalling package.""" |
604 | + |
605 | + test.assertMatchResult(PreviewColumnMatcher() |
606 | + .column(PreviewMatcher() |
607 | + .widget(PreviewWidgetMatcher("header") |
608 | + .type("header") |
609 | + .data({ |
610 | + "title": title, |
611 | + "subtitle": subtitle, |
612 | + "mascot": mascot, |
613 | + "attributes": [ |
614 | + {"value": "✓ Installed"} |
615 | + ] |
616 | + })) |
617 | + .widget(PreviewWidgetMatcher("uninstall") |
618 | + .type("progress") |
619 | + .data({ |
620 | + "source": { |
621 | + "dbus-name": "com.canonical.applications.WebdmPackageManager", |
622 | + "dbus-object": "/com/canonical/applications/WebdmPackageManager/operation/{}".format(operationNumber) |
623 | + } |
624 | + })) |
625 | + .widget(PreviewWidgetMatcher("summary") |
626 | + .type("text") |
627 | + .data({ |
628 | + "title": "Info", |
629 | + "text": description |
630 | + })) |
631 | + .widget(PreviewWidgetMatcher("updates_table") |
632 | + .type("table") |
633 | + .data({ |
634 | + "title": "Updates", |
635 | + "values": [ |
636 | + ["Version number", version], |
637 | + ["Size", size] |
638 | + ] |
639 | + }))) |
640 | + .match(preview.widgets)) |
641 | + |
642 | +def verifyConfirmUninstallPreview(test, preview, name): |
643 | + """Verify the preview to confirm request to uninstall a package.""" |
644 | + |
645 | + test.assertMatchResult(PreviewColumnMatcher() |
646 | + .column(PreviewMatcher() |
647 | + .widget(PreviewWidgetMatcher("confirm") |
648 | + .type("text") |
649 | + .data({ |
650 | + "text": "Are you sure you want to uninstall {}?".format(name) |
651 | + })) |
652 | + .widget(PreviewWidgetMatcher("confirmation") |
653 | + .type("actions") |
654 | + .data({ |
655 | + "actions": [ |
656 | + { |
657 | + "id": "uninstall_confirm", |
658 | + "label": "Uninstall" |
659 | + }, |
660 | + { |
661 | + "id": "uninstall_cancel", |
662 | + "label": "Cancel" |
663 | + } |
664 | + ] |
665 | + }))) |
666 | + .match(preview.widgets)) |
667 | |
668 | === added file 'test/store/test_package_management.py' |
669 | --- test/store/test_package_management.py 1970-01-01 00:00:00 +0000 |
670 | +++ test/store/test_package_management.py 2015-07-20 15:27:32 +0000 |
671 | @@ -0,0 +1,141 @@ |
672 | +#!/usr/bin/env python3 |
673 | + |
674 | +import os |
675 | +import sys |
676 | +import subprocess |
677 | +import unittest |
678 | +import fixtures |
679 | + |
680 | +from scope_harness.testing import ScopeHarnessTestCase |
681 | +from scope_harness import (ScopeHarness, |
682 | + Parameters, |
683 | + PreviewView, |
684 | + PreviewColumnMatcher, |
685 | + PreviewMatcher, |
686 | + PreviewWidgetMatcher) |
687 | + |
688 | +# Local imports |
689 | +from test.fakes import FakeWebdmServer |
690 | +from test.test_fixtures import ServerFixture |
691 | +import test.store.package_management_tasks as tasks |
692 | + |
693 | +THIS_FILE_PATH = os.path.dirname(os.path.realpath(__file__)) |
694 | + |
695 | +class TestPackageManagement(ScopeHarnessTestCase, fixtures.TestWithFixtures): |
696 | + """Test package installation/uninstallation via the scope. |
697 | + """ |
698 | + |
699 | + def setUp(self): |
700 | + """Setup the test fixtures, run the store scope, and fire up the daemon. |
701 | + """ |
702 | + |
703 | + server = ServerFixture(FakeWebdmServer) |
704 | + self.useFixture(server) |
705 | + |
706 | + # Run the package management daemon to communicate with our fake server |
707 | + self.daemon = subprocess.Popen(["package-management-daemon", |
708 | + "-webdm={}".format(server.url)]) |
709 | + |
710 | + os.environ["WEBDM_URL"] = server.url |
711 | + |
712 | + self.harness = ScopeHarness.new_from_scope_list(Parameters([ |
713 | + "{}/../../store/snappy-store.ini".format(THIS_FILE_PATH) |
714 | + ])) |
715 | + self.view = self.harness.results_view |
716 | + self.view.active_scope = "snappy-store" |
717 | + self.view.search_query = "" |
718 | + |
719 | + def tearDown(self): |
720 | + """Terminate the daemon and make sure it exits cleanly.""" |
721 | + |
722 | + self.daemon.terminate() |
723 | + |
724 | + # Make sure the daemon exits cleanly |
725 | + self.assertEqual(self.daemon.wait(), 0) |
726 | + |
727 | + def testPackageInstallation(self): |
728 | + """Test installing a package via the scope.""" |
729 | + |
730 | + self.installPackage("store_packages", "package2.canonical", "package2", |
731 | + "Canonical", "http://icon2", "description2", "0.2", |
732 | + "124 kB") |
733 | + |
734 | + def testPackageUninstallation(self): |
735 | + """Test uninstalling a package via the scope.""" |
736 | + |
737 | + self.uninstallPackage("store_packages", "package1.canonical", |
738 | + "package1", "Canonical", "http://icon1", |
739 | + "description1", "0.1", "124 kB") |
740 | + |
741 | + def testPackageInstallUninstallReinstall(self): |
742 | + """Test handling of package install/uninstall/reinstall flow.""" |
743 | + |
744 | + # Install the package |
745 | + self.installPackage("store_packages", "package2.canonical", "package2", |
746 | + "Canonical", "http://icon2", "description2", "0.2", |
747 | + "124 kB") |
748 | + |
749 | + # Now unstall the package |
750 | + self.uninstallPackage("store_packages", "package2.canonical", |
751 | + "package2", "Canonical", "http://icon2", |
752 | + "description2", "0.2", "124 kB", 2) |
753 | + |
754 | + # Finally, re-install the package |
755 | + self.installPackage("store_packages", "package2.canonical", "package2", |
756 | + "Canonical", "http://icon2", "description2", "0.2", |
757 | + "124 kB", 3) |
758 | + |
759 | + def installPackage(self, categoryId, packageId, title, subtitle, mascot, |
760 | + description, version, size, operationNumber=1): |
761 | + """Install and verify package installation via scope.""" |
762 | + |
763 | + preview = tasks.installPackage(self, categoryId, packageId) |
764 | + self.assertIsInstance(preview, PreviewView) |
765 | + |
766 | + tasks.verifyInstallingPreview(self, preview, title, subtitle, mascot, |
767 | + description, version, size, |
768 | + operationNumber) |
769 | + |
770 | + # Force the installation to succeed |
771 | + widgetFound = False |
772 | + for columnWidgets in preview.widgets: |
773 | + for widget in columnWidgets: |
774 | + if widget.type == "progress": |
775 | + preview = widget.trigger("finished", "") |
776 | + widgetFound = True |
777 | + |
778 | + self.assertTrue(widgetFound, |
779 | + "Expected progress widget to be in preview.") |
780 | + |
781 | + tasks.verifyInstalledPreview(self, preview, title, subtitle, mascot, |
782 | + description, version, size) |
783 | + |
784 | + def uninstallPackage(self, categoryId, packageId, title, subtitle, mascot, |
785 | + description, version, size, operationNumber=1): |
786 | + """Uninstall and verify package uninstallation via scope.""" |
787 | + |
788 | + preview = tasks.uninstallPackage(self, categoryId, packageId) |
789 | + self.assertIsInstance(preview, PreviewView) |
790 | + |
791 | + tasks.verifyConfirmUninstallPreview(self, preview, title) |
792 | + |
793 | + preview = tasks.confirmUninstallPackage(self, preview) |
794 | + self.assertIsInstance(preview, PreviewView) |
795 | + |
796 | + tasks.verifyUninstallingPreview(self, preview, title, subtitle, mascot, |
797 | + description, version, size, |
798 | + operationNumber) |
799 | + |
800 | + # Force the uninstallation to succeed |
801 | + widgetFound = False |
802 | + for columnWidgets in preview.widgets: |
803 | + for widget in columnWidgets: |
804 | + if widget.type == "progress": |
805 | + preview = widget.trigger("finished", "") |
806 | + widgetFound = True |
807 | + |
808 | + self.assertTrue(widgetFound, |
809 | + "Expected progress widget to be in preview.") |
810 | + |
811 | + tasks.verifyStorePreview(self, preview, title, subtitle, mascot, |
812 | + description, version, size) |
813 | |
814 | === added file 'test/store/test_package_management_failures.py' |
815 | --- test/store/test_package_management_failures.py 1970-01-01 00:00:00 +0000 |
816 | +++ test/store/test_package_management_failures.py 2015-07-20 15:27:32 +0000 |
817 | @@ -0,0 +1,102 @@ |
818 | +#!/usr/bin/env python3 |
819 | + |
820 | +import os |
821 | +import sys |
822 | +import subprocess |
823 | +import unittest |
824 | +import fixtures |
825 | + |
826 | +from scope_harness.testing import ScopeHarnessTestCase |
827 | +from scope_harness import (ScopeHarness, |
828 | + Parameters, |
829 | + PreviewView, |
830 | + PreviewColumnMatcher, |
831 | + PreviewMatcher, |
832 | + PreviewWidgetMatcher) |
833 | + |
834 | +# Local imports |
835 | +from test.fakes import FakeWebdmServer |
836 | +from test.test_fixtures import ServerFixture |
837 | +import test.store.package_management_tasks as tasks |
838 | + |
839 | +THIS_FILE_PATH = os.path.dirname(os.path.realpath(__file__)) |
840 | + |
841 | +class TestPackageManagementFailures(ScopeHarnessTestCase, fixtures.TestWithFixtures): |
842 | + """Test package installation/uninstallation failures in the scope. |
843 | + """ |
844 | + |
845 | + def setUp(self): |
846 | + """Setup the test fixtures, run the store scope, and fire up the daemon. |
847 | + """ |
848 | + |
849 | + server = ServerFixture(FakeWebdmServer, True) |
850 | + self.useFixture(server) |
851 | + |
852 | + # Run the package management daemon to communicate with our fake server |
853 | + self.daemon = subprocess.Popen(["package-management-daemon", |
854 | + "-webdm={}".format(server.url)]) |
855 | + |
856 | + os.environ["WEBDM_URL"] = server.url |
857 | + |
858 | + self.harness = ScopeHarness.new_from_scope_list(Parameters([ |
859 | + "{}/../../store/snappy-store.ini".format(THIS_FILE_PATH) |
860 | + ])) |
861 | + self.view = self.harness.results_view |
862 | + self.view.active_scope = "snappy-store" |
863 | + self.view.search_query = "" |
864 | + |
865 | + def tearDown(self): |
866 | + """Terminate the daemon and make sure it exits cleanly.""" |
867 | + |
868 | + self.daemon.terminate() |
869 | + |
870 | + # Make sure the daemon exits cleanly |
871 | + self.assertEqual(self.daemon.wait(), 0) |
872 | + |
873 | + def testPackageInstallationFailure(self): |
874 | + """Test failure handling from the progress widget while installing.""" |
875 | + |
876 | + preview = tasks.installPackage(self, "store_packages", "package2.canonical") |
877 | + self.assertIsInstance(preview, PreviewView) |
878 | + |
879 | + # Force the installation to fail |
880 | + widgetFound = False |
881 | + for columnWidgets in preview.widgets: |
882 | + for widget in columnWidgets: |
883 | + if widget.type == "progress": |
884 | + preview = widget.trigger("failed", "") |
885 | + widgetFound = True |
886 | + |
887 | + self.assertTrue(widgetFound, |
888 | + "Expected progress widget to be in preview.") |
889 | + |
890 | + # Verify that the package still shows the package as not installed. |
891 | + tasks.verifyStorePreview(self, preview, "package2", "Canonical", |
892 | + "http://icon2", "description2", "0.2", "124 kB") |
893 | + |
894 | + def testPackageUninstallationFailure(self): |
895 | + """Test failure handling from the progress widget while uninstalling.""" |
896 | + |
897 | + preview = tasks.uninstallPackage(self, "store_packages", "package1.canonical") |
898 | + self.assertIsInstance(preview, PreviewView) |
899 | + |
900 | + tasks.verifyConfirmUninstallPreview(self, preview, "package1") |
901 | + |
902 | + preview = tasks.confirmUninstallPackage(self, preview) |
903 | + self.assertIsInstance(preview, PreviewView) |
904 | + |
905 | + # Force the uninstallation to fail |
906 | + widgetFound = False |
907 | + for columnWidgets in preview.widgets: |
908 | + for widget in columnWidgets: |
909 | + if widget.type == "progress": |
910 | + preview = widget.trigger("failed", "") |
911 | + widgetFound = True |
912 | + |
913 | + self.assertTrue(widgetFound, |
914 | + "Expected progress widget to be in preview.") |
915 | + |
916 | + # Verify that the preview still shows the package as installed. |
917 | + tasks.verifyInstalledPreview(self, preview, "package1", "Canonical", |
918 | + "http://icon1", "description1", "0.1", |
919 | + "124 kB") |
Alright Xavi, this one is actually ready for review now as well.