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