added new pypsyc as 'mjacob2'

This commit is contained in:
psyc://psyced.org/~lynX 2012-01-02 14:12:33 +01:00
parent 0abc7a0888
commit 9f7c4147c7
46 changed files with 7243 additions and 0 deletions

21
mjacob2/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2010 Manuel Jacob
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

42
mjacob2/bin/pypsyc Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env python
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import logging
logging.basicConfig()
from optparse import OptionParser
from os import path, mkdir
def main():
parser = OptionParser()
parser.add_option('-c', '--config', dest='config_dir',
default=path.expanduser(path.join('~', '.pypsyc')))
options, args = parser.parse_args()
if not path.exists(options.config_dir):
mkdir(options.config_dir)
accounts_file = path.join(options.config_dir, 'accounts')
if not path.exists(accounts_file):
with open(accounts_file, 'w') as f:
f.write('[]')
from twisted.internet import gtk2reactor
gtk2reactor.install()
from pypsyc.client.controller import MainController
from pypsyc.client.model import Client
from pypsyc.client.view import MainView
model = Client(accounts_file=accounts_file)
view = MainView()
MainController(model, view)
model.load_accounts()
if __name__ == '__main__':
main()
from twisted.internet import reactor
reactor.run()

45
mjacob2/bin/pypsycd Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import logging
logging.basicConfig()
from optparse import OptionParser
from os import path, mkdir
from socket import gethostname
from twisted.internet import reactor
from pypsyc.server import Server
def main():
parser = OptionParser()
parser.add_option('-d', '--directory', dest='directory',
default=path.expanduser(path.join('~', '.pypsycd')),
help="directory where the database is stored "
"(defaults to ~/.pypsycd)")
parser.add_option('-H', '--hostname', dest='hostname',
help="hostname of the psyc server")
parser.add_option('-i', '--interface', dest='interface', default='',
help="interface to bind to (defaults to all)")
parser.add_option('-p', '--psyc-port', type='int', default=4404,
help="port to listen for incoming psyc connections "
"or 0 to disable them (defaults to 4404)")
parser.add_option('-w', '--webif-port', type='int', default=8080,
help="port of the web interface or 0 to disable it "
"(defaults to 8080)")
options = parser.parse_args()[0]
if not path.exists(options.directory):
mkdir(options.directory)
Server(hostname=options.hostname or gethostname(),
interface=options.interface,
psyc_port=options.psyc_port,
webif_port=options.webif_port,
db_file=path.join(options.directory, 'database'))
reactor.run()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,8 @@
"""
pypsyc
~~~~~~
Pypsyc is a pythonic framework for PSYC servers and clients.
"""
__version__ = '0.0dev'

View File

@ -0,0 +1,4 @@
"""
pypsyc.client
~~~~~~~~~~~~~
"""

View File

@ -0,0 +1,383 @@
"""
pypsyc.client.controller
~~~~~~~~~~~~~~~~~~~~~~~~
Classes which connect the models with the views. They observe the models
and update the view. They are injected into the views and have methods
that can change the models after user input validation.
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from functools import partial
from pypsyc.client.model import Account
from pypsyc.client.observable import ObsList
from pypsyc.util import schedule
def _observe_list(obs_list, insert, delitem, update, *args):
if insert:
for i, v in enumerate(obs_list):
insert(i, v, *args)
obs_list.insert_evt.add_observer(insert, *args)
if delitem is not None:
obs_list.delitem_evt.add_observer(delitem)
if update is not None:
obs_list.update_evt += update
def _observe_dict(obs_dict, setitem, delitem, update, *args):
if setitem is not None:
for k, v in obs_dict.iteritems():
setitem(k, None, v, *args)
obs_dict.setitem_evt.add_observer(setitem, *args)
if delitem is not None:
obs_dict.delitem_evt += delitem
if update is not None:
obs_dict.update_evt += update
def _unobserve_list(obs_list, insert, delitem, update):
if insert is not None:
obs_list.insert_evt -= insert
if delitem is not None:
obs_list.delitem_evt -= delitem
for v in obs_list:
delitem(0, v)
if update is not None:
obs_list.update_evt -= update
def _unobserve_dict(obs_dict, setitem, delitem, update):
if setitem is not None:
obs_dict.setitem_evt -= setitem
if delitem is not None:
obs_dict.delitem_evt -= delitem
for k, v in obs_dict.iteritems():
delitem(k, v)
if update is not None:
obs_dict.update_evt -= update
class ListBinding(object):
def __init__(self, model_list, view_list, make_row):
self.model_list = model_list
self.view_list = view_list
self.make_row = make_row
_observe_list(model_list, self._add, self._del, self._update)
def unbind(self):
_unobserve_list(self.model_list, self._add, self._del, self._update)
def _add(self, index, obj):
self.view_list.append(self.make_row(obj))
def _del(self, index, obj):
del self.view_list[index]
def _update(self, index, obj):
self.view_list[index] = self.make_row(obj)
class AccountsController(object):
def __init__(self, client, view):
self.client = client
self.view = view
view.controller = self
self.binding = ListBinding(self.client.accounts, view.accounts,
lambda obj: (obj.uni, obj.active))
def add_account(self):
account = Account(self.client, "", "", "", False, False)
self.view.show_addedit_dialog(account.__dict__, True,
partial(self._saved, account, True))
def edit_account(self, pos):
account = self.client.accounts[pos]
self.view.show_addedit_dialog(account.__dict__, False,
partial(self._saved, account, False))
def _saved(self, account, add):
if add:
self.client.accounts.append(account)
else:
self.client.accounts.updated_item(account)
self.client.save_accounts()
def remove_account(self, pos):
del self.client.accounts[pos]
self.client.save_accounts()
def set_active(self, pos, active):
account = self.client.accounts[pos]
account.active = active
self.client.accounts.updated_item(account)
self.client.save_accounts()
def closed(self):
self.binding.unbind()
class DumpController(object):
def __init__(self, client, view):
self.view = view
view.controller = self
self.accounts = client.accounts
_observe_list(self.accounts, self._add, self._del, None)
def _add(self, index, account):
self._update(None, getattr(account, 'circuit', None), account.uni)
account.update_evt['circuit'].add_observer(self._update, account.uni)
def _del(self, index, account):
account.update_evt['circuit'] -= self._update
if hasattr(account, 'circuit'):
account.circuit.dump_evt -= self.view.show_line
def _update(self, old, value, account_uni):
if old is not None:
old.dump_evt -= self.view.show_line
if value is not None:
value.dump_evt.add_observer(self.view.show_line, account_uni)
def closed(self):
_unobserve_list(self.accounts, self._add, self._del, None)
class ConversationController(object):
def __init__(self, tabs_controller, conversation, view):
self.tabs_controller = tabs_controller
self.conversation = conversation
self.view = view
view.controller = self
conversation.unknown_target_evt += view.show_unknown_target
conversation.delivery_failed_evt += view.show_delivery_failed
_observe_list(conversation.messages, self.add_message, None, None)
def add_message(self, index, message):
line = "(%s) <%s> %s" % (message.time.strftime("%H:%M:%S"),
message.source, message.message)
self.view.show_message(line)
def enter(self, text):
if text.startswith('/'):
self.view.show_unknown_command()
else:
self.conversation.send_message(text)
def closed(self):
_unobserve_list(self.conversation.messages, self.add_message, None, None)
self.conversation.unknown_target_evt -= self.view.show_unknown_target
self.conversation.delivery_failed_evt -= self.view.show_delivery_failed
class ConferenceController(ConversationController):
def __init__(self, tabs_controller, conference, view):
self.conference = conference
ConversationController.__init__(self, tabs_controller, conference,
view)
self.binding = ListBinding(conference.members, view.members,
lambda member: (member.uni, member.nick))
def open_conversation(self, pos):
account = self.conference.conferencing.account
member = self.conference.members[pos]
conversation = account.get_conversation(member.uni)
self.tabs_controller.focus_conversation(conversation)
def closed(self):
self.binding.unbind()
ConversationController.closed(self)
class TabsController(object):
def __init__(self, client, view):
self.view = view
view.controller = self
self.view_to_model = {}
self.model_to_controller = {}
_observe_list(client.accounts, self._add_account, self._del_account, None)
def _add_account(self, index, account):
_observe_dict(account.conversations, self._add_conversation,
self._del_conversation, None, account.conversations,
self.view.show_conversation, ConversationController)
_observe_dict(account.conferences, self._add_conversation,
self._del_conversation, None, account.conferences,
self.view.show_conference, ConferenceController)
def _del_account(self, index, account):
_unobserve_dict(account.conversations, self._add_conversation,
self._del_conversation, None)
_unobserve_dict(account.conferences, self._add_conversation,
self._del_conversation, None)
def _add_conversation(self, uni, old, conversation, list, show, Class):
conv_view = show(conversation.uni)
controller = Class(self, conversation, conv_view)
self.view_to_model[conv_view] = list, conversation.uni
self.model_to_controller[conversation] = controller, conv_view
def _del_conversation(self, uni, conversation):
controller, view = self.model_to_controller.pop(conversation)
del self.view_to_model[view]
self.view.remove_tab(view)
controller.closed()
def focus_conversation(self, conversation):
self.view.focus_tab(self.model_to_controller[conversation][1])
def close_tab(self, view):
list, uni = self.view_to_model[view]
del list[uni]
class FriendListController(object):
def __init__(self, client, view, tabs_controller):
self.view = view
view.controller = self
self.tabs_controller = tabs_controller
self.friends = ObsList()
self.binding = ListBinding(self.friends, view.friends, self._make_row)
_observe_list(client.accounts, self._add_account, self._del_account, None)
def _add_account(self, index, account):
_observe_dict(account.friends, self._add_friend, self._del_friend,
self._update_friend)
def _del_account(self, index, account):
_unobserve_dict(account.friends, self._add_friend, self._del_friend,
self._update_friend)
def _add_friend(self, uni, old, friend):
self.friends.append(friend)
def _del_friend(self, uni, friend):
self.friends.remove(friend)
def _update_friend(self, uni, friend):
self.friends.updated_item(friend)
def _make_row(self, friend):
uni = friend.uni
if uni.startswith('psyc://' + friend.account.server + '/'):
uni = uni.rpartition('/~')[2]
return (uni, friend.presence.availability > 1, friend.state)
def open_conversation(self, pos):
friend = self.friends[pos]
model = friend.account.get_conversation(friend.uni)
self.tabs_controller.focus_conversation(model)
def accept_friendship(self, pos):
friend = self.friends[pos]
schedule(friend.account.add_friend, friend.uni)
def cancel_friendship(self, pos):
friend = self.friends[pos]
schedule(friend.account.remove_friend, friend.uni)
class MainController(object):
def __init__(self, client, view):
self.client = client
self.view = view
view.controller = self
self.active_accounts = set()
self.tabs_controller = TabsController(client, view.tabs_view)
FriendListController(client, view.friends_view, self.tabs_controller)
_observe_list(client.accounts, self._add_account, self._del_account,
self._update_account)
def _add_account(self, index, account):
account.no_password_evt.add_observer(self._no_password, account)
account.connection_error_evt.add_observer(self._conn_error, account)
account.no_such_user_evt += self._no_such_user
account.auth_error_evt.add_observer(self._auth_error, account)
if account.active:
self._account_activated(account)
def _del_account(self, index, account):
account.no_password_evt -= self._no_password
account.connection_error_evt -= self._conn_error
account.no_such_user_evt -= self._no_such_user
account.auth_error_evt -= self._auth_error
if account.active:
self._account_deactivated(account)
def _update_account(self, index, account):
if account.active:
self._account_activated(account)
else:
self._account_deactivated(account)
def _account_activated(self, account):
had_active_accounts = bool(self.active_accounts)
self.active_accounts.add(account)
if not had_active_accounts:
self.view.show_active_accounts(True)
def _account_deactivated(self, account):
had_active_accounts = bool(self.active_accounts)
self.active_accounts.discard(account)
if had_active_accounts and not self.active_accounts:
self.view.show_active_accounts(False)
def _no_password(self, account):
def callback():
account.active = True
self.client.accounts.updated_item(account)
self.client.save_accounts()
self.view.show_password_dialog(account.uni, account.__dict__, callback)
def _conn_error(self, error, account):
self.view.show_conn_error(account.uni, error.args[0])
def _no_such_user(self, error):
self.view.show_no_such_user(error.args[0])
def _auth_error(self, error, account):
self.view.show_auth_error(account.uni, error.args[0])
def open_accounts(self):
AccountsController(self.client, self.view.show_accounts())
def open_dump(self):
DumpController(self.client, self.view.show_dump_win())
def open_conversation(self):
accounts = [acc for acc in self.client.accounts if acc.active]
def callback(account, server, person):
uni = 'psyc://%s/~%s' % (server, person)
model = accounts[account].get_conversation(uni)
self.tabs_controller.focus_conversation(model)
self.view.show_open_conv_dialog((account.uni for account in accounts),
callback)
def open_conference(self):
accounts = [acc for acc in self.client.accounts if acc.active]
def callback(account, server, place):
uni = 'psyc://%s/@%s' % (server, place)
model = accounts[account].get_conference(uni, subscribe=True)
self.tabs_controller.focus_conversation(model)
self.view.show_open_conf_dialog((account.uni for account in accounts),
callback)
def add_friend(self):
accounts = [acc for acc in self.client.accounts if acc.active]
def callback(account, server, person):
uni = 'psyc://%s/~%s' % (server, person)
schedule(accounts[account].add_friend, uni)
self.view.show_add_friend_dialog((account.uni for account in accounts),
callback)
def quit(self):
_unobserve_list(self.client.accounts, self._add_account,
self._del_account, self._update_account)
self.client.quit()

View File

@ -0,0 +1,349 @@
"""
pypsyc.client.model
~~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from collections import namedtuple, defaultdict
from datetime import datetime
from json import dump, load
from twisted.internet import reactor
from twisted.internet.protocol import ClientFactory
from pypsyc.core.mmp import Circuit, Uni
from pypsyc.core.psyc import PSYCObject
from pypsyc.client.observable import ObsObj, ObsAttr, ObsList, ObsDict
from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError,
AuthenticationError, LinkingClient as LinkingProtocol,
RoutingErrorClient as RoutingErrorProtocol,
Messaging as MessagingProtocol,
ConferencingClient as ConferencingProtocol,
FriendshipClient as FriendshipProtocol,
ClientInterfaceClient as ClientInterfaceProtocol)
from pypsyc.util import (schedule, Event, resolve_hostname, connect,
key_intersection)
class Message(object):
def __init__(self, source, message):
self.source = source
self.message = message
self.time = datetime.now()
class Conversation(object):
def __init__(self, messaging, uni):
self.messaging = messaging
self.uni = uni
self.messages = ObsList()
self.unknown_target_evt = Event()
self.delivery_failed_evt = Event()
def send_message(self, message):
self.messaging.protocol.send_private_message(
self.uni, message, relay=self.messaging.account.uni)
self.messages.append(Message(self.messaging.account.uni, message))
Member = namedtuple('Member', ('uni', 'nick'))
class Conference(object):
def __init__(self, conferencing, uni):
self.conferencing = conferencing
self.uni = uni
self.members = ObsList()
self.messages = ObsList()
self.unknown_target_evt = Event()
self.delivery_failed_evt = Event()
def send_message(self, message):
self.conferencing.protocol.send_public_message(self.uni, message)
class Messaging(object):
def __init__(self, account):
self.account = account
self.protocol = MessagingProtocol(self)
def private_message(self, source, message):
conversation = self.account.get_conversation(source)
conversation.messages.append(Message(source, message))
def connected(self, circuit):
self.psyc = circuit.psyc
self.psyc.add_handler(self.protocol)
def disconnected(self):
pass
class Conferencing(object):
def __init__(self, account):
self.account = account
self.protocol = ConferencingProtocol(self)
def member_entered(self, place, uni, nick):
conference = self.account.get_conference(place)
conference.members.append(Member(uni, nick))
def member_left(self, place, uni):
conference = self.account.get_conference(place)
for i in conference.members:
if i.uni == uni:
conference.members.remove(i)
def public_message(self, place, source, message):
conference = self.account.get_conference(place)
conference.messages.append(Message(source, message))
def connected(self, circuit):
self.psyc = circuit.psyc
self.psyc.add_handler(self.protocol)
circuit.add_pvar_handler(self.protocol)
def disconnected(self):
pass
Presence = namedtuple('Presence', ('availability',))
class Friend(object):
def __init__(self, account, uni):
self.account = account
self.uni = uni
self.presence = Presence(0)
class FriendList(object):
def __init__(self, account):
self.account = account
self.protocol = FriendshipProtocol(self)
def friendship(self, uni, state):
friend = self.account.friends.get(uni) or Friend(self.account, uni)
friend.state = state
if uni in self.account.friends:
self.account.friends.updated_item(uni)
else:
self.account.friends[uni] = friend
def friendship_removed(self, uni):
del self.account.friends[uni]
def presence(self, context, value):
self.account.friends[context].presence = Presence(value)
self.account.friends.updated_item(context)
def connected(self, circuit):
self.psyc = circuit.psyc
self.psyc.add_handler(self.protocol)
circuit.add_pvar_handler(self.protocol)
def disconnected(self):
self.account.friends.clear()
class Account(ObsObj):
_active = False
circuit = ObsAttr('circuit')
def __init__(self, client, server, person, password, save_password,
active):
ObsObj.__init__(self)
self.no_password_evt = Event()
self.connection_error_evt = Event()
self.no_such_user_evt = Event()
self.auth_error_evt = Event()
self.client = client
self.server = server
self.person = person
self.resource = "*Resource"
self.password = password
self.save_password = save_password
schedule(setattr, self, 'active', active)
self.conversations = ObsDict()
self.conferences = ObsDict()
self.conferences.delitem_evt += self._del_conference
self.friends = ObsDict()
self.messaging = Messaging(self)
self.conferencing = Conferencing(self)
self.friend_list = FriendList(self)
@property
def uni(self):
return Uni.from_parts([self.server, '~' + self.person])
def get_conversation(self, uni):
if not uni in self.conversations:
self.conversations[uni] = Conversation(self.messaging, uni)
return self.conversations[uni]
def get_conference(self, uni, subscribe=False):
if not uni in self.conferences:
self.conferences[uni] = Conference(self.conferencing, uni)
if subscribe:
schedule(self._subscribe, uni)
return self.conferences[uni]
def _subscribe(self, uni):
try:
self.client_interface.subscribe(uni)
except UnknownTargetError:
self.conferences[uni].unknown_target_evt()
except DeliveryFailedError as e:
self.conferences[uni].delivery_failed_evt(e.args[0])
def _del_conference(self, uni, old):
schedule(self.client_interface.unsubscribe, uni)
def add_friend(self, uni):
self.client_interface.add_friend(uni)
def remove_friend(self, uni):
self.client_interface.remove_friend(uni)
def unknown_target_error(self, uni):
conversation = self.conversations.get(uni) or self.conferences.get(uni)
if conversation:
conversation.unknown_target_evt()
def delivery_failed_error(self, uni, message):
conversation = self.conversations.get(uni) or self.conferences.get(uni)
if conversation:
conversation.delivery_failed_evt(message)
@property
def active(self):
return self._active
@active.setter
def active(self, active):
if active != self._active:
self._active = active
if active:
schedule(self._activate)
elif hasattr(self, 'circuit'):
self._deactivate()
def _activate(self):
if not self.save_password and not self.password:
self.active = False
self.no_password_evt()
return
try:
ip, port = resolve_hostname(self.server)
circuit = self.circuit = connect(ip, port, self.client)
except Exception as e:
return self._error(self.connection_error_evt, e)
try:
linking_protocol = LinkingProtocol(circuit)
linking_protocol.link(self.uni, self.resource, self.password)
self.psyc = circuit.psyc
self.psyc.uni = self.uni.chain(self.resource)
except UnknownTargetError as e:
return self._error(self.no_such_user_evt, e)
except AuthenticationError as e:
return self._error(self.auth_error_evt, e)
circuit.psyc.add_handler(RoutingErrorProtocol(self))
self.client_interface = ClientInterfaceProtocol(self)
self.messaging.connected(circuit)
self.conferencing.connected(circuit)
self.friend_list.connected(circuit)
def _deactivate(self):
self.circuit.transport.loseConnection()
del self.circuit
self.messaging.disconnected()
self.conferencing.disconnected()
self.friend_list.disconnected()
def _error(self, event, error):
self.active = False
self.client.accounts.updated_item(self)
event(error)
def connection_error(self, error):
self._error(self.connection_error_evt, error)
class ClientCircuit(Circuit):
def __init__(self):
self.dump_evt = Event()
Circuit.__init__(self)
self.psyc = PSYCObject(self.send)
self.pvar_handlers = defaultdict(dict)
def connectionMade(self):
if hasattr(self.transport, 'write'):
self.orig_write = self.transport.write
self.transport.write = self.dump_write
Circuit.connectionMade(self)
def dump_write(self, data):
for line in data.strip('\n').split('\n'):
self.dump_evt('o', line)
self.orig_write(data)
def lineReceived(self, line):
self.dump_evt('i', line)
return Circuit.lineReceived(self, line)
def packet_received(self, header, content):
if '_source_relay' in header:
assert header.source.is_ancestor_of(self.psyc.uni)
header.source = Uni(header['_source_relay'])
packet = self.psyc.handle_packet(header, content)
for modifier in key_intersection(self.pvar_handlers, packet.modifiers):
handlers = self.pvar_handlers[modifier]
vars = packet.modifiers[modifier]
for var in key_intersection(handlers, vars):
handlers[var](packet.header.context, vars)
def add_pvar_handler(self, handler):
for i in dir(handler):
if i.startswith('state_'):
_, operation, var = i.split('_', 2)
modifier = {'set': '=', 'add': '+', 'remove': '-'}[operation]
self.pvar_handlers[modifier]['_' + var] = getattr(handler, i)
class Client(ClientFactory, object):
protocol = ClientCircuit
def __init__(self, accounts_file=None):
self.accounts = ObsList()
self.accounts_file = accounts_file
def load_accounts(self):
with open(self.accounts_file, 'r') as f:
self.accounts.extend(Account(self, d['server'].encode('utf-8'),
d['person'].encode('utf-8'),
d['password'].encode('utf-8'),
d['save_password'], d['active'])
for d in load(f))
def save_accounts(self):
with open(self.accounts_file, 'w') as f:
dump(self.accounts, f, indent=4, default=lambda a:
{'server': a.server, 'person': a.person,
'password': a.password if a.save_password else '',
'save_password': a.save_password, 'active': a.active})
def connection_lost(self, circuit, error):
for account in self.accounts:
if getattr(account, 'circuit', None) is circuit:
account.connection_error(Exception(str(error)))
def quit(self):
for account in self.accounts:
account.active = False
reactor.stop()

View File

