Merge lp:~nataliabidart/software-center/fix-986563 into lp:software-center/5.2

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 3032
Proposed branch: lp:~nataliabidart/software-center/fix-986563
Merge into: lp:software-center/5.2
Prerequisite: lp:~nataliabidart/software-center/fix-977931
Diff against target: 862 lines (+323/-159)
9 files modified
softwarecenter/backend/channel.py (+4/-4)
softwarecenter/db/application.py (+5/-4)
softwarecenter/db/database.py (+20/-5)
softwarecenter/testutils.py (+56/-0)
softwarecenter/ui/gtk3/models/appstore2.py (+11/-5)
softwarecenter/ui/gtk3/views/catview_gtk.py (+28/-16)
test/gtk3/test_app.py (+2/-36)
test/gtk3/test_catview.py (+190/-88)
test/test_database.py (+7/-1)
To merge this branch: bzr merge lp:~nataliabidart/software-center/fix-986563
Reviewer Review Type Date Requested Status
Michael Vogt Approve
Review via email: mp+106710@code.launchpad.net

Commit message

- Filtered out those exhibits that do not their packages available in the db.

Description of the change

Some of the changes in this diff were needed to avoid some nasty exception while running the test suite (for example the changes of how get_distro is imported).

To post a comment you must log in.
3027. By Natalia Bidart

Attaching bug #986563.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for your branch.

This is again a beauty to read and I much appreciate the effort that goes beyond the immediate fix and that
helps making the code and tests better. The ExhibitsTestCase code is a excellent example on a good and
clean test, very nice.

Some questions/comments:
 - what do you think about moving "_pkg_available()" out into a more generic place like db/utils.py ?
 - _filter_and_set_exhibits() should probably have a small comment that its ok if result list is empty and that empty list is set via set_exhibits() as the exhibit banner will ignore empty lists

Why is
281 + if self.top_rated is None:
282 + return
needed? Is that for the tests when the spawn helper stuff is disabled?

This looks like its not really needed for the pkg:
735 + pkg.banner_url = ''
736 + pkg.title_translated = ''
or am I misreading this?

One thing that is maybe nice to test is a list with a existing and a non-existing pkgname.

But none of the above is a showstopper and I'm happy to approve it.

While looking at the test_catview.py I noticed quite a bit of older code that wasn't ideal so I took the
liberty to do some cleanup in lp:~mvo/software-center/test-catview-cleanup that is based on your branch.
I will propose this for merging.

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

> This is again a beauty to read and I much appreciate the effort that goes
> beyond the immediate fix and that
> helps making the code and tests better. The ExhibitsTestCase code is a
> excellent example on a good and
> clean test, very nice.

:-)

> Some questions/comments:
> - what do you think about moving "_pkg_available()" out into a more generic
> place like db/utils.py ?

(09:42:04 AM) nessita: mvo: is there any better/simpler way of knowing if a given pkg is available, without creating an Application? Like, can the StoreDatabase know that?

(otherwise we need to pass the db to the helper and that sounds, at least to me, a bit dirty)

> - _filter_and_set_exhibits() should probably have a small comment that its ok
> if result list is empty and that empty list is set via set_exhibits() as the
> exhibit banner will ignore empty lists

Added.

> Why is
> 281 + if self.top_rated is None:
> 282 + return
> needed? Is that for the tests when the spawn helper stuff is disabled?

When running the catview tests, I was having tracebacks about this, apparently
_update_top_rated_content was being called before the top_rated attribute was set, so I added that workaround.

> This looks like its not really needed for the pkg:
> 735 + pkg.banner_url = ''
> 736 + pkg.title_translated = ''
> or am I misreading this?

This is needed since the _HtmlRenderer in exhibits.py is calling the following fragment:

    144 html = EXHIBIT_HTML % {
    145 'banner_url': self.exhibit.banner_url,
    146 'title': self.exhibit.title_translated,
    147 'subtitle': "",
    148 }

and since I'm passing a Mock, I need to mimic those attributes.

> One thing that is maybe nice to test is a list with a existing and a non-
> existing pkgname.

Adding a test, sounds great.

> While looking at the test_catview.py I noticed quite a bit of older code that
> wasn't ideal so I took the
> liberty to do some cleanup in lp:~mvo/software-center/test-catview-cleanup
> that is based on your branch.
> I will propose this for merging.

Sounds even better!

3028. By Natalia Bidart

Merged 5.2 in.

3029. By Natalia Bidart

Adding tweaks from the review.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for this update and for addressing the review points so quickly!

On Tue, May 22, 2012 at 12:57:20PM -0000, Natalia Bidart wrote:
> > - what do you think about moving "_pkg_available()" out into a more generic
> > place like db/utils.py ?
>
> (09:42:04 AM) nessita: mvo: is there any better/simpler way of knowing if a given pkg is available, without creating an Application? Like, can the StoreDatabase know that?
>
> (otherwise we need to pass the db to the helper and that sounds, at least to me, a bit dirty)

I think putting it into the DB is the ideal way forward, its fine to
land it as it is IMO, we can create a seperate branch that moves it
into the DB.

> > Why is
> > 281 + if self.top_rated is None:
> > 282 + return
> > needed? Is that for the tests when the spawn helper stuff is disabled?
>
> When running the catview tests, I was having tracebacks about this, apparently
> _update_top_rated_content was being called before the top_rated attribute was set, so I added that workaround.

