Merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.8 into lp:ubuntu/natty/ubuntu-sso-client

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 23
Proposed branch: lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.8
Merge into: lp:ubuntu/natty/ubuntu-sso-client
Diff against target: 2287 lines (+676/-719)
21 files modified
PKG-INFO (+1/-1)
bin/ubuntu-sso-login (+8/-4)
contrib/__init__.py (+0/-18)
contrib/dbus_util.py (+0/-77)
contrib/test (+0/-146)
contrib/testing/__init__.py (+0/-1)
contrib/testing/dbus-session.conf (+0/-63)
contrib/testing/testcase.py (+0/-123)
debian/changelog (+18/-0)
run-tests (+13/-4)
setup.py (+3/-4)
ubuntu_sso/credentials.py (+24/-32)
ubuntu_sso/gtk/gui.py (+27/-32)
ubuntu_sso/gtk/tests/test_gui.py (+156/-87)
ubuntu_sso/logger.py (+1/-0)
ubuntu_sso/main.py (+53/-4)
ubuntu_sso/tests/__init__.py (+14/-0)
ubuntu_sso/tests/bin/show_gui (+7/-6)
ubuntu_sso/tests/bin/show_nm_state (+3/-3)
ubuntu_sso/tests/test_credentials.py (+56/-100)
ubuntu_sso/tests/test_main.py (+292/-14)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.8
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+46049@code.launchpad.net