@ -0,0 +1,108 @@
"""
pypsyc.client.observable
~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from collections import MutableSequence, MutableMapping
from pypsyc.util import Event
class ObsAttr(object):
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
try:
return instance.__dict__[self.name]
except KeyError:
raise AttributeError
def __set__(self, instance, value):
old = instance.__dict__.get(self.name)
instance.__dict__[self.name] = value
if value != old:
instance.update_evt[self.name](old, value)
def __delete__(self, instance):
old = instance.__dict__.get(self.name)
try:
del instance.__dict__[self.name]
except KeyError:
raise AttributeError
instance.update_evt[self.name](old, None)
class ObsObj(object):
def __init__(self):
self.update_evt = dict((obs_attr.name, Event()) for obs_attr in
self.__class__.__dict__.itervalues()
if isinstance(obs_attr, ObsAttr))
class ObsList(list, MutableSequence):
def __init__(self, *args, **kwds):
list.__init__(self, *args, **kwds)
self.setitem_evt = Event()
self.delitem_evt = Event()
self.insert_evt = Event()
self.update_evt = Event()
def __setitem__(self, index, value):
old = self[index]
list.__setitem__(self, index, value)
if value != old:
self.setitem_evt(index, old, value)
def __delitem__(self, index):
if index < 0:
index = len(self) + index
old = self[index]
list.__delitem__(self, index)
self.delitem_evt(index, old)
def insert(self, index, value):
list.insert(self, index, value)
self.insert_evt(index, value)
append = MutableSequence.append
reverse = MutableSequence.reverse
extend = MutableSequence.extend
pop = MutableSequence.pop
remove = MutableSequence.remove
__iadd__ = MutableSequence.__iadd__
def updated_item(self, obj):
self.update_evt(self.index(obj), obj)
class ObsDict(dict, MutableMapping):
def __init__(self, *args, **kwds):
dict.__init__(self, *args, **kwds)
self.setitem_evt = Event()
self.delitem_evt = Event()
self.update_evt = Event()
def __setitem__(self, key, value):
old = self.get(key)
dict.__setitem__(self, key, value)
if value != old:
self.setitem_evt(key, old, value)
def __delitem__(self, key):
old = self.get(key)
dict.__delitem__(self, key)
self.delitem_evt(key, old)
pop = MutableMapping.pop
popitem = MutableMapping.popitem
clear = MutableMapping.clear
update = MutableMapping.update
setdefault = MutableMapping.setdefault
def updated_item(self, key):
self.update_evt(key, self[key])

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,686 @@
"""
pypsyc.client.view
~~~~~~~~~~~~~~~~~~
pypsyc GTK views
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import pygtk
pygtk.require('2.0')
import gtk
import pango
from gobject import idle_add
from pkg_resources import resource_filename
def _(message):
return message
class Form(gtk.HBox):
def __init__(self, dict_, attrs):
gtk.HBox.__init__(self)
self.dict = dict_
self.fields = []
left = gtk.VBox()
self.pack_start(left, False, False, 10)
right = gtk.VBox()
self.pack_start(right)
for l, a in attrs:
x = dict_[a]
le, ri, value_func = getattr(self, '_' + type(x).__name__)(l, x)
left.pack_start(le, padding=10)
right.pack_start(ri)
self.fields.append((a, value_func))
def _str(self, l, s):
label = gtk.Label(_(l) + ":")
label.set_alignment(0, 0.5)
entry = gtk.Entry()
entry.set_text(s)
if l.lower() == 'password':
entry.set_visibility(False)
return label, entry, entry.get_text
def _bool(self, l, b):
check = gtk.CheckButton(_(l))
check.set_active(b)
return gtk.Label(), check, check.get_active
def _list(self, l, list_):
label = gtk.Label(_(l) + ":")
label.set_alignment(0, 0.5)
combobox = gtk.combo_box_new_text()
for i in list_:
combobox.append_text(i)
combobox.set_active(0)
return label, combobox, combobox.get_active
_generator = _list
def save(self):
self.dict.update((a, f()) for a, f in self.fields)
return self.dict
def _open_form_dialog(title, window, dict_, attrs, buttons, func, markup=None):
dialog = gtk.Dialog(title, window,
(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT |
gtk.DIALOG_NO_SEPARATOR), buttons)
if markup is not None:
label = gtk.Label()
label.set_markup(markup)
dialog.vbox.pack_start(label)
form = Form(dict_, attrs)
dialog.vbox.pack_start(form)
dialog.connect('response', _dialog_response, form, func)
dialog.show_all()
def _dialog_response(w, response, form, func):
if response == gtk.RESPONSE_ACCEPT:
func(form.save())
if response != gtk.RESPONSE_DELETE_EVENT:
w.destroy()
class AccountsView(object):
def __init__(self):
self.window = self.create_window()
self.window.show_all()
def create_window(self):
win = gtk.Window()
win.set_title(_("Accounts"))
win.connect('destroy', lambda w: self.controller.closed())
vbox = gtk.VBox()
win.add(vbox)
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scroll.add(self.create_tree_view())
vbox.pack_start(scroll)
buttons = gtk.HButtonBox()
vbox.pack_start(buttons)
add_button = gtk.Button(stock=gtk.STOCK_ADD)
buttons.pack_start(add_button)
remove_button = self.remove_button = gtk.Button(stock=gtk.STOCK_REMOVE)
remove_button.set_sensitive(False)
buttons.pack_start(remove_button)
edit_button = self.edit_button = gtk.Button(stock=gtk.STOCK_EDIT)
edit_button.set_sensitive(False)
buttons.pack_start(edit_button)
add_button.connect('clicked', lambda w: self.controller.add_account())
remove_button.connect('clicked', self.on_remove)
edit_button.connect('clicked', self.on_edit)
return win
def create_tree_view(self):
self.accounts = gtk.ListStore(str, bool)
tv = gtk.TreeView(self.accounts)
tv.set_size_request(400, 200)
self.selection = tv.get_selection()
self.selection.connect('changed', self.on_selection)
renderer = gtk.CellRendererToggle()
renderer.connect('toggled', self.on_toggle)
column = gtk.TreeViewColumn(_("Active"), renderer, active=1)
tv.append_column(column)
column = gtk.TreeViewColumn("UNI", gtk.CellRendererText(), text=0)
tv.append_column(column)
return tv
def on_selection(self, selection):
selected = selection.get_selected()[1] is not None
self.remove_button.set_sensitive(selected)
self.edit_button.set_sensitive(selected)
def on_remove(self, w):
model, iter = self.selection.get_selected()
self.controller.remove_account(model.get_path(iter)[0])
def on_edit(self, w):
model, iter = self.selection.get_selected()
self.controller.edit_account(model.get_path(iter)[0])
def on_toggle(self, w, path):
self.controller.set_active(int(path), not self.accounts[path][1])
def show_addedit_dialog(self, account, add, callback):
attrs = (('Server', 'server'),
('User', 'person'),
('Password', 'password'),
('Save password', 'save_password'))
_open_form_dialog(_("Add Account" if add else "Edit Account"),
self.window, account, attrs,
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT),
lambda d: callback())
class ConversationView(gtk.VBox):
def __init__(self, tabs_view):
self.tabs_view = tabs_view
gtk.VBox.__init__(self)
self.pack_start(self.create_content())
self.entry = gtk.Entry()
self.entry.connect('activate', self._enter)
self.pack_start(self.entry, False)
def create_content(self):
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
self.vadjustment = scroll.get_vadjustment()
tv = gtk.TextView()
self.text_buffer = tv.get_buffer()
tv.set_editable(False)
tv.set_cursor_visible(False)
tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
scroll.add(tv)
return scroll
def _enter(self, w):
text = w.get_text()
if not text:
return
self.controller.enter(text)
w.set_text("")
def show_message(self, message):
self._show_line(message)
self.tabs_view.showed_message(self)
def show_unknown_command(self):
self._show_line(_("Unknown command"))
def show_unknown_target(self):
self._show_line(_("Unknown user"))
def show_delivery_failed(self, message):
self._show_line(_("Delivery failed") + ': ' + message)
def _show_line(self, line):
va = self.vadjustment
must_scroll = (va.value == va.upper - va.page_size)
self.text_buffer.insert(self.text_buffer.get_end_iter(), '\n' + line)
if must_scroll:
idle_add(self._scroll)
def _scroll(self):
va = self.vadjustment
va.set_value(va.upper - va.page_size)
class ConferenceView(ConversationView):
def create_content(self):
hpane = gtk.HPaned()
hpane.pack1(ConversationView.create_content(self), True)
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scroll.add(self.create_tree_view())
hpane.pack2(scroll, True)
hpane.set_position(450)
return hpane
def create_tree_view(self):
self.members = gtk.ListStore(str, str)
tv = gtk.TreeView(self.members)
tv.set_headers_visible(False)
tv.set_tooltip_column(0)
cell = gtk.CellRendererText()
cell.set_property('ellipsize', pango.ELLIPSIZE_END)
column = gtk.TreeViewColumn(None, cell, text=1)
tv.append_column(column)
tv.connect('row-activated', self._double_click)
tv.connect('button-press-event', self._button_press)
tv.connect('popup-menu', self._popup_menu)
return tv
def _double_click(self, tv, path, view_column):
self.controller.open_conversation(path[0])
def _button_press(self, widget, event):
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
model = widget.get_model()
path = widget.get_path_at_pos(int(event.x), int(event.y))[0]
menu = self.create_context_menu(model, model.get_iter(path))
menu.popup(None, None, None, event.button, event.time)
def _popup_menu(self, widget):
model, iter = widget.get_selection().get_selected()
menu = self.create_context_menu(model, iter)
menu.popup(None, None, None, 3, 0)
return True
def create_context_menu(self, model, iter):
menu = gtk.Menu()
conv = gtk.MenuItem(_("Open Conversation"))
menu.append(conv)
conv.connect('activate', lambda w:
self.controller.open_conversation(model.get_path(iter)[0]))
if model.get_value(iter, 2) == 'offered':
accept = gtk.MenuItem(_("Accept Friendship Request"))
menu.append(accept)
accept.connect('activate', lambda w:
self.controller.accept_friendship(model.get_path(iter)[0]))
menu.show_all()
return menu
def show_unknown_target(self):
self._show_line(_("Unknown place"))
class TabsView(object):
def __init__(self, status_icon):
self.status_icon = status_icon
self.window = self.create_window()
self.window.set_default_size(600, 400)
self.unread_tabs = []
settings = gtk.Entry().get_settings()
settings.set_property('gtk-entry-select-on-focus', False)
def create_window(self):
win = gtk.Window()
win.set_title("pypsyc")
win.connect("delete_event", self._delete_event)
win.connect('focus-in-event', self._window_focus)
self.notebook = gtk.Notebook()
self.notebook.set_scrollable(True)
self.notebook.connect('switch-page', self._switched_page)
win.add(self.notebook)
self.notebook.show()
return win
def _delete_event(self, w, e):
self.window.hide()
for tab in self.notebook:
self.controller.close_tab(tab)
return True
def _window_focus(self, w, e):
current_page = self.notebook.get_current_page()
if current_page != -1:
self._switched_page(None, None, current_page)
def _switched_page(self, w, page_ptr, page_num):
tab = self.notebook.get_nth_page(page_num)
if tab in self.unread_tabs:
self._change_tab_color(tab, 'black')
self.unread_tabs.remove(tab)
if not self.unread_tabs:
self.status_icon.set_blinking(False)
idle_add(tab.entry.grab_focus)
def showed_message(self, tab):
if (not self.window.is_active() or
self.notebook.get_current_page() != self.notebook.page_num(tab)):
self._change_tab_color(tab, 'red')
if tab not in self.unread_tabs:
if not self.unread_tabs:
self.status_icon.set_blinking(True)
self.unread_tabs.append(tab)
def _change_tab_color(self, tab, color_spec):
tab_label = self.notebook.get_tab_label(tab).get_children()[0]
color = gtk.gdk.color_parse(color_spec)
tab_label.modify_fg(gtk.STATE_NORMAL, color)
tab_label.modify_fg(gtk.STATE_ACTIVE, color)
def on_status_icon_click(self):
if not self.unread_tabs:
return True
self.focus_tab(self.unread_tabs[0])
def show_conversation(self, label):
view = ConversationView(self)
self._add_tab(view, label)
return view
def show_conference(self, label):
view = ConferenceView(self)
self._add_tab(view, label)
return view
def _add_tab(self, tab, label):
tab_label = gtk.HBox()
tab_label.pack_start(gtk.Label(label))
image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
button = gtk.Button()
button.set_image(image)
button.set_relief(gtk.RELIEF_NONE)
button.connect('clicked', lambda w: self.controller.close_tab(tab))
tab_label.pack_start(button)
tab.show_all()
tab_label.show_all()
self.notebook.append_page(tab, tab_label)
def focus_tab(self, tab):
self.notebook.set_current_page(self.notebook.page_num(tab))
self.window.present()
def remove_tab(self, tab):
self.notebook.remove_page(self.notebook.page_num(tab))
if self.notebook.get_n_pages() == 0:
self.window.hide()
if tab in self.unread_tabs:
self.unread_tabs.remove(tab)
if not self.unread_tabs:
self.status_icon.set_blinking(False)
class FriendsView(object):
def __init__(self):
self.list_view = self.create_tree_view()
def create_tree_view(self):
self.friends = gtk.ListStore(str, bool, str)
tv = gtk.TreeView(self.friends)
tv.set_headers_visible(False)
tv.set_size_request(200, 400)
tv.set_property('has-tooltip', True)
cell = gtk.CellRendererText()
cell.set_property('foreground-set', True)
column = gtk.TreeViewColumn(None, cell, text=0)
column.set_cell_data_func(cell, self._foreground)
tv.append_column(column)
tv.connect('row-activated', self._double_click)
tv.connect('query-tooltip', self._tooltip)
tv.connect('button-press-event', self._button_press)
tv.connect('popup-menu', self._popup_menu)
return tv
def _foreground(self, column, cell, model, iter):
if model.get_value(iter, 1):
cell.set_property('foreground', 'black')
else:
cell.set_property('foreground', 'grey')
def _double_click(self, tv, path, view_column):
self.controller.open_conversation(path[0])
def _tooltip(self, widget, x, y, keyboard_mode, tooltip):
tooltip_context = widget.get_tooltip_context(x, y, keyboard_mode)
if tooltip_context is None:
return False
model, path, iter = tooltip_context
tooltip.set_text(_("Friendship state") + ": %s" % model.get(iter, 2))
widget.set_tooltip_row(tooltip, path)
return True
def _button_press(self, widget, event):
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
model = widget.get_model()
path = widget.get_path_at_pos(int(event.x), int(event.y))[0]
menu = self.create_context_menu(model, model.get_iter(path))
menu.popup(None, None, None, event.button, event.time)
def _popup_menu(self, widget):
model, iter = widget.get_selection().get_selected()
menu = self.create_context_menu(model, iter)
menu.popup(None, None, None, 3, 0)
return True
def create_context_menu(self, model, iter):
menu = gtk.Menu()
conv = gtk.MenuItem(_("Open Conversation"))
menu.append(conv)
conv.connect('activate', lambda w:
self.controller.open_conversation(model.get_path(iter)[0]))
if model.get_value(iter, 2) == 'offered':
accept = gtk.MenuItem(_("Accept Friendship Request"))
menu.append(accept)
accept.connect('activate', lambda w:
self.controller.accept_friendship(model.get_path(iter)[0]))
deny = gtk.MenuItem(_("Deny Friendship Request"))
menu.append(deny)
deny.connect('activate', lambda w:
self.controller.cancel_friendship(model.get_path(iter)[0]))
elif model.get_value(iter, 2) == 'pending':
cancel = gtk.MenuItem(_("Cancel Friendship Request"))
menu.append(cancel)
cancel.connect('activate', lambda w:
self.controller.cancel_friendship(model.get_path(iter)[0]))
else:
cancel = gtk.MenuItem(_("Cancel Friendship"))
menu.append(cancel)
cancel.connect('activate', lambda w:
self.controller.cancel_friendship(model.get_path(iter)[0]))
menu.show_all()
return menu
class DumpView(object):
def __init__(self):
self.buffers = {}
self.window = self.create_window()
self.window.show_all()
def create_window(self):
win = gtk.Window()
win.set_title(_("pypsyc protocol dump"))
win.set_default_size(500, 300)
win.connect('destroy', lambda w: self.controller.closed())
self.notebook = gtk.Notebook()
win.add(self.notebook)
return win
def show_line(self, direction, line, account):
if account not in self.buffers:
scroll = gtk.ScrolledWindow()
tv = gtk.TextView()
tv.set_editable(False)
scroll.add(tv)
scroll.show_all()
self.notebook.append_page(scroll, gtk.Label(account))
self.buffers[account] = tv.get_buffer(), scroll.get_vadjustment()
buffer, va = self.buffers[account]
must_scroll = (va.value == va.upper - va.page_size)
line = repr(line)[1:-1]
buffer.insert(buffer.get_end_iter(),
('\n< ' if direction == 'o' else '\n> ') + line)
if must_scroll:
idle_add(self.scroll, va)
def scroll(self, va):
va.set_value(va.upper - va.page_size)
class MainView(object):
def __init__(self):
self.window = self.create_window()
self.window.show_all()
self.position = self.window.get_position()
self.status_icon = self.create_status_icon()
self.tabs_view = TabsView(self.status_icon)
def create_window(self):
win = gtk.Window()
win.set_title("pypsyc")
win.connect("delete_event", lambda w, e: win.hide() or True)
vbox = gtk.VBox()
win.add(vbox)
menu_bar = gtk.MenuBar()
vbox.pack_start(menu_bar, False)
menu_bar.append(self.create_file_menu())
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
vbox.pack_start(scroll)
self.friends_view = FriendsView()
scroll.add(self.friends_view.list_view)
return win
def create_file_menu(self):
menu = gtk.Menu()
accounts = gtk.MenuItem(_("Accounts"))
menu.append(accounts)
accounts.connect('activate', lambda w: self.controller.open_accounts())
conv = gtk.MenuItem(_("Open Conversation"))
menu.append(conv)
conv.connect('activate', lambda w: self.controller.open_conversation())
conf = gtk.MenuItem(_("Open Conference"))
menu.append(conf)
conf.connect('activate', lambda w: self.controller.open_conference())
add_friend = gtk.MenuItem(_("Add Friend"))
menu.append(add_friend)
add_friend.connect('activate', lambda w: self.controller.add_friend())
self.account_buttons = (conv, conf, add_friend)
self.show_active_accounts(False)
dump = gtk.MenuItem(_("Protocol Dump"))
menu.append(dump)
dump.connect('activate', lambda w: self.controller.open_dump())
menu.append(gtk.MenuItem())
quit = gtk.MenuItem(_("Quit"))
menu.append(quit)
quit.connect('activate', lambda w: self.controller.quit())
item = gtk.MenuItem(_("File"))
item.set_submenu(menu)
return item
def create_status_icon(self):
status_icon = gtk.StatusIcon()
status_icon.set_from_file(resource_filename(__name__, 'psyc.ico'))
status_icon.connect('activate', self._activated_status_icon)
return status_icon
def _activated_status_icon(self, w):
if self.tabs_view.on_status_icon_click():
if self.window.is_active():
self.position = self.window.get_position()
self.window.hide()
else:
self.window.move(*self.position)
self.window.present()
def show_active_accounts(self, active):
for i in self.account_buttons:
i.set_sensitive(active)
def show_password_dialog(self, uni, account, callback):
title = _("Enter Password")
attrs = (('Password', 'password'),
('Save password', 'save_password'))
text = "<big><b>Please enter password for account %s.</b></big>" % uni
_open_form_dialog(title, self.window, account, attrs,
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
lambda d: callback(), markup=text)
def show_conn_error(self, account, message):
text = "%s - %s" % (_("Connection error"), account)
dialog = gtk.MessageDialog(self.window, (gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT),
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, text)
dialog.format_secondary_text(message)
dialog.connect('response', lambda w, r: dialog.destroy())
dialog.show()
def show_no_such_user(self, account):
text = "No such user"
dialog = gtk.MessageDialog(self.window, (gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT),
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, text)
dialog.format_secondary_text(account)
dialog.connect('response', lambda w, r: dialog.destroy())
dialog.show()
def show_auth_error(self, account, message):
text = "%s - %s" % (_("Authentication error"), account)
dialog = gtk.MessageDialog(self.window, (gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT),
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, text)
dialog.format_secondary_text(message)
dialog.connect('response', lambda w, r: dialog.destroy())
dialog.show()
def show_accounts(self):
return AccountsView()
def show_open_conv_dialog(self, accounts, callback):
data = {'account': accounts, 'server': "", 'person': ""}
attrs = (('Account', 'account'),
('Server', 'server'),
('Person', 'person'))
_open_form_dialog(_("Open Conversation"),
self.window, data, attrs,
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
lambda d: callback(**d))
def show_open_conf_dialog(self, accounts, callback):
data = {'account': accounts, 'server': "", 'place': ""}
attrs = (('Account', 'account'),
('Server', 'server'),
('Place', 'place'))
_open_form_dialog(_("Open Conference"),
self.window, data, attrs,
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
lambda d: callback(**d))
def show_add_friend_dialog(self, accounts, callback):
data = {'account': accounts, 'server': "", 'person': ""}
attrs = (('Account', 'account'),
('Server', 'server'),
('Person', 'person'))
_open_form_dialog(_("Add Friend"),
self.window, data, attrs,
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
lambda d: callback(**d))
def show_dump_win(self):
return DumpView()

View File

@ -0,0 +1,4 @@
"""
pypsyc.core
~~~~~~~~~~~
"""

158
mjacob2/pypsyc/core/mmp.py Normal file
View File

@ -0,0 +1,158 @@
"""
pypsyc.core.mmp
~~~~~~~~~~~~~~~
Classes which are related to the routing layer (called MMP).
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from cStringIO import StringIO
from twisted.internet.tcp import BaseClient
from twisted.protocols.basic import LineReceiver
class Uni(str):
"""
This class provides some helpers for working with PSYC Uniforms.
It can be initialized with a string or a list (:meth:`from_parts`).
"""
def __init__(self, uni):
str.__init__(uni)
self.prefix = uni.split(':', 1)[0]
def into_parts(self):
tmp = self.split(':', 1)[1]
return tmp.strip('/').split('/')
@classmethod
def from_parts(cls, parts, prefix='psyc'):
"""
Construct a new UNI object from a list of parts. A prefix other than
'psyc' can be given optionally.
:param parts: the parts which represent the UNI's path
:type parts: list of strings
:param prefix: prefix of the UNI
:type prefix: string
"""
uni = str.__new__(cls, '%s://%s' % (prefix, '/'.join(parts)))
uni.prefix = prefix
return uni
def chain(self, child):
return Uni('/'.join((self.strip('/'), child)))
def is_descendant_of(self, other):
"""Return whether this uni is a descendant of another."""
other = other.strip('/')
l = len(other)
return self[0:l] == other and self[l] == '/'
def is_ancestor_of(self, other):
"""Return whether this uni is an ancestor of another."""
self_ = self.strip('/')
l = len(self_)
return other[0:l] == self_ and other[l] == '/'
class Header(dict):
"""
A class which provides simple access to the header of a PSYC Packet.
.. attribute:: source
'_source' variable of the packet header as :class:`Uni`
.. attribute:: target
'_target' variable of the packet header as :class:`Uni`
.. attribute:: context
'_context' variable of the packet header as :class:`Uni`
"""
def _init(self):
"""Initialize the header with the set values."""
source = self.get('_source')
if source: self.source = Uni(source)
else: self.source = None
target = self.get('_target')
if target: self.target = Uni(target)
else: self.target = None
context = self.get('_context')
if context: self.context = Uni(context)
else: self.context = None
class Circuit(LineReceiver, object):
"""Base class for all PSYC (MMP) Circuits."""
delimiter = '\n'
def __init__(self):
# states:
# 0 = uninitialized
# 1 = header
# 2 = content
self.state = 0
self.vars = {'=': {}}
self.inited = None
def connectionMade(self):
if not hasattr(self, 'initiator'):
self.initiator = isinstance(self.transport, BaseClient)
if self.initiator:
self._init()
def _init(self):
self.send({}, '')
if callable(self.inited):
self.inited()
del self.inited
del self.initiator
def lineReceived(self, line):
if line == '|': # end of packet
if self.state != 0: # the connection is inited already
header = Header(self.vars['='])
header.update(self.vars[':'])
header._init()
self.packet_received(header, self.content)
elif not getattr(self, 'initiator', True):
self._init()
# reset parser state
self.state = 1
self.vars[':'] = {}
self.content = []
elif self.state == 1:
if line == '':
self.state = 2
else:
self.vars[line[0]].__setitem__(*line[1:].split('\t', 1))
elif self.state == 2:
self.content.append(line)
def packet_received(self, header, content):
"""Overide this in subclass."""
raise NotImplementedError
def send(self, header, content):
"""Send a packet over this circuit."""
out = StringIO()
out.writelines(':%s\t%s\n' % x for x in header.iteritems())
content = '\n'.join(content)
if content:
out.write('\n')
out.write(content)
out.write('\n')
out.write('|\n')
self.transport.write(out.getvalue())

254
mjacob2/pypsyc/core/psyc.py Normal file
View File

@ -0,0 +1,254 @@
"""
pypsyc.core.psyc
~~~~~~~~~~~~~~~~
Classes which are related to the entity layer.
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import logging
from collections import defaultdict
from os import urandom
from greenlet import greenlet
from pypsyc.core.mmp import Header
from pypsyc.util import Waiter
log = logging.getLogger(__name__)
def _get_binary_arg(it, must_len, first_fragment):
fragments = [first_fragment]
cur_len = len(first_fragment)
while cur_len < must_len:
f = it.next()
fragments.append(f)
cur_len += len(f) + 1
return '\n'.join(fragments)
class PSYCPacket(object):
"""
Initialize a new PSYCPacket with the given arguments.
:param modifiers: entity modifiers
:type modifiers: dict
:param mc: method
:type mc: string or None
:param data: data
:type data: string
Attributes:
.. attribute:: modifiers
entity modifiers as described `here <http://about.psyc.eu/Spec:Packet>`_
.. attribute:: cvars
same as :attr:`modifiers`\[':'\]
.. attribute:: mc
method as described `here <http://about.psyc.eu/Spec:Packet>`_
.. attribute:: data
data as described `here <http://about.psyc.eu/Spec:Packet>`_
"""
def __init__(self, modifiers=None, mc=None, data=''):
self.modifiers = modifiers or {}
self.cvars = self.modifiers.setdefault(':', {})
self.mc = mc
self.data = data
@classmethod
def from_kwds(cls, mc=None, data='', **kwds):
"""
Return a new PSYCPacket which is initialized with mc and data given by
arguments and cvars given by keyword arguments.
::
packet1 = PSYCPacket.from_kwds(mc='_message_public',
data="hello", _somevar='foo')
packet2 = PSYCPacket(mc='_message_public', data="hello",
modifiers={':': {'_somevar': 'foo'}})
packet1 == packet2 # True
:param mc: method
:type mc: string or None
:param data: data
:type data: string
:rtype: PSYCPacket
"""
return cls({':': kwds}, mc, data)
def render(self):
"""Return a generator of lines which represent this packet."""
for glyph, vars in self.modifiers.iteritems():
for x, y in vars.iteritems():
if '\n' in y:
yield '%s%s %s\t%s' % (glyph, x, len(y), y)
else:
yield '%s%s\t%s' % (glyph, x, y)
if self.mc:
yield self.mc
if self.data:
yield self.data
else:
assert not self.data
@classmethod
def parse(cls, content):
"""Parse a list of lines to a packet."""
modifiers = defaultdict(dict)
it = iter(content)
for line in it:
if line.startswith('_'): # body
return cls(modifiers, line, '\n'.join(it))
else: # entity-modifier
glyph = line[0]
x, y = line[1:].split('\t', 1)
if ' ' in x: # binary-arg
var, l = x.split(' ')
modifiers[glyph][var] = _get_binary_arg(it, int(l), y)
else: # simple-arg
modifiers[glyph][x] = y
return cls(modifiers, None, '')
def __repr__(self):
return '\n'.join((
"PSYCPacket(",
" modifiers={",
",\n".join(' '*8 + "'%s': %s" % i for i in self.modifiers.items()),
" },",
" mc=%r,",
" data=%r",
")"
)) % (self.mc, self.data)
def __eq__(self, other):
return (self.modifiers == other.modifiers and
self.mc == other.mc and
self.data == other.data)
def __ne__(self, other):
return not self.__eq__(other)
class PSYCObject(object):
"""
This is the base class for all classes that provide PSYC functionality,
such as receiving and sending :class:`PSYCPacket`\s.
Subclassed by :class:`pypsyc.server.Entity`. Used by
:class:`pypsyc.server.routing.ServerCircuit`.
"""
def __init__(self, sendfunc, uni=None):
self._send = sendfunc
self.uni = uni
self.handlers = {}
self.tags = {}
def handle_packet(self, header, content):
packet = PSYCPacket.parse(content)
packet.header = header
tag_relay = header.get('_tag_relay')
if tag_relay:
self.tags.pop(tag_relay).callback(packet)
return packet
mc = packet.mc
while mc:
f = self.handlers.get(mc)
if f is not None:
break
mc = mc.rpartition('_')[0]
else:
return packet
tag = header.get('_tag')
if tag is None:
try:
f(packet)
except Exception:
log.exception("error calling handler %s of %s", mc, self)
else:
def respond():
try:
response = f(packet)
except Exception:
log.exception("error calling handler %s of %s", mc, self)
response = PSYCPacket(mc='_error_internal')
self.sendmsg(header.source, response, {'_tag_relay': tag})
greenlet(respond).switch()
return packet
def sendmsg(self, target, packet=None, header=None, **kwds):
"""
Send a packet.
1.
Simple example usage::
packet = PSYCPacket(mc='_message_public',
modifiers={':': {'_somevar': 'foo'}})
psyc.sendmsg(target, packet)
This simply sends the packet to the target.
2.
The following code block is equal to the previous::
psyc.sendmsg(target, mc='_message_public', _somevar='foo')
See also :meth:`PSYCPacket.from_kwds`.
:param target: target of the packet
:type target: :class:`pypsyc.core.mmp.Uni` or \
:class:`pypsyc.core.mmp.Circuit`
:param packet: packet to send, use keyword arguments if None
:type packet: :class:`PSYCPacket` or None
:param header: additional header variables
:type header: dict or None
:rtype: PSYCPacket or None
"""
if packet is None:
packet = PSYCPacket.from_kwds(**kwds)
header = Header() if header is None else Header(header)
if self.uni is not None:
header['_source'] = self.uni
if packet.mc and packet.mc.startswith('_request'):
tag = urandom(20).replace('\n', '')
header['_tag'] = tag
waiter = self.tags[tag] = Waiter()
else:
waiter = None
if isinstance(target, str):
header['_target'] = target
header._init()
self._send(header, packet.render())
else: # the target is a circuit
target.send(header, packet.render())
if waiter is not None:
return waiter.get()
def add_handler(self, handler):
self.handlers.update((i[6:], getattr(handler, i)) for i in
dir(handler) if i.startswith('handle_'))
def __repr__(self):
return '<PSYCObject uni=%s>' % self.uni

333
mjacob2/pypsyc/protocol.py Normal file
View File