Looking at the code I think this happens because "self.build(desktopdir)"
is called in LobbyViewGtk after the db reopen signal is connected and
the refresh-review-stats signal too. So hopefully its enough to move
"self.build(desktopdir)" up before the signal connection, could you
please check if that fixes the crash?

> > This looks like its not really needed for the pkg:
> > 735 + pkg.banner_url = ''
> > 736 + pkg.title_translated = ''
> > or am I misreading this?
>
> This is needed since the _HtmlRenderer in exhibits.py is calling the following fragment:
>
> 144 html = EXHIBIT_HTML % {
> 145 'banner_url': self.exhibit.banner_url,
> 146 'title': self.exhibit.title_translated,
> 147 'subtitle': "",
> 148 }
>
> and since I'm passing a Mock, I need to mimic those attributes.

Right, what I'm puzzled about is that those are parts of the "pkg"
Mock in line 735 not the "exhibit" Mock.

Thanks!
 Michael

3030. By Natalia Bidart

Moving the package availability calculation to StoreDatabase.

3031. By Natalia Bidart

More cleanup after review comments.

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

> > > Why is
> > > 281 + if self.top_rated is None:
> > > 282 + return
> > > needed? Is that for the tests when the spawn helper stuff is disabled?
> >
> > When running the catview tests, I was having tracebacks about this,
> apparently
> > _update_top_rated_content was being called before the top_rated attribute
> was set, so I added that workaround.
>
> Looking at the code I think this happens because "self.build(desktopdir)"
> is called in LobbyViewGtk after the db reopen signal is connected and
> the refresh-review-stats signal too. So hopefully its enough to move
> "self.build(desktopdir)" up before the signal connection, could you
> please check if that fixes the crash?

It does! Pushing that.

> > > This looks like its not really needed for the pkg:
> > > 735 + pkg.banner_url = ''
> > > 736 + pkg.title_translated = ''
> > > or am I misreading this?
> >
> > This is needed since the _HtmlRenderer in exhibits.py is calling the
> following fragment:
> >
> > 144 html = EXHIBIT_HTML % {
> > 145 'banner_url': self.exhibit.banner_url,
> > 146 'title': self.exhibit.title_translated,
> > 147 'subtitle': "",
> > 148 }
> >
> > and since I'm passing a Mock, I need to mimic those attributes.
>
> Right, what I'm puzzled about is that those are parts of the "pkg"
> Mock in line 735 not the "exhibit" Mock.

Ah, good point. I debugged a bit more and found out, like you predicted, that setting that to the pkd was indeed unnecessary (the output confused me before due to the exceptions being printed out-of-order, perhaps there are threads involved?). I removed it.

3032. By Natalia Bidart

PyFlakes fixes.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for this update and for moving the code into the DB, even nicer now. I will merge this now and it will be part of the next SRU.

review: Approve
Revision history for this message
Michael Vogt (mvo) wrote :

One thought for later that I just had was that it would be nice to have the code react to the db-reopen signal and refilter the exhibits again to update once there are new apps from e.g. the software-center-agent.

Revision history for this message
Michael Vogt (mvo) wrote :

I just noticed one regression that is a bit of a client/server interaction issue.

For the Ubuntu books exhibit the server sends:
        "package_names": "ubuntu-unleashed-2012,ubuntu-unleashed-2011,ubuntu-practical-guide,ubuntu-server-book,official-ubuntu-book\r\n",

note the \r\n at the end, so we need to either fix the server or do a strip() in the packagename.

Revision history for this message
Michael Vogt (mvo) wrote :

