Merge lp:~kyrofa/unity-scope-snappy/integration-tests_package-management into lp:~unity-api-team/unity-scope-snappy/trunk

Proposed by Kyle Fazzari
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
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.

To post a comment you must log in.
Revision history for this message
Kyle Fazzari (kyrofa) wrote :

Alright Xavi, this one is actually ready for review now as well.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
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)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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")

Subscribers

People subscribed via source and target branches