@ -0,0 +1,333 @@
"""
pypsyc.protocol
~~~~~~~~~~~~~~~
Protocol classes for PSYC :class:`~pypsyc.server.Entity`\s.
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import hmac
from hashlib import sha256
from itertools import izip
from os import urandom
from pypsyc.core.psyc import PSYCPacket
class Error(Exception):
pass
class ServerProtocol(object):
def __init__(self, package):
self.package = package
def sendmsg(self, *args, **kwds):
return self.package.entity.sendmsg(*args, **kwds)
def castmsg(self, *args, **kwds):
return self.package.entity.castmsg(*args, **kwds)
def state_set(self, *args, **kwds):
return self.package.entity.context_master.state_set(*args, **kwds)
def state_add(self, *args, **kwds):
return self.package.entity.context_master.state_add(*args, **kwds)
def state_remove(self, *args, **kwds):
return self.package.entity.context_master.state_remove(*args, **kwds)
class ClientProtocol(object):
def __init__(self, package):
self.package = package
def sendmsg(self, *args, **kwds):
return self.package.psyc.sendmsg(*args, **kwds)
class UnknownTargetError(Error):
pass
class DeliveryFailedError(Error):
pass
def check_response(packet, default_mc):
if packet.mc == '_error_unknown_target':
raise UnknownTargetError(packet.cvars['_uni'])
if packet.mc == '_failure_unsuccessful_delivery':
raise DeliveryFailedError(packet.data, packet.cvars['_uni'])
assert packet.mc == default_mc, "response mc is %s" % packet.mc
class RoutingErrorRoot(ServerProtocol):
def send_unknown_target_error(self, target, uni):
self.sendmsg(target, mc='_error_unknown_target', _uni=uni)
def send_delivery_failed_error(self, target, uni, message):
self.sendmsg(target, mc='_failure_unsuccessful_delivery', data=message,
_uni=uni)
class RoutingErrorRelaying(ServerProtocol):
def handle_error_unknown_target(self, packet):
self.package.unknown_target_error(packet.cvars['_uni'])
def handle_failure_unsuccessful_delivery(self, packet):
self.package.delivery_failed_error(packet.cvars['_uni'], packet.data)
def relay_unknown_target_error(self, target, uni):
self.sendmsg(target, mc='_error_unknown_target', _uni=uni)
def relay_delivery_failed_error(self, target, uni, message):
self.sendmsg(target, mc='_failure_unsuccessful_delivery', data=message,
_uni=uni)
class RoutingErrorClient(ClientProtocol):
def handle_error_unknown_target(self, packet):
self.package.unknown_target_error(packet.cvars['_uni'])
def handle_failure_unsuccessful_delivery(self, packet):
self.package.delivery_failed_error(packet.cvars['_uni'], packet.data)
class AuthenticationError(Error):
pass
class LinkingServer(ServerProtocol):
def __init__(self, package):
ServerProtocol.__init__(self, package)
self.attempts = {}
def handle_request_link(self, p):
password = p.cvars.get('_password')
if not password:
nonce = urandom(20)
self.attempts[p.header.source] = (nonce, p.cvars['_resource'])
return PSYCPacket.from_kwds(mc='_query_password', _nonce=nonce)
else:
nonce, resource = self.attempts[p.header.source]
try:
self.package.authenticate(('hmac', password, nonce),
p.header.source, resource)
except AuthenticationError as e:
return PSYCPacket(mc='_error_authentication', data=e.args[0])
return PSYCPacket(mc='_echo_link')
class LinkingClient(ClientProtocol):
def link(self, target, resource, password):
r = self.sendmsg(target, mc='_request_link', _resource=resource)
check_response(r, '_query_password')
digest = hmac.new(password, r.cvars['_nonce'], sha256).digest()
r = self.sendmsg(target, mc='_request_link', _password=digest)
if r.mc == '_error_authentication':
raise AuthenticationError(r.data)
check_response(r, '_echo_link')
class Messaging(ClientProtocol):
def send_private_message(self, target, message, relay=None):
packet = PSYCPacket(mc='_message_private', data=message)
if relay:
self.sendmsg(relay, packet, {'_target_relay': target})
else:
self.sendmsg(target, packet)
def handle_message_private(self, packet):
self.package.private_message(packet.header.source, packet.data)
class MessageRelaying(ServerProtocol):
def handle_message_private(self, packet):
target_relay = packet.header.get('_target_relay')
if target_relay is not None:
self.package.private_message_relay(packet.header.source,
target_relay,
packet.data)
else:
self.package.private_message(packet.header.source, packet.data)
def send_private_message(self, target, message):
self.sendmsg(target, mc='_message_private', data=message)
def relay_private_message(self, source, target, message):
self.sendmsg(target, header={'_source_relay': source},
mc='_message_private', data=message)
class ConferencingServer(ServerProtocol):
def cast_member_entered(self, member, nick):
self.state_add(_list_members=member, _list_members_nick=nick)
def cast_member_left(self, member):
self.state_remove(_list_members=member)
def handle_message_public(self, packet):
self.package.public_message(packet.header.source, packet.data)
def cast_public_message(self, source, message):
self.castmsg(mc='_message_public', data=message, _member=source)
class ConferencingClient(ClientProtocol):
def state_set_list_members(self, context, vars):
for args in izip(vars['_list_members'].split('|'),
vars['_list_members_nick'].split('|')):
self.package.member_entered(context, *args)
def state_add_list_members(self, context, vars):
self.package.member_entered(context, vars['_list_members'],
vars['_list_members_nick'])
def state_remove_list_members(self, context, vars):
self.package.member_left(context, vars['_list_members'])
def send_public_message(self, target, message):
self.sendmsg(target, mc='_message_public', data=message)
def handle_message_public(self, packet):
self.package.public_message(
packet.header.context, packet.cvars['_member'], packet.data)
class EntryDeniedError(Error):
pass
class ContextMaster(ServerProtocol):
def handle_request_context_enter(self, packet):
try:
state = self.package.enter_request(packet.header.source)
except EntryDeniedError as e:
return PSYCPacket(mc='_error_illegal_entry', data=e.args[0])
return PSYCPacket({'=': state}, '_echo_context_enter')
def handle_request_context_leave(self, packet):
self.package.leave_context(packet.header.source)
return PSYCPacket(mc='_echo_context_leave')
class ContextSlave(ServerProtocol):
def enter(self, target):
r = self.sendmsg(target, mc='_request_context_enter')
if r.mc == '_error_illegal_entry':
raise EntryDeniedError(r.data)
check_response(r, '_echo_context_enter')
return r.modifiers.get('=')
def leave(self, target):
r = self.sendmsg(target, mc='_request_context_leave')
check_response(r, '_echo_context_leave')
class UnauthorizedError(Error):
pass
class ClientInterfaceServer(ServerProtocol):
client_interface = {
'subscribe': lambda p: (p.cvars['_group'], p.header.source),
'unsubscribe': lambda p: (p.cvars['_group'], p.header.source),
'presence': lambda p: (int(p.cvars['_degree_availability']),),
'add_friend': lambda p: (p.cvars['_person'],),
'remove_friend': lambda p: (p.cvars['_person'],)
}
def handle_request_do(self, packet):
if not packet.header.source.is_descendant_of(self.package.entity.uni):
return PSYCPacket(mc='_error_illegal_source')
action = packet.mc[12:]
method = self.client_interface.get(action, None)
try:
getattr(self.package, 'client_' + action)(*method(packet))
except UnknownTargetError as e:
return PSYCPacket.from_kwds(mc='_error_unknown_target',
_uni=e.args[0])
except DeliveryFailedError as e:
return PSYCPacket.from_kwds(mc='_failure_unsuccessful_delivery',
data=e.args[0], _uni=e.args[1])
return PSYCPacket(mc='_echo_do_' + action)
class ClientInterfaceClient(ClientProtocol):
def subscribe(self, uni):
self._request_do('subscribe', _group=uni)
def unsubscribe(self, uni):
self._request_do('unsubscribe', _group=uni)
def cast_presence(self, availability):
self._request_do('presence', _degree_availability=str(availability))
def add_friend(self, uni):
self._request_do('add_friend', _person=uni)
def remove_friend(self, uni):
self._request_do('remove_friend', _person=uni)
def _request_do(self, command, **kwds):
r = self.sendmsg(self.package.uni, mc='_request_do_' + command, **kwds)
if r.mc == '_error_illegal_source':
raise UnauthorizedError
check_response(r, '_echo_do_' + command)
class FriendshipPendingError(Error):
pass
class FriendshipEstablishedError(Error):
pass
class FriendshipServer(ServerProtocol):
def handle_request_friendship(self, packet):
state = self.package.friendship_request(packet.header.source)
return PSYCPacket(mc='_echo_friendship_' + state)
def establish(self, uni):
r = self.sendmsg(uni, mc='_request_friendship')
if r.mc == '_echo_friendship_pending':
return 'pending'
if r.mc == '_echo_friendship_established':
return 'established'
else:
raise Exception("unknown mc: %s" % r.mc)
def handle_request_friendship_remove(self, packet):
self.package.friendship_cancel(packet.header.source)
return PSYCPacket(mc='_echo_friendship_removed')
def remove(self, uni):
r = self.sendmsg(uni, mc='_request_friendship_remove')
check_response(r, '_echo_friendship_removed')
def send_friendships(self, target, friendships):
unis = friendships.iterkeys()
states = friendships.itervalues()
self.sendmsg(target, mc='_notice_list_friendships',
_list_friendships='|'.join(unis),
_list_friendships_state='|'.join(i['state'] for i
in states))
def send_updated_friendship(self, target, uni, fs):
self.sendmsg(target, mc='_notice_friendship_updated',
_person=uni, _state=fs['state'])
def send_removed_friendship(self, target, uni):
self.sendmsg(target, mc='_notice_friendship_removed', _person=uni)
def cast_presence(self, availability):
self.state_set(_degree_availability=str(availability))
class FriendshipClient(ClientProtocol):
def handle_notice_list_friendships(self, packet):
if packet.cvars['_list_friendships'] == '':
return
for args in izip(packet.cvars['_list_friendships'].split('|'),
packet.cvars['_list_friendships_state'].split('|')):
self.package.friendship(*args)
def handle_notice_friendship_updated(self, packet):
self.package.friendship(packet.cvars['_person'],
packet.cvars['_state'])
def handle_notice_friendship_removed(self, packet):
self.package.friendship_removed(packet.cvars['_person'])
def state_set_degree_availability(self, context, vars):
self.package.presence(context, int(vars['_degree_availability']))

View File

@ -0,0 +1,105 @@
"""
pypsyc.server
~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import signal
from pkg_resources import iter_entry_points
from twisted.internet import reactor
from pypsyc.core.mmp import Uni, Header
from pypsyc.core.psyc import PSYCObject, PSYCPacket
from pypsyc.server.db import Database
from pypsyc.server.routing import _TreeNode, Routing
from pypsyc.server.multicast import ContextMaster
from pypsyc.server.webif import run_webif
from pypsyc.util import schedule
class Entity(PSYCObject, _TreeNode):
def __init__(self, parent=None, name='', server=None):
_TreeNode.__init__(self, parent, name)
self.server = (server or self._root.server)
if parent is None:
uni = Uni('psyc://%s/' % self.server.hostname)
else:
uni = parent.uni.chain(name)
PSYCObject.__init__(self, self.server.routing.route_singlecast, uni)
self.context_master = ContextMaster(self)
self.packages = {}
def castmsg(self, packet=None, header=None, **kwds):
if packet is None:
packet = PSYCPacket.from_kwds(**kwds)
header = Header() if header is None else Header(header)
header['_context'] = self.uni
header._init()
self.server.routing.route_multicast(header, packet.render())
class Server(object):
def __init__(self, hostname, interface, psyc_port, webif_port, db_file):
self.hostname = hostname
self.routing = Routing(hostname, interface)
self.root = Entity(server=self)
self.routing.init(self.root)
if psyc_port:
self.routing.listen(psyc_port)
self.database = Database(db_file)
schedule(self._load_entities)
if webif_port:
schedule(run_webif, self, interface, webif_port, None)
signal.signal(signal.SIGINT, self.shutdown)
signal.signal(signal.SIGTERM, self.shutdown)
try:
signal.signal(signal.SIGBREAK, self.shutdown)
except AttributeError:
pass
def _load_entities(self):
self.database.execute(
'CREATE TABLE IF NOT EXISTS packages ('
'entity TEXT, package TEXT, PRIMARY KEY (entity, package))')
entities = self.database.fetch('SELECT entity, package FROM packages')
for entity_name, package_name in entities:
self._load_package(entity_name, package_name)
def _load_package(self, entity_name, package_name):
if entity_name is None:
entity = self.root
else:
try:
entity = self.root.children[entity_name]
except KeyError:
entity = Entity(self.root, entity_name)
assert package_name not in entity.packages
l = list(iter_entry_points('pypsyc.server.packages', package_name))
assert len(l) == 1
package = l[0].load()(entity)
entity.packages[package_name] = package
return package
def add_package(self, entity_name, package_name):
package = self._load_package(entity_name, package_name)
self.database.execute('INSERT INTO packages VALUES (?, ?)',
entity_name, package_name)
return package
def add_place(self, name):
return self.add_package('@' + name, 'place')
def register_person(self, name, password):
person = self.add_package('~' + name, 'person')
person.register(password)
return person
def shutdown(self, signum=None, frame=None):
self.database.stop()
reactor.stop()

105
mjacob2/pypsyc/server/db.py Normal file
View File

@ -0,0 +1,105 @@
"""
pypsyc.server.db
~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import sqlite3
from threading import Thread
from Queue import Queue
from greenlet import getcurrent
from twisted.internet import reactor
class Database(object):
"""
Open a sqlite database that queues the database operations and executes
them in a thread.
"""
def __init__(self, filename):
self.filename = filename
self.sync = (filename == ':memory:')
if self.sync:
self._connect()
else:
self.queue = Queue()
self.thread = Thread(target=self.run_async)
self.thread.start()
self.main_greenlet = getcurrent()
def execute(self, *args):
"""
Execute a query.
:param query: the sqlite query
:type query: string
:param \*params: additional query parameters
:type \*params: one or more strings
"""
if self.sync:
return self._execute(*args)
self.queue.put(('execute', args, getcurrent()))
assert getcurrent() is not self.main_greenlet
return self.main_greenlet.switch()
def fetch(self, *args):
"""
Fetch a result from the database asynchronously.
:param query: the sqlite query
:type query: string
:param \*params: additional query parameters
:type \*params: one or more strings
:rtype: result rows
"""
if self.sync:
return self._fetch(*args)
self.queue.put(('fetch', args, getcurrent()))
assert getcurrent() is not self.main_greenlet
return self.main_greenlet.switch()
def stop(self):
"""Close the database and stop the worker thread."""
if self.sync:
return self._close()
del self.sync # make database unusable
self.queue.put(('stop', (), None))
self.thread.join()
def run_async(self):
self._connect()
while 1:
cmd, args, gl = self.queue.get()
if cmd == 'stop':
self._close()
break
try:
if cmd == 'execute':
ret = self._execute(*args)
elif cmd == 'fetch':
ret = self._fetch(*args)
except Exception, e:
reactor.callFromThread(gl.throw, e)
else:
reactor.callFromThread(gl.switch, ret)
def _connect(self):
self.conn = sqlite3.connect(self.filename)
# return bytestrings instead of unicode strings
self.conn.text_factory = str
def _close(self):
self.conn.close()
def _execute(self, query, *params):
cur = self.conn.cursor()
cur.execute(query, params)
self.conn.commit()
def _fetch(self, query, *params):
cur = self.conn.cursor()
cur.execute(query, params)
return cur.fetchall()

View File

@ -0,0 +1,100 @@
"""
pypsyc.server.multicast
~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from collections import defaultdict
from pypsyc.core.mmp import Header
from pypsyc.core.psyc import PSYCPacket
from pypsyc.protocol import ContextSlave as ContextSlaveProtocol
class ContextMaster(object):
def __init__(self, entity):
self.state = {}
self.list_state = defaultdict(list)
self.entity = entity
if hasattr(entity.server.routing.mrouting_table, '__setitem__'):
entity.server.routing.mrouting_table[entity.uni] = set()
def add_member(self, uni):
if not uni.is_descendant_of(self.entity._root.uni):
routing = self.entity.server.routing
circuit = routing.srouting_table[uni.into_parts()[0]]
routing.mrouting_table[self.entity.uni].add(circuit)
state = self.state.copy()
state.update((k, '|'.join(v)) for k, v in self.list_state.iteritems())
return state
def remove_member(self, uni):
if not uni.is_descendant_of(self.entity._root.uni):
routing = self.entity.server.routing
circuit = routing.srouting_table[uni.into_parts()[0]]
routing.mrouting_table[self.entity.uni].remove(circuit)
def state_set(self, **kwds):
self.state.update(kwds)
self.entity.castmsg(PSYCPacket({'=': kwds}))
def state_add(self, **kwds):
for key, value in kwds.iteritems():
self.list_state[key].append(value)
self.entity.castmsg(PSYCPacket({'+': kwds}))
def state_remove(self, **kwds):
for key, value in kwds.iteritems():
idx = self.list_state[key].index(value)
for i, j in self.list_state.items():
if i.startswith(key):
del j[idx]
if not j:
del self.list_state[i]
self.entity.castmsg(PSYCPacket({'-': kwds}))
class ContextSlave(object):
def __init__(self, person):
self.protocol = ContextSlaveProtocol(person)
self.person = person
self.subscribed_contexts = set()
def enter(self, context, resource=None):
if context not in self.subscribed_contexts:
state = self.protocol.enter(context)
self.subscribed_contexts.add(context)
else:
state = None # TODO: get state from context master
entity = self.person.entity
table = entity.server.routing.mrouting_table.setdefault(context, set())
resources = ([entity.children[resource.into_parts()[-1]]] if resource
else entity.children.values())
table.update(resource.circuit for resource in resources)
if state:
header = Header({'_context': context})
content = list(PSYCPacket({'=': state}).render())
for resource in resources:
resource.circuit.send(header, content)
def leave(self, context, resource=None):
if context not in self.subscribed_contexts:
return
entity = self.person.entity
table = entity.server.routing.mrouting_table[context]
circuits = (r.circuit for r in entity.children.itervalues())
if resource:
table.remove(entity.children[resource.into_parts()[-1]].circuit)
if table.intersection(circuits): return
else:
table.difference_update(circuits)
self.protocol.leave(context)
self.subscribed_contexts.remove(context)
if not (table or entity._root.uni.is_ancestor_of(context)):
del entity.server.routing.mrouting_table[context]
def leave_all(self):
for i in list(self.subscribed_contexts):
self.leave(i)

View File

@ -0,0 +1,188 @@
"""
pypsyc.server.person
~~~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import hmac
from hashlib import sha256
from pypsyc.server.multicast import ContextSlave
from pypsyc.protocol import (RoutingErrorRelaying, LinkingServer,
MessageRelaying, AuthenticationError, EntryDeniedError,
ContextMaster as ContextMasterProtocol, FriendshipPendingError,
FriendshipEstablishedError, FriendshipServer as FriendshipProtocol,
ClientInterfaceServer as ClientInterfaceProtocol)
from pypsyc.server import _TreeNode
from pypsyc.util import schedule
class Resource(_TreeNode):
def __init__(self, parent, resource, circuit):
_TreeNode.__init__(self, parent=parent, name=resource)
self.circuit = circuit
if hasattr(circuit, 'allowed_sources'):
circuit.allowed_sources.append(parent.uni.chain(resource))
def handle_packet(self, header, content):
self.circuit.send(header, content)
class Person(object):
def __init__(self, entity):
self.entity = entity
self.uni = entity.uni
self.routing_error_relaying = RoutingErrorRelaying(self)
entity.add_handler(self.routing_error_relaying)
entity.add_handler(LinkingServer(self))
self.message_relaying = MessageRelaying(self)
entity.add_handler(self.message_relaying)
entity.add_handler(ContextMasterProtocol(self))
self.context_slave = ContextSlave(self)
self.friendship_protocol = FriendshipProtocol(self)
entity.add_handler(self.friendship_protocol)
entity.add_handler(ClientInterfaceProtocol(self))
def register(self, password):
"""
Register this person entity with the given password. This method has to
be called after the entity was created.
"""
sql = ('CREATE TABLE IF NOT EXISTS passwords ('
'person TEXT PRIMARY KEY, password BLOB)')
self.entity.server.database.execute(sql)
sql = ('CREATE TABLE IF NOT EXISTS friendships ('
'person TEXT, uni TEXT, state TEXT, PRIMARY KEY (person, uni))')
self.entity.server.database.execute(sql)
sql = 'INSERT INTO passwords VALUES (?, ?)'
self.entity.server.database.execute(sql, self.uni, password)
def unknown_target_error(self, uni):
for i in self.entity.children:
self.routing_error_relaying.relay_unknown_target_error(
self.uni.chain(i), uni)
def delivery_failed_error(self, uni, message):
for i in self.entity.children:
self.routing_error_relaying.relay_delivery_failed_error(
self.uni.chain(i), uni, message)
def authenticate(self, pw, circuit, resource):
sql = 'SELECT password FROM passwords WHERE person = ?'
db_pass = self.entity.server.database.fetch(sql, self.uni)[0][0]
type_, req_pass, nonce = pw
assert type_ == 'hmac'
if req_pass != hmac.new(db_pass, nonce, sha256).digest():
raise AuthenticationError("Incorrect password.")
Resource(self.entity, resource, circuit)
self.friendship_protocol.cast_presence(7)
schedule(self._after_authentication, self.uni.chain(resource))
def _after_authentication(self, uni):
sql = 'SELECT uni, state FROM friendships WHERE person = ?'
rows = self.entity.server.database.fetch(sql, self.uni)
friendships = dict((i[0], {'state': i[1]}) for i in rows)
self.friendship_protocol.send_friendships(uni, friendships)
for context, state in rows:
if state == 'established':
self.context_slave.enter(context, uni)
def unlink(self, resource):
del self.entity.children[resource]
self.friendship_protocol.cast_presence(1)
self.context_slave.leave_all()
def private_message_relay(self, source, target, message):
if source.is_descendant_of(self.uni):
self.message_relaying.send_private_message(target, message)
def private_message(self, source, message):
for i in self.entity.children:
self.message_relaying.relay_private_message(
source, self.uni.chain(i), message)
def friendship_request(self, uni):
state = self._get_friendship_state(uni)
if state == 'none':
self._update_friendship(uni, 'offered', True)
return 'pending'
elif state == 'pending':
self._update_friendship(uni, 'established', False)
self.context_slave.enter(uni)
return 'established'
elif state == 'offered':
return 'pending'
elif state == 'established':
return 'established'
else:
raise Exception("unknown friendship state: " + state)
def friendship_cancel(self, uni):
self.context_slave.leave(uni)
self._update_friendship(uni, None)
def client_add_friend(self, uni):
state = self._get_friendship_state(uni)
if state == 'none':
self.friendship_protocol.establish(uni)
self._update_friendship(uni, 'pending', True)
elif state == 'pending':
raise FriendshipPendingError
elif state == 'offered':
self.friendship_protocol.establish(uni)
self._update_friendship(uni, 'established', False)
self.context_slave.enter(uni)
elif state == 'established':
raise FriendshipEstablishedError
else:
raise Exception("unknown friendship state: " + state)
def client_remove_friend(self, uni):
self.context_slave.leave(uni)
self.friendship_protocol.remove(uni)
self._update_friendship(uni, None)
def _get_friendship_state(self, uni):
sql = 'SELECT state FROM friendships WHERE person = ? AND uni = ?'
ret = self.entity.server.database.fetch(sql, self.uni, uni)
if ret:
return ret[0][0]
return 'none'
def _update_friendship(self, uni, state, insert=False):
if state is None:
sql = 'DELETE FROM friendships WHERE person = ? AND uni = ?'
self.entity.server.database.execute(sql, self.uni, uni)
elif insert:
sql = 'INSERT INTO friendships VALUES(?, ?, ?)'
self.entity.server.database.execute(sql, self.uni, uni, state)
else:
sql = ('UPDATE friendships SET state = ? '
'WHERE person = ? AND uni = ?')
self.entity.server.database.execute(sql, state, self.uni, uni)
for resource in self.entity.children:
if state:
self.friendship_protocol.send_updated_friendship(
self.uni.chain(resource), uni, {'state': state})
else:
self.friendship_protocol.send_removed_friendship(
self.uni.chain(resource), uni)
def enter_request(self, uni):
if self._get_friendship_state(uni) == 'established':
return self.entity.context_master.add_member(uni)
else:
raise EntryDeniedError("You are not my friend!")
def leave_context(self, uni):
self.entity.context_master.remove_member(uni)
def client_presence(self, availability):
self.friendship_protocol.cast_presence(availability)
def client_subscribe(self, uni, resource_uni):
self.context_slave.enter(uni, resource_uni)
def client_unsubscribe(self, uni, resource_uni):
self.context_slave.leave(uni, resource_uni)

View File

@ -0,0 +1,34 @@
"""
pypsyc.server.place
~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from pypsyc.protocol import (ContextMaster as ContextProtocol,
ConferencingServer as ConferencingProtocol)
class Place(object):
def __init__(self, entity):
self.entity = entity
entity.add_handler(ContextProtocol(self))
self.conferencing_protocol = ConferencingProtocol(self)
entity.add_handler(self.conferencing_protocol)
self.members = set()
def enter_request(self, uni):
nick = uni.rsplit('/', 1)[1].lstrip('~')
self.conferencing_protocol.cast_member_entered(uni, nick)
self.members.add(uni)
return self.entity.context_master.add_member(uni)
def leave_context(self, uni):
self.entity.context_master.remove_member(uni)
self.conferencing_protocol.cast_member_left(uni)
self.members.remove(uni)
def public_message(self, source, message):
member = source.rsplit('/', 1)[0]
if member in self.members:
self.conferencing_protocol.cast_public_message(member, message)

View File

