Merge lp:~nataliabidart/software-center/fix-977931 into lp:software-center

Proposed by Natalia Bidart
Status: Superseded
Proposed branch: lp:~nataliabidart/software-center/fix-977931
Merge into: lp:software-center
Diff against target: 1310 lines (+667/-156) (has conflicts)
21 files modified
README (+1/-0)
debian/changelog (+17/-2)
softwarecenter/backend/spawn_helper.py (+3/-0)
softwarecenter/db/__init__.py (+7/-2)
softwarecenter/db/database.py (+4/-0)
softwarecenter/db/debfile.py (+38/-21)
softwarecenter/enums.py (+6/-0)
softwarecenter/testutils.py (+11/-1)
softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py (+2/-1)
softwarecenter/ui/gtk3/app.py (+162/-93)
softwarecenter/ui/gtk3/models/appstore2.py (+1/-0)
softwarecenter/ui/gtk3/session/appmanager.py (+5/-0)
softwarecenter/ui/gtk3/session/viewmanager.py (+5/-0)
softwarecenter/ui/gtk3/views/appdetailsview.py (+2/-2)
softwarecenter/ui/gtk3/widgets/reviews.py (+12/-4)
softwarecenter/ui/gtk3/widgets/videoplayer.py (+5/-1)
test/gtk3/test_app.py (+357/-0)
test/gtk3/test_debfile_view.py (+3/-8)
test/gtk3/test_purchase.py (+8/-12)
test/test_database.py (+8/-3)
test/test_debfileapplication.py (+10/-6)
Text conflict in debian/changelog
To merge this branch: bzr merge lp:~nataliabidart/software-center/fix-977931
Reviewer Review Type Date Requested Status
software-store-developers Pending
Review via email: mp+102321@code.launchpad.net

This proposal has been superseded by a proposal from 2012-05-16.

Commit message

- Unified package string parsing into a single method that will be used from either the command line arguments, or from the dbus method 'bringToFront'. This way, search will be consistent between all entry points.
- Also added proper test suites for the above.

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks Natalia for this branch and sorry for my slow reply.

First let me say thanks for the work and the refactor and the excellent test, we shall use that
as a example.

This looks fine, there have been some changes in the 5.2 branch so I merged it and resolved
conflicts and tweaked it a bit more, please remerge lp:~mvo/software-center/fix-977931/ with this branch
and let me know what you think.

I was curious how to reproduce/understand the changes in appview.py, reviews.py that cause the model to
be "None". I revert that change but for me its not crashing so I wonder if that is still needed? Or is it
a random issue? Except for this (which I just would like to understand better how it happens) the branch
is +10 approve ;)

Additional thanks for unifying get_mock_options() and the with open() fix (and the cleanup) in test_database.py.

2991. By Natalia Bidart

Merged 5.2 in.

* lp:~evfool/software-center/lp987801:
  - Only show the version label once for each version in
    reviews (LP: #987801)
* lp:~mvo/software-center/fix-lp994632:
  - add a dep to ensure we get the correct ubuntu-sso-client-gtk
    (LP: #994632)
* lp:~mvo/software-center/proper-error-on-unknown-files:
  - show a proper error message when attempting to open unknown
    file types (LP: #944868)
* lp:~mvo/software-center/lp921799:
  - fix crash when decoding screenshots JSON (LP: #921799)
* lp:~mvo/software-center/lp959612:
  - fix crash when selecting the Installed view very quickly
    after startup (LP: #959612)

[ Ken VanDine ]
* lp:~ken-vandine/software-center/lp_982567:
  - Check if the proxy is enabled, if the proxy host is set but not
    enabled we shouldn't attempt to use the proxy (LP: #982567)

[ Gary Lasker ]
* lp:~gary-lasker/software-center/fix-crash-lp969732:
  - need to explicitly declare the needs-refresh signal in the
    AppTreeStore class to prevent a crash (LP: #969732)
* lp:~gary-lasker/software-center/fix-crash-lp870822:
  - don't crash if we don't get a pkgversion object back
    (LP: #870822)
* lp:~gary-lasker/software-center/fix-shutdown-crash-lp996333:
  - never crash when writing out the software center config file on
    shutdown (LP: #996333)
* lp:~gary-lasker/software-center/fix-makedirs-race-crashes:
  - fix crash on race when creating the cache or config directories
    (LP: #743003, LP: #621182)
* lp:~gary-lasker/software-center/fix-crash_lp973379:
  - ensure that the cache is ready before using the recommender
    service (LP: #973379 )
* lp:~gary-lasker/software-center/toolbar-buttons-insensitive-during-startup:
  - set the toolbar buttons insensitive for the duration of time that the
    lobby panels are initializing (LP: #999486, LP: #994341)
[ Robert Roth ]
* lp:~evfool/software-center/bug532072:
  - escape markup for support info and license (LP: #993279)
  - ellipsize summary at trailing end (LP: #532072)
* lp:~evfool/software-center/lp626037:
  - subtle background color tweak for consistency
    between views (LP: #626037)
* lp:~evfool/software-center/lp835005:
  - no need for the software-properties dialog to be modal
    to software-center (LP: #835005)
* lp:~evfool/software-center/lp839389:
  - don't display an "icon not found" image in the Unity
    launcher when a report a review window is opened (LP: #839389)
* lp:~evfool/software-center/867588:
  - capitalize the first letter of the package summary
    (LP: #867588)
* lp:~evfool/software-center/lp876657:
  - make sure the search field does not disappear when an
    install or remove is in progress (LP: #876657)
* lp:~evfool/software-center/lp987797:
  - fix alignment in the reviews part of the appdetails (LP: #987797)
* lp:~evfool/software-center/lp844768:
  - show a indeterminate progress bar when software-center waits for
    another packagemanager to exit (LP: #844768)
[ Dave Morley ]
* lp:~davmor2/software-center/add-performance:
  - test code change only! add memory and cpt stats to the
    test script

2992. By Natalia Bidart

Applying changes from review.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Merged the branch suggested by mvo adding a minor tweaks about detetcting .deb files.

2993. By Natalia Bidart

Merged 5.2 in.

2994. By Natalia Bidart

Merged 5.2 in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2012-03-08 18:08:49 +0000
3+++ README 2012-05-16 15:58:19 +0000
4@@ -53,6 +53,7 @@
5 SOFTWARE_CENTER_DISTRO_CODENAME - overwrite "lsb_release -c -s" output
6 SOFTWARE_CENTER_ARCHITECTURE - overwrite the current architecture
7 SOFTWARE_CENTER_NO_SC_AGENT - disable the software-center-agent
8+SOFTWARE_CENTER_DISABLE_SPAWN_HELPER - disable everything that is run via the "SpawnHelper", i.e. recommender-agent, software-center-agent, reviews
9 SOFTWARE_CENTER_DEBUG_TABS - show notebook tabs for debugging
10 SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK - disables certificates checking in webkit views (for use in test environments)
11 SOFTWARE_CENTER_FORCE_NON_SSL - disable SSL (for use in test environments)
12
13=== modified file 'debian/changelog'
14--- debian/changelog 2012-05-15 09:23:50 +0000
15+++ debian/changelog 2012-05-16 15:58:19 +0000
16@@ -1,3 +1,4 @@
17+<<<<<<< TREE
18 BRANCH INSTEAD until "quantal" gets the first upload
19
20 ALSO DO NOT MERGE TRUNK BACK INTO 5.2, ONLY 5.2 INTO TRUNK
21@@ -19,6 +20,17 @@
22 -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 11 May 2012 10:06:18 +0200
23
24 software-center (5.2.2) UNRELEASED; urgency=low
25+=======
26+software-center (5.2.3) UNRELEASED; urgency=low
27+
28+ * lp:~evfool/software-center/lp987801:
29+ - Only show the version label once for each version in
30+ reviews (LP: #987801)
31+
32+ -- Michael Vogt <michael.vogt@ubuntu.com> Wed, 16 May 2012 08:58:34 +0200
33+
34+software-center (5.2.2) precise-proposed; urgency=low
35+>>>>>>> MERGE-SOURCE
36
37 [ Michael Vogt ]
38 * lp:~mvo/software-center/whatsnew-leak-lp985389:
39@@ -57,7 +69,10 @@
40 * lp:~gary-lasker/software-center/fix-crash_lp973379:
41 - ensure that the cache is ready before using the recommender
42 service (LP: #973379 )
43-
44+ * lp:~gary-lasker/software-center/toolbar-buttons-insensitive-during-startup:
45+ - set the toolbar buttons insensitive for the duration of time that the
46+ lobby panels are initializing (LP: #999486, LP: #994341)
47+
48 [ Robert Roth ]
49 * lp:~evfool/software-center/bug532072:
50 - escape markup for support info and license (LP: #993279)
51@@ -88,7 +103,7 @@
52 - test code change only! add memory and cpt stats to the
53 test script
54
55- -- Gary Lasker <gary.lasker@canonical.com> Mon, 14 May 2012 22:36:15 -0400
56+ -- Michael Vogt <michael.vogt@ubuntu.com> Tue, 15 May 2012 20:22:02 +0200
57
58 software-center (5.2.1) precise-proposed; urgency=low
59
60
61=== modified file 'softwarecenter/backend/spawn_helper.py'
62--- softwarecenter/backend/spawn_helper.py 2012-03-19 13:35:47 +0000
63+++ softwarecenter/backend/spawn_helper.py 2012-05-16 15:58:19 +0000
64@@ -86,6 +86,9 @@
65 self.run(cmd)
66
67 def run(self, cmd):
68+ # only useful for debugging
69+ if "SOFTWARE_CENTER_DISABLE_SPAWN_HELPER" in os.environ:
70+ return
71 self._cmd = cmd
72 (pid, stdin, stdout, stderr) = GObject.spawn_async(
73 cmd, flags=GObject.SPAWN_DO_NOT_REAP_CHILD,
74
75=== modified file 'softwarecenter/db/__init__.py'
76--- softwarecenter/db/__init__.py 2012-03-15 04:30:04 +0000
77+++ softwarecenter/db/__init__.py 2012-05-16 15:58:19 +0000
78@@ -1,9 +1,14 @@
79 import logging
80+
81 try:
82- from debfile import DebFileApplication
83+ from debfile import DebFileApplication, DebFileOpenError
84 DebFileApplication # pyflakes
85+ DebFileOpenError # pyflakes
86 except:
87 logging.exception("DebFileApplication import")
88
89- class DebFileApplication():
90+ class DebFileApplication(object):
91+ pass
92+
93+ class DebFileOpenError(Exception):
94 pass
95
96=== modified file 'softwarecenter/db/database.py'
97--- softwarecenter/db/database.py 2012-04-12 10:15:36 +0000
98+++ softwarecenter/db/database.py 2012-05-16 15:58:19 +0000
99@@ -139,6 +139,10 @@
100
101 def __init__(self, pathname=None, cache=None):
102 GObject.GObject.__init__(self)
103+ # initialize at creation time to avoid spurious AttributeError
104+ self._use_agent = False
105+ self._use_axi = False
106+
107 if pathname is None:
108 pathname = softwarecenter.paths.XAPIAN_PATH
109 self._db_pathname = pathname
110
111=== modified file 'softwarecenter/db/debfile.py'
112--- softwarecenter/db/debfile.py 2012-03-29 00:18:23 +0000
113+++ softwarecenter/db/debfile.py 2012-05-16 15:58:19 +0000
114@@ -27,12 +27,28 @@
115 from softwarecenter.utils import ExecutionTime, utf8
116
117
118+DEB_MIME_TYPE = 'application/x-debian-package'
119+
120+
121+def is_deb_file(debfile):
122+ mtype = guess_type(debfile)
123+ return mtype is not None and DEB_MIME_TYPE in mtype
124+
125+
126+class DebFileOpenError(Exception):
127+ """ Raised if a DebFile fails to open """
128+
129+ def __init__(self, msg, path):
130+ super(DebFileOpenError, self).__init__(msg)
131+ self.path = path
132+
133+
134 class DebFileApplication(Application):
135
136 def __init__(self, debfile):
137- # sanity check
138- if not debfile.endswith(".deb"):
139- raise ValueError("Need a deb file, got '%s'" % debfile)
140+ if not is_deb_file(debfile):
141+ raise DebFileOpenError("Could not open %r." % debfile, debfile)
142+
143 # work out debname/appname
144 debname = os.path.splitext(os.path.basename(debfile))[0]
145 pkgname = debname.split('_')[0].lower()
146@@ -50,7 +66,21 @@
147 def __init__(self, db, doc=None, application=None):
148 super(AppDetailsDebFile, self).__init__(db, doc, application)
149 if doc:
150- raise ValueError("doc must be None for deb files")
151+ raise DebFileOpenError("AppDetailsDebFile: doc must be None.")
152+
153+ self._error = None
154+ # check errors before creating the DebPackage
155+ if not os.path.exists(self._app.request):
156+ self._error = _("Not found")
157+ self._error_not_found = utf8(_(u"The file \u201c%s\u201d "
158+ "does not exist.")) % utf8(self._app.request)
159+ elif not is_deb_file(self._app.request):
160+ self._error = _("Not found")
161+ self._error_not_found = utf8(_(u"The file \u201c%s\u201d "
162+ "is not a software package.")) % utf8(self._app.request)
163+
164+ if self._error is not None:
165+ return
166
167 try:
168 with ExecutionTime("create DebPackage"):
169@@ -59,23 +89,10 @@
170 self._deb = DebPackage(self._app.request, self._cache._cache)
171 except:
172 self._deb = None
173- self._pkg = None
174- if not os.path.exists(self._app.request):
175- self._error = _("Not found")
176- self._error_not_found = utf8(_(u"The file \u201c%s\u201d "
177- "does not exist.")) % utf8(self._app.request)
178- else:
179- mimetype = guess_type(self._app.request)
180- if mimetype[0] != "application/x-debian-package":
181- self._error = _("Not found")
182- self._error_not_found = utf8(_(u"The file \u201c%s\u201d "
183- "is not a software package.")) % utf8(
184- self._app.request)
185- else:
186- # deb files which are corrupt
187- self._error = _("Internal Error")
188- self._error_not_found = utf8(_(u"The file \u201c%s\u201d "
189- "could not be opened.")) % utf8(self._app.request)
190+ # deb files which are corrupt
191+ self._error = _("Internal Error")
192+ self._error_not_found = utf8(_(u"The file \u201c%s\u201d "
193+ "could not be opened.")) % utf8(self._app.request)
194 return
195
196 if self.pkgname and self.pkgname != self._app.pkgname:
197
198=== modified file 'softwarecenter/enums.py'
199--- softwarecenter/enums.py 2012-03-21 20:54:46 +0000
200+++ softwarecenter/enums.py 2012-05-16 15:58:19 +0000
201@@ -237,6 +237,12 @@
202 REPAIR = "repair_dependencies"
203
204
205+# Search separators
206+class SearchSeparators:
207+ REGULAR = " "
208+ PACKAGE = ","
209+
210+
211 # mouse event codes for back/forward buttons
212 # TODO: consider whether we ought to get these values from gconf so that we
213 # can be sure to use the corresponding values used by Nautilus:
214
215=== modified file 'softwarecenter/testutils.py'
216--- softwarecenter/testutils.py 2012-03-19 14:23:52 +0000
217+++ softwarecenter/testutils.py 2012-05-16 15:58:19 +0000
218@@ -22,6 +22,7 @@
219 import tempfile
220 import time
221
222+from mock import Mock
223
224 m_dbus = m_polkit = m_aptd = None
225
226@@ -145,7 +146,6 @@
227 """ take a application and return a app where the details are a mock
228 of the real details so they can easily be modified
229 """
230- from mock import Mock
231 import copy
232 app = copy.copy(real_app)
233 db = get_test_db()
234@@ -160,6 +160,16 @@
235 return app
236
237
238+def get_mock_options():
239+ """Return a mock suitable to act as SoftwareCenterAppGtk3's options."""
240+ mock_options = Mock()
241+ mock_options.display_navlog = False
242+ mock_options.disable_apt_xapian_index = False
243+ mock_options.disable_buy = False
244+
245+ return mock_options
246+
247+
248 def setup_test_env():
249 """ Setup environment suitable for running the test/* code in a checkout.
250 This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir.
251
252=== modified file 'softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py'
253--- softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py 2012-03-15 09:32:18 +0000
254+++ softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py 2012-05-16 15:58:19 +0000
255@@ -23,9 +23,10 @@
256
257
258 # based on SimpleGladeApp
259-class SimpleGtkbuilderApp:
260+class SimpleGtkbuilderApp(object):
261
262 def __init__(self, path, domain):
263+ super(SimpleGtkbuilderApp, self).__init__()
264 self.builder = Gtk.Builder()
265 self.builder.set_translation_domain(domain)
266 self.builder.add_from_file(path)
267
268=== modified file 'softwarecenter/ui/gtk3/app.py'
269--- softwarecenter/ui/gtk3/app.py 2012-05-15 09:23:50 +0000
270+++ softwarecenter/ui/gtk3/app.py 2012-05-16 15:58:19 +0000
271@@ -31,6 +31,7 @@
272 import gettext
273 import logging
274 import os
275+import re
276 import subprocess
277 import sys
278 import xapian
279@@ -47,28 +48,35 @@
280
281 # db imports
282 from softwarecenter.db.application import Application
283-from softwarecenter.db import DebFileApplication
284+from softwarecenter.db import DebFileApplication, DebFileOpenError
285 from softwarecenter.i18n import init_locale
286
287 # misc imports
288 from softwarecenter.plugin import PluginManager
289 from softwarecenter.paths import SOFTWARE_CENTER_PLUGIN_DIRS
290-from softwarecenter.enums import (Icons,
291- PkgStates,
292- ViewPages,
293- AppActions,
294- DB_SCHEMA_VERSION,
295- MOUSE_EVENT_FORWARD_BUTTON,
296- MOUSE_EVENT_BACK_BUTTON,
297- SOFTWARE_CENTER_TOS_LINK,
298- SOFTWARE_CENTER_NAME_KEYRING)
299-from softwarecenter.utils import (clear_token_from_ubuntu_sso_sync,
300- get_http_proxy_string_from_gsettings,
301- wait_for_apt_cache_ready,
302- ExecutionTime,
303- is_unity_running)
304-from softwarecenter.ui.gtk3.utils import (get_sc_icon_theme,
305- init_sc_css_provider)
306+from softwarecenter.enums import (
307+ AppActions,
308+ DB_SCHEMA_VERSION,
309+ Icons,
310+ MOUSE_EVENT_FORWARD_BUTTON,
311+ MOUSE_EVENT_BACK_BUTTON,
312+ PkgStates,
313+ SearchSeparators,
314+ SOFTWARE_CENTER_TOS_LINK,
315+ SOFTWARE_CENTER_NAME_KEYRING,
316+ ViewPages,
317+)
318+from softwarecenter.utils import (
319+ clear_token_from_ubuntu_sso_sync,
320+ get_http_proxy_string_from_gsettings,
321+ wait_for_apt_cache_ready,
322+ ExecutionTime,
323+ is_unity_running,
324+)
325+from softwarecenter.ui.gtk3.utils import (
326+ get_sc_icon_theme,
327+ init_sc_css_provider,
328+)
329 from softwarecenter.version import VERSION
330 from softwarecenter.db.database import StoreDatabase
331 try:
332@@ -87,10 +95,14 @@
333 from softwarecenter.ui.gtk3.panes.historypane import HistoryPane
334 from softwarecenter.ui.gtk3.panes.globalpane import GlobalPane
335 from softwarecenter.ui.gtk3.panes.pendingpane import PendingPane
336-from softwarecenter.ui.gtk3.session.appmanager import (ApplicationManager,
337- get_appmanager)
338+from softwarecenter.ui.gtk3.session.appmanager import (
339+ ApplicationManager,
340+ get_appmanager,
341+ )
342 from softwarecenter.ui.gtk3.session.viewmanager import (
343- ViewManager, get_viewmanager)
344+ ViewManager,
345+ get_viewmanager,
346+ )
347 from softwarecenter.ui.gtk3.widgets.recommendations import (
348 RecommendationsOptInDialog)
349
350@@ -112,6 +124,10 @@
351 from gi.repository import Gdk
352
353 LOG = logging.getLogger(__name__)
354+PACKAGE_PREFIX = 'apt:'
355+# "apt:///" is a valid prefix for 'apt:pkgname' in alt+F2 in gnome
356+PACKAGE_PREFIX_REGEX = re.compile('^%s(?:/{2,3})*' % PACKAGE_PREFIX)
357+SEARCH_PREFIX = 'search:'
358
359
360 # py3 compat
361@@ -119,6 +135,64 @@
362 return isinstance(func, collections.Callable)
363
364
365+def parse_packages_args(packages):
366+ search_text = ''
367+ app = None
368+
369+ # avoid treating strings as sequences ('foo' should not be 'f', 'o', 'o')
370+ if isinstance(packages, basestring):
371+ packages = (packages,)
372+
373+ if not isinstance(packages, collections.Iterable):
374+ LOG.warning('show_available_packages: argument is not an iterable %r',
375+ packages)
376+ return search_text, app
377+
378+ items = [] # make a copy of the given sequence
379+ for arg in packages:
380+ # support both "pkg1 pkg" and "pkg1,pkg2" (and "pkg1,pkg2 pkg3")
381+ if "," in arg:
382+ items.extend(arg.split(SearchSeparators.PACKAGE))
383+ else:
384+ items.append(arg)
385+
386+ if len(items) > 0:
387+ # allow s-c to be called with a search term
388+ if items[0].startswith(SEARCH_PREFIX):
389+ # remove the initial search prefix
390+ items[0] = items[0].replace(SEARCH_PREFIX, '', 1)
391+ search_text = SearchSeparators.REGULAR.join(items)
392+ else:
393+ # strip away the initial apt: prefix, if present
394+ items[0] = re.sub(PACKAGE_PREFIX_REGEX, '', items[0])
395+ if len(items) > 1:
396+ # turn multiple packages into a search with "," as separator
397+ search_text = SearchSeparators.PACKAGE.join(items)
398+
399+ if not search_text and len(items) == 1:
400+ request = items[0]
401+ # are we dealing with a path?
402+ if os.path.exists(request) and not os.path.isdir(request):
403+ if not request.startswith('/'):
404+ # we may have been given a relative path
405+ request = os.path.abspath(request)
406+ # will raise DebOpenFileError if request is invalid
407+ app = DebFileApplication(request)
408+ else:
409+ # package from archive
410+ # if there is a "/" in the string consider it as tuple
411+ # of (pkgname, appname) for exact matching (used by
412+ # e.g. unity
413+ (pkgname, sep, appname) = request.partition("/")
414+ if pkgname or appname:
415+ app = Application(appname, pkgname)
416+ else:
417+ LOG.warning('show_available_packages: received %r but '
418+ 'can not build an Application from it.', request)
419+
420+ return search_text, app
421+
422+
423 class SoftwarecenterDbusController(dbus.service.Object):
424 """
425 This is a helper to provide the SoftwarecenterIFace
426@@ -158,13 +232,16 @@
427 # the size of the icon for dialogs
428 APP_ICON_SIZE = Gtk.IconSize.DIALOG
429
430+ START_DBUS = True
431+
432 def __init__(self, datadir, xapian_base_path, options, args=None):
433- # setup dbus and exit if there is another instance already
434- # running
435- self.setup_dbus_or_bring_other_instance_to_front(args)
436+ self.dbusControler = None
437+ if self.START_DBUS:
438+ # setup dbus and exit if there is another instance already running
439+ self.setup_dbus_or_bring_other_instance_to_front(args)
440
441 self.datadir = datadir
442- SimpleGtkbuilderApp.__init__(self,
443+ super(SoftwareCenterAppGtk3, self).__init__(
444 datadir + "/ui/gtk3/SoftwareCenter.ui",
445 "software-center")
446 gettext.bindtextdomain("software-center", "/usr/share/locale")
447@@ -273,6 +350,10 @@
448 self.vbox1.pack_start(self.global_pane, False, False, 0)
449 self.vbox1.reorder_child(self.global_pane, 1)
450
451+ # start with the toolbar buttons insensitive and don't make them
452+ # sensitive until the panel elements are ready
453+ #self.global_pane.view_switcher.set_sensitive(False)
454+
455 # available pane
456 self.available_pane = AvailablePane(self.cache,
457 self.db,
458@@ -292,8 +373,8 @@
459 self.distro,
460 self.icons,
461 self.datadir)
462- #~ self.installed_pane.connect("installed-pane-created",
463- #~ self.on_installed_pane_created)
464+ self.installed_pane.connect("installed-pane-created",
465+ self.on_installed_pane_created)
466 self.view_manager.register(self.installed_pane,
467 ViewPages.INSTALLED)
468
469@@ -472,9 +553,12 @@
470 "recommendations-opt-out",
471 self._on_recommendations_opt_out)
472 self.menuitem_recommendations.set_sensitive(True)
473+ # set the main toolbar buttons sensitive
474+ self.global_pane.view_switcher.set_sensitive(True)
475
476- #~ def on_installed_pane_created(self, widget):
477- #~ pass
478+ def on_installed_pane_created(self, widget):
479+ # set the main toolbar buttons sensitive
480+ self.global_pane.view_switcher.set_sensitive(True)
481
482 def _on_recommendations_opt_in(self, rec_panel):
483 self._update_recommendations_menuitem(opted_in=True)
484@@ -510,6 +594,18 @@
485 def on_review_stats_loaded(self, reviews):
486 LOG.debug("on_review_stats_loaded: '%s'" % len(reviews))
487
488+ def destroy(self):
489+ """Destroy this instance and every used resource."""
490+ self.window_main.destroy()
491+
492+ # remove global instances of Managers
493+ self.app_manager.destroy()
494+ self.view_manager.destroy()
495+
496+ if self.dbusControler is not None:
497+ # ensure that the dbus controller is really gone
498+ self.dbusControler.stop()
499+
500 def close_app(self):
501 """ perform tasks like save-state etc when the application is
502 exited
503@@ -524,15 +620,14 @@
504 if hasattr(self, "glaunchpad"):
505 self.glaunchpad.shutdown()
506 self.save_state()
507+ self.destroy()
508+
509 # this will not throw exceptions in pygi but "only" log via g_critical
510 # to the terminal but it might in the future so we add a handler here
511 try:
512 Gtk.main_quit()
513 except:
514 LOG.exception("Gtk.main_quit failed")
515- # ensure that the dbus controller is really gone, just for good
516- # measure
517- self.dbusControler.stop()
518 # exit here explictely to ensure that no further gtk event loops or
519 # threads run and cause havoc on exit (LP: #914393)
520 sys.exit(0)
521@@ -1202,75 +1297,49 @@
522 bus_name = dbus.service.BusName('com.ubuntu.Softwarecenter', bus)
523 self.dbusControler = SoftwarecenterDbusController(self, bus_name)
524
525+ @wait_for_apt_cache_ready
526+ def show_app(self, app):
527+ """Show 'app' in the installed pane if is installed.
528+
529+ If 'app' is not installed, show it in the available pane.
530+
531+ """
532+ if (app.pkgname in self.cache and self.cache[app.pkgname].installed):
533+ with ExecutionTime("installed_pane.init_view()"):
534+ self.installed_pane.init_view()
535+ with ExecutionTime("installed_pane.show_app()"):
536+ self.installed_pane.show_app(app)
537+ else:
538+ self.available_pane.init_view()
539+ self.available_pane.show_app(app)
540+
541 def show_available_packages(self, packages):
542 """ Show packages given as arguments in the available_pane
543 If the list of packages is only one element long show that,
544 otherwise turn it into a comma seperated search
545 """
546- # strip away the apt: prefix
547- if packages and packages[0].startswith("apt:///"):
548- # this is for 'apt:pkgname' in alt+F2 in gnome
549- packages[0] = packages[0].partition("apt:///")[2]
550- elif packages and packages[0].startswith("apt://"):
551- packages[0] = packages[0].partition("apt://")[2]
552- elif packages and packages[0].startswith("apt:"):
553- packages[0] = packages[0].partition("apt:")[2]
554-
555- # allow s-c to be called with a search term
556- if packages and packages[0].startswith("search:"):
557- packages[0] = packages[0].partition("search:")[2]
558- self.available_pane.init_view()
559- self.available_pane.searchentry.set_text(" ".join(packages))
560- return
561-
562- if len(packages) == 1:
563- request = packages[0]
564-
565- # are we dealing with a path?
566- if os.path.exists(request) and not os.path.isdir(request):
567- if not request.startswith('/'):
568- # we may have been given a relative path
569- request = os.path.join(os.getcwd(), request)
570- try:
571- app = DebFileApplication(request)
572- except ValueError as e:
573- LOG.error("can not open %s: %s" % (request, e))
574- from softwarecenter.ui.gtk3.dialogs import error
575- error(None,
576+ try:
577+ search_text, app = parse_packages_args(packages)
578+ except DebFileOpenError as e:
579+ LOG.exception("show_available_packages: can not open %r, error:",
580+ packages)
581+ dialogs.error(None,
582 _("Error"),
583- _("The file “%s” could not be opened.") % request)
584- app = None
585- else:
586- # package from archive
587- # if there is a "/" in the string consider it as tuple
588- # of (pkgname, appname) for exact matching (used by
589- # e.g. unity
590- (pkgname, sep, appname) = packages[0].partition("/")
591- app = Application(appname, pkgname)
592-
593- @wait_for_apt_cache_ready
594- def show_app(self, app):
595- # if the pkg is installed, show it in the installed pane
596- if (app.pkgname in self.cache and
597- self.cache[app.pkgname].installed):
598- with ExecutionTime("installed_pane.init_view()"):
599- self.installed_pane.init_view()
600- with ExecutionTime("installed_pane.show_app()"):
601- self.installed_pane.show_app(app)
602- else:
603- self.available_pane.init_view()
604- self.available_pane.show_app(app)
605- if app:
606- show_app(self, app)
607- return
608- elif len(packages) > 1:
609- # turn multiple packages into a search with ","
610+ _("The file “%s” could not be opened.") % e.path)
611+ search_text = app = None
612+
613+ LOG.info('show_available_packages: search_text is %r, app is %r.',
614+ search_text, app)
615+
616+ if search_text:
617 self.available_pane.init_view()
618- self.available_pane.searchentry.set_text(",".join(packages))
619- return
620- # normal startup, show the lobby (it will have a spinner when
621- # its not ready yet) - it will also initialize the view
622- self.view_manager.set_active_view(ViewPages.AVAILABLE)
623+ self.available_pane.searchentry.set_text(search_text)
624+ elif app is not None:
625+ self.show_app(app)
626+ else:
627+ # normal startup, show the lobby (it will have a spinner when
628+ # its not ready yet) - it will also initialize the view
629+ self.view_manager.set_active_view(ViewPages.AVAILABLE)
630
631 def restore_state(self):
632 if self.config.has_option("general", "size"):
633
634=== modified file 'softwarecenter/ui/gtk3/models/appstore2.py'
635--- softwarecenter/ui/gtk3/models/appstore2.py 2012-05-11 13:48:04 +0000
636+++ softwarecenter/ui/gtk3/models/appstore2.py 2012-05-16 15:58:19 +0000
637@@ -163,6 +163,7 @@
638 if doc.installed is None:
639 pkgname = self.get_pkgname(doc)
640 doc.installed = (self.is_available(doc) and
641+ pkgname in self.cache and
642 self.cache[pkgname].is_installed)
643 return doc.installed
644
645
646=== modified file 'softwarecenter/ui/gtk3/session/appmanager.py'
647--- softwarecenter/ui/gtk3/session/appmanager.py 2012-03-15 00:16:03 +0000
648+++ softwarecenter/ui/gtk3/session/appmanager.py 2012-05-16 15:58:19 +0000
649@@ -71,6 +71,11 @@
650 else:
651 _appmanager = self
652
653+ def destroy(self):
654+ """Destroy the global instance."""
655+ global _appmanager
656+ _appmanager = None
657+
658 def request_action(self, app, addons_install, addons_remove, action):
659 """callback when an app action is requested from the appview,
660 if action is "remove", must check if other dependencies have to be
661
662=== modified file 'softwarecenter/ui/gtk3/session/viewmanager.py'
663--- softwarecenter/ui/gtk3/session/viewmanager.py 2012-03-15 00:16:03 +0000
664+++ softwarecenter/ui/gtk3/session/viewmanager.py 2012-05-16 15:58:19 +0000
665@@ -69,6 +69,11 @@
666 else:
667 _viewmanager = self
668
669+ def destroy(self):
670+ """Destroy the global instance."""
671+ global _viewmanager
672+ _viewmanager = None
673+
674 def on_search_terms_changed(self, widget, new_text):
675 pane = self.get_current_view_widget()
676 if hasattr(pane, "on_search_terms_changed"):
677
678=== modified file 'softwarecenter/ui/gtk3/views/appdetailsview.py'
679--- softwarecenter/ui/gtk3/views/appdetailsview.py 2012-05-14 03:16:07 +0000
680+++ softwarecenter/ui/gtk3/views/appdetailsview.py 2012-05-16 15:58:19 +0000
681@@ -1738,7 +1738,7 @@
682 # update all (but skip the addons calculation if this is a
683 # DebFileApplication as this is not useful for this case and it
684 # increases the view load time dramatically)
685- skip_update_addons = type(self.app) == DebFileApplication
686+ skip_update_addons = isinstance(self.app, DebFileApplication)
687 self._update_all(self.app_details,
688 skip_update_addons=skip_update_addons)
689
690@@ -2013,7 +2013,7 @@
691 self.addons_manager.addons_to_remove,
692 self.app.archive_suite)
693 total_download_size, total_install_size = res
694- if res == (0, 0) and type(self.app) == DebFileApplication:
695+ if res == (0, 0) and isinstance(self.app, DebFileApplication):
696 total_install_size = self.app_details.installed_size
697 if total_download_size > 0:
698 download_size = GLib.format_size(total_download_size)
699
700=== modified file 'softwarecenter/ui/gtk3/widgets/reviews.py'
701--- softwarecenter/ui/gtk3/widgets/reviews.py 2012-05-15 08:37:41 +0000
702+++ softwarecenter/ui/gtk3/widgets/reviews.py 2012-05-16 15:58:19 +0000
703@@ -194,11 +194,18 @@
704 UI vbox out of them
705 """
706 self.logged_in_person = get_person_from_config()
707+ is_first_for_version = None
708 if self.reviews:
709+ previous_review = None
710 for r in self.reviews:
711 pkgversion = self._parent.app_details.version
712+ if previous_review:
713+ is_first_for_version = previous_review.version != r.version
714+ else:
715+ is_first_for_version = True
716+ previous_review = r
717 review = UIReview(r, pkgversion, self.logged_in_person,
718- self.useful_votes)
719+ self.useful_votes, is_first_for_version)
720 review.show_all()
721 self.vbox.pack_start(review, True, True, 0)
722
723@@ -399,7 +406,8 @@
724 useful/inappropriate etc
725 """
726 def __init__(self, review_data=None, app_version=None,
727- logged_in_person=None, useful_votes=None):
728+ logged_in_person=None, useful_votes=None,
729+ first_for_version=True):
730 GObject.GObject.__init__(self)
731 self.set_spacing(StockEms.SMALL)
732
733@@ -439,8 +447,8 @@
734 self.usefulness_error = False
735 self.delete_error = False
736 self.modify_error = False
737-
738- self.pack_start(self.version_label, False, False, 0)
739+ if first_for_version:
740+ self.pack_start(self.version_label, False, False, 0)
741 self.pack_start(self.header, False, False, 0)
742 self.pack_start(self.body, False, False, 0)
743 self.pack_start(self.footer, False, False, StockEms.SMALL)
744
745=== modified file 'softwarecenter/ui/gtk3/widgets/videoplayer.py'
746--- softwarecenter/ui/gtk3/widgets/videoplayer.py 2012-03-08 17:42:36 +0000
747+++ softwarecenter/ui/gtk3/widgets/videoplayer.py 2012-05-16 15:58:19 +0000
748@@ -72,7 +72,11 @@
749 # uri property
750 def _set_uri(self, v):
751 self._uri = v or ""
752- self.webkit.load_uri(self._uri)
753+ if self._uri:
754+ # only load the uri if it's defined, otherwise we may get:
755+ # Program received signal SIGSEGV, Segmentation fault.
756+ # webkit_web_frame_load_uri () from /usr/lib/libwebkitgtk-3.0.so.0
757+ self.webkit.load_uri(self._uri)
758
759 def _get_uri(self):
760 return self._uri
761
762=== added file 'test/gtk3/__init__.py'
763=== added file 'test/gtk3/test_app.py'
764--- test/gtk3/test_app.py 1970-01-01 00:00:00 +0000
765+++ test/gtk3/test_app.py 2012-05-16 15:58:19 +0000
766@@ -0,0 +1,357 @@
767+#!/usr/bin/python
768+
769+import os
770+import unittest
771+
772+from collections import defaultdict
773+from functools import partial
774+
775+from mock import Mock
776+
777+from testutils import get_mock_options, setup_test_env
778+setup_test_env()
779+
780+import softwarecenter.paths
781+from softwarecenter.db import DebFileApplication, DebFileOpenError
782+from softwarecenter.enums import PkgStates, SearchSeparators
783+from softwarecenter.ui.gtk3 import app
784+
785+
786+class FakedCache(dict):
787+ """A faked cache."""
788+
789+ def __init__(self, *a, **kw):
790+ super(FakedCache, self).__init__()
791+ self._callbacks = defaultdict(list)
792+ self.ready = False
793+
794+ def open(self):
795+ """Open this cache."""
796+ self.ready = True
797+
798+ def connect(self, signal, callback):
799+ """Connect a signal with a callback."""
800+ self._callbacks[signal].append(callback)
801+
802+ def disconnect_by_func(self, callback):
803+ """Disconnect 'callback' from every signal."""
804+ for signal, cb in self._callbacks.iteritems():
805+ if cb == callback:
806+ self._callbacks[signal].remove(callback)
807+ if len(self._callbacks[signal]) == 0:
808+ self._callbacks.pop(signal)
809+
810+ def get_addons(self, pkgname):
811+ """Return (recommended, suggested) addons for 'pkgname'."""
812+ return ([],[])
813+
814+ def get_total_size_on_install(self,pkgname, addons_to_install,
815+ addons_to_remove, archive_suite):
816+ """Return a fake (total_download_size, total_install_size) result."""
817+ return (0, 0)
818+
819+
820+class ParsePackagesArgsTestCase(unittest.TestCase):
821+ """Test suite for the parse_packages_args helper."""
822+
823+ pkg_name = 'foo'
824+
825+ def transform_for_test(self, items):
826+ """Transform a sequence into a comma separated string."""
827+ return app.SEARCH_PREFIX + SearchSeparators.REGULAR.join(items)
828+
829+ def do_check(self, apps, items=None):
830+ """Check that the available_pane was shown."""
831+ if items is None:
832+ items = self.transform_for_test(apps)
833+
834+ search_text, result_app = app.parse_packages_args(items)
835+
836+ self.assertEqual(SearchSeparators.REGULAR.join(apps), search_text)
837+ self.assertIsNone(result_app)
838+
839+ def test_empty(self):
840+ """Pass an empty argument, show the 'available' view."""
841+ self.do_check(apps=())
842+
843+ def test_single_empty_item(self):
844+ """Pass a single empty item, show the 'available' view."""
845+ self.do_check(apps=('',))
846+
847+ def test_single_item(self):
848+ """Pass a single item, show the 'available' view."""
849+ self.do_check(apps=(self.pkg_name,))
850+
851+ def test_two_items(self):
852+ """Pass two items, show the 'available' view."""
853+ self.do_check(apps=(self.pkg_name, 'bar'))
854+
855+ def test_several_items(self):
856+ """Pass several items, show the 'available' view."""
857+ self.do_check(apps=(self.pkg_name, 'firefox', 'software-center'))
858+
859+
860+class ParsePackageArgsAsFileTestCase(unittest.TestCase):
861+
862+ def test_item_is_a_file(self):
863+ """Pass an item that is an existing file."""
864+ # pass a real deb here
865+ fname = os.path.join(os.path.dirname(os.path.abspath(__file__)),
866+ "..", "data", "test_debs", "gdebi-test1.deb")
867+ assert os.path.exists(fname)
868+ # test once as string and as list
869+ for items in ( fname, [fname] ):
870+ search_text, result_app = app.parse_packages_args(fname)
871+ self.assertIsInstance(result_app, DebFileApplication)
872+
873+ def test_item_is_invalid_file(self):
874+ """ Pass an invalid file item """
875+ fname = __file__
876+ assert os.path.exists(fname)
877+ self.assertRaises(DebFileOpenError, app.parse_packages_args, fname)
878+
879+
880+class ParsePackagesWithAptPrefixTestCase(ParsePackagesArgsTestCase):
881+
882+ installed = None
883+
884+ def setUp(self):
885+ super(ParsePackagesWithAptPrefixTestCase, self).setUp()
886+
887+ self.cache = FakedCache()
888+ self.db = app.StoreDatabase(cache=self.cache)
889+
890+ assert self.pkg_name not in self.cache
891+ if self.installed is not None:
892+ mock_cache_entry = Mock()
893+ mock_cache_entry.website = None
894+ mock_cache_entry.license = None
895+ mock_cache_entry.installed_files = []
896+ mock_cache_entry.candidate = Mock()
897+ mock_cache_entry.candidate.version = '1.0'
898+ mock_cache_entry.candidate.description = 'A nonsense app.'
899+ mock_cache_entry.candidate.origins = ()
900+ mock_cache_entry.versions = (Mock(),)
901+ mock_cache_entry.versions[0].version = '0.99'
902+ mock_cache_entry.versions[0].origins = (Mock(),)
903+ mock_cache_entry.versions[0].origins[0].archive = 'test'
904+ mock_cache_entry.is_installed = self.installed
905+ if self.installed:
906+ mock_cache_entry.installed = Mock()
907+ mock_cache_entry.installed.version = '0.90'
908+ mock_cache_entry.installed.installed_size = 0
909+ else:
910+ mock_cache_entry.installed = None
911+
912+ self.cache[self.pkg_name] = mock_cache_entry
913+ self.addCleanup(self.cache.pop, self.pkg_name)
914+
915+
916+ def transform_for_test(self, items):
917+ """Do nothing."""
918+ return items
919+
920+ def check_package_availability(self, name):
921+ """Check whether the package 'name' is available."""
922+ if name not in self.cache:
923+ state = PkgStates.NOT_FOUND
924+ elif self.cache[name].installed:
925+ state = PkgStates.INSTALLED
926+ else:
927+ state = PkgStates.UNINSTALLED
928+ return state
929+
930+ def do_check(self, apps, items=None):
931+ """Check that the available_pane was shown."""
932+ if items is None:
933+ items = self.transform_for_test(apps)
934+
935+ search_text, result_app = app.parse_packages_args(items)
936+
937+ if apps and len(apps) == 1 and apps[0] and not os.path.isfile(apps[0]):
938+ self.assertIsNotNone(result_app)
939+ app_details = result_app.get_details(self.db)
940+
941+ self.assertEqual(apps[0], app_details.name)
942+ state = self.check_package_availability(app_details.name)
943+ self.assertEqual(state, app_details.pkg_state)
944+ else:
945+ self.assertIsNone(result_app)
946+
947+ if apps and (len(apps) > 1 or os.path.isfile(apps[0])):
948+ self.assertEqual(SearchSeparators.PACKAGE.join(apps), search_text)
949+ else:
950+ self.assertEqual('', search_text)
951+
952+ def test_item_with_prefix(self):
953+ """Pass a item with the item prefix."""
954+ for prefix in ('apt:', 'apt://', 'apt:///'):
955+ for case in (self.pkg_name, app.PACKAGE_PREFIX + self.pkg_name):
956+ self.do_check(apps=(case,), items=(prefix + case,))
957+
958+
959+class ParsePackagesNotInstalledTestCase(ParsePackagesWithAptPrefixTestCase):
960+ """Test suite for parsing/searching/loading package lists."""
961+
962+ installed = False
963+
964+
965+class ParsePackagesInstalledTestCase(ParsePackagesWithAptPrefixTestCase):
966+ """Test suite for parsing/searching/loading package lists."""
967+
968+ installed = True
969+
970+
971+class ParsePackagesArgsStringTestCase(ParsePackagesWithAptPrefixTestCase):
972+ """Test suite for parsing/loading package lists from strings."""
973+
974+ def transform_for_test(self, items):
975+ """Transform a sequence into a comma separated string."""
976+ return SearchSeparators.PACKAGE.join(items)
977+
978+
979+class AppTestCase(unittest.TestCase):
980+ """Test suite for the app module."""
981+
982+ def setUp(self):
983+ super(AppTestCase, self).setUp()
984+ self.called = defaultdict(list)
985+ self.addCleanup(self.called.clear)
986+
987+ orig = app.SoftwareCenterAppGtk3.START_DBUS
988+ self.addCleanup(setattr, app.SoftwareCenterAppGtk3, 'START_DBUS', orig)
989+ app.SoftwareCenterAppGtk3.START_DBUS = False
990+
991+ orig = app.get_pkg_info
992+ self.addCleanup(setattr, app, 'get_pkg_info', orig)
993+ app.get_pkg_info = lambda: FakedCache()
994+
995+ datadir = softwarecenter.paths.datadir
996+ xapianpath = softwarecenter.paths.XAPIAN_BASE_PATH
997+ options = get_mock_options()
998+ self.app = app.SoftwareCenterAppGtk3(datadir, xapianpath, options)
999+ self.addCleanup(self.app.destroy)
1000+
1001+ self.app.cache.open()
1002+
1003+ # connect some signals of interest
1004+ cid = self.app.installed_pane.connect('installed-pane-created',
1005+ partial(self.track_calls, 'pane-created'))
1006+ self.addCleanup(self.app.installed_pane.disconnect, cid)
1007+
1008+ cid = self.app.available_pane.connect('available-pane-created',
1009+ partial(self.track_calls, 'pane-created'))
1010+ self.addCleanup(self.app.available_pane.disconnect, cid)
1011+
1012+ def track_calls(self, name, *a, **kw):
1013+ """Record the callback for 'name' using 'args' and 'kwargs'."""
1014+ self.called[name].append((a, kw))
1015+
1016+
1017+class ShowPackagesTestCase(AppTestCase):
1018+ """Test suite for parsing/searching/loading package lists."""
1019+
1020+ def do_check(self, packages, search_text):
1021+ """Check that the available_pane was shown."""
1022+ self.app.show_available_packages(packages=packages)
1023+
1024+ self.assertEqual(self.called,
1025+ {'pane-created': [((self.app.available_pane,), {})]})
1026+
1027+ actual = self.app.available_pane.searchentry.get_text()
1028+ self.assertEqual(search_text, actual,
1029+ 'Expected search text %r (got %r instead) for packages %r.' %
1030+ (search_text, actual, packages))
1031+
1032+ self.assertIsNone(self.app.available_pane.app_details_view.app_details)
1033+
1034+ def test_show_available_packages_search_prefix(self):
1035+ """Check that the available_pane was shown."""
1036+ self.do_check(packages='search:foo,bar baz', search_text='foo bar baz')
1037+
1038+ def test_show_available_packages_apt_prefix(self):
1039+ """Check that the available_pane was shown."""
1040+ for prefix in ('apt:', 'apt://', 'apt:///'):
1041+ self.do_check(packages=prefix + 'foo,bar,baz',
1042+ search_text='foo,bar,baz')
1043+
1044+
1045+class ShowPackagesOnePackageTestCase(AppTestCase):
1046+ """Test suite for parsing/searching/loading package lists."""
1047+
1048+ pkg_name = 'foo'
1049+ installed = None
1050+
1051+ def setUp(self):
1052+ super(ShowPackagesOnePackageTestCase, self).setUp()
1053+ assert self.pkg_name not in self.app.cache
1054+ if self.installed is not None:
1055+ mock_cache_entry = Mock()
1056+ mock_cache_entry.website = None
1057+ mock_cache_entry.license = None
1058+ mock_cache_entry.installed_files = []
1059+ mock_cache_entry.candidate = Mock()
1060+ mock_cache_entry.candidate.version = '1.0'
1061+ mock_cache_entry.candidate.description = 'A nonsense app.'
1062+ mock_cache_entry.candidate.origins = ()
1063+ mock_cache_entry.versions = (Mock(),)
1064+ mock_cache_entry.versions[0].version = '0.99'
1065+ mock_cache_entry.versions[0].origins = (Mock(),)
1066+ mock_cache_entry.versions[0].origins[0].archive = 'test'
1067+ mock_cache_entry.is_installed = self.installed
1068+ if self.installed:
1069+ mock_cache_entry.installed = Mock()
1070+ mock_cache_entry.installed.version = '0.90'
1071+ mock_cache_entry.installed.installed_size = 0
1072+ else:
1073+ mock_cache_entry.installed = None
1074+
1075+ self.app.cache[self.pkg_name] = mock_cache_entry
1076+ self.addCleanup(self.app.cache.pop, self.pkg_name)
1077+
1078+ def check_package_availability(self, name):
1079+ """Check whether the package 'name' is available."""
1080+ pane = self.app.available_pane
1081+ if name not in self.app.cache:
1082+ state = PkgStates.NOT_FOUND
1083+ elif self.app.cache[name].installed:
1084+ state = PkgStates.INSTALLED
1085+ pane = self.app.installed_pane
1086+ else:
1087+ state = PkgStates.UNINSTALLED
1088+
1089+ self.assertEqual(state, pane.app_details_view.app_details.pkg_state)
1090+
1091+ return pane
1092+
1093+ def test_show_available_packages(self):
1094+ """Check that the available_pane was shown."""
1095+ self.app.show_available_packages(packages=self.pkg_name)
1096+
1097+ expected_pane = self.check_package_availability(self.pkg_name)
1098+ name = expected_pane.app_details_view.app_details.name
1099+ self.assertEqual(self.pkg_name, name)
1100+
1101+ self.assertEqual('', self.app.available_pane.searchentry.get_text())
1102+
1103+ self.assertEqual(self.called,
1104+ {'pane-created': [((expected_pane,), {})]})
1105+
1106+
1107+class ShowPackagesNotInstalledTestCase(ShowPackagesOnePackageTestCase):
1108+ """Test suite for parsing/searching/loading package lists."""
1109+
1110+ installed = False
1111+
1112+
1113+class ShowPackagesInstalledTestCase(ShowPackagesOnePackageTestCase):
1114+ """Test suite for parsing/searching/loading package lists."""
1115+
1116+ installed = True
1117+
1118+
1119+if __name__ == "__main__":
1120+ # avoid spawning recommender-agent, reviews, software-center-agent etc,
1121+ # cuts ~5s or so
1122+ os.environ["SOFTWARE_CENTER_DISABLE_SPAWN_HELPER"] = "1"
1123+ unittest.main()
1124
1125=== modified file 'test/gtk3/test_debfile_view.py'
1126--- test/gtk3/test_debfile_view.py 2012-02-23 16:40:27 +0000
1127+++ test/gtk3/test_debfile_view.py 2012-05-16 15:58:19 +0000
1128@@ -3,22 +3,18 @@
1129 import time
1130 import unittest
1131
1132-from mock import Mock
1133-
1134-from testutils import setup_test_env, do_events
1135+from testutils import do_events, get_mock_options, setup_test_env
1136 setup_test_env()
1137
1138 import softwarecenter.paths
1139 from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3
1140 from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane
1141
1142+
1143 class DebFileOpenTestCase(unittest.TestCase):
1144
1145 def test_deb_file_view_error(self):
1146- mock_options = Mock()
1147- mock_options.display_navlog = False
1148- mock_options.disable_apt_xapian_index = False
1149- mock_options.disable_buy = False
1150+ mock_options = get_mock_options()
1151 xapianpath = softwarecenter.paths.XAPIAN_BASE_PATH
1152 app = SoftwareCenterAppGtk3(
1153 softwarecenter.paths.datadir, xapianpath, mock_options)
1154@@ -37,7 +33,6 @@
1155 # this is deb that is not installable
1156 action_button = app.available_pane.app_details_view.pkg_statusbar.button
1157 self.assertFalse(action_button.get_property("visible"))
1158-
1159
1160
1161 if __name__ == "__main__":
1162
1163=== modified file 'test/gtk3/test_purchase.py'
1164--- test/gtk3/test_purchase.py 2012-03-19 14:38:10 +0000
1165+++ test/gtk3/test_purchase.py 2012-05-16 15:58:19 +0000
1166@@ -3,12 +3,12 @@
1167 import time
1168 import unittest
1169
1170-from mock import Mock,patch
1171+from mock import Mock, patch
1172
1173 from testutils import setup_test_env
1174 setup_test_env()
1175
1176-from softwarecenter.testutils import do_events
1177+from softwarecenter.testutils import do_events, get_mock_options
1178 from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3
1179 from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane
1180 import softwarecenter.paths
1181@@ -35,7 +35,7 @@
1182 self.assertTrue("skipping" in mock.debug.call_args[0][0])
1183 self.assertFalse("consumer_secret" in mock.debug.call_args[0][0])
1184 mock.reset_mock()
1185-
1186+
1187 # run another one
1188 win.destroy()
1189
1190@@ -71,25 +71,21 @@
1191 # run another one
1192 win.destroy()
1193
1194-
1195 def test_reinstall_previous_purchase_display(self):
1196- mock_options = Mock()
1197- mock_options.display_navlog = False
1198- mock_options.disable_apt_xapian_index = False
1199- mock_options.disable_buy = False
1200+ mock_options = get_mock_options()
1201 xapiandb = "/var/cache/software-center/"
1202 app = SoftwareCenterAppGtk3(
1203 softwarecenter.paths.datadir, xapiandb, mock_options)
1204- # real app opens cache async
1205- app.cache.open()
1206- # show it
1207+ # real app opens cache async
1208+ app.cache.open()
1209+ # show it
1210 app.window_main.show_all()
1211 app.available_pane.init_view()
1212 self._p()
1213 app.on_menuitem_reinstall_purchases_activate(None)
1214 # it can take a bit until the sso client is ready
1215 for i in range(100):
1216- if (app.available_pane.get_current_page() ==
1217+ if (app.available_pane.get_current_page() ==
1218 AvailablePane.Pages.LIST):
1219 break
1220 self._p()
1221
1222=== modified file 'test/test_database.py'
1223--- test/test_database.py 2012-04-13 16:32:26 +0000
1224+++ test/test_database.py 2012-05-16 15:58:19 +0000
1225@@ -406,9 +406,13 @@
1226 packagesize 3 # package size
1227 app-popcon 4 # app-install .desktop popcon rank
1228 """
1229- open("axi-test-values","w").write(s)
1230+ fname = "axi-test-values"
1231+ with open(fname, "w") as f:
1232+ f.write(s)
1233+ self.addCleanup(os.remove, fname)
1234+
1235 #db = StoreDatabase("/var/cache/software-center/xapian", self.cache)
1236- axi_values = parse_axi_values_file("axi-test-values")
1237+ axi_values = parse_axi_values_file(fname)
1238 self.assertNotEqual(axi_values, {})
1239 print axi_values
1240
1241@@ -428,7 +432,8 @@
1242 nonblocking_load=False)
1243 self.assertTrue(len(enquirer.get_docids()) > 0)
1244 # FIXME: test more of the interface
1245-
1246+
1247+
1248 class UtilsTestCase(unittest.TestCase):
1249
1250 def test_utils_get_installed_package_list(self):
1251
1252=== modified file 'test/test_debfileapplication.py'
1253--- test/test_debfileapplication.py 2012-02-23 16:40:27 +0000
1254+++ test/test_debfileapplication.py 2012-05-16 15:58:19 +0000
1255@@ -7,7 +7,7 @@
1256 setup_test_env()
1257
1258 from softwarecenter.enums import PkgStates
1259-from softwarecenter.db.debfile import DebFileApplication
1260+from softwarecenter.db.debfile import DebFileApplication, DebFileOpenError
1261 from softwarecenter.testutils import get_test_db
1262
1263 DEBFILE_PATH = './data/test_debs/gdebi-test9.deb'
1264@@ -22,6 +22,7 @@
1265 DEBFILE_PATH_CORRUPT = './data/test_debs/corrupt.deb'
1266 DEBFILE_NOT_INSTALLABLE = './data/test_debs/gdebi-test1.deb'
1267
1268+
1269 class TestDebFileApplication(unittest.TestCase):
1270 """ Test the class DebFileApplication """
1271
1272@@ -31,7 +32,7 @@
1273 def test_get_name(self):
1274 debfileapplication = DebFileApplication(DEBFILE_PATH)
1275 debfiledetails = debfileapplication.get_details(self.db)
1276-
1277+
1278 self.assertEquals(debfiledetails.name, DEBFILE_NAME)
1279
1280 def test_get_description(self):
1281@@ -64,9 +65,10 @@
1282 debfileapplication = DebFileApplication(DEBFILE_PATH_NOTFOUND)
1283 debfiledetails = debfileapplication.get_details(self.db)
1284 self.assertEquals(debfiledetails.pkg_state, PkgStates.NOT_FOUND)
1285-
1286+
1287 def test_get_pkg_state_not_a_deb(self):
1288- self.assertRaises(ValueError, DebFileApplication, DEBFILE_PATH_NOTADEB)
1289+ self.assertRaises(DebFileOpenError,
1290+ DebFileApplication, DEBFILE_PATH_NOTADEB)
1291
1292 def test_get_pkg_state_corrupt(self):
1293 debfileapplication = DebFileApplication(DEBFILE_PATH_CORRUPT)
1294@@ -87,12 +89,14 @@
1295 debfileapplication = DebFileApplication(DEBFILE_PATH)
1296 debfiledetails = debfileapplication.get_details(self.db)
1297 self.assertEquals(debfiledetails.installed_size, 0)
1298-
1299+
1300 def test_get_warning(self):
1301 debfileapplication = DebFileApplication(DEBFILE_PATH)
1302 debfiledetails = debfileapplication.get_details(self.db)
1303 self.assertEquals(debfiledetails.warning, DEBFILE_WARNING)
1304-
1305+
1306+
1307 if __name__ == "__main__":
1308 logging.basicConfig(level=logging.DEBUG)
1309 unittest.main()
1310+