Description of the change

  * New upstream release:

    [ Natalia B. Bidart <email address hidden>]
     - The service should shutdown when unused
       (LP: #701606),
     - On error, {find,clear,store}_credentials send proper CredentialsError
       signal (LP: #696676).
     - No more gobject dependency on non-GUI classes!
       (LP: #695798).
     - After login, if the storing of credentials fails, send LoginError
       (LP: #693531).
     - Use ubuntuone-dev-tools to run the tests and the lint checker
       (LP: #686606).

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2010-12-16 16:31:34 +0000
3+++ PKG-INFO 2011-01-13 00:33:10 +0000
4@@ -1,6 +1,6 @@
5 Metadata-Version: 1.1
6 Name: ubuntu-sso-client
7-Version: 1.1.7
8+Version: 1.1.8
9 Summary: Ubuntu Single Sign-On client
10 Home-page: https://launchpad.net/ubuntu-sso-client
11 Author: Natalia Bidart
12
13=== modified file 'bin/ubuntu-sso-login'
14--- bin/ubuntu-sso-login 2010-10-11 13:22:16 +0000
15+++ bin/ubuntu-sso-login 2011-01-13 00:33:10 +0000
16@@ -21,7 +21,9 @@
17
18 """Run the dbus service for UserManagement and ApplicationCredentials."""
19
20-# import decimal even if we don't need it.
21+# Invalid name "ubuntu-sso-login", pylint: disable=C0103
22+
23+# import decimal even if we don't need it, pylint: disable=W0611
24 import decimal
25 # This is a workaround for LP: #467397. Some module in our depency chain sets
26 # the locale and imports decimal, and that generates the following trace:
27@@ -70,8 +72,8 @@
28 # See the link below for info:
29 # www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html
30 #
31- # gtk.main_quit and the logger methods are safe to be called from any thread.
32- # Just don't call other random stuff here.
33+ # gtk.main_quit and the logger methods are safe to be called from any
34+ # thread. Just don't call other random stuff here.
35 logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.")
36 gtk.main_quit()
37
38@@ -92,6 +94,8 @@
39 bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())
40 SSOLogin(bus_name, object_path=DBUS_ACCOUNT_PATH)
41 SSOCredentials(bus_name, object_path=DBUS_CRED_PATH)
42- CredentialsManagement(bus_name, object_path=DBUS_CREDENTIALS_PATH)
43+ CredentialsManagement(timeout_func=gtk.timeout_add,
44+ shutdown_func=gtk.main_quit,
45+ bus_name=bus_name, object_path=DBUS_CREDENTIALS_PATH)
46
47 gtk.main()
48
49=== removed directory 'contrib'
50=== removed file 'contrib/__init__.py'
51--- contrib/__init__.py 2010-06-22 14:18:04 +0000
52+++ contrib/__init__.py 1970-01-01 00:00:00 +0000
53@@ -1,18 +0,0 @@
54-# contrib - Extra required code to build/install the client
55-#
56-# Author: Rodney Dawes <rodney.dawes@canonical.com>
57-#
58-# Copyright 2009-2010 Canonical Ltd.
59-#
60-# This program is free software: you can redistribute it and/or modify it
61-# under the terms of the GNU General Public License version 3, as published
62-# by the Free Software Foundation.
63-#
64-# This program is distributed in the hope that it will be useful, but
65-# WITHOUT ANY WARRANTY; without even the implied warranties of
66-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
67-# PURPOSE. See the GNU General Public License for more details.
68-#
69-# You should have received a copy of the GNU General Public License along
70-# with this program. If not, see <http://www.gnu.org/licenses/>.
71-"""Extra things we need to build, test, or install the client."""
72
73=== removed file 'contrib/dbus_util.py'
74--- contrib/dbus_util.py 2010-09-08 19:25:02 +0000
75+++ contrib/dbus_util.py 1970-01-01 00:00:00 +0000
76@@ -1,77 +0,0 @@
77-#
78-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
79-#
80-#
81-# This program is free software: you can redistribute it and/or modify it
82-# under the terms of the GNU General Public License version 3, as published
83-# by the Free Software Foundation.
84-#
85-# This program is distributed in the hope that it will be useful, but
86-# WITHOUT ANY WARRANTY; without even the implied warranties of
87-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
88-# PURPOSE. See the GNU General Public License for more details.
89-#
90-# You should have received a copy of the GNU General Public License along
91-# with this program. If not, see <http://www.gnu.org/licenses/>.
92-
93-"""Utilies to run a separated DBus session."""
94-
95-import os
96-import signal
97-import subprocess
98-
99-from distutils.spawn import find_executable
100-
101-SRCDIR = os.environ.get('SRCDIR', os.getcwd())
102-
103-
104-class DBusLaunchError(Exception):
105- """Error while launching dbus-daemon."""
106-
107-
108-class NotFoundError(Exception):
109- """Not found error."""
110-
111-
112-class DBusRunner(object):
113- """A DBus runner."""
114-
115- # pylint: disable=C0103
116-
117- def __init__(self):
118- self.dbus_address = None
119- self.dbus_pid = None
120- self.running = False
121-
122- def startDBus(self):
123- """Start our own session bus daemon for testing."""
124- dbus = find_executable("dbus-daemon")
125- if not dbus:
126- raise NotFoundError("dbus-daemon was not found.")
127-
128- config_file = os.path.join(os.path.abspath(SRCDIR),
129- "contrib", "testing",
130- "dbus-session.conf")
131- dbus_args = ["--fork",
132- "--config-file=" + config_file,
133- "--print-address=1",
134- "--print-pid=2"]
135- p = subprocess.Popen([dbus] + dbus_args,
136- bufsize=4096, stdout=subprocess.PIPE,
137- stderr=subprocess.PIPE)
138-
139- self.dbus_address = "".join(p.stdout.readlines()).strip()
140- self.dbus_pid = int("".join(p.stderr.readlines()).strip())
141-
142- if self.dbus_address != "":
143- os.environ["DBUS_SESSION_BUS_ADDRESS"] = self.dbus_address
144- else:
145- os.kill(self.dbus_pid, signal.SIGKILL)
146- raise DBusLaunchError("There was a problem launching dbus-daemon.")
147- self.running = True
148-
149- def stopDBus(self):
150- """Stop our DBus session bus daemon."""
151- del os.environ["DBUS_SESSION_BUS_ADDRESS"]
152- os.kill(self.dbus_pid, signal.SIGKILL)
153- self.running = False
154
155=== removed file 'contrib/test'
156--- contrib/test 2010-09-08 19:25:02 +0000
157+++ contrib/test 1970-01-01 00:00:00 +0000
158@@ -1,146 +0,0 @@
159-#!/usr/bin/env python
160-#
161-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
162-#
163-# Copyright 2009-2010 Canonical Ltd.
164-#
165-# This program is free software: you can redistribute it and/or modify it
166-# under the terms of the GNU General Public License version 3, as published
167-# by the Free Software Foundation.
168-#
169-# This program is distributed in the hope that it will be useful, but
170-# WITHOUT ANY WARRANTY; without even the implied warranties of
171-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
172-# PURPOSE. See the GNU General Public License for more details.
173-#
174-# You should have received a copy of the GNU General Public License along
175-# with this program. If not, see <http://www.gnu.org/licenses/>.
176-
177-import os
178-import re
179-import signal
180-import sys
181-import string
182-import subprocess
183-import unittest
184-
185-sys.path.insert(0, os.path.abspath("."))
186-
187-from distutils.spawn import find_executable
188-
189-
190-class TestRunner(object):
191-
192- def _load_unittest(self, relpath):
193- """Load unittests from a Python module with the given relative path."""
194- assert relpath.endswith(".py"), (
195- "%s does not appear to be a Python module" % relpath)
196- modpath = relpath.replace(os.path.sep, ".")[:-3]
197- module = __import__(modpath, None, None, [""])
198-
199- # If the module has a 'suite' or 'test_suite' function, use that
200- # to load the tests.
201- if hasattr(module, "suite"):
202- return module.suite()
203- elif hasattr(module, "test_suite"):
204- return module.test_suite()
205- else:
206- return unittest.defaultTestLoader.loadTestsFromModule(module)
207-
208- def _collect_tests(self, testpath, test_pattern):
209- """Return the set of unittests."""
210- suite = unittest.TestSuite()
211- if test_pattern:
212- pattern = re.compile('.*%s.*' % test_pattern)
213- else:
214- pattern = None
215-
216- if testpath:
217- module_suite = self._load_unittest(testpath)
218- if pattern:
219- for inner_suite in module_suite._tests:
220- for test in inner_suite._tests:
221- if pattern.match(test.id()):
222- suite.addTest(test)
223- else:
224- suite.addTests(module_suite)
225- return suite
226-
227- # We don't use the dirs variable, so ignore the warning
228- # pylint: disable=W0612
229- for root, dirs, files in os.walk("ubuntu_sso"):
230- for file in files:
231- path = os.path.join(root, file)
232- if file.endswith(".py") and file.startswith("test_"):
233- module_suite = self._load_unittest(path)
234- if pattern:
235- for inner_suite in module_suite._tests:
236- for test in inner_suite._tests:
237- if pattern.match(test.id()):
238- suite.addTest(test)
239- else:
240- suite.addTests(module_suite)
241- return suite
242-
243- def run(self, testpath, test_pattern=None, loops=None):
244- """run the tests. """
245- # install the glib2reactor before any import of the reactor to avoid
246- # using the default SelectReactor and be able to run the dbus tests
247- from twisted.internet import glib2reactor
248- glib2reactor.install()
249- from twisted.internet import reactor
250- from twisted.trial.reporter import TreeReporter
251- from twisted.trial.runner import TrialRunner
252-
253- from contrib.dbus_util import DBusRunner
254- dbus_runner = DBusRunner()
255- dbus_runner.startDBus()
256-
257- workingDirectory = os.path.join(os.getcwd(), "_trial_temp", "tmp")
258- runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True,
259- workingDirectory=workingDirectory)
260-
261- # setup a custom XDG_CACHE_HOME and create the logs directory
262- xdg_cache = os.path.join(os.getcwd(), "_trial_temp", "xdg_cache")
263- os.environ["XDG_CACHE_HOME"] = xdg_cache
264- # setup the ROOTDIR env var
265- os.environ['ROOTDIR'] = os.getcwd()
266- if not os.path.exists(xdg_cache):
267- os.makedirs(xdg_cache)
268- success = 0
269- try:
270- suite = self._collect_tests(testpath, test_pattern)
271- if loops:
272- old_suite = suite
273- suite = unittest.TestSuite()
274- for x in xrange(loops):
275- suite.addTest(old_suite)
276- result = runner.run(suite)
277- success = result.wasSuccessful()
278- finally:
279- dbus_runner.stopDBus()
280- if not success:
281- sys.exit(1)
282- else:
283- sys.exit(0)
284-
285-
286-if __name__ == '__main__':
287- from optparse import OptionParser
288- usage = '%prog [options] path'
289- parser = OptionParser(usage=usage)
290- parser.add_option("-t", "--test", dest="test",
291- help="run specific tests, e.g: className.methodName")
292- parser.add_option("-l", "--loop", dest="loops", type="int", default=1,
293- help="loop selected tests LOOPS number of times",
294- metavar="LOOPS")
295-
296- (options, args) = parser.parse_args()
297- if args:
298- testpath = args[0]
299- if not os.path.exists(testpath):
300- print "the path to test does not exists!"
301- sys.exit()
302- else:
303- testpath = None
304- TestRunner().run(testpath, options.test, options.loops)
305
306=== removed directory 'contrib/testing'
307=== removed file 'contrib/testing/__init__.py'
308--- contrib/testing/__init__.py 2010-09-08 19:25:02 +0000
309+++ contrib/testing/__init__.py 1970-01-01 00:00:00 +0000
310@@ -1,1 +0,0 @@
311-"""Testing utilities for Ubuntu SSO code."""
312
313=== removed file 'contrib/testing/dbus-session.conf'
314--- contrib/testing/dbus-session.conf 2010-06-18 20:51:58 +0000
315+++ contrib/testing/dbus-session.conf 1970-01-01 00:00:00 +0000
316@@ -1,63 +0,0 @@
317-<!-- This configuration file controls our test-only session bus -->
318-
319-<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
320- "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
321-<busconfig>
322- <!-- We only use a session bus -->
323- <type>session</type>
324-
325- <listen>unix:tmpdir=/tmp</listen>
326-
327- <!-- Load our own services.
328- To make other dbus service in this session bus, just add another servicedir entry. -->
329- <servicedir>dbus-session</servicedir>
330- <!-- Load the standard session services -->
331- <!--standard_session_servicedirs /-->
332-
333- <policy context="default">
334- <!-- Allow everything to be sent -->
335- <allow send_destination="*" eavesdrop="true"/>
336- <!-- Allow everything to be received -->
337- <allow eavesdrop="true"/>
338- <!-- Allow anyone to own anything -->
339- <allow own="*"/>
340- </policy>
341-
342- <!-- Config files are placed here that among other things,
343- further restrict the above policy for specific services. -->
344- <includedir>/etc/dbus-1/session.d</includedir>
345-
346- <!-- raise the service start timeout to 40 seconds as it can timeout
347- on the live cd on slow machines -->
348- <limit name="service_start_timeout">60000</limit>
349-
350- <!-- This is included last so local configuration can override what's
351- in this standard file -->
352- <include ignore_missing="yes">session-local.conf</include>
353-
354- <include ignore_missing="yes" if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
355-
356- <!-- For the session bus, override the default relatively-low limits
357- with essentially infinite limits, since the bus is just running
358- as the user anyway, using up bus resources is not something we need
359- to worry about. In some cases, we do set the limits lower than
360- "all available memory" if exceeding the limit is almost certainly a bug,
361- having the bus enforce a limit is nicer than a huge memory leak. But the
362- intent is that these limits should never be hit. -->
363-
364- <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max -->
365- <limit name="max_incoming_bytes">1000000000</limit>
366- <limit name="max_outgoing_bytes">1000000000</limit>
367- <limit name="max_message_size">1000000000</limit>
368- <limit name="service_start_timeout">120000</limit>
369- <limit name="auth_timeout">240000</limit>
370- <limit name="max_completed_connections">100000</limit>
371- <limit name="max_incomplete_connections">10000</limit>
372- <limit name="max_connections_per_user">100000</limit>
373- <limit name="max_pending_service_starts">10000</limit>
374- <limit name="max_names_per_connection">50000</limit>
375- <limit name="max_match_rules_per_connection">50000</limit>
376- <limit name="max_replies_per_connection">50000</limit>
377- <limit name="reply_timeout">300000</limit>
378-
379-</busconfig>
380
381=== removed file 'contrib/testing/testcase.py'
382--- contrib/testing/testcase.py 2010-10-11 13:22:16 +0000
383+++ contrib/testing/testcase.py 1970-01-01 00:00:00 +0000
384@@ -1,123 +0,0 @@
385-#
386-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
387-# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
388-#
389-# Copyright 2009-2010 Canonical Ltd.
390-#
391-# This program is free software: you can redistribute it and/or modify it
392-# under the terms of the GNU General Public License version 3, as published
393-# by the Free Software Foundation.
394-#
395-# This program is distributed in the hope that it will be useful, but
396-# WITHOUT ANY WARRANTY; without even the implied warranties of
397-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
398-# PURPOSE. See the GNU General Public License for more details.
399-#
400-# You should have received a copy of the GNU General Public License along
401-# with this program. If not, see <http://www.gnu.org/licenses/>.
402-"""Base tests cases and test utilities."""
403-
404-import logging
405-
406-import dbus
407-
408-from dbus.mainloop.glib import DBusGMainLoop
409-from twisted.internet import defer
410-from twisted.python import failure
411-from twisted.trial.unittest import TestCase
412-
413-
414-class DBusTestCase(TestCase):
415- """Test the DBus event handling."""
416-
417- def setUp(self):
418- """Setup the infrastructure fo the test (dbus service)."""
419- self.loop = DBusGMainLoop(set_as_default=True)
420- self.bus = dbus.bus.BusConnection(mainloop=self.loop)
421- # monkeypatch busName.__del__ to avoid errors on gc
422- # we take care of releasing the name in shutdown
423- dbus.service.BusName.__del__ = lambda _: None
424- self.bus.set_exit_on_disconnect(False)
425- self.signal_receivers = set()
426-
427- def tearDown(self):
428- """Cleanup the test."""
429- d = self.cleanup_signal_receivers(self.signal_receivers)
430- d.addBoth(self._tear_down)
431- return d
432-
433- def _tear_down(self, _):
434- """Shutdown."""
435- self.bus.flush()
436- self.bus.close()
437-
438- def error_handler(self, error):
439- """Default error handler for DBus calls."""
440- if isinstance(error, failure.Failure):
441- self.fail(error.getErrorMessage())
442-
443- def cleanup_signal_receivers(self, signal_receivers):
444- """Cleanup self.signal_receivers and returns a deferred."""
445- deferreds = []
446- for match in signal_receivers:
447- d = defer.Deferred()
448-
449- def callback(*args):
450- """Callback that accepts *args."""
451- if not d.called:
452- d.callback(args)
453-
454- self.bus.call_async(dbus.bus.BUS_DAEMON_NAME,
455- dbus.bus.BUS_DAEMON_PATH,
456- dbus.bus.BUS_DAEMON_IFACE, 'RemoveMatch', 's',
457- (str(match),), callback, self.error_handler)
458- deferreds.append(d)
459- if deferreds:
460- return defer.DeferredList(deferreds)
461- else:
462- return defer.succeed(True)
463-
464-
465-class MementoHandler(logging.Handler):
466- """A handler class which store logging records in a list."""
467-
468- def __init__(self, *args, **kwargs):
469- """Create the instance, and add a records attribute."""
470- logging.Handler.__init__(self, *args, **kwargs)
471- self.records = []
472-
473- def emit(self, record):
474- """Just add the record to self.records."""
475- self.records.append(record)
476-
477- def check(self, level, *msgs):
478- """Verifies that the msgs are logged in the specified level."""
479- for rec in self.records:
480- if rec.levelno == level and all(m in rec.message for m in msgs):
481- return True
482- return False
483-
484- def check_debug(self, *msgs):
485- """Shortcut for checking in DEBUG."""
486- return self.check(logging.DEBUG, *msgs)
487-
488- def check_info(self, *msgs):
489- """Shortcut for checking in INFO."""
490- return self.check(logging.INFO, *msgs)
491-
492- def check_warning(self, *msgs):
493- """Shortcut for checking in WARNING."""
494- return self.check(logging.WARNING, *msgs)
495-
496- def check_error(self, *msgs):
497- """Shortcut for checking in ERROR."""
498- return self.check(logging.ERROR, *msgs)
499-
500- def check_exception(self, exception_class, *msgs):
501- """Shortcut for checking exceptions."""
502- for rec in self.records:
503- if rec.levelno == logging.ERROR and \
504- all(m in rec.exc_text for m in msgs) and \
505- exception_class == rec.exc_info[0]:
506- return True
507- return False
508
509=== modified file 'debian/changelog'
510--- debian/changelog 2010-12-17 20:25:51 +0000
511+++ debian/changelog 2011-01-13 00:33:10 +0000
512@@ -1,3 +1,21 @@
513+ubuntu-sso-client (1.1.8-0ubuntu1) UNRELEASED; urgency=low
514+
515+ * New upstream release:
516+
517+ [ Natalia B. Bidart <natalia.bidart@canonical.com>]
518+ - The service should shutdown when unused
519+ (LP: #701606),
520+ - On error, {find,clear,store}_credentials send proper CredentialsError
521+ signal (LP: #696676).
522+ - No more gobject dependency on non-GUI classes!
523+ (LP: #695798).
524+ - After login, if the storing of credentials fails, send LoginError
525+ (LP: #693531).
526+ - Use ubuntuone-dev-tools to run the tests and the lint checker
527+ (LP: #686606).
528+
529+ -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Wed, 12 Jan 2011 15:59:23 -0300
530+
531 ubuntu-sso-client (1.1.7-0ubuntu1) natty; urgency=low
532
533 * New upstream release.
534
535=== modified file 'run-tests'
536--- run-tests 2010-10-11 13:22:16 +0000
537+++ run-tests 2011-01-13 00:33:10 +0000
538@@ -15,14 +15,23 @@
539 # You should have received a copy of the GNU General Public License along
540 # with this program. If not, see <http://www.gnu.org/licenses/>.
541
542+if [ $# -ne 0 ]; then
543+ # an extra argument was given
544+ MODULE="$@"
545+else
546+ # run all tests, useful for tarmac and reviews
547+ MODULE="ubuntu_sso"
548+fi
549+
550 style_check() {
551- pylint contrib ubuntu_sso
552+ u1lint
553 if [ -x `which pep8` ]; then
554- pep8 --repeat bin/ contrib/ ubuntu_sso/
555+ pep8 --repeat bin/ $MODULE
556 else
557 echo "Please install the 'pep8' package."
558 fi
559 }
560
561-`which xvfb-run` ./contrib/test "$@" && style_check
562-rm -rf _trial_temp/
563+echo "Running test suite for ""$MODULE"
564+`which xvfb-run` u1trial "$MODULE" && style_check
565+rm -rf _trial_temp
566
567=== modified file 'setup.py'
568--- setup.py 2010-12-16 16:31:34 +0000
569+++ setup.py 2011-01-13 00:33:10 +0000
570@@ -31,16 +31,15 @@
571 assert DistUtilsExtra.auto.__version__ >= '2.18', \
572 'needs DistUtilsExtra.auto >= 2.18'
573
574-from distutils.core import setup
575 from distutils.spawn import find_executable
576-from distutils.command import clean, build
577+from distutils.command import clean
578
579 # Defining variables for various rules here, similar to a Makefile.am
580 CLEANFILES = ['data/com.ubuntu.sso.service', 'po/ubuntu-sso-client.pot',
581 'MANIFEST']
582
583
584-# XXX: This needs some serious cleanup
585+# This needs some serious cleanup
586 class SSOBuild(build_extra.build_extra):
587 """Class to build the extra files."""
588
589@@ -87,7 +86,7 @@
590
591 DistUtilsExtra.auto.setup(
592 name='ubuntu-sso-client',
593- version='1.1.7',
594+ version='1.1.8',
595 license='GPL v3',
596 author='Natalia Bidart',
597 author_email='natalia.bidart@canonical.com',
598
599=== modified file 'ubuntu_sso/credentials.py'
600--- ubuntu_sso/credentials.py 2010-11-30 13:21:17 +0000
601+++ ubuntu_sso/credentials.py 2011-01-13 00:33:10 +0000
602@@ -22,10 +22,12 @@
603
604 * find_credentials
605 * clear_credentials
606+ * store_credentials
607 * register
608 * login
609
610-The first two returns immediately (for now).
611+The first three return a Deferred that will be fired when the operation was
612+completed.
613
614 The second two use the 'success_cb', 'error_cb' and 'denial_cb' to signal the
615 caller when the credentials were retrieved successfully, when there was an
616@@ -41,8 +43,6 @@
617
618 from functools import wraps
619
620-import gobject
621-
622 from oauth import oauth
623 from twisted.internet.defer import inlineCallbacks, returnValue
624
625@@ -187,13 +187,15 @@
626
627 @handle_failures(msg='Problem while retrieving credentials')
628 @inlineCallbacks
629- def _login_success_cb(self, dialog, app_name, email):
630+ def _login_success_cb(self, app_name, email):
631 """Store credentials when the login/registration succeeded.
632
633 Also, open self.ping_url/email to notify about this new token. If any
634 error occur, self.error_cb is called. Otherwise, self.success_cb is
635 called.
636
637+ Return 0 on success, and a non-zero value (or None) on error.
638+
639 """
640 logger.info('Login/registration was successful for app %r, email %r',
641 app_name, email)
642@@ -202,19 +204,21 @@
643 assert len(creds) > 0, 'Creds are empty! This should not happen'
644 # ping a server with the credentials if we were requested to
645 if self.ping_url is not None:
646- status = self._ping_url(app_name, email, creds)
647+ status = yield self._ping_url(app_name, email, creds)
648 if status is None:
649 yield self.clear_credentials()
650 return
651
652 self.success_cb(creds)
653+ returnValue(0)
654
655- def _auth_denial_cb(self, dialog, app_name):
656+ def _auth_denial_cb(self, app_name):
657 """The user decided not to allow the registration or login."""
658 logger.warning('Login/registration was denied to app %r', app_name)
659 self.denial_cb(app_name)
660
661- @handle_exceptions(msg='Problem opening the ping_url')
662+ @handle_failures(msg='Problem opening the ping_url')
663+ @inlineCallbacks
664 def _ping_url(self, app_name, email, credentials):
665 """Ping the self.ping_url with the email attached.
666
667@@ -237,9 +241,12 @@
668 request = urllib2.Request(url, headers=oauth_req.to_header())
669 logger.debug('Opening the url "%s" with urllib2.urlopen.',
670 request.get_full_url())
671+ # This code is blocking, we should change this.
672+ # I've tried with deferToThread an twisted.web.client.getPage
673+ # but the returned deferred will never be fired (nataliabidart).
674 response = urllib2.urlopen(request)
675 logger.debug('Url opened. Response: %s.', response.code)
676- return response.code
677+ returnValue(response.code)
678
679 @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface')
680 def _show_ui(self, login_only):
681@@ -252,10 +259,9 @@
682 tc_url=self.tc_url, help_text=self.help_text,
683 window_id=self.window_id, login_only=login_only)
684
685- self.gui.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb)
686- self.gui.connect(gui.SIG_REGISTRATION_SUCCEEDED,
687- self._login_success_cb)
688- self.gui.connect(gui.SIG_USER_CANCELATION, self._auth_denial_cb)
689+ self.gui.login_success_callback = self._login_success_cb
690+ self.gui.registration_success_callback = self._login_success_cb
691+ self.gui.user_cancellation_callback = self._auth_denial_cb
692
693 @handle_failures(msg='Problem while retrieving credentials')
694 @inlineCallbacks
695@@ -265,35 +271,23 @@
696 if token is not None and len(token) > 0:
697 self.success_cb(token)
698 elif token == {}:
699- gobject.idle_add(self._show_ui, login_only)
700+ self._show_ui(login_only)
701 else:
702 # something went wrong with find_credentials, already handled.
703 logger.info('_login_or_register: call to "find_credentials" went '
704 'wrong, and was already handled.')
705
706 def error_cb(self, error_dict):
707- """Handle error.
708-
709- Notify the caller and finish the GUI with error.
710-
711- """
712+ """Handle error and notify the caller."""
713+ logger.error('Calling error callback at %r (error is %r).',
714+ self._error_cb, error_dict)
715 self._error_cb(self.app_name, error_dict)
716- if self.gui is not None:
717- self.gui.finish_error(error=error_dict)
718- self.gui = None
719
720 def success_cb(self, creds):
721- """Handle success.
722-
723- Notify the caller and finish the GUI with success.
724-
725- """
726+ """Handle success and notify the caller."""
727+ logger.debug('Calling success callback at %r.', self._success_cb)
728 self._success_cb(self.app_name, creds)
729- if self.gui is not None:
730- self.gui.finish_success()
731- self.gui = None
732
733- @handle_failures(msg='Problem while retrieving credentials')
734 @inlineCallbacks
735 def find_credentials(self):
736 """Get the credentials for 'self.app_name'. Return {} if not there."""
737@@ -302,13 +296,11 @@
738 'result is {}? %s', self.app_name, creds is None)
739 returnValue(creds if creds is not None else {})
740
741- @handle_failures(msg='Problem while deleting credentials')
742 @inlineCallbacks
743 def clear_credentials(self):
744 """Clear the credentials for 'self.app_name'."""
745 yield Keyring().delete_credentials(self.app_name)
746
747- @handle_failures(msg='Problem while storing credentials')
748 @inlineCallbacks
749 def store_credentials(self, token):
750 """Store the credentials for 'self.app_name'."""
751
752=== modified file 'ubuntu_sso/gtk/gui.py'
753--- ubuntu_sso/gtk/gui.py 2010-11-30 13:21:17 +0000
754+++ ubuntu_sso/gtk/gui.py 2011-01-13 00:33:10 +0000
755@@ -30,11 +30,11 @@
756
757 import dbus
758 import gettext
759-import gobject
760 import gtk
761 import xdg
762
763 from dbus.mainloop.glib import DBusGMainLoop
764+from twisted.internet.defer import inlineCallbacks
765
766 from ubuntu_sso import (DBUS_ACCOUNT_PATH, DBUS_BUS_NAME, DBUS_IFACE_USER_NAME,
767 NO_OP)
768@@ -72,20 +72,6 @@
769 HELP_TEXT_COLOR = gtk.gdk.Color("#bfbfbf")
770 WARNING_TEXT_COLOR = gtk.gdk.Color("red")
771
772-SIG_LOGIN_SUCCEEDED = 'login-succeeded'
773-SIG_REGISTRATION_SUCCEEDED = 'registration-succeeded'
774-SIG_USER_CANCELATION = 'user-cancelation'
775-
776-SIGNAL_ARGUMENTS = [
777- (SIG_LOGIN_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)),
778- (SIG_REGISTRATION_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)),
779- (SIG_USER_CANCELATION, (gobject.TYPE_STRING,)),
780-]
781-
782-for sig, sig_args in SIGNAL_ARGUMENTS:
783- gobject.signal_new(sig, gtk.Window, gobject.SIGNAL_RUN_FIRST,
784- gobject.TYPE_NONE, sig_args)
785-
786
787 def get_data_dir():
788 """Build absolute path to the 'data' directory."""
789@@ -198,7 +184,7 @@
790
791
792 class UbuntuSSOClientGUI(object):
793- """Ubuntu single sign on GUI."""
794+ """Ubuntu single sign-on GUI."""
795
796 CAPTCHA_SOLUTION_ENTRY = _('Type the characters above')
797 CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, '
798@@ -256,7 +242,7 @@
799 CAPTCHA_RELOAD_TOOLTIP = _('Reload')
800
801 def __init__(self, app_name, tc_url='', help_text='',
802- window_id=0, login_only=False, close_callback=None):
803+ window_id=0, login_only=False):
804 """Create the GUI and initialize widgets."""
805 gtk.link_button_set_uri_hook(NO_OP)
806
807@@ -270,7 +256,12 @@
808 self.tc_url = tc_url
809 self.help_text = help_text
810 self.login_only = login_only
811- self.close_callback = close_callback
812+
813+ self.close_callback = NO_OP
814+ self.login_success_callback = NO_OP
815+ self.registration_success_callback = NO_OP
816+ self.user_cancellation_callback = NO_OP
817+
818 self.user_email = None
819 self.user_password = None
820
821@@ -730,17 +721,12 @@
822 signal_name, handler, args, kwargs)
823 self.window.connect(signal_name, handler, *args, **kwargs)
824
825- def emit(self, *args, **kwargs):
826- """Emit a signal proxing the main window."""
827- logger.debug('emit: args %r, kwargs, %r', args, kwargs)
828- self.window.emit(*args, **kwargs)
829-
830 def finish_success(self):
831 """The whole process was completed succesfully. Show success page."""
832 self._done = True
833 self._set_current_page(self.success_vbox)
834
835- def finish_error(self, error):
836+ def finish_error(self):
837 """The whole process was not completed succesfully. Show error page."""
838 self._done = True
839 self._set_current_page(self.error_vbox)
840@@ -766,18 +752,17 @@
841 if self.window is not None:
842 self.window.hide()
843
844- # process any pending events before emitting signals
845+ # process any pending events before callbacking with result
846 while gtk.events_pending():
847 gtk.main_iteration()
848
849 if not self._done:
850- self.emit(SIG_USER_CANCELATION, self.app_name)
851+ self.user_cancellation_callback(self.app_name)
852
853 # call user defined callback
854- if self.close_callback is not None:
855- logger.info('Calling custom close_callback %r with params %r, %r',
856- self.close_callback, args, kwargs)
857- self.close_callback(*args, **kwargs)
858+ logger.info('Calling custom close_callback %r with params %r, %r',
859+ self.close_callback, args, kwargs)
860+ self.close_callback(*args, **kwargs)
861
862 def on_sign_in_button_clicked(self, *args, **kwargs):
863 """User wants to sign in, present the Login page."""
864@@ -1096,10 +1081,15 @@
865 self._set_current_page(self.enter_details_vbox, warning_text=msg)
866
867 @log_call
868+ @inlineCallbacks
869 def on_email_validated(self, app_name, email, *args, **kwargs):
870 """User email was successfully verified."""
871 self._done = True
872- self.emit(SIG_REGISTRATION_SUCCEEDED, self.app_name, email)
873+ result = yield self.registration_success_callback(self.app_name, email)
874+ if result == 0:
875+ self.finish_success()
876+ else:
877+ self.finish_error()
878
879 @log_call
880 def on_email_validation_error(self, app_name, error, *args, **kwargs):
881@@ -1112,10 +1102,15 @@
882 self._set_current_page(self.verify_email_vbox, warning_text=msg)
883
884 @log_call
885+ @inlineCallbacks
886 def on_logged_in(self, app_name, email, *args, **kwargs):
887 """User was successfully logged in."""
888 self._done = True
889- self.emit(SIG_LOGIN_SUCCEEDED, self.app_name, email)
890+ result = yield self.login_success_callback(self.app_name, email)
891+ if result == 0:
892+ self.finish_success()
893+ else:
894+ self.finish_error()
895
896 @log_call
897 def on_login_error(self, app_name, error, *args, **kwargs):
898
899=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
900--- ubuntu_sso/gtk/tests/test_gui.py 2010-11-30 13:21:17 +0000
901+++ ubuntu_sso/gtk/tests/test_gui.py 2011-01-13 00:33:10 +0000
902@@ -30,8 +30,8 @@
903 import webkit
904
905 from twisted.trial.unittest import TestCase
906+from ubuntuone.devtools.handlers import MementoHandler
907
908-from contrib.testing.testcase import MementoHandler
909 from ubuntu_sso.gtk import gui
910 from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID,
911 CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, NAME, PASSWORD, RESET_PASSWORD_TOKEN)
912@@ -517,6 +517,11 @@
913 self.assertTrue(self._called,
914 'close_callback was called when window was closed.')
915
916+ def test_close_callback_if_not_set(self):
917+ """The close_callback is a no op if not set."""
918+ self.ui.on_close_clicked()
919+ # no crash when close_callback is not set
920+
921 def test_app_name_is_stored(self):
922 """The app_name is stored for further use."""
923 self.assertIn(APP_NAME, self.ui.app_name)
924@@ -555,18 +560,6 @@
925
926 self.assertEqual(self.ui.bus.callbacks, {})
927
928- def test_close_callback(self):
929- """A close_callback parameter is called when closing the window."""
930- ui = self.gui_class(close_callback=self._set_called, **self.kwargs)
931- ui.on_close_clicked()
932- self.assertTrue(self._called, 'close_callback was called on close.')
933-
934- def test_close_callback_if_none(self):
935- """A close_callback parameter is not called if is None."""
936- ui = self.gui_class(close_callback=None, **self.kwargs)
937- ui.on_close_clicked()
938- # no crash when close_callback is None
939-
940 def test_pages_are_packed_into_container(self):
941 """All the pages are packed in the main container."""
942 children = self.ui.content.get_children()
943@@ -620,8 +613,7 @@
944 buttons = filter(lambda name: 'cancel_button' in name or
945 'close_button' in name, self.ui.widgets)
946 for name in buttons:
947- self.ui = self.gui_class(close_callback=self._set_called,
948- **self.kwargs)
949+ self.ui.close_callback = self._set_called
950 widget = getattr(self.ui, name)
951 widget.clicked()
952 self.assertEqual(self._called, ((widget,), {}), msg % name)
953@@ -661,7 +653,7 @@
954
955 def test_finish_error_shows_error_page(self):
956 """When calling 'finish_error' the error page is shown."""
957- self.ui.finish_error(error=self.error)
958+ self.ui.finish_error()
959 self.assert_pages_visibility(finish=True)
960 self.assertEqual(self.ui.ERROR, self.ui.finish_vbox.label.get_text())
961
962@@ -1164,10 +1156,10 @@
963 self.click_verify_email_with_valid_data()
964 self.assert_warnings_visibility()
965
966- def test_on_email_validated_shows_processing_page(self):
967- """On email validated the procesing page is still shown."""
968+ def test_on_email_validated_shows_finish_page(self):
969+ """On email validated the finish page is shown."""
970 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
971- self.assert_pages_visibility(processing=True)
972+ self.assert_pages_visibility(finish=True)
973
974 def test_on_email_validated_does_not_clear_the_help_text(self):
975 """On email validated the help text is not removed."""
976@@ -1227,6 +1219,32 @@
977 self.ui.verify_token_button.clicked()
978 self.assertTrue(self._called)
979
980+ def test_after_registration_success_finish_success(self):
981+ """After REGISTRATION_SUCCESS is called, finish_success is called.
982+
983+ Only when REGISTRATION_SUCCESS returns 0.
984+
985+ """
986+ self.patch(self.ui, 'finish_success', self._set_called)
987+ self.ui.registration_success_callback = lambda app, email: 0
988+
989+ self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
990+
991+ self.assertEqual(self._called, ((), {}))
992+
993+ def test_after_registration_error_finish_error(self):
994+ """After REGISTRATION_SUCCESS is called, finish_error is called.
995+
996+ Only when REGISTRATION_SUCCESS returns a non-zero value.
997+
998+ """
999+ self.patch(self.ui, 'finish_error', self._set_called)
1000+ self.ui.registration_success_callback = lambda app, email: -1
1001+
1002+ self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1003+
1004+ self.assertEqual(self._called, ((), {}))
1005+
1006
1007 class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase):
1008 """Test suite for the user registration validation (verify email page)."""
1009@@ -1480,11 +1498,11 @@
1010 self.click_connect_with_valid_data()
1011 self.assert_pages_visibility(processing=True)
1012
1013- def test_on_logged_in_morphs_to_processing_page(self):
1014- """When user logged in the processing page is still shown."""
1015+ def test_on_logged_in_morphs_to_finish_page(self):
1016+ """When user logged in the finish page is shown."""
1017 self.click_connect_with_valid_data()
1018 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1019- self.assert_pages_visibility(processing=True)
1020+ self.assert_pages_visibility(finish=True)
1021
1022 def test_on_login_error_morphs_to_login_page(self):
1023 """On user login error, the previous page is shown."""
1024@@ -1541,6 +1559,32 @@
1025 self.assertEqual(self.ui.user_email, EMAIL)
1026 self.assertEqual(self.ui.user_password, PASSWORD)
1027
1028+ def test_after_login_success_finish_success(self):
1029+ """After LOGIN_SUCCESSFULL is called, finish_success is called.
1030+
1031+ Only when LOGIN_SUCCESSFULL returns 0.
1032+
1033+ """
1034+ self.patch(self.ui, 'finish_success', self._set_called)
1035+ self.ui.login_success_callback = lambda app, email: 0
1036+
1037+ self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1038+
1039+ self.assertEqual(self._called, ((), {}))
1040+
1041+ def test_after_login_error_finish_error(self):
1042+ """After LOGIN_SUCCESSFULL is called, finish_error is called.
1043+
1044+ Only when LOGIN_SUCCESSFULL returns a non-zero value.
1045+
1046+ """
1047+ self.patch(self.ui, 'finish_error', self._set_called)
1048+ self.ui.login_success_callback = lambda app, email: -1
1049+
1050+ self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1051+
1052+ self.assertEqual(self._called, ((), {}))
1053+
1054
1055 class LoginValidationTestCase(UbuntuSSOClientTestCase):
1056 """Test suite for the user login validation."""
1057@@ -2060,119 +2104,144 @@
1058 self.assertEqual(self.ui.help_label.get_text(), HELP_TEXT)
1059
1060
1061-class SignalsTestCase(UbuntuSSOClientTestCase):
1062- """Test the GTK signal emission."""
1063+class CallbacksTestCase(UbuntuSSOClientTestCase):
1064+ """Test the GTK callback calls."""
1065+
1066+ LOGIN_SUCCESSFULL = 'login_success_callback'
1067+ REGISTRATION_SUCCESS = 'registration_success_callback'
1068+ USER_CANCELLATION = 'user_cancellation_callback'
1069
1070 def setUp(self):
1071 """Init."""
1072- super(SignalsTestCase, self).setUp()
1073+ super(CallbacksTestCase, self).setUp()
1074 self._called = {}
1075- for sig_name, _ in gui.SIGNAL_ARGUMENTS:
1076- self.ui.connect(sig_name, self._set_called, sig_name)
1077-
1078- def _set_called(self, widget, *args, **kwargs):
1079- """Keep trace of signals emition."""
1080+ for name in (self.LOGIN_SUCCESSFULL,
1081+ self.REGISTRATION_SUCCESS,
1082+ self.USER_CANCELLATION):
1083+ setattr(self.ui, name, self._set_called(name))
1084+
1085+ def _set_called(self, callback_name):
1086+ """Keep trace of callbacks calls."""
1087+
1088 # pylint: disable=W0221
1089- self._called[args[-1]] = (widget, args[:-1], kwargs)
1090-
1091- def test_closing_main_window_sends_outcome_as_signal(self):
1092- """A signal is sent when closing the main window."""
1093+
1094+ def inner(*args, **kwargs):
1095+ """Store arguments used in this call."""
1096+ self._called[callback_name] = args
1097+
1098+ return inner
1099+
1100+ def test_closing_main_window(self):
1101+ """When closing the main window, USER_CANCELLATION is called."""
1102 self.ui.window.emit('delete-event', gtk.gdk.Event(gtk.gdk.DELETE))
1103- expected = (self.ui.window, (APP_NAME,), {})
1104- self.assertEqual(self._called[gui.SIG_USER_CANCELATION], expected)
1105+ self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
1106
1107- def test_every_cancel_emits_proper_signal(self):
1108- """Clicking on any cancel button, 'user-cancelation' signal is sent."""
1109- sig = gui.SIG_USER_CANCELATION
1110- msg = 'user-cancelation is emitted when "%s" is clicked.'
1111+ def test_every_cancel_calls_proper_callback(self):
1112+ """When any cancel button is clicked, USER_CANCELLATION is called."""
1113+ msg = 'user_cancellation_callback is called when "%s" is clicked.'
1114 buttons = filter(lambda name: 'cancel_button' in name, self.ui.widgets)
1115 for name in buttons:
1116- self.ui = self.gui_class(**self.kwargs)
1117- self.ui.connect(sig, self._set_called, sig)
1118 widget = getattr(self.ui, name)
1119 widget.clicked()
1120- expected_args = (self.ui.window, (APP_NAME,), {})
1121- self.assertEqual(self._called[sig], expected_args, msg % name)
1122+ self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,),
1123+ msg % name)
1124 self._called = {}
1125
1126- def test_on_user_registration_error_proper_signal_is_emitted(self):
1127- """On UserRegistrationError, SIG_USER_CANCELATION signal is sent."""
1128+ def test_on_user_registration_error_proper_callback_is_called(self):
1129+ """On UserRegistrationError, USER_CANCELLATION is called."""
1130 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1131 self.ui.on_close_clicked()
1132- expected = (self.ui.window, (APP_NAME,), {})
1133- self.assertEqual(expected,
1134- self._called[gui.SIG_USER_CANCELATION])
1135-
1136- def test_on_email_validated_proper_signals_is_emitted(self):
1137- """On EmailValidated, 'registration-succeeded' signal is sent."""
1138+
1139+ self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
1140+
1141+ def test_on_email_validated_proper_callback_is_called(self):
1142+ """On EmailValidated, REGISTRATION_SUCCESS is called."""
1143 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1144 self.ui.on_close_clicked()
1145- self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}),
1146- self._called[gui.SIG_REGISTRATION_SUCCEEDED])
1147-
1148- def test_on_email_validation_error_proper_signals_is_emitted(self):
1149- """On EmailValidationError, SIG_USER_CANCELATION signal is sent."""
1150+
1151+ self.assertEqual(self._called[self.REGISTRATION_SUCCESS],
1152+ (APP_NAME, EMAIL))
1153+
1154+ def test_on_email_validation_error_proper_callback_is_called(self):
1155+ """On EmailValidationError, USER_CANCELLATION is called."""
1156 self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error)
1157 self.ui.on_close_clicked()
1158- expected = (self.ui.window, (APP_NAME,), {})
1159- self.assertEqual(expected,
1160- self._called[gui.SIG_USER_CANCELATION])
1161-
1162- def test_on_logged_in_proper_signals_is_emitted(self):
1163- """On LoggedIn, 'login-succeeded' signal is sent."""
1164+
1165+ self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
1166+
1167+ def test_on_logged_in_proper_callback_is_called(self):
1168+ """On LoggedIn, LOGIN_SUCCESSFULL is called."""
1169 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1170 self.ui.on_close_clicked()
1171- self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}),
1172- self._called[gui.SIG_LOGIN_SUCCEEDED])
1173-
1174- def test_on_login_error_proper_signals_is_emitted(self):
1175- """On LoginError, SIG_USER_CANCELATION signal is sent."""
1176+
1177+ self.assertEqual(self._called[self.LOGIN_SUCCESSFULL],
1178+ (APP_NAME, EMAIL))
1179+
1180+ def test_on_login_error_proper_callback_is_called(self):
1181+ """On LoginError, USER_CANCELLATION is called."""
1182 self.click_connect_with_valid_data()
1183 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1184 self.ui.on_close_clicked()
1185- expected = (self.ui.window, (APP_NAME,), {})
1186- self.assertEqual(expected,
1187- self._called[gui.SIG_USER_CANCELATION])
1188-
1189- def test_registration_successfull_even_if_prior_registration_error(self):
1190- """Only one signal is sent with the final outcome."""
1191+
1192+ self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
1193+
1194+ def test_registration_success_even_if_prior_registration_error(self):
1195+ """Only one callback is called with the final outcome.
1196+
1197+ When the user successfully registers, REGISTRATION_SUCCESS is
1198+ called even if there were errors before.
1199+
1200+ """
1201 self.click_join_with_valid_data()
1202 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1203 self.click_join_with_valid_data()
1204 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1205 self.ui.on_close_clicked()
1206
1207- self.assertEqual(len(self._called), 1)
1208- self.assertTrue(gui.SIG_REGISTRATION_SUCCEEDED in self._called)
1209-
1210- def test_login_successfull_even_if_prior_login_error(self):
1211- """Only one signal is sent with the final outcome."""
1212+ self.assertEqual(self._called[self.REGISTRATION_SUCCESS],
1213+ (APP_NAME, EMAIL))
1214+
1215+ def test_login_success_even_if_prior_login_error(self):
1216+ """Only one callback is called with the final outcome.
1217+
1218+ When the user successfully logins, LOGIN_SUCCESSFULL is called even if
1219+ there were errors before.
1220+
1221+ """
1222 self.click_connect_with_valid_data()
1223 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1224 self.click_connect_with_valid_data()
1225 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1226 self.ui.on_close_clicked()
1227
1228- self.assertEqual(len(self._called), 1)
1229- self.assertTrue(gui.SIG_LOGIN_SUCCEEDED in self._called)
1230+ self.assertEqual(self._called[self.LOGIN_SUCCESSFULL],
1231+ (APP_NAME, EMAIL))
1232
1233 def test_user_cancelation_even_if_prior_registration_error(self):
1234- """Only one signal is sent with the final outcome."""
1235+ """Only one callback is called with the final outcome.
1236+
1237+ When the user closes the window, USER_CANCELLATION is called even if
1238+ there were registration errors before.
1239+
1240+ """
1241 self.click_join_with_valid_data()
1242 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1243 self.ui.join_cancel_button.clicked()
1244
1245- self.assertEqual(len(self._called), 1)
1246- self.assertTrue(gui.SIG_USER_CANCELATION in self._called)
1247+ self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
1248
1249 def test_user_cancelation_even_if_prior_login_error(self):
1250- """Only one signal is sent with the final outcome."""
1251+ """Only one callback is called with the final outcome.
1252+
1253+ When the user closes the window, USER_CANCELLATION is called even if
1254+ there were login errors before.
1255+
1256+ """
1257 self.click_connect_with_valid_data()
1258 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1259 self.ui.login_cancel_button.clicked()
1260
1261- self.assertEqual(len(self._called), 1)
1262- self.assertTrue(gui.SIG_USER_CANCELATION in self._called)
1263+ self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
1264
1265
1266 class DefaultButtonsTestCase(UbuntuSSOClientTestCase):
1267
1268=== modified file 'ubuntu_sso/logger.py'
1269--- ubuntu_sso/logger.py 2010-10-11 13:22:16 +0000
1270+++ ubuntu_sso/logger.py 2011-01-13 00:33:10 +0000
1271@@ -57,6 +57,7 @@
1272 logger.addHandler(MAIN_HANDLER)
1273 if os.environ.get('DEBUG'):
1274 debug_handler = logging.StreamHandler(sys.stderr)
1275+ debug_handler.setFormatter(logging.Formatter(fmt=FMT))
1276 logger.addHandler(debug_handler)
1277
1278 return logger
1279
1280=== modified file 'ubuntu_sso/main.py'
1281--- ubuntu_sso/main.py 2010-12-16 16:31:34 +0000
1282+++ ubuntu_sso/main.py 2011-01-13 00:33:10 +0000
1283@@ -50,6 +50,7 @@
1284
1285 logger = setup_logging("ubuntu_sso.main")
1286 U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
1287+TIMEOUT_INTERVAL = 500
1288
1289
1290 class SSOLoginProcessor(Account):
1291@@ -198,7 +199,9 @@
1292 # pylint: disable=E1101
1293 d = Keyring().set_credentials(app_name, credentials)
1294 d.addCallback(lambda _: self.LoggedIn(app_name, email))
1295- d.addErrback(lambda _: self.UserNotValidated(app_name, email))
1296+ d.addErrback(lambda failure: \
1297+ self.LoginError(app_name,
1298+ except_to_errdict(failure.value)))
1299 else:
1300 self.UserNotValidated(app_name, email)
1301 blocking(f, app_name, success_cb, self.LoginError)
1302@@ -419,6 +422,12 @@
1303
1304 """
1305
1306+ def __init__(self, timeout_func, shutdown_func, *args, **kwargs):
1307+ super(CredentialsManagement, self).__init__(*args, **kwargs)
1308+ self._ref_count = 0
1309+ self.timeout_func = timeout_func
1310+ self.shutdown_func = shutdown_func
1311+
1312 # Operator not preceded by a space (fails with dbus decorators)
1313 # pylint: disable=C0322
1314
1315@@ -434,39 +443,70 @@
1316 result[DENIAL_CB_KEY] = self.AuthorizationDenied
1317 return result
1318
1319+ def _process_failure(self, failure, app_name):
1320+ """Process the 'failure' and emit CredentialsError."""
1321+ self.CredentialsError(app_name, except_to_errdict(failure.value))
1322+
1323+ def _get_ref_count(self):
1324+ """Get value of ref_count."""
1325+ logger.debug('ref_count is %r.', self._ref_count)
1326+ return self._ref_count
1327+
1328+ def _set_ref_count(self, new_value):
1329+ """Set a new value to ref_count."""
1330+ logger.debug('ref_count is %r, changing value to %r.',
1331+ self._ref_count, new_value)
1332+ if new_value < 0:
1333+ self._ref_count = 0
1334+ msg = 'Attempting to decrease ref_count to a negative value (%r).'
1335+ logger.warning(msg, new_value)
1336+ else:
1337+ self._ref_count = new_value
1338+
1339+ if self._ref_count == 0:
1340+ self.timeout_func(TIMEOUT_INTERVAL, self.shutdown_func)
1341+
1342+ ref_count = property(fget=_get_ref_count, fset=_set_ref_count)
1343+
1344 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1345 def AuthorizationDenied(self, app_name):
1346 """Signal thrown when the user denies the authorization."""
1347+ self.ref_count -= 1
1348 logger.info('%s: emitting AuthorizationDenied with app_name "%s".',
1349 self.__class__.__name__, app_name)
1350
1351 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
1352 def CredentialsFound(self, app_name, credentials):
1353 """Signal thrown when the credentials are found."""
1354+ self.ref_count -= 1
1355 logger.info('%s: emitting CredentialsFound with app_name "%s".',
1356 self.__class__.__name__, app_name)
1357
1358 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1359 def CredentialsNotFound(self, app_name):
1360 """Signal thrown when the credentials are not found."""
1361+ self.ref_count -= 1
1362 logger.info('%s: emitting CredentialsNotFound with app_name "%s".',
1363 self.__class__.__name__, app_name)
1364
1365 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1366 def CredentialsCleared(self, app_name):
1367 """Signal thrown when the credentials were cleared."""
1368+ self.ref_count -= 1
1369 logger.info('%s: emitting CredentialsCleared with app_name "%s".',
1370 self.__class__.__name__, app_name)
1371
1372 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1373 def CredentialsStored(self, app_name):
1374 """Signal thrown when the credentials were cleared."""
1375+ self.ref_count -= 1
1376 logger.info('%s: emitting CredentialsStored with app_name "%s".',
1377 self.__class__.__name__, app_name)
1378
1379 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
1380 def CredentialsError(self, app_name, error_dict):
1381 """Signal thrown when there is a problem getting the credentials."""
1382+ self.ref_count -= 1
1383 logger.error('%s: emitting CredentialsError with app_name "%s" and '
1384 'error_dict %r.', self.__class__.__name__, app_name,
1385 error_dict)
1386@@ -482,6 +522,7 @@
1387 - 'args' is a dictionary, currently not used.
1388
1389 """
1390+ self.ref_count += 1
1391
1392 def success_cb(credentials):
1393 """Find credentials and notify using signals."""
1394@@ -494,7 +535,7 @@
1395 d = obj.find_credentials()
1396 # pylint: disable=E1101
1397 d.addCallback(success_cb)
1398- d.addErrback(self.CredentialsError)
1399+ d.addErrback(self._process_failure, app_name)
1400
1401 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1402 in_signature='sa{ss}', out_signature='')
1403@@ -507,11 +548,13 @@
1404 - 'args' is a dictionary, currently not used.
1405
1406 """
1407+ self.ref_count += 1
1408+
1409 obj = Credentials(app_name)
1410 d = obj.clear_credentials()
1411 # pylint: disable=E1101
1412 d.addCallback(lambda _: self.CredentialsCleared(app_name))
1413- d.addErrback(self.CredentialsError)
1414+ d.addErrback(self._process_failure, app_name)
1415
1416 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1417 in_signature='sa{ss}', out_signature='')
1418@@ -526,16 +569,20 @@
1419 'consumer_secret'.
1420
1421 """
1422+ self.ref_count += 1
1423+
1424 obj = Credentials(app_name)
1425 d = obj.store_credentials(args)
1426 # pylint: disable=E1101
1427 d.addCallback(lambda _: self.CredentialsStored(app_name))
1428- d.addErrback(self.CredentialsError)
1429+ d.addErrback(self._process_failure, app_name)
1430
1431 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1432 in_signature='sa{ss}', out_signature='')
1433 def register(self, app_name, args):
1434 """Get credentials if found else prompt GUI to register."""
1435+ self.ref_count += 1
1436+
1437 obj = Credentials(app_name, **self._parse_args(args))
1438 obj.register()
1439
1440@@ -543,5 +590,7 @@
1441 in_signature='sa{ss}', out_signature='')
1442 def login(self, app_name, args):
1443 """Get credentials if found else prompt GUI to login."""
1444+ self.ref_count += 1
1445+
1446 obj = Credentials(app_name, **self._parse_args(args))
1447 obj.login()
1448
1449=== modified file 'ubuntu_sso/tests/__init__.py'
1450--- ubuntu_sso/tests/__init__.py 2010-11-30 13:21:17 +0000
1451+++ ubuntu_sso/tests/__init__.py 2011-01-13 00:33:10 +0000
1452@@ -18,6 +18,8 @@
1453
1454 import os
1455
1456+from twisted.trial import unittest
1457+
1458 from ubuntu_sso.keyring import get_token_name
1459
1460 APP_NAME = 'The Super App!'
1461@@ -45,3 +47,15 @@
1462 TOKEN_NAME = get_token_name(APP_NAME)
1463 TC_URL = 'http://localhost/'
1464 WINDOW_ID = 5
1465+
1466+
1467+class TestCase(unittest.TestCase):
1468+ """Customized test case that keeps tracks of method calls."""
1469+
1470+ def setUp(self):
1471+ super(TestCase, self).setUp()
1472+ self._called = False
1473+
1474+ def _set_called(self, *args, **kwargs):
1475+ """Keep track of a method call."""
1476+ self._called = (args, kwargs)
1477
1478=== modified file 'ubuntu_sso/tests/bin/show_gui'
1479--- ubuntu_sso/tests/bin/show_gui 2010-11-30 13:21:17 +0000
1480+++ ubuntu_sso/tests/bin/show_gui 2011-01-13 00:33:10 +0000
1481@@ -31,11 +31,12 @@
1482 'Nam sed lorem nibh. Suspendisse gravida nulla non nunc suscipit' \
1483 ' pulvinar tempus ut augue.'
1484
1485-main_quit = lambda *args, **kwargs: gtk.main_quit()
1486-
1487
1488 if __name__ == '__main__':
1489- tc_url=TC_URL
1490+
1491+ # pylint: disable=C0103, E1101
1492+
1493+ tc_url = TC_URL
1494 login_only = False
1495 xid = 0
1496
1497@@ -51,7 +52,7 @@
1498 win.show()
1499 xid = win.window.xid
1500
1501- UbuntuSSOClientGUI(close_callback=main_quit, app_name=APP_NAME,
1502- tc_url=tc_url, help_text=HELP_TEXT, window_id=xid,
1503- login_only=login_only)
1504+ UbuntuSSOClientGUI(close_callback=lambda *args, **kwargs: gtk.main_quit(),
1505+ app_name=APP_NAME, tc_url=tc_url, help_text=HELP_TEXT,
1506+ window_id=xid, login_only=login_only)
1507 gtk.main()
1508
1509=== modified file 'ubuntu_sso/tests/bin/show_nm_state'
1510--- ubuntu_sso/tests/bin/show_nm_state 2010-09-08 19:25:02 +0000
1511+++ ubuntu_sso/tests/bin/show_nm_state 2011-01-13 00:33:10 +0000
1512@@ -26,7 +26,6 @@
1513 from ubuntu_sso.networkstate import NetworkManagerState, NM_STATE_NAMES
1514
1515 DBusGMainLoop(set_as_default=True)
1516-loop = gobject.MainLoop()
1517
1518
1519 def got_state(state):
1520@@ -34,8 +33,9 @@
1521 try:
1522 print NM_STATE_NAMES[state]
1523 finally:
1524- loop.quit()
1525+ gobject.MainLoop().quit()
1526
1527+# pylint: disable=C0103
1528 nms = NetworkManagerState(got_state)
1529 nms.find_online_state()
1530-loop.run()
1531+gobject.MainLoop().run()
1532
1533=== modified file 'ubuntu_sso/tests/test_credentials.py'
1534--- ubuntu_sso/tests/test_credentials.py 2010-11-30 13:21:17 +0000
1535+++ ubuntu_sso/tests/test_credentials.py 2011-01-13 00:33:10 +0000
1536@@ -21,13 +21,11 @@
1537 import logging
1538 import urllib
1539
1540-import gobject
1541-
1542 from twisted.internet import defer
1543 from twisted.internet.defer import inlineCallbacks
1544 from twisted.trial.unittest import TestCase, FailTest
1545+from ubuntuone.devtools.handlers import MementoHandler
1546
1547-from contrib.testing.testcase import MementoHandler
1548 from ubuntu_sso import credentials
1549 from ubuntu_sso.credentials import (APP_NAME_KEY, HELP_TEXT_KEY, NO_OP,
1550 PING_URL_KEY, TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
1551@@ -60,10 +58,6 @@
1552 WINDOW_ID_KEY: WINDOW_ID,
1553 }
1554
1555-SIG_LOGIN_SUCCEEDED = '1'
1556-SIG_REGISTRATION_SUCCEEDED = '2'
1557-SIG_USER_CANCELATION = '3'
1558-
1559
1560 class SampleMiscException(Exception):
1561 """An error to be used while testing."""
1562@@ -82,17 +76,9 @@
1563 def __init__(self, *args, **kwargs):
1564 self.args = args
1565 self.kwargs = kwargs
1566- self.signals = []
1567- self.methods = []
1568- self.connect = lambda *a: self.signals.append(a)
1569-
1570- def finish_success(self):
1571- """Fake the success finish."""
1572- self.methods.append('finish_success')
1573-
1574- def finish_error(self, error):
1575- """Fake the error finish."""
1576- self.methods.append(('finish_error', error))
1577+ self.login_success_callback = None
1578+ self.registration_success_callback = None
1579+ self.user_cancellation_callback = None
1580
1581
1582 class BasicTestCase(TestCase):
1583@@ -100,7 +86,6 @@
1584
1585 def setUp(self):
1586 """Init."""
1587- self.patch(gobject, 'idle_add', lambda f, *a, **kw: f(*a, **kw))
1588 self._called = False # helper
1589
1590 self.memento = MementoHandler()
1591@@ -226,45 +211,21 @@
1592
1593 self.assertEqual(getattr(self.obj, UI_MODULE_KEY), GTK_GUI_MODULE)
1594
1595- def test_success_cb_gui_none(self):
1596- """Success callback calls the caller but not the GUI."""
1597- self.obj.gui = None
1598- self.obj.success_cb(creds=TOKEN)
1599-
1600- self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),
1601- 'caller _success_cb was called.')
1602-
1603- def test_success_cb_gui_not_none(self):
1604- """Success callback calls the caller and finish the GUI."""
1605- self.obj.gui = ui = FakedClientGUI(APP_NAME)
1606- self.obj.success_cb(creds=TOKEN)
1607-
1608- self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),
1609- 'caller _success_cb was called.')
1610- self.assertEqual(ui.methods, ['finish_success'],
1611- 'GUI finish_success was called.')
1612- self.assertTrue(self.obj.gui is None, 'gui is reset to None.')
1613-
1614- def test_error_cb_gui_none(self):
1615- """Error callback calls the caller but not the GUI."""
1616- self.obj.gui = None
1617- error_dict = {'foo': 'bar'}
1618- self.obj.error_cb(error_dict=error_dict)
1619-
1620- self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
1621- 'caller _error_cb was called.')
1622-
1623- def test_error_cb_gui_not_none(self):
1624- """Error callback calls the caller and finish the GUI."""
1625- self.obj.gui = ui = FakedClientGUI(APP_NAME)
1626- error_dict = {'foo': 'bar'}
1627- self.obj.error_cb(error_dict=error_dict)
1628-
1629- self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
1630- 'caller _error_cb was called.')
1631- self.assertEqual(ui.methods, [('finish_error', error_dict)],
1632- 'GUI finish_error was called.')
1633- self.assertTrue(self.obj.gui is None, 'gui is reset to None.')
1634+ def test_success_cb(self):
1635+ """Success callback calls the caller."""
1636+ self.obj.gui = None
1637+ self.obj.success_cb(creds=TOKEN)
1638+
1639+ self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),
1640+ 'caller _success_cb was called.')
1641+
1642+ def test_error_cb(self):
1643+ """Error callback calls the caller."""
1644+ error_dict = {'foo': 'bar'}
1645+ self.obj.error_cb(error_dict=error_dict)
1646+
1647+ self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
1648+ 'caller _error_cb was called.')
1649
1650
1651 class CredentialsLoginSuccessTestCase(CredentialsTestCase):
1652@@ -276,12 +237,12 @@
1653 self.patch(credentials.Keyring, 'get_credentials',
1654 lambda kr, app: defer.succeed(None))
1655
1656- yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,
1657- email=EMAIL)
1658+ result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
1659
1660 msg = 'Creds are empty! This should not happen'
1661 self.assert_error_cb_called(msg='Problem while retrieving credentials',
1662 detailed_error=AssertionError(msg))
1663+ self.assertEqual(result, None)
1664
1665 @inlineCallbacks
1666 def test_cred_error(self):
1667@@ -290,11 +251,11 @@
1668 self.patch(credentials.Keyring, 'get_credentials',
1669 lambda kr, app: defer.fail(expected_error))
1670
1671- yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,
1672- email=EMAIL)
1673+ result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
1674
1675 msg = 'Problem while retrieving credentials'
1676 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
1677+ self.assertEqual(result, None)
1678 self.assertTrue(self.memento.check_exception(SampleMiscException))
1679
1680 @inlineCallbacks
1681@@ -304,10 +265,10 @@
1682 lambda kr, app: defer.succeed(TOKEN))
1683 self.patch(self.obj, '_ping_url', lambda *a, **kw: 200)
1684
1685- yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,
1686- email=EMAIL)
1687+ result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
1688
1689 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
1690+ self.assertEqual(result, 0)
1691
1692 @inlineCallbacks
1693 def test_ping_error(self):
1694@@ -324,12 +285,12 @@
1695 self.patch(self.obj, 'clear_credentials',
1696 lambda: setattr(self, '_cred_cleared', True))
1697
1698- yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,
1699- email=EMAIL)
1700+ result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
1701
1702 # error cb called correctly
1703 msg = 'Problem opening the ping_url'
1704 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))
1705+ self.assertEqual(result, None)
1706
1707 # credentials cleared
1708 self.assertTrue(self._cred_cleared)
1709@@ -349,8 +310,7 @@
1710 self.patch(self.obj, '_ping_url',
1711 lambda *a, **kw: setattr(self, '_url_pinged', (a, kw)))
1712
1713- yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,
1714- email=EMAIL)
1715+ yield self.obj._login_success_cb(APP_NAME, EMAIL)
1716
1717 self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {}))
1718
1719@@ -366,10 +326,10 @@
1720 self.patch(self.obj, 'clear_credentials', self._set_called)
1721 self.obj.ping_url = None
1722
1723- yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,
1724- email=EMAIL)
1725+ result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
1726
1727 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
1728+ self.assertEqual(result, 0)
1729
1730
1731 class CredentialsAuthDeniedTestCase(CredentialsTestCase):
1732@@ -377,10 +337,9 @@
1733
1734 def test_auth_denial_cb(self):
1735 """On auth denied, self.denial_cb is called."""
1736- self.obj.gui = ui = FakedClientGUI(APP_NAME)
1737- self.obj._auth_denial_cb(dialog=None, app_name=APP_NAME)
1738+ self.obj._auth_denial_cb(app_name=APP_NAME)
1739+
1740 self.assertEqual(self._called, (('denial', APP_NAME), {}))
1741- self.assertEqual(ui.methods, [], 'no GUI method was called.')
1742
1743
1744 class PingUrlTestCase(CredentialsTestCase):
1745@@ -401,24 +360,29 @@
1746
1747 self.patch(credentials.urllib2, 'urlopen', faked_urlopen)
1748
1749+ @inlineCallbacks
1750 def test_ping_url_if_url_is_none(self):
1751 """self.ping_url is opened."""
1752 self.patch(credentials.urllib2, 'urlopen', self.fail)
1753 self.obj.ping_url = None
1754- self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN)
1755+ yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
1756+ credentials=TOKEN)
1757 # no failure
1758
1759+ @inlineCallbacks
1760 def test_ping_url(self):
1761 """On auth success, self.ping_url is opened."""
1762- self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN)
1763+ yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
1764+ credentials=TOKEN)
1765
1766 self.assertIsInstance(self._request, credentials.urllib2.Request)
1767 self.assertEqual(self._request.get_full_url(),
1768 self.obj.ping_url + EMAIL)
1769
1770+ @inlineCallbacks
1771 def test_request_is_signed_with_credentials(self):
1772 """The http request to self.ping_url is signed with the credentials."""
1773- result = self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
1774+ result = yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
1775 headers = self._request.headers
1776
1777 self.assertIn('Authorization', headers)
1778@@ -431,12 +395,13 @@
1779 self.assertIn(expected, oauth_stuff)
1780 self.assertEqual(result, 200)
1781
1782+ @inlineCallbacks
1783 def test_ping_url_error(self):
1784 """Exception is handled if ping fails."""
1785 error = 'Blu'
1786 self.patch(credentials.urllib2, 'urlopen', lambda r: self.fail(error))
1787
1788- self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
1789+ yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
1790
1791 msg = 'Problem opening the ping_url'
1792 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))
1793@@ -472,11 +437,8 @@
1794 self.patch(credentials.Keyring, 'get_credentials',
1795 lambda kr, app: defer.fail(expected_error))
1796
1797- yield self.obj.find_credentials()
1798-
1799- msg = 'Problem while retrieving credentials'
1800- self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
1801- self.assertTrue(self.memento.check_exception(SampleMiscException))
1802+ yield self.assertFailure(self.obj.find_credentials(),
1803+ SampleMiscException)
1804
1805
1806 class ClearCredentialsTestCase(CredentialsTestCase):
1807@@ -498,11 +460,8 @@
1808 self.patch(credentials.Keyring, 'delete_credentials',
1809 lambda kr, app: defer.fail(expected_error))
1810
1811- yield self.obj.clear_credentials()
1812-
1813- msg = 'Problem while deleting credentials'
1814- self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
1815- self.assertTrue(self.memento.check_exception(SampleMiscException))
1816+ yield self.assertFailure(self.obj.clear_credentials(),
1817+ SampleMiscException)
1818
1819
1820 class StoreCredentialsTestCase(CredentialsTestCase):
1821@@ -524,11 +483,8 @@
1822 self.patch(credentials.Keyring, 'set_credentials',
1823 lambda kr, app, token: defer.fail(expected_error))
1824
1825- yield self.obj.store_credentials(TOKEN)
1826-
1827- msg = 'Problem while storing credentials'
1828- self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
1829- self.assertTrue(self.memento.check_exception(SampleMiscException))
1830+ yield self.assertFailure(self.obj.store_credentials(TOKEN),
1831+ SampleMiscException)
1832
1833
1834 class RegisterTestCase(CredentialsTestCase):
1835@@ -592,17 +548,17 @@
1836
1837 @inlineCallbacks
1838 def test_connects_gui_signals(self):
1839- """Outcome signals from GUI are connected to callbacks."""
1840+ """GUI callbacks are properly connected."""
1841 self.patch(credentials.Keyring, 'get_credentials',
1842 lambda kr, app: defer.succeed(None))
1843-
1844- expected = [
1845- (SIG_LOGIN_SUCCEEDED, self.obj._login_success_cb),
1846- (SIG_REGISTRATION_SUCCEEDED, self.obj._login_success_cb),
1847- (SIG_USER_CANCELATION, self.obj._auth_denial_cb),
1848- ]
1849 yield getattr(self.obj, self.operation)()
1850- self.assertEqual(self.obj.gui.signals, expected)
1851+
1852+ self.assertEqual(self.obj.gui.login_success_callback,
1853+ self.obj._login_success_cb)
1854+ self.assertEqual(self.obj.gui.registration_success_callback,
1855+ self.obj._login_success_cb)
1856+ self.assertEqual(self.obj.gui.user_cancellation_callback,
1857+ self.obj._auth_denial_cb)
1858
1859 @inlineCallbacks
1860 def test_gui_is_created(self):
1861
1862=== modified file 'ubuntu_sso/tests/test_main.py'
1863--- ubuntu_sso/tests/test_main.py 2010-12-16 16:31:34 +0000
1864+++ ubuntu_sso/tests/test_main.py 2011-01-13 00:33:10 +0000
1865@@ -27,21 +27,22 @@
1866 from twisted.internet import defer
1867 from twisted.internet.defer import Deferred, inlineCallbacks
1868 from twisted.trial.unittest import TestCase
1869+from ubuntuone.devtools.handlers import MementoHandler
1870
1871 import ubuntu_sso.keyring
1872 import ubuntu_sso.main
1873
1874-from contrib.testing.testcase import MementoHandler
1875 from ubuntu_sso import DBUS_CREDENTIALS_IFACE
1876 from ubuntu_sso.keyring import U1_APP_NAME
1877-from ubuntu_sso.main import (U1_PING_URL, blocking, except_to_errdict,
1878+from ubuntu_sso.main import (U1_PING_URL, TIMEOUT_INTERVAL,
1879+ blocking, except_to_errdict,
1880 CredentialsManagement, SSOCredentials, SSOLogin)
1881 from ubuntu_sso.main import (HELP_TEXT_KEY, PING_URL_KEY,
1882 TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
1883 SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY)
1884 from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID,
1885 CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, PASSWORD, PING_URL, TOKEN,
1886- TOKEN_NAME, WINDOW_ID)
1887+ TOKEN_NAME, WINDOW_ID, TestCase)
1888
1889
1890 # Access to a protected member 'yyy' of a client class
1891@@ -70,6 +71,8 @@
1892 class SsoDbusTestCase(TestCase):
1893 """Test the SSOLogin DBus interface."""
1894
1895+ timeout = 2
1896+
1897 def setUp(self):
1898 """Create the mocking bus."""
1899 self.mocker = Mocker()
1900@@ -256,8 +259,8 @@
1901 client.login(APP_NAME, EMAIL, PASSWORD)
1902 return d
1903
1904- def test_login_error(self):
1905- """Test that the login method fails as expected."""
1906+ def test_login_error_get_token_name(self):
1907+ """The login method fails as expected when get_token_name fails."""
1908 d = Deferred()
1909 self.create_mock_processor()
1910 self.patch(ubuntu_sso.main, "blocking", fake_err_blocking)
1911@@ -284,6 +287,40 @@
1912 client.login(APP_NAME, EMAIL, PASSWORD)
1913 return d
1914
1915+ def test_login_error_set_credentials(self):
1916+ """The login method fails as expected when set_credentials fails."""
1917+ d = Deferred()
1918+ processor = self.create_mock_processor()
1919+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
1920+ self.mocker.result(TOKEN)
1921+ processor.is_validated(TOKEN)
1922+ self.mocker.result(True)
1923+
1924+ self.patch(ubuntu_sso.main, "blocking", fake_ok_blocking)
1925+
1926+ def fake_set_creds(*args):
1927+ """A fake Keyring.set_credentials that fails."""
1928+ return defer.fail(BlockingSampleException())
1929+
1930+ self.patch(ubuntu_sso.main.Keyring, "set_credentials", fake_set_creds)
1931+ self.mocker.replay()
1932+
1933+ def verify(app_name, errdict):
1934+ """The actual test."""
1935+ self.assertEqual(app_name, APP_NAME)
1936+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
1937+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
1938+ d.callback("Ok")
1939+
1940+ client = SSOLogin(self.mockbusname,
1941+ sso_login_processor_class=self.mockprocessorclass)
1942+ fail = lambda app, res: d.errback((app, res))
1943+ self.patch(client, "LoggedIn", fail)
1944+ self.patch(client, "LoginError", verify)
1945+ self.patch(client, "UserNotValidated", fail)
1946+ client.login(APP_NAME, EMAIL, PASSWORD)
1947+ return d
1948+
1949 def test_validate_email(self):
1950 """Test that the validate_email method works ok."""
1951 d = Deferred()
1952@@ -666,21 +703,30 @@
1953 class CredentialsManagementTestCase(TestCase):
1954 """Tests for the CredentialsManagement DBus interface."""
1955
1956+ timeout = 2
1957 base_args = {HELP_TEXT_KEY: HELP_TEXT, PING_URL_KEY: PING_URL,
1958 TC_URL_KEY: TC_URL, WINDOW_ID_KEY: WINDOW_ID,
1959 UI_CLASS_KEY: 'SuperUI', UI_MODULE_KEY: 'foo.bar.baz',
1960 }
1961
1962 def setUp(self):
1963+ super(CredentialsManagementTestCase, self).setUp()
1964+
1965 self.mocker = Mocker()
1966- self.client = CredentialsManagement()
1967+ self.client = CredentialsManagement(timeout_func=lambda *a: None,
1968+ shutdown_func=lambda *a: None)
1969 self.args = {}
1970 self.cred_args = {}
1971
1972+ self.memento = MementoHandler()
1973+ self.memento.setLevel(logging.DEBUG)
1974+ ubuntu_sso.main.logger.addHandler(self.memento)
1975+
1976 def tearDown(self):
1977 """Verify the mocking stuff and shut it down."""
1978 self.mocker.verify()
1979 self.mocker.restore()
1980+ super(CredentialsManagementTestCase, self).tearDown()
1981
1982 def assert_dbus_method_correct(self, method):
1983 """Check that 'method' is a dbus method with proper signatures."""
1984@@ -703,10 +749,168 @@
1985 self.assertIsInstance(self.client, ubuntu_sso.main.dbus.service.Object)
1986
1987
1988-class CredentialsManagementFindClearTestCase(CredentialsManagementTestCase):
1989- """Tests for the CredentialsManagement find/clear/store methods."""
1990-
1991- timeout = 5
1992+class CredentialsManagementRefCountingTestCase(CredentialsManagementTestCase):
1993+ """Tests for the CredentialsManagement ref counting."""
1994+
1995+ def test_ref_counting(self):
1996+ """Ref counting is in place."""
1997+ self.assertEqual(self.client.ref_count, 0)
1998+
1999+ def test_find_credentials(self):
2000+ """Keep proper track of on going requests."""
2001+ self.client.find_credentials(APP_NAME, self.args)
2002+
2003+ self.assertEqual(self.client.ref_count, 1)
2004+
2005+ def test_clear_credentials(self):
2006+ """Keep proper track of on going requests."""
2007+ self.client.clear_credentials(APP_NAME, self.args)
2008+
2009+ self.assertEqual(self.client.ref_count, 1)
2010+
2011+ def test_store_credentials(self):
2012+ """Keep proper track of on going requests."""
2013+ self.client.store_credentials(APP_NAME, self.args)
2014+
2015+ self.assertEqual(self.client.ref_count, 1)
2016+
2017+ def test_register(self):
2018+ """Keep proper track of on going requests."""
2019+ self.client.register(APP_NAME, self.args)
2020+
2021+ self.assertEqual(self.client.ref_count, 1)
2022+
2023+ def test_login(self):
2024+ """Keep proper track of on going requests."""
2025+ self.client.login(APP_NAME, self.args)
2026+
2027+ self.assertEqual(self.client.ref_count, 1)
2028+
2029+ def test_several_requests(self):
2030+ """Requests can be nested."""
2031+ self.client.login(APP_NAME, self.args)
2032+ self.client.clear_credentials(APP_NAME, self.args)
2033+ self.client.find_credentials(APP_NAME, self.args)
2034+ self.client.register(APP_NAME, self.args)
2035+ self.client.store_credentials(APP_NAME, self.args)
2036+
2037+ self.assertEqual(self.client.ref_count, 5)
2038+
2039+ def test_credentials_found(self):
2040+ """Ref counter is decreased when a signal is sent."""
2041+ self.client.ref_count = 3
2042+ self.client.CredentialsFound(APP_NAME, TOKEN)
2043+
2044+ self.assertEqual(self.client.ref_count, 2)
2045+
2046+ def test_credentials_not_found(self):
2047+ """Ref counter is decreased when a signal is sent."""
2048+ self.client.ref_count = 3
2049+ self.client.CredentialsNotFound(APP_NAME)
2050+
2051+ self.assertEqual(self.client.ref_count, 2)
2052+
2053+ def test_credentials_cleared(self):
2054+ """Ref counter is decreased when a signal is sent."""
2055+ self.client.ref_count = 3
2056+ self.client.CredentialsCleared(APP_NAME)
2057+
2058+ self.assertEqual(self.client.ref_count, 2)
2059+
2060+ def test_credentials_stored(self):
2061+ """Ref counter is decreased when a signal is sent."""
2062+ self.client.ref_count = 3
2063+ self.client.CredentialsStored(APP_NAME)
2064+
2065+ self.assertEqual(self.client.ref_count, 2)
2066+
2067+ def test_credentials_error(self):
2068+ """Ref counter is decreased when a signal is sent."""
2069+ self.client.ref_count = 3
2070+ self.client.CredentialsError(APP_NAME, {'error_type': 'test'})
2071+
2072+ self.assertEqual(self.client.ref_count, 2)
2073+
2074+ def test_authorization_denied(self):
2075+ """Ref counter is decreased when a signal is sent."""
2076+ self.client.ref_count = 3
2077+ self.client.AuthorizationDenied(APP_NAME)
2078+
2079+ self.assertEqual(self.client.ref_count, 2)
2080+
2081+ def test_credentials_found_when_ref_count_is_not_positive(self):
2082+ """Ref counter is decreased when a signal is sent."""
2083+ self.client._ref_count = -3
2084+ self.client.CredentialsFound(APP_NAME, TOKEN)
2085+
2086+ self.assertEqual(self.client.ref_count, 0)
2087+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
2088+ self.assertTrue(self.memento.check_warning(msg))
2089+
2090+ def test_credentials_not_found_when_ref_count_is_not_positive(self):
2091+ """Ref counter is decreased when a signal is sent."""
2092+ self.client._ref_count = -3
2093+ self.client.CredentialsNotFound(APP_NAME)
2094+
2095+ self.assertEqual(self.client.ref_count, 0)
2096+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
2097+ self.assertTrue(self.memento.check_warning(msg))
2098+
2099+ def test_credentials_cleared_when_ref_count_is_not_positive(self):
2100+ """Ref counter is decreased when a signal is sent."""
2101+ self.client._ref_count = -3
2102+ self.client.CredentialsCleared(APP_NAME)
2103+
2104+ self.assertEqual(self.client.ref_count, 0)
2105+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
2106+ self.assertTrue(self.memento.check_warning(msg))
2107+
2108+ def test_credentials_stored_when_ref_count_is_not_positive(self):
2109+ """Ref counter is decreased when a signal is sent."""
2110+ self.client._ref_count = -3
2111+ self.client.CredentialsStored(APP_NAME)
2112+
2113+ self.assertEqual(self.client.ref_count, 0)
2114+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
2115+ self.assertTrue(self.memento.check_warning(msg))
2116+
2117+ def test_credentials_error_when_ref_count_is_not_positive(self):
2118+ """Ref counter is decreased when a signal is sent."""
2119+ self.client._ref_count = -3
2120+ self.client.CredentialsError(APP_NAME, {'error_type': 'test'})
2121+
2122+ self.assertEqual(self.client.ref_count, 0)
2123+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
2124+ self.assertTrue(self.memento.check_warning(msg))
2125+
2126+ def test_autorization_denied_when_ref_count_is_not_positive(self):
2127+ """Ref counter is decreased when a signal is sent."""
2128+ self.client._ref_count = -3
2129+ self.client.AuthorizationDenied(APP_NAME)
2130+
2131+ self.assertEqual(self.client.ref_count, 0)
2132+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
2133+ self.assertTrue(self.memento.check_warning(msg))
2134+
2135+ def test_on_zero_ref_count_shutdown(self):
2136+ """When ref count reaches 0, queue shutdown op."""
2137+ self.client.timeout_func = self._set_called
2138+ self.client.find_credentials(APP_NAME, self.args)
2139+ self.client.CredentialsFound(APP_NAME, TOKEN)
2140+
2141+ self.assertEqual(self._called,
2142+ ((TIMEOUT_INTERVAL, self.client.shutdown_func), {}))
2143+
2144+ def test_on_non_zero_ref_count_do_not_shutdown(self):
2145+ """If ref count is not 0, do not queue shutdown op."""
2146+ self.client.timeout_func = self._set_called
2147+ self.client.find_credentials(APP_NAME, self.args)
2148+
2149+ self.assertEqual(self._called, False)
2150+
2151+
2152+class CredentialsManagementFindTestCase(CredentialsManagementTestCase):
2153+ """Tests for the CredentialsManagement find method."""
2154
2155 def test_find_credentials(self):
2156 """The credentials are asked and returned in signals."""
2157@@ -762,7 +966,8 @@
2158 else:
2159 d.callback(app_name)
2160
2161- self.patch(self.client, 'CredentialsFound', d.errback)
2162+ self.patch(self.client, 'CredentialsFound',
2163+ lambda app, creds: d.errback(app))
2164 self.patch(self.client, 'CredentialsNotFound', verify)
2165
2166 self.create_mock_backend().find_credentials()
2167@@ -772,6 +977,32 @@
2168 self.client.find_credentials(APP_NAME, self.args)
2169 return d
2170
2171+ def test_find_credentials_error(self):
2172+ """If find_credentials fails, CredentialsError is sent."""
2173+ d = Deferred()
2174+
2175+ def verify(app_name, errdict):
2176+ """The actual test."""
2177+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
2178+ self.assertEqual(app_name, APP_NAME)
2179+ d.callback("Ok")
2180+
2181+ self.patch(self.client, 'CredentialsFound',
2182+ lambda app, creds: d.errback(app))
2183+ self.patch(self.client, 'CredentialsNotFound', d.errback)
2184+ self.patch(self.client, 'CredentialsError', verify)
2185+
2186+ self.create_mock_backend().find_credentials()
2187+ self.mocker.result(defer.fail(BlockingSampleException()))
2188+ self.mocker.replay()
2189+
2190+ self.client.find_credentials(APP_NAME, self.args)
2191+ return d
2192+
2193+
2194+class CredentialsManagementClearTestCase(CredentialsManagementTestCase):
2195+ """Tests for the CredentialsManagement clear method."""
2196+
2197 def test_clear_credentials(self):
2198 """The credentials are removed."""
2199 self.create_mock_backend().clear_credentials()
2200@@ -795,7 +1026,8 @@
2201 d.callback(app_name)
2202
2203 self.patch(self.client, 'CredentialsCleared', verify)
2204- self.patch(self.client, 'CredentialsError', d.errback)
2205+ self.patch(self.client, 'CredentialsError',
2206+ lambda app, err: d.errback(app))
2207
2208 self.create_mock_backend().clear_credentials()
2209 self.mocker.result(defer.succeed(APP_NAME))
2210@@ -804,6 +1036,30 @@
2211 self.client.clear_credentials(APP_NAME, self.args)
2212 return d
2213
2214+ def test_clear_credentials_error(self):
2215+ """If clear_credentials fails, CredentialsError is sent."""
2216+ d = Deferred()
2217+
2218+ def verify(app_name, errdict):
2219+ """The actual test."""
2220+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
2221+ self.assertEqual(app_name, APP_NAME)
2222+ d.callback("Ok")
2223+
2224+ self.patch(self.client, 'CredentialsCleared', d.errback)
2225+ self.patch(self.client, 'CredentialsError', verify)
2226+
2227+ self.create_mock_backend().clear_credentials()
2228+ self.mocker.result(defer.fail(BlockingSampleException()))
2229+ self.mocker.replay()
2230+
2231+ self.client.clear_credentials(APP_NAME, self.args)
2232+ return d
2233+
2234+
2235+class CredentialsManagementStoreTestCase(CredentialsManagementTestCase):
2236+ """Tests for the CredentialsManagement store method."""
2237+
2238 def test_store_credentials(self):
2239 """The credentials are stored and the outcome is a signal."""
2240 self.create_mock_backend().store_credentials(TOKEN)
2241@@ -831,7 +1087,8 @@
2242 d.callback(app_name)
2243
2244 self.patch(self.client, 'CredentialsStored', verify)
2245- self.patch(self.client, 'CredentialsError', d.errback)
2246+ self.patch(self.client, 'CredentialsError',
2247+ lambda app, err: d.errback(app))
2248
2249 self.create_mock_backend().store_credentials(TOKEN)
2250 self.mocker.result(defer.succeed(APP_NAME))
2251@@ -840,6 +1097,26 @@
2252 self.client.store_credentials(APP_NAME, TOKEN)
2253 return d
2254
2255+ def test_store_credentials_error(self):
2256+ """If store_credentials fails, CredentialsError is sent."""
2257+ d = Deferred()
2258+
2259+ def verify(app_name, errdict):
2260+ """The actual test."""
2261+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
2262+ self.assertEqual(app_name, APP_NAME)
2263+ d.callback("Ok")
2264+
2265+ self.patch(self.client, 'CredentialsStored', d.errback)
2266+ self.patch(self.client, 'CredentialsError', verify)
2267+
2268+ self.create_mock_backend().store_credentials(TOKEN)
2269+ self.mocker.result(defer.fail(BlockingSampleException()))
2270+ self.mocker.replay()
2271+
2272+ self.client.store_credentials(APP_NAME, TOKEN)
2273+ return d
2274+
2275
2276 class CredentialsManagementOpsTestCase(CredentialsManagementTestCase):
2277 """Tests for the CredentialsManagement login/register methods."""
2278@@ -881,7 +1158,8 @@
2279 """Tests for the CredentialsManagement DBus signals."""
2280
2281 def setUp(self):
2282- self.client = CredentialsManagement()
2283+ self.client = CredentialsManagement(timeout_func=lambda *a: None,
2284+ shutdown_func=lambda *a: None)
2285
2286 self.memento = MementoHandler()
2287 self.memento.setLevel(logging.DEBUG)

Subscribers

People subscribed via source and target branches