Merge lp:~dobey/ubuntu-sso-client/update-4-0 into lp:ubuntu-sso-client/stable-4-0

Proposed by dobey
Status: Merged
Approved by: dobey
Approved revision: no longer in the source branch.
Merged at revision: 960
Proposed branch: lp:~dobey/ubuntu-sso-client/update-4-0
Merge into: lp:ubuntu-sso-client/stable-4-0
Diff against target: 1801 lines (+311/-618)
50 files modified
bin/ubuntu-sso-login (+1/-1)
setup.py (+2/-4)
ubuntu_sso/account.py (+5/-5)
ubuntu_sso/gtk/gui.py (+23/-5)
ubuntu_sso/gtk/tests/test_gui.py (+14/-5)
ubuntu_sso/keyring/tests/test_linux.py (+1/-1)
ubuntu_sso/logger.py (+2/-2)
ubuntu_sso/main/__init__.py (+1/-1)
ubuntu_sso/main/linux.py (+6/-1)
ubuntu_sso/main/tests/test_linux.py (+59/-0)
ubuntu_sso/networkstate/darwin.py (+2/-2)
ubuntu_sso/networkstate/linux.py (+3/-3)
ubuntu_sso/networkstate/windows.py (+1/-1)
ubuntu_sso/qt/__init__.py (+1/-1)
ubuntu_sso/qt/current_user_sign_in_page.py (+1/-3)
ubuntu_sso/qt/main/__init__.py (+2/-0)
ubuntu_sso/qt/main/tests/test_main.py (+1/-0)
ubuntu_sso/qt/proxy_dialog.py (+1/-1)
ubuntu_sso/qt/setup_account_page.py (+2/-3)
ubuntu_sso/qt/sso_wizard_page.py (+2/-2)
ubuntu_sso/qt/tests/test_arrow.py (+1/-1)
ubuntu_sso/qt/tests/test_common.py (+3/-3)
ubuntu_sso/qt/tests/test_current_user_sign_in_page.py (+1/-1)
ubuntu_sso/qt/tests/test_setup_account.py (+1/-2)
ubuntu_sso/tests/test_account.py (+1/-1)
ubuntu_sso/tests/test_credentials.py (+2/-1)
ubuntu_sso/utils/__init__.py (+1/-1)
ubuntu_sso/utils/ipc.py (+3/-3)
ubuntu_sso/utils/runner/glib.py (+1/-1)
ubuntu_sso/utils/runner/tests/test_glib.py (+1/-1)
ubuntu_sso/utils/runner/tests/test_qt.py (+2/-2)
ubuntu_sso/utils/runner/tx.py (+2/-2)
ubuntu_sso/utils/tcpactivation.py (+5/-1)
ubuntu_sso/utils/tests/test_common.py (+1/-0)
ubuntu_sso/utils/tests/test_tcpactivation.py (+49/-50)
ubuntu_sso/utils/txsecrets.py (+1/-1)
ubuntu_sso/utils/webclient/common.py (+2/-2)
ubuntu_sso/utils/webclient/gsettings.py (+9/-2)
ubuntu_sso/utils/webclient/libsoup.py (+1/-1)
ubuntu_sso/utils/webclient/qtnetwork.py (+3/-3)
ubuntu_sso/utils/webclient/restful.py (+12/-1)
ubuntu_sso/utils/webclient/tests/test_gsettings.py (+32/-0)
ubuntu_sso/utils/webclient/tests/test_restful.py (+44/-0)
ubuntu_sso/utils/webclient/tests/test_webclient.py (+2/-2)
ubuntu_sso/utils/webclient/timestamp.py (+1/-1)
ubuntu_sso/xdg_base_directory/__init__.py (+0/-104)
ubuntu_sso/xdg_base_directory/tests/__init__.py (+0/-29)
ubuntu_sso/xdg_base_directory/tests/test_common.py (+0/-91)
ubuntu_sso/xdg_base_directory/tests/test_windows.py (+0/-157)
ubuntu_sso/xdg_base_directory/windows.py (+0/-113)
To merge this branch: bzr merge lp:~dobey/ubuntu-sso-client/update-4-0
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+112380@code.launchpad.net

Commit message