@ -0,0 +1,13 @@
"""
pypsyc.server.root
~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
class Root(object):
def __init__(self, entity):
self.entity = entity
self.server = entity.server

View File

@ -0,0 +1,209 @@
"""
pypsyc.server.routing
~~~~~~~~~~~~~~~~~~~~~
This module defines the following classes:
- `ServerCircuit`
- `_TreeNode`
- `Routing`
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import logging
from twisted.internet import reactor
from twisted.internet.protocol import Factory
from pypsyc.core.mmp import Circuit, Uni
from pypsyc.core.psyc import PSYCPacket, PSYCObject
from pypsyc.protocol import Error, check_response
from pypsyc.util import schedule, resolve_hostname, DNSError, connect
log = logging.getLogger(__name__)
class InvalidTargetError(Error):
pass
class InvalidSourceError(Error):
pass
class ServerCircuit(Circuit):
def __init__(self):
Circuit.__init__(self)
self.allowed_sources = []
self.psyc = PSYCObject(self.send)
self.psyc.add_handler(self)
def packet_received(self, header, content):
"""Handle a packet that was received."""
if header.context:
if header.source:
raise NotImplementedError
if header.target:
self.factory.route_singlecast(header, content)
else:
self.factory.route_multicast(header, content)
else:
if header.source:
if not any((header.source == i or
header.source.is_descendant_of(i))
for i in self.allowed_sources):
return
else:
header.source = self
if header.target:
self.factory.route_singlecast(header, content)
else:
self.psyc.handle_packet(header, content)
def handle_request_verification(self, packet):
source_uni = Uni(packet.cvars['_uni_source'])
target_uni = packet.cvars['_uni_target']
try:
self.factory.verify_address(self, source_uni, target_uni)
except InvalidSourceError:
return PSYCPacket(mc='_error_invalid_source')
except InvalidTargetError:
return PSYCPacket(mc='_error_invalid_target')
self.allowed_sources.append(source_uni)
return PSYCPacket(mc='_echo_verification')
def request_verification(self, source_uni, target_uni):
r = self.psyc.sendmsg(self, mc='_request_verification',
_uni_source=source_uni, _uni_target=target_uni)
if r.mc == '_error_invalid_source':
raise InvalidSourceError
if r.mc == '_error_invalid_target':
raise InvalidTargetError
check_response(r, '_echo_verification')
self.allowed_sources.append(target_uni)
def connectionLost(self, reason):
for uni in self.allowed_sources:
parts = uni.into_parts()
if len(parts) == 1:
del self.factory.srouting_table[parts[0]]
else:
entity = self.factory.root.children[parts[1]]
entity.packages['person'].unlink(parts[2])
class _TreeNode(object):
def __init__(self, parent=None, name=''):
self._parent = parent
self.children = {}
if parent:
self._root = parent._root
parent.children[name] = self
else:
self._root = self
class Routing(Factory, object):
"""This class handles routing and circuit managment."""
protocol = ServerCircuit
def __init__(self, hostname, interface):
self.hostname = hostname
self.interface = interface
self.circuits = {} # ip -> circuit
self.queues = {} # hostname -> list
self.srouting_table = {} # hostname -> circuit
self.mrouting_table = {} # context -> list of circuits
def init(self, root):
self.root = root
def route_singlecast(self, header, content):
"""Route the packet to the right target."""
parts = header.target.into_parts()
host = parts[0]
if host == self.hostname:
node = self.root
try:
for i in parts[1:]:
node = node.children[i]
except KeyError:
self._error(header, '_error_unknown_target')
else:
node.handle_packet(header, content)
elif header.get('_source'):
try:
self.srouting_table[host].send(header, content)
except KeyError:
if host not in self.queues:
self.queues[host] = []
schedule(self._add_route, host)
self.queues[host].append((header, content))
else:
client = header.source.transport.client
log.error("Dropped packet without _source from %s", client)
def _error(self, header, error_mc, message=None):
tag = header.get('_tag')
self.root.sendmsg(header.source, None, tag and {'_tag_relay': tag},
mc=error_mc, _uni=header.target, data=message)
def route_multicast(self, header, content):
content = list(content)
for circuit in self.mrouting_table[header.context]:
circuit.send(header, content)
def listen(self, port):
reactor.listenTCP(port, self, interface=self.interface)
def _add_route(self, host):
"""
Add a route to host.
First check if we have an open connection to the target host.
If, do address verification.
If not, try to connect to the server and then do address verification.
"""
try:
addr = resolve_hostname(host)
except DNSError:
self._unsuccessful_delivery(host, "Can't resolve '%s'" % host)
return
try:
circuit = self.circuits[addr]
except KeyError:
try:
ip, p = addr
circuit = connect(ip, p, self, bindAddress=(self.interface, 0))
except Exception:
self._unsuccessful_delivery(host, "Can't connect '%s'" % host)
return
self.circuits[addr] = circuit
try:
target_uni = Uni('psyc://%s/' % host)
circuit.request_verification(self.root.uni, target_uni)
except Error:
self._unsuccessful_delivery(host, "Can't verify %s" % target_uni)
else:
self.srouting_table[host] = circuit
for header, content in self.queues.pop(host):
circuit.send(header, content)
def _unsuccessful_delivery(self, host, message):
log.exception(message)
for header, content in self.queues.pop(host):
self._error(header, '_failure_unsuccessful_delivery', message)
def verify_address(self, circuit, source_uni, target_uni):
if target_uni != self.root.uni:
raise InvalidTargetError
source_host = source_uni.into_parts()[0]
if resolve_hostname(source_host)[0] != circuit.transport.client[0]:
raise InvalidSourceError
self.srouting_table[source_host] = circuit
def connection_lost(self, circuit, error):
pass

View File

@ -0,0 +1,117 @@
"""
pypsyc.server.webif
~~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from collections import MutableMapping
from logging import getLogger
from os import urandom
from sqlite3 import IntegrityError
from flask import Flask, request, flash, url_for, redirect, render_template
from greenlet import greenlet
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.wsgi import WSGIResource
log = getLogger(__name__)
app = Flask(__name__)
app.jinja_env.trim_blocks = True
@app.route('/')
def index():
return render_template('index.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
error = None
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
password2 = request.form.get('password2')
if not username:
error = 'please specify a valid username'
elif '~' + username in server.root.children:
error = 'username already in use'
elif not password or len(password) < 6:
error = 'your password must have at least 6 characters'
elif password != password2:
error = 'the two passwords do not match'
else:
server.register_person(username, password)
flash("you were registered")
return redirect(url_for('index'))
return render_template('register.html', error=error)
class PersistentStore(MutableMapping):
def __init__(self):
server.database.execute('CREATE TABLE IF NOT EXISTS webif_store ('
'key TEXT, value BLOB, PRIMARY KEY (key))')
def __getitem__(self, key):
sql = 'SELECT value FROM webif_store WHERE key = ?'
ret = server.database.fetch(sql, key)
if not ret:
raise KeyError
return ret[0][0]
def __setitem__(self, key, value):
try:
sql = 'INSERT INTO webif_store VALUES (?, ?)'
server.database.execute(sql, key, value)
except IntegrityError:
sql = 'UPDATE webif_store SET value = ? WHERE key = ?'
server.database.execute(sql, value, key)
def __delitem__(self, key):
server.database.execute('DELETE FROM webif_store WHERE key = ?', key)
def __iter__(self):
sql = 'SELECT key FROM webif_store'
return (ret[0] for ret in server.database.fetch(sql))
def __len__(self):
return server.database.fetch('SELECT count(*) FROM webif_store')[0][0]
class _Reactor(object):
def callFromThread(self, f, *args, **kwds):
f(*args, **kwds)
class _Threadpool(object):
def callInThread(self, f, *args, **kwds):
greenlet(f).switch(*args, **kwds)
def run_webif(server_, interface, port, context_factory):
global server
server = server_
global store
store = PersistentStore()
try:
app.secret_key = store['secret_key']
except:
app.secret_key = store['secret_key'] = urandom(20)
assert len(app.secret_key) == 20
site = Site(WSGIResource(_Reactor(), _Threadpool(), app.wsgi_app))
if context_factory is None:
log.warning("listening on localhost because ssl is disabled")
reactor.listenTCP(port, site, interface='localhost')
else:
reactor.listenSSL(port, site, context_factory, interface=interface)
if __name__ == '__main__': # pragma: no cover
from minimock import Mock
server = Mock('server')
server.root.children = {}
app.debug = True
app.secret_key='debug key'
app.run(port=8080)

View File

@ -0,0 +1,6 @@
{% extends "layout.html" %}
{% block title %}welcome{% endblock %}
{% block content %}
<h1>pypsyc</h1>
<a href="{{ url_for('register') }}">register</a>
{% endblock %}

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>{% block title %}{% endblock %} - pypsyc</title>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>

View File

@ -0,0 +1,23 @@
{% extends "layout.html" %}
{% block title %}register{% endblock %}
{% macro input(name, type='text', label=None) %}
<tr>
<td align="right">{{ label or name|capitalize }}:</td>
<td><input name="{{ name }}" type="{{ type }}" size="30"></td>
</tr>
{%- endmacro %}
{% block content %}
<h1>register your identity</h1>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('register') }}" method="post">
<table border="0" cellpadding="0" cellspacing="4">
{{ input('username') }}
{{ input('password', type='password') }}
{{ input('password2', label='Confirm password', type='password') }}
<tr>
<td>&#160;</td>
<td><input type="submit" value="Register"></td>
</tr>
</table>
</form>
{% endblock %}

157
mjacob2/pypsyc/util.py Normal file
View File

@ -0,0 +1,157 @@
"""
pypsyc.util
~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import logging
from greenlet import greenlet, getcurrent
from twisted.internet import reactor, error
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.tcp import Connector
scheduler = getcurrent()
def schedule(f, *args, **kwds):
g = greenlet(f, scheduler)
if getcurrent() is scheduler:
g.switch(*args, **kwds)
else:
reactor.callLater(0, g.switch, *args, **kwds)
class Waiter(object):
def __init__(self):
self.greenlet = None
self.value = None
self.exception = None
def callback(self, *args, **kwds):
if self.greenlet is None:
self.value, = args
else:
assert getcurrent() is not self.greenlet
self.greenlet.switch(*args, **kwds)
def errback(self, exception):
if self.greenlet is None:
self.exception = exception
else:
self.greenlet.throw(exception)
def get(self):
if self.value is not None:
return self.value
if self.exception is not None:
raise self.exception
self.greenlet = getcurrent()
return self.greenlet.parent.switch()
class DNSError(Exception):
pass
@inlineCallbacks
def _resolve_hostname(host):
try:
name = '_psyc._tcp.%s.' % host
answers, auth, add = yield lookupService(name)
srv_rr = answers[0]
assert srv_rr.name.name == name
host = srv_rr.payload.target.name
port = srv_rr.payload.port
a_rr = [rr for rr in add if rr.name.name == host][0]
ip = a_rr.payload.dottedQuad()
except (DNSNameError, IndexError):
try:
ip = yield getHostByName(host)
except DNSNameError:
raise DNSError("Unknown host %s." % host)
port = 4404
returnValue((ip, port))
try:
from twisted.names.client import lookupService, getHostByName
from twisted.names.error import DNSNameError
except ImportError:
log = logging.getLogger(__name__)
log.warn("twisted names isn't installed -- DNS SRV is disabled")
def resolve_hostname(host):
return host, 4404
else:
def resolve_hostname(host):
waiter = Waiter()
_resolve_hostname(host).addCallbacks(waiter.callback,
lambda f: waiter.errback(f.value))
return waiter.get()
class _PSYCConnector(Connector):
def __init__(self, *args):
Connector.__init__(self, *args, reactor=reactor)
self.waiter = Waiter()
def connect(self):
assert self.state == 'disconnected', "can't connect in this state"
self.state = 'connecting'
self.transport = transport = self._makeTransport()
self.timeoutID = self.reactor.callLater(self.timeout,
transport.failIfNotConnected,
error.TimeoutError())
self.waiter.get()
return self.circuit
def buildProtocol(self, addr):
self.circuit = Connector.buildProtocol(self, addr)
self.circuit.inited = self.waiter.callback
return self.circuit
def connectionFailed(self, reason):
self.cancelTimeout()
self.transport = None
self.state = 'disconnected'
self.waiter.errback(reason.value)
def connectionLost(self, reason):
self.state = 'disconnected'
self.factory.connection_lost(self.circuit, reason.value)
def connect(host, port, factory, timeout=30, bindAddress=None):
return _PSYCConnector(host, port, factory, timeout, bindAddress).connect()
class Event(object):
def __init__(self):
self.observers = []
def add_observer(self, observer, *args, **kwds):
self.observers.append((observer, args, kwds))
def __iadd__(self, observer):
self.observers.append((observer, (), {}))
return self
def __isub__(self, observer):
observers = [i for i in self.observers if i[0] == observer]
assert len(observers) == 1, observers
self.observers.remove(observers[0])
return self
def __call__(self, *args, **kwds):
for observer, args2, kwds2 in self.observers:
kwds.update(kwds2)
observer(*(args + args2), **kwds)
def key_intersection(a, b):
for k in b:
if k in a:
yield k

21
mjacob2/setup.py Normal file
View File

@ -0,0 +1,21 @@
from setuptools import setup, find_packages
import pypsyc
setup(
name = "pypsyc",
version = pypsyc.__version__,
packages = find_packages(exclude='tests'),
package_data = {
'pypsyc.client': ['psyc.ico'],
'pypsyc.server.webif': ['templates/*']
},
zip_safe = False,
scripts = ['bin/pypsyc', 'bin/pypsycd'],
entry_points = '''
[pypsyc.server.packages]
root = pypsyc.server.root:Root
person = pypsyc.server.person:Person
place = pypsyc.server.place:Place
'''
)

View File

@ -0,0 +1,4 @@
import tests.helpers
from twisted.python.log import defaultObserver, PythonLoggingObserver
defaultObserver.stop()
PythonLoggingObserver().start()

View File

@ -0,0 +1,41 @@
from pypsyc.client.model import Presence
from pypsyc.core.mmp import Uni
SERVER1 = 'server1'
SERVER1_UNI = Uni('psyc://%s/' % SERVER1)
USER1 = '~user1'
USER2 = '~user2'
USER1_UNI = SERVER1_UNI.chain(USER1)
USER2_UNI = SERVER1_UNI.chain(USER2)
USER1_NICK = 'user1'
USER2_NICK = 'user2'
RESOURCE = '*Resource'
RESOURCE1_UNI = USER1_UNI.chain(RESOURCE)
RESOURCE2_UNI = USER2_UNI.chain(RESOURCE)
PLACE = '@place'
PLACE_UNI = SERVER1_UNI.chain(PLACE)
SERVER2 = 'server2'
SERVER2_UNI = Uni('psyc://%s/' % SERVER2)
SERVER3 = 'server3'
SERVER3_UNI = Uni('psyc://%s/' % SERVER3)
INTERFACE = 'interface'
IP = '10.0.0.1'
PORT = 4404
VARIABLES = {'_key': 'value'}
CONTENT = ['']
PASSWORD = 'password'
MESSAGE = 'message'
UNKNOWN = 0
OFFLINE = 1
ONLINE = 7
PRESENCE_UNKNOWN = Presence(UNKNOWN)
PRESENCE_OFFLINE = Presence(OFFLINE)
PRESENCE_ONLINE = Presence(ONLINE)
PENDING = 'pending'
OFFERED = 'offered'
ESTABLISHED = 'established'
ERROR = 'error'
EXCEPTION = Exception(ERROR)

133
mjacob2/tests/helpers.py Normal file
View File

@ -0,0 +1,133 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from functools import wraps
from traceback import format_exc
import sys
from greenlet import getcurrent
from mock import Mock
import twisted.internet
from pypsyc.core.mmp import Header
# keep reactor from being installed
assert not 'twisted.internet.reactor' in sys.modules
twisted.internet.reactor = sys.modules['twisted.internet.reactor'] = None
# random helpers
def inited_header(*args, **kwds):
header = Header(*args, **kwds)
header._init()
return header
def _make_send_function(other_circuit):
def f(header, content):
try:
other_circuit.packet_received(inited_header(header), content)
except:
raise Exception("%s.packet_received raised exception:\n%s" %
(other_circuit, format_exc()))
return f
def connect_circuits(c1, c2):
"""Make a virtual connection between two curcuits."""
c1.send = c1.psyc._send = _make_send_function(c2)
c2.send = c2.psyc._send = _make_send_function(c1)
return c1, c2
class iter_(object):
def __init__(self, *iter_):
self.iter_ = iter_
def __eq__(self, iter_):
return tuple(iter_) == self.iter_
def rendered(packet):
return iter_(*packet.render())
def check_success(f):
"""
Wrap `f` so that it checks if :attr:`TestCase.success` is ``True``. This is
useful to check if an asynchronous test has passed completely.
"""
@wraps(f)
def _wrapper(self):
self.success = False
f(self)
assert self.success
del self.success
return _wrapper
class AsyncMethod(object):
def __call__(self, *args, **kwds):
self.child = getcurrent()
return self.child.parent.switch()
def callback(self, *args, **kwds):
self.child.switch(*args, **kwds)
def errback(self, *args, **kwds):
self.child.throw(*args, **kwds)
def mockify(obj, attrs):
"""Replace attrs of obj with mocks. Return the original attributes."""
originals = {}
for attr in attrs:
orig = originals[attr] = getattr(obj, attr)
mock = Mock(spec=orig)
setattr(obj, attr, mock)
return originals
class mockified(object):
"""
Wrap a function so that it 'mockifies' and resets `obj`. If `obj` is a
string, mockify the module named like it.
Can also be used as a context manager.
"""
def __init__(self, obj, attrs):
if isinstance(obj, basestring):
self.obj = __import__(obj, globals(), locals(), attrs)
else:
self.obj = obj
self.attrs = attrs
def __enter__(self):
self.originals = mockify(self.obj, self.attrs)
return (getattr(self.obj, attr) for attr in self.attrs)
def __exit__(self, type, value, traceback):
for attr, original in self.originals.iteritems():
setattr(self.obj, attr, original)
def __call__(self, f):
@wraps(f)
def _wrapper(*args, **kwds):
with self:
kwds.update((attr, getattr(self.obj, attr)) for attr in self.attrs)
return f(*args, **kwds)
return _wrapper
class PlaceHolder(object):
"""
An object that be used as a placeholder for comparisons. The object on the
other side of the comparison is saved as attribute `obj`.
"""
def __eq__(self, other):
self.obj = other
return True
class StubException(Exception):
pass

View File

@ -0,0 +1,680 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock, MagicMock
from nose.tools import assert_raises
from tests.constants import (SERVER1, USER1_UNI, USER2_UNI, USER1_NICK,
USER2_NICK, PLACE_UNI, SERVER2_UNI, MESSAGE, PRESENCE_OFFLINE,
PRESENCE_ONLINE, ERROR, EXCEPTION)
from tests.helpers import mockified, AsyncMethod, PlaceHolder, iter_
from pypsyc.client.controller import (ListBinding, AccountsController,
ConversationController, ConferenceController, TabsController,
FriendListController, DumpController, MainController)
from pypsyc.client.model import Message
from pypsyc.client.observable import ObsList, ObsDict, ObsObj, ObsAttr
from pypsyc.util import Event
def _check_obs_list(obs_list):
assert obs_list.setitem_evt.observers == []
assert obs_list.delitem_evt.observers == []
assert obs_list.insert_evt.observers == []
assert obs_list.update_evt.observers == []
return True
def _check_obs_dict(obs_dict):
assert obs_dict.setitem_evt.observers == []
assert obs_dict.delitem_evt.observers == []
assert obs_dict.update_evt.observers == []
return True
def _check_obs_obj(obs_obj):
assert obs_obj.update_evt != {}
for evt in obs_obj.update_evt.itervalues():
assert evt.observers == []
return True
class TestListBinding(object):
def setup(self):
self.model = Mock()
self.model_list = ObsList([self.model])
self.view_list = []
self.make_row = Mock()
self.binding = ListBinding(self.model_list, self.view_list,
self.make_row)
def test_bind(self):
assert self.make_row.call_args_list == [((self.model,),)]
assert self.view_list == [self.make_row.return_value]
def test_add(self):
assert self.make_row.call_args_list == [((self.model,),)]
self.make_row.reset_mock()
model2 = Mock()
self.model_list.append(model2)
assert self.make_row.call_args_list == [((model2,),)]
assert self.view_list == [self.make_row.return_value] * 2
def test_update(self):
assert self.make_row.call_args_list == [((self.model,),)]
self.make_row.reset_mock()
self.model_list.updated_item(self.model)
assert self.make_row.call_args_list == [((self.model,),)]
assert self.view_list == [self.make_row.return_value]
def test_delete(self):
del self.model_list[0]
assert self.view_list == []
def test_unbind(self):
self.binding.unbind()
assert _check_obs_list(self.model_list)
assert self.view_list == []
class _List(list):
def __init__(self, *args, **kwds):
list.__init__(self, *args, **kwds)
self.updated_item = Mock()
class TestAccountsController(object):
@mockified('pypsyc.client.controller', ['ListBinding'])
def setup(self, ListBinding):
self.ListBinding = ListBinding
self.account = Mock()
self.client = Mock()
self.client.accounts = _List([self.account])
self.view = Mock()
self.controller = AccountsController(self.client, self.view)
def test_binding(self):
make_row_ph = PlaceHolder()
assert self.ListBinding.call_args_list == [
((self.client.accounts, self.view.accounts, make_row_ph),)]
account = Mock()
row = make_row_ph.obj(account)
assert row == (account.uni, account.active)
@mockified('pypsyc.client.controller', ['Account'])
def test_add_account(self, Account):
account = Account.return_value
callback_ph = PlaceHolder()
self.controller.add_account()
assert Account.call_args_list == [
((self.client, '', '', '', False, False),)]
assert self.view.method_calls == [
('show_addedit_dialog', (account.__dict__, True, callback_ph))]
callback_ph.obj()
assert self.client.accounts == [self.account, account]
assert self.client.method_calls == [('save_accounts',)]
def test_edit_account(self):
callback_ph = PlaceHolder()
self.controller.edit_account(0)
assert self.view.method_calls == [
('show_addedit_dialog', (self.account.__dict__, False,
callback_ph))]
callback_ph.obj()
assert self.client.accounts.updated_item.call_args_list == [
((self.account,),)]
assert self.client.method_calls == [('save_accounts',)]
def test_remove_account(self):
assert_raises(IndexError, self.controller.remove_account, 1)
self.controller.remove_account(0)
assert self.client.accounts == []
assert self.client.method_calls == [('save_accounts',)]
def test_set_active(self):
self.controller.set_active(0, True)
assert self.account.active == True
assert self.client.accounts.updated_item.call_args_list == [
((self.account,),)]
assert self.client.method_calls == [('save_accounts',)]
def test_closed(self):
self.controller.closed()
assert self.ListBinding.return_value.method_calls == [('unbind',)]
IN = 'i'
OUT = 'o'
LINE = 'line'
class _StubAccount(ObsObj):
circuit = ObsAttr('circuit')
class TestDumpController:
def setup(self):
self.circuit = Mock()
self.circuit.dump_evt = Event()
self.account = _StubAccount()
self.account.uni = USER1_UNI
self.client = Mock()
self.client.accounts = ObsList()
self.view = Mock()
def test_connected(self):
self.account.circuit = self.circuit
self.client.accounts.append(self.account)
DumpController(self.client, self.view)
self.circuit.dump_evt(IN, LINE)
assert self.view.method_calls == [('show_line', (IN, LINE, USER1_UNI))]
def test_connect(self):
DumpController(self.client, self.view)
self.client.accounts.append(self.account)
self.account.circuit = self.circuit
self.circuit.dump_evt(IN, LINE)
assert self.view.method_calls == [('show_line', (IN, LINE, USER1_UNI))]
def test_disconnect(self):
self.account.circuit = self.circuit
self.client.accounts.append(self.account)
DumpController(self.client, self.view)
del self.account.circuit
assert self.circuit.dump_evt.observers == []
def test_reconnect(self):
DumpController(self.client, self.view)
self.account.circuit = self.circuit
self.client.accounts.append(self.account)
circuit2 = Mock()
circuit2.dump_evt = Event()
self.account.circuit = circuit2
assert self.circuit.dump_evt.observers == []
circuit2.dump_evt(OUT, LINE)
assert self.view.method_calls == [
('show_line', (OUT, LINE, USER1_UNI))]
def test_remove(self):
self.client.accounts.append(self.account)
self.account.circuit = self.circuit
DumpController(self.client, self.view)
self.client.accounts.remove(self.account)
assert _check_obs_obj(self.account)
assert self.circuit.dump_evt.observers == []
def test_remove_nocircuit(self):
self.client.accounts.append(self.account)
DumpController(self.client, self.view)
self.client.accounts.remove(self.account)
assert _check_obs_obj(self.account)
def test_closed(self):
self.client.accounts.append(self.account)
self.account.circuit = self.circuit
dump_controller = DumpController(self.client, self.view)
dump_controller.closed()
assert _check_obs_list(self.client.accounts)
assert _check_obs_obj(self.account)
assert self.circuit.dump_evt.observers == []
class TestConversationController(object):
def setup(self):
self.tabs_controller = Mock()
self.conversation = Mock()
self.conversation.messages = ObsList()
self.conversation.unknown_target_evt = Event()
self.conversation.delivery_failed_evt = Event()
self.view = Mock()
self.controller = ConversationController(self.tabs_controller,
self.conversation, self.view)
def test_message(self):
MESSAGE_OBJ = Message(USER2_UNI, MESSAGE)
MESSAGE_LINE = "(%s) <%s> %s" % (
MESSAGE_OBJ.time.strftime("%H:%M:%S"), USER2_UNI, MESSAGE)
self.conversation.messages.append(MESSAGE_OBJ)
assert self.view.method_calls == [('show_message', (MESSAGE_LINE,))]
def test_unknown_target(self):
self.conversation.unknown_target_evt()
assert self.view.method_calls == [('show_unknown_target',)]
def test_delivery_failed(self):
self.conversation.delivery_failed_evt(ERROR)
assert self.view.method_calls == [('show_delivery_failed', (ERROR,))]
def test_enter_message(self):
self.controller.enter(MESSAGE)
assert self.conversation.method_calls == [('send_message', (MESSAGE,))]
def test_enter_command(self):
self.controller.enter("/command")
assert self.view.method_calls == [('show_unknown_command',)]
assert self.conversation.method_calls == []
def test_closed(self):
self.conversation.messages.append(Message(None, None))
self.controller.closed()
assert _check_obs_list(self.conversation.messages)
assert self.conversation.unknown_target_evt.observers == []
assert self.conversation.delivery_failed_evt.observers == []
class TestConferenceController(object):
@mockified('pypsyc.client.controller', ['ListBinding'])
def setup(self, ListBinding):
self.ListBinding = ListBinding
self.tabs_controller = Mock()
self.conference = Mock()
self.conference.members = []
self.conference.messages = ObsList()
self.conference.unknown_target_evt = Event()
self.conference.delivery_failed_evt = Event()
self.view = Mock()
self.controller = ConferenceController(self.tabs_controller,
self.conference, self.view)
def test_binding(self):
makerow_ph = PlaceHolder()
assert self.ListBinding.call_args_list == [
((self.conference.members, self.view.members, makerow_ph),)]
member = Mock()
row = makerow_ph.obj(member)
assert row == (member.uni, member.nick)
def test_open_conversation(self):
member = Mock()
self.conference.members.append(member)
account = self.conference.conferencing.account
conversation = account.get_conversation.return_value
self.controller.open_conversation(0)
assert self.conference.conferencing.account.method_calls == [
('get_conversation', (member.uni,))]
assert self.tabs_controller.method_calls == [
('focus_conversation', (conversation,))]
def test_closed(self):
self.controller.closed()
assert self.ListBinding.return_value.method_calls == [('unbind',)]
assert _check_obs_list(self.conference.messages)
assert self.conference.unknown_target_evt.observers == []
assert self.conference.delivery_failed_evt.observers == []
class TestTabsController(object):
def setup(self):
self.client = Mock()
self.client.accounts = ObsList()
self.view = Mock()
self.account = Mock()
self.account.conversations = ObsDict()
self.account.conferences = ObsDict()
self.client.accounts.append(self.account)
@mockified('pypsyc.client.controller', ['ConversationController',
'ConferenceController'])
def test_deleted_account(self, ConversationController,
ConferenceController):
self.account.conversations[USER2_UNI] = Mock()
self.account.conferences[PLACE_UNI] = Mock()
conv_view = self.view.show_conversation.return_value
conv_controller = ConversationController.return_value
conf_view = self.view.show_conference.return_value
conf_controller = ConferenceController.return_value
TabsController(self.client, self.view)
self.view.reset_mock()
del self.client.accounts[0]
assert _check_obs_dict(self.account.conversations)
assert _check_obs_dict(self.account.conferences)
assert self.view.method_calls == [('remove_tab', (conv_view,)),
('remove_tab', (conf_view,))]
assert conv_controller.method_calls == [('closed',)]
assert conf_controller.method_calls == [('closed',)]
@mockified('pypsyc.client.controller', ['ConversationController'])
def test_conversation(self, ConversationController):
conversation = Mock()
conversation.uni = USER2_UNI
conv_view = self.view.show_conversation.return_value
conv_controller = ConversationController.return_value
controller = TabsController(self.client, self.view)
self.account.conversations[USER2_UNI] = conversation
assert self.view.method_calls == [('show_conversation', (USER2_UNI,))]
assert ConversationController.call_args_list == [
((controller, conversation, conv_view),)]
self.view.reset_mock()
controller.focus_conversation(conversation)
assert self.view.method_calls == [('focus_tab', (conv_view,))]
self.view.reset_mock()
controller.close_tab(conv_view)
assert self.account.conversations == {}
assert self.view.method_calls == [('remove_tab', (conv_view,))]
assert conv_controller.method_calls == [('closed',)]
@mockified('pypsyc.client.controller', ['ConferenceController'])
def test_conference(self, ConferenceController):
conference = Mock()
conference.uni = PLACE_UNI
conf_view = self.view.show_conference.return_value
conf_controller = ConferenceController.return_value
self.account.conferences[PLACE_UNI] = conference
controller = TabsController(self.client, self.view)
assert self.view.method_calls == [
('show_conference', (PLACE_UNI,),)]
assert ConferenceController.call_args_list == [
((controller, conference, conf_view),)]
self.view.reset_mock()
controller.focus_conversation(conference)
assert self.view.method_calls == [('focus_tab', (conf_view,))]
self.view.reset_mock()
controller.close_tab(conf_view)
assert self.account.conferences == {}
assert self.view.method_calls == [('remove_tab', (conf_view,))]
assert conf_controller.method_calls == [('closed',)]
class TestFriendListController(object):
@mockified('pypsyc.client.controller', ['ListBinding'])
def setup(self, ListBinding):
self.ListBinding = ListBinding
model_list_ph = PlaceHolder()
make_row_ph = PlaceHolder()
self.client = Mock()
self.client.accounts = ObsList()
self.view = Mock()
self.tabs_controller = Mock()
self.controller = FriendListController(self.client, self.view,
self.tabs_controller)
assert self.ListBinding.call_args_list == [
((model_list_ph, self.view.friends, make_row_ph),)]
self.model_list = model_list_ph.obj
self.make_row = make_row_ph.obj
def test_binding(self):
EXTERN_FRIEND_UNI = SERVER2_UNI.chain('~friend')
friend = Mock()
friend.account.server = SERVER1
friend.uni = USER1_UNI
friend.presence = PRESENCE_OFFLINE
row = self.make_row(friend)
assert row == (USER1_NICK, False, friend.state)
friend.uni = EXTERN_FRIEND_UNI
friend.presence = PRESENCE_ONLINE
row = self.make_row(friend)
assert row == (EXTERN_FRIEND_UNI, True, friend.state)
def test_account(self):
update_evt = Mock()
friend1 = Mock()
friend2 = Mock()
account = Mock()
account.friends = ObsDict({USER1_UNI: friend1})
self.model_list.update_evt += update_evt
assert self.model_list == []
self.client.accounts.append(account)
assert self.model_list == [friend1]
account.friends[USER2_UNI] = friend2
assert self.model_list == [friend1, friend2]
account.friends.updated_item(USER2_UNI)
assert update_evt.call_args_list == [((1, friend2),)]
del account.friends[USER1_UNI]
assert self.model_list == [friend2]
del self.client.accounts[0]
assert _check_obs_dict(account.friends)
assert self.model_list == []
def test_open_conversation(self):
friend = Mock()
friend.uni = USER2_UNI
self.model_list.append(friend)
conversation = friend.account.get_conversation.return_value
self.controller.open_conversation(0)
assert friend.account.method_calls == [
('get_conversation', (USER2_UNI,))]
assert self.tabs_controller.method_calls == [
('focus_conversation', (conversation,))]
def test_accept_friendship(self):
friend = Mock()
friend.uni = USER2_UNI
friend.account.add_friend.side_effect = AsyncMethod()
self.model_list.append(friend)
self.controller.accept_friendship(0)
assert friend.account.method_calls == [('add_friend', (USER2_UNI,))]
def test_cancel_friendship(self):
friend = Mock()
friend.uni = USER2_UNI
friend.account.remove_friend.side_effect = AsyncMethod()
self.model_list.append(friend)
self.controller.cancel_friendship(0)
assert friend.account.method_calls == [('remove_friend', (USER2_UNI,))]
class TestMainController(object):
@mockified('pypsyc.client.controller', ['TabsController',
'FriendListController'])
def setup(self, TabsController, FriendListController):
self.client = Mock()
self.client.accounts = ObsList()
self.view = Mock()
self.controller = MainController(self.client, self.view)
self.TabsController = TabsController
self.FriendListController = FriendListController
def test_main_controller(self):
tabs_controller = self.TabsController.return_value
assert self.TabsController.call_args_list == [
((self.client, self.view.tabs_view),)]
assert self.FriendListController.call_args_list == [
((self.client, self.view.friends_view, tabs_controller),)]
def test_no_password(self):
account = self._make_account()
self.client.accounts.append(account)
callback_ph = PlaceHolder()
update_evt = Mock()
self.client.accounts.update_evt += update_evt
account.no_password_evt()
assert self.view.method_calls == [
('show_password_dialog', (account.uni, account.__dict__,
callback_ph))]
callback_ph.obj()
assert account.active == True
assert update_evt.call_args_list == [((0, account),)]
assert self.client.method_calls == [('save_accounts',)]
del self.client.accounts[0]
assert account.no_password_evt.observers == []
def test_connection_error(self):
account = self._make_account()
self.client.accounts.append(account)
account.connection_error_evt(EXCEPTION)
assert self.view.method_calls == [
('show_conn_error', (account.uni, ERROR))]
del self.client.accounts[0]
assert account.connection_error_evt.observers == []
def test_no_such_user(self):
account = self._make_account()
self.client.accounts.append(account)
account.no_such_user_evt(EXCEPTION)
assert self.view.method_calls == [('show_no_such_user', (ERROR,))]
del self.client.accounts[0]
assert account.no_such_user_evt.observers == []
def test_auth_error(self):
account = self._make_account()
self.client.accounts.append(account)
account.auth_error_evt(EXCEPTION)
assert self.view.method_calls == [
('show_auth_error', (account.uni, ERROR))]
del self.client.accounts[0]
assert account.auth_error_evt.observers == []
def _make_account(self, active=False):
account = Mock()
account.active = active
account.no_password_evt = Event()
account.connection_error_evt = Event()
account.no_such_user_evt = Event()
account.auth_error_evt = Event()
return account
@mockified('pypsyc.client.controller', ['AccountsController'])
def test_open_accounts(self, AccountsController):
self.controller.open_accounts()
assert AccountsController.call_args_list == [
((self.client, self.view.show_accounts.return_value),)]
@mockified('pypsyc.client.controller', ['DumpController'])
def test_open_dump(self, DumpController):
self.controller.open_dump()
assert DumpController.call_args_list == [
((self.client, self.view.show_dump_win.return_value),)]
def test_open_conversation(self):
account1 = Mock()
account1.active = False
account2 = Mock()
account2.active = True
self.client.accounts = [account1, account2]
callback_ph = PlaceHolder()
conversation = account2.get_conversation.return_value
self.controller.open_conversation()
assert self.view.method_calls == [
('show_open_conv_dialog', (iter_(account2.uni), callback_ph))]
callback_ph.obj(0, SERVER1, USER2_NICK)
assert account2.method_calls == [('get_conversation', (USER2_UNI,))]
assert self.TabsController.return_value.method_calls == [
('focus_conversation', (conversation,))]
def test_open_conference(self):
account1 = Mock()
account1.active = False
account2 = Mock()
account2.active = True
self.client.accounts = [account1, account2]
callback_ph = PlaceHolder()
conference = account2.get_conference.return_value
self.controller.open_conference()
assert self.view.method_calls == [
('show_open_conf_dialog', (iter_(account2.uni), callback_ph))]
callback_ph.obj(0, SERVER1, 'place')
assert account2.method_calls == [
('get_conference', (PLACE_UNI,), {'subscribe': True})]
assert self.TabsController.return_value.method_calls == [
('focus_conversation', (conference,))]
def test_add_friend(self):
account1 = Mock()
account1.active = False
account2 = Mock()
account2.active = True
account2.add_friend.side_effect = AsyncMethod()
callback_ph = PlaceHolder()
self.client.accounts = [account1, account2]
self.controller.add_friend()
assert self.view.method_calls == [
('show_add_friend_dialog', (iter_(account2.uni), callback_ph))]
callback_ph.obj(0, SERVER1, USER2_NICK)
assert account2.method_calls == [('add_friend', (USER2_UNI,))]
def test_show_active_accounts(self):
account1 = self._make_account(active=True)
account2 = self._make_account(active=True)
self.client.accounts.append(account1)
assert self.view.method_calls == [('show_active_accounts', (True,))]
self.view.reset_mock()
self.client.accounts.append(account2)
assert self.view.method_calls == []
self.client.accounts.remove(account1)
assert self.view.method_calls == []
self.client.accounts.remove(account2)
assert self.view.method_calls == [('show_active_accounts', (False,))]
def test_show_active_accounts_updated(self):
account = self._make_account(active=False)
self.client.accounts.append(account)
assert self.view.method_calls == []
account.active = True
self.client.accounts.updated_item(account)
assert self.view.method_calls == [('show_active_accounts', (True,))]
self.view.reset_mock()
self.client.accounts.updated_item(account)
assert self.view.method_calls == []
account.active = False
self.client.accounts.updated_item(account)
assert self.view.method_calls == [('show_active_accounts', (False,))]
self.view.reset_mock()
self.client.accounts.updated_item(account)
assert self.view.method_calls == []
del self.client.accounts[0]
assert self.view.method_calls == []
def test_quit(self):
self.controller.quit()
assert _check_obs_list(self.client.accounts)
assert self.client.method_calls == [('quit',)]