I filed bug #1004417 about this.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'softwarecenter/backend/channel.py'
2--- softwarecenter/backend/channel.py 2012-03-19 14:20:55 +0000
3+++ softwarecenter/backend/channel.py 2012-05-22 14:21:21 +0000
4@@ -22,7 +22,7 @@
5
6 from gettext import gettext as _
7
8-from softwarecenter.distro import get_distro
9+import softwarecenter.distro
10
11 from softwarecenter.enums import (SortMethods,
12 Icons,
13@@ -34,7 +34,7 @@
14
15 class ChannelsManager(object):
16 def __init__(self, db, **kwargs):
17- self.distro = get_distro()
18+ self.distro = softwarecenter.distro.get_distro()
19 self.db = db
20
21 # public
22@@ -154,7 +154,7 @@
23 self.installed_only = installed_only
24 self._channel_sort_mode = channel_sort_mode
25 # distro specific stuff
26- self.distro = get_distro()
27+ self.distro = softwarecenter.distro.get_distro()
28 # configure the channel
29 self._channel_display_name = self._get_display_name_for_channel(
30 channel_name, channel_origin, channel_component)
31@@ -351,7 +351,7 @@
32 return AptChannelsManager.channel_available(channelname)
33
34 if __name__ == "__main__":
35- distro = get_distro()
36+ distro = softwarecenter.distro.get_distro()
37 channel = SoftwareChannel(distro.get_distro_channel_name(),
38 None, None)
39 print(channel)
40
41=== modified file 'softwarecenter/db/application.py'
42--- softwarecenter/db/application.py 2012-05-11 13:48:04 +0000
43+++ softwarecenter/db/application.py 2012-05-22 14:21:21 +0000
44@@ -24,9 +24,10 @@
45 import os
46 import re
47
48+import softwarecenter.distro
49+
50 from gettext import gettext as _
51 from softwarecenter.backend.channel import is_channel_available
52-from softwarecenter.distro import get_distro
53 from softwarecenter.enums import PkgStates, XapianValues, Icons
54
55 from softwarecenter.paths import (APP_INSTALL_CHANNELS_PATH,
56@@ -177,7 +178,7 @@
57 self._db = db
58 self._db.connect("reopen", self._on_db_reopen)
59 self._cache = self._db._aptcache
60- self._distro = get_distro()
61+ self._distro = softwarecenter.distro.get_distro()
62 self._history = None
63 # import here (intead of global) to avoid dbus dependency
64 # in update-software-center (that imports application, but
65@@ -298,7 +299,7 @@
66 # try apt first
67 if self._pkg:
68 for origin in self._pkg.candidate.origins:
69- if (origin.origin == get_distro().get_distro_channel_name() and
70+ if (origin.origin == self._distro.get_distro_channel_name() and
71 origin.trusted and origin.component):
72 return origin.component
73 # then xapian
74@@ -639,7 +640,7 @@
75 self.emit("screenshots-available", self._screenshot_list)
76 return
77 # download it
78- distro = get_distro()
79+ distro = self._distro
80 url = distro.SCREENSHOT_JSON_URL % self._app.pkgname
81 try:
82 f = Gio.File.new_for_uri(url)
83
84=== modified file 'softwarecenter/db/database.py'
85--- softwarecenter/db/database.py 2012-05-03 20:27:00 +0000
86+++ softwarecenter/db/database.py 2012-05-22 14:21:21 +0000
87@@ -493,9 +493,10 @@
88 return popcon
89
90 def get_xapian_document(self, appname, pkgname):
91- """ Get the machting xapian document for appname, pkgname
92-
93- If no document is found, raise a IndexError
94+ """Get the machting xapian document for appname, pkgname.
95+
96+ If no document is found, raise a IndexError.
97+
98 """
99 #LOG.debug("get_xapian_document app='%s' pkg='%s'" % (appname,
100 # pkgname))
101@@ -517,9 +518,23 @@
102 raise IndexError("No app '%s' for '%s' in database" % (appname,
103 pkgname))
104
105+ def is_pkgname_known(self, pkgname):
106+ """Check if 'pkgname' is known to this database.
107+
108+ Note that even if this function returns True, it may mean that the
109+ package needs to be purchased first or is available in a
110+ not-yet-enabled source.
111+
112+ """
113+ # check cache first, then our own database
114+ return (pkgname in self._aptcache or
115+ any(self.xapiandb.postlist("AP" + pkgname)))
116+
117 def is_appname_duplicated(self, appname):
118- """Check if the given appname is stored multiple times in the db
119- This can happen for generic names like "Terminal"
120+ """Check if the given appname is stored multiple times in the db.
121+
122+ This can happen for generic names like "Terminal".
123+
124 """
125 for (i, m) in enumerate(self.xapiandb.postlist("AA" + appname)):
126 if i > 0:
127
128=== modified file 'softwarecenter/testutils.py'
129--- softwarecenter/testutils.py 2012-04-16 16:41:29 +0000
130+++ softwarecenter/testutils.py 2012-05-22 14:21:21 +0000
131@@ -22,6 +22,8 @@
132 import tempfile
133 import time
134
135+from collections import defaultdict
136+
137 from mock import Mock
138
139 m_dbus = m_polkit = m_aptd = None
140@@ -314,3 +316,57 @@
141 {u'rating': 1.5, u'package_name': u'tucan'}],
142 u'app': u'pitivi'}
143 return recommend_app_data
144+
145+
146+class ObjectWithSignals(object):
147+ """A faked object that you can connect to and emit signals."""
148+
149+ def __init__(self, *a, **kw):
150+ super(ObjectWithSignals, self).__init__()
151+ self._callbacks = defaultdict(list)
152+
153+ def connect(self, signal, callback):
154+ """Connect a signal with a callback."""
155+ self._callbacks[signal].append(callback)
156+
157+ def disconnect(self, signal, callback):
158+ """Connect a signal with a callback."""
159+ self._callbacks[signal].remove(callback)
160+ if len(self._callbacks[signal]) == 0:
161+ self._callbacks.pop(signal)
162+
163+ def disconnect_by_func(self, callback):
164+ """Disconnect 'callback' from every signal."""
165+ # do not use iteritems since we may change the dict inside the for
166+ for signal, callbacks in self._callbacks.items():
167+ if callback in callbacks:
168+ self.disconnect(signal, callback)
169+
170+ def emit(self, signal, *args, **kwargs):
171+ """Emit 'signal' passing *args, **kwargs to every callback."""
172+ for callback in self._callbacks[signal]:
173+ callback(*args, **kwargs)
174+
175+
176+class FakedCache(ObjectWithSignals, dict):
177+ """A faked cache."""
178+
179+ def __init__(self, *a, **kw):
180+ super(FakedCache, self).__init__()
181+ self.ready = False
182+
183+ def open(self):
184+ """Open this cache."""
185+ self.ready = True
186+
187+ def component_available(self, distro_codename, component):
188+ """Return whether 'component' is available in 'distro_codename'."""
189+
190+ def get_addons(self, pkgname):
191+ """Return (recommended, suggested) addons for 'pkgname'."""
192+ return ([], [])
193+
194+ def get_total_size_on_install(self, pkgname, addons_to_install,
195+ addons_to_remove, archive_suite):
196+ """Return a fake (total_download_size, total_install_size) result."""
197+ return (0, 0)
198
199=== modified file 'softwarecenter/ui/gtk3/models/appstore2.py'
200--- softwarecenter/ui/gtk3/models/appstore2.py 2012-05-22 12:19:51 +0000
201+++ softwarecenter/ui/gtk3/models/appstore2.py 2012-05-22 14:21:21 +0000
202@@ -115,9 +115,7 @@
203 self.icons = icons
204 self.icon_size = icon_size
205
206- # cache the 'missing icon' used in the treeview for apps without an
207- # icon
208- self._missing_icon = icons.load_icon(Icons.MISSING_APP, icon_size, 0)
209+ self._missing_icon = None # delay this until actually needed
210 if global_icon_cache:
211 self.icon_cache = _app_icon_cache
212 else:
213@@ -146,6 +144,14 @@
214 on_image_download_complete, pkgname)
215 image_downloader.download_file(url, icon_file_path)
216
217+ @property
218+ def missing_icon(self):
219+ # cache the 'missing icon' used in treeviews for apps without an icon
220+ if self._missing_icon is None:
221+ self._missing_icon = self.icons.load_icon(Icons.MISSING_APP,
222+ self.icon_size, 0)
223+ return self._missing_icon
224+
225 def update_availability(self, doc):
226 doc.available = None
227 doc.installed = None
228@@ -228,10 +234,10 @@
229 self.get_pkgname(doc),
230 full_icon_file_name)
231 # display the missing icon while the real one downloads
232- self.icon_cache[icon_name] = self._missing_icon
233+ self.icon_cache[icon_name] = self.missing_icon
234 except GObject.GError as e:
235 LOG.debug("get_icon returned '%s'" % e)
236- return self._missing_icon
237+ return self.missing_icon
238
239 def get_review_stats(self, doc):
240 return self.review_loader.get_review_stats(self.get_application(doc))
241
242=== modified file 'softwarecenter/ui/gtk3/views/catview_gtk.py'
243--- softwarecenter/ui/gtk3/views/catview_gtk.py 2012-03-16 16:52:54 +0000
244+++ softwarecenter/ui/gtk3/views/catview_gtk.py 2012-05-22 14:21:21 +0000
245@@ -28,9 +28,11 @@
246
247 import softwarecenter.paths
248 from softwarecenter.db.application import Application
249-from softwarecenter.enums import (NonAppVisibility,
250- SortMethods,
251- TOP_RATED_CAROUSEL_LIMIT)
252+from softwarecenter.enums import (
253+ NonAppVisibility,
254+ SortMethods,
255+ TOP_RATED_CAROUSEL_LIMIT,
256+)
257 from softwarecenter.utils import wait_for_apt_cache_ready
258 from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper
259 from softwarecenter.ui.gtk3.widgets.viewport import Viewport
260@@ -224,11 +226,15 @@
261 apps_filter, apps_limit=0):
262 CategoriesViewGtk.__init__(self, datadir, desktopdir, cache, db, icons,
263 apps_filter, apps_limit=0)
264+ self.top_rated = None
265+ self.exhibit_banner = None
266
267 # sections
268 self.departments = None
269 self.appcount = None
270
271+ self.build(desktopdir)
272+
273 # ensure that on db-reopen we refresh the whats-new titles
274 self.db.connect("reopen", self._on_db_reopen)
275
276@@ -237,9 +243,6 @@
277 self.reviews_loader.connect(
278 "refresh-review-stats-finished", self._on_refresh_review_stats)
279
280- self.build(desktopdir)
281- return
282-
283 def _on_db_reopen(self, db):
284 self._update_whats_new_content()
285
286@@ -270,7 +273,6 @@
287
288 #self._append_video_clips()
289 #self._append_top_of_the_pops
290- return
291
292 #~ def _append_top_of_the_pops(self):
293 #~ self.totp_hbox = Gtk.HBox(spacing=self.SPACING)
294@@ -337,24 +339,34 @@
295 flags=['nonapps-visible'])
296 self.emit("category-selected", cat)
297
298+ def _filter_and_set_exhibits(self, sca_client, exhibit_list):
299+ result = []
300+ # filter out those exhibits that are not available in this run
301+ for exhibit in exhibit_list:
302+ available = all(self.db.is_pkgname_known(p) for p in
303+ exhibit.package_names.split(','))
304+ if available:
305+ result.append(exhibit)
306+
307+ # its ok if result is empty, since set_exhibits() will ignore
308+ # empty lists
309+ self.exhibit_banner.set_exhibits(result)
310+
311 def _append_banner_ads(self):
312- exhibit_banner = ExhibitBanner()
313- exhibit_banner.set_exhibits([FeaturedExhibit(),
314- ])
315- exhibit_banner.connect("show-exhibits-clicked", self._on_show_exhibits)
316+ self.exhibit_banner = ExhibitBanner()
317+ self.exhibit_banner.set_exhibits([FeaturedExhibit()])
318+ self.exhibit_banner.connect(
319+ "show-exhibits-clicked", self._on_show_exhibits)
320
321 # query using the agent
322 scagent = SoftwareCenterAgent()
323- scagent.connect(
324- "exhibits", lambda sca, l: exhibit_banner.set_exhibits(l))
325+ scagent.connect("exhibits", self._filter_and_set_exhibits)
326 scagent.query_exhibits()
327
328 a = Gtk.Alignment()
329 a.set_padding(0, StockEms.SMALL, 0, 0)
330- a.add(exhibit_banner)
331-
332+ a.add(self.exhibit_banner)
333 self.vbox.pack_start(a, False, False, 0)
334- return
335
336 def _append_departments(self):
337 # set the departments section to use the label markup we have just
338
339=== modified file 'test/gtk3/test_app.py'
340--- test/gtk3/test_app.py 2012-05-16 15:52:07 +0000
341+++ test/gtk3/test_app.py 2012-05-22 14:21:21 +0000
342@@ -8,7 +8,7 @@
343
344 from mock import Mock
345
346-from testutils import get_mock_options, setup_test_env
347+from testutils import FakedCache, get_mock_options, setup_test_env
348 setup_test_env()
349
350 import softwarecenter.paths
351@@ -17,40 +17,6 @@
352 from softwarecenter.ui.gtk3 import app
353
354
355-class FakedCache(dict):
356- """A faked cache."""
357-
358- def __init__(self, *a, **kw):
359- super(FakedCache, self).__init__()
360- self._callbacks = defaultdict(list)
361- self.ready = False
362-
363- def open(self):
364- """Open this cache."""
365- self.ready = True
366-
367- def connect(self, signal, callback):
368- """Connect a signal with a callback."""
369- self._callbacks[signal].append(callback)
370-
371- def disconnect_by_func(self, callback):
372- """Disconnect 'callback' from every signal."""
373- for signal, cb in self._callbacks.iteritems():
374- if cb == callback:
375- self._callbacks[signal].remove(callback)
376- if len(self._callbacks[signal]) == 0:
377- self._callbacks.pop(signal)
378-
379- def get_addons(self, pkgname):
380- """Return (recommended, suggested) addons for 'pkgname'."""
381- return ([],[])
382-
383- def get_total_size_on_install(self,pkgname, addons_to_install,
384- addons_to_remove, archive_suite):
385- """Return a fake (total_download_size, total_install_size) result."""
386- return (0, 0)
387-
388-
389 class ParsePackagesArgsTestCase(unittest.TestCase):
390 """Test suite for the parse_packages_args helper."""
391
392@@ -109,7 +75,7 @@
393 fname = __file__
394 assert os.path.exists(fname)
395 self.assertRaises(DebFileOpenError, app.parse_packages_args, fname)
396-
397+
398
399 class ParsePackagesWithAptPrefixTestCase(ParsePackagesArgsTestCase):
400
401
402=== modified file 'test/gtk3/test_catview.py'
403--- test/gtk3/test_catview.py 2012-03-15 22:23:21 +0000
404+++ test/gtk3/test_catview.py 2012-05-22 14:21:21 +0000
405@@ -1,28 +1,42 @@
406-from gi.repository import Gtk
407 import time
408 import unittest
409+
410+from gi.repository import Gtk
411 from mock import patch, Mock
412
413 from testutils import setup_test_env
414 setup_test_env()
415
416+import softwarecenter.distro
417+import softwarecenter.paths
418+
419+from softwarecenter.db.database import StoreDatabase
420 from softwarecenter.enums import SortMethods
421-from softwarecenter.testutils import (get_test_db,
422- make_recommender_agent_recommend_me_dict)
423+from softwarecenter.testutils import (
424+ FakedCache,
425+ get_test_db,
426+ make_recommender_agent_recommend_me_dict,
427+ ObjectWithSignals,
428+)
429+from softwarecenter.ui.gtk3.views import catview_gtk
430+from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
431+from softwarecenter.ui.gtk3.widgets.containers import FramedHeaderBox
432+from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook
433+
434
435 class TestCatView(unittest.TestCase):
436
437 def setUp(self):
438+ self._cat = None
439 self.db = get_test_db()
440
441 def _on_category_selected(self, subcatview, category):
442- #print "**************", subcatview, category
443 self._cat = category
444-
445- def test_subcatview_top_rated(self):
446- from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
447+
448+ def test_top_rated(self):
449 # get the widgets we need
450 win = get_test_window_catview()
451+ self.addCleanup(win.destroy)
452 lobby = win.get_data("lobby")
453
454 # simulate review-stats refresh
455@@ -37,12 +51,11 @@
456 self.assertNotEqual(self._cat, None)
457 self.assertEqual(self._cat.name, "Top Rated")
458 self.assertEqual(self._cat.sortmode, SortMethods.BY_TOP_RATED)
459- win.destroy()
460
461- def test_subcatview_new(self):
462- from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
463+ def test_new(self):
464 # get the widgets we need
465 win = get_test_window_catview()
466+ self.addCleanup(win.destroy)
467 lobby = win.get_data("lobby")
468
469 # test db reopen triggers whats-new update
470@@ -58,9 +71,8 @@
471 # encoding is utf-8 (since r2218, see category.py)
472 self.assertEqual(self._cat.name, 'What\xe2\x80\x99s New')
473 self.assertEqual(self._cat.sortmode, SortMethods.BY_CATALOGED_TIME)
474- win.destroy()
475
476- def test_subcatview_new_no_sort_info_yet(self):
477+ def test_new_no_sort_info_yet(self):
478 # ensure that we don't show a empty "whats new" category
479 # see LP: #865985
480 from softwarecenter.testutils import get_test_db
481@@ -68,7 +80,7 @@
482 cache = db._aptcache
483 # simulate a fresh install with no catalogedtime info
484 del db._axi_values["catalogedtime"]
485-
486+
487 from softwarecenter.testutils import get_test_gtk3_icon_cache
488 icons = get_test_gtk3_icon_cache()
489
490@@ -76,15 +88,15 @@
491 apps_filter = AppFilter(db, cache)
492
493 from softwarecenter.distro import get_distro
494- import softwarecenter.paths
495- from softwarecenter.paths import APP_INSTALL_PATH
496 from softwarecenter.ui.gtk3.views.catview_gtk import LobbyViewGtk
497- view = LobbyViewGtk(softwarecenter.paths.datadir, APP_INSTALL_PATH,
498+ view = LobbyViewGtk(softwarecenter.paths.datadir,
499+ softwarecenter.paths.APP_INSTALL_PATH,
500 cache, db, icons, get_distro(), apps_filter)
501 view.show()
502
503 # gui
504 win = Gtk.Window()
505+ self.addCleanup(win.destroy)
506 win.set_size_request(800, 400)
507
508 scroll = Gtk.ScrolledWindow()
509@@ -96,82 +108,85 @@
510 self._p()
511 self.assertFalse(view.whats_new_frame.get_property("visible"))
512 self._p()
513- win.destroy()
514
515- def test_subcatview_recommended_for_you_opt_in_display(self):
516-
517- # patch the recommender UUID value to insure that we are not opted-in for this test
518- get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
519- self.addCleanup(get_recommender_opted_in_patcher.stop)
520- mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start()
521+ def test_recommended_for_you_opt_in_display(self):
522+ # patch the recommender UUID value to ensure that we are not opted-in
523+ # for this test
524+ recommender_opted_in_patcher = patch(
525+ 'softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
526+ self.addCleanup(recommender_opted_in_patcher.stop)
527+ mock_get_recommender_opted_in = recommender_opted_in_patcher.start()
528 mock_get_recommender_opted_in.return_value = False
529-
530- from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
531+
532 # get the widgets we need
533 win = get_test_window_catview()
534+ self.addCleanup(win.destroy)
535+
536 lobby = win.get_data("lobby")
537 rec_panel = lobby.recommended_for_you_panel
538 self._p()
539- from softwarecenter.ui.gtk3.widgets.containers import FramedHeaderBox
540- self.assertTrue(rec_panel.spinner_notebook.get_current_page() == FramedHeaderBox.CONTENT)
541+ self.assertEqual(rec_panel.spinner_notebook.get_current_page(),
542+ FramedHeaderBox.CONTENT)
543 self.assertTrue(rec_panel.opt_in_vbox.get_property("visible"))
544- win.destroy()
545-
546+
547 # patch out the agent query method to avoid making the actual server call
548 @patch('softwarecenter.backend.recagent.RecommenderAgent'
549 '.post_submit_profile')
550- def test_subcatview_recommended_for_you_spinner_display(self, mock_query):
551-
552- # patch the recommender UUID value to insure that we are not opted-in for this test
553- get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
554- self.addCleanup(get_recommender_opted_in_patcher.stop)
555- mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start()
556+ def test_recommended_for_you_spinner_display(self, mock_query):
557+ # patch the recommender UUID value to insure that we are not opted-in
558+ # for this test
559+ recommender_opted_in_patcher = patch(
560+ 'softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
561+ self.addCleanup(recommender_opted_in_patcher.stop)
562+ mock_get_recommender_opted_in = recommender_opted_in_patcher.start()
563 mock_get_recommender_opted_in.return_value = False
564-
565- from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
566+
567 # get the widgets we need
568 win = get_test_window_catview()
569+ self.addCleanup(win.destroy)
570 lobby = win.get_data("lobby")
571 rec_panel = lobby.recommended_for_you_panel
572 self._p()
573- # click the opt-in button to initiate the process, this will show the spinner
574+ # click the opt-in button to initiate the process,
575+ # this will show the spinner
576 rec_panel.opt_in_button.emit('clicked')
577 self._p()
578- from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook
579- self.assertTrue(rec_panel.spinner_notebook.get_current_page() == SpinnerNotebook.SPINNER_PAGE)
580+ self.assertEqual(rec_panel.spinner_notebook.get_current_page(),
581+ SpinnerNotebook.SPINNER_PAGE)
582 self.assertTrue(rec_panel.opt_in_vbox.get_property("visible"))
583- win.destroy()
584
585 # patch out the agent query method to avoid making the actual server call
586 @patch('softwarecenter.backend.recagent.RecommenderAgent'
587 '.post_submit_profile')
588- def test_subcatview_recommended_for_you_display_recommendations(self, mock_query):
589-
590- # patch the recommender UUID value to insure that we are not opted-in for this test
591- get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
592- self.addCleanup(get_recommender_opted_in_patcher.stop)
593- mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start()
594+ def test_recommended_for_you_display_recommendations(self,
595+ mock_query):
596+ # patch the recommender UUID value to insure that we are not opted-in
597+ # for this test
598+ recommender_opted_in_patcher = patch(
599+ 'softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
600+ self.addCleanup(recommender_opted_in_patcher.stop)
601+ mock_get_recommender_opted_in = recommender_opted_in_patcher.start()
602 mock_get_recommender_opted_in.return_value = False
603-
604- from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
605+
606 # get the widgets we need
607 win = get_test_window_catview()
608+ self.addCleanup(win.destroy)
609 lobby = win.get_data("lobby")
610 rec_panel = lobby.recommended_for_you_panel
611 self._p()
612- # click the opt-in button to initiate the process, this will show the spinner
613+ # click the opt-in button to initiate the process,
614+ # this will show the spinner
615 rec_panel.opt_in_button.emit('clicked')
616 self._p()
617 rec_panel._update_recommended_for_you_content()
618 self._p()
619 # we fake the callback from the agent here
620- lobby.recommended_for_you_panel.recommended_for_you_cat._recommend_me_result(
621- None,
622- make_recommender_agent_recommend_me_dict())
623- self.assertNotEqual(
624- lobby.recommended_for_you_panel.recommended_for_you_cat.get_documents(self.db), [])
625- from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook
626- self.assertTrue(rec_panel.spinner_notebook.get_current_page() == SpinnerNotebook.CONTENT_PAGE)
627+ for_you = lobby.recommended_for_you_panel.recommended_for_you_cat
628+ for_you._recommend_me_result(None,
629+ make_recommender_agent_recommend_me_dict())
630+ self.assertNotEqual(for_you.get_documents(self.db), [])
631+ self.assertEqual(rec_panel.spinner_notebook.get_current_page(),
632+ SpinnerNotebook.CONTENT_PAGE)
633 self._p()
634 # test clicking recommended_for_you More button
635 lobby.connect("category-selected", self._on_category_selected)
636@@ -179,49 +194,51 @@
637 self._p()
638 self.assertNotEqual(self._cat, None)
639 self.assertEqual(self._cat.name, "Recommended For You")
640- win.destroy()
641-
642+
643 # patch out the agent query method to avoid making the actual server call
644 @patch('softwarecenter.backend.recagent.RecommenderAgent'
645 '.query_recommend_me')
646- def test_subcatview_recommended_for_you_display_recommendations_not_opted_in(self, mock_query):
647-
648- # patch the recommender UUID value to insure that we are not opted-in for this test
649- get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
650- self.addCleanup(get_recommender_opted_in_patcher.stop)
651- mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start()
652+ def test_recommended_for_you_display_recommendations_not_opted_in(self,
653+ mock_query):
654+ # patch the recommender UUID value to insure that we are not opted-in
655+ # for this test
656+ recommender_opted_in_patcher = patch(
657+ 'softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
658+ self.addCleanup(recommender_opted_in_patcher.stop)
659+ mock_get_recommender_opted_in = recommender_opted_in_patcher.start()
660 mock_get_recommender_opted_in.return_value = False
661-
662- from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
663+
664 # get the widgets we need
665 win = get_test_window_catview()
666+ self.addCleanup(win.destroy)
667 # we want to work in the "subcat" view
668 notebook = win.get_child()
669 notebook.next_page()
670-
671+
672 subcat_view = win.get_data("subcat")
673 self._p()
674- self.assertFalse(subcat_view.recommended_for_you_in_cat.get_property("visible"))
675- win.destroy()
676-
677+ self.assertFalse(subcat_view.recommended_for_you_in_cat.get_property(
678+ "visible"))
679+
680 # patch out the agent query method to avoid making the actual server call
681 @patch('softwarecenter.backend.recagent.RecommenderAgent'
682 '.query_recommend_me')
683- def test_subcatview_recommended_for_you_display_recommendations_opted_in(self, mock_query):
684-
685- # patch the recommender UUID value to insure that we are not opted-in for this test
686- get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
687- self.addCleanup(get_recommender_opted_in_patcher.stop)
688- mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start()
689+ def test_recommended_for_you_display_recommendations_opted_in(self, mock_query):
690+ # patch the recommender UUID value to insure that we are not opted-in
691+ # for this test
692+ recommender_opted_in_patcher = patch(
693+ 'softwarecenter.backend.recagent.RecommenderAgent.is_opted_in')
694+ self.addCleanup(recommender_opted_in_patcher.stop)
695+ mock_get_recommender_opted_in = recommender_opted_in_patcher.start()
696 mock_get_recommender_opted_in.return_value = True
697-
698- from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview
699+
700 # get the widgets we need
701 win = get_test_window_catview()
702+ self.addCleanup(win.destroy)
703 # we want to work in the "subcat" view
704 notebook = win.get_child()
705 notebook.next_page()
706-
707+
708 subcat_view = win.get_data("subcat")
709 rec_cat_panel = subcat_view.recommended_for_you_in_cat
710 self._p()
711@@ -231,18 +248,21 @@
712 rec_cat_panel.recommended_for_you_cat._recommend_me_result(
713 None,
714 make_recommender_agent_recommend_me_dict())
715- result_docs = rec_cat_panel.recommended_for_you_cat.get_documents(self.db)
716+ result_docs = rec_cat_panel.recommended_for_you_cat.get_documents(
717+ self.db)
718 self.assertNotEqual(result_docs, [])
719- # check that we are getting the correct number of results, corresponding
720- # to the following Internet items:
721+ # check that we are getting the correct number of results,
722+ # corresponding to the following Internet items:
723 # Mangler, Midori, Midori Private Browsing, Psi
724 self.assertTrue(len(result_docs) == 4)
725- from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook
726- self.assertTrue(rec_cat_panel.spinner_notebook.get_current_page() == SpinnerNotebook.CONTENT_PAGE)
727+ self.assertEqual(rec_cat_panel.spinner_notebook.get_current_page(),
728+ SpinnerNotebook.CONTENT_PAGE)
729 # check that the tiles themselves are visible
730 self._p()
731- self.assertTrue(rec_cat_panel.recommended_for_you_content.get_property("visible"))
732- self.assertTrue(rec_cat_panel.recommended_for_you_content.get_children()[0].title.get_property("visible"))
733+ self.assertTrue(rec_cat_panel.recommended_for_you_content.get_property(
734+ "visible"))
735+ self.assertTrue(rec_cat_panel.recommended_for_you_content.get_children(
736+ )[0].title.get_property("visible"))
737 self._p()
738 # test clicking recommended_for_you More button
739 subcat_view.connect("category-selected", self._on_category_selected)
740@@ -250,7 +270,6 @@
741 self._p()
742 self.assertNotEqual(self._cat, None)
743 self.assertEqual(self._cat.name, "Recommended For You in Internet")
744- win.destroy()
745
746 def _p(self):
747 for i in range(5):
748@@ -259,6 +278,89 @@
749 Gtk.main_iteration()
750
751
752+class ExhibitsTestCase(unittest.TestCase):
753+ """The test suite for the exhibits carousel."""
754+
755+ def setUp(self):
756+ self.datadir = softwarecenter.paths.datadir
757+ self.desktopdir = softwarecenter.paths.APP_INSTALL_PATH
758+ self.cache = FakedCache()
759+ self.db = StoreDatabase(cache=self.cache)
760+ self.lobby = catview_gtk.LobbyViewGtk(datadir=self.datadir,
761+ desktopdir=self.desktopdir, cache=self.cache, db=self.db,
762+ icons=None, apps_filter=None)
763+ self.addCleanup(self.lobby.destroy)
764+
765+ def _get_banner_from_lobby(self):
766+ return self.lobby.vbox.get_children()[-1].get_child()
767+
768+ def test_featured_exhibit_by_default(self):
769+ """Show the featured exhibit before querying the remote service."""
770+ self.lobby._append_banner_ads()
771+
772+ banner = self._get_banner_from_lobby()
773+ self.assertEqual(1, len(banner.exhibits))
774+ self.assertIsInstance(banner.exhibits[0], catview_gtk.FeaturedExhibit)
775+
776+ def test_no_exhibit_if_not_available(self):
777+ """The exhibit should not be shown if the package is not available."""
778+ exhibit = Mock()
779+ exhibit.package_names = u'foobarbaz'
780+
781+ sca = ObjectWithSignals()
782+ sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
783+
784+ with patch.object(catview_gtk, 'SoftwareCenterAgent', lambda: sca):
785+ self.lobby._append_banner_ads()
786+
787+ banner = self._get_banner_from_lobby()
788+ self.assertEqual(1, len(banner.exhibits))
789+ self.assertIsInstance(banner.exhibits[0], catview_gtk.FeaturedExhibit)
790+
791+ def test_exhibit_if_available(self):
792+ """The exhibit should be shown if the package is available."""
793+ exhibit = Mock()
794+ exhibit.package_names = u'foobarbaz'
795+ exhibit.banner_url = 'banner'
796+ exhibit.title_translated = ''
797+
798+ self.cache[u'foobarbaz'] = Mock()
799+
800+ sca = ObjectWithSignals()
801+ sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit])
802+
803+ with patch.object(catview_gtk, 'SoftwareCenterAgent', lambda: sca):
804+ self.lobby._append_banner_ads()
805+
806+ banner = self._get_banner_from_lobby()
807+ self.assertEqual(1, len(banner.exhibits))
808+ self.assertIs(banner.exhibits[0], exhibit)
809+
810+ def test_exhibit_if_mixed_availability(self):
811+ """The exhibit should be shown even if some are not available."""
812+ # available exhibit
813+ exhibit = Mock()
814+ exhibit.package_names = u'foobarbaz'
815+ exhibit.banner_url = 'banner'
816+ exhibit.title_translated = ''
817+
818+ self.cache[u'foobarbaz'] = Mock()
819+
820+ # not available exhibit
821+ other = Mock()
822+ other.package_names = u'not-there'
823+
824+ sca = ObjectWithSignals()
825+ sca.query_exhibits = lambda: sca.emit('exhibits', sca,
826+ [exhibit, other])
827+
828+ with patch.object(catview_gtk, 'SoftwareCenterAgent', lambda: sca):
829+ self.lobby._append_banner_ads()
830+
831+ banner = self._get_banner_from_lobby()
832+ self.assertEqual(1, len(banner.exhibits))
833+ self.assertIs(banner.exhibits[0], exhibit)
834+
835
836 if __name__ == "__main__":
837 #import logging
838
839=== modified file 'test/test_database.py'
840--- test/test_database.py 2012-04-16 17:44:04 +0000
841+++ test/test_database.py 2012-05-22 14:21:21 +0000
842@@ -424,7 +424,7 @@
843 self.assertTrue(len(details.tags) > 2)
844
845 def test_app_enquire(self):
846- db = StoreDatabase("/var/cache/software-center/xapian", self.cache)
847+ db = StoreDatabase(cache=self.cache)
848 db.open()
849 # test the AppEnquire engine
850 enquirer = AppEnquire(self.cache, db)
851@@ -433,6 +433,12 @@
852 self.assertTrue(len(enquirer.get_docids()) > 0)
853 # FIXME: test more of the interface
854
855+ def test_is_pkgname_known(self):
856+ db = StoreDatabase(cache=self.cache)
857+ db.open()
858+ self.assertTrue(db.is_pkgname_known("apt"))
859+ self.assertFalse(db.is_pkgname_known("i+am-not-a-pkg"))
860+
861
862 class UtilsTestCase(unittest.TestCase):
863

Subscribers

People subscribed via source and target branches