From 8e9b3d0760f7cfbce0d480cb61050a9b78746f8c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 May 2011 15:28:47 -0400 Subject: [PATCH 1/2] Ensure that the XEP-0086 plugin is loaded. Since the XEP-0086 plugin auto adds error code values, it must be reliably loaded or unloaded when certain tests are run so that stanzas may be matched. In this case, we ensure that the plugin is used. --- tests/test_stanza_error.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_stanza_error.py b/tests/test_stanza_error.py index 5eecfee..a41bf4b 100644 --- a/tests/test_stanza_error.py +++ b/tests/test_stanza_error.py @@ -3,6 +3,11 @@ from sleekxmpp.test import * class TestErrorStanzas(SleekTest): + def setUp(self): + # Ensure that the XEP-0086 plugin has been loaded. + self.stream_start() + self.stream_close() + def testSetup(self): """Test setting initial values in error stanza.""" msg = self.Message() From 9f1648328f17b608651989606b9cf2636cdcfbec Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 20 May 2011 12:56:00 -0400 Subject: [PATCH 2/2] Resolve timeout errors for get_roster. See issue #89 Using get_roster will now return the same types of values as Iq.send. If a timeout occurs, then the event 'roster_timeout' will be fired. A successful call to get_roster will also raise the 'roster_received' event. To ensure that the get_roster call was successful, here is a pattern to follow: def __init__(self, ...): ... self.add_event_handler('session_start', self.session_start) self.add_event_handler('roster_timeout', self.roster_timeout) self.add_event_handler('roster_received', self.roster_received) def session_start(self, e): self.send_presence() self.get_roster() def roster_timeout(self, e): # Optionally increase the timeout period self.get_roster(timeout=self.response_timeout * 2) def roster_received(self, iq): # Do stuff, roster has been initialized. ... --- sleekxmpp/clientxmpp.py | 53 ++++++++++++++++++++---- tests/test_stream_roster.py | 81 +++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 20cc941..c518a4c 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -206,7 +206,8 @@ class ClientXMPP(BaseXMPP): pointer, breaker)) - def update_roster(self, jid, name=None, subscription=None, groups=[]): + def update_roster(self, jid, name=None, subscription=None, groups=[], + block=True, timeout=None, callback=None): """ Add or change a roster item. @@ -217,12 +218,24 @@ class ClientXMPP(BaseXMPP): 'to', 'from', 'both', or 'none'. If set to 'remove', the entry will be deleted. groups -- The roster groups that contain this item. + block -- Specify if the roster request will block + until a response is received, or a timeout + occurs. Defaults to True. + timeout -- The length of time (in seconds) to wait + for a response before continuing if blocking + is used. Defaults to self.response_timeout. + callback -- Optional reference to a stream handler function. + Will be executed when the roster is received. + Implies block=False. """ - iq = self.Iq()._set_stanza_values({'type': 'set'}) + iq = self.Iq() + iq['type'] = 'set' iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} - response = iq.send() + response = iq.send(block, timeout, callback) + if response in [False, None]: + return response return response['type'] == 'result' def del_roster_item(self, jid): @@ -235,11 +248,33 @@ class ClientXMPP(BaseXMPP): """ return self.update_roster(jid, subscription='remove') - def get_roster(self): - """Request the roster from the server.""" - iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster') - response = iq.send() - self._handle_roster(response, request=True) + def get_roster(self, block=True, timeout=None, callback=None): + """ + Request the roster from the server. + + Arguments: + block -- Specify if the roster request will block until a + response is received, or a timeout occurs. + Defaults to True. + timeout -- The length of time (in seconds) to wait for a response + before continuing if blocking is used. + Defaults to self.response_timeout. + callback -- Optional reference to a stream handler function. Will + be executed when the roster is received. + Implies block=False. + """ + iq = self.Iq() + iq['type'] = 'get' + iq.enable('roster') + response = iq.send(block, timeout, callback) + + if response == False: + self.event('roster_timeout') + + if response in [False, None] or not isinstance(response, Iq): + return response + else: + return self._handle_roster(response, request=True) def _handle_stream_features(self, features): """ @@ -431,12 +466,14 @@ class ClientXMPP(BaseXMPP): 'presence': {}, 'in_roster': True} self.roster[jid].update(iq['roster']['items'][jid]) + self.event('roster_received', iq) self.event("roster_update", iq) if iq['type'] == 'set': iq.reply() iq.enable('roster') iq.send() + return True # To comply with PEP8, method names now use underscores. diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py index 165a8bc..731d114 100644 --- a/tests/test_stream_roster.py +++ b/tests/test_stream_roster.py @@ -16,6 +16,13 @@ class TestStreamRoster(SleekTest): self.stream_start(mode='client') self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.") + events = [] + + def roster_received(iq): + events.append('roster_received') + + self.xmpp.add_event_handler('roster_received', roster_received) + # Since get_roster blocks, we need to run it in a thread. t = threading.Thread(name='get_roster', target=self.xmpp.get_roster) t.start() @@ -41,6 +48,9 @@ class TestStreamRoster(SleekTest): # Wait for get_roster to return. t.join() + # Give the event queue time to process. + time.sleep(.1) + roster = {'user@localhost': {'name': 'User', 'subscription': 'both', 'groups': ['Friends', 'Examples'], @@ -48,11 +58,20 @@ class TestStreamRoster(SleekTest): 'in_roster': True}} self.failUnless(self.xmpp.roster == roster, "Unexpected roster values: %s" % self.xmpp.roster) + self.failUnless('roster_received' in events, + "Roster received event not triggered: %s" % events) def testRosterSet(self): """Test handling pushed roster updates.""" self.stream_start(mode='client') self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.") + events = [] + + def roster_update(e): + events.append('roster_update') + + self.xmpp.add_event_handler('roster_update', roster_update) + self.recv(""" @@ -72,6 +91,9 @@ class TestStreamRoster(SleekTest): """) + # Give the event queue time to process. + time.sleep(.1) + roster = {'user@localhost': {'name': 'User', 'subscription': 'both', 'groups': ['Friends', 'Examples'], @@ -79,7 +101,66 @@ class TestStreamRoster(SleekTest): 'in_roster': True}} self.failUnless(self.xmpp.roster == roster, "Unexpected roster values: %s" % self.xmpp.roster) + self.failUnless('roster_update' in events, + "Roster updated event not triggered: %s" % events) + def testRosterTimeout(self): + """Test handling a timed out roster request.""" + self.stream_start() + events = [] + + def roster_timeout(event): + events.append('roster_timeout') + + self.xmpp.add_event_handler('roster_timeout', roster_timeout) + self.xmpp.get_roster(timeout=0) + + # Give the event queue time to process. + time.sleep(.1) + + self.failUnless(events == ['roster_timeout'], + "Roster timeout event not triggered: %s." % events) + + def testRosterCallback(self): + """Test handling a roster request callback.""" + self.stream_start() + events = [] + + def roster_callback(iq): + events.append('roster_callback') + + # Since get_roster blocks, we need to run it in a thread. + t = threading.Thread(name='get_roster', + target=self.xmpp.get_roster, + kwargs={'callback': roster_callback}) + t.start() + + self.send(""" + + + + """) + self.recv(""" + + + + Friends + Examples + + + + """) + + # Wait for get_roster to return. + t.join() + + # Give the event queue time to process. + time.sleep(.1) + + self.failUnless(events == ['roster_callback'], + "Roster timeout event not triggered: %s." % events)