View File

@ -0,0 +1,563 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from os import remove
from tempfile import NamedTemporaryFile
from mock import Mock, MagicMock, sentinel
from nose.tools import assert_raises
from tests.constants import (SERVER1, SERVER2, USER1_UNI, USER2_UNI,
USER1_NICK, USER2_NICK, RESOURCE, RESOURCE1_UNI, PLACE_UNI, VARIABLES,
CONTENT, PASSWORD, MESSAGE, ONLINE, PRESENCE_UNKNOWN, PRESENCE_ONLINE,
OFFERED, ESTABLISHED, ERROR, EXCEPTION)
from tests.helpers import mockified, PlaceHolder, inited_header, AsyncMethod
from pypsyc.client.model import (Conversation, Member, Conference, Messaging,
Conferencing, Account, FriendList, ClientCircuit, Circuit, Client)
from pypsyc.core.psyc import PSYCPacket
from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError,
AuthenticationError)
class TestConversation(object):
def test_send_message(self):
message_ph = PlaceHolder()
messaging = Mock()
conversation = Conversation(messaging, USER2_UNI)
conversation.send_message(MESSAGE)
assert messaging.protocol.method_calls == [
('send_private_message', (USER2_UNI, MESSAGE),
{'relay': messaging.account.uni})]
assert conversation.messages == [message_ph]
assert message_ph.obj.source == messaging.account.uni
class TestConference(object):
def test_send_message(self):
conferencing = Mock()
conference = Conference(conferencing, PLACE_UNI)
conference.send_message(MESSAGE)
assert conferencing.protocol.method_calls == [
('send_public_message', (PLACE_UNI, MESSAGE))]
class TestMessaging(object):
def test_recv_message(self):
account = Mock()
conversation = account.get_conversation.return_value
conversation.messages = []
message_ph = PlaceHolder()
messaging = Messaging(account)
messaging.private_message(USER2_UNI, MESSAGE)
assert account.get_conversation.call_args_list == [((USER2_UNI,),)]
assert conversation.messages == [message_ph]
assert message_ph.obj.source == USER2_UNI
assert message_ph.obj.message == MESSAGE
@mockified('pypsyc.client.model', ['MessagingProtocol'])
def test_connected(self, MessagingProtocol):
circuit = Mock()
messaging = Messaging(Mock())
messaging.connected(circuit)
assert circuit.psyc.method_calls == [
('add_handler', (MessagingProtocol.return_value,))]
def test_disconnected(self):
Messaging(Mock).disconnected()
class TestConferencing(object):
def test_member_entered(self):
account = Mock()
conference = account.get_conference.return_value
conference.members = []
conferencing = Conferencing(account)
conferencing.member_entered(PLACE_UNI, USER2_UNI, USER2_NICK)
assert account.get_conference.call_args_list == [((PLACE_UNI,),)]
assert conference.members == [Member(USER2_UNI, USER2_NICK)]
def test_member_left(self):
account = Mock()
conference = account.get_conference.return_value
conference.members = [Member(USER1_UNI, USER1_NICK),
Member(USER2_UNI, USER2_NICK)]
conferencing = Conferencing(account)
conferencing.member_left(PLACE_UNI, USER2_UNI)
assert conference.members == [Member(USER1_UNI, USER1_NICK)]
def test_recv_message(self):
account = Mock()
conference = account.get_conference.return_value
conference.messages = []
message_ph = PlaceHolder()
conferencing = Conferencing(account)
conferencing.public_message(PLACE_UNI, USER2_UNI, MESSAGE)
assert account.get_conference.call_args_list == [((PLACE_UNI,),)]
assert conference.messages == [message_ph]
assert message_ph.obj.source == USER2_UNI
assert message_ph.obj.message == MESSAGE
@mockified('pypsyc.client.model', ['ConferencingProtocol'])
def test_connected(self, ConferencingProtocol):
circuit = Mock()
conferencing = Conferencing(Mock())
conferencing.connected(circuit)
assert circuit.method_calls == [
('psyc.add_handler', (ConferencingProtocol.return_value,)),
('add_pvar_handler', (ConferencingProtocol.return_value,))]
def test_disconnected(self):
Conferencing(Mock()).disconnected()
class _Dict(dict):
def __init__(self, *args, **kwds):
dict.__init__(self, *args, **kwds)
self.updated_item = Mock()
class TestFriendList(object):
def test_friendship(self):
friend_ph = PlaceHolder()
account = Mock()
account.friends = _Dict()
friend_list = FriendList(account)
friend_list.friendship(USER1_UNI, OFFERED)
assert account.friends == {USER1_UNI: friend_ph}
friend = friend_ph.obj
assert friend.account == account
assert friend.uni == USER1_UNI
assert friend.presence == PRESENCE_UNKNOWN
assert friend.state == OFFERED
friend_list.friendship(USER1_UNI, ESTABLISHED)
assert friend.state == ESTABLISHED
assert account.friends.updated_item.call_args_list == [((USER1_UNI,),)]
friend_list.friendship_removed(USER1_UNI)
assert account.friends == {}
def test_presence(self):
account = Mock()
account.friends = _Dict({USER2_UNI: Mock()})
friend_list = FriendList(account)
friend_list.presence(USER2_UNI, ONLINE)
assert account.friends[USER2_UNI].presence == PRESENCE_ONLINE
assert account.friends.updated_item.call_args_list == [((USER2_UNI,),)]
@mockified('pypsyc.client.model', ['FriendshipProtocol'])
def test_connected(self, FriendshipProtocol):
circuit = Mock()
friend_list = FriendList(Mock())
friend_list.connected(circuit)
assert circuit.method_calls == [
('psyc.add_handler', (FriendshipProtocol.return_value,)),
('add_pvar_handler', (FriendshipProtocol.return_value,))]
def test_disconnected(self):
account = Mock()
account.friends = _Dict({USER2_UNI: Mock()})
friend_list = FriendList(account)
friend_list.disconnected()
assert account.friends == {}
class TestAccount(object):
@mockified('pypsyc.client.model', ['ObsDict'])
def test_account(self, ObsDict):
obs_dict = ObsDict.return_value = MagicMock()
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
assert account.uni == USER1_UNI
assert ObsDict.call_args_list == [(), (), ()]
assert account.conversations == obs_dict
assert account.conferences == obs_dict
assert account.friends == obs_dict
@mockified('pypsyc.client.model', ['Messaging', 'Conversation'])
def test_get_conversation(self, Messaging, Conversation):
messaging = Messaging.return_value
conversation = Conversation.return_value
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
assert Messaging.call_args_list == [((account,),)]
assert account.get_conversation(USER2_UNI) == conversation
assert account.get_conversation(USER2_UNI) == conversation
assert Conversation.call_args_list == [((messaging, USER2_UNI),)]
assert account.conversations == {USER2_UNI: conversation}
@mockified('pypsyc.client.model', ['Conferencing', 'Conference'])
def test_get_conference(self, Conferencing, Conference):
conferencing = Conferencing.return_value
conference = Conference.return_value
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
assert Conferencing.call_args_list == [((account,),)]
assert account.get_conference(PLACE_UNI) == conference
assert account.get_conference(PLACE_UNI) == conference
assert Conference.call_args_list == [((conferencing, PLACE_UNI),)]
assert account.conferences == {PLACE_UNI: conference}
@mockified('pypsyc.client.model', ['Conferencing', 'Conference'])
def test_get_conference_subscribe(self, Conferencing, Conference):
conferencing = Conferencing.return_value
conference = Conference.return_value
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
client_interface = account.client_interface = Mock()
client_interface.subscribe.side_effect = AsyncMethod()
assert account.get_conference(PLACE_UNI, subscribe=True) == conference
assert account.get_conference(PLACE_UNI, subscribe=True) == conference
assert Conference.call_args_list == [((conferencing, PLACE_UNI),)]
assert account.conferences == {PLACE_UNI: conference}
assert client_interface.method_calls == [('subscribe', (PLACE_UNI,))]
@mockified('pypsyc.client.model', ['Conference'])
def test_get_conference_subscribe_unknown_target(self, Conference):
conference = Conference.return_value
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
client_interface = account.client_interface = Mock()
client_interface.subscribe.side_effect = UnknownTargetError
account.get_conference(PLACE_UNI, subscribe=True)
assert conference.method_calls == [('unknown_target_evt',)]
@mockified('pypsyc.client.model', ['Conference'])
def test_get_conference_subscribe_delivery_failed(self, Conference):
conference = Conference.return_value
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
client_interface = account.client_interface = Mock()
client_interface.subscribe.side_effect = DeliveryFailedError(ERROR)
account.get_conference(PLACE_UNI, subscribe=True)
assert conference.method_calls == [('delivery_failed_evt', (ERROR,))]
def test_remove_conference(self):
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
account.conferences[PLACE_UNI] = Mock()
client_interface = account.client_interface = Mock()
client_interface.unsubscribe.side_effect = AsyncMethod()
del account.conferences[PLACE_UNI]
assert client_interface.method_calls == [('unsubscribe', (PLACE_UNI,))]
def test_add_friend(self):
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
client_interface = account.client_interface = Mock()
account.add_friend(USER2_UNI)
assert client_interface.method_calls == [('add_friend', (USER2_UNI,))]
def test_remove_friend(self):
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
client_interface = account.client_interface = Mock()
account.remove_friend(USER2_UNI)
assert client_interface.method_calls == [
('remove_friend', (USER2_UNI,))]
@mockified('pypsyc.client.model', ['FriendList'])
def test_friendlist(self, FriendList):
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
assert FriendList.call_args_list == [((account,),)]
def test_unknown_target_error(self):
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
account.conversations[USER1_UNI] = conversation = Mock()
account.conferences[PLACE_UNI] = conference = Mock()
account.unknown_target_error(USER1_UNI)
account.unknown_target_error(USER2_UNI)
account.unknown_target_error(PLACE_UNI)
assert conversation.method_calls == [('unknown_target_evt',)]
assert conference.method_calls == [('unknown_target_evt',)]
def test_delivery_failed_error(self):
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
account.conversations[USER1_UNI] = conversation = Mock()
account.conferences[PLACE_UNI] = conference = Mock()
account.delivery_failed_error(USER1_UNI, ERROR)
account.delivery_failed_error(USER2_UNI, ERROR)
account.delivery_failed_error(PLACE_UNI, ERROR)
assert conversation.method_calls == [('delivery_failed_evt', (ERROR,))]
assert conference.method_calls == [('delivery_failed_evt', (ERROR,))]
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
'LinkingProtocol',
'RoutingErrorProtocol',
'ClientInterfaceProtocol', 'Messaging',
'Conferencing', 'FriendList'])
def test_active(self, resolve_hostname, connect, LinkingProtocol,
RoutingErrorProtocol, ClientInterfaceProtocol, Messaging,
Conferencing, FriendList):
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
circuit = connect.return_value
linking_protocol = LinkingProtocol.return_value
linked = linking_protocol.link.side_effect = AsyncMethod()
messaging = Messaging.return_value
conferencing = Conferencing.return_value
friend_list = FriendList.return_value
client = Mock()
account = Account(client, SERVER1, USER1_NICK, PASSWORD, True, False)
account.active = True
account.active = True
assert resolve_hostname.call_args_list == [((account.server,),)]
assert connect.call_args_list == [
((sentinel.ip, sentinel.port, client),)]
assert LinkingProtocol.call_args_list == [((circuit,),)]
assert linking_protocol.method_calls == [
('link', (USER1_UNI, RESOURCE, PASSWORD))]
linked.callback()
assert circuit.psyc.uni == RESOURCE1_UNI
assert RoutingErrorProtocol.call_args_list == [((account,),)]
assert circuit.psyc.method_calls == [
('add_handler', (RoutingErrorProtocol.return_value,))]
assert ClientInterfaceProtocol.call_args_list == [((account,),)]
assert messaging.method_calls == [('connected', (circuit,))]
assert conferencing.method_calls == [('connected', (circuit,))]
assert friend_list.method_calls == [('connected', (circuit,))]
messaging.reset_mock()
conferencing.reset_mock()
friend_list.reset_mock()
account.active = False
account.active = False
assert circuit.transport.method_calls == [('loseConnection',)]
assert not hasattr(account, 'circuit')
assert messaging.method_calls == [('disconnected',)]
assert conferencing.method_calls == [('disconnected',)]
assert friend_list.method_calls == [('disconnected',)]
@mockified('pypsyc.client.model', ['resolve_hostname'])
def test_active_no_password(self, resolve_hostname):
no_password_evt = Mock()
account = Account(None, SERVER1, USER1_NICK, '', False, False)
account.no_password_evt += no_password_evt
account.active = True
assert account.active == False
assert no_password_evt.call_args_list == [()]
assert resolve_hostname.call_args_list == []
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect'])
def test_active_dns_error(self, resolve_hostname, connect):
resolve_hostname.side_effect = EXCEPTION
client = Mock()
connection_error_evt = Mock()
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
account.connection_error_evt += connection_error_evt
account.active = True
assert account.active == False
assert client.accounts.updated_item.call_args_list == [((account,),)]
assert connection_error_evt.call_args_list == [((EXCEPTION,),)]
assert connect.call_args_list == []
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
'LinkingProtocol'])
def test_active_connect_error(self, resolve_hostname, connect,
LinkingProtocol):
client = Mock()
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
connect.side_effect = EXCEPTION
connection_error_evt = Mock()
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
account.connection_error_evt += connection_error_evt
account.active = True
assert account.active == False
assert client.accounts.updated_item.call_args_list == [((account,),)]
assert connection_error_evt.call_args_list == [((EXCEPTION,),)]
assert LinkingProtocol.call_args_list == []
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
'LinkingProtocol'])
def test_active_no_such_user(self, resolve_hostname, connect,
LinkingProtocol):
ERROR = UnknownTargetError()
client = Mock()
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
circuit = connect.return_value
LinkingProtocol.return_value.link.side_effect = ERROR
no_such_user_evt = Mock()
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
account.no_such_user_evt += no_such_user_evt
account.active = True
assert account.active == False
assert client.accounts.updated_item.call_args_list == [((account,),)]
assert no_such_user_evt.call_args_list == [((ERROR,),)]
assert circuit.psyc.uni != account.uni.chain(account.resource)
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
'LinkingProtocol'])
def test_active_incorrect_password(self, resolve_hostname, connect,
LinkingProtocol):
ERROR = AuthenticationError()
client = Mock()
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
circuit = connect.return_value
LinkingProtocol.return_value.link.side_effect = ERROR
auth_error_evt = Mock()
account = Account(client, SERVER1, USER1_NICK, 'wrong', False, False)
account.auth_error_evt += auth_error_evt
account.active = True
assert account.active == False
assert client.accounts.updated_item.call_args_list == [((account,),)]
assert auth_error_evt.call_args_list == [((ERROR,),)]
assert circuit.psyc.uni != account.uni.chain(account.resource)
def test_connection_error(self):
client = Mock()
connection_error_evt = Mock()
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
account.connection_error_evt += connection_error_evt
account.connection_error(EXCEPTION)
assert account.active == False
assert client.accounts.updated_item.call_args_list == [((account,),)]
assert connection_error_evt.call_args_list == [((EXCEPTION,),)]
class TestClientCircuit(object):
@mockified(Circuit, ['connectionMade'])
def test_dump_out(self, connectionMade):
LINE1 = ':_target\t' + USER2_UNI
LINE2 = '|'
cc = ClientCircuit()
dump_evt = Mock()
cc.dump_evt += dump_evt
cc.transport = Mock()
orig_write = cc.transport.write
cc.connectionMade()
assert connectionMade.call_args_list == [((cc,),)]
cc.send({'_target': USER2_UNI}, '')
assert dump_evt.call_args_list == [(('o', LINE1),), (('o', LINE2),)]
assert orig_write.call_args_list == [((LINE1+'\n'+LINE2+'\n',),)]
@mockified(Circuit, ['lineReceived'])
def test_dump_in(self, lineReceived):
lineReceived.return_value = None
cc = ClientCircuit()
dump_evt = Mock()
cc.dump_evt += dump_evt
cc.dataReceived('a\nb\n')
assert dump_evt.call_args_list == [(('i', 'a'),), (('i', 'b'),)]
assert lineReceived.call_args_list == [((cc, 'a'),), ((cc, 'b'),)]
@mockified('pypsyc.client.model', ['PSYCObject'])
def test_packet_received(self, PSYCObject):
psyc = PSYCObject.return_value
psyc.handle_packet.return_value = PSYCPacket()
cc = ClientCircuit()
assert PSYCObject.call_args_list == [((cc.send,),)]
cc.packet_received({}, CONTENT)
assert psyc.method_calls == [('handle_packet', ({}, CONTENT))]
@mockified('pypsyc.client.model', ['PSYCObject'])
def test_relay(self, PSYCObject):
psyc = PSYCObject.return_value
psyc.uni = RESOURCE1_UNI
psyc.handle_packet.return_value = PSYCPacket()
cc = ClientCircuit()
header = inited_header(_source=USER1_UNI, _source_relay=USER2_UNI)
cc.packet_received(header, CONTENT)
assert header.source == USER2_UNI
header2 = inited_header(_source=USER2_UNI, _source_relay=USER2_UNI)
assert_raises(AssertionError, cc.packet_received, header2, CONTENT)
@mockified('pypsyc.client.model', ['PSYCObject'])
def test_persistent(self, PSYCObject):
HEADER = {'_context': USER2_UNI}
packet = PSYCPacket({'=': VARIABLES, '+': VARIABLES, '-': VARIABLES})
packet.header = inited_header(HEADER)
psyc = PSYCObject.return_value
psyc.handle_packet.return_value = packet
cc = ClientCircuit()
handler = Mock()
state_set_key = handler.state_set_key = Mock()
state_add_key = handler.state_add_key = Mock()
state_remove_key = handler.state_remove_key = Mock()
cc.add_pvar_handler(handler)
cc.packet_received(HEADER, CONTENT)
assert psyc.method_calls == [('handle_packet', (HEADER, CONTENT))]
assert state_set_key.call_args_list == [((USER2_UNI, VARIABLES),)]
assert state_add_key.call_args_list == [((USER2_UNI, VARIABLES),)]
assert state_remove_key.call_args_list == [((USER2_UNI, VARIABLES),)]
class TestClient(object):
@mockified('pypsyc.client.model', ['Account'])
def test_accounts(self, Account):
account = Account.return_value
error_ph = PlaceHolder()
account1 = Mock()
account1.server = SERVER1
account1.person = USER1_NICK
account1.password = PASSWORD
account1.save_password = True
account1.active = False
account2 = Mock()
account2.server = SERVER2
account2.person = USER2_NICK
account2.password = PASSWORD
account2.save_password = False
account2.active = True
try:
with NamedTemporaryFile(delete=False) as f:
f.write('[]')
client = Client(accounts_file=f.name)
assert client.accounts == []
client.accounts = [account1, account2]
client.save_accounts()
client = Client(accounts_file=f.name)
client.load_accounts()
assert client.accounts == [Account.return_value] * 2
assert Account.call_args_list == [
((client, SERVER1, USER1_NICK, PASSWORD, True, False),),
((client, SERVER2, USER2_NICK, '', False, True),)]
finally:
remove(f.name)
client.connection_lost(account.circuit, sentinel.error)
assert account.method_calls == [('connection_error', (error_ph,))] * 2
assert error_ph.obj.args[0] == str(sentinel.error)
@mockified('pypsyc.client.model', ['reactor'])
def test_quit(self, reactor):
account = Mock()
client = Client()
client.accounts = [account]
client.quit()
assert account.active == False
assert reactor.method_calls == [('stop',)]