[Mike McCracken]

    - Delay importing twisted.internet.reactor to avoid creating default reactor when we need qt4reactor. (LP: #1017672)
    - Correctly bring main sso client window to the front on OS X. (LP: #1012837)
    - Fixes bug with loading incorrect plug-ins caused by ignoring qt.conf. (LP: #1010102)

[Alejandro Cura]

    - Account for g_variant_print type annotations (LP: #1007109).

[Brian Curtin]

    - Move from iteritems/itervalues to items/values for Python 3 support.
    - Use Python 3 style exception handling syntax.

[Rodney Dawes]

    - Remove the xdg_base_directory module as it duplicates dirspec now.
    - Disable the color validation for warning labels due to new GTK+ breaking it.
    - Ensure we are using strict ssl with the system ca-certificates.crt file.
    - Trap DBusException when getting the SessionBus, and add a test case for it.

[Manuel de la Pena]

    - Changed tests so that they use u1-dev-tools better and do not get blocked in the clean up (LP: #1009071).

[Natalia Bidart]

    - Added a little more logging to the webclient when doing REST calls.

[Roberto Alsina]

    - Remove back buttons in signin/signup pages (Fixes LP: #1009107).

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) wrote :

+1 looks good

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :

Voting does not meet specified criteria. Required: Approve >= 1, Disapprove == 0, Needs Fixing == 0, Needs Information == 0, Resubmit == 0, Pending == 0. Got: 1 Approve, 1 Pending.

Revision history for this message
Alejandro J. Cura (alecu) wrote :

+1 looks just like trunk

review: Approve
960. By Brian Curtin

[Mike McCracken]

    - Delay importing twisted.internet.reactor to avoid creating default reactor when we need qt4reactor. (LP: #1017672)
    - Correctly bring main sso client window to the front on OS X. (LP: #1012837)
    - Fixes bug with loading incorrect plug-ins caused by ignoring qt.conf. (LP: #1010102)

[Alejandro Cura]

    - Account for g_variant_print type annotations (LP: #1007109).

[Brian Curtin]

    - Move from iteritems/itervalues to items/values for Python 3 support.
    - Use Python 3 style exception handling syntax.

[Rodney Dawes]

    - Remove the xdg_base_directory module as it duplicates dirspec now.
    - Disable the color validation for warning labels due to new GTK+ breaking it.
    - Ensure we are using strict ssl with the system ca-certificates.crt file.
    - Trap DBusException when getting the SessionBus, and add a test case for it.

[Manuel de la Pena]

    - Changed tests so that they use u1-dev-tools better and do not get blocked in the clean up (LP: #1009071).

[Natalia Bidart]

    - Added a little more logging to the webclient when doing REST calls.

[Roberto Alsina]

    - Remove back buttons in signin/signup pages (Fixes LP: #1009107).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/ubuntu-sso-login'
--- bin/ubuntu-sso-login 2012-05-18 14:25:49 +0000
+++ bin/ubuntu-sso-login 2012-06-27 15:30:29 +0000
@@ -57,7 +57,7 @@
57 # need to create the QApplication before installing the reactor57 # need to create the QApplication before installing the reactor
58 if os.environ.get('TESTABILITY', False):58 if os.environ.get('TESTABILITY', False):
59 sys.argv.append('-testability')59 sys.argv.append('-testability')
60 QtGui.QApplication(sys.argv)60 app = QtGui.QApplication(sys.argv)
6161
62 # pylint: disable=F040162 # pylint: disable=F0401
63 import qt4reactor63 import qt4reactor
6464
=== modified file 'setup.py'
--- setup.py 2012-06-05 19:55:04 +0000
+++ setup.py 2012-06-27 15:30:29 +0000
@@ -301,8 +301,8 @@
301 author='Natalia Bidart',301 author='Natalia Bidart',
302 author_email='natalia.bidart@canonical.com',302 author_email='natalia.bidart@canonical.com',
303 description='Ubuntu Single Sign-On client',303 description='Ubuntu Single Sign-On client',
304 long_description='Desktop service to allow applications to sign in' \304 long_description=('Desktop service to allow applications to sign in'
305 'to Ubuntu services via SSO',305 'to Ubuntu services via SSO'),
306 url='https://launchpad.net/ubuntu-sso-client',306 url='https://launchpad.net/ubuntu-sso-client',
307 extra_path='ubuntu-sso-client',307 extra_path='ubuntu-sso-client',
308 data_files=data_files,308 data_files=data_files,
@@ -325,8 +325,6 @@
325 'ubuntu_sso.utils.runner.tests',325 'ubuntu_sso.utils.runner.tests',
326 'ubuntu_sso.utils.webclient',326 'ubuntu_sso.utils.webclient',
327 'ubuntu_sso.utils.webclient.tests',327 'ubuntu_sso.utils.webclient.tests',
328 'ubuntu_sso.xdg_base_directory',
329 'ubuntu_sso.xdg_base_directory.tests',
330 ],328 ],
331 cmdclass=cmdclass,329 cmdclass=cmdclass,
332 **extra)330 **extra)
333331
=== modified file 'ubuntu_sso/account.py'
--- ubuntu_sso/account.py 2012-05-29 16:56:45 +0000
+++ ubuntu_sso/account.py 2012-06-27 15:30:29 +0000
@@ -110,7 +110,7 @@
110 def _format_webservice_errors(self, errdict):110 def _format_webservice_errors(self, errdict):
111 """Turn each list of strings in the errdict into a LF separated str."""111 """Turn each list of strings in the errdict into a LF separated str."""
112 result = {}112 result = {}
113 for key, val in errdict.iteritems():113 for key, val in errdict.items():
114 # workaround until bug #624955 is solved114 # workaround until bug #624955 is solved
115 if isinstance(val, basestring):115 if isinstance(val, basestring):
116 result[key] = val116 result[key] = val
@@ -197,7 +197,7 @@
197 finally:197 finally:
198 restful_client.shutdown()198 restful_client.shutdown()
199199
200 logger.debug('login: authentication successful! consumer_key: %r, ' \200 logger.debug('login: authentication successful! consumer_key: %r, '
201 'token_name: %r', credentials['consumer_key'], token_name)201 'token_name: %r', credentials['consumer_key'], token_name)
202 defer.returnValue(credentials)202 defer.returnValue(credentials)
203203
@@ -212,7 +212,7 @@
212 finally:212 finally:
213 restful_client.shutdown()213 restful_client.shutdown()
214 key = 'preferred_email'214 key = 'preferred_email'
215 result = key in me_info and me_info[key] != None215 result = key in me_info and me_info[key] is not None
216216
217 logger.info('is_validated: consumer_key: %r, result: %r.',217 logger.info('is_validated: consumer_key: %r, result: %r.',
218 token['consumer_key'], result)218 token['consumer_key'], result)
@@ -249,7 +249,7 @@
249 try:249 try:
250 operation = u"registration.request_password_reset_token"250 operation = u"registration.request_password_reset_token"
251 result = yield restful_client.restcall(operation, email=email)251 result = yield restful_client.restcall(operation, email=email)
252 except WebClientError, e:252 except WebClientError as e:
253 logger.exception('request_password_reset_token failed with:')253 logger.exception('request_password_reset_token failed with:')
254 raise ResetPasswordTokenError(e[1].split('\n')[0])254 raise ResetPasswordTokenError(e[1].split('\n')[0])
255 finally:255 finally:
@@ -275,7 +275,7 @@
275 u"registration.set_new_password",275 u"registration.set_new_password",
276 email=email, token=token,276 email=email, token=token,
277 new_password=new_password)277 new_password=new_password)
278 except WebClientError, e:278 except WebClientError as e:
279 logger.exception('set_new_password failed with:')279 logger.exception('set_new_password failed with:')
280 raise NewPasswordError(e[1].split('\n')[0])280 raise NewPasswordError(e[1].split('\n')[0])
281 finally:281 finally:
282282
=== modified file 'ubuntu_sso/gtk/gui.py'
--- ubuntu_sso/gtk/gui.py 2012-04-11 16:38:28 +0000
+++ ubuntu_sso/gtk/gui.py 2012-06-27 15:30:29 +0000
@@ -109,6 +109,12 @@
109LARGE_MARKUP = u'<span size="x-large">%s</span>'109LARGE_MARKUP = u'<span size="x-large">%s</span>'
110110
111111
112# SSL properties and certs location
113STRICT_SSL_PROP = 'ssl-strict'
114CERTS_FILE_PROP = 'ssl-ca-file'
115CA_CERT_FILE = '/etc/ssl/certs/ca-certificates.crt'
116
117
112def log_call(f):118def log_call(f):
113 """Decorator to log call funtions."""119 """Decorator to log call funtions."""
114120
@@ -356,7 +362,7 @@
356 if app_name == self.app_name:362 if app_name == self.app_name:
357 result = f(app_name, *args, **kwargs)363 result = f(app_name, *args, **kwargs)
358 else:364 else:
359 logger.info('%s: ignoring call since received app_name '\365 logger.info('%s: ignoring call since received app_name '
360 '%r (expected %r)',366 '%r (expected %r)',
361 f.__name__, app_name, self.app_name)367 f.__name__, app_name, self.app_name)
362 return result368 return result
@@ -365,7 +371,7 @@
365371
366 def _setup_signals(self):372 def _setup_signals(self):
367 """Bind signals to callbacks to be able to test the pages."""373 """Bind signals to callbacks to be able to test the pages."""
368 for signal, method in self._signals.iteritems():374 for signal, method in self._signals.items():
369 actual = self._signals_receivers.get(signal)375 actual = self._signals_receivers.get(signal)
370 if actual is not None:376 if actual is not None:
371 msg = 'Signal %r is already connected with %r.'377 msg = 'Signal %r is already connected with %r.'
@@ -703,7 +709,7 @@
703 if os.path.exists(self._captcha_filename):709 if os.path.exists(self._captcha_filename):
704 os.remove(self._captcha_filename)710 os.remove(self._captcha_filename)
705711
706 for signal, match in self._signals_receivers.iteritems():712 for signal, match in self._signals_receivers.items():
707 self.backend.disconnect_from_signal(signal, match)713 self.backend.disconnect_from_signal(signal, match)
708714
709 # hide the main window715 # hide the main window
@@ -790,7 +796,7 @@
790 self.user_email = email1796 self.user_email = email1
791 self.user_password = password1797 self.user_password = password1
792798
793 logger.info('Calling register_user with email %r, password <hidden>,' \799 logger.info('Calling register_user with email %r, password <hidden>,'
794 ' name %r, captcha_id %r and captcha_solution %r.', email1,800 ' name %r, captcha_id %r and captcha_solution %r.', email1,
795 name, self._captcha_id, captcha_solution)801 name, self._captcha_id, captcha_solution)
796802
@@ -943,7 +949,7 @@
943 return949 return
944950
945 email = self.reset_email_entry.get_text()951 email = self.reset_email_entry.get_text()
946 logger.info('Calling set_new_password with email %r, token %r and ' \952 logger.info('Calling set_new_password with email %r, token %r and '
947 'new password: <hidden>.', email, token)953 'new password: <hidden>.', email, token)
948 f = self.backend.set_new_password954 f = self.backend.set_new_password
949 error_handler = partial(self._handle_error, f,955 error_handler = partial(self._handle_error, f,
@@ -953,11 +959,23 @@
953959
954 self._set_current_page(self.processing_vbox)960 self._set_current_page(self.processing_vbox)
955961
962 def _webkit_init_ssl(self):
963 """Set the WebKit ssl strictness."""
964 # delay the import of webkit to be able to build without it
965 from gi.repository import WebKit # pylint: disable=E0611
966
967 # Set the Soup session to be strict and use system CA certs
968 session = WebKit.get_default_session()
969 session.set_property(STRICT_SSL_PROP, True)
970 session.set_property(CERTS_FILE_PROP, CA_CERT_FILE)
971
956 def _add_webkit_browser(self):972 def _add_webkit_browser(self):
957 """Add the webkit browser for the t&c."""973 """Add the webkit browser for the t&c."""
958 # delay the import of webkit to be able to build without it974 # delay the import of webkit to be able to build without it
959 from gi.repository import WebKit # pylint: disable=E0611975 from gi.repository import WebKit # pylint: disable=E0611
960976
977 self._webkit_init_ssl()
978
961 browser = WebKit.WebView()979 browser = WebKit.WebView()
962980
963 browser.connect('notify::load-status',981 browser.connect('notify::load-status',
964982
=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
--- ubuntu_sso/gtk/tests/test_gui.py 2012-04-11 16:38:28 +0000
+++ ubuntu_sso/gtk/tests/test_gui.py 2012-06-27 15:30:29 +0000
@@ -168,6 +168,7 @@
168 self.memento = MementoHandler()168 self.memento = MementoHandler()
169 self.memento.setLevel(logging.DEBUG)169 self.memento.setLevel(logging.DEBUG)
170 gui.logger.addHandler(self.memento)170 gui.logger.addHandler(self.memento)
171 self.addCleanup(gui.logger.removeHandler, self.memento)
171172
172 def _set_called(self, *args, **kwargs):173 def _set_called(self, *args, **kwargs):
173 """Set _called to True."""174 """Set _called to True."""
@@ -446,9 +447,10 @@
446 self.assertEqual(actual, message)447 self.assertEqual(actual, message)
447448
448 # content color is correct449 # content color is correct
449 expected = gui.WARNING_TEXT_COLOR450 # FIXME - New GTK+ 3.5 breaks this check - see bug #1014772
450 actual = label.get_style().fg[Gtk.StateFlags.NORMAL]451 # expected = gui.WARNING_TEXT_COLOR
451 self.assert_color_equal(expected, actual)452 # actual = label.get_style().fg[Gtk.StateFlags.NORMAL]
453 # self.assert_color_equal(expected, actual)
452454
453 def assert_correct_entry_warning(self, entry, message):455 def assert_correct_entry_warning(self, entry, message):
454 """Check that a warning is shown displaying 'message'."""456 """Check that a warning is shown displaying 'message'."""
@@ -911,6 +913,7 @@
911 def setUp(self):913 def setUp(self):
912 yield super(TermsAndConditionsBrowserTestCase, self).setUp()914 yield super(TermsAndConditionsBrowserTestCase, self).setUp()
913 self.patch(WebKit, 'WebView', FakedEmbeddedBrowser)915 self.patch(WebKit, 'WebView', FakedEmbeddedBrowser)
916 self.patch(self.ui, '_webkit_init_ssl', self._set_called)
914917
915 self.ui.tc_button.clicked()918 self.ui.tc_button.clicked()
916 self.addCleanup(self.ui.tc_browser_vbox.hide)919 self.addCleanup(self.ui.tc_browser_vbox.hide)
@@ -919,6 +922,12 @@
919 assert len(children) == 1922 assert len(children) == 1
920 self.browser = children[0]923 self.browser = children[0]
921924
925 def test_ssl_validation(self):
926 """The browser is set to validate SSL."""
927 self.assertEqual(self._called, ((), {}),
928 '_webkit_init_ssl should be called when creating a '
929 'webkit browser.')
930
922 def test_tc_browser_is_created_when_tc_page_is_shown(self):931 def test_tc_browser_is_created_when_tc_page_is_shown(self):
923 """The browser is created when the TC button is clicked."""932 """The browser is created when the TC button is clicked."""
924 self.ui.on_tc_browser_notify_load_status(self.browser)933 self.ui.on_tc_browser_notify_load_status(self.browser)
@@ -1983,7 +1992,7 @@
1983 """Callbacks are connected to signals of interest."""1992 """Callbacks are connected to signals of interest."""
1984 msg1 = 'callback %r for signal %r must be added to the backend.'1993 msg1 = 'callback %r for signal %r must be added to the backend.'
1985 msg2 = 'callback %r for signal %r must be added to the ui log.'1994 msg2 = 'callback %r for signal %r must be added to the ui log.'
1986 for signal, method in self.ui._signals.iteritems():1995 for signal, method in self.ui._signals.items():
1987 actual = self.ui.backend.callbacks.get(signal)1996 actual = self.ui.backend.callbacks.get(signal)
1988 self.assertEqual([method], actual, msg1 % (method, signal))1997 self.assertEqual([method], actual, msg1 % (method, signal))
1989 actual = self.ui._signals_receivers.get(signal)1998 actual = self.ui._signals_receivers.get(signal)
@@ -1992,7 +2001,7 @@
1992 def test_callbacks_only_log_when_app_name_doesnt_match(self):2001 def test_callbacks_only_log_when_app_name_doesnt_match(self):
1993 """Callbacks do nothing but logging when app_name doesn't match."""2002 """Callbacks do nothing but logging when app_name doesn't match."""
1994 mismatch_app_name = self.ui.app_name * 22003 mismatch_app_name = self.ui.app_name * 2
1995 for method in self.ui._signals.itervalues():2004 for method in self.ui._signals.values():
1996 msgs = ('ignoring', method.__name__, repr(mismatch_app_name))2005 msgs = ('ignoring', method.__name__, repr(mismatch_app_name))
1997 method(mismatch_app_name, 'dummy')2006 method(mismatch_app_name, 'dummy')
1998 self.assertTrue(self.memento.check(logging.INFO, *msgs))2007 self.assertTrue(self.memento.check(logging.INFO, *msgs))
19992008
=== modified file 'ubuntu_sso/keyring/tests/test_linux.py'
--- ubuntu_sso/keyring/tests/test_linux.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/keyring/tests/test_linux.py 2012-06-27 15:30:29 +0000
@@ -137,7 +137,7 @@
137137
138 def get_mock_service(self):138 def get_mock_service(self):
139 """Create only one instance of the mock service per test."""139 """Create only one instance of the mock service per test."""
140 if self.mock_service == None:140 if self.mock_service is None:
141 self.mock_service = MockSecretService()141 self.mock_service = MockSecretService()
142 return self.mock_service142 return self.mock_service
143143
144144
=== modified file 'ubuntu_sso/logger.py'
--- ubuntu_sso/logger.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/logger.py 2012-06-27 15:30:29 +0000
@@ -37,11 +37,11 @@
37import os37import os
38import sys38import sys
3939
40from dirspec.basedir import xdg_cache_home
41from dirspec.utils import unicode_path
40from functools import wraps42from functools import wraps
41from logging.handlers import RotatingFileHandler43from logging.handlers import RotatingFileHandler
4244
43from ubuntu_sso.xdg_base_directory import unicode_path, xdg_cache_home
44
45LOGFOLDER = os.path.join(xdg_cache_home, 'sso')45LOGFOLDER = os.path.join(xdg_cache_home, 'sso')
46# create log folder if it doesn't exists46# create log folder if it doesn't exists
47if not os.path.exists(unicode_path(LOGFOLDER)):47if not os.path.exists(unicode_path(LOGFOLDER)):
4848
=== modified file 'ubuntu_sso/main/__init__.py'
--- ubuntu_sso/main/__init__.py 2012-05-21 14:28:14 +0000
+++ ubuntu_sso/main/__init__.py 2012-06-27 15:30:29 +0000
@@ -348,7 +348,7 @@
348348
349 def _parse_args(self, args):349 def _parse_args(self, args):
350 """Retrieve values from the generic param 'args'."""350 """Retrieve values from the generic param 'args'."""
351 result = dict(i for i in args.iteritems() if i[0] in self.valid_keys)351 result = dict(i for i in args.items() if i[0] in self.valid_keys)
352 result[WINDOW_ID_KEY] = int(args.get(WINDOW_ID_KEY, 0))352 result[WINDOW_ID_KEY] = int(args.get(WINDOW_ID_KEY, 0))
353 return result353 return result
354354
355355
=== modified file 'ubuntu_sso/main/linux.py'
--- ubuntu_sso/main/linux.py 2012-04-10 10:43:48 +0000
+++ ubuntu_sso/main/linux.py 2012-06-27 15:30:29 +0000
@@ -342,10 +342,15 @@
342342
343 def __init__(self, root):343 def __init__(self, root):
344 self.root = root344 self.root = root
345 self.bus = dbus.SessionBus()
346 self.sso_login = None345 self.sso_login = None
347 self.cred_manager = None346 self.cred_manager = None
348347
348 try:
349 self.bus = dbus.SessionBus()
350 except dbus.service.DBusException as e:
351 logger.exception(e)
352 shutdown_func()
353
349 def start(self):354 def start(self):
350 """Start listening, nothing async to be done in this platform."""355 """Start listening, nothing async to be done in this platform."""
351 # Register DBus service for making sure we run only one instance356 # Register DBus service for making sure we run only one instance
352357
=== added file 'ubuntu_sso/main/tests/test_linux.py'
--- ubuntu_sso/main/tests/test_linux.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/main/tests/test_linux.py 2012-06-27 15:30:29 +0000
@@ -0,0 +1,59 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Tests for the main SSO Linux client code."""
30
31# pylint: disable=C0103
32do_tests = False
33try:
34 import dbus
35 import dbus.service
36 from ubuntu_sso.main import linux as main
37 do_tests = True
38except ImportError:
39 do_tests = False
40# pylint enable=C0103
41
42from ubuntuone.devtools.testcases import BaseTestCase, skipIf
43
44
45@skipIf(not do_tests, 'Tests only work with DBus and linux imports.')
46class LinuxProxyTestCase(BaseTestCase):
47 """Test some Linux specific cases."""
48
49 def test_dbus_missing_exists_clean(self):
50 """Test that dbus-daemon not being available gives us a clean exit."""
51 def patched(*args, **kwargs):
52 """Method to patch in for testing."""
53 raise dbus.service.DBusException(
54 'org.freedesktop.DBus.Error.NoServer')
55
56 self.patch(dbus, "SessionBus", patched)
57 self.patch(main, "shutdown_func", patched)
58 self.assertRaises(dbus.service.DBusException,
59 main.UbuntuSSOProxy, None)
060
=== modified file 'ubuntu_sso/networkstate/darwin.py'
--- ubuntu_sso/networkstate/darwin.py 2012-05-21 14:48:25 +0000
+++ ubuntu_sso/networkstate/darwin.py 2012-06-27 15:30:29 +0000
@@ -126,7 +126,7 @@
126 CFRelease(target)126 CFRelease(target)
127127
128 if not ok:128 if not ok:
129 logger.error("Error getting reachability status of '%s'" % \129 logger.error("Error getting reachability status of '%s'" %
130 HOSTNAME_TO_CHECK)130 HOSTNAME_TO_CHECK)
131 raise NetworkFailException()131 raise NetworkFailException()
132132
@@ -298,6 +298,6 @@
298 """298 """
299 try:299 try:
300 return defer.succeed(check_connected_state())300 return defer.succeed(check_connected_state())
301 except Exception, e: # pylint: disable=W0703301 except Exception as e: # pylint: disable=W0703
302 logger.exception("Exception calling check_connected_state:")302 logger.exception("Exception calling check_connected_state:")
303 return defer.fail(NetworkFailException(e))303 return defer.fail(NetworkFailException(e))
304304
=== modified file 'ubuntu_sso/networkstate/linux.py'
--- ubuntu_sso/networkstate/linux.py 2012-05-24 04:20:08 +0000
+++ ubuntu_sso/networkstate/linux.py 2012-06-27 15:30:29 +0000
@@ -79,7 +79,7 @@
79 # the user has connected in some other way79 # the user has connected in some other way
80 self.call_result_cb(ONLINE)80 self.call_result_cb(ONLINE)
81 else:81 else:
82 logger.error("Error contacting NetworkManager: %s" % \82 logger.error("Error contacting NetworkManager: %s" %
83 str(error))83 str(error))
84 self.call_result_cb(UNKNOWN)84 self.call_result_cb(UNKNOWN)
8585
@@ -107,7 +107,7 @@
107 nm_proxy.Get(NM_DBUS_INTERFACE, "State",107 nm_proxy.Get(NM_DBUS_INTERFACE, "State",
108 reply_handler=self.got_state,108 reply_handler=self.got_state,
109 error_handler=self.got_error)109 error_handler=self.got_error)
110 except Exception, e: # pylint: disable=W0703110 except Exception as e: # pylint: disable=W0703
111 self.got_error(e)111 self.got_error(e)
112112
113113
@@ -127,7 +127,7 @@
127 try:127 try:
128 network = NetworkManagerState(got_state)128 network = NetworkManagerState(got_state)
129 network.find_online_state()129 network.find_online_state()
130 except Exception, e:130 except Exception as e:
131 logger.exception('is_machine_connected failed with:')131 logger.exception('is_machine_connected failed with:')
132 d.errback(NetworkFailException(e))132 d.errback(NetworkFailException(e))
133 # pylint: enable=W0702, W0703133 # pylint: enable=W0702, W0703
134134
=== modified file 'ubuntu_sso/networkstate/windows.py'
--- ubuntu_sso/networkstate/windows.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/networkstate/windows.py 2012-06-27 15:30:29 +0000
@@ -184,7 +184,7 @@
184 flags = DWORD()184 flags = DWORD()
185 connected = wininet.InternetGetConnectedState(byref(flags), None)185 connected = wininet.InternetGetConnectedState(byref(flags), None)
186 return defer.succeed(connected == 1)186 return defer.succeed(connected == 1)
187 except Exception, e:187 except Exception as e:
188 logger.exception('is_machine_connected failed with:')188 logger.exception('is_machine_connected failed with:')
189 return defer.fail(NetworkFailException(e))189 return defer.fail(NetworkFailException(e))
190 # pylint: enable=W0703, W0702190 # pylint: enable=W0703, W0702
191191
=== modified file 'ubuntu_sso/qt/__init__.py'
--- ubuntu_sso/qt/__init__.py 2012-04-23 13:32:52 +0000
+++ ubuntu_sso/qt/__init__.py 2012-06-27 15:30:29 +0000
@@ -75,7 +75,7 @@
75 if 'errtype' in errordict:75 if 'errtype' in errordict:
76 del errordict['errtype']76 del errordict['errtype']
77 result = '\n'.join(77 result = '\n'.join(
78 [('%s: %s' % (k, v)) for k, v in errordict.iteritems()])78 [('%s: %s' % (k, v)) for k, v in errordict.items()])
79 else:79 else:
80 result = GENERIC_BACKEND_ERROR80 result = GENERIC_BACKEND_ERROR
81 logger.error('build_general_error_message with unknown error: %r',81 logger.error('build_general_error_message with unknown error: %r',
8282
=== modified file 'ubuntu_sso/qt/current_user_sign_in_page.py'
--- ubuntu_sso/qt/current_user_sign_in_page.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/qt/current_user_sign_in_page.py 2012-06-27 15:30:29 +0000
@@ -97,9 +97,7 @@
97 self.setButtonText(QtGui.QWizard.CancelButton, CANCEL_BUTTON)97 self.setButtonText(QtGui.QWizard.CancelButton, CANCEL_BUTTON)
98 # Layout without custom button 1,98 # Layout without custom button 1,
99 # without finish button99 # without finish button
100 self.wizard().setButtonLayout([100 self.wizard().setButtonLayout([])
101 QtGui.QWizard.BackButton,
102 QtGui.QWizard.Stretch])
103101
104 # Set sign_in_button as default when the page is shown.102 # Set sign_in_button as default when the page is shown.
105 self.ui.sign_in_button.setDefault(True)103 self.ui.sign_in_button.setDefault(True)
106104
=== modified file 'ubuntu_sso/qt/main/__init__.py'
--- ubuntu_sso/qt/main/__init__.py 2012-05-09 18:00:22 +0000
+++ ubuntu_sso/qt/main/__init__.py 2012-06-27 15:30:29 +0000
@@ -80,5 +80,7 @@
80 ui.size(), app.desktop().availableGeometry())80 ui.size(), app.desktop().availableGeometry())
81 ui.setGeometry(style)81 ui.setGeometry(style)
82 ui.show()82 ui.show()
83 if sys.platform == 'darwin':
84 ui.raise_()
8385
84 source.main_start(app)86 source.main_start(app)
8587
=== modified file 'ubuntu_sso/qt/main/tests/test_main.py'
--- ubuntu_sso/qt/main/tests/test_main.py 2012-05-09 17:48:32 +0000
+++ ubuntu_sso/qt/main/tests/test_main.py 2012-06-27 15:30:29 +0000
@@ -53,6 +53,7 @@
53 """Fake setGeometry."""53 """Fake setGeometry."""
5454
55 show = setGeometry55 show = setGeometry
56 raise_ = lambda self: None
5657
5758
58class FakeDesktop(object):59class FakeDesktop(object):
5960
=== modified file 'ubuntu_sso/qt/proxy_dialog.py'
--- ubuntu_sso/qt/proxy_dialog.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/qt/proxy_dialog.py 2012-06-27 15:30:29 +0000
@@ -116,7 +116,7 @@
116 try:116 try:
117 logger.debug('Save credentials as for domain %s.', self.domain)117 logger.debug('Save credentials as for domain %s.', self.domain)
118 yield self.keyring.set_credentials(self.domain, creds)118 yield self.keyring.set_credentials(self.domain, creds)
119 except Exception, e:119 except Exception as e:
120 logger.exception('Could not set credentials:')120 logger.exception('Could not set credentials:')
121 self.done(EXCEPTION_RAISED)121 self.done(EXCEPTION_RAISED)
122 logger.debug('Stored creds')122 logger.debug('Stored creds')
123123
=== modified file 'ubuntu_sso/qt/setup_account_page.py'
--- ubuntu_sso/qt/setup_account_page.py 2012-05-01 12:48:42 +0000
+++ ubuntu_sso/qt/setup_account_page.py 2012-06-27 15:30:29 +0000
@@ -154,7 +154,6 @@
154154
155 # Button setup155 # Button setup
156 self.wizard().setButtonLayout([156 self.wizard().setButtonLayout([
157 QtGui.QWizard.BackButton,
158 QtGui.QWizard.Stretch,157 QtGui.QWizard.Stretch,
159 QtGui.QWizard.CustomButton3])158 QtGui.QWizard.CustomButton3])
160159
@@ -246,8 +245,8 @@
246 common.NORMAL))245 common.NORMAL))
247246
248 self.ui.refresh_label.linkActivated.connect(self.hide_error)247 self.ui.refresh_label.linkActivated.connect(self.hide_error)
249 self.ui.refresh_label.linkActivated.connect(lambda url: \248 self.ui.refresh_label.linkActivated.connect(
250 self._refresh_captcha())249 lambda url: self._refresh_captcha())
251 # We need to check if we enable the button on many signals250 # We need to check if we enable the button on many signals
252 self.ui.name_edit.textEdited.connect(self._enable_setup_button)251 self.ui.name_edit.textEdited.connect(self._enable_setup_button)
253 self.ui.email_edit.textEdited.connect(self._enable_setup_button)252 self.ui.email_edit.textEdited.connect(self._enable_setup_button)
254253
=== modified file 'ubuntu_sso/qt/sso_wizard_page.py'
--- ubuntu_sso/qt/sso_wizard_page.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/qt/sso_wizard_page.py 2012-06-27 15:30:29 +0000
@@ -253,7 +253,7 @@
253 if app_name == self.app_name:253 if app_name == self.app_name:
254 result = f(app_name, *args, **kwargs)254 result = f(app_name, *args, **kwargs)
255 else:255 else:
256 logger.info('%s: ignoring call since received app_name '\256 logger.info('%s: ignoring call since received app_name '
257 '"%s" (expected "%s")',257 '"%s" (expected "%s")',
258 f.__name__, app_name, self.app_name)258 f.__name__, app_name, self.app_name)
259 return result259 return result
@@ -262,7 +262,7 @@
262262
263 def _setup_signals(self):263 def _setup_signals(self):
264 """Bind signals to callbacks to be able to test the pages."""264 """Bind signals to callbacks to be able to test the pages."""
265 for signal, method in self._signals.iteritems():265 for signal, method in self._signals.items():
266 actual = self._signals_receivers.get(signal)266 actual = self._signals_receivers.get(signal)
267 if actual is not None:267 if actual is not None:
268 msg = 'Signal %r is already connected with %r.'268 msg = 'Signal %r is already connected with %r.'
269269
=== modified file 'ubuntu_sso/qt/tests/test_arrow.py'
--- ubuntu_sso/qt/tests/test_arrow.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/qt/tests/test_arrow.py 2012-06-27 15:30:29 +0000
@@ -96,7 +96,7 @@
96 arrow = QArrow(QArrow.RIGHT)96 arrow = QArrow(QArrow.RIGHT)
97 self.patch(arrow, 'style', self.style)97 self.patch(arrow, 'style', self.style)
9898
99 for key, value in directions_map.iteritems():99 for key, value in directions_map.items():
100 self.style.called = []100 self.style.called = []
101 arrow.direction = key101 arrow.direction = key
102 arrow.paintEvent(None)102 arrow.paintEvent(None)
103103
=== modified file 'ubuntu_sso/qt/tests/test_common.py'
--- ubuntu_sso/qt/tests/test_common.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/qt/tests/test_common.py 2012-06-27 15:30:29 +0000
@@ -350,7 +350,7 @@
350 result = build_general_error_message(err_dict)350 result = build_general_error_message(err_dict)
351351
352 expected = '\n'.join(352 expected = '\n'.join(
353 [('%s: %s' % (k, v)) for k, v in err_dict.iteritems()])353 [('%s: %s' % (k, v)) for k, v in err_dict.items()])
354 self.assertEqual(result, expected)354 self.assertEqual(result, expected)
355355
356 def test_with_random_keys_with_errtype(self):356 def test_with_random_keys_with_errtype(self):
@@ -362,8 +362,8 @@
362 result = build_general_error_message(err_dict)362 result = build_general_error_message(err_dict)
363363
364 expected = '\n'.join(364 expected = '\n'.join(
365 [('%s: %s' % (k, v)) \365 [('%s: %s' % (k, v))
366 for k, v in {'my_bad': error, 'odd_error': error2}.iteritems()])366 for k, v in {'my_bad': error, 'odd_error': error2}.items()])
367 self.assertEqual(result, expected)367 self.assertEqual(result, expected)
368368
369 def test_with_not_dict(self):369 def test_with_not_dict(self):
370370
=== modified file 'ubuntu_sso/qt/tests/test_current_user_sign_in_page.py'
--- ubuntu_sso/qt/tests/test_current_user_sign_in_page.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/qt/tests/test_current_user_sign_in_page.py 2012-06-27 15:30:29 +0000
@@ -59,7 +59,7 @@
59 self.assertEqual(wizard.buttons_text[QtGui.QWizard.CancelButton],59 self.assertEqual(wizard.buttons_text[QtGui.QWizard.CancelButton],
60 "Cancel")60 "Cancel")
61 self.assertIn(('setButtonLayout',61 self.assertIn(('setButtonLayout',
62 (([QtGui.QWizard.BackButton, QtGui.QWizard.Stretch],), {})),62 (([],), {})),
63 wizard.called)63 wizard.called)
64 self.assertTrue(button.properties['default'])64 self.assertTrue(button.properties['default'])
65 self.assertFalse(button.isEnabled())65 self.assertFalse(button.isEnabled())
6666
=== modified file 'ubuntu_sso/qt/tests/test_setup_account.py'
--- ubuntu_sso/qt/tests/test_setup_account.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/qt/tests/test_setup_account.py 2012-06-27 15:30:29 +0000
@@ -207,8 +207,7 @@
207 self.ui.initializePage()207 self.ui.initializePage()
208208
209 # set up account button209 # set up account button
210 expected = [QtGui.QWizard.BackButton, QtGui.QWizard.Stretch,210 expected = [QtGui.QWizard.Stretch, QtGui.QWizard.CustomButton3]
211 QtGui.QWizard.CustomButton3]
212 self.assertIn(('setButtonLayout', ((expected,), {})),211 self.assertIn(('setButtonLayout', ((expected,), {})),
213 self.ui.wizard().called)212 self.ui.wizard().called)
214 self.assertEqual(gui.SET_UP_ACCOUNT_BUTTON,213 self.assertEqual(gui.SET_UP_ACCOUNT_BUTTON,
215214
=== modified file 'ubuntu_sso/tests/test_account.py'
--- ubuntu_sso/tests/test_account.py 2012-05-29 16:58:30 +0000
+++ ubuntu_sso/tests/test_account.py 2012-06-27 15:30:29 +0000
@@ -230,7 +230,7 @@
230230
231 def verify_frc_shutdown(self):231 def verify_frc_shutdown(self):
232 """Verify that the FakeRestfulClient was stopped."""232 """Verify that the FakeRestfulClient was stopped."""
233 assert self.frc.started == False, "Restfulclient must be shut down."233 assert self.frc.started is False, "Restfulclient must be shut down."
234234
235 @defer.inlineCallbacks235 @defer.inlineCallbacks
236 def test_generate_captcha(self):236 def test_generate_captcha(self):
237237
=== modified file 'ubuntu_sso/tests/test_credentials.py'
--- ubuntu_sso/tests/test_credentials.py 2012-05-04 18:48:54 +0000
+++ ubuntu_sso/tests/test_credentials.py 2012-06-27 15:30:29 +0000
@@ -122,6 +122,7 @@
122 self.memento = MementoHandler()122 self.memento = MementoHandler()
123 self.memento.setLevel(logging.DEBUG)123 self.memento.setLevel(logging.DEBUG)
124 credentials.logger.addHandler(self.memento)124 credentials.logger.addHandler(self.memento)
125 self.addCleanup(credentials.logger.removeHandler, self.memento)
125126
126 self.patch(credentials, 'get_bin_dir', lambda: self.bin_dir)127 self.patch(credentials, 'get_bin_dir', lambda: self.bin_dir)
127128
@@ -147,7 +148,7 @@
147148
148 def test_creation_parameters_are_stored(self):149 def test_creation_parameters_are_stored(self):
149 """Creation parameters are stored."""150 """Creation parameters are stored."""
150 for key, value in KWARGS.iteritems():151 for key, value in KWARGS.items():
151 self.assertEqual(getattr(self.obj, key), value)152 self.assertEqual(getattr(self.obj, key), value)
152153
153 def test_tc_url_defaults_to_none(self):154 def test_tc_url_defaults_to_none(self):
154155
=== modified file 'ubuntu_sso/utils/__init__.py'
--- ubuntu_sso/utils/__init__.py 2012-04-25 14:17:16 +0000
+++ ubuntu_sso/utils/__init__.py 2012-06-27 15:30:29 +0000
@@ -165,7 +165,7 @@
165 logger.debug("Calculated server-local time skew: %r",165 logger.debug("Calculated server-local time skew: %r",
166 self.skew)166 self.skew)
167 #pylint: disable=W0703167 #pylint: disable=W0703
168 except Exception, server_error:168 except Exception as server_error:
169 logger.debug("Error while verifying the server time skew: %r",169 logger.debug("Error while verifying the server time skew: %r",
170 server_error)170 server_error)
171 self.next_check = local_time + self.ERROR_INTERVAL171 self.next_check = local_time + self.ERROR_INTERVAL
172172
=== modified file 'ubuntu_sso/utils/ipc.py'
--- ubuntu_sso/utils/ipc.py 2012-05-16 09:48:55 +0000
+++ ubuntu_sso/utils/ipc.py 2012-06-27 15:30:29 +0000
@@ -132,7 +132,7 @@
132132
133 def remote_unregister_to_signals(self, client):133 def remote_unregister_to_signals(self, client):
134 """Allow a client to unregister from the signal."""134 """Allow a client to unregister from the signal."""
135 for connected_clients in self.clients_per_signal.itervalues():135 for connected_clients in self.clients_per_signal.values():
136 if client in connected_clients:136 if client in connected_clients:
137 connected_clients.remove(client)137 connected_clients.remove(client)
138138
@@ -198,7 +198,7 @@
198 super(BaseService, self).__init__()198 super(BaseService, self).__init__()
199 self.factory = None199 self.factory = None
200 self.listener = None200 self.listener = None
201 for name, service_class in self.services.iteritems():201 for name, service_class in self.services.items():
202 service = service_class(*a, **kw)202 service = service_class(*a, **kw)
203 setattr(self, name, service)203 setattr(self, name, service)
204 setattr(self, 'remote_get_%s' % name,204 setattr(self, 'remote_get_%s' % name,
@@ -344,7 +344,7 @@
344 """Request all the diff remote objects used for the communication."""344 """Request all the diff remote objects used for the communication."""
345 logger.debug('Requesting remote objects (%r) for %s',345 logger.debug('Requesting remote objects (%r) for %s',
346 self.clients.keys(), self.__class__.__name__)346 self.clients.keys(), self.__class__.__name__)
347 for name, client_class in self.clients.iteritems():347 for name, client_class in self.clients.items():
348 remote = yield root.callRemote('get_%s' % name)348 remote = yield root.callRemote('get_%s' % name)
349 setattr(self, name, client_class(remote))349 setattr(self, name, client_class(remote))
350350
351351
=== modified file 'ubuntu_sso/utils/runner/glib.py'
--- ubuntu_sso/utils/runner/glib.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/runner/glib.py 2012-06-27 15:30:29 +0000
@@ -89,7 +89,7 @@
8989
90 try:90 try:
91 pid, _, _, _ = GLib.spawn_async(argv=bytes_args, flags=flags)91 pid, _, _, _ = GLib.spawn_async(argv=bytes_args, flags=flags)
92 except GLib.GError, e:92 except GLib.GError as e:
93 handle_error(e)93 handle_error(e)
94 else:94 else:
95 logger.debug('Spawning the program %r with the glib mainloop '95 logger.debug('Spawning the program %r with the glib mainloop '
9696
=== modified file 'ubuntu_sso/utils/runner/tests/test_glib.py'
--- ubuntu_sso/utils/runner/tests/test_glib.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/runner/tests/test_glib.py 2012-06-27 15:30:29 +0000
@@ -87,7 +87,7 @@
8787
88 try:88 try:
89 subprocess.call(argv)89 subprocess.call(argv)
90 except Exception, e:90 except Exception as e:
91 exc = GLib.GError()91 exc = GLib.GError()
92 exc.message = str(e)92 exc.message = str(e)
93 exc.code = 4293 exc.code = 42
9494
=== modified file 'ubuntu_sso/utils/runner/tests/test_qt.py'
--- ubuntu_sso/utils/runner/tests/test_qt.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/runner/tests/test_qt.py 2012-06-27 15:30:29 +0000
@@ -86,12 +86,12 @@
8686
87 try:87 try:
88 subprocess.call(bytes_args)88 subprocess.call(bytes_args)
89 except OSError, e:89 except OSError as e:
90 if e.errno == 2:90 if e.errno == 2:
91 self.error.emit(self.FailedToStart)91 self.error.emit(self.FailedToStart)
92 else:92 else:
93 self.error.emit(e)93 self.error.emit(e)
94 except Exception, e:94 except Exception as e:
95 self.error.emit(e)95 self.error.emit(e)
96 else:96 else:
97 self.finished.emit(self._status_code)97 self.finished.emit(self._status_code)
9898
=== modified file 'ubuntu_sso/utils/runner/tx.py'
--- ubuntu_sso/utils/runner/tx.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/runner/tx.py 2012-06-27 15:30:29 +0000
@@ -102,9 +102,9 @@
102102
103 try:103 try:
104 d = utils.getProcessOutputAndValue(program, bytes_args, env=os.environ)104 d = utils.getProcessOutputAndValue(program, bytes_args, env=os.environ)
105 except OSError, e:105 except OSError as e:
106 error_handler(msg=e, failed_to_start=True)106 error_handler(msg=e, failed_to_start=True)
107 except Exception, e:107 except Exception as e:
108 error_handler(msg=e, failed_to_start=False)108 error_handler(msg=e, failed_to_start=False)
109 else:109 else:
110 logger.debug('Spawning the program %r with the twisted reactor '110 logger.debug('Spawning the program %r with the twisted reactor '
111111
=== modified file 'ubuntu_sso/utils/tcpactivation.py'
--- ubuntu_sso/utils/tcpactivation.py 2012-05-03 11:42:55 +0000
+++ ubuntu_sso/utils/tcpactivation.py 2012-06-27 15:30:29 +0000
@@ -30,7 +30,7 @@
3030
31import subprocess31import subprocess
3232
33from twisted.internet import defer, error, protocol, reactor33from twisted.internet import defer, error, protocol
34from twisted.internet.endpoints import clientFromString34from twisted.internet.endpoints import clientFromString
3535
36LOCALHOST = "127.0.0.1"36LOCALHOST = "127.0.0.1"
@@ -43,6 +43,7 @@
4343
44def async_sleep(delay):44def async_sleep(delay):
45 """Fire the returned deferred after some specified delay."""45 """Fire the returned deferred after some specified delay."""
46 from twisted.internet import reactor
46 d = defer.Deferred()47 d = defer.Deferred()
47 # pylint: disable=E110148 # pylint: disable=E1101
48 reactor.callLater(delay, d.callback, None)49 reactor.callLater(delay, d.callback, None)
@@ -87,11 +88,13 @@
8788
88 def clientConnectionLost(self, connector, reason):89 def clientConnectionLost(self, connector, reason):
89 """The connection was lost."""90 """The connection was lost."""
91 protocol.ClientFactory.clientConnectionLost(self, connector, reason)
90 if not self.d.called:92 if not self.d.called:
91 self.d.callback(False)93 self.d.callback(False)
9294
93 def clientConnectionFailed(self, connector, reason):95 def clientConnectionFailed(self, connector, reason):
94 """The connection failed."""96 """The connection failed."""
97 protocol.ClientFactory.clientConnectionFailed(self, connector, reason)
95 if not self.d.called:98 if not self.d.called:
96 self.d.callback(False)99 self.d.callback(False)
97100
@@ -116,6 +119,7 @@
116 @defer.inlineCallbacks119 @defer.inlineCallbacks
117 def is_already_running(self):120 def is_already_running(self):
118 """Check if the instance is already running."""121 """Check if the instance is already running."""
122 from twisted.internet import reactor
119 factory = PortDetectFactory()123 factory = PortDetectFactory()
120 client = clientFromString(reactor, self.config.description.client)124 client = clientFromString(reactor, self.config.description.client)
121 try:125 try:
122126
=== modified file 'ubuntu_sso/utils/tests/test_common.py'
--- ubuntu_sso/utils/tests/test_common.py 2012-04-17 20:27:21 +0000
+++ ubuntu_sso/utils/tests/test_common.py 2012-06-27 15:30:29 +0000
@@ -106,6 +106,7 @@
106 self.memento = MementoHandler()106 self.memento = MementoHandler()
107 self.memento.setLevel(logging.DEBUG)107 self.memento.setLevel(logging.DEBUG)
108 utils.logger.addHandler(self.memento)108 utils.logger.addHandler(self.memento)
109 self.addCleanup(utils.logger.removeHandler, self.memento)
109110
110 self.get_dir = getattr(utils, self.DIR_GETTER)111 self.get_dir = getattr(utils, self.DIR_GETTER)
111112
112113
=== modified file 'ubuntu_sso/utils/tests/test_tcpactivation.py'
--- ubuntu_sso/utils/tests/test_tcpactivation.py 2012-05-03 12:36:08 +0000
+++ ubuntu_sso/utils/tests/test_tcpactivation.py 2012-06-27 15:30:29 +0000
@@ -35,7 +35,8 @@
35# this test module access a few protected members (starting with _)35# this test module access a few protected members (starting with _)
36# pylint: disable=W021236# pylint: disable=W0212
3737
38from twisted.internet import defer, protocol, reactor, task38import twisted.internet
39from twisted.internet import defer, protocol, task
39from twisted.trial.unittest import TestCase40from twisted.trial.unittest import TestCase
4041
41# pylint: disable=W061142# pylint: disable=W0611
@@ -72,7 +73,7 @@
72 self.transport.write(data)73 self.transport.write(data)
7374
7475
75class FakeServerFactory(protocol.Factory):76class FakeServerFactory(protocol.ServerFactory):
76 """A factory for the test server."""77 """A factory for the test server."""
7778
78 protocol = FakeServerProtocol79 protocol = FakeServerProtocol
@@ -97,37 +98,6 @@
97 self.client = client_description98 self.client = client_description
9899
99100
100class FakeConnectedClient(object):
101 """Fake class use to patch connections."""
102
103 def __init__(self, testcase, inner):
104 """Create a new instance."""
105 self.testcase = testcase
106 self.inner = inner
107
108 @defer.inlineCallbacks
109 def connect(self, factory):
110 """Connect using the given factory."""
111 # we need to get the class of the factory and patch it
112 yield self.testcase.connect_client(PortDetectFactory)
113 self.inner.factory = self.testcase.client_factory
114 defer.returnValue(self.testcase.connector)
115
116
117class WrapperFactory(object):
118 """Wrapps a factory so that it can be replaced in a test."""
119
120 def __init__(self, factory):
121 self.factory = None
122
123 def __call__(self, *args, **kwargs):
124 return self
125
126 def is_listening(self):
127 """Return if the service is listening."""
128 return self.factory.is_listening()
129
130
131class AsyncSleepTestCase(TestCase):101class AsyncSleepTestCase(TestCase):
132 """Tests for the async_sleep function."""102 """Tests for the async_sleep function."""
133103
@@ -137,7 +107,7 @@
137 yield super(AsyncSleepTestCase, self).setUp()107 yield super(AsyncSleepTestCase, self).setUp()
138 self.test_timeout = 5.0108 self.test_timeout = 5.0
139 self.clock = task.Clock()109 self.clock = task.Clock()
140 self.patch(tcpactivation, "reactor", self.clock)110 self.patch(twisted.internet, "reactor", self.clock)
141 self.d = tcpactivation.async_sleep(self.test_timeout)111 self.d = tcpactivation.async_sleep(self.test_timeout)
142112
143 def test_async_sleep_not_fired_immediately(self):113 def test_async_sleep_not_fired_immediately(self):
@@ -231,7 +201,7 @@
231 self.assertEqual(config.description, SAMPLE_CLIENT_DESCRIPTION)201 self.assertEqual(config.description, SAMPLE_CLIENT_DESCRIPTION)
232202
233203
234class ActivationDetectorTestCase(ServerTestCase):204class ActivationDetectorTestCase(TestCase):
235 """Tests for the ActivationDetector class."""205 """Tests for the ActivationDetector class."""
236206
237 timeoue = 3207 timeoue = 3
@@ -270,15 +240,29 @@
270 @defer.inlineCallbacks240 @defer.inlineCallbacks
271 def test_is_already_running(self):241 def test_is_already_running(self):
272 """The is_already_running method returns True if already started."""242 """The is_already_running method returns True if already started."""
273 inner = WrapperFactory(None)243 server = self.get_server()
274244 self.addCleanup(server.clean_up)
275 self.patch(tcpactivation, 'PortDetectFactory', inner)245
276 client = FakeConnectedClient(self, inner)246 class TestConnect(object):
247 """A fake connection object."""
248
249 # pylint: disable=E0213
250 @defer.inlineCallbacks
251 def connect(my_self, factory):
252 """A fake connection."""
253
254 connected_factory = yield server.connect_client(
255 PortDetectFactory)
256 self.patch(factory, 'is_listening',
257 connected_factory.is_listening)
258 defer.returnValue(connected_factory)
259 # pylint: enable=E0213
277260
278 self.patch(tcpactivation, 'clientFromString',261 self.patch(tcpactivation, 'clientFromString',
279 lambda *args: client)262 lambda *args: TestConnect())
280263
281 yield self.listen_server(FakeServerFactory)264 yield server.listen_server(protocol.ServerFactory)
265
282 # pylint: disable=E1101266 # pylint: disable=E1101
283 ad = ActivationDetector(self.config)267 ad = ActivationDetector(self.config)
284 result = yield ad.is_already_running()268 result = yield ad.is_already_running()
@@ -347,7 +331,7 @@
347 """Test the _wait_server_active method."""331 """Test the _wait_server_active method."""
348 ac = ActivationClient(self.config)332 ac = ActivationClient(self.config)
349 clock = task.Clock()333 clock = task.Clock()
350 self.patch(tcpactivation, "reactor", clock)334 self.patch(twisted.internet, "reactor", clock)
351 self.patch(ac, "is_already_running", lambda: defer.succeed(False))335 self.patch(ac, "is_already_running", lambda: defer.succeed(False))
352336
353 d = ac._wait_server_active()337 d = ac._wait_server_active()
@@ -363,7 +347,7 @@
363 """If the server takes too long to start then timeout."""347 """If the server takes too long to start then timeout."""
364 ac = ActivationClient(self.config)348 ac = ActivationClient(self.config)
365 clock = task.Clock()349 clock = task.Clock()
366 self.patch(tcpactivation, "reactor", clock)350 self.patch(twisted.internet, "reactor", clock)
367 self.patch(ac, "is_already_running", lambda: defer.succeed(False))351 self.patch(ac, "is_already_running", lambda: defer.succeed(False))
368 d = ac._wait_server_active()352 d = ac._wait_server_active()
369 clock.pump([tcpactivation.DELAY_BETWEEN_CHECKS] *353 clock.pump([tcpactivation.DELAY_BETWEEN_CHECKS] *
@@ -420,15 +404,28 @@
420 @defer.inlineCallbacks404 @defer.inlineCallbacks
421 def test_get_description_fails_if_service_already_started(self):405 def test_get_description_fails_if_service_already_started(self):
422 """The get_description method fails if service already started."""406 """The get_description method fails if service already started."""
423 inner = WrapperFactory(None)407 server = self.get_server()
424408 self.addCleanup(server.clean_up)
425 self.patch(tcpactivation, 'PortDetectFactory', inner)409
426 client = FakeConnectedClient(self, inner)410 class TestConnect(object):
411 """A fake connection object."""
412
413 # pylint: disable=E0213
414 @defer.inlineCallbacks
415 def connect(my_self, factory):
416 """A fake connection."""
417
418 connected_factory = yield server.connect_client(
419 PortDetectFactory)
420 self.patch(factory, 'is_listening',
421 connected_factory.is_listening)
422 defer.returnValue(connected_factory)
423 # pylint: enable=E0213
427424
428 self.patch(tcpactivation, 'clientFromString',425 self.patch(tcpactivation, 'clientFromString',
429 lambda *args: client)426 lambda *args: TestConnect())
430427
431 yield self.listen_server(FakeServerFactory)428 yield server.listen_server(protocol.ServerFactory)
432429
433 ai = ActivationInstance(self.config)430 ai = ActivationInstance(self.config)
434 yield self.assertFailure(ai.get_server_description(),431 yield self.assertFailure(ai.get_server_description(),
@@ -438,6 +435,7 @@
438435
439def server_test(config):436def server_test(config):
440 """An IRL test of the server."""437 """An IRL test of the server."""
438 from twisted.internet import reactor
441439
442 def got_description(description):440 def got_description(description):
443 """The description was found."""441 """The description was found."""
@@ -472,6 +470,7 @@
472470
473def client_test(config):471def client_test(config):
474 """An IRL test of the client."""472 """An IRL test of the client."""
473 from twisted.internet import reactor
475 print "starting the client."474 print "starting the client."
476 ac = ActivationClient(config)475 ac = ActivationClient(config)
477 d = ac.get_active_client_description()476 d = ac.get_active_client_description()
478477
=== modified file 'ubuntu_sso/utils/txsecrets.py'
--- ubuntu_sso/utils/txsecrets.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/txsecrets.py 2012-06-27 15:30:29 +0000
@@ -100,7 +100,7 @@
100 self.service.OpenSession(ALGORITHM, parameters,100 self.service.OpenSession(ALGORITHM, parameters,
101 reply_handler=session_opened,101 reply_handler=session_opened,
102 error_handler=d.errback)102 error_handler=d.errback)
103 except dbus.exceptions.DBusException, e:103 except dbus.exceptions.DBusException as e:
104 d.errback(e)104 d.errback(e)
105 return d105 return d
106106
107107
=== modified file 'ubuntu_sso/utils/webclient/common.py'
--- ubuntu_sso/utils/webclient/common.py 2012-04-12 12:07:46 +0000
+++ ubuntu_sso/utils/webclient/common.py 2012-06-27 15:30:29 +0000
@@ -206,7 +206,7 @@
206 try:206 try:
207 creds = yield keyring.get_credentials(str(domain))207 creds = yield keyring.get_credentials(str(domain))
208 logger.debug('Got credentials from keyring.')208 logger.debug('Got credentials from keyring.')
209 except Exception, e:209 except Exception as e:
210 logger.error('Error when retrieving the creds.')210 logger.error('Error when retrieving the creds.')
211 raise WebClientError('Error when retrieving the creds.', e)211 raise WebClientError('Error when retrieving the creds.', e)
212 if creds is not None:212 if creds is not None:
@@ -249,7 +249,7 @@
249249
250 try:250 try:
251 return_code = yield self._launch_proxy_creds_dialog(domain, retry)251 return_code = yield self._launch_proxy_creds_dialog(domain, retry)
252 except Exception, e:252 except Exception as e:
253 logger.error('Error when running external ui process.')253 logger.error('Error when running external ui process.')
254 raise WebClientError('Error when running external ui process.', e)254 raise WebClientError('Error when running external ui process.', e)
255255
256256
=== modified file 'ubuntu_sso/utils/webclient/gsettings.py'
--- ubuntu_sso/utils/webclient/gsettings.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/webclient/gsettings.py 2012-06-27 15:30:29 +0000
@@ -30,7 +30,11 @@
3030
31import subprocess31import subprocess
3232
33from ubuntu_sso.logger import setup_logging
34
35logger = setup_logging("ubuntu_sso.utils.webclient.gsettings")
33GSETTINGS_CMDLINE = "gsettings list-recursively org.gnome.system.proxy"36GSETTINGS_CMDLINE = "gsettings list-recursively org.gnome.system.proxy"
37CANNOT_PARSE_WARNING = "Cannot parse gsettings value: %r"
3438
3539
36def parse_proxy_host(hostname):40def parse_proxy_host(hostname):
@@ -80,12 +84,15 @@
80 continue84 continue
81 if value.startswith("'"):85 if value.startswith("'"):
82 parsed_value = value[1:-1]86 parsed_value = value[1:-1]
83 elif value.startswith('['):87 elif value.startswith(('[', '@')):
84 parsed_value = value88 parsed_value = value
85 elif value in ('true', 'false'):89 elif value in ('true', 'false'):
86 parsed_value = (value == 'true')90 parsed_value = (value == 'true')
91 elif value.isdigit():
92 parsed_value = int(value)
87 else:93 else:
88 parsed_value = int(value)94 logger.warning(CANNOT_PARSE_WARNING, value)
95 parsed_value = value
89 relative_key = (path + "." + key)[base_len:]96 relative_key = (path + "." + key)[base_len:]
90 gsettings[relative_key] = parsed_value97 gsettings[relative_key] = parsed_value
91 mode = gsettings["mode"]98 mode = gsettings["mode"]
9299
=== modified file 'ubuntu_sso/utils/webclient/libsoup.py'
--- ubuntu_sso/utils/webclient/libsoup.py 2012-04-12 12:07:46 +0000
+++ ubuntu_sso/utils/webclient/libsoup.py 2012-06-27 15:30:29 +0000
@@ -131,7 +131,7 @@
131 d = defer.Deferred()131 d = defer.Deferred()
132 message = self.soup.Message.new(method, uri)132 message = self.soup.Message.new(method, uri)
133133
134 for key, value in headers.iteritems():134 for key, value in headers.items():
135 message.request_headers.append(key, value)135 message.request_headers.append(key, value)
136136
137 if post_content:137 if post_content:
138138
=== modified file 'ubuntu_sso/utils/webclient/qtnetwork.py'
--- ubuntu_sso/utils/webclient/qtnetwork.py 2012-04-24 14:05:22 +0000
+++ ubuntu_sso/utils/webclient/qtnetwork.py 2012-06-27 15:30:29 +0000
@@ -158,14 +158,14 @@
158 headers = yield self.build_request_headers(uri, method, extra_headers,158 headers = yield self.build_request_headers(uri, method, extra_headers,
159 oauth_credentials)159 oauth_credentials)
160160
161 for key, value in headers.iteritems():161 for key, value in headers.items():
162 request.setRawHeader(key, value)162 request.setRawHeader(key, value)
163163
164 post_buffer = QBuffer()164 post_buffer = QBuffer()
165 post_buffer.setData(post_content)165 post_buffer.setData(post_content)
166 try:166 try:
167 result = yield self._perform_request(request, method, post_buffer)167 result = yield self._perform_request(request, method, post_buffer)
168 except ProxyUnauthorizedError, e:168 except ProxyUnauthorizedError as e:
169 app_proxy = QNetworkProxy.applicationProxy()169 app_proxy = QNetworkProxy.applicationProxy()
170 proxy_host = app_proxy.hostName() if app_proxy else "proxy server"170 proxy_host = app_proxy.hostName() if app_proxy else "proxy server"
171 got_creds = yield self.request_proxy_auth_credentials(171 got_creds = yield self.request_proxy_auth_credentials(
@@ -221,7 +221,7 @@
221 QSslCertificate.CountryName: 'country_name',221 QSslCertificate.CountryName: 'country_name',
222 QSslCertificate.StateOrProvinceName: 'state_name'}222 QSslCertificate.StateOrProvinceName: 'state_name'}
223 details = {}223 details = {}
224 for info, title in detail_titles.iteritems():224 for info, title in detail_titles.items():
225 details[title] = str(cert.issuerInfo(info))225 details[title] = str(cert.issuerInfo(info))
226 return self.format_ssl_details(details)226 return self.format_ssl_details(details)
227227
228228
=== modified file 'ubuntu_sso/utils/webclient/restful.py'
--- ubuntu_sso/utils/webclient/restful.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/webclient/restful.py 2012-06-27 15:30:29 +0000
@@ -33,8 +33,11 @@
3333
34from twisted.internet import defer34from twisted.internet import defer
3535
36from ubuntu_sso.logger import setup_logging
36from ubuntu_sso.utils import webclient37from ubuntu_sso.utils import webclient
3738
39logger = setup_logging("ubuntu_sso.utils.webclient.restful")
40
38POST_HEADERS = {41POST_HEADERS = {
39 "content-type": "application/x-www-form-urlencoded",42 "content-type": "application/x-www-form-urlencoded",
40}43}
@@ -67,11 +70,19 @@
67 encoded_args = urllib.urlencode(params)70 encoded_args = urllib.urlencode(params)
68 iri = self.service_iri + namespace71 iri = self.service_iri + namespace
69 creds = self.oauth_credentials72 creds = self.oauth_credentials
73 logger.debug('Performing REST call to %r.', iri)
70 result = yield self.webclient.request(iri, method="POST",74 result = yield self.webclient.request(iri, method="POST",
71 oauth_credentials=creds,75 oauth_credentials=creds,
72 post_content=encoded_args,76 post_content=encoded_args,
73 extra_headers=POST_HEADERS)77 extra_headers=POST_HEADERS)
74 defer.returnValue(json.loads(result.content))78 try:
79 response = json.loads(result.content)
80 except:
81 logger.exception('Can not load json from REST request response '
82 '(content is %r).', result.content)
83 raise
84 else:
85 defer.returnValue(response)
7586
76 def shutdown(self):87 def shutdown(self):
77 """Stop the webclient used by this class."""88 """Stop the webclient used by this class."""
7889
=== modified file 'ubuntu_sso/utils/webclient/tests/test_gsettings.py'
--- ubuntu_sso/utils/webclient/tests/test_gsettings.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/webclient/tests/test_gsettings.py 2012-06-27 15:30:29 +0000
@@ -28,7 +28,10 @@
28# files in the program, then also delete it here.28# files in the program, then also delete it here.
29"""Test the gsettings parser."""29"""Test the gsettings parser."""
3030
31import logging
32
31from twisted.trial.unittest import TestCase33from twisted.trial.unittest import TestCase
34from ubuntuone.devtools.handlers import MementoHandler
3235
33from ubuntu_sso.utils.webclient import gsettings36from ubuntu_sso.utils.webclient import gsettings
34from ubuntu_sso.utils.webclient.tests import (37from ubuntu_sso.utils.webclient.tests import (
@@ -91,6 +94,35 @@
91 """Test a parser of gsettings."""94 """Test a parser of gsettings."""
92 self._assert_parser_anonymous('https')95 self._assert_parser_anonymous('https')
9396
97 def test_gsettings_empty_ignore_hosts(self):
98 """Missing values in the ignore hosts."""
99 troublesome_value = "@as []"
100 template_values = dict(BASE_GSETTINGS_VALUES)
101 template_values["ignore_hosts"] = troublesome_value
102 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
103 self.patch(gsettings.subprocess, "check_output",
104 lambda _: fake_output)
105 ps = gsettings.get_proxy_settings()
106 self.assertEqual(ps, {})
107
108 def test_gsettings_cannot_parse(self):
109 """Some weird setting that cannot be parsed is logged with warning."""
110 memento = MementoHandler()
111 memento.setLevel(logging.DEBUG)
112 gsettings.logger.addHandler(memento)
113 self.addCleanup(gsettings.logger.removeHandler, memento)
114
115 troublesome_value = "#bang"
116 template_values = dict(BASE_GSETTINGS_VALUES)
117 template_values["ignore_hosts"] = troublesome_value
118 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
119 self.patch(gsettings.subprocess, "check_output",
120 lambda _: fake_output)
121 ps = gsettings.get_proxy_settings()
122 self.assertTrue(memento.check_warning(gsettings.CANNOT_PARSE_WARNING %
123 troublesome_value))
124 self.assertEqual(ps, {})
125
94 def test_gsettings_parser_http_authenticated(self):126 def test_gsettings_parser_http_authenticated(self):
95 """Test a parser of gsettings."""127 """Test a parser of gsettings."""
96 template_values = dict(BASE_GSETTINGS_VALUES)128 template_values = dict(BASE_GSETTINGS_VALUES)
97129
=== modified file 'ubuntu_sso/utils/webclient/tests/test_restful.py'
--- ubuntu_sso/utils/webclient/tests/test_restful.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/webclient/tests/test_restful.py 2012-06-27 15:30:29 +0000
@@ -28,9 +28,11 @@
28# files in the program, then also delete it here.28# files in the program, then also delete it here.
29"""Tests for the proxy-enabled restful client."""29"""Tests for the proxy-enabled restful client."""
3030
31import logging
31import urlparse32import urlparse
3233
33from twisted.internet import defer34from twisted.internet import defer
35from ubuntuone.devtools.handlers import MementoHandler
34from ubuntuone.devtools.testcases import TestCase36from ubuntuone.devtools.testcases import TestCase
3537
36from ubuntu_sso.utils.webclient import restful38from ubuntu_sso.utils.webclient import restful
@@ -184,3 +186,45 @@
184 yield rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS)186 yield rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS)
185 _, _, kwargs = self.wc.called[0]187 _, _, kwargs = self.wc.called[0]
186 self.assertEqual(kwargs["oauth_credentials"], SAMPLE_OAUTH_CREDS)188 self.assertEqual(kwargs["oauth_credentials"], SAMPLE_OAUTH_CREDS)
189
190
191class LogginTestCase(BaseTestCase):
192 """Ensure that proper debug logging is done."""
193
194 @defer.inlineCallbacks
195 def setUp(self):
196 """Initialize this testcase."""
197 yield super(LogginTestCase, self).setUp()
198 self.memento = MementoHandler()
199 restful.logger.addHandler(self.memento)
200 restful.logger.setLevel(logging.DEBUG)
201 self.addCleanup(restful.logger.removeHandler, self.memento)
202
203 self.rc = restful.RestfulClient(SAMPLE_SERVICE_IRI)
204 self.addCleanup(self.rc.shutdown)
205
206 @defer.inlineCallbacks
207 def test_log_rest_call(self):
208 """Check that proper DEBUG is made for every REST call."""
209 yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS)
210
211 expected_msgs = (
212 SAMPLE_SERVICE_IRI + SAMPLE_NAMESPACE,
213 )
214 self.assertTrue(self.memento.check_debug(*expected_msgs))
215
216 @defer.inlineCallbacks
217 def test_log_json_loads_exception(self):
218 """Check that json load errors are properly logged."""
219 invalid_json = 'NOTAVALIDJSON'
220 self.patch(self.wc, 'return_value', invalid_json)
221 yield self.assertFailure(self.rc.restcall(SAMPLE_OPERATION),
222 ValueError)
223
224 self.memento.debug = True
225 expected_msgs = (
226 ValueError,
227 'Can not load json from REST request response',
228 invalid_json
229 )
230 self.assertTrue(self.memento.check_exception(*expected_msgs))
187231
=== modified file 'ubuntu_sso/utils/webclient/tests/test_webclient.py'
--- ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-04-24 14:05:22 +0000
+++ ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-06-27 15:30:29 +0000
@@ -753,7 +753,7 @@
753753
754 self.assertEqual(method, "OAuth")754 self.assertEqual(method, "OAuth")
755755
756 for k, expected_value in self.expected_params.iteritems():756 for k, expected_value in self.expected_params.items():
757 self.assertIn(k, params)757 self.assertIn(k, params)
758 if expected_value is not ANY_VALUE:758 if expected_value is not ANY_VALUE:
759 self.assertEqual(params[k], expected_value)759 self.assertEqual(params[k], expected_value)
@@ -782,7 +782,7 @@
782 self.assert_headers_correct(request.to_header())782 self.assert_headers_correct(request.to_header())
783 self.assertEqual(request.http_url, self.sample_url)783 self.assertEqual(request.http_url, self.sample_url)
784 if params is not None:784 if params is not None:
785 for param, value in params.iteritems():785 for param, value in params.items():
786 self.assertIn(param, request.parameters)786 self.assertIn(param, request.parameters)
787 actual = request.parameters[param]787 actual = request.parameters[param]
788 self.assertEqual(value, actual)788 self.assertEqual(value, actual)
789789
=== modified file 'ubuntu_sso/utils/webclient/timestamp.py'
--- ubuntu_sso/utils/webclient/timestamp.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/utils/webclient/timestamp.py 2012-06-27 15:30:29 +0000
@@ -83,7 +83,7 @@
83 logger.debug("Calculated server time skew: %r", self.skew)83 logger.debug("Calculated server time skew: %r", self.skew)
84 # We just log all exceptions while trying to get the server time84 # We just log all exceptions while trying to get the server time
85 # pylint: disable=W070385 # pylint: disable=W0703
86 except Exception, e:86 except Exception as e:
87 logger.debug("Error while verifying server time skew: %r", e)87 logger.debug("Error while verifying server time skew: %r", e)
88 self.next_check = local_time + self.ERROR_INTERVAL88 self.next_check = local_time + self.ERROR_INTERVAL
89 # delay import, otherwise a default reactor gets installed89 # delay import, otherwise a default reactor gets installed
9090
=== removed directory 'ubuntu_sso/xdg_base_directory'
=== removed file 'ubuntu_sso/xdg_base_directory/__init__.py'
--- ubuntu_sso/xdg_base_directory/__init__.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/xdg_base_directory/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,104 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2011-2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""XDG multiplatform."""
30
31import os
32import sys
33import warnings
34
35
36def unicode_path(utf8path):
37 """Turn an utf8 path into a unicode path."""
38 return utf8path.decode("utf-8")
39
40
41def native_path(utf8path):
42 """Turn an utf8 path into a path useable in the current encoding."""
43 warnings.warn('native_path will be removed soon', DeprecationWarning)
44 return unicode_path(utf8path)
45
46
47def syncdaemon_path(mbcspath):
48 """Turn a mbcs path in a utf-8 path as CURRENTLY used inside syncdaemon."""
49 warnings.warn('syncdaemon_path will be removed soon', DeprecationWarning)
50 return mbcspath.decode(sys.getfilesystemencoding()).encode("utf-8")
51
52
53# pylint: disable=C0103,F0401,E0611,E1101
54if sys.platform == "win32":
55 from ubuntu_sso.xdg_base_directory import windows
56 source = windows
57 xdg_home = source.home_path
58else:
59 import xdg.BaseDirectory
60 source = xdg.BaseDirectory
61 xdg_home = os.path.expanduser('~')
62
63xdg_cache_home = source.xdg_cache_home
64
65xdg_config_home = source.xdg_config_home
66xdg_config_dirs = source.xdg_config_dirs
67
68xdg_data_home = source.xdg_data_home
69xdg_data_dirs = source.xdg_data_dirs
70
71
72def load_config_paths(*resource):
73 """Iterator of configuration paths.
74
75 Return an iterator which gives each directory named 'resource' in
76 the configuration search path. Information provided by earlier
77 directories should take precedence over later ones (ie, the user's
78 config dir comes first).
79 """
80 resource = os.path.join(*resource)
81 for config_dir in xdg_config_dirs:
82 path = os.path.join(config_dir, resource)
83 # access the file system always with unicode
84 # to properly behave in some operating systems
85 if os.path.exists(unicode_path(path)):
86 yield path
87
88
89def save_config_path(*resource):
90 """Path to save configuration.
91
92 Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path.
93 'resource' should normally be the name of your application. Use this
94 when SAVING configuration settings. Use the xdg_config_dirs variable
95 for loading.
96 """
97 resource = os.path.join(*resource)
98 assert not resource.startswith('/')
99 path = os.path.join(xdg_config_home, resource)
100 # access the file system always with unicode
101 # to properly behave in some operating systems
102 if not os.path.isdir(unicode_path(path)):
103 os.makedirs(unicode_path(path), 0700)
104 return path
1050
=== removed directory 'ubuntu_sso/xdg_base_directory/tests'
=== removed file 'ubuntu_sso/xdg_base_directory/tests/__init__.py'
--- ubuntu_sso/xdg_base_directory/tests/__init__.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/xdg_base_directory/tests/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,29 +0,0 @@
1# ubuntu_sso - Ubuntu Single Sign On client support for desktop apps
2#
3# Copyright 2009-2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""XDG tests."""
300
=== removed file 'ubuntu_sso/xdg_base_directory/tests/test_common.py'
--- ubuntu_sso/xdg_base_directory/tests/test_common.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/xdg_base_directory/tests/test_common.py 1970-01-01 00:00:00 +0000
@@ -1,91 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2011-2012 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19# In addition, as a special exception, the copyright holders give
20# permission to link the code of portions of this program with the
21# OpenSSL library under certain conditions as described in each
22# individual source file, and distribute linked combinations
23# including the two.
24# You must obey the GNU General Public License in all respects
25# for all of the code used other than OpenSSL. If you modify
26# file(s) with this exception, you may extend this exception to your
27# version of the file(s), but you are not obligated to do so. If you
28# do not wish to do so, delete this exception statement from your
29# version. If you delete this exception statement from all source
30# files in the program, then also delete it here.
31"""Platform independent tests for the XDG constants."""
32
33import os
34
35from twisted.trial.unittest import TestCase
36
37from ubuntu_sso import xdg_base_directory
38
39
40class TestBaseDirectory(TestCase):
41 """Tests for the BaseDirectory module."""
42
43 def assert_utf8_bytes(self, value):
44 """Check that 'value' is a bytes sequence encoded with utf-8."""
45 self.assertIsInstance(value, str)
46 try:
47 value.decode('utf-8')
48 except UnicodeDecodeError:
49 self.fail('%r should be a utf8 encoded string.' % value)
50
51 def test_xdg_home_is_utf8_bytes(self):
52 """The returned path is bytes."""
53 actual = xdg_base_directory.xdg_home
54 self.assert_utf8_bytes(actual)
55
56 def test_xdg_cache_home_is_utf8_bytes(self):
57 """The returned path is bytes."""
58 actual = xdg_base_directory.xdg_cache_home
59 self.assert_utf8_bytes(actual)
60
61 def test_xdg_config_home_is_utf8_bytes(self):
62 """The returned path is bytes."""
63 actual = xdg_base_directory.xdg_config_home
64 self.assert_utf8_bytes(actual)
65
66 def test_xdg_config_dirs_are_bytes(self):
67 """The returned path is bytes."""
68 result = xdg_base_directory.xdg_config_dirs
69 for actual in result:
70 self.assert_utf8_bytes(actual)
71
72 def test_xdg_data_home_is_utf8_bytes(self):
73 """The returned path is bytes."""
74 actual = xdg_base_directory.xdg_data_home
75 self.assert_utf8_bytes(actual)
76
77 def test_xdg_data_dirs_are_bytes(self):
78 """The returned path is bytes."""
79 result = xdg_base_directory.xdg_data_dirs
80 for actual in result:
81 self.assert_utf8_bytes(actual)
82
83 def test_load_config_paths_filter(self):
84 """Since those folders don't exist, this should be empty."""
85 self.assertEqual(list(xdg_base_directory.load_config_paths("x")), [])
86
87 def test_save_config_path(self):
88 """The path should end with xdg_config/x (respecting the separator)."""
89 self.patch(os, "makedirs", lambda *args: None)
90 result = xdg_base_directory.save_config_path("x")
91 self.assertEqual(result.split(os.sep)[-2:], ['xdg_config', 'x'])
920
=== removed file 'ubuntu_sso/xdg_base_directory/tests/test_windows.py'
--- ubuntu_sso/xdg_base_directory/tests/test_windows.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/xdg_base_directory/tests/test_windows.py 1970-01-01 00:00:00 +0000
@@ -1,157 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2011-2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Windows-specific tests for the XDG constants."""
30
31import os
32import sys
33
34from twisted.trial.unittest import TestCase
35
36
37# pylint: disable=E1101, E0611, F0401
38import win32com.shell
39from ubuntu_sso.xdg_base_directory.windows import (
40 get_config_dirs,
41 get_data_dirs,
42 get_env_path,
43 get_special_folders,
44)
45
46
47class FakeShellConModule(object):
48 """Override CSIDL_ constants."""
49
50 CSIDL_PROFILE = 0
51 CSIDL_LOCAL_APPDATA = 1
52 CSIDL_COMMON_APPDATA = 2
53
54
55class FakeShellModule(object):
56
57 """Fake Shell Module."""
58
59 def __init__(self):
60 """Set the proper mapping between CSIDL_ consts."""
61 self.values = {
62 0: u'c:\\path\\to\\users\\home',
63 1: u'c:\\path\\to\\users\\home\\appData\\local',
64 2: u'c:\\programData',
65 }
66
67 # pylint: disable=C0103
68 def SHGetFolderPath(self, dummy0, shellconValue, dummy2, dummy3):
69 """Override SHGetFolderPath functionality."""
70 return self.values[shellconValue]
71 # pylint: enable=C0103
72
73
74class TestBaseDirectoryWindows(TestCase):
75 """Tests for the BaseDirectory module."""
76
77 def test_get_special_folders(self):
78 """Make sure we can import the platform module."""
79
80 shell_module = FakeShellModule()
81 self.patch(win32com.shell, "shell", shell_module)
82 self.patch(win32com.shell, "shellcon", FakeShellConModule())
83 special_folders = get_special_folders()
84 self.assertTrue('Personal' in special_folders)
85 self.assertTrue('Local AppData' in special_folders)
86 self.assertTrue('AppData' in special_folders)
87 self.assertTrue('Common AppData' in special_folders)
88
89 self.assertTrue(special_folders['Personal'] == \
90 shell_module.values[FakeShellConModule.CSIDL_PROFILE])
91 self.assertTrue(special_folders['Local AppData'] == \
92 shell_module.values[FakeShellConModule.CSIDL_LOCAL_APPDATA])
93 self.assertTrue(special_folders['Local AppData'].startswith(
94 special_folders['AppData']))
95 self.assertTrue(special_folders['Common AppData'] == \
96 shell_module.values[FakeShellConModule.CSIDL_COMMON_APPDATA])
97
98 for val in special_folders.itervalues():
99 self.assertIsInstance(val, str)
100 val.decode('utf8')
101 # Should not raise exceptions
102
103 def test_get_data_dirs(self):
104 """Check thet get_data_dirs uses pathsep correctly."""
105 bad_sep = filter(lambda x: x not in os.pathsep, ":;")
106 dir_list = ["A", "B", bad_sep, "C"]
107 self.patch(os, "environ",
108 dict(XDG_DATA_DIRS=os.pathsep.join(
109 dir_list)))
110 dirs = get_data_dirs()
111 self.assertEqual(dirs, dir_list)
112
113 def test_get_config_dirs(self):
114 """Check thet get_data_dirs uses pathsep correctly."""
115 bad_sep = filter(lambda x: x not in os.pathsep, ":;")
116 dir_list = ["A", "B", bad_sep, "C"]
117 self.patch(os, "environ",
118 dict(XDG_CONFIG_DIRS=os.pathsep.join(
119 dir_list)))
120 dirs = get_config_dirs()[1:]
121 self.assertEqual(dirs, dir_list)
122
123 def set_fake_environ(self, key, value):
124 """Set (and restore) a fake environ variable."""
125 if key in os.environ:
126 prev = os.environ[key]
127 self.addCleanup(os.environ.__setitem__, key, prev)
128 else:
129 self.addCleanup(os.environ.__delitem__, key)
130 os.environ[key] = value
131
132 def unset_fake_environ(self, key):
133 """Unset (and restore) a fake environ variable."""
134 if key in os.environ:
135 current_value = os.environ[key]
136 self.addCleanup(os.environ.__setitem__, key, current_value)
137 del(os.environ[key])
138
139 def test_get_env_path_var(self):
140 """Test that get_env_path transforms an env var."""
141 fake_path = u"C:\\Users\\Ñandú"
142 fake_env_var = "FAKE_ENV_VAR"
143
144 mbcs_path = fake_path.encode(sys.getfilesystemencoding())
145 utf8_path = fake_path.encode("utf-8")
146
147 self.set_fake_environ(fake_env_var, mbcs_path)
148 self.assertEqual(get_env_path(fake_env_var, "unexpected"), utf8_path)
149
150 def test_get_env_path_no_var(self):
151 """Test that get_env_path returns the default when env var not set."""
152 fake_path = u"C:\\Users\\Ñandú"
153 fake_env_var = "fake_env_var"
154 default = fake_path.encode(sys.getfilesystemencoding())
155
156 self.unset_fake_environ(fake_env_var)
157 self.assertEqual(get_env_path(fake_env_var, default), default)
1580
=== removed file 'ubuntu_sso/xdg_base_directory/windows.py'
--- ubuntu_sso/xdg_base_directory/windows.py 2012-04-09 17:38:24 +0000
+++ ubuntu_sso/xdg_base_directory/windows.py 1970-01-01 00:00:00 +0000
@@ -1,113 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2011-2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""XDG helpers for windows."""
30
31import os
32import sys
33
34
35# pylint: disable=C0103
36def get_special_folders():
37 """ Routine to grab all the Windows Special Folders locations.
38
39 If successful, returns dictionary
40 of shell folder locations indexed on Windows keyword for each;
41 otherwise, returns an empty dictionary.
42 """
43 # pylint: disable=W0621, F0401, E0611
44 special_folders = {}
45
46 from win32com.shell import shell, shellcon
47 # CSIDL_LOCAL_APPDATA = C:\Users\<username>\AppData\Local
48 # CSIDL_PROFILE = C:\Users\<username>
49 # CSIDL_COMMON_APPDATA = C:\ProgramData
50 # More information on these constants at
51 # http://msdn.microsoft.com/en-us/library/bb762494
52
53 # as per http://msdn.microsoft.com/en-us/library/windows/desktop/bb762181,
54 # SHGetFolderPath is deprecated, we should migrate to SHGetKnownFolderPath
55 # (http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188)
56 get_path = lambda name: shell.SHGetFolderPath(
57 0, getattr(shellcon, name), None, 0).encode('utf8')
58 special_folders['Personal'] = get_path("CSIDL_PROFILE")
59 special_folders['Local AppData'] = get_path("CSIDL_LOCAL_APPDATA")
60 special_folders['AppData'] = os.path.dirname(
61 special_folders['Local AppData'])
62 special_folders['Common AppData'] = get_path("CSIDL_COMMON_APPDATA")
63 return special_folders
64
65
66def get_env_path(key, default):
67 """Get a utf8 path from an environment variable."""
68 if key in os.environ:
69 # on windows, environment variables are mbcs bytes
70 # so we must turn them into utf-8 Syncdaemon paths
71 path = os.environ.get(key)
72 return path.decode(sys.getfilesystemencoding()).encode("utf-8")
73 else:
74 return default
75
76
77special_folders = get_special_folders()
78
79home_path = special_folders['Personal']
80app_local_data_path = special_folders['Local AppData']
81app_global_data_path = special_folders['Common AppData']
82
83xdg_data_home = get_env_path('XDG_DATA_HOME',
84 os.path.join(app_local_data_path, 'xdg'))
85
86xdg_cache_home = get_env_path('XDG_CACHE_HOME',
87 os.path.join(xdg_data_home, 'cache'))
88
89xdg_config_home = get_env_path('XDG_CONFIG_HOME',
90 app_local_data_path)
91
92
93def get_data_dirs():
94 """Returns XDG data directories."""
95 return get_env_path('XDG_DATA_DIRS',
96 '{0}{1}{2}'.format(app_local_data_path, os.pathsep,
97 app_global_data_path)).split(os.pathsep)
98
99
100xdg_data_dirs = get_data_dirs()
101
102
103def get_config_dirs():
104 """Return XDG config directories."""
105 return [xdg_config_home] + \
106 get_env_path('XDG_CONFIG_DIRS',
107 app_global_data_path,
108 ).split(os.pathsep)
109
110xdg_config_dirs = get_config_dirs()
111
112xdg_data_dirs = filter(lambda x: x, xdg_data_dirs)
113xdg_config_dirs = filter(lambda x: x, xdg_config_dirs)

Subscribers

People subscribed via source and target branches

to all changes: