diff -Nru software-center-5.2.2.2/debian/changelog software-center-5.2.3/debian/changelog --- software-center-5.2.2.2/debian/changelog 2012-06-01 17:08:25.000000000 +0000 +++ software-center-5.2.3/debian/changelog 2012-06-04 06:53:25.000000000 +0000 @@ -1,3 +1,66 @@ +software-center (5.2.3) precise-proposed; urgency=low + + [ Robert Roth ] + * lp:~evfool/software-center/lp987801: + - Only show the version label once for each version in + reviews (LP: #987801) + * lp:~evfool/software-center/lp874430: + - display tooltips for package titles in the application + tiles of the lobby view (LP: #874430) + * lp:~evfool/software-center/lp983831: + - Avoid merging two words while normalizing description (LP: #983831) + * lp:~evfool/software-center/lp822625: + - Set default value for reviewstats histogram (LP: #822625) + * lp:~evfool/software-center/lp875874: + - Set word wrap with fallback to char wrapping for the review + text label (LP: #875874) + + [ Gabor Kelemen ] + * lp:~kelemeng/software-center/bug1001746: + - merge i18n fix (LP: #1001746) + + [ Michael Vogt ] + * lp:~mvo/software-center/lp872760-for-5.2: + - fix translations for certain category names (LP: #872760) + * lp:~mvo/software-center/lp987321: + - fix dependency to ensure that we have humanity-icon-theme + as we need it for the history view icons (LP: #987321) + * lp:~mvo/software-center/test-catview-cleanup: + - refactor unit tests for the catview + * lp:~mvo/software-center/lp1002271: + - fix regresion in 5.2.2 (LP: #1002271) for empty descriptions + * lp:~mvo/software-center/lp1005104: + - fix regression in 5.2.2 (LP: #1005104) in initial navigation + history + * lp:~mvo/software-center/client-lp1004417: + - client side fix for when exhibit package names contain + extra whitespace (LP: #1004417) + + [ Gary Lasker ] + * lp:~gary-lasker/software-center/fix-crash-lp1000238: + - remove obsolete workaround for an old bug that has long since + been fixed, this fixes a hard crash on Quantal (LP: #1000238) + * lp:~gary-lasker/software-center/toolbar-buttons-insensitive-during-startup: + - re-enable the fix for LP: #999486, LP: #994341 that was inadvertently + disabled in the 5.2.2 release + + [ Natalia Bidart ] + * lp:~nataliabidart/software-center/fix-977931: + - 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. LP: #977931 + - Also added proper test suites for the above. + * lp:~nataliabidart/software-center/fix-965093: + - Fixed the SpinnerNtebook show_spinner method so the spinner page is + not shown until the configured threshold is reached (250ms since + this branch). Plus proper test suite was added. + * lp:~nataliabidart/software-center/fix-986563: + - Filtered out those exhibits that do not their packages available + in the db (LP: #986563) + + -- Michael Vogt Mon, 04 Jun 2012 08:53:25 +0200 + software-center (5.2.2.2) precise-proposed; urgency=low * tweak timeout for the software-center-agent for the HIB diff -Nru software-center-5.2.2.2/debian/control software-center-5.2.3/debian/control --- software-center-5.2.2.2/debian/control 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/debian/control 2012-05-31 07:54:49.000000000 +0000 @@ -25,7 +25,7 @@ app-install-data (>= 0.4.0), aptdaemon (>= 0.40), software-center-aptdaemon-plugins, - humanity-icon-theme|gnome-icon-theme, + humanity-icon-theme, gir1.2-glib-2.0 (>= 1.31), gir1.2-gtk-3.0, gir1.2-gmenu-3.0 (>= 3.1.5), diff -Nru software-center-5.2.2.2/README software-center-5.2.3/README --- software-center-5.2.2.2/README 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/README 2012-05-31 07:54:49.000000000 +0000 @@ -53,6 +53,7 @@ SOFTWARE_CENTER_DISTRO_CODENAME - overwrite "lsb_release -c -s" output SOFTWARE_CENTER_ARCHITECTURE - overwrite the current architecture SOFTWARE_CENTER_NO_SC_AGENT - disable the software-center-agent +SOFTWARE_CENTER_DISABLE_SPAWN_HELPER - disable everything that is run via the "SpawnHelper", i.e. recommender-agent, software-center-agent, reviews SOFTWARE_CENTER_DEBUG_TABS - show notebook tabs for debugging SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK - disables certificates checking in webkit views (for use in test environments) SOFTWARE_CENTER_FORCE_NON_SSL - disable SSL (for use in test environments) diff -Nru software-center-5.2.2.2/softwarecenter/backend/channel.py software-center-5.2.3/softwarecenter/backend/channel.py --- software-center-5.2.2.2/softwarecenter/backend/channel.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/backend/channel.py 2012-05-31 07:54:49.000000000 +0000 @@ -22,7 +22,7 @@ from gettext import gettext as _ -from softwarecenter.distro import get_distro +import softwarecenter.distro from softwarecenter.enums import (SortMethods, Icons, @@ -34,7 +34,7 @@ class ChannelsManager(object): def __init__(self, db, **kwargs): - self.distro = get_distro() + self.distro = softwarecenter.distro.get_distro() self.db = db # public @@ -154,7 +154,7 @@ self.installed_only = installed_only self._channel_sort_mode = channel_sort_mode # distro specific stuff - self.distro = get_distro() + self.distro = softwarecenter.distro.get_distro() # configure the channel self._channel_display_name = self._get_display_name_for_channel( channel_name, channel_origin, channel_component) @@ -351,7 +351,7 @@ return AptChannelsManager.channel_available(channelname) if __name__ == "__main__": - distro = get_distro() + distro = softwarecenter.distro.get_distro() channel = SoftwareChannel(distro.get_distro_channel_name(), None, None) print(channel) diff -Nru software-center-5.2.2.2/softwarecenter/backend/piston/rnrclient_fake.py software-center-5.2.3/softwarecenter/backend/piston/rnrclient_fake.py --- software-center-5.2.2.2/softwarecenter/backend/piston/rnrclient_fake.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/backend/piston/rnrclient_fake.py 2012-05-31 07:54:49.000000000 +0000 @@ -79,7 +79,8 @@ s = {'app_name': '', 'package_name': self._PACKAGE_NAMES[i], 'ratings_total': str(random.randrange(1, 200)), - 'ratings_average': str(random.randrange(0, 5)) + 'ratings_average': str(random.randrange(0, 5)), + 'histogram': None } stats.append(s) diff -Nru software-center-5.2.2.2/softwarecenter/backend/reviews/__init__.py software-center-5.2.3/softwarecenter/backend/reviews/__init__.py --- software-center-5.2.2.2/softwarecenter/backend/reviews/__init__.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/backend/reviews/__init__.py 2012-05-31 07:54:49.000000000 +0000 @@ -66,6 +66,7 @@ self.ratings_total = 0 self.rating_spread = [0, 0, 0, 0, 0] self.dampened_rating = 3.00 + self.histogram = None def __repr__(self): return (" 0: diff -Nru software-center-5.2.2.2/softwarecenter/db/debfile.py software-center-5.2.3/softwarecenter/db/debfile.py --- software-center-5.2.2.2/softwarecenter/db/debfile.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/db/debfile.py 2012-05-31 07:54:49.000000000 +0000 @@ -27,12 +27,28 @@ from softwarecenter.utils import ExecutionTime, utf8 +DEB_MIME_TYPE = 'application/x-debian-package' + + +def is_deb_file(debfile): + mtype = guess_type(debfile) + return mtype is not None and DEB_MIME_TYPE in mtype + + +class DebFileOpenError(Exception): + """ Raised if a DebFile fails to open """ + + def __init__(self, msg, path): + super(DebFileOpenError, self).__init__(msg) + self.path = path + + class DebFileApplication(Application): def __init__(self, debfile): - # sanity check - if not debfile.endswith(".deb"): - raise ValueError("Need a deb file, got '%s'" % debfile) + if not is_deb_file(debfile): + raise DebFileOpenError("Could not open %r." % debfile, debfile) + # work out debname/appname debname = os.path.splitext(os.path.basename(debfile))[0] pkgname = debname.split('_')[0].lower() @@ -50,7 +66,21 @@ def __init__(self, db, doc=None, application=None): super(AppDetailsDebFile, self).__init__(db, doc, application) if doc: - raise ValueError("doc must be None for deb files") + raise DebFileOpenError("AppDetailsDebFile: doc must be None.") + + self._error = None + # check errors before creating the DebPackage + if not os.path.exists(self._app.request): + self._error = _("Not found") + self._error_not_found = utf8(_(u"The file \u201c%s\u201d " + "does not exist.")) % utf8(self._app.request) + elif not is_deb_file(self._app.request): + self._error = _("Not found") + self._error_not_found = utf8(_(u"The file \u201c%s\u201d " + "is not a software package.")) % utf8(self._app.request) + + if self._error is not None: + return try: with ExecutionTime("create DebPackage"): @@ -59,23 +89,10 @@ self._deb = DebPackage(self._app.request, self._cache._cache) except: self._deb = None - self._pkg = None - if not os.path.exists(self._app.request): - self._error = _("Not found") - self._error_not_found = utf8(_(u"The file \u201c%s\u201d " - "does not exist.")) % utf8(self._app.request) - else: - mimetype = guess_type(self._app.request) - if mimetype[0] != "application/x-debian-package": - self._error = _("Not found") - self._error_not_found = utf8(_(u"The file \u201c%s\u201d " - "is not a software package.")) % utf8( - self._app.request) - else: - # deb files which are corrupt - self._error = _("Internal Error") - self._error_not_found = utf8(_(u"The file \u201c%s\u201d " - "could not be opened.")) % utf8(self._app.request) + # deb files which are corrupt + self._error = _("Internal Error") + self._error_not_found = utf8(_(u"The file \u201c%s\u201d " + "could not be opened.")) % utf8(self._app.request) return if self.pkgname and self.pkgname != self._app.pkgname: diff -Nru software-center-5.2.2.2/softwarecenter/db/__init__.py software-center-5.2.3/softwarecenter/db/__init__.py --- software-center-5.2.2.2/softwarecenter/db/__init__.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/db/__init__.py 2012-05-31 07:54:49.000000000 +0000 @@ -1,9 +1,14 @@ import logging + try: - from debfile import DebFileApplication + from debfile import DebFileApplication, DebFileOpenError DebFileApplication # pyflakes + DebFileOpenError # pyflakes except: logging.exception("DebFileApplication import") - class DebFileApplication(): + class DebFileApplication(object): + pass + + class DebFileOpenError(Exception): pass diff -Nru software-center-5.2.2.2/softwarecenter/enums.py software-center-5.2.3/softwarecenter/enums.py --- software-center-5.2.2.2/softwarecenter/enums.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/enums.py 2012-05-31 07:54:49.000000000 +0000 @@ -29,22 +29,23 @@ "To reinstall previous purchases, sign in to the " "Ubuntu Single Sign-On account you used to pay for them.") +SOFTWARE_CENTER_DEBUG_TABS = os.environ.get('SOFTWARE_CENTER_DEBUG_TABS', + False) + +SOFTWARE_CENTER_BUY_HOST = os.environ.get("SOFTWARE_CENTER_BUY_HOST", + "https://software-center.ubuntu.com") # buy-something base url #BUY_SOMETHING_HOST = "http://localhost:8000/" -BUY_SOMETHING_HOST = os.environ.get("SOFTWARE_CENTER_AGENT_HOST") or \ - os.environ.get("SOFTWARE_CENTER_BUY_HOST") or \ - "https://software-center.ubuntu.com" - -BUY_SOMETHING_HOST_ANONYMOUS = os.environ.get("SOFTWARE_CENTER_AGENT_HOST") \ - or os.environ.get("SOFTWARE_CENTER_BUY_HOST") or \ - "http://software-center.ubuntu.com" +BUY_SOMETHING_HOST = os.environ.get("SOFTWARE_CENTER_AGENT_HOST", + SOFTWARE_CENTER_BUY_HOST) + +BUY_SOMETHING_HOST_ANONYMOUS = BUY_SOMETHING_HOST # recommender -RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or \ - "https://rec.ubuntu.com" -#RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or \ -# "https://rec.staging.ubuntu.com" +RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST", + "https://rec.ubuntu.com") +# "https://rec.staging.ubuntu.com") # for the sso login. ussoc expects the USSOC_SERVICE_URL environment variable # to be a full path to the service root (including /api/1.0), not just the @@ -237,6 +238,12 @@ REPAIR = "repair_dependencies" +# Search separators +class SearchSeparators: + REGULAR = " " + PACKAGE = "," + + # mouse event codes for back/forward buttons # TODO: consider whether we ought to get these values from gconf so that we # can be sure to use the corresponding values used by Nautilus: diff -Nru software-center-5.2.2.2/softwarecenter/testutils.py software-center-5.2.3/softwarecenter/testutils.py --- software-center-5.2.2.2/softwarecenter/testutils.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/testutils.py 2012-05-31 07:54:49.000000000 +0000 @@ -22,6 +22,9 @@ import tempfile import time +from collections import defaultdict + +from mock import Mock m_dbus = m_polkit = m_aptd = None @@ -141,11 +144,16 @@ main_loop.iteration() +def do_events_with_sleep(iterations=5, sleep=0.1): + for i in range(iterations): + do_events() + time.sleep(sleep) + + def get_mock_app_from_real_app(real_app): """ take a application and return a app where the details are a mock of the real details so they can easily be modified """ - from mock import Mock import copy app = copy.copy(real_app) db = get_test_db() @@ -160,6 +168,16 @@ return app +def get_mock_options(): + """Return a mock suitable to act as SoftwareCenterAppGtk3's options.""" + mock_options = Mock() + mock_options.display_navlog = False + mock_options.disable_apt_xapian_index = False + mock_options.disable_buy = False + + return mock_options + + def setup_test_env(): """ Setup environment suitable for running the test/* code in a checkout. This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir. @@ -304,3 +322,57 @@ {u'rating': 1.5, u'package_name': u'tucan'}], u'app': u'pitivi'} return recommend_app_data + + +class ObjectWithSignals(object): + """A faked object that you can connect to and emit signals.""" + + def __init__(self, *a, **kw): + super(ObjectWithSignals, self).__init__() + self._callbacks = defaultdict(list) + + def connect(self, signal, callback): + """Connect a signal with a callback.""" + self._callbacks[signal].append(callback) + + def disconnect(self, signal, callback): + """Connect a signal with a callback.""" + self._callbacks[signal].remove(callback) + if len(self._callbacks[signal]) == 0: + self._callbacks.pop(signal) + + def disconnect_by_func(self, callback): + """Disconnect 'callback' from every signal.""" + # do not use iteritems since we may change the dict inside the for + for signal, callbacks in self._callbacks.items(): + if callback in callbacks: + self.disconnect(signal, callback) + + def emit(self, signal, *args, **kwargs): + """Emit 'signal' passing *args, **kwargs to every callback.""" + for callback in self._callbacks[signal]: + callback(*args, **kwargs) + + +class FakedCache(ObjectWithSignals, dict): + """A faked cache.""" + + def __init__(self, *a, **kw): + super(FakedCache, self).__init__() + self.ready = False + + def open(self): + """Open this cache.""" + self.ready = True + + def component_available(self, distro_codename, component): + """Return whether 'component' is available in 'distro_codename'.""" + + def get_addons(self, pkgname): + """Return (recommended, suggested) addons for 'pkgname'.""" + return ([], []) + + def get_total_size_on_install(self, pkgname, addons_to_install, + addons_to_remove, archive_suite): + """Return a fake (total_download_size, total_install_size) result.""" + return (0, 0) diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/app.py software-center-5.2.3/softwarecenter/ui/gtk3/app.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/app.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/app.py 2012-06-01 17:50:45.000000000 +0000 @@ -31,6 +31,7 @@ import gettext import logging import os +import re import subprocess import sys import xapian @@ -47,28 +48,36 @@ # db imports from softwarecenter.db.application import Application -from softwarecenter.db import DebFileApplication +from softwarecenter.db import DebFileApplication, DebFileOpenError from softwarecenter.i18n import init_locale # misc imports from softwarecenter.plugin import PluginManager from softwarecenter.paths import SOFTWARE_CENTER_PLUGIN_DIRS -from softwarecenter.enums import (Icons, - PkgStates, - ViewPages, - AppActions, - DB_SCHEMA_VERSION, - MOUSE_EVENT_FORWARD_BUTTON, - MOUSE_EVENT_BACK_BUTTON, - SOFTWARE_CENTER_TOS_LINK, - SOFTWARE_CENTER_NAME_KEYRING) -from softwarecenter.utils import (clear_token_from_ubuntu_sso_sync, - get_http_proxy_string_from_gsettings, - wait_for_apt_cache_ready, - ExecutionTime, - is_unity_running) -from softwarecenter.ui.gtk3.utils import (get_sc_icon_theme, - init_sc_css_provider) +from softwarecenter.enums import ( + AppActions, + DB_SCHEMA_VERSION, + Icons, + MOUSE_EVENT_FORWARD_BUTTON, + MOUSE_EVENT_BACK_BUTTON, + PkgStates, + SearchSeparators, + SOFTWARE_CENTER_DEBUG_TABS, + SOFTWARE_CENTER_NAME_KEYRING, + SOFTWARE_CENTER_TOS_LINK, + ViewPages, +) +from softwarecenter.utils import ( + clear_token_from_ubuntu_sso_sync, + get_http_proxy_string_from_gsettings, + wait_for_apt_cache_ready, + ExecutionTime, + is_unity_running, +) +from softwarecenter.ui.gtk3.utils import ( + get_sc_icon_theme, + init_sc_css_provider, +) from softwarecenter.version import VERSION from softwarecenter.db.database import StoreDatabase try: @@ -87,10 +96,14 @@ from softwarecenter.ui.gtk3.panes.historypane import HistoryPane from softwarecenter.ui.gtk3.panes.globalpane import GlobalPane from softwarecenter.ui.gtk3.panes.pendingpane import PendingPane -from softwarecenter.ui.gtk3.session.appmanager import (ApplicationManager, - get_appmanager) +from softwarecenter.ui.gtk3.session.appmanager import ( + ApplicationManager, + get_appmanager, + ) from softwarecenter.ui.gtk3.session.viewmanager import ( - ViewManager, get_viewmanager) + ViewManager, + get_viewmanager, + ) from softwarecenter.ui.gtk3.widgets.recommendations import ( RecommendationsOptInDialog) @@ -112,6 +125,10 @@ from gi.repository import Gdk LOG = logging.getLogger(__name__) +PACKAGE_PREFIX = 'apt:' +# "apt:///" is a valid prefix for 'apt:pkgname' in alt+F2 in gnome +PACKAGE_PREFIX_REGEX = re.compile('^%s(?:/{2,3})*' % PACKAGE_PREFIX) +SEARCH_PREFIX = 'search:' # py3 compat @@ -119,6 +136,64 @@ return isinstance(func, collections.Callable) +def parse_packages_args(packages): + search_text = '' + app = None + + # avoid treating strings as sequences ('foo' should not be 'f', 'o', 'o') + if isinstance(packages, basestring): + packages = (packages,) + + if not isinstance(packages, collections.Iterable): + LOG.warning('show_available_packages: argument is not an iterable %r', + packages) + return search_text, app + + items = [] # make a copy of the given sequence + for arg in packages: + # support both "pkg1 pkg" and "pkg1,pkg2" (and "pkg1,pkg2 pkg3") + if "," in arg: + items.extend(arg.split(SearchSeparators.PACKAGE)) + else: + items.append(arg) + + if len(items) > 0: + # allow s-c to be called with a search term + if items[0].startswith(SEARCH_PREFIX): + # remove the initial search prefix + items[0] = items[0].replace(SEARCH_PREFIX, '', 1) + search_text = SearchSeparators.REGULAR.join(items) + else: + # strip away the initial apt: prefix, if present + items[0] = re.sub(PACKAGE_PREFIX_REGEX, '', items[0]) + if len(items) > 1: + # turn multiple packages into a search with "," as separator + search_text = SearchSeparators.PACKAGE.join(items) + + if not search_text and len(items) == 1: + request = items[0] + # are we dealing with a path? + if os.path.exists(request) and not os.path.isdir(request): + if not request.startswith('/'): + # we may have been given a relative path + request = os.path.abspath(request) + # will raise DebOpenFileError if request is invalid + app = DebFileApplication(request) + else: + # package from archive + # if there is a "/" in the string consider it as tuple + # of (pkgname, appname) for exact matching (used by + # e.g. unity + (pkgname, sep, appname) = request.partition("/") + if pkgname or appname: + app = Application(appname, pkgname) + else: + LOG.warning('show_available_packages: received %r but ' + 'can not build an Application from it.', request) + + return search_text, app + + class SoftwarecenterDbusController(dbus.service.Object): """ This is a helper to provide the SoftwarecenterIFace @@ -158,13 +233,16 @@ # the size of the icon for dialogs APP_ICON_SIZE = Gtk.IconSize.DIALOG + START_DBUS = True + def __init__(self, datadir, xapian_base_path, options, args=None): - # setup dbus and exit if there is another instance already - # running - self.setup_dbus_or_bring_other_instance_to_front(args) + self.dbusControler = None + if self.START_DBUS: + # setup dbus and exit if there is another instance already running + self.setup_dbus_or_bring_other_instance_to_front(args) self.datadir = datadir - SimpleGtkbuilderApp.__init__(self, + super(SoftwareCenterAppGtk3, self).__init__( datadir + "/ui/gtk3/SoftwareCenter.ui", "software-center") gettext.bindtextdomain("software-center", "/usr/share/locale") @@ -172,7 +250,7 @@ init_locale() - if "SOFTWARE_CENTER_DEBUG_TABS" in os.environ: + if SOFTWARE_CENTER_DEBUG_TABS: self.notebook_view.set_show_tabs(True) # distro specific stuff @@ -459,8 +537,7 @@ if proxy: os.environ["http_proxy"] = proxy else: - if "http_proxy" in os.environ: - del os.environ["http_proxy"] + os.environ.pop("http_proxy", None) except Exception as e: # if no gnome settings are installed, do not mess with # http_proxy @@ -525,6 +602,18 @@ def on_review_stats_loaded(self, reviews): LOG.debug("on_review_stats_loaded: '%s'" % len(reviews)) + def destroy(self): + """Destroy this instance and every used resource.""" + self.window_main.destroy() + + # remove global instances of Managers + self.app_manager.destroy() + self.view_manager.destroy() + + if self.dbusControler is not None: + # ensure that the dbus controller is really gone + self.dbusControler.stop() + def close_app(self): """ perform tasks like save-state etc when the application is exited @@ -539,15 +628,14 @@ if hasattr(self, "glaunchpad"): self.glaunchpad.shutdown() self.save_state() + self.destroy() + # this will not throw exceptions in pygi but "only" log via g_critical # to the terminal but it might in the future so we add a handler here try: Gtk.main_quit() except: LOG.exception("Gtk.main_quit failed") - # ensure that the dbus controller is really gone, just for good - # measure - self.dbusControler.stop() # exit here explictely to ensure that no further gtk event loops or # threads run and cause havoc on exit (LP: #914393) sys.exit(0) @@ -1217,75 +1305,49 @@ bus_name = dbus.service.BusName('com.ubuntu.Softwarecenter', bus) self.dbusControler = SoftwarecenterDbusController(self, bus_name) + @wait_for_apt_cache_ready + def show_app(self, app): + """Show 'app' in the installed pane if is installed. + + If 'app' is not installed, show it in the available pane. + + """ + if (app.pkgname in self.cache and self.cache[app.pkgname].installed): + with ExecutionTime("installed_pane.init_view()"): + self.installed_pane.init_view() + with ExecutionTime("installed_pane.show_app()"): + self.installed_pane.show_app(app) + else: + self.available_pane.init_view() + self.available_pane.show_app(app) + def show_available_packages(self, packages): """ Show packages given as arguments in the available_pane If the list of packages is only one element long show that, otherwise turn it into a comma seperated search """ - # strip away the apt: prefix - if packages and packages[0].startswith("apt:///"): - # this is for 'apt:pkgname' in alt+F2 in gnome - packages[0] = packages[0].partition("apt:///")[2] - elif packages and packages[0].startswith("apt://"): - packages[0] = packages[0].partition("apt://")[2] - elif packages and packages[0].startswith("apt:"): - packages[0] = packages[0].partition("apt:")[2] - - # allow s-c to be called with a search term - if packages and packages[0].startswith("search:"): - packages[0] = packages[0].partition("search:")[2] - self.available_pane.init_view() - self.available_pane.searchentry.set_text(" ".join(packages)) - return - - if len(packages) == 1: - request = packages[0] - - # are we dealing with a path? - if os.path.exists(request) and not os.path.isdir(request): - if not request.startswith('/'): - # we may have been given a relative path - request = os.path.join(os.getcwd(), request) - try: - app = DebFileApplication(request) - except ValueError as e: - LOG.error("can not open %s: %s" % (request, e)) - from softwarecenter.ui.gtk3.dialogs import error - error(None, + try: + search_text, app = parse_packages_args(packages) + except DebFileOpenError as e: + LOG.exception("show_available_packages: can not open %r, error:", + packages) + dialogs.error(None, _("Error"), - _("The file ā€œ%sā€ could not be opened.") % request) - app = None - else: - # package from archive - # if there is a "/" in the string consider it as tuple - # of (pkgname, appname) for exact matching (used by - # e.g. unity - (pkgname, sep, appname) = packages[0].partition("/") - app = Application(appname, pkgname) + _("The file ā€œ%sā€ could not be opened.") % e.path) + search_text = app = None - @wait_for_apt_cache_ready - def show_app(self, app): - # if the pkg is installed, show it in the installed pane - if (app.pkgname in self.cache and - self.cache[app.pkgname].installed): - with ExecutionTime("installed_pane.init_view()"): - self.installed_pane.init_view() - with ExecutionTime("installed_pane.show_app()"): - self.installed_pane.show_app(app) - else: - self.available_pane.init_view() - self.available_pane.show_app(app) - if app: - show_app(self, app) - return - elif len(packages) > 1: - # turn multiple packages into a search with "," + LOG.info('show_available_packages: search_text is %r, app is %r.', + search_text, app) + + if search_text: self.available_pane.init_view() - self.available_pane.searchentry.set_text(",".join(packages)) - return - # normal startup, show the lobby (it will have a spinner when - # its not ready yet) - it will also initialize the view - self.view_manager.set_active_view(ViewPages.AVAILABLE) + self.available_pane.searchentry.set_text(search_text) + elif app is not None: + self.show_app(app) + else: + # normal startup, show the lobby (it will have a spinner when + # its not ready yet) - it will also initialize the view + self.view_manager.set_active_view(ViewPages.AVAILABLE) def restore_state(self): if self.config.has_option("general", "size"): diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/models/appstore2.py software-center-5.2.3/softwarecenter/ui/gtk3/models/appstore2.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/models/appstore2.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/models/appstore2.py 2012-05-31 07:54:49.000000000 +0000 @@ -34,6 +34,7 @@ split_icon_ext, capitalize_first_word, utf8, + unescape, ) from softwarecenter.backend import get_install_backend from softwarecenter.backend.reviews import get_review_loader @@ -114,9 +115,7 @@ self.icons = icons self.icon_size = icon_size - # cache the 'missing icon' used in the treeview for apps without an - # icon - self._missing_icon = icons.load_icon(Icons.MISSING_APP, icon_size, 0) + self._missing_icon = None # delay this until actually needed if global_icon_cache: self.icon_cache = _app_icon_cache else: @@ -145,6 +144,14 @@ on_image_download_complete, pkgname) image_downloader.download_file(url, icon_file_path) + @property + def missing_icon(self): + # cache the 'missing icon' used in treeviews for apps without an icon + if self._missing_icon is None: + self._missing_icon = self.icons.load_icon(Icons.MISSING_APP, + self.icon_size, 0) + return self._missing_icon + def update_availability(self, doc): doc.available = None doc.installed = None @@ -163,6 +170,7 @@ if doc.installed is None: pkgname = self.get_pkgname(doc) doc.installed = (self.is_available(doc) and + pkgname in self.cache and self.cache[pkgname].is_installed) return doc.installed @@ -226,10 +234,10 @@ self.get_pkgname(doc), full_icon_file_name) # display the missing icon while the real one downloads - self.icon_cache[icon_name] = self._missing_icon + self.icon_cache[icon_name] = self.missing_icon except GObject.GError as e: LOG.debug("get_icon returned '%s'" % e) - return self._missing_icon + return self.missing_icon def get_review_stats(self, doc): return self.review_loader.get_review_stats(self.get_application(doc)) @@ -249,8 +257,15 @@ for cat in self.all_categories: if cat.untranslated_name == catname: return cat.name - # else just use plain gettext - return _(catname) + # try normal translation first + translated_catname = _(catname) + if translated_catname == catname: + # if no normal translation is found, try to find a escaped + # translation (LP: #872760) + translated_catname = _(GObject.markup_escape_text(catname)) + # the parent expect the string unescaped + translated_catname = unescape(translated_catname) + return translated_catname def get_categories(self, doc): categories = doc.get_value(XapianValues.CATEGORIES).split(';') or [] diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/availablepane.py software-center-5.2.3/softwarecenter/ui/gtk3/panes/availablepane.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/availablepane.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/panes/availablepane.py 2012-05-31 07:54:49.000000000 +0000 @@ -236,6 +236,12 @@ # again (LP: #851671) self.view_initialized = True + # important to "seed" the initial history stack (LP: #1005104) + vm = get_viewmanager() + vm.display_page( + self, AvailablePane.Pages.LOBBY, + self.state, self.display_lobby_page) + if window is not None: window.set_cursor(None) @@ -591,8 +597,8 @@ self.display_search_page) def on_db_reopen(self, db): - " called when the database is reopened" - #print "on_db_open" + """Called when the database is reopened.""" + super(AvailablePane, self).on_db_reopen(db) self.refresh_apps() if self.app_details_view: self.app_details_view.refresh_app() diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/installedpane.py software-center-5.2.3/softwarecenter/ui/gtk3/panes/installedpane.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/installedpane.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/panes/installedpane.py 2012-05-31 07:54:49.000000000 +0000 @@ -681,7 +681,7 @@ self.app_details_view.refresh_app() def on_db_reopen(self, db): - LOG.debug("on_db_reopen") + super(InstalledPane, self).on_db_reopen(db) self._refresh_on_cache_or_db_change() def on_cache_ready(self, cache): diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/softwarepane.py software-center-5.2.3/softwarecenter/ui/gtk3/panes/softwarepane.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/softwarepane.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/panes/softwarepane.py 2012-05-31 07:54:49.000000000 +0000 @@ -22,20 +22,22 @@ from gi.repository import Gtk, Gdk #~ from gi.repository import Cairo import logging -import os import xapian from softwarecenter.backend import get_install_backend from softwarecenter.db.database import Application from softwarecenter.db.enquire import AppEnquire -from softwarecenter.enums import (SortMethods, - DEFAULT_SEARCH_LIMIT, - NonAppVisibility) - -from softwarecenter.utils import (ExecutionTime, - wait_for_apt_cache_ready, - utf8 - ) +from softwarecenter.enums import ( + DEFAULT_SEARCH_LIMIT, + NonAppVisibility, + SOFTWARE_CENTER_DEBUG_TABS, + SortMethods, +) +from softwarecenter.utils import ( + ExecutionTime, + utf8, + wait_for_apt_cache_ready, +) from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager from softwarecenter.ui.gtk3.widgets.actionbar import ActionBar @@ -178,7 +180,7 @@ self.back_forward = vm.get_global_backforward() # a notebook below self.notebook = Gtk.Notebook() - if not "SOFTWARE_CENTER_DEBUG_TABS" in os.environ: + if not SOFTWARE_CENTER_DEBUG_TABS: self.notebook.set_show_tabs(False) self.notebook.set_show_border(False) # make a spinner view to display while the applist is loading @@ -487,37 +489,37 @@ None, False) def on_search_terms_changed(self, terms): - " stub implementation " + """Stub implementation.""" pass - def on_db_reopen(self): - " stub implementation " - pass + def on_db_reopen(self, db): + """Stub implementation.""" + LOG.debug("%r: on_db_reopen (db is %r).", self.__class__.__name__, db) def is_category_view_showing(self): - " stub implementation " + """Stub implementation.""" pass def is_applist_view_showing(self): - " stub implementation " + """Stub implementation.""" pass def is_app_details_view_showing(self): - " stub implementation " + """Stub implementation.""" pass def get_current_app(self): - " stub implementation " + """Stub implementation.""" pass def on_application_selected(self, widget, app): - " stub implementation " + """Stub implementation.""" pass def get_current_category(self): - " stub implementation " + """Stub implementation.""" pass def unset_current_category(self): - " stub implementation " + """Stub implementation.""" pass diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/session/appmanager.py software-center-5.2.3/softwarecenter/ui/gtk3/session/appmanager.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/session/appmanager.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/session/appmanager.py 2012-05-31 07:54:49.000000000 +0000 @@ -71,6 +71,11 @@ else: _appmanager = self + def destroy(self): + """Destroy the global instance.""" + global _appmanager + _appmanager = None + def request_action(self, app, addons_install, addons_remove, action): """callback when an app action is requested from the appview, if action is "remove", must check if other dependencies have to be diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/session/viewmanager.py software-center-5.2.3/softwarecenter/ui/gtk3/session/viewmanager.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/session/viewmanager.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/session/viewmanager.py 2012-05-31 07:54:49.000000000 +0000 @@ -69,6 +69,11 @@ else: _viewmanager = self + def destroy(self): + """Destroy the global instance.""" + global _viewmanager + _viewmanager = None + def on_search_terms_changed(self, widget, new_text): pane = self.get_current_view_widget() if hasattr(pane, "on_search_terms_changed"): diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py software-center-5.2.3/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py 2012-05-31 07:54:49.000000000 +0000 @@ -23,9 +23,10 @@ # based on SimpleGladeApp -class SimpleGtkbuilderApp: +class SimpleGtkbuilderApp(object): def __init__(self, path, domain): + super(SimpleGtkbuilderApp, self).__init__() self.builder = Gtk.Builder() self.builder.set_translation_domain(domain) self.builder.add_from_file(path) diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/views/appdetailsview.py software-center-5.2.3/softwarecenter/ui/gtk3/views/appdetailsview.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/views/appdetailsview.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/views/appdetailsview.py 2012-05-31 07:54:49.000000000 +0000 @@ -172,9 +172,6 @@ "changed", self._on_combo_multiple_versions_changed) self.progress = Gtk.ProgressBar() - # theme engine hint for bug #606942 - self.progress.set_data("transparent-bg-hint", True) - self.pkg_state = None self.hbox.pack_start(self.installed_icon, False, False, 0) @@ -1738,7 +1735,7 @@ # update all (but skip the addons calculation if this is a # DebFileApplication as this is not useful for this case and it # increases the view load time dramatically) - skip_update_addons = type(self.app) == DebFileApplication + skip_update_addons = isinstance(self.app, DebFileApplication) self._update_all(self.app_details, skip_update_addons=skip_update_addons) @@ -2013,7 +2010,7 @@ self.addons_manager.addons_to_remove, self.app.archive_suite) total_download_size, total_install_size = res - if res == (0, 0) and type(self.app) == DebFileApplication: + if res == (0, 0) and isinstance(self.app, DebFileApplication): total_install_size = self.app_details.installed_size if total_download_size > 0: download_size = GLib.format_size(total_download_size) diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/views/catview_gtk.py software-center-5.2.3/softwarecenter/ui/gtk3/views/catview_gtk.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/views/catview_gtk.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/views/catview_gtk.py 2012-05-31 07:54:49.000000000 +0000 @@ -28,9 +28,11 @@ import softwarecenter.paths from softwarecenter.db.application import Application -from softwarecenter.enums import (NonAppVisibility, - SortMethods, - TOP_RATED_CAROUSEL_LIMIT) +from softwarecenter.enums import ( + NonAppVisibility, + SortMethods, + TOP_RATED_CAROUSEL_LIMIT, +) from softwarecenter.utils import wait_for_apt_cache_ready from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper from softwarecenter.ui.gtk3.widgets.viewport import Viewport @@ -224,11 +226,16 @@ apps_filter, apps_limit=0): CategoriesViewGtk.__init__(self, datadir, desktopdir, cache, db, icons, apps_filter, apps_limit=0) + self.top_rated = None + self.exhibit_banner = None # sections self.departments = None self.appcount = None + # build before connecting the signals to avoid race + self.build(desktopdir) + # ensure that on db-reopen we refresh the whats-new titles self.db.connect("reopen", self._on_db_reopen) @@ -237,9 +244,6 @@ self.reviews_loader.connect( "refresh-review-stats-finished", self._on_refresh_review_stats) - self.build(desktopdir) - return - def _on_db_reopen(self, db): self._update_whats_new_content() @@ -270,7 +274,6 @@ #self._append_video_clips() #self._append_top_of_the_pops - return #~ def _append_top_of_the_pops(self): #~ self.totp_hbox = Gtk.HBox(spacing=self.SPACING) @@ -337,24 +340,34 @@ flags=['nonapps-visible']) self.emit("category-selected", cat) + def _filter_and_set_exhibits(self, sca_client, exhibit_list): + result = [] + # filter out those exhibits that are not available in this run + for exhibit in exhibit_list: + available = all(self.db.is_pkgname_known(p) for p in + exhibit.package_names.split(',')) + if available: + result.append(exhibit) + + # its ok if result is empty, since set_exhibits() will ignore + # empty lists + self.exhibit_banner.set_exhibits(result) + def _append_banner_ads(self): - exhibit_banner = ExhibitBanner() - exhibit_banner.set_exhibits([FeaturedExhibit(), - ]) - exhibit_banner.connect("show-exhibits-clicked", self._on_show_exhibits) + self.exhibit_banner = ExhibitBanner() + self.exhibit_banner.set_exhibits([FeaturedExhibit()]) + self.exhibit_banner.connect( + "show-exhibits-clicked", self._on_show_exhibits) # query using the agent scagent = SoftwareCenterAgent() - scagent.connect( - "exhibits", lambda sca, l: exhibit_banner.set_exhibits(l)) + scagent.connect("exhibits", self._filter_and_set_exhibits) scagent.query_exhibits() a = Gtk.Alignment() a.set_padding(0, StockEms.SMALL, 0, 0) - a.add(exhibit_banner) - + a.add(self.exhibit_banner) self.vbox.pack_start(a, False, False, 0) - return def _append_departments(self): # set the departments section to use the label markup we have just @@ -715,21 +728,24 @@ #return -def get_test_window_catview(): +def get_test_window_catview(db=None): def on_category_selected(view, cat): print "on_category_selected view: ", view print "on_category_selected cat: ", cat - from softwarecenter.db.pkginfo import get_pkg_info - cache = get_pkg_info() - cache.open() - - from softwarecenter.db.database import StoreDatabase - xapian_base_path = "/var/cache/software-center" - pathname = os.path.join(xapian_base_path, "xapian") - db = StoreDatabase(pathname, cache) - db.open() + if db is None: + from softwarecenter.db.pkginfo import get_pkg_info + cache = get_pkg_info() + cache.open() + + from softwarecenter.db.database import StoreDatabase + xapian_base_path = "/var/cache/software-center" + pathname = os.path.join(xapian_base_path, "xapian") + db = StoreDatabase(pathname, cache) + db.open() + else: + cache = db._aptcache import softwarecenter.paths datadir = softwarecenter.paths.datadir diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/buttons.py software-center-5.2.3/softwarecenter/ui/gtk3/widgets/buttons.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/buttons.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/widgets/buttons.py 2012-05-31 07:54:49.000000000 +0000 @@ -201,6 +201,7 @@ GObject.markup_escape_text(label)) self.title.set_alignment(0.0, 0.5) self.title.set_use_markup(True) + self.title.set_tooltip_text(label) self.title.set_ellipsize(Pango.EllipsizeMode.END) self.content_right.pack_start(self.title, False, False, 0) diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/reviews.py software-center-5.2.3/softwarecenter/ui/gtk3/widgets/reviews.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/reviews.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/widgets/reviews.py 2012-05-31 07:54:49.000000000 +0000 @@ -194,11 +194,18 @@ UI vbox out of them """ self.logged_in_person = get_person_from_config() + is_first_for_version = None if self.reviews: + previous_review = None for r in self.reviews: pkgversion = self._parent.app_details.version + if previous_review: + is_first_for_version = previous_review.version != r.version + else: + is_first_for_version = True + previous_review = r review = UIReview(r, pkgversion, self.logged_in_person, - self.useful_votes) + self.useful_votes, is_first_for_version) review.show_all() self.vbox.pack_start(review, True, True, 0) @@ -399,7 +406,8 @@ useful/inappropriate etc """ def __init__(self, review_data=None, app_version=None, - logged_in_person=None, useful_votes=None): + logged_in_person=None, useful_votes=None, + first_for_version=True): GObject.GObject.__init__(self) self.set_spacing(StockEms.SMALL) @@ -439,8 +447,8 @@ self.usefulness_error = False self.delete_error = False self.modify_error = False - - self.pack_start(self.version_label, False, False, 0) + if first_for_version: + self.pack_start(self.version_label, False, False, 0) self.pack_start(self.header, False, False, 0) self.pack_start(self.body, False, False, 0) self.pack_start(self.footer, False, False, StockEms.SMALL) @@ -651,6 +659,7 @@ text = Gtk.Label() text.set_text(review_data.review_text) text.set_line_wrap(True) + text.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) text.set_selectable(True) text.set_alignment(0, 0) diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/spinner.py software-center-5.2.3/softwarecenter/ui/gtk3/widgets/spinner.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/spinner.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/widgets/spinner.py 2012-05-31 07:54:49.000000000 +0000 @@ -18,16 +18,20 @@ import gi gi.require_version("Gtk", "3.0") -import os from gi.repository import Gtk, GObject +from softwarecenter.enums import SOFTWARE_CENTER_DEBUG_TABS + class SpinnerView(Gtk.Viewport): + """A panel that contains a spinner with an optional legend. + + The spinner is preset to a standard size and centered. An optional + label_text value can be specified for display with the spinner. + """ - a panel that contains a spinner preset to a standard size and centered - an optional label_text value can be specified for display with the spinner - """ + def __init__(self, label_text=""): Gtk.Viewport.__init__(self) self.spinner = Gtk.Spinner() @@ -48,26 +52,23 @@ self.set_shadow_type(Gtk.ShadowType.NONE) def start_and_show(self): - """ - start the spinner and show it - """ + """Start the spinner and show it.""" self.spinner.start() self.spinner.show() def stop_and_hide(self): - """ - stop the spinner and hide it - """ + """Stop the spinner and hide it.""" self.spinner.stop() self.spinner.hide() def set_text(self, spinner_text=""): - """ - useful for adding/removing/changing the label text after the spinner - instance has been created - """ + """Add/remove/change this spinner's label text.""" self.spinner_label.set_markup('%s' % spinner_text) + def get_text(self): + """Return the spinner's currently set label text.""" + return self.spinner_label.get_text() + class SpinnerNotebook(Gtk.Notebook): """ this provides a Gtk.Notebook that contains a content page @@ -78,11 +79,12 @@ def __init__(self, content, msg=""): Gtk.Notebook.__init__(self) + self._last_timeout_id = None self.spinner_view = SpinnerView(msg) # its critical to show() the spinner early as otherwise # gtk_notebook_set_active_page() will not switch to it self.spinner_view.show() - if not "SOFTWARE_CENTER_DEBUG_TABS" in os.environ: + if not SOFTWARE_CENTER_DEBUG_TABS: self.set_show_tabs(False) self.set_show_border(False) self.append_page(content, Gtk.Label("content")) @@ -91,6 +93,8 @@ def _unmask_view_spinner(self): # start is actually start_and_show() self.spinner_view.start_and_show() + self.set_current_page(self.SPINNER_PAGE) + self._last_timeout_id = None return False def show_spinner(self, msg=""): @@ -100,11 +104,14 @@ # "mask" the spinner view momentarily to prevent it from flashing into # view in the case of short delays where it isn't actually needed self.spinner_view.stop_and_hide() - GObject.timeout_add(100, self._unmask_view_spinner) - self.set_current_page(self.SPINNER_PAGE) + self._last_timeout_id = GObject.timeout_add(250, + self._unmask_view_spinner) def hide_spinner(self): """ hide the spinner page again and show the content page """ + if self._last_timeout_id is not None: + GObject.source_remove(self._last_timeout_id) + self._last_timeout_id = None self.spinner_view.stop_and_hide() self.set_current_page(self.CONTENT_PAGE) diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/thumbnail.py software-center-5.2.3/softwarecenter/ui/gtk3/widgets/thumbnail.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/thumbnail.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/widgets/thumbnail.py 2012-05-31 07:54:49.000000000 +0000 @@ -210,7 +210,7 @@ self.screenshot.pack_start(self.button, True, False, 0) # unavailable layout - self.unavailable = Gtk.Label(label=self.NOT_AVAILABLE_STRING) + self.unavailable = Gtk.Label(label=_(self.NOT_AVAILABLE_STRING)) self.unavailable.set_alignment(0.5, 0.5) # force the label state to INSENSITIVE so we get the nice # subtle etched in look @@ -325,7 +325,7 @@ self.unavailable.show() self.button.hide() acc = self.get_accessible() - acc.set_name(self.NOT_AVAILABLE_STRING) + acc.set_name(_(self.NOT_AVAILABLE_STRING)) acc.set_role(Atk.Role.LABEL) def display_image(self): diff -Nru software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/videoplayer.py software-center-5.2.3/softwarecenter/ui/gtk3/widgets/videoplayer.py --- software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/videoplayer.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/ui/gtk3/widgets/videoplayer.py 2012-05-31 07:54:49.000000000 +0000 @@ -72,7 +72,11 @@ # uri property def _set_uri(self, v): self._uri = v or "" - self.webkit.load_uri(self._uri) + if self._uri: + # only load the uri if it's defined, otherwise we may get: + # Program received signal SIGSEGV, Segmentation fault. + # webkit_web_frame_load_uri () from /usr/lib/libwebkitgtk-3.0.so.0 + self.webkit.load_uri(self._uri) def _get_uri(self): return self._uri diff -Nru software-center-5.2.2.2/softwarecenter/utils.py software-center-5.2.3/softwarecenter/utils.py --- software-center-5.2.2.2/softwarecenter/utils.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/softwarecenter/utils.py 2012-05-31 07:54:49.000000000 +0000 @@ -149,6 +149,12 @@ break return i + def maybe_add_space(prefix): + if not (prefix.endswith("\n") or prefix.endswith(" ")): + return " " + else: + return "" + BULLETS = ('- ', '* ', 'o ') norm_description = "" in_blist = False @@ -171,12 +177,10 @@ if in_blist: in_blist = False norm_description += '\n' - norm_description += part + '\n' + norm_description += maybe_add_space(norm_description) + part + '\n' else: in_blist = False - if not norm_description.endswith("\n"): - norm_description += " " - norm_description += part + norm_description += maybe_add_space(norm_description) + part return norm_description.strip() @@ -722,7 +726,6 @@ try: os.makedirs(dir_path) except OSError as e: - print e if e.errno == errno.EEXIST: # it seems that another process has already created this # directory in the meantime, that's ok diff -Nru software-center-5.2.2.2/softwarecenter/version.py software-center-5.2.3/softwarecenter/version.py --- software-center-5.2.2.2/softwarecenter/version.py 2012-06-01 17:22:18.000000000 +0000 +++ software-center-5.2.3/softwarecenter/version.py 2012-06-04 16:43:57.000000000 +0000 @@ -1,5 +1,5 @@ -VERSION='5.2.2.2' +VERSION='5.2.3' CODENAME='precise-proposed' DISTRO='Ubuntu' RELEASE='12.04' diff -Nru software-center-5.2.2.2/test/axi-test-values software-center-5.2.3/test/axi-test-values --- software-center-5.2.2.2/test/axi-test-values 2012-06-01 17:10:45.000000000 +0000 +++ software-center-5.2.3/test/axi-test-values 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ - -# This file contains the mapping between names of numeric values indexed in the -# APT Xapian index and their index -# -# Xapian allows to index numeric values as well as keywords and to use them for -# all sorts of useful querying tricks. However, every numeric value needs to -# have a unique index, and this configuration file is needed to record which -# indices are allocated and to provide a mnemonic name for them. -# -# The format is exactly like /etc/services with name, number and optional -# aliases, with the difference that the second column does not use the -# "/protocol" part, which would be meaningless here. - -version 0 # package version -catalogedtime 1 # Cataloged timestamp -installedsize 2 # installed size -packagesize 3 # package size -app-popcon 4 # app-install .desktop popcon rank diff -Nru software-center-5.2.2.2/test/coverage_summary software-center-5.2.3/test/coverage_summary --- software-center-5.2.2.2/test/coverage_summary 2012-06-01 17:22:08.000000000 +0000 +++ software-center-5.2.3/test/coverage_summary 2012-06-04 16:43:48.000000000 +0000 @@ -1,197 +1,199 @@ -Name Stmts Miss Cover ---------------------------------------------------------------------------------------------------------------------------------------------- -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/__init__ 2 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/channel 181 18 90% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/channel_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/channel_impl/aptchannels 144 47 67% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/fake_review_settings 76 33 57% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/installbackend 36 14 61% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/installbackend_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/installbackend_impl/aptd 502 330 34% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/launchpad 184 112 39% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/login 9 2 78% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/login_sso 97 33 66% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/oneconfhandler/__init__ 17 5 71% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/oneconfhandler/core 97 14 86% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/piston/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/piston/rnrclient 44 29 34% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/piston/rnrclient_fake 147 80 46% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/piston/rnrclient_pristine 80 26 68% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/recagent 138 41 70% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/reviews/__init__ 360 129 64% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/reviews/rnr 205 100 51% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/scagent 78 22 72% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/spawn_helper 88 14 84% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/transactionswatcher 50 19 62% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/ubuntusso 75 38 49% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/unitylauncher 32 11 66% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/weblive 168 100 40% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/backend/weblive_pristine 147 84 43% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/cmdfinder 31 3 90% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/config 39 16 59% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/__init__ 8 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/appfilter 72 15 79% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/application 520 127 76% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/categories 310 36 88% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/database 365 95 74% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/debfile 125 35 72% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/enquire 146 6 96% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/history 36 13 64% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/history_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/history_impl/apthistory 132 33 75% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/pkginfo 107 36 66% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/pkginfo_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/pkginfo_impl/aptcache 524 154 71% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/update 657 67 90% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/db/utils 23 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/distro/Debian 86 61 29% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/distro/Ubuntu 126 51 60% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/distro/__init__ 90 43 52% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/enums 121 8 93% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/expunge 41 28 32% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/gwibber_helper 64 22 66% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/hw 40 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/i18n 47 4 91% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/log 57 1 98% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/netstatus 88 18 80% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/paths 46 8 83% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/plugin 62 4 94% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/region 79 5 94% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/testutils 115 4 97% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/SimpleGtkbuilderApp 18 5 72% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/app 730 355 51% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/aptd_gtk3 44 36 18% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/dialogs/__init__ 80 28 65% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog 79 67 15% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/dialogs/dependency_dialogs 79 19 76% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/dialogs/dialog_tos 50 5 90% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/drawing 66 32 52% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/em 33 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/gmenusearch 82 35 57% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/models/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/models/appstore2 275 29 89% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/models/pendingstore 117 69 41% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/availablepane 440 166 62% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/basepane 15 5 67% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/globalpane 58 4 93% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/historypane 252 18 93% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/installedpane 461 147 68% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/pendingpane 95 28 71% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/softwarepane 276 66 76% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/panes/viewswitcher 168 65 61% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/review_gui_helper 795 311 61% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/session/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/session/appmanager 89 12 87% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/session/navhistory 164 17 90% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/session/viewmanager 125 28 78% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/utils 65 9 86% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/views/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/views/appdetailsview 1271 204 84% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/views/appview 194 47 76% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/views/catview_gtk 446 61 86% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/views/pkgnamesview 64 8 88% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/views/purchaseview 203 81 60% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/views/webkit 60 29 52% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/actionbar 214 77 64% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/apptreeview 463 240 48% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/backforward 101 20 80% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/buttons 410 66 84% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/cellrenderers 309 51 83% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/containers 378 24 94% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/description 791 424 46% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/exhibits 373 63 83% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/imagedialog 35 3 91% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/labels 60 11 82% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/menubutton 64 45 30% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/oneconfviews 98 45 54% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/recommendations 222 40 82% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/reviews 575 108 81% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/searchaid 179 61 66% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/searchentry 90 25 72% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/separators 26 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/spinner 64 2 97% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/stars 362 44 88% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/symbolic_icons 149 10 93% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/thumbnail 340 59 83% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/videoplayer 110 56 49% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/viewport 24 15 38% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/gtk3/widgets/weblivedialog 68 58 15% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/qml/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/qml/categoriesmodel 53 21 60% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/qml/pkglist 134 66 51% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/ui/qml/reviewslist 46 3 93% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/utils 467 117 75% -/home/egon/devel/software-center/build-area/software-center-5.2.2.2/softwarecenter/version 4 0 100% -__init__ 0 0 100% -data/plugins/mock_plugin 4 0 100% -gtk3/test_app_view 64 0 100% -gtk3/test_appdetailsview 332 5 98% -gtk3/test_appmanager 51 0 100% -gtk3/test_appstore2 35 0 100% -gtk3/test_catview 172 0 100% -gtk3/test_custom_lists 38 0 100% -gtk3/test_debfile_view 27 0 100% -gtk3/test_dialogs 31 0 100% -gtk3/test_globalpane 13 0 100% -gtk3/test_install_progress 32 0 100% -gtk3/test_installedpane 40 0 100% -gtk3/test_navhistory 135 0 100% -gtk3/test_panes 42 0 100% -gtk3/test_purchase 77 0 100% -gtk3/test_recommendations_widgets 24 0 100% -gtk3/test_reviews 129 2 98% -gtk3/test_search 48 0 100% -gtk3/test_unity_launcher_integration 84 8 90% -gtk3/test_views 40 0 100% -gtk3/test_widgets 168 1 99% -qml/test_ui_qml_helpers 78 0 100% -stat 66 66 0% -test_addons 30 0 100% -test_aptd 72 28 61% -test_apthistory 71 1 99% -test_categories 64 0 100% -test_channels 25 0 100% -test_cmdfiner 20 0 100% -test_database 418 1 99% -test_debfileapplication 67 1 99% -test_description_norm 35 0 100% -test_distro 20 0 100% -test_downloader 49 0 100% -test_enquire 28 2 93% -test_gwibber 34 7 79% -test_htmlize 21 0 100% -test_hw 22 0 100% -test_i18n 39 0 100% -test_launchpad 31 1 97% -test_logging 21 0 100% -test_login_backend 19 0 100% -test_mime 25 0 100% -test_netstatus 16 0 100% -test_origin 22 0 100% -test_package_info 59 0 100% -test_pep8 39 1 97% -test_pkginfo 36 1 97% -test_plugin 18 0 100% -test_ppa_iconfilename 41 1 98% -test_purchase_backend 47 23 51% -test_pyflakes 9 0 100% -test_recagent 101 26 74% -test_region 46 0 100% -test_reinstall_purchased 140 0 100% -test_rnr_api 14 0 100% -test_scagent 40 2 95% -test_spawn_helper 17 0 100% -test_startup 39 24 38% -test_testutils 37 0 100% -test_ubuntu_sso_api 19 0 100% -test_utils 170 14 92% -test_where_is_it 63 12 81% -test_xapian 71 1 99% -test_xapian_query 53 1 98% ---------------------------------------------------------------------------------------------------------------------------------------------- -TOTAL 24155 6163 74% +Name Stmts Miss Cover +------------------------------------------------------------------------------------------------------------------------------------------- +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/__init__ 2 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/channel 181 18 90% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/channel_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/channel_impl/aptchannels 144 47 67% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/fake_review_settings 76 33 57% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/installbackend 36 14 61% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/installbackend_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/installbackend_impl/aptd 502 330 34% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/launchpad 184 112 39% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/login 9 2 78% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/login_sso 97 33 66% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/oneconfhandler/__init__ 17 5 71% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/oneconfhandler/core 97 14 86% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/piston/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/piston/rnrclient 44 29 34% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/piston/rnrclient_fake 147 80 46% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/piston/rnrclient_pristine 80 26 68% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/recagent 138 41 70% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/reviews/__init__ 361 129 64% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/reviews/rnr 205 98 52% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/scagent 80 22 73% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/spawn_helper 90 14 84% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/transactionswatcher 50 19 62% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/ubuntusso 75 38 49% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/unitylauncher 32 11 66% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/weblive 168 100 40% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/backend/weblive_pristine 147 84 43% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/cmdfinder 31 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/config 39 16 59% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/__init__ 11 6 45% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/appfilter 72 11 85% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/application 520 110 79% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/categories 310 36 88% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/database 369 93 75% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/debfile 134 35 74% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/enquire 146 6 96% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/history 36 13 64% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/history_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/history_impl/apthistory 132 33 75% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/pkginfo 107 36 66% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/pkginfo_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/pkginfo_impl/aptcache 524 151 71% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/update 657 67 90% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/db/utils 23 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/distro/Debian 86 61 29% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/distro/Ubuntu 126 64 49% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/distro/__init__ 90 43 52% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/enums 126 8 94% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/expunge 41 28 32% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/gwibber_helper 64 22 66% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/hw 40 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/i18n 47 4 91% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/log 57 1 98% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/netstatus 88 18 80% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/paths 46 8 83% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/plugin 62 4 94% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/region 79 5 94% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/testutils 154 6 96% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/SimpleGtkbuilderApp 19 5 74% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/app 753 337 55% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/aptd_gtk3 44 36 18% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/dialogs/__init__ 80 28 65% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog 79 67 15% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/dialogs/dependency_dialogs 79 19 76% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/dialogs/dialog_tos 50 5 90% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/drawing 66 32 52% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/em 33 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/gmenusearch 82 35 57% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/models/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/models/appstore2 283 29 90% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/models/pendingstore 117 69 41% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/availablepane 443 157 65% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/basepane 15 5 67% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/globalpane 58 4 93% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/historypane 252 21 92% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/installedpane 461 146 68% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/pendingpane 95 28 71% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/softwarepane 275 51 81% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/panes/viewswitcher 168 65 61% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/review_gui_helper 795 311 61% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/session/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/session/appmanager 91 12 87% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/session/navhistory 164 17 90% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/session/viewmanager 127 27 79% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/utils 65 9 86% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/views/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/views/appdetailsview 1270 203 84% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/views/appview 194 47 76% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/views/catview_gtk 454 61 87% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/views/pkgnamesview 64 8 88% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/views/purchaseview 203 88 57% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/views/webkit 60 29 52% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/actionbar 214 77 64% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/apptreeview 463 240 48% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/backforward 101 20 80% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/buttons 411 66 84% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/cellrenderers 309 51 83% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/containers 378 24 94% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/description 791 424 46% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/exhibits 373 61 84% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/imagedialog 35 3 91% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/labels 60 11 82% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/menubutton 64 45 30% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/oneconfviews 98 44 55% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/recommendations 222 40 82% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/reviews 583 108 81% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/searchaid 179 61 66% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/searchentry 90 25 72% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/separators 26 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/spinner 71 2 97% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/stars 362 44 88% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/symbolic_icons 149 10 93% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/thumbnail 340 59 83% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/videoplayer 111 56 50% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/viewport 24 15 38% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/gtk3/widgets/weblivedialog 68 58 15% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/qml/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/qml/categoriesmodel 53 21 60% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/qml/pkglist 134 66 51% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/ui/qml/reviewslist 46 3 93% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/utils 468 116 75% +/home/egon/devel/software-center/build-area/software-center-5.2.3/softwarecenter/version 4 0 100% +__init__ 0 0 100% +data/plugins/mock_plugin 4 0 100% +gtk3/test_app 190 0 100% +gtk3/test_app_view 64 0 100% +gtk3/test_appdetailsview 332 5 98% +gtk3/test_appmanager 51 0 100% +gtk3/test_appstore2 45 0 100% +gtk3/test_catview 186 9 95% +gtk3/test_custom_lists 38 0 100% +gtk3/test_debfile_view 23 0 100% +gtk3/test_dialogs 31 0 100% +gtk3/test_globalpane 13 0 100% +gtk3/test_install_progress 32 0 100% +gtk3/test_installedpane 40 0 100% +gtk3/test_navhistory 135 0 100% +gtk3/test_panes 42 0 100% +gtk3/test_purchase 74 0 100% +gtk3/test_recommendations_widgets 24 0 100% +gtk3/test_reviews 129 2 98% +gtk3/test_search 48 0 100% +gtk3/test_spinner 87 0 100% +gtk3/test_unity_launcher_integration 84 8 90% +gtk3/test_views 40 0 100% +gtk3/test_widgets 168 1 99% +qml/test_ui_qml_helpers 78 0 100% +stat 66 66 0% +test_addons 30 4 87% +test_aptd 72 28 61% +test_apthistory 71 1 99% +test_categories 64 0 100% +test_channels 25 0 100% +test_cmdfiner 20 0 100% +test_database 426 1 99% +test_debfileapplication 67 1 99% +test_description_norm 39 0 100% +test_distro 20 0 100% +test_downloader 49 0 100% +test_enquire 28 2 93% +test_gwibber 34 7 79% +test_htmlize 21 0 100% +test_hw 22 0 100% +test_i18n 39 0 100% +test_launchpad 31 1 97% +test_logging 21 0 100% +test_login_backend 19 0 100% +test_mime 25 0 100% +test_netstatus 16 0 100% +test_origin 22 0 100% +test_package_info 59 4 93% +test_pep8 39 1 97% +test_pkginfo 36 1 97% +test_plugin 18 0 100% +test_ppa_iconfilename 41 1 98% +test_purchase_backend 47 23 51% +test_pyflakes 9 0 100% +test_recagent 101 26 74% +test_region 46 0 100% +test_reinstall_purchased 140 0 100% +test_rnr_api 14 0 100% +test_scagent 54 2 96% +test_spawn_helper 17 0 100% +test_startup 39 24 38% +test_testutils 37 0 100% +test_ubuntu_sso_api 19 0 100% +test_utils 170 14 92% +test_where_is_it 63 12 81% +test_xapian 71 1 99% +test_xapian_query 53 1 98% +------------------------------------------------------------------------------------------------------------------------------------------- +TOTAL 24603 6131 75% diff -Nru software-center-5.2.2.2/test/gtk3/test_app.py software-center-5.2.3/test/gtk3/test_app.py --- software-center-5.2.2.2/test/gtk3/test_app.py 1970-01-01 00:00:00.000000000 +0000 +++ software-center-5.2.3/test/gtk3/test_app.py 2012-05-31 07:54:49.000000000 +0000 @@ -0,0 +1,323 @@ +#!/usr/bin/python + +import os +import unittest + +from collections import defaultdict +from functools import partial + +from mock import Mock + +from testutils import FakedCache, get_mock_options, setup_test_env +setup_test_env() + +import softwarecenter.paths +from softwarecenter.db import DebFileApplication, DebFileOpenError +from softwarecenter.enums import PkgStates, SearchSeparators +from softwarecenter.ui.gtk3 import app + + +class ParsePackagesArgsTestCase(unittest.TestCase): + """Test suite for the parse_packages_args helper.""" + + pkg_name = 'foo' + + def transform_for_test(self, items): + """Transform a sequence into a comma separated string.""" + return app.SEARCH_PREFIX + SearchSeparators.REGULAR.join(items) + + def do_check(self, apps, items=None): + """Check that the available_pane was shown.""" + if items is None: + items = self.transform_for_test(apps) + + search_text, result_app = app.parse_packages_args(items) + + self.assertEqual(SearchSeparators.REGULAR.join(apps), search_text) + self.assertIsNone(result_app) + + def test_empty(self): + """Pass an empty argument, show the 'available' view.""" + self.do_check(apps=()) + + def test_single_empty_item(self): + """Pass a single empty item, show the 'available' view.""" + self.do_check(apps=('',)) + + def test_single_item(self): + """Pass a single item, show the 'available' view.""" + self.do_check(apps=(self.pkg_name,)) + + def test_two_items(self): + """Pass two items, show the 'available' view.""" + self.do_check(apps=(self.pkg_name, 'bar')) + + def test_several_items(self): + """Pass several items, show the 'available' view.""" + self.do_check(apps=(self.pkg_name, 'firefox', 'software-center')) + + +class ParsePackageArgsAsFileTestCase(unittest.TestCase): + + def test_item_is_a_file(self): + """Pass an item that is an existing file.""" + # pass a real deb here + fname = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "data", "test_debs", "gdebi-test1.deb") + assert os.path.exists(fname) + # test once as string and as list + for items in ( fname, [fname] ): + search_text, result_app = app.parse_packages_args(fname) + self.assertIsInstance(result_app, DebFileApplication) + + def test_item_is_invalid_file(self): + """ Pass an invalid file item """ + fname = __file__ + assert os.path.exists(fname) + self.assertRaises(DebFileOpenError, app.parse_packages_args, fname) + + +class ParsePackagesWithAptPrefixTestCase(ParsePackagesArgsTestCase): + + installed = None + + def setUp(self): + super(ParsePackagesWithAptPrefixTestCase, self).setUp() + + self.cache = FakedCache() + self.db = app.StoreDatabase(cache=self.cache) + + assert self.pkg_name not in self.cache + if self.installed is not None: + mock_cache_entry = Mock() + mock_cache_entry.website = None + mock_cache_entry.license = None + mock_cache_entry.installed_files = [] + mock_cache_entry.candidate = Mock() + mock_cache_entry.candidate.version = '1.0' + mock_cache_entry.candidate.description = 'A nonsense app.' + mock_cache_entry.candidate.origins = () + mock_cache_entry.versions = (Mock(),) + mock_cache_entry.versions[0].version = '0.99' + mock_cache_entry.versions[0].origins = (Mock(),) + mock_cache_entry.versions[0].origins[0].archive = 'test' + mock_cache_entry.is_installed = self.installed + if self.installed: + mock_cache_entry.installed = Mock() + mock_cache_entry.installed.version = '0.90' + mock_cache_entry.installed.installed_size = 0 + else: + mock_cache_entry.installed = None + + self.cache[self.pkg_name] = mock_cache_entry + self.addCleanup(self.cache.pop, self.pkg_name) + + + def transform_for_test(self, items): + """Do nothing.""" + return items + + def check_package_availability(self, name): + """Check whether the package 'name' is available.""" + if name not in self.cache: + state = PkgStates.NOT_FOUND + elif self.cache[name].installed: + state = PkgStates.INSTALLED + else: + state = PkgStates.UNINSTALLED + return state + + def do_check(self, apps, items=None): + """Check that the available_pane was shown.""" + if items is None: + items = self.transform_for_test(apps) + + search_text, result_app = app.parse_packages_args(items) + + if apps and len(apps) == 1 and apps[0] and not os.path.isfile(apps[0]): + self.assertIsNotNone(result_app) + app_details = result_app.get_details(self.db) + + self.assertEqual(apps[0], app_details.name) + state = self.check_package_availability(app_details.name) + self.assertEqual(state, app_details.pkg_state) + else: + self.assertIsNone(result_app) + + if apps and (len(apps) > 1 or os.path.isfile(apps[0])): + self.assertEqual(SearchSeparators.PACKAGE.join(apps), search_text) + else: + self.assertEqual('', search_text) + + def test_item_with_prefix(self): + """Pass a item with the item prefix.""" + for prefix in ('apt:', 'apt://', 'apt:///'): + for case in (self.pkg_name, app.PACKAGE_PREFIX + self.pkg_name): + self.do_check(apps=(case,), items=(prefix + case,)) + + +class ParsePackagesNotInstalledTestCase(ParsePackagesWithAptPrefixTestCase): + """Test suite for parsing/searching/loading package lists.""" + + installed = False + + +class ParsePackagesInstalledTestCase(ParsePackagesWithAptPrefixTestCase): + """Test suite for parsing/searching/loading package lists.""" + + installed = True + + +class ParsePackagesArgsStringTestCase(ParsePackagesWithAptPrefixTestCase): + """Test suite for parsing/loading package lists from strings.""" + + def transform_for_test(self, items): + """Transform a sequence into a comma separated string.""" + return SearchSeparators.PACKAGE.join(items) + + +class AppTestCase(unittest.TestCase): + """Test suite for the app module.""" + + def setUp(self): + super(AppTestCase, self).setUp() + self.called = defaultdict(list) + self.addCleanup(self.called.clear) + + orig = app.SoftwareCenterAppGtk3.START_DBUS + self.addCleanup(setattr, app.SoftwareCenterAppGtk3, 'START_DBUS', orig) + app.SoftwareCenterAppGtk3.START_DBUS = False + + orig = app.get_pkg_info + self.addCleanup(setattr, app, 'get_pkg_info', orig) + app.get_pkg_info = lambda: FakedCache() + + datadir = softwarecenter.paths.datadir + xapianpath = softwarecenter.paths.XAPIAN_BASE_PATH + options = get_mock_options() + self.app = app.SoftwareCenterAppGtk3(datadir, xapianpath, options) + self.addCleanup(self.app.destroy) + + self.app.cache.open() + + # connect some signals of interest + cid = self.app.installed_pane.connect('installed-pane-created', + partial(self.track_calls, 'pane-created')) + self.addCleanup(self.app.installed_pane.disconnect, cid) + + cid = self.app.available_pane.connect('available-pane-created', + partial(self.track_calls, 'pane-created')) + self.addCleanup(self.app.available_pane.disconnect, cid) + + def track_calls(self, name, *a, **kw): + """Record the callback for 'name' using 'args' and 'kwargs'.""" + self.called[name].append((a, kw)) + + +class ShowPackagesTestCase(AppTestCase): + """Test suite for parsing/searching/loading package lists.""" + + def do_check(self, packages, search_text): + """Check that the available_pane was shown.""" + self.app.show_available_packages(packages=packages) + + self.assertEqual(self.called, + {'pane-created': [((self.app.available_pane,), {})]}) + + actual = self.app.available_pane.searchentry.get_text() + self.assertEqual(search_text, actual, + 'Expected search text %r (got %r instead) for packages %r.' % + (search_text, actual, packages)) + + self.assertIsNone(self.app.available_pane.app_details_view.app_details) + + def test_show_available_packages_search_prefix(self): + """Check that the available_pane was shown.""" + self.do_check(packages='search:foo,bar baz', search_text='foo bar baz') + + def test_show_available_packages_apt_prefix(self): + """Check that the available_pane was shown.""" + for prefix in ('apt:', 'apt://', 'apt:///'): + self.do_check(packages=prefix + 'foo,bar,baz', + search_text='foo,bar,baz') + + +class ShowPackagesOnePackageTestCase(AppTestCase): + """Test suite for parsing/searching/loading package lists.""" + + pkg_name = 'foo' + installed = None + + def setUp(self): + super(ShowPackagesOnePackageTestCase, self).setUp() + assert self.pkg_name not in self.app.cache + if self.installed is not None: + mock_cache_entry = Mock() + mock_cache_entry.website = None + mock_cache_entry.license = None + mock_cache_entry.installed_files = [] + mock_cache_entry.candidate = Mock() + mock_cache_entry.candidate.version = '1.0' + mock_cache_entry.candidate.description = 'A nonsense app.' + mock_cache_entry.candidate.origins = () + mock_cache_entry.versions = (Mock(),) + mock_cache_entry.versions[0].version = '0.99' + mock_cache_entry.versions[0].origins = (Mock(),) + mock_cache_entry.versions[0].origins[0].archive = 'test' + mock_cache_entry.is_installed = self.installed + if self.installed: + mock_cache_entry.installed = Mock() + mock_cache_entry.installed.version = '0.90' + mock_cache_entry.installed.installed_size = 0 + else: + mock_cache_entry.installed = None + + self.app.cache[self.pkg_name] = mock_cache_entry + self.addCleanup(self.app.cache.pop, self.pkg_name) + + def check_package_availability(self, name): + """Check whether the package 'name' is available.""" + pane = self.app.available_pane + if name not in self.app.cache: + state = PkgStates.NOT_FOUND + elif self.app.cache[name].installed: + state = PkgStates.INSTALLED + pane = self.app.installed_pane + else: + state = PkgStates.UNINSTALLED + + self.assertEqual(state, pane.app_details_view.app_details.pkg_state) + + return pane + + def test_show_available_packages(self): + """Check that the available_pane was shown.""" + self.app.show_available_packages(packages=self.pkg_name) + + expected_pane = self.check_package_availability(self.pkg_name) + name = expected_pane.app_details_view.app_details.name + self.assertEqual(self.pkg_name, name) + + self.assertEqual('', self.app.available_pane.searchentry.get_text()) + + self.assertEqual(self.called, + {'pane-created': [((expected_pane,), {})]}) + + +class ShowPackagesNotInstalledTestCase(ShowPackagesOnePackageTestCase): + """Test suite for parsing/searching/loading package lists.""" + + installed = False + + +class ShowPackagesInstalledTestCase(ShowPackagesOnePackageTestCase): + """Test suite for parsing/searching/loading package lists.""" + + installed = True + + +if __name__ == "__main__": + # avoid spawning recommender-agent, reviews, software-center-agent etc, + # cuts ~5s or so + os.environ["SOFTWARE_CENTER_DISABLE_SPAWN_HELPER"] = "1" + unittest.main() diff -Nru software-center-5.2.2.2/test/gtk3/test_appstore2.py software-center-5.2.3/test/gtk3/test_appstore2.py --- software-center-5.2.2.2/test/gtk3/test_appstore2.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/gtk3/test_appstore2.py 2012-05-31 07:54:49.000000000 +0000 @@ -4,7 +4,7 @@ import xapian from gi.repository import Gtk - +from mock import patch from testutils import setup_test_env setup_test_env() @@ -24,6 +24,19 @@ self.icons = get_test_gtk3_icon_cache() self.db = get_test_db() + def test_lp872760(self): + def monkey_(s): + translations = { + "Painting & Editing" : "translation for Painting & " + "Editing", + } + return translations.get(s, s) + with patch("softwarecenter.ui.gtk3.models.appstore2._", new=monkey_): + model = AppListStore(self.db, self.cache, self.icons) + untranslated = "Painting & Editing" + translated = model._category_translate(untranslated) + self.assertNotEqual(untranslated, translated) + def test_app_store(self): # get a enquire object enquirer = AppEnquire(self.cache, self.db) diff -Nru software-center-5.2.2.2/test/gtk3/test_catview.py software-center-5.2.3/test/gtk3/test_catview.py --- software-center-5.2.2.2/test/gtk3/test_catview.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/gtk3/test_catview.py 2012-06-04 16:08:00.000000000 +0000 @@ -1,66 +1,81 @@ -from gi.repository import Gtk -import time import unittest + +from gi.repository import Gtk from mock import patch, Mock from testutils import setup_test_env setup_test_env() -from softwarecenter.enums import SortMethods -from softwarecenter.testutils import (get_test_db, - make_recommender_agent_recommend_me_dict) +import softwarecenter.distro +import softwarecenter.paths -class TestCatView(unittest.TestCase): +from softwarecenter.db.database import StoreDatabase +from softwarecenter.enums import SortMethods +from softwarecenter.testutils import ( + do_events_with_sleep, + do_events, + FakedCache, + get_test_db, + make_recommender_agent_recommend_me_dict, + ObjectWithSignals, +) +from softwarecenter.ui.gtk3.views import catview_gtk +from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview +from softwarecenter.ui.gtk3.widgets.containers import FramedHeaderBox +from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook + + +class CatViewBaseTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.db = get_test_db() def setUp(self): - self.db = get_test_db() + self._cat = None + self.win = get_test_window_catview(self.db) + self.addCleanup(self.win.destroy) + self.notebook = self.win.get_child() + self.lobby = self.win.get_data("lobby") + self.subcat_view = self.win.get_data("subcat") + self.rec_panel = self.lobby.recommended_for_you_panel def _on_category_selected(self, subcatview, category): - #print "**************", subcatview, category self._cat = category - - def test_subcatview_top_rated(self): - from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview - # get the widgets we need - win = get_test_window_catview() - lobby = win.get_data("lobby") + +class TopAndWhatsNewTestCase(CatViewBaseTestCase): + + def test_top_rated(self): # simulate review-stats refresh - lobby._update_top_rated_content = Mock() - lobby.reviews_loader.emit("refresh-review-stats-finished", []) - self.assertTrue(lobby._update_top_rated_content.called) + self.lobby._update_top_rated_content = Mock() + self.lobby.reviews_loader.emit("refresh-review-stats-finished", []) + self.assertTrue(self.lobby._update_top_rated_content.called) # test clicking top_rated - lobby.connect("category-selected", self._on_category_selected) - lobby.top_rated_frame.more.clicked() - self._p() + self.lobby.connect("category-selected", self._on_category_selected) + self.lobby.top_rated_frame.more.clicked() + do_events() self.assertNotEqual(self._cat, None) self.assertEqual(self._cat.name, "Top Rated") self.assertEqual(self._cat.sortmode, SortMethods.BY_TOP_RATED) - win.destroy() - - def test_subcatview_new(self): - from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview - # get the widgets we need - win = get_test_window_catview() - lobby = win.get_data("lobby") + def test_new(self): # test db reopen triggers whats-new update - lobby._update_whats_new_content = Mock() - lobby.db.emit("reopen") - self.assertTrue(lobby._update_whats_new_content.called) + self.lobby._update_whats_new_content = Mock() + self.lobby.db.emit("reopen") + self.assertTrue(self.lobby._update_whats_new_content.called) # test clicking new - lobby.connect("category-selected", self._on_category_selected) - lobby.whats_new_frame.more.clicked() - self._p() + self.lobby.connect("category-selected", self._on_category_selected) + self.lobby.whats_new_frame.more.clicked() + do_events() self.assertNotEqual(self._cat, None) # encoding is utf-8 (since r2218, see category.py) self.assertEqual(self._cat.name, 'What\xe2\x80\x99s New') self.assertEqual(self._cat.sortmode, SortMethods.BY_CATALOGED_TIME) - win.destroy() - def test_subcatview_new_no_sort_info_yet(self): + def test_new_no_sort_info_yet(self): # ensure that we don't show a empty "whats new" category # see LP: #865985 from softwarecenter.testutils import get_test_db @@ -68,7 +83,7 @@ cache = db._aptcache # simulate a fresh install with no catalogedtime info del db._axi_values["catalogedtime"] - + from softwarecenter.testutils import get_test_gtk3_icon_cache icons = get_test_gtk3_icon_cache() @@ -76,15 +91,15 @@ apps_filter = AppFilter(db, cache) from softwarecenter.distro import get_distro - import softwarecenter.paths - from softwarecenter.paths import APP_INSTALL_PATH from softwarecenter.ui.gtk3.views.catview_gtk import LobbyViewGtk - view = LobbyViewGtk(softwarecenter.paths.datadir, APP_INSTALL_PATH, + view = LobbyViewGtk(softwarecenter.paths.datadir, + softwarecenter.paths.APP_INSTALL_PATH, cache, db, icons, get_distro(), apps_filter) view.show() # gui win = Gtk.Window() + self.addCleanup(win.destroy) win.set_size_request(800, 400) scroll = Gtk.ScrolledWindow() @@ -93,171 +108,223 @@ win.add(scroll) win.show() # test visibility - self._p() + do_events() self.assertFalse(view.whats_new_frame.get_property("visible")) - self._p() - win.destroy() - def test_subcatview_recommended_for_you_opt_in_display(self): - - # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') - self.addCleanup(get_recommender_opted_in_patcher.stop) - mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + +class RecommendationsTestCase(CatViewBaseTestCase): + """The test suite for the recommendations .""" + + @unittest.skip("Disabled because of race condition in test") + @patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') + def test_recommended_for_you_opt_in_display( + self, mock_get_recommender_opted_in): + # patch the recommender UUID value to ensure that we are not opted-in + # for this test mock_get_recommender_opted_in.return_value = False - - from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview - # get the widgets we need - win = get_test_window_catview() - lobby = win.get_data("lobby") - rec_panel = lobby.recommended_for_you_panel - self._p() - from softwarecenter.ui.gtk3.widgets.containers import FramedHeaderBox - self.assertTrue(rec_panel.spinner_notebook.get_current_page() == FramedHeaderBox.CONTENT) - self.assertTrue(rec_panel.opt_in_vbox.get_property("visible")) - win.destroy() - + + do_events() + self.assertEqual(self.rec_panel.spinner_notebook.get_current_page(), + FramedHeaderBox.CONTENT) + self.assertTrue(self.rec_panel.opt_in_vbox.get_property("visible")) + + @unittest.skip("Disabled because of race condition in test") # patch out the agent query method to avoid making the actual server call + @patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') @patch('softwarecenter.backend.recagent.RecommenderAgent' '.post_submit_profile') - def test_subcatview_recommended_for_you_spinner_display(self, mock_query): - - # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') - self.addCleanup(get_recommender_opted_in_patcher.stop) - mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + def test_recommended_for_you_spinner_display( + self, mock_query, mock_get_recommender_opted_in): + # patch the recommender UUID value to insure that we are not opted-in + # for this test mock_get_recommender_opted_in.return_value = False - - from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview - # get the widgets we need - win = get_test_window_catview() - lobby = win.get_data("lobby") - rec_panel = lobby.recommended_for_you_panel - self._p() - # click the opt-in button to initiate the process, this will show the spinner - rec_panel.opt_in_button.emit('clicked') - self._p() - from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook - self.assertTrue(rec_panel.spinner_notebook.get_current_page() == SpinnerNotebook.SPINNER_PAGE) - self.assertTrue(rec_panel.opt_in_vbox.get_property("visible")) - win.destroy() + + # click the opt-in button to initiate the process, + # this will show the spinner + self.rec_panel.opt_in_button.emit('clicked') + do_events() + self.assertEqual(self.rec_panel.spinner_notebook.get_current_page(), + SpinnerNotebook.SPINNER_PAGE) + self.assertTrue(self.rec_panel.opt_in_vbox.get_property("visible")) # patch out the agent query method to avoid making the actual server call + @patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') @patch('softwarecenter.backend.recagent.RecommenderAgent' '.post_submit_profile') - def test_subcatview_recommended_for_you_display_recommendations(self, mock_query): - - # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') - self.addCleanup(get_recommender_opted_in_patcher.stop) - mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + def test_recommended_for_you_display_recommendations(self, + mock_query, mock_get_recommender_opted_in): + # patch the recommender UUID value to insure that we are not opted-in + # for this test mock_get_recommender_opted_in.return_value = False - - from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview - # get the widgets we need - win = get_test_window_catview() - lobby = win.get_data("lobby") - rec_panel = lobby.recommended_for_you_panel - self._p() - # click the opt-in button to initiate the process, this will show the spinner - rec_panel.opt_in_button.emit('clicked') - self._p() - rec_panel._update_recommended_for_you_content() - self._p() + + # click the opt-in button to initiate the process, + # this will show the spinner + self.rec_panel.opt_in_button.emit('clicked') + do_events() + self.rec_panel._update_recommended_for_you_content() + do_events() # we fake the callback from the agent here - lobby.recommended_for_you_panel.recommended_for_you_cat._recommend_me_result( - None, - make_recommender_agent_recommend_me_dict()) - self.assertNotEqual( - lobby.recommended_for_you_panel.recommended_for_you_cat.get_documents(self.db), []) - from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook - self.assertTrue(rec_panel.spinner_notebook.get_current_page() == SpinnerNotebook.CONTENT_PAGE) - self._p() + for_you = self.lobby.recommended_for_you_panel.recommended_for_you_cat + for_you._recommend_me_result(None, + make_recommender_agent_recommend_me_dict()) + self.assertNotEqual(for_you.get_documents(self.db), []) + self.assertEqual(self.rec_panel.spinner_notebook.get_current_page(), + SpinnerNotebook.CONTENT_PAGE) + do_events() # test clicking recommended_for_you More button - lobby.connect("category-selected", self._on_category_selected) - lobby.recommended_for_you_panel.more.clicked() - self._p() + self.lobby.connect("category-selected", self._on_category_selected) + self.lobby.recommended_for_you_panel.more.clicked() + # this is delayed for some reason so we need to sleep here + do_events_with_sleep() self.assertNotEqual(self._cat, None) self.assertEqual(self._cat.name, "Recommended For You") - win.destroy() - + # patch out the agent query method to avoid making the actual server call + @patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') @patch('softwarecenter.backend.recagent.RecommenderAgent' '.query_recommend_me') - def test_subcatview_recommended_for_you_display_recommendations_not_opted_in(self, mock_query): - - # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') - self.addCleanup(get_recommender_opted_in_patcher.stop) - mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + def test_recommended_for_you_display_recommendations_not_opted_in(self, + mock_query, mock_get_recommender_opted_in): + # patch the recommender UUID value to insure that we are not opted-in + # for this test mock_get_recommender_opted_in.return_value = False - - from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview - # get the widgets we need - win = get_test_window_catview() + # we want to work in the "subcat" view - notebook = win.get_child() - notebook.next_page() - - subcat_view = win.get_data("subcat") - self._p() - self.assertFalse(subcat_view.recommended_for_you_in_cat.get_property("visible")) - win.destroy() - + self.notebook.next_page() + + do_events() + visible = self.subcat_view.recommended_for_you_in_cat.get_property( + "visible") + self.assertFalse(visible) + # patch out the agent query method to avoid making the actual server call + @patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') @patch('softwarecenter.backend.recagent.RecommenderAgent' '.query_recommend_me') - def test_subcatview_recommended_for_you_display_recommendations_opted_in(self, mock_query): - - # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') - self.addCleanup(get_recommender_opted_in_patcher.stop) - mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + def test_recommended_for_you_display_recommendations_opted_in( + self, mock_query, mock_get_recommender_opted_in): + # patch the recommender UUID value to insure that we are not opted-in + # for this test mock_get_recommender_opted_in.return_value = True - - from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview - # get the widgets we need - win = get_test_window_catview() + # we want to work in the "subcat" view - notebook = win.get_child() - notebook.next_page() - - subcat_view = win.get_data("subcat") - rec_cat_panel = subcat_view.recommended_for_you_in_cat - self._p() + self.notebook.next_page() + + rec_cat_panel = self.subcat_view.recommended_for_you_in_cat rec_cat_panel._update_recommended_for_you_content() - self._p() + do_events() # we fake the callback from the agent here rec_cat_panel.recommended_for_you_cat._recommend_me_result( None, make_recommender_agent_recommend_me_dict()) - result_docs = rec_cat_panel.recommended_for_you_cat.get_documents(self.db) + result_docs = rec_cat_panel.recommended_for_you_cat.get_documents( + self.db) self.assertNotEqual(result_docs, []) - # check that we are getting the correct number of results, corresponding - # to the following Internet items: + # check that we are getting the correct number of results, + # corresponding to the following Internet items: # Mangler, Midori, Midori Private Browsing, Psi self.assertTrue(len(result_docs) == 4) - from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook - self.assertTrue(rec_cat_panel.spinner_notebook.get_current_page() == SpinnerNotebook.CONTENT_PAGE) + self.assertEqual(rec_cat_panel.spinner_notebook.get_current_page(), + SpinnerNotebook.CONTENT_PAGE) # check that the tiles themselves are visible - self._p() - self.assertTrue(rec_cat_panel.recommended_for_you_content.get_property("visible")) - self.assertTrue(rec_cat_panel.recommended_for_you_content.get_children()[0].title.get_property("visible")) - self._p() + do_events() + self.assertTrue(rec_cat_panel.recommended_for_you_content.get_property( + "visible")) + self.assertTrue(rec_cat_panel.recommended_for_you_content.get_children( + )[0].title.get_property("visible")) + do_events() # test clicking recommended_for_you More button - subcat_view.connect("category-selected", self._on_category_selected) + self.subcat_view.connect( + "category-selected", self._on_category_selected) rec_cat_panel.more.clicked() - self._p() + # this is delayed for some reason so we need to sleep here + do_events_with_sleep() self.assertNotEqual(self._cat, None) self.assertEqual(self._cat.name, "Recommended For You in Internet") - win.destroy() - def _p(self): - for i in range(5): - time.sleep(0.1) - while Gtk.events_pending(): - Gtk.main_iteration() +class ExhibitsTestCase(unittest.TestCase): + """The test suite for the exhibits carousel.""" + + def setUp(self): + self.datadir = softwarecenter.paths.datadir + self.desktopdir = softwarecenter.paths.APP_INSTALL_PATH + self.cache = FakedCache() + self.db = StoreDatabase(cache=self.cache) + self.lobby = catview_gtk.LobbyViewGtk(datadir=self.datadir, + desktopdir=self.desktopdir, cache=self.cache, db=self.db, + icons=None, apps_filter=None) + self.addCleanup(self.lobby.destroy) + + def _get_banner_from_lobby(self): + return self.lobby.vbox.get_children()[-1].get_child() + + def test_featured_exhibit_by_default(self): + """Show the featured exhibit before querying the remote service.""" + self.lobby._append_banner_ads() + + banner = self._get_banner_from_lobby() + self.assertEqual(1, len(banner.exhibits)) + self.assertIsInstance(banner.exhibits[0], catview_gtk.FeaturedExhibit) + + def test_no_exhibit_if_not_available(self): + """The exhibit should not be shown if the package is not available.""" + exhibit = Mock() + exhibit.package_names = u'foobarbaz' + + sca = ObjectWithSignals() + sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit]) + + with patch.object(catview_gtk, 'SoftwareCenterAgent', lambda: sca): + self.lobby._append_banner_ads() + + banner = self._get_banner_from_lobby() + self.assertEqual(1, len(banner.exhibits)) + self.assertIsInstance(banner.exhibits[0], catview_gtk.FeaturedExhibit) + + def test_exhibit_if_available(self): + """The exhibit should be shown if the package is available.""" + exhibit = Mock() + exhibit.package_names = u'foobarbaz' + exhibit.banner_url = 'banner' + exhibit.title_translated = '' + + self.cache[u'foobarbaz'] = Mock() + + sca = ObjectWithSignals() + sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit]) + + with patch.object(catview_gtk, 'SoftwareCenterAgent', lambda: sca): + self.lobby._append_banner_ads() + + banner = self._get_banner_from_lobby() + self.assertEqual(1, len(banner.exhibits)) + self.assertIs(banner.exhibits[0], exhibit) + + def test_exhibit_if_mixed_availability(self): + """The exhibit should be shown even if some are not available.""" + # available exhibit + exhibit = Mock() + exhibit.package_names = u'foobarbaz' + exhibit.banner_url = 'banner' + exhibit.title_translated = '' + + self.cache[u'foobarbaz'] = Mock() + + # not available exhibit + other = Mock() + other.package_names = u'not-there' + + sca = ObjectWithSignals() + sca.query_exhibits = lambda: sca.emit('exhibits', sca, + [exhibit, other]) + + with patch.object(catview_gtk, 'SoftwareCenterAgent', lambda: sca): + self.lobby._append_banner_ads() + + banner = self._get_banner_from_lobby() + self.assertEqual(1, len(banner.exhibits)) + self.assertIs(banner.exhibits[0], exhibit) if __name__ == "__main__": diff -Nru software-center-5.2.2.2/test/gtk3/test_debfile_view.py software-center-5.2.3/test/gtk3/test_debfile_view.py --- software-center-5.2.2.2/test/gtk3/test_debfile_view.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/gtk3/test_debfile_view.py 2012-05-31 07:54:49.000000000 +0000 @@ -3,22 +3,18 @@ import time import unittest -from mock import Mock - -from testutils import setup_test_env, do_events +from testutils import do_events, get_mock_options, setup_test_env setup_test_env() import softwarecenter.paths from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane + class DebFileOpenTestCase(unittest.TestCase): def test_deb_file_view_error(self): - mock_options = Mock() - mock_options.display_navlog = False - mock_options.disable_apt_xapian_index = False - mock_options.disable_buy = False + mock_options = get_mock_options() xapianpath = softwarecenter.paths.XAPIAN_BASE_PATH app = SoftwareCenterAppGtk3( softwarecenter.paths.datadir, xapianpath, mock_options) @@ -37,7 +33,6 @@ # this is deb that is not installable action_button = app.available_pane.app_details_view.pkg_statusbar.button self.assertFalse(action_button.get_property("visible")) - if __name__ == "__main__": diff -Nru software-center-5.2.2.2/test/gtk3/test_purchase.py software-center-5.2.3/test/gtk3/test_purchase.py --- software-center-5.2.2.2/test/gtk3/test_purchase.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/gtk3/test_purchase.py 2012-05-31 07:54:49.000000000 +0000 @@ -3,12 +3,12 @@ import time import unittest -from mock import Mock,patch +from mock import Mock, patch from testutils import setup_test_env setup_test_env() -from softwarecenter.testutils import do_events +from softwarecenter.testutils import do_events, get_mock_options from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane import softwarecenter.paths @@ -35,7 +35,7 @@ self.assertTrue("skipping" in mock.debug.call_args[0][0]) self.assertFalse("consumer_secret" in mock.debug.call_args[0][0]) mock.reset_mock() - + # run another one win.destroy() @@ -71,25 +71,21 @@ # run another one win.destroy() - def test_reinstall_previous_purchase_display(self): - mock_options = Mock() - mock_options.display_navlog = False - mock_options.disable_apt_xapian_index = False - mock_options.disable_buy = False + mock_options = get_mock_options() xapiandb = "/var/cache/software-center/" app = SoftwareCenterAppGtk3( softwarecenter.paths.datadir, xapiandb, mock_options) - # real app opens cache async - app.cache.open() - # show it + # real app opens cache async + app.cache.open() + # show it app.window_main.show_all() app.available_pane.init_view() self._p() app.on_menuitem_reinstall_purchases_activate(None) # it can take a bit until the sso client is ready for i in range(100): - if (app.available_pane.get_current_page() == + if (app.available_pane.get_current_page() == AvailablePane.Pages.LIST): break self._p() diff -Nru software-center-5.2.2.2/test/gtk3/test_spinner.py software-center-5.2.3/test/gtk3/test_spinner.py --- software-center-5.2.2.2/test/gtk3/test_spinner.py 1970-01-01 00:00:00.000000000 +0000 +++ software-center-5.2.3/test/gtk3/test_spinner.py 2012-05-31 07:54:49.000000000 +0000 @@ -0,0 +1,152 @@ +import os +import unittest + +from gi.repository import Gtk +from mock import patch + +from testutils import setup_test_env +setup_test_env() + +from softwarecenter.ui.gtk3.widgets import spinner + + +class SpinnerNotebookTestCase(unittest.TestCase): + """The test case for the SpinnerNotebook.""" + + _fake_timeout_id = object() + + def setUp(self): + # helpers to check the timeout's callbacks + self._interval = None + self._callback = None + + self.content = Gtk.Label('My test') + self.content.show() + self.addCleanup(self.content.hide) + self.addCleanup(self.content.destroy) + + self.obj = spinner.SpinnerNotebook(self.content) + self.addCleanup(self.obj.hide) + self.addCleanup(self.obj.destroy) + assert spinner.SOFTWARE_CENTER_DEBUG_TABS not in os.environ + + self.obj.show() + + def _fake_timeout_add(self, interval, callback): + self._interval = interval + self._callback = callback + return self._fake_timeout_id + + def _fake_source_remove(self, event_id): + if event_id is self._fake_timeout_id: + self._fake_timeout_id = None + return True + + def test_no_borders(self): + """The notebook has no borders.""" + self.assertFalse(self.obj.get_show_border()) + + def test_no_tabs(self): + """The notebook has no visible tabs.""" + self.assertFalse(self.obj.get_show_tabs()) + + def test_tabs_if_debug_set(self): + """The notebook has visible tabs if debug is set.""" + with patch.object(spinner, 'SOFTWARE_CENTER_DEBUG_TABS', True): + self.obj = spinner.SpinnerNotebook(self.content) + self.assertTrue(self.obj.get_show_tabs()) + + def test_has_two_pages(self): + """The notebook has two pages.""" + self.assertEqual(self.obj.get_n_pages(), 2) + + def test_has_content(self): + """The notebook has the given content.""" + self.assertEqual(self.obj.get_nth_page(self.obj.CONTENT_PAGE), + self.content) + + def test_has_spinner(self): + """The notebook has the spinner view.""" + self.assertEqual(self.obj.get_nth_page(self.obj.SPINNER_PAGE), + self.obj.spinner_view) + self.assertTrue(self.obj.spinner_view.get_visible()) + + def test_show_content_by_default(self): + """The content tab is shown by default.""" + self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) + + def test_show_spinner(self): + """The spinner is shown only after the timeout occurs.""" + assert self._interval is None + assert self._callback is None + + with patch.object(spinner.GObject, 'timeout_add', + self._fake_timeout_add): + self.obj.show_spinner() + + # this must hold before the callback is fired + self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) + self.assertFalse(self.obj.spinner_view.spinner.get_property('active')) + self.assertFalse(self.obj.spinner_view.spinner.get_visible()) + self.assertEqual(self._interval, 250) + self.assertEqual(self._callback, self.obj._unmask_view_spinner) + + result = self._callback() # fire the timeout + + # this must hold after the callback is fired + self.assertFalse(result, 'The timeout callback should return False.') + self.assertTrue(self.obj.spinner_view.spinner.get_property('active')) + self.assertTrue(self.obj.spinner_view.spinner.get_visible()) + self.assertEqual(self.obj.get_current_page(), self.obj.SPINNER_PAGE) + + def test_show_spinner_with_msg(self): + """The spinner is shown with the given message.""" + message = 'Something I want to show' + with patch.object(spinner.GObject, 'timeout_add', lambda *a: None): + self.obj.show_spinner(msg=message) + + self.assertEqual(self.obj.spinner_view.get_text(), message) + + def test_hide_spinner_before_timeout(self): + """The spinner is hidden cancelling the timeout.""" + with patch.object(spinner.GObject, 'timeout_add', + self._fake_timeout_add): + self.obj.show_spinner() + + with patch.object(spinner.GObject, 'source_remove', + self._fake_source_remove): + self.obj.hide_spinner() + + # hide_spinner should call source_remove with the proper event id, + # which in turn will set the _fake_timeout_id to None + self.assertTrue(self._fake_timeout_id is None, + 'The timeout should be removed by calling GObject.source_remove') + # the content page is shown + self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) + # the spinner is stoppped and hidden + self.assertFalse(self.obj.spinner_view.spinner.get_property('active')) + self.assertFalse(self.obj.spinner_view.spinner.get_visible()) + + def test_hide_spinner_after_timeout(self): + """The spinner is hidden without cancelling the timeout.""" + with patch.object(spinner.GObject, 'timeout_add', + self._fake_timeout_add): + self.obj.show_spinner() + + self._callback() # fake the timeout being fired + + with patch.object(spinner.GObject, 'source_remove', + self._fake_source_remove): + self.obj.hide_spinner() + + self.assertTrue(self._fake_timeout_id is not None, + 'GObject.source_remove should not be called if already fired.') + # the content page is shown + self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) + # the spinner is stoppped and hidden + self.assertFalse(self.obj.spinner_view.spinner.get_property('active')) + self.assertFalse(self.obj.spinner_view.spinner.get_visible()) + + +if __name__ == "__main__": + unittest.main() diff -Nru software-center-5.2.2.2/test/gtk3/testutils.py software-center-5.2.3/test/gtk3/testutils.py --- software-center-5.2.2.2/test/gtk3/testutils.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/gtk3/testutils.py 2012-05-31 07:54:49.000000000 +0000 @@ -22,6 +22,9 @@ import tempfile import time +from collections import defaultdict + +from mock import Mock m_dbus = m_polkit = m_aptd = None @@ -141,11 +144,16 @@ main_loop.iteration() +def do_events_with_sleep(iterations=5, sleep=0.1): + for i in range(iterations): + do_events() + time.sleep(sleep) + + def get_mock_app_from_real_app(real_app): """ take a application and return a app where the details are a mock of the real details so they can easily be modified """ - from mock import Mock import copy app = copy.copy(real_app) db = get_test_db() @@ -160,6 +168,16 @@ return app +def get_mock_options(): + """Return a mock suitable to act as SoftwareCenterAppGtk3's options.""" + mock_options = Mock() + mock_options.display_navlog = False + mock_options.disable_apt_xapian_index = False + mock_options.disable_buy = False + + return mock_options + + def setup_test_env(): """ Setup environment suitable for running the test/* code in a checkout. This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir. @@ -304,3 +322,57 @@ {u'rating': 1.5, u'package_name': u'tucan'}], u'app': u'pitivi'} return recommend_app_data + + +class ObjectWithSignals(object): + """A faked object that you can connect to and emit signals.""" + + def __init__(self, *a, **kw): + super(ObjectWithSignals, self).__init__() + self._callbacks = defaultdict(list) + + def connect(self, signal, callback): + """Connect a signal with a callback.""" + self._callbacks[signal].append(callback) + + def disconnect(self, signal, callback): + """Connect a signal with a callback.""" + self._callbacks[signal].remove(callback) + if len(self._callbacks[signal]) == 0: + self._callbacks.pop(signal) + + def disconnect_by_func(self, callback): + """Disconnect 'callback' from every signal.""" + # do not use iteritems since we may change the dict inside the for + for signal, callbacks in self._callbacks.items(): + if callback in callbacks: + self.disconnect(signal, callback) + + def emit(self, signal, *args, **kwargs): + """Emit 'signal' passing *args, **kwargs to every callback.""" + for callback in self._callbacks[signal]: + callback(*args, **kwargs) + + +class FakedCache(ObjectWithSignals, dict): + """A faked cache.""" + + def __init__(self, *a, **kw): + super(FakedCache, self).__init__() + self.ready = False + + def open(self): + """Open this cache.""" + self.ready = True + + def component_available(self, distro_codename, component): + """Return whether 'component' is available in 'distro_codename'.""" + + def get_addons(self, pkgname): + """Return (recommended, suggested) addons for 'pkgname'.""" + return ([], []) + + def get_total_size_on_install(self, pkgname, addons_to_install, + addons_to_remove, archive_suite): + """Return a fake (total_download_size, total_install_size) result.""" + return (0, 0) diff -Nru software-center-5.2.2.2/test/test_addons.py software-center-5.2.3/test/test_addons.py --- software-center-5.2.2.2/test/test_addons.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/test_addons.py 2012-05-31 08:23:03.000000000 +0000 @@ -15,6 +15,7 @@ self.cache = get_pkg_info() self.cache.open() + @unittest.skip("disabled until fixture setup is done") def test_get_addons_simple(self): # 7zip res = self.cache.get_addons("p7zip-full", ignore_installed=False) diff -Nru software-center-5.2.2.2/test/test_database.py software-center-5.2.3/test/test_database.py --- software-center-5.2.2.2/test/test_database.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/test_database.py 2012-05-31 07:54:49.000000000 +0000 @@ -406,9 +406,13 @@ packagesize 3 # package size app-popcon 4 # app-install .desktop popcon rank """ - open("axi-test-values","w").write(s) + fname = "axi-test-values" + with open(fname, "w") as f: + f.write(s) + self.addCleanup(os.remove, fname) + #db = StoreDatabase("/var/cache/software-center/xapian", self.cache) - axi_values = parse_axi_values_file("axi-test-values") + axi_values = parse_axi_values_file(fname) self.assertNotEqual(axi_values, {}) print axi_values @@ -420,7 +424,7 @@ self.assertTrue(len(details.tags) > 2) def test_app_enquire(self): - db = StoreDatabase("/var/cache/software-center/xapian", self.cache) + db = StoreDatabase(cache=self.cache) db.open() # test the AppEnquire engine enquirer = AppEnquire(self.cache, db) @@ -428,7 +432,14 @@ nonblocking_load=False) self.assertTrue(len(enquirer.get_docids()) > 0) # FIXME: test more of the interface - + + def test_is_pkgname_known(self): + db = StoreDatabase(cache=self.cache) + db.open() + self.assertTrue(db.is_pkgname_known("apt")) + self.assertFalse(db.is_pkgname_known("i+am-not-a-pkg")) + + class UtilsTestCase(unittest.TestCase): def test_utils_get_installed_package_list(self): diff -Nru software-center-5.2.2.2/test/test_debfileapplication.py software-center-5.2.3/test/test_debfileapplication.py --- software-center-5.2.2.2/test/test_debfileapplication.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/test_debfileapplication.py 2012-05-31 07:54:49.000000000 +0000 @@ -7,7 +7,7 @@ setup_test_env() from softwarecenter.enums import PkgStates -from softwarecenter.db.debfile import DebFileApplication +from softwarecenter.db.debfile import DebFileApplication, DebFileOpenError from softwarecenter.testutils import get_test_db DEBFILE_PATH = './data/test_debs/gdebi-test9.deb' @@ -22,6 +22,7 @@ DEBFILE_PATH_CORRUPT = './data/test_debs/corrupt.deb' DEBFILE_NOT_INSTALLABLE = './data/test_debs/gdebi-test1.deb' + class TestDebFileApplication(unittest.TestCase): """ Test the class DebFileApplication """ @@ -31,7 +32,7 @@ def test_get_name(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) - + self.assertEquals(debfiledetails.name, DEBFILE_NAME) def test_get_description(self): @@ -64,9 +65,10 @@ debfileapplication = DebFileApplication(DEBFILE_PATH_NOTFOUND) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.pkg_state, PkgStates.NOT_FOUND) - + def test_get_pkg_state_not_a_deb(self): - self.assertRaises(ValueError, DebFileApplication, DEBFILE_PATH_NOTADEB) + self.assertRaises(DebFileOpenError, + DebFileApplication, DEBFILE_PATH_NOTADEB) def test_get_pkg_state_corrupt(self): debfileapplication = DebFileApplication(DEBFILE_PATH_CORRUPT) @@ -87,12 +89,14 @@ debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.installed_size, 0) - + def test_get_warning(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.warning, DEBFILE_WARNING) - + + if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) unittest.main() + diff -Nru software-center-5.2.2.2/test/test_description_norm.py software-center-5.2.3/test/test_description_norm.py --- software-center-5.2.2.2/test/test_description_norm.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/test_description_norm.py 2012-05-31 07:54:49.000000000 +0000 @@ -12,6 +12,20 @@ class TestAppDescriptionNormalize(unittest.TestCase): """ tests the description noramlization """ + def test_description_parser_regression_test_merged_words(self): + # this is a regression test for the description parser + # there is a bug that the newline is stripped and two words + # are merged (LP: #983831) + s = """A test package + + The goal is to test that multi-line descriptions are handled correctly, + especially with regards to sentences that span more than two lines and how + spaces and line wrapping is handled. +""" + description_text = normalize_package_description(s) + self.assertEqual( + description_text, + """A test package\nThe goal is to test that multi-line descriptions are handled correctly, especially with regards to sentences that span more than two lines and how spaces and line wrapping is handled.""") def test_description_parser_regression_test_moppet(self): # this is a regression test for the description parser # there is a bug that after GAME FEATURES the bullet list diff -Nru software-center-5.2.2.2/test/test_package_info.py software-center-5.2.3/test/test_package_info.py --- software-center-5.2.2.2/test/test_package_info.py 2012-06-01 15:06:45.000000000 +0000 +++ software-center-5.2.3/test/test_package_info.py 2012-06-04 16:33:15.000000000 +0000 @@ -65,6 +65,7 @@ pkginfo = self.pkginfo self.assertTrue(len(pkginfo.get_addons("firefox")) > 0) + @unittest.skip("disabled due to invalid fixture data") def test_removal(self): pkginfo = self.pkginfo pkg = pkginfo['coreutils'] diff -Nru software-center-5.2.2.2/test/test_scagent.py software-center-5.2.3/test/test_scagent.py --- software-center-5.2.2.2/test/test_scagent.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/test_scagent.py 2012-05-31 07:54:49.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/python from gi.repository import GObject -from mock import patch +from mock import Mock, patch import unittest from testutils import setup_test_env @@ -52,6 +52,24 @@ 'SoftwareCenterAgentAPI', 'subscriptions_for_me', complete_only=True) + def test_regression_lp1004417(self): + mock_ex = Mock() + mock_ex.package_names = "foo,bar\n\r" + results = [mock_ex] + sca = SoftwareCenterAgent() + sca.emit = Mock() + sca._on_exhibits_data_available(None, results) + self.assertTrue(sca.emit.called) + # get the args to "emit()" + args, kwargs = sca.emit.call_args + # split the args up + scagent, exhibit_list = args + # and ensure we get the right list len + self.assertEqual(len(exhibit_list), 1) + # and the right data in the list + exhibit = exhibit_list[0] + self.assertEqual(exhibit.package_names, "foo,bar") + self.assertFalse(exhibit.package_names.endswith("\n\r")) if __name__ == "__main__": import logging diff -Nru software-center-5.2.2.2/test/testutils.py software-center-5.2.3/test/testutils.py --- software-center-5.2.2.2/test/testutils.py 2012-06-01 14:38:51.000000000 +0000 +++ software-center-5.2.3/test/testutils.py 2012-05-31 07:54:49.000000000 +0000 @@ -22,6 +22,9 @@ import tempfile import time +from collections import defaultdict + +from mock import Mock m_dbus = m_polkit = m_aptd = None @@ -141,11 +144,16 @@ main_loop.iteration() +def do_events_with_sleep(iterations=5, sleep=0.1): + for i in range(iterations): + do_events() + time.sleep(sleep) + + def get_mock_app_from_real_app(real_app): """ take a application and return a app where the details are a mock of the real details so they can easily be modified """ - from mock import Mock import copy app = copy.copy(real_app) db = get_test_db() @@ -160,6 +168,16 @@ return app +def get_mock_options(): + """Return a mock suitable to act as SoftwareCenterAppGtk3's options.""" + mock_options = Mock() + mock_options.display_navlog = False + mock_options.disable_apt_xapian_index = False + mock_options.disable_buy = False + + return mock_options + + def setup_test_env(): """ Setup environment suitable for running the test/* code in a checkout. This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir. @@ -304,3 +322,57 @@ {u'rating': 1.5, u'package_name': u'tucan'}], u'app': u'pitivi'} return recommend_app_data + + +class ObjectWithSignals(object): + """A faked object that you can connect to and emit signals.""" + + def __init__(self, *a, **kw): + super(ObjectWithSignals, self).__init__() + self._callbacks = defaultdict(list) + + def connect(self, signal, callback): + """Connect a signal with a callback.""" + self._callbacks[signal].append(callback) + + def disconnect(self, signal, callback): + """Connect a signal with a callback.""" + self._callbacks[signal].remove(callback) + if len(self._callbacks[signal]) == 0: + self._callbacks.pop(signal) + + def disconnect_by_func(self, callback): + """Disconnect 'callback' from every signal.""" + # do not use iteritems since we may change the dict inside the for + for signal, callbacks in self._callbacks.items(): + if callback in callbacks: + self.disconnect(signal, callback) + + def emit(self, signal, *args, **kwargs): + """Emit 'signal' passing *args, **kwargs to every callback.""" + for callback in self._callbacks[signal]: + callback(*args, **kwargs) + + +class FakedCache(ObjectWithSignals, dict): + """A faked cache.""" + + def __init__(self, *a, **kw): + super(FakedCache, self).__init__() + self.ready = False + + def open(self): + """Open this cache.""" + self.ready = True + + def component_available(self, distro_codename, component): + """Return whether 'component' is available in 'distro_codename'.""" + + def get_addons(self, pkgname): + """Return (recommended, suggested) addons for 'pkgname'.""" + return ([], []) + + def get_total_size_on_install(self, pkgname, addons_to_install, + addons_to_remove, archive_suite): + """Return a fake (total_download_size, total_install_size) result.""" + return (0, 0)