View File

@ -0,0 +1,173 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock
from nose.tools import assert_raises
from pypsyc.client.observable import ObsAttr, ObsObj, ObsList, ObsDict
class StubObsObj(ObsObj):
foo = ObsAttr('foo')
def test_obs_obj():
obs_obj = StubObsObj()
update_evt = Mock()
obs_obj.update_evt['foo'] += update_evt
obs_obj.ignore = 'foo'
assert not hasattr(obs_obj, 'foo')
obs_obj.foo = 'foo'
obs_obj.foo = 'foo'
obs_obj.foo = 'bar'
assert obs_obj.foo == 'bar'
del obs_obj.foo
assert not hasattr(obs_obj, 'foo')
assert_raises(AttributeError, obs_obj.__delattr__, 'foo')
assert update_evt.call_args_list == [
((None, 'foo'),), (('foo', 'bar'),), (('bar', None),)]
class TestObsList(object):
def test_basic(self):
obs_list = ObsList(xrange(10))
setitem_evt = Mock()
obs_list.setitem_evt += setitem_evt
delitem_evt = Mock()
obs_list.delitem_evt += delitem_evt
insert_evt = Mock()
obs_list.insert_evt += insert_evt
obs_list[3] = 3
obs_list[3] = 333
del obs_list[3]
obs_list.insert(3, 3)
assert obs_list == list(xrange(10))
assert setitem_evt.call_args_list == [((3, 3, 333),)]
assert delitem_evt.call_args_list == [((3, 333),)]
assert insert_evt.call_args_list == [((3, 3),)]
def test_ext_add(self):
obs_list = ObsList()
insert_evt = Mock()
obs_list.insert_evt += insert_evt
obs_list.append(42)
obs_list.extend([42, 1337])
obs_list += [42, 1337]
assert obs_list == [42, 42, 1337, 42, 1337]
assert insert_evt.call_args_list == [
((0, 42),), ((1, 42),), ((2, 1337),), ((3, 42),), ((4, 1337),)]
def test_ext_del(self):
obs_list = ObsList(xrange(10))
delitem_evt = Mock()
obs_list.delitem_evt += delitem_evt
assert obs_list.pop() == 9
assert obs_list.pop(8) == 8
obs_list.remove(7)
assert delitem_evt.call_args_list == [((9, 9),), ((8, 8),), ((7, 7),)]
assert obs_list == list(xrange(7))
def test_reverse(self):
obs_list = ObsList(xrange(10))
obs_list.reverse()
assert obs_list == list(reversed(xrange(10)))
def test_updated_item(self):
obs_list = ObsList(['a', 'b'])
update_evt = Mock()
obs_list.update_evt += update_evt
obs_list.updated_item('b')
assert update_evt.call_args_list == [((1, 'b'),)]
class TestObsDict(object):
def test_basic(self):
obs_dict = ObsDict(a='A')
setitem_evt = Mock()
obs_dict.setitem_evt += setitem_evt
delitem_evt = Mock()
obs_dict.delitem_evt += delitem_evt
obs_dict['a'] = 'A'
obs_dict['a'] = 'X'
del obs_dict['a']
obs_dict['b'] = 'B'
assert obs_dict == dict(b='B')
assert setitem_evt.call_args_list == [(('a', 'A', 'X'),),
(('b', None, 'B'),)]
assert delitem_evt.call_args_list == [(('a', 'X'),)]
def test_pop(self):
obs_dict = ObsDict(a='A', b='B')
delitem_evt = Mock()
obs_dict.delitem_evt += delitem_evt
assert obs_dict.pop('a') == 'A'
assert obs_dict.pop('a', 'X') == 'X'
assert obs_dict.pop('b', 'X') == 'B'
assert obs_dict == {}
assert delitem_evt.call_args_list == [(('a', 'A'),), (('b', 'B'),)]
def test_popitem(self):
obs_dict = ObsDict(a='A', b='B')
delitem_evt = Mock()
obs_dict.delitem_evt += delitem_evt
k, v = obs_dict.popitem()
assert v == k.upper()
assert len(obs_dict) == 1
assert delitem_evt.call_args_list == [((k, v),)]
def test_clear(self):
obs_dict = ObsDict(a='A', b='B')
delitem_evt = Mock()
obs_dict.delitem_evt += delitem_evt
obs_dict.clear()
assert delitem_evt.call_args_list == [(('a', 'A'),), (('b', 'B'),)]
def test_update(self):
obs_dict = ObsDict()
setitem_evt = Mock()
obs_dict.setitem_evt += setitem_evt
obs_dict.update({'a': 'A'}, b='B')
obs_dict.update((('c', 'C'),))
assert obs_dict == dict(a='A', b='B', c='C')
assert setitem_evt.call_args_list == [
(('a', None, 'A'),), (('b', None, 'B'),), (('c', None, 'C'),)]
def test_setdefault(self):
obs_dict = ObsDict(a='A')
setitem_evt = Mock()
obs_dict.setitem_evt += setitem_evt
assert obs_dict.setdefault('a') == 'A'
assert obs_dict.setdefault('b', 'B') == 'B'
assert obs_dict == dict(a='A', b='B')
assert setitem_evt.call_args_list == [(('b', None, 'B'),)]
def test_updated_item(self):
obs_dict = ObsDict({'a': 'A', 'b': 'B'})
update_evt = Mock()
obs_dict.update_evt += update_evt
obs_dict.updated_item('b')
assert update_evt.call_args_list == [(('b', 'B'),)]

View File

@ -0,0 +1,111 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
def _callback(*args, **kwds): # pragma: no cover
print ('callback', args, kwds)
def _test_accounts_view(): # pragma: no cover
accounts_view = AccountsView()
Controller(controller.AccountsController, accounts_view)
accounts_view.window.connect('destroy', lambda w: gtk.main_quit())
for i in xrange(10):
accounts_view.accounts.append(("psyc://server/~account%i" % i, True))
accounts_view.accounts[4] = ("psyc://server/~updatedaccount4", False)
#return
from pypsyc.client.model import Account
account = Account(None, "", "", "", False, False)
accounts_view.show_addedit_dialog(account.__dict__, True, _callback)
def _test_tabs_view(): # pragma: no cover
status_icon = gtk.StatusIcon()
status_icon.set_from_file('pypsyc/client/psyc.ico')
status_icon.connect('activate', lambda w: tabs_view.on_status_icon_click())
tabs_view = TabsView(status_icon)
tabs_view.window.connect('destroy', lambda w: gtk.main_quit())
Controller(controller.TabsController, tabs_view)
conversation_view = tabs_view.show_conversation("Conversation 1")
Controller(controller.ConversationController, conversation_view)
tabs_view.focus_tab(conversation_view)
conference_view = tabs_view.show_conference("Conversation 2")
Controller(controller.ConferenceController, conference_view)
def show():
for i in xrange(40):
conversation_view.show_message("Message %02i" % i)
for i in xrange(40):
conference_view.members.append(("psyc://server/~person%02i" % i,
"person%02i" % i))
conference_view.show_message("Message %02i" % i)
conference_view.members[20] = ("psyc://server/~longpersonname20",
"longpersonname20")
idle_add(show)
def _test_dump_view(): # pragma: no cover
dump_view = DumpView()
Controller(controller.DumpController, dump_view)
dump_view.show_line('o', 'a', 'psyc://server/~account')
dump_view.show_line('i', ':_tag_relay\t\xc8\xe6', 'psyc://server/~account')
def _test_main_view(): # pragma: no cover
main_view = MainView()
Controller(controller.MainController, main_view)
Controller(controller.FriendListController, main_view.friends_view)
for i in xrange(40):
main_view.friends_view.friends.append(("Friend %02i" % i, False,
'pending'))
for i in xrange(0, 40, 2):
main_view.friends_view.friends[i] = ("Friend %02i updated" % i, False,
'offered')
for i in xrange(0, 40, 3):
main_view.friends_view.friends[i] = ("Friend %02i updated 2" % i, True,
'established')
#return
account_dict = {'password': '', 'save_password': False}
main_view.show_password_dialog("psyc://server/~account", account_dict,
_callback)
main_view.show_no_such_user("psyc://server/~account")
main_view.show_auth_error("psyc://server/~account", "Error description")
main_view.show_open_conv_dialog(["psyc://server/~person1",
"psyc://server/~person2"], _callback)
main_view.show_open_conf_dialog(["psyc://server/~person1",
"psyc://server/~person2"], _callback)
main_view.show_add_friend_dialog(["psyc://server/~person1",
"psyc://server/~person2"], _callback)
if __name__ == '__main__': # pragma: no cover
class Controller(object):
def __init__(self, controller, view):
self.controller = controller
self.c_name = controller.__name__
view.controller = self
def __getattr__(self, attr):
m = getattr(self.controller, attr, None)
assert m, "%s has no method %s" % (self.c_name, attr)
def f(*args, **kwds):
m_argcount = m.__code__.co_argcount - 1 # minus self
f_argcount = len(args) + len(kwds)
assert m_argcount == f_argcount, \
"%s.%s has %s arguments, called with %s arguments" % (
self.c_name, attr, m_argcount, f_argcount)
print "%s.%s called: %s, %s" % (self.c_name, attr, args, kwds)
return f
from gobject import idle_add
import gtk
from pypsyc.client.view import AccountsView, TabsView, DumpView, MainView
from pypsyc.client import controller
# _test_accounts_view()
# _test_tabs_view()
# _test_dump_view()
# _test_main_view()
gtk.main()

View File

@ -0,0 +1,97 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from nose.tools import assert_raises
from twisted.protocols.loopback import loopbackAsync as loopback_async
from pypsyc.core.mmp import Uni, Header, Circuit
UNI = 'psyc://server/foo/bar'
class TestUni(object):
def test_uni(self):
uni = Uni(UNI)
assert uni == UNI
assert uni.prefix == 'psyc'
def test_parts(self):
uni = Uni(UNI)
assert uni.into_parts() == ['server', 'foo', 'bar']
assert Uni.from_parts(uni.into_parts()) == uni
def test_descendant_ancestor(self):
uni = Uni(UNI)
assert uni.is_descendant_of('psyc://server/foo')
assert not uni.is_descendant_of('psyc://server/spam')
assert uni.is_ancestor_of('psyc://server/foo/bar/child')
assert not uni.is_ancestor_of('psyc://server/spam/eggs/child')
assert uni.is_descendant_of('psyc://server/')
assert Uni('psyc://server/').is_ancestor_of('psyc://server/foo/bar')
def test_chain(self):
assert Uni(UNI).chain('foobar') == 'psyc://server/foo/bar/foobar'
assert Uni('psyc://server/').chain('foo') == 'psyc://server/foo'
class TestHeader(object):
def test_empty(self):
header = Header()
assert header == {}
header._init()
assert header.source is None
assert header.target is None
assert header.context is None
def test_header(self):
header_dict = {
'_source': 'psyc://server/uni1',
'_target': 'psyc://server/uni2',
'_context': 'psyc://server/@place'
}
header = Header(header_dict)
assert header == header_dict
header._init()
assert header.source == Uni('psyc://server/uni1')
assert header.target == Uni('psyc://server/uni2')
assert header.context == Uni('psyc://server/@place')
class TestCircuit(object):
def setup(self):
self.success = False
def test_render_empty(self):
self._test({}, [])
def test_onlyvars(self):
self._test({'_target': 'psyc://example.org/'}, [])
def test_onlycontent(self):
self._test({}, ['content'])
def test_packet(self):
self._test({'_target': 'psyc://example.org/'}, ['content'])
def _test(self, header, content):
def received(header_, content_):
assert header_ == header
assert content_ == content
self.success = True
circuit1 = Circuit()
circuit1.packet_received = received
circuit2 = Circuit()
circuit2.initiator = True
circuit2.inited = lambda: circuit2.send(header, iter(content))
loopback_async(circuit1, circuit2)
assert self.success
def test_subclass(self):
circuit = Circuit()
circuit.lineReceived('|')
assert_raises(NotImplementedError, circuit.lineReceived, '|')

View File

@ -0,0 +1,151 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock
from nose.tools import assert_raises
from tests.constants import USER1_UNI, USER2_UNI
from tests.helpers import check_success, rendered
from pypsyc.core.psyc import PSYCPacket, PSYCObject
from pypsyc.util import schedule
class TestPSYCPacket(object):
def test_vars(self):
packet = PSYCPacket(
modifiers={
':': {'_foo': 'bar'},
'=': {'_spam': 'eggs'}
}
)
self._test(packet, [':_foo\tbar', '=_spam\teggs'])
def test_method(self):
packet = PSYCPacket(mc='_notice_foo')
self._test(packet, ['_notice_foo'])
def test_packet(self):
packet = PSYCPacket(
modifiers={
':': {'_foo': 'bar'}
},
mc='_message_foo',
data='this is\ndata'
)
self._test(packet, [':_foo\tbar', '_message_foo', 'this is\ndata'])
def test_binary1(self):
packet = PSYCPacket({':': {'_foo': 'foo\nbar'}})
self._test(packet, [':_foo 7\tfoo\nbar'])
assert PSYCPacket.parse([':_foo 7\tfoo', 'bar']) == packet
def test_binary2(self):
packet = PSYCPacket({':': {'_foo': 'foo\n\nbar'}})
self._test(packet, [':_foo 8\tfoo\n\nbar'])
assert PSYCPacket.parse([':_foo 8\tfoo', '', 'bar']) == packet
def _test(self, packet, rendered_):
assert PSYCPacket.parse(packet.render()) == packet
assert rendered(packet) == rendered_
def test_nomethod(self):
packet = PSYCPacket(data='foobar')
assert_raises(AssertionError, list, packet.render())
def test_repr(self):
s = '\n'.join((
r"PSYCPacket(",
r" modifiers={",
r" ':': {'_foo': 'bar'},",
r" '=': {'_foo': 'bar'}",
r" },",
r" mc='_message_foo',",
r" data='this is\ndata'",
r")"
))
assert repr(eval(s)) == s
def test_equal(self):
assert PSYCPacket() == PSYCPacket()
assert not PSYCPacket() != PSYCPacket()
def test_empty(self):
packet = PSYCPacket()
self._test(packet, [])
assert packet.modifiers == {':': {}}
assert packet.mc is None
assert packet.data == ''
def test_from_kwds(self):
packet1 = PSYCPacket.from_kwds(mc='_message_public',
data="hello", _somevar='foo')
packet2 = PSYCPacket(mc='_message_public', data="hello",
modifiers={':': {'_somevar': 'foo'}})
assert packet1 == packet2
class TestPSYCObject(object):
def test_handle_packet(self):
psyc = PSYCObject(None)
psyc.handlers['_message'] = message = Mock()
psyc.handlers['_message_public'] = message_public = Mock()
p1 = PSYCPacket() # psyc_packet should return
p2 = PSYCPacket(mc='_message_public')
p3 = PSYCPacket(mc='_message_private')
assert psyc.handle_packet({}, p1.render()) == p1
assert psyc.handle_packet({}, p2.render()) == p2
assert psyc.handle_packet({}, p3.render()) == p3
assert message_public.call_args_list == [((p2,),)]
assert message.call_args_list == [((p3,),)]
def test_handle_packet_internal_error(self):
psyc = PSYCObject(None)
psyc.handlers['_message_public'] = Mock(side_effect=Exception)
packet = PSYCPacket(mc='_message_public')
assert psyc.handle_packet({}, packet.render()) == packet
def test_sendmsg_packet(self):
packet = PSYCPacket()
sendfunc = Mock()
psyc = PSYCObject(sendfunc, USER1_UNI)
psyc.sendmsg(USER2_UNI, packet)
header = {'_source': USER1_UNI, '_target': USER2_UNI}
assert sendfunc.call_args_list == [((header, rendered(packet)),)]
def test_sendmsg_kwds(self):
packet = PSYCPacket({':': {'_somevar': 'foo'}}, mc='_message_private')
sendfunc = Mock()
psyc = PSYCObject(sendfunc, USER1_UNI)
psyc.sendmsg(USER2_UNI, mc='_message_private', _somevar='foo')
header = {'_source': USER1_UNI, '_target': USER2_UNI}
assert sendfunc.call_args_list == [((header, rendered(packet)),)]
@check_success
def test_async(self):
MC = '_request_foo'
ECHO = PSYCPacket(mc='_echo_foo')
o1 = PSYCObject(lambda *args: o2.handle_packet(*args), USER1_UNI)
o2 = PSYCObject(lambda *args: o1.handle_packet(*args), USER2_UNI)
o2.handlers[MC] = lambda _: ECHO
def _test():
ret = o1.sendmsg(USER2_UNI, mc=MC)
assert ret == ECHO
self.success = True
schedule(_test)
def test_async_internal_error(self):
MC = '_request_foo'
ERROR = PSYCPacket(mc='_error_internal')
o1 = PSYCObject(lambda *args: o2.handle_packet(*args), USER1_UNI)
o2 = PSYCObject(lambda *args: o1.handle_packet(*args), USER2_UNI)
o2.handlers[MC] = Mock(side_effect=Exception)
ret = o1.sendmsg(USER2_UNI, mc=MC)
assert ret == ERROR

View File

@ -0,0 +1,396 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import hmac
from hashlib import sha256
from mock import Mock
from nose.tools import assert_raises
from tests.constants import (SERVER1, USER1, USER2, USER1_UNI, USER2_UNI,
USER1_NICK, RESOURCE, RESOURCE1_UNI, RESOURCE2_UNI, PLACE, PLACE_UNI,
INTERFACE, VARIABLES, PASSWORD, MESSAGE, ONLINE, PENDING, OFFERED,
ESTABLISHED, ERROR)
from tests.helpers import connect_circuits, PlaceHolder
from pypsyc.client.model import ClientCircuit
from pypsyc.core.psyc import PSYCPacket
from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError,
RoutingErrorRoot, RoutingErrorRelaying, RoutingErrorClient,
AuthenticationError, LinkingServer, LinkingClient, Messaging,
MessageRelaying, ConferencingServer, ConferencingClient, EntryDeniedError,
ContextMaster, ContextSlave, UnauthorizedError, ClientInterfaceServer,
ClientInterfaceClient, FriendshipServer, FriendshipClient)
from pypsyc.server import Entity
from pypsyc.server.person import Resource
from pypsyc.server.routing import Routing
def _setup():
server = Mock()
server.hostname = SERVER1
routing = Routing(server.hostname, INTERFACE)
server.routing = routing
routing.init(_entity(server=server))
return routing
def _entity(*args, **kwds):
entity = Entity(*args, **kwds)
entity.package = Mock()
entity.package.entity = entity
return entity
def _c2s_circuit(routing, link_to=None):
sc, cc = connect_circuits(routing.buildProtocol(None), ClientCircuit())
cc.package = Mock()
cc.package.psyc = cc.psyc
if link_to is not None:
Resource(link_to, RESOURCE, sc)
cc.psyc.uni = link_to.uni.chain(RESOURCE)
return sc, cc
def _enter(routing, entity, sc):
routing.mrouting_table[entity.uni].add(sc)
state = entity.context_master.add_member(USER1_UNI)
sc.send({'_context': entity.uni}, PSYCPacket({'=': state}).render())
class TestRoutingErrorProtocol(object):
def setup(self):
routing = _setup()
self.root = routing.root
self.user1 = _entity(routing.root, USER1)
self.sc, self.client = _c2s_circuit(routing, link_to=self.user1)
def test_unknown_target_error(self):
rer = RoutingErrorRoot(self.root.package)
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
rer.send_unknown_target_error(RESOURCE1_UNI, USER2_UNI)
assert self.client.package.method_calls == [
('unknown_target_error', (USER2_UNI,))]
def test_relay_unknown_target_error1(self):
rer = RoutingErrorRoot(self.root.package)
self.user1.add_handler(RoutingErrorRelaying(self.user1.package))
rer.send_unknown_target_error(USER1_UNI, USER2_UNI)
assert self.user1.package.method_calls == [
('unknown_target_error', (USER2_UNI,))]
def test_relay_unknown_target_error2(self):
rer = RoutingErrorRelaying(self.root.package)
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
rer.relay_unknown_target_error(RESOURCE1_UNI, USER2_UNI)
assert self.client.package.method_calls == [
('unknown_target_error', (USER2_UNI,))]
def test_delivery_failed_error(self):
rer = RoutingErrorRoot(self.root.package)
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
rer.send_delivery_failed_error(RESOURCE1_UNI, USER2_UNI, ERROR)
assert self.client.package.method_calls == [
('delivery_failed_error', (USER2_UNI, ERROR))]
def test_relay_delivery_failed_error1(self):
rer = RoutingErrorRoot(self.root.package)
self.user1.add_handler(RoutingErrorRelaying(self.user1.package))
rer.send_delivery_failed_error(USER1_UNI, USER2_UNI, ERROR)
assert self.user1.package.method_calls == [
('delivery_failed_error', (USER2_UNI, ERROR))]
def test_relay_delivery_failed_error2(self):
rer = RoutingErrorRelaying(self.root.package)
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
rer.relay_delivery_failed_error(RESOURCE1_UNI, USER2_UNI, ERROR)
assert self.client.package.method_calls == [
('delivery_failed_error', (USER2_UNI, ERROR))]
class TestLinkingProtocol(object):
def setup(self):
routing = _setup()
self.user1 = _entity(routing.root, USER1)
self.sc, self.client = _c2s_circuit(routing)
def test_linking(self):
lc = LinkingClient(self.client.package)
self.user1.add_handler(LinkingServer(self.user1.package))
pw = PlaceHolder()
lc.link(USER1_UNI, RESOURCE, PASSWORD)
assert self.user1.package.method_calls == [
('authenticate', (pw, self.sc, RESOURCE))]
assert pw.obj[0] == 'hmac'
assert pw.obj[1] == hmac.new(PASSWORD, pw.obj[2], sha256).digest()
def test_incorrect_password(self):
lc = LinkingClient(self.client.package)
self.user1.add_handler(LinkingServer(self.user1.package))
self.user1.package.authenticate.side_effect = AuthenticationError('xy')
with assert_raises(AuthenticationError) as cm:
lc.link(USER1_UNI, RESOURCE, 'pw')
assert cm.exception.args == ('xy',)
class TestMessagingProtocol(object):
def setup(self):
routing = _setup()
self.user1 = _entity(routing.root, USER1)
self.user2 = _entity(routing.root, USER2)
sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1)
sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2)
def test_private_message(self):
msg1 = Messaging(self.client1.package)
self.client2.psyc.add_handler(Messaging(self.client2.package))
msg1.send_private_message(RESOURCE2_UNI, MESSAGE)
assert self.client2.package.method_calls == [
('private_message', (RESOURCE1_UNI, MESSAGE))]
def test_message_relaying1(self):
msg1 = Messaging(self.client1.package)
self.user1.add_handler(MessageRelaying(self.user1.package))
msg1.send_private_message(USER2_UNI, MESSAGE, relay=USER1_UNI)
assert self.user1.package.method_calls == [
('private_message_relay', (RESOURCE1_UNI, USER2_UNI, MESSAGE))]
def test_message_relaying2(self):
msg_relaying1 = MessageRelaying(self.user1.package)
self.user2.add_handler(MessageRelaying(self.user2.package))
msg_relaying1.send_private_message(USER2_UNI, MESSAGE)
assert self.user2.package.method_calls == [
('private_message', (USER1_UNI, MESSAGE))]
def test_message_relaying3(self):
msg_relaying2 = MessageRelaying(self.user2.package)
self.client2.psyc.add_handler(Messaging(self.client2.package))
msg_relaying2.relay_private_message(USER1_UNI, RESOURCE2_UNI, MESSAGE)
assert self.client2.package.method_calls == [
('private_message', (USER1_UNI, MESSAGE))]
class TestConferencingProtocol(object):
def setup(self):
routing = self.routing = _setup()
self.place = _entity(routing.root, PLACE)
self.user1 = _entity(routing.root, USER1)
self.user2 = _entity(routing.root, USER2)
self.sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1)
self.sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2)
_enter(routing, self.place, self.sc1)
def test_cast_member(self):
con_server = ConferencingServer(self.place.package)
self.client1.add_pvar_handler(ConferencingClient(self.client1.package))
self.client2.add_pvar_handler(ConferencingClient(self.client2.package))
con_server.cast_member_entered(USER1_UNI, USER1_NICK)
assert self.client1.package.method_calls == [
('member_entered', (PLACE_UNI, USER1_UNI, USER1_NICK))]
_enter(self.routing, self.place, self.sc2)
assert self.client2.package.method_calls == [
('member_entered', (PLACE_UNI, USER1_UNI, USER1_NICK))]
self.client1.package.reset_mock()
con_server.cast_member_left(USER1_UNI)
assert self.client1.package.method_calls == [
('member_left', (PLACE_UNI, USER1_UNI))]
def test_public_message1(self):
con_client = ConferencingClient(self.client1.package)
self.place.add_handler(ConferencingServer(self.place.package))
con_client.send_public_message(PLACE_UNI, MESSAGE)
assert self.place.package.method_calls == [
('public_message', (RESOURCE1_UNI, MESSAGE))]
def test_public_message2(self):
con_server = ConferencingServer(self.place.package)
self.client1.psyc.add_handler(ConferencingClient(self.client1.package))
self.client2.psyc.add_handler(ConferencingClient(self.client2.package))
con_server.cast_public_message(USER1_UNI, MESSAGE)
assert self.client1.package.method_calls == [
('public_message', (PLACE_UNI, USER1_UNI, MESSAGE))]
class TestContextProtocol(object):
def setup(self):
routing = _setup()
self.place = _entity(routing.root, PLACE)
self.user1 = _entity(routing.root, USER1)
def test_enter(self):
cs = ContextSlave(self.user1.package)
self.place.add_handler(ContextMaster(self.place.package))
self.place.package.enter_request.return_value = VARIABLES
state = cs.enter(PLACE_UNI)
assert state == VARIABLES
assert self.place.package.method_calls == [
('enter_request', (USER1_UNI,))]
def test_enter_denied(self):
cs = ContextSlave(self.user1.package)
self.place.add_handler(ContextMaster(self.place.package))
self.place.package.enter_request.side_effect = EntryDeniedError('xy')
with assert_raises(EntryDeniedError) as cm:
cs.enter(PLACE_UNI)
assert cm.exception.args == ('xy',)
def test_leave(self):
cs = ContextSlave(self.user1.package)
self.place.add_handler(ContextMaster(self.place.package))
cs.leave(PLACE_UNI)
assert self.place.package.method_calls == [
('leave_context', (USER1_UNI,))]
class TestClientInterface(object):
def setup(self):
routing = self.routing = _setup()
self.user1 = _entity(routing.root, USER1)
_, self.client = _c2s_circuit(routing, link_to=self.user1)
self.client.package.uni = USER1_UNI
self.ci_client = ClientInterfaceClient(self.client.package)
self.user1.add_handler(ClientInterfaceServer(self.user1.package))
def test_unauthorized(self):
user2 = _entity(self.routing.root, USER2)
_, client2 = _c2s_circuit(self.routing, link_to=user2)
client2.package.uni = USER1_UNI
ci_client2 = ClientInterfaceClient(client2.package)
assert_raises(UnauthorizedError, ci_client2.subscribe, PLACE_UNI)
assert self.user1.package.method_calls == []
def test_unknown_target_error(self):
error = UnknownTargetError('xy')
self.user1.package.client_subscribe.side_effect = error
with assert_raises(UnknownTargetError) as cm:
self.ci_client.subscribe(PLACE_UNI)
assert cm.exception.args == ('xy',)
def test_delivery_failed_error(self):
error = DeliveryFailedError('x', 'y')
self.user1.package.client_subscribe.side_effect = error
with assert_raises(DeliveryFailedError) as cm:
self.ci_client.subscribe(PLACE_UNI)
assert cm.exception.args == ('x', 'y')
def test_subscribe(self):
self.ci_client.subscribe(PLACE_UNI)
assert self.user1.package.method_calls == [
('client_subscribe', (PLACE_UNI, RESOURCE1_UNI))]
def test_unsubscribe(self):
self.ci_client.unsubscribe(PLACE_UNI)
assert self.user1.package.method_calls == [
('client_unsubscribe', (PLACE_UNI, RESOURCE1_UNI))]
def test_presence(self):
self.ci_client.cast_presence(ONLINE)
assert self.user1.package.method_calls == [
('client_presence', (ONLINE,))]
def test_add_friend(self):
self.ci_client.add_friend(USER2_UNI)
assert self.user1.package.method_calls == [
('client_add_friend', (USER2_UNI,))]
def test_remove_friend(self):
self.ci_client.remove_friend(USER2_UNI)
assert self.user1.package.method_calls == [
('client_remove_friend', (USER2_UNI,))]
class TestFriendshipProtocol(object):
def setup(self):
routing = _setup()
self.user1 = _entity(routing.root, USER1)
self.user2 = _entity(routing.root, USER2)
sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1)
sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2)
routing.mrouting_table[USER1_UNI] = [sc2]
def test_establish_pending(self):
friendship_server1 = FriendshipServer(self.user1.package)
self.user2.add_handler(FriendshipServer(self.user2.package))
self.user2.package.friendship_request.return_value = PENDING
state = friendship_server1.establish(USER2_UNI)
assert state == PENDING
assert self.user2.package.method_calls == [
('friendship_request', (USER1_UNI,))]
def test_establish_established(self):
friendship_server1 = FriendshipServer(self.user1.package)
self.user2.add_handler(FriendshipServer(self.user2.package))
self.user2.package.friendship_request.return_value = ESTABLISHED
state = friendship_server1.establish(USER2_UNI)
assert state == ESTABLISHED
assert self.user2.package.method_calls == [
('friendship_request', (USER1_UNI,))]
def test_remove(self):
friendship_server1 = FriendshipServer(self.user1.package)
self.user2.add_handler(FriendshipServer(self.user2.package))
friendship_server1.remove(USER2_UNI)
assert self.user2.package.method_calls == [
('friendship_cancel', (USER1_UNI,))]
def test_friendships(self):
friendship_server = FriendshipServer(self.user1.package)
self.client1.psyc.add_handler(FriendshipClient(self.client1.package))
friendship_server.send_friendships(RESOURCE1_UNI, {
USER1_UNI: {'state': ESTABLISHED},
USER2_UNI: {'state': OFFERED}})
friendship_server.send_friendships(RESOURCE1_UNI, {})
assert self.client1.package.method_calls == [
('friendship', (USER1_UNI, ESTABLISHED)),
('friendship', (USER2_UNI, OFFERED))]
def test_updated_friendship(self):
friendship_server = FriendshipServer(self.user1.package)
self.client1.psyc.add_handler(FriendshipClient(self.client1.package))
friendship_server.send_updated_friendship(RESOURCE1_UNI, USER1_UNI,
{'state': 'established'})
assert self.client1.package.method_calls == [
('friendship', (USER1_UNI, ESTABLISHED))]
def test_removed_friendship(self):
friendship_server = FriendshipServer(self.user1.package)
self.client1.psyc.add_handler(FriendshipClient(self.client1.package))
friendship_server.send_removed_friendship(RESOURCE1_UNI, USER1_UNI)
assert self.client1.package.method_calls == [
('friendship_removed', (USER1_UNI,))]
def test_presence(self):
friendship_server = FriendshipServer(self.user1.package)
self.client2.add_pvar_handler(FriendshipClient(self.client2.package))
friendship_server.cast_presence(ONLINE)
assert self.client2.package.method_calls == [
('presence', (USER1_UNI, ONLINE))]

View File

@ -0,0 +1,50 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from greenlet import greenlet
from nose.tools import assert_raises
from tests.helpers import mockify, mockified, check_success
from pypsyc.server.db import Database
class TestDatabase:
def test_sync(self):
db = Database(':memory:')
db.execute('CREATE TABLE test(test TEXT)')
db.execute("INSERT INTO test VALUES ('test')")
result = db.fetch('SELECT test FROM test')
assert result == [('test',)]
assert isinstance(result[0][0], str)
db.stop()
@check_success
@mockified('pypsyc.server.db',['sqlite3', 'Thread', 'reactor'])
def test_async(self, sqlite3, Thread, reactor):
db = Database('')
mockify(db, ['_execute', '_fetch'])
e = Exception()
db._execute.side_effect = e
def f():
assert_raises(Exception, db.execute)
assert db.fetch() == db._fetch.return_value
self.success = True
gl = greenlet(f)
gl.switch()
# emulate reactor calling this methods
gl.throw(e)
gl.switch(db._fetch.return_value)
db.stop()
assert db.thread.method_calls == [('start',), ('join',)]
db.run_async()
assert sqlite3.method_calls == [('connect', ('',))]
assert reactor.method_calls == [
('callFromThread', (gl.throw, e)),
('callFromThread', (gl.switch, db._fetch.return_value))]

View File

@ -0,0 +1,162 @@
"""
::copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock
from tests.constants import (SERVER1, SERVER2, SERVER1_UNI, SERVER2_UNI, USER1, USER2,
USER1_UNI, USER2_UNI, RESOURCE, PLACE_UNI, VARIABLES)
from tests.helpers import mockified, rendered
from pypsyc.core.psyc import PSYCPacket
from pypsyc.server import Entity
from pypsyc.server.multicast import ContextSlave
from pypsyc.server.person import Resource
EXTERN_USER_UNI = SERVER2_UNI.chain(USER1)
SET_PACKET = PSYCPacket({'=': VARIABLES})
ADD_PACKET = PSYCPacket({'+': VARIABLES})
REMOVE_PACKET = PSYCPacket({'-': VARIABLES})
class TestContextMaster(object):
def setup(self):
self.server = Mock()
self.server.hostname = SERVER1
self.server.routing.mrouting_table = self.mrouting_table = {}
root = Entity(server=self.server)
self.user1 = Entity(root, USER1)
self.user1.castmsg = Mock()
user2 = Entity(root, USER2)
Resource(user2, RESOURCE, Mock())
def test_extern_member(self):
circuit = Mock()
self.server.routing.srouting_table = {SERVER2: circuit}
self.user1.context_master.add_member(EXTERN_USER_UNI)
assert self.mrouting_table[USER1_UNI] == set([circuit])
self.user1.context_master.remove_member(EXTERN_USER_UNI)
assert self.mrouting_table[USER1_UNI] == set()
def test_set_persistent(self):
self.user1.context_master.state_set(**VARIABLES)
assert self.user1.castmsg.call_args_list == [((SET_PACKET,),),]
state = self.user1.context_master.add_member(USER2_UNI)
assert state == VARIABLES
def test_persistent_list(self):
self.user1.context_master.state_add(**VARIABLES)
assert self.user1.castmsg.call_args_list == [((ADD_PACKET,),)]
self.user1.castmsg.reset_mock()
state = self.user1.context_master.add_member(USER2_UNI)
assert state == VARIABLES
self.user1.context_master.state_remove(**VARIABLES)
assert self.user1.castmsg.call_args_list == [((REMOVE_PACKET,),)]
state = self.user1.context_master.add_member(USER2_UNI)
assert state == {}
def test_remove_inheritance(self):
self.user1.context_master.state_add(_a='a', _a_b='b')
self.user1.context_master.state_remove(_a='a')
state = self.user1.context_master.add_member(USER2_UNI)
assert state == {}
RESOURCE1 = '*resource1'
RESOURCE2 = '*resource2'
USER1_RESOURCE1_UNI = USER1_UNI.chain(RESOURCE1)
USER1_RESOURCE2_UNI = USER1_UNI.chain(RESOURCE2)
class TestContextSlave(object):
@mockified('pypsyc.server.multicast', ['ContextSlaveProtocol'])
def setup(self, ContextSlaveProtocol):
person = Mock()
self.entity = person.entity
self.entity._root.uni = SERVER1_UNI
self.entity.uni = USER1_UNI
self.entity.server.routing.mrouting_table = self.mrouting_table = {}
self.entity.children = {}
self.entity.children[RESOURCE1] = self.resource1 = Mock()
self.entity.children[RESOURCE2] = self.resource2 = Mock()
self.protocol = ContextSlaveProtocol.return_value
self.protocol.enter.return_value = VARIABLES
self.context_slave = ContextSlave(person)
assert ContextSlaveProtocol.call_args_list == [((person,),)]
def test_enter_leave(self):
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI)
assert self.protocol.method_calls == [('enter', (PLACE_UNI,))]
assert self.mrouting_table[PLACE_UNI] == set([self.resource1.circuit])
assert self.resource1.circuit.method_calls == [
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)))]
self.protocol.reset_mock()
self.context_slave.leave(PLACE_UNI, USER1_RESOURCE1_UNI)
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
assert self.mrouting_table[PLACE_UNI] == set()
def test_enter_leave_all_resources(self):
self.context_slave.enter(PLACE_UNI)
assert self.protocol.method_calls == [('enter', (PLACE_UNI,))]
assert self.mrouting_table[PLACE_UNI] == set([self.resource1.circuit,
self.resource2.circuit])
assert self.resource1.circuit.method_calls == [
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)),)]
assert self.resource2.circuit.method_calls == [
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)),)]
self.protocol.reset_mock()
self.context_slave.leave(PLACE_UNI)
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
assert self.mrouting_table[PLACE_UNI] == set()
def test_enter_leave_two_resources(self):
extern_circuit = Mock()
self.mrouting_table[PLACE_UNI] = set([extern_circuit])
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI)
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE2_UNI)
assert self.protocol.method_calls == [('enter', (PLACE_UNI,))]
assert self.mrouting_table[PLACE_UNI] == set([extern_circuit,
self.resource1.circuit,
self.resource2.circuit])
assert self.resource1.circuit.method_calls == [
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)))]
self.protocol.reset_mock()
self.context_slave.leave(PLACE_UNI, USER1_RESOURCE1_UNI)
assert self.protocol.method_calls == []
self.context_slave.leave(PLACE_UNI, USER1_RESOURCE2_UNI)
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
assert self.mrouting_table[PLACE_UNI] == set([extern_circuit])
def test_enter_leave_extern(self):
self.context_slave.enter(EXTERN_USER_UNI, USER1_RESOURCE1_UNI)
assert self.protocol.method_calls == [('enter', (EXTERN_USER_UNI,))]
assert self.mrouting_table[EXTERN_USER_UNI] == \
set([self.resource1.circuit])
assert self.resource1.circuit.method_calls == [
('send', ({'_context':EXTERN_USER_UNI}, rendered(SET_PACKET)))]
self.protocol.reset_mock()
self.context_slave.leave(EXTERN_USER_UNI, USER1_RESOURCE1_UNI)
assert self.protocol.method_calls == [('leave', (EXTERN_USER_UNI,))]
assert EXTERN_USER_UNI not in self.mrouting_table
def test_leave_all(self):
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI)
self.protocol.reset_mock()
self.context_slave.leave_all()
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
assert self.mrouting_table[PLACE_UNI] == set()
def test_not_leave(self):
self.context_slave.leave(PLACE_UNI)
assert self.protocol.method_calls == []

View File

@ -0,0 +1,268 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import hmac
from hashlib import sha256
from mock import Mock
from nose.tools import assert_raises
from tests.constants import (SERVER1, USER1, USER1_UNI, USER2_UNI, RESOURCE,
RESOURCE1_UNI, RESOURCE2_UNI, PLACE_UNI, VARIABLES, PASSWORD, MESSAGE,
ONLINE, ERROR)
from tests.helpers import mockified
from pypsyc.protocol import (AuthenticationError, FriendshipPendingError,
FriendshipEstablishedError, EntryDeniedError)
from pypsyc.server import Entity
from pypsyc.server.db import Database
from pypsyc.server.person import Person
class TestPerson(object):
def setup(self):
self.server = Mock()
self.server.hostname = SERVER1
self.server.database = Database(':memory:')
root = Entity(server=self.server)
self.entity = Entity(root, USER1)
@mockified('pypsyc.server.person', ['RoutingErrorRelaying'])
def test_unknown_target_error(self, RoutingErrorRelaying):
RESOURCE1 = USER1_UNI.chain('*resource1')
RESOURCE2 = USER1_UNI.chain('*resource2')
Entity(self.entity, '*resource1')
Entity(self.entity, '*resource2')
routing_error_relaying = RoutingErrorRelaying.return_value
person = Person(self.entity)
assert RoutingErrorRelaying.call_args_list == [((person,),)]
person.unknown_target_error(USER2_UNI)
assert routing_error_relaying.method_calls == [
('relay_unknown_target_error', (RESOURCE1, USER2_UNI)),
('relay_unknown_target_error', (RESOURCE2, USER2_UNI))]
@mockified('pypsyc.server.person', ['RoutingErrorRelaying'])
def test_unknown_target_error(self, RoutingErrorRelaying):
RESOURCE1 = USER1_UNI.chain('*resource1')
RESOURCE2 = USER1_UNI.chain('*resource2')
Entity(self.entity, '*resource1')
Entity(self.entity, '*resource2')
routing_error_relaying = RoutingErrorRelaying.return_value
person = Person(self.entity)
assert RoutingErrorRelaying.call_args_list == [((person,),)]
person.delivery_failed_error(USER2_UNI, ERROR)
assert routing_error_relaying.method_calls == [
('relay_delivery_failed_error', (RESOURCE1, USER2_UNI, ERROR)),
('relay_delivery_failed_error', (RESOURCE2, USER2_UNI, ERROR))]
@mockified('pypsyc.server.person', ['LinkingServer', 'FriendshipProtocol',
'ContextSlave'])
def test_authenticate(self, LinkingServer, FriendshipProtocol,
ContextSlave):
NONCE = 'random_nonce'
DIGEST = hmac.new(PASSWORD, NONCE, sha256).digest()
circuit = Mock()
circuit.allowed_sources = []
friendship_protocol = FriendshipProtocol.return_value
context_slave = ContextSlave.return_value
person = Person(self.entity)
person.register(PASSWORD)
assert LinkingServer.call_args_list == [((person,),)]
assert_raises(AuthenticationError,
person.authenticate, ('hmac', 'wrong', NONCE), None, RESOURCE)
sql = "INSERT INTO friendships VALUES(?, ?, 'established')"
self.server.database.execute(sql, USER1_UNI, USER2_UNI)
person.authenticate(('hmac', DIGEST, NONCE), circuit, RESOURCE)
assert self.entity.children[RESOURCE].circuit == circuit
assert circuit.allowed_sources == [RESOURCE1_UNI]
friendships = {USER2_UNI: {'state': 'established'}}
assert friendship_protocol.method_calls == [
('cast_presence', (7,)),
('send_friendships', (RESOURCE1_UNI, friendships))]
assert context_slave.method_calls == [
('enter', (USER2_UNI, RESOURCE1_UNI))]
friendship_protocol.reset_mock()
context_slave.reset_mock()
person.unlink(RESOURCE)
assert self.entity.children == {}
assert friendship_protocol.method_calls == [('cast_presence', (1,))]
assert context_slave.method_calls == [('leave_all',)]
@mockified('pypsyc.server.person', ['MessageRelaying'])
def test_message_relay(self, MessageRelaying):
message_relaying = MessageRelaying.return_value
person = Person(self.entity)
assert MessageRelaying.call_args_list == [((person,),)]
person.private_message_relay(RESOURCE1_UNI, USER2_UNI, MESSAGE)
person.private_message_relay(RESOURCE2_UNI, USER2_UNI, MESSAGE)
assert message_relaying.method_calls == [
('send_private_message', (USER2_UNI, MESSAGE))]
@mockified('pypsyc.server.person', ['MessageRelaying'])
def test_message(self, MessageRelaying):
RESOURCE1 = USER1_UNI.chain('*resource1')
RESOURCE2 = USER1_UNI.chain('*resource2')
Entity(self.entity, '*resource1')
Entity(self.entity, '*resource2')
message_relaying = MessageRelaying.return_value
person = Person(self.entity)
person.private_message(USER2_UNI, MESSAGE)
assert message_relaying.method_calls == [
('relay_private_message', (USER2_UNI, RESOURCE1, MESSAGE)),
('relay_private_message', (USER2_UNI, RESOURCE2, MESSAGE))]
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
def test_outgoing_friendship(self, FriendshipProtocol, ContextSlave):
friendship_protocol = FriendshipProtocol.return_value
context_slave = ContextSlave.return_value
person = Person(self.entity)
person.register('')
Entity(self.entity, RESOURCE)
person.client_add_friend(USER2_UNI)
fs = {'state': 'pending'}
assert friendship_protocol.method_calls == [
('establish', (USER2_UNI,)),
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
friendship_protocol.reset_mock()
assert_raises(FriendshipPendingError, person.client_add_friend,
USER2_UNI)
state = person.friendship_request(USER2_UNI)
assert state == 'established'
state = person.friendship_request(USER2_UNI)
assert state == 'established'
fs = {'state': 'established'}
assert friendship_protocol.method_calls == [
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
assert context_slave.method_calls == [('enter', (USER2_UNI,))]
assert_raises(FriendshipEstablishedError, person.client_add_friend,
USER2_UNI)
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
def test_incoming_friendship(self, FriendshipProtocol, ContextSlave):
friendship_protocol = FriendshipProtocol.return_value
context_slave = ContextSlave.return_value
person = Person(self.entity)
person.register('')
Entity(self.entity, RESOURCE)
state = person.friendship_request(USER2_UNI)
assert state == 'pending'
state = person.friendship_request(USER2_UNI)
assert state == 'pending'
fs = {'state': 'offered'}
assert friendship_protocol.method_calls == [
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
friendship_protocol.reset_mock()
person.client_add_friend(USER2_UNI)
fs = {'state': 'established'}
assert friendship_protocol.method_calls == [
('establish', (USER2_UNI,)),
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
assert context_slave.method_calls == [('enter', (USER2_UNI,))]
assert_raises(FriendshipEstablishedError, person.client_add_friend,
USER2_UNI)
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
def test_cancel_friendship(self, FriendshipProtocol, ContextSlave):
friendship_protocol = FriendshipProtocol.return_value
context_slave = ContextSlave.return_value
person = Person(self.entity)
person.register('')
Entity(self.entity, RESOURCE)
person.friendship_cancel(USER2_UNI)
assert context_slave.method_calls == [('leave', (USER2_UNI,))]
assert friendship_protocol.method_calls == [
('send_removed_friendship', (RESOURCE1_UNI, USER2_UNI))]
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
def test_remove_friend(self, FriendshipProtocol, ContextSlave):
friendship_protocol = FriendshipProtocol.return_value
context_slave = ContextSlave.return_value
person = Person(self.entity)
person.register('')
Entity(self.entity, RESOURCE)
person.client_remove_friend(USER2_UNI)
assert context_slave.method_calls == [('leave', (USER2_UNI,))]
assert friendship_protocol.method_calls == [
('remove', (USER2_UNI,)),
('send_removed_friendship', (RESOURCE1_UNI, USER2_UNI))]
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave',
'ContextMasterProtocol'])
def test_enter_context(self, FriendshipProtocol, ContextSlave,
ContextMasterProtocol):
cm = self.entity.context_master = Mock()
cm.add_member.return_value = VARIABLES
person = Person(self.entity)
person.register('')
person.friendship_request(USER2_UNI)
person.client_add_friend(USER2_UNI)
assert ContextMasterProtocol.call_args_list == [((person,),)]
state = person.enter_request(USER2_UNI)
assert state == VARIABLES
assert cm.method_calls == [('add_member', (USER2_UNI,))]
def test_enter_context_entry_denied(self):
person = Person(self.entity)
person.register('')
assert_raises(EntryDeniedError, person.enter_request, USER2_UNI)
def test_leave_context(self):
cm = self.entity.context_master = Mock()
person = Person(self.entity)
person.leave_context(USER2_UNI)
assert cm.method_calls == [('remove_member', (USER2_UNI,))]
@mockified('pypsyc.server.person', ['FriendshipProtocol'])
def test_client_presence(self, FriendshipProtocol):
friendship_protocol = FriendshipProtocol.return_value
person = Person(self.entity)
assert FriendshipProtocol.call_args_list == [((person,),)]
person.client_presence(ONLINE)
assert friendship_protocol.method_calls == [
('cast_presence', (ONLINE,))]
@mockified('pypsyc.server.person', ['ContextSlave',
'ClientInterfaceProtocol'])
def test_client_subscribe(self, ContextSlave, ClientInterfaceProtocol):
context_slave = ContextSlave.return_value
person = Person(self.entity)
assert ContextSlave.call_args_list == [((person,),)]
assert ClientInterfaceProtocol.call_args_list == [((person,),)]
person.client_subscribe(PLACE_UNI, RESOURCE1_UNI)
assert context_slave.method_calls == [
('enter', (PLACE_UNI, RESOURCE1_UNI))]
@mockified('pypsyc.server.person', ['ContextSlave'])
def test_client_unsubscribe(self, ContextSlave):
context_slave = ContextSlave.return_value
person = Person(self.entity)
person.client_unsubscribe(PLACE_UNI, RESOURCE1_UNI)
assert context_slave.method_calls == [
('leave', (PLACE_UNI, RESOURCE1_UNI))]

View File

@ -0,0 +1,68 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock
from tests.constants import (SERVER1, USER1_UNI, USER2_UNI, USER1_NICK,
RESOURCE1_UNI, RESOURCE2_UNI, PLACE, VARIABLES, MESSAGE)
from tests.helpers import mockified
from pypsyc.server import Entity
from pypsyc.server.place import Place
class TestPlace(object):
def setup(self):
self.server = Mock()
self.server.hostname = SERVER1
root = Entity(server=self.server)
self.entity = Entity(root, PLACE)
self.entity.context_master = Mock()
@mockified('pypsyc.server.place', ['ContextProtocol',
'ConferencingProtocol'])
def test_subscription(self, ContextProtocol, ConferencingProtocol):
conferencing_protocol = ConferencingProtocol.return_value
self.entity.context_master.add_member.return_value = VARIABLES
place = Place(self.entity)
assert ContextProtocol.call_args_list == [((place,),)]
state = place.enter_request(USER1_UNI)
assert state == VARIABLES
assert conferencing_protocol.method_calls == [
('cast_member_entered', (USER1_UNI, USER1_NICK))]
assert self.entity.context_master.method_calls == [
('add_member', (USER1_UNI,))]
@mockified('pypsyc.server.place', ['ConferencingProtocol'])
def test_leave(self, ConferencingProtocol):
conferencing_protocol = ConferencingProtocol.return_value
place = Place(self.entity)
place.enter_request(USER1_UNI)
self.entity.context_master.reset_mock()
conferencing_protocol.reset_mock()
place.leave_context(USER1_UNI)
assert self.entity.context_master.method_calls == [
('remove_member', (USER1_UNI,))]
assert conferencing_protocol.method_calls == [
('cast_member_left', (USER1_UNI,))]
@mockified('pypsyc.server.place', ['ConferencingProtocol'])
def test_public_message(self, ConferencingProtocol):
conferencing_protocol = ConferencingProtocol.return_value
place = Place(self.entity)
assert ConferencingProtocol.call_args_list == [((place,),)]
place.enter_request(USER1_UNI)
place.enter_request(USER2_UNI)
place.leave_context(USER2_UNI)
conferencing_protocol.reset_mock()
place.public_message(RESOURCE2_UNI, MESSAGE)
place.public_message(RESOURCE1_UNI, MESSAGE)
assert conferencing_protocol.method_calls == [
('cast_public_message', (USER1_UNI, MESSAGE))]

View File

@ -0,0 +1,21 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock
from tests.constants import SERVER1
from pypsyc.server import Entity
from pypsyc.server.root import Root
class TestRoot(object):
def setup(self):
server = Mock()
server.hostname = SERVER1
root_entity = Entity(server=server)
self.root = Root(root_entity)
def test_root(self):
pass

View File

@ -0,0 +1,353 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock
from nose.tools import assert_raises
from tests.constants import (SERVER1, SERVER2, SERVER1_UNI, SERVER2_UNI,
SERVER3_UNI, USER1, USER2, USER1_UNI, USER2_UNI, RESOURCE, RESOURCE1_UNI,
RESOURCE2_UNI, PLACE_UNI, INTERFACE, IP, PORT, CONTENT)
from tests.helpers import (inited_header, connect_circuits, mockified,
AsyncMethod, PlaceHolder)
from pypsyc.core.mmp import Uni
from pypsyc.protocol import Error
from pypsyc.server.routing import (InvalidTargetError, InvalidSourceError,
ServerCircuit, _TreeNode, Routing)
from pypsyc.util import DNSError
class TestServerCircuit(object):
def setup(self):
self.sc = ServerCircuit()
self.sc.factory = self.routing = Mock()
self.sc.psyc = Mock()
self.sc.allowed_sources.append(SERVER2_UNI)
def _send(self, header):
self.sc.packet_received(inited_header(header), CONTENT)
def test_withtarget(self):
HEADER1 = {'_target': SERVER1_UNI}
HEADER2 = {'_target': SERVER1_UNI, '_source': SERVER2_UNI}
HEADER3 = {'_target': SERVER1_UNI, '_source': SERVER3_UNI}
self._send(HEADER1)
assert self.routing.method_calls == [
('route_singlecast', (HEADER1, CONTENT))]
self._send(HEADER2)
assert self.routing.method_calls == [
('route_singlecast', (HEADER1, CONTENT)),
('route_singlecast', (HEADER2, CONTENT))]
self._send(HEADER3)
assert self.routing.method_calls == [
('route_singlecast', (HEADER1, CONTENT)),
('route_singlecast', (HEADER2, CONTENT))]
def test_withouttarget(self):
HEADER1 = {}
HEADER2 = {'_source': SERVER2_UNI}
HEADER3 = {'_source': SERVER3_UNI}
self._send(HEADER1)
assert self.routing.method_calls == []
assert self.sc.psyc.method_calls == [
('handle_packet', (HEADER1, CONTENT))]
self._send(HEADER2)
assert self.routing.method_calls == []
assert self.sc.psyc.method_calls == [
('handle_packet', (HEADER1, CONTENT)),
('handle_packet', (HEADER2, CONTENT))]
self._send(HEADER3)
assert self.routing.method_calls == []
assert self.sc.psyc.method_calls == [
('handle_packet', (HEADER1, CONTENT)),
('handle_packet', (HEADER2, CONTENT))]
def test_context(self):
HEADER1 = {'_context': PLACE_UNI}
HEADER2 = {'_context': PLACE_UNI, '_target': USER1_UNI}
self._send(HEADER1)
assert self.routing.method_calls == [
('route_multicast', (HEADER1, CONTENT))]
self._send(HEADER2)
assert self.routing.method_calls == [
('route_multicast', (HEADER1, CONTENT)),
('route_singlecast', (HEADER2, CONTENT))]
def test_context_withsource(self):
HEADER1 = {'_context': PLACE_UNI, '_source': USER1_UNI}
HEADER2 = {'_context': PLACE_UNI, '_source': USER2_UNI,
'_target': USER1_UNI}
assert_raises(NotImplementedError, self._send, HEADER1)
assert self.routing.method_calls == []
assert_raises(NotImplementedError, self._send, HEADER2)
assert self.routing.method_calls == []
def test_verification(self):
sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit())
sc2.factory = Mock()
sc1.request_verification(SERVER1_UNI, SERVER2_UNI)
assert sc2.factory.method_calls == [
('verify_address', (sc2, SERVER1_UNI, SERVER2_UNI))]
assert type(sc2.factory.method_calls[0][1][1]) is Uni
assert sc1.allowed_sources == [SERVER2_UNI]
assert sc2.allowed_sources == [SERVER1_UNI]
assert type(sc2.allowed_sources[0]) is Uni
def test_verification_invalid_source(self):
sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit())
sc2.factory = Mock()
sc2.factory.verify_address.side_effect = InvalidSourceError
assert_raises(InvalidSourceError, sc1.request_verification,
SERVER1_UNI, SERVER2_UNI)
assert sc1.allowed_sources == []
assert sc2.allowed_sources == []
def test_verification_invalid_target(self):
sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit())
sc2.factory = Mock()
sc2.factory.verify_address.side_effect = InvalidTargetError
assert_raises(InvalidTargetError, sc1.request_verification,
SERVER1_UNI, SERVER2_UNI)
assert sc1.allowed_sources == []
assert sc2.allowed_sources == []
def test_connection_lost(self):
root = self.routing.root = _TreeNode()
person = Mock()
person_entity = _TreeNode(root, USER1)
person_entity.packages = {'person': person}
self.sc.allowed_sources.append(RESOURCE1_UNI)
self.routing.srouting_table = {SERVER2: Mock()}
self.sc.connectionLost(None)
assert self.routing.srouting_table == {}
assert person.method_calls == [('unlink', (RESOURCE,))]
def test_treenode():
root = _TreeNode()
assert root._root == root
n1 = _TreeNode(root, 'n1')
assert root.children == {'n1': n1}
assert n1._parent == root
assert n1._root == root
n2 = _TreeNode(n1, 'n2')
assert n1.children == {'n2': n2}
assert n2._parent == n1
assert n2._root == root
class StubEntity(_TreeNode):
def __init__(self, *args, **kwds):
_TreeNode.__init__(self, *args, **kwds)
self.headers = []
def handle_packet(self, header, contents):
self.headers.append(header)
EXTERN_HEADER = {'_source': USER1_UNI, '_target': SERVER2_UNI}
class TestRouting(object):
def test_sroute_local(self):
HEADER1 = {'_target': SERVER1_UNI}
HEADER2 = {'_target': USER1_UNI}
HEADER3 = {'_target': RESOURCE2_UNI}
root = StubEntity()
user1 = StubEntity(root, USER1)
user2 = StubEntity(root, USER2)
home = StubEntity(user2, RESOURCE)
routing = Routing(SERVER1, INTERFACE)
routing.init(root)
routing.route_singlecast(inited_header(HEADER1), CONTENT)
routing.route_singlecast(inited_header(HEADER2), CONTENT)
routing.route_singlecast(inited_header(HEADER3), CONTENT)
assert root.headers == [HEADER1]
assert user1.headers == [HEADER2]
assert user2.headers == []
assert home.headers == [HEADER3]
def test_sroute_unkown_target(self):
HEADER1 = inited_header({'_source': USER1_UNI, '_target': USER2_UNI})
HEADER2 = inited_header({'_target': USER2_UNI, '_tag': 'tag'})
KWDS = {'mc': '_error_unknown_target', '_uni': USER2_UNI, 'data': None}
root = StubEntity()
root.sendmsg = Mock()
routing = Routing(SERVER1, INTERFACE)
routing.init(root)
routing.route_singlecast(HEADER1, CONTENT)
routing.route_singlecast(HEADER2, CONTENT)
assert root.sendmsg.call_args_list == [
((USER1_UNI, None, None), KWDS),
((None, None, {'_tag_relay': 'tag'}), KWDS)]
assert root.headers == []
def test_sroute_extern(self):
routing = Routing(SERVER1, INTERFACE)
circuit = routing.srouting_table[SERVER2] = Mock()
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
assert circuit.method_calls == [(('send'), (EXTERN_HEADER, CONTENT))]
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
def test_sroute_extern_queued(self, resolve_hostname, connect):
resolve_hostname.return_value = IP, PORT
connected = connect.side_effect = AsyncMethod()
root = Mock()
root.uni = SERVER1_UNI
routing = Routing(SERVER1, INTERFACE)
routing.init(root)
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
assert connect.call_args_list == [
((IP, PORT, routing), {'bindAddress': (INTERFACE, 0)})]
circuit = Mock()
connected.callback(circuit)
assert circuit.method_calls == [
('request_verification', (SERVER1_UNI, SERVER2_UNI)),
('send', (EXTERN_HEADER, CONTENT)),
('send', (EXTERN_HEADER, CONTENT))]
assert type(circuit.method_calls[0][1][1]) is Uni
assert routing.srouting_table == {SERVER2: circuit}
assert routing.queues == {}
@mockified('pypsyc.server.routing', ['resolve_hostname'])
def test_sroute_extern_resolution_fail(self, resolve_hostname):
KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI}
resolved = resolve_hostname.side_effect = AsyncMethod()
data_ph = KWDS['data'] = PlaceHolder()
root = Mock()
routing = Routing(SERVER1, INTERFACE)
routing.init(root)
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
resolved.errback(DNSError)
assert root.method_calls == [
('sendmsg', (USER1_UNI, None, None), KWDS)] * 2
assert 'resolve' in data_ph.obj
assert routing.queues == {}
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
def test_sroute_extern_connection_fail(self, resolve_hostname, connect):
KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI}
resolve_hostname.return_value = None, None
connected = connect.side_effect = AsyncMethod()
data_ph = KWDS['data'] = PlaceHolder()
root = Mock()
routing = Routing(SERVER1, INTERFACE)
routing.init(root)
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
connected.errback(Exception('error'))
assert root.method_calls == [
('sendmsg', (USER1_UNI, None, None), KWDS)]
assert 'connect' in data_ph.obj
assert routing.queues == {}
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
def test_sroute_extern_verification_fail(self, resolve_hostname, connect):
KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI}
resolve_hostname.return_value = None, None
circuit = connect.return_value
verified = circuit.request_verification.side_effect = AsyncMethod()
data_ph = KWDS['data'] = PlaceHolder()
root = Mock()
routing = Routing(SERVER1, INTERFACE)
routing.init(root)
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
verified.errback(Error)
assert root.method_calls == [
('sendmsg', (USER1_UNI, None, None), KWDS)]
assert 'verify' in data_ph.obj
assert routing.queues == {}
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
def test_sroute_extern_no_source(self, resolve_hostname, connect):
header = inited_header({'_target': SERVER2_UNI})
header.source = Mock()
routing = Routing(SERVER1, INTERFACE)
routing.route_singlecast(header, CONTENT)
header['_source'] = ''
routing.route_singlecast(header, CONTENT)
assert resolve_hostname.call_args_list == []
assert connect.call_args_list == []
assert routing.srouting_table == {}
assert routing.queues == {}
def test_mroute(self):
HEADER = {'_context': PLACE_UNI}
circuit1 = Mock()
circuit2 = Mock()
routing = Routing(SERVER1, INTERFACE)
routing.mrouting_table[PLACE_UNI] = [circuit1, circuit2]
routing.route_multicast(inited_header(HEADER), iter(CONTENT))
assert circuit1.method_calls == [('send', (HEADER, CONTENT))]
assert circuit2.method_calls == [('send', (HEADER, CONTENT))]
@mockified('pypsyc.server.routing', ['reactor'])
def test_listen(self, reactor):
routing = Routing(SERVER1, INTERFACE)
routing.listen(PORT)
assert reactor.method_calls == [
('listenTCP', (PORT, routing), {'interface': INTERFACE})]
def _setup_verification(self):
root = Mock()
root.uni = SERVER1_UNI
routing = Routing(SERVER1, INTERFACE)
routing.init(root)
circuit = Mock()
circuit.transport.client = IP, 35771
return routing, circuit
@mockified('pypsyc.server.routing', ['resolve_hostname'])
def test_verification(self, resolve_hostname):
resolve_hostname.return_value = IP, PORT
routing, circuit = self._setup_verification()
routing.verify_address(circuit, SERVER2_UNI, SERVER1_UNI)
assert resolve_hostname.call_args_list == [((SERVER2,),)]
assert routing.srouting_table == {SERVER2: circuit}
@mockified('pypsyc.server.routing', ['resolve_hostname'])
def test_verification_invalid_target(self, resolve_hostname):
routing, circuit = self._setup_verification()
assert_raises(InvalidTargetError, routing.verify_address,
circuit, SERVER2_UNI, SERVER3_UNI)
assert resolve_hostname.call_args_list == []
assert routing.srouting_table == {}
@mockified('pypsyc.server.routing', ['resolve_hostname'])
def test_verification_invalid_source(self, resolve_hostname):
resolve_hostname.return_value = '10.0.0.2', PORT
routing, circuit = self._setup_verification()
assert_raises(InvalidSourceError, routing.verify_address,
circuit, SERVER2_UNI, SERVER1_UNI)
assert resolve_hostname.call_args_list == [((SERVER2,),)]
assert routing.srouting_table == {}

View File

@ -0,0 +1,134 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from contextlib import contextmanager
from mock import Mock
from nose.tools import assert_raises
from tests.constants import (SERVER1, SERVER1_UNI, USER1, USER1_UNI, PLACE,
INTERFACE, PORT, PASSWORD)
from tests.helpers import mockified, rendered, AsyncMethod
from pypsyc.core.psyc import PSYCPacket
from pypsyc.server import Entity, Server
from pypsyc.server.routing import _TreeNode
class TestEntity(object):
def setup(self):
self.server = Mock()
self.server.hostname = SERVER1
self.entity = Entity(server=self.server)
def test_uni(self):
assert self.entity.uni == SERVER1_UNI
assert Entity(self.entity, USER1).uni == USER1_UNI
def test_castmsg(self):
HEADER = {'_context': self.entity.uni}
PACKET = PSYCPacket(mc='_message_public', data="Hello")
self.entity.castmsg(PACKET)
assert self.server.routing.method_calls == [
('route_multicast', (HEADER, rendered(PACKET)))]
def test_castmsg_kwds(self):
HEADER = {'_context': self.entity.uni}
PACKET = PSYCPacket(mc='_message_public', data="Hello")
self.entity.castmsg(mc='_message_public', data="Hello")
assert self.server.routing.method_calls == [
('route_multicast', (HEADER, rendered(PACKET)))]
def StubEntity(*args, **kwds):
entity = _TreeNode(*args, **kwds)
entity.packages = {}
return entity
class TestServer(object):
@mockified('pypsyc.server', ['Routing', 'Entity', 'run_webif', 'signal'])
def test_server1(self, Routing, Entity, run_webif, signal):
WEBIF_PORT = 8080
routing = Routing.return_value
root = Entity.return_value = StubEntity()
run_webif.side_effect = AsyncMethod()
server = Server(SERVER1, INTERFACE, PORT, WEBIF_PORT, ':memory:')
assert Routing.call_args_list == [((SERVER1, INTERFACE),)]
assert Entity.call_args_list == [({'server': server},)]
assert routing.method_calls == [('init', (root,)), ('listen', (PORT,))]
assert run_webif.call_args_list == [
((server, INTERFACE, WEBIF_PORT, None),)]
assert signal.signal.called
with mockified('pypsyc.server', ['iter_entry_points', 'Entity']) as x:
with self._test_load_package(root, *x) as packages:
assert server.add_package(None, 'package') == packages[0]
assert server.add_place('place') == packages[1]
assert server.add_package(PLACE, 'package') == packages[2]
assert server.add_package(USER1, 'package') == packages[3]
person = server.register_person('user1', PASSWORD)
assert person == packages[4]
assert person.method_calls == [('register', (PASSWORD,))]
assert_raises(AssertionError, server.add_package, None, 'package')
self._test_server2(server.database)
@mockified('pypsyc.server', ['Routing', 'Entity', 'Database', 'run_webif'])
def _test_server2(self, database, Routing, Entity, Database, run_webif):
routing = Routing.return_value
root = Entity.return_value = StubEntity()
Database.return_value = database
executed = database.execute = AsyncMethod()
server = Server(SERVER1, INTERFACE, 0, 0, ':memory:')
assert routing.method_calls == [('init', (root,))]
assert run_webif.call_args_list == []
with mockified('pypsyc.server', ['iter_entry_points', 'Entity']) as x:
with self._test_load_package(root, *x):
executed.callback()
assert_raises(AssertionError, server.add_package, None, 'package1')
@contextmanager
def _test_load_package(self, root, iter_entry_points, Entity):
package_classes = [Mock(), Mock(), Mock(), Mock(), Mock()]
entrypoint = Mock()
entrypoint.load.side_effect = iter(package_classes).next
iter_entry_points.return_value = [entrypoint]
Entity.side_effect = StubEntity
packages = [package.return_value for package in package_classes]
yield packages
assert Entity.call_args_list == [((root, PLACE),), ((root, USER1),)]
assert iter_entry_points.call_args_list == [
(('pypsyc.server.packages', 'package'),),
(('pypsyc.server.packages', 'place'),),
(('pypsyc.server.packages', 'package'),),
(('pypsyc.server.packages', 'package'),),
(('pypsyc.server.packages', 'person'),)]
place = root.children[PLACE]
person = root.children[USER1]
assert package_classes[0].call_args_list == [((root,),)]
assert package_classes[1].call_args_list == [((place,),)]
assert package_classes[2].call_args_list == [((place,),)]
assert package_classes[3].call_args_list == [((person,),)]
assert package_classes[4].call_args_list == [((person,),)]
assert root.packages == {'package': packages[0]}
assert place.packages == {'place': packages[1], 'package': packages[2]}
assert person.packages == {'package': packages[3],
'person': packages[4]}
@mockified('pypsyc.server', ['Routing', 'Entity', 'Database', 'signal',
'reactor'])
def test_shutdown(self, Routing, Entity, Database, signal, reactor):
database = Database.return_value
database.fetch.return_value = ()
server = Server(SERVER1, INTERFACE, 0, 0, ':memory')
database.reset_mock()
server.shutdown()
assert database.method_calls == [('stop',)]
assert reactor.method_calls == [('stop',)]

View File

@ -0,0 +1,139 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from mock import Mock, sentinel
from nose.tools import assert_raises
from tests.constants import USER1, USER1_NICK, PASSWORD
from tests.helpers import mockified, PlaceHolder
from pypsyc.server import webif
from pypsyc.server.db import Database
from pypsyc.server.webif import app, PersistentStore, run_webif
class TestViews(object):
@classmethod
def setup_class(cls):
app.secret_key = 'testing key'
def setup(self):
self.server = webif.server = Mock()
self.server.root.children = {}
self.client = app.test_client()
def test_index(self):
rv = self.client.get('/')
assert 'register' in rv.data
def _register(self, username, password, password2):
return self.client.post('/register', data=dict(
username=username, password=password, password2=password2
), follow_redirects=True)
def test_register_get(self):
rv = self.client.get('/register')
assert 'Username' in rv.data
assert 'Register' in rv.data
def test_register_post(self):
rv = self._register(USER1_NICK, PASSWORD, PASSWORD)
assert 'you were registered' in rv.data
assert self.server.method_calls == [
('register_person', (USER1_NICK, PASSWORD))]
def test_register_nousername(self):
rv = self._register('', '', '')
assert 'please specify a valid username' in rv.data
def test_register_username_inuse(self):
self.server.root.children = {USER1: True}
rv = self._register(USER1_NICK, '', '')
assert 'username already in use' in rv.data
def test_register_nopassword(self):
rv = self._register(USER1_NICK, '', '')
assert 'your password must have at least 6 characters' in rv.data
def test_register_shortpassword(self):
rv = self._register(USER1_NICK, 'passw', 'passw')
assert 'your password must have at least 6 characters' in rv.data
def test_register_unmatching_passwords(self):
rv = self._register(USER1_NICK, PASSWORD, 'password2')
assert 'the two passwords do not match' in rv.data
def test_persistent_store():
webif.server = Mock()
webif.server.database = Database(':memory:')
store = PersistentStore()
assert_raises(KeyError, store.__getitem__, 'key')
assert dict(store) == {}
assert len(store) == 0
store['key'] = 'value'
assert store['key'] == 'value'
assert len(store) == 1
store['key'] = 'value2'
assert dict(store) == {'key': 'value2'}
assert len(store) == 1
del store['key']
assert dict(store) == {}
assert len(store) == 0
@mockified('pypsyc.server.webif', ['PersistentStore', 'urandom',
'WSGIResource', 'Site', 'reactor',
'greenlet'])
def test_run_webif_ssl(PersistentStore, urandom, WSGIResource, Site, reactor,
greenlet):
store = PersistentStore.return_value = {'secret_key': 'key1'*5}
reactor_ph = PlaceHolder()
threadpool_ph = PlaceHolder()
wsgi_resource = WSGIResource.return_value
site = Site.return_value
run_webif(sentinel.server, sentinel.interface, sentinel.port,
sentinel.context_factory)
assert webif.server == sentinel.server
assert PersistentStore.call_args_list == [()]
assert webif.store == store
assert app.secret_key == 'key1'*5
assert not urandom.called
assert WSGIResource.call_args_list == [
((reactor_ph, threadpool_ph, app.wsgi_app),)]
assert Site.call_args_list == [((wsgi_resource,),)]
assert reactor.method_calls == [
('listenSSL', (sentinel.port, site, sentinel.context_factory),
{'interface': sentinel.interface})]
func = Mock()
reactor_ph.obj.callFromThread(func, sentinel.a, b=sentinel.b)
assert func.call_args_list == [((sentinel.a,), {'b': sentinel.b})]
threadpool_ph.obj.callInThread(sentinel.func, sentinel.a, b=sentinel.b)
assert greenlet.call_args_list == [((sentinel.func,),)]
assert greenlet.return_value.method_calls == [
('switch', (sentinel.a,), {'b': sentinel.b})]
@mockified('pypsyc.server.webif', ['PersistentStore', 'urandom',
'WSGIResource', 'Site', 'reactor'])
def test_run_webif_tcp(PersistentStore, urandom, WSGIResource, Site, reactor):
store = PersistentStore.return_value = {}
rand = urandom.return_value = 'key2'*5
wsgi_resource = WSGIResource.return_value
site = Site.return_value
run_webif(sentinel.server, sentinel.interface, sentinel.port, None)
assert urandom.call_args_list == [((20,),)]
assert app.secret_key == rand
assert store['secret_key'] == rand
assert Site.call_args_list == [((wsgi_resource,),)]
assert reactor.method_calls == [
('listenTCP', (sentinel.port, site), {'interface': 'localhost'})]

206
mjacob2/tests/test_util.py Normal file
View File

@ -0,0 +1,206 @@
"""
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from greenlet import greenlet
from mock import Mock, sentinel
from nose.tools import assert_raises
from tests.constants import SERVER1, IP, PORT
from tests.helpers import mockified, check_success, StubException
from twisted.internet.defer import Deferred, fail
from twisted.names.dns import RRHeader, Record_A, Record_SRV
from twisted.names.error import DNSNameError
from twisted.python.failure import Failure
from pypsyc.util import (scheduler, schedule, Waiter, DNSError,
resolve_hostname, _PSYCConnector, connect, Event)
@mockified('pypsyc.util', ['greenlet'])
def test_schedule_scheduler(greenlet):
func = Mock()
schedule(func)
assert greenlet.call_args_list == [((func, scheduler),)]
assert greenlet.return_value.method_calls == [('switch',)]
orig_greenlet = greenlet
@mockified('pypsyc.util', ['greenlet', 'reactor'])
def test_schedule_nonscheduler(greenlet, reactor):
func = Mock()
orig_greenlet(schedule).switch(func)
assert greenlet.call_args_list == [((func, scheduler),)]
assert reactor.method_calls == [
('callLater', (0, greenlet.return_value.switch))]
class TestWaiter(object):
def test_callback_sync(self):
waiter = Waiter()
waiter.callback(sentinel.value)
ret = waiter.get()
assert ret == sentinel.value
def test_errback_sync(self):
e = StubException()
waiter = Waiter()
waiter.errback(e)
assert_raises(StubException, waiter.get)
@check_success
def test_callback_async(self):
waiter = Waiter()
def _test():
ret = waiter.get()
assert ret == sentinel.value
self.success = True
greenlet(_test).switch()
waiter.callback(sentinel.value)
@check_success
def test_errback_async(self):
e = StubException()
waiter = Waiter()
def _test():
assert_raises(StubException, waiter.get)
self.success = True
greenlet(_test).switch()
waiter.errback(e)
class TestResolveHostname(object):
@check_success
@mockified('pypsyc.util', ['lookupService', 'getHostByName'])
def test_resolve_hostname_srv(self, lookupService, getHostByName):
SERVICE = '_psyc._tcp.%s.' % SERVER1
srv_rr = RRHeader(name=SERVICE, type=Record_SRV.TYPE,
payload=Record_SRV(target=SERVER1, port=PORT))
a_rr = RRHeader(name=SERVER1, type=Record_A.TYPE,
payload=Record_A(IP))
looked_up = lookupService.return_value = Deferred()
def _test():
ip, port = resolve_hostname(SERVER1)
assert ip == IP
assert port == PORT
assert lookupService.call_args_list == [((SERVICE,),)]
self.success = True
schedule(_test)
looked_up.callback(([srv_rr], [], [a_rr]))
@check_success
@mockified('pypsyc.util', ['lookupService', 'getHostByName'])
def test_resolve_hostname_a(self, lookupService, getHostByName):
lookupService.side_effect = DNSNameError
looked_up = getHostByName.return_value = Deferred()
def _test():
ip, port = resolve_hostname(SERVER1)
assert ip == IP
assert port == PORT
assert getHostByName.call_args_list == [((SERVER1,),)]
self.success = True
schedule(_test)
looked_up.callback(IP)
@check_success
@mockified('pypsyc.util', ['lookupService', 'getHostByName'])
def test_resolve_hostname_a_error(self, lookupService, getHostByName):
lookupService.side_effect = DNSNameError
getHostByName.return_value = fail(DNSNameError())
def _test():
assert_raises(DNSError, resolve_hostname, SERVER1)
self.success = True
schedule(_test)
class TestConnector(object):
@mockified('pypsyc.util', ['reactor'])
def setup(self, reactor):
self.reactor = reactor
self.factory = Mock()
self.circuit = self.factory.buildProtocol.return_value
self.connector = _PSYCConnector(SERVER1, PORT, self.factory, 30, None)
self.transport = Mock()
self.connector._makeTransport = lambda: self.transport
self.connector.cancelTimeout = Mock()
@check_success
def test_connect(self):
def _test():
circuit = self.connector.connect()
assert circuit == self.circuit
self.success = True
schedule(_test)
assert self.connector.state == 'connecting'
assert self.connector.transport == self.transport
assert self.connector.timeoutID == self.reactor.callLater.return_value
self.connector.buildProtocol(None)
assert self.connector.state == 'connected'
assert self.connector.cancelTimeout.called
assert self.factory.method_calls == [('buildProtocol', (None,))]
self.circuit.inited()
@check_success
def test_connection_failed(self):
def _test():
assert_raises(StubException, self.connector.connect)
self.success = True
schedule(_test)
self.connector.connectionFailed(Failure(StubException()))
assert self.connector.cancelTimeout.called
assert self.connector.transport is None
assert self.connector.state == 'disconnected'
def test_connection_lost(self):
ERROR = StubException()
self.connector.buildProtocol(None)
self.connector.connectionLost(Failure(ERROR))
assert self.connector.state == 'disconnected'
assert self.factory.method_calls == [
('buildProtocol', (None,)),
('connection_lost', (self.circuit, ERROR))]
@mockified('pypsyc.util', ['_PSYCConnector'])
def test_connect1(_PSYCConnector):
connect(sentinel.host, sentinel.port, sentinel.factory)
assert _PSYCConnector.call_args_list == [
((sentinel.host, sentinel.port, sentinel.factory, 30, None),)]
assert _PSYCConnector.return_value.method_calls == [('connect',)]
@mockified('pypsyc.util', ['_PSYCConnector'])
def test_connect2(_PSYCConnector):
connect(sentinel.host, sentinel.port, sentinel.factory,
timeout=sentinel.timeout, bindAddress=sentinel.bindAddress)
assert _PSYCConnector.call_args_list == [
((sentinel.host, sentinel.port, sentinel.factory, sentinel.timeout,
sentinel.bindAddress),)]
assert _PSYCConnector.return_value.method_calls == [('connect',)]
def test_event():
event = Event()
f1 = Mock()
f2 = Mock()
event += f1
event.add_observer(f2, 1, i=2)
x = Mock()
y = Mock()
event(x, y=y)
assert f1.call_args_list == [((x,), {'y': y})]
assert f2.call_args_list == [((x, 1), {'y': y, 'i': 2})]
event -= f2
event(x)
assert f1.call_args_list == [((x,), {'y': y}), ((x,),)]
assert f2.call_args_list == [((x, 1), {'y': y, 'i': 2})]