diff options
author | David Laban <david.laban@collabora.co.uk> | 2010-12-03 16:38:38 +0000 |
---|---|---|
committer | David Laban <david.laban@collabora.co.uk> | 2011-01-26 17:16:33 +0000 |
commit | a9f18ec9ffe748c2078886cbc51c28babc3ce944 (patch) | |
tree | d64e4f8a808e910570f3984a301f41d6db24ce63 | |
parent | 63dc72f4e45d249dc7cbccf9e721dc3ec07e2dc1 (diff) |
initial import of outgoing-basics.py from gabble
-rw-r--r-- | tests/twisted/voip/outgoing-basics.py | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/tests/twisted/voip/outgoing-basics.py b/tests/twisted/voip/outgoing-basics.py new file mode 100644 index 0000000..d138f6d --- /dev/null +++ b/tests/twisted/voip/outgoing-basics.py @@ -0,0 +1,351 @@ +""" +Test basic outgoing call handling, using CreateChannel and all three variations +of RequestChannel. +""" + +import dbus +from twisted.words.xish import xpath + +from gabbletest import exec_test +from servicetest import ( + make_channel_proxy, wrap_channel, + EventPattern, call_async, + assertEquals, assertContains, assertLength, + ) +import constants as cs +from jingletest2 import JingleTest2, test_all_dialects + +# There are various deprecated APIs for requesting calls, documented at +# <http://telepathy.freedesktop.org/wiki/Requesting StreamedMedia channels>. +# These are ordered from most recent to most deprecated. +CREATE = 0 # CreateChannel({TargetHandleType: Contact, TargetHandle: h}); + # RequestStreams() +REQUEST_ANONYMOUS = 1 # RequestChannel(HandleTypeNone, 0); RequestStreams() +REQUEST_ANONYMOUS_AND_ADD = 2 # RequestChannel(HandleTypeNone, 0); + # AddMembers([h], ...); RequestStreams(h,...) +REQUEST_NONYMOUS = 3 # RequestChannel(HandleTypeContact, h); + # RequestStreams(h, ...) + +def create(jp, q, bus, conn, stream, peer='foo@bar.com/Res'): + worker(jp, q, bus, conn, stream, CREATE, peer) + +def request_anonymous(jp, q, bus, conn, stream, peer='publish@foo.com/Res'): + worker(jp, q, bus, conn, stream, REQUEST_ANONYMOUS, peer) + +def request_anonymous_and_add(jp, q, bus, conn, stream, + peer='publish-subscribe@foo.com/Res'): + worker(jp, q, bus, conn, stream, REQUEST_ANONYMOUS_AND_ADD, peer) + +def request_nonymous(jp, q, bus, conn, stream, peer='subscribe@foo.com/Res'): + worker(jp, q, bus, conn, stream, REQUEST_NONYMOUS, peer) + +def worker(jp, q, bus, conn, stream, variant, peer): + jt2 = JingleTest2(jp, conn, q, stream, 'test@localhost', peer) + jt2.prepare(send_presence=True, send_roster=True) + + self_handle = conn.GetSelfHandle() + remote_handle = conn.RequestHandles(1, [jt2.peer])[0] + + if variant == REQUEST_NONYMOUS: + path = conn.RequestChannel(cs.CHANNEL_TYPE_STREAMED_MEDIA, + cs.HT_CONTACT, remote_handle, True) + elif variant == CREATE: + path = conn.Requests.CreateChannel({ + cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_HANDLE: remote_handle, + })[0] + else: + path = conn.RequestChannel(cs.CHANNEL_TYPE_STREAMED_MEDIA, + cs.HT_NONE, 0, True) + + old_sig, new_sig = q.expect_many( + EventPattern('dbus-signal', signal='NewChannel', + predicate=lambda e: cs.CHANNEL_TYPE_CONTACT_LIST not in e.args), + EventPattern('dbus-signal', signal='NewChannels', + predicate=lambda e: + cs.CHANNEL_TYPE_CONTACT_LIST not in e.args[0][0][1].values()), + ) + + if variant == REQUEST_NONYMOUS or variant == CREATE: + assertEquals( [path, cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.HT_CONTACT, + remote_handle, True], old_sig.args) + else: + assertEquals( [path, cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.HT_NONE, 0, + True], old_sig.args) + + assertLength(1, new_sig.args) + assertLength(1, new_sig.args[0]) # one channel + assertLength(2, new_sig.args[0][0]) # two struct members + emitted_props = new_sig.args[0][0][1] + + assertEquals( + cs.CHANNEL_TYPE_STREAMED_MEDIA, emitted_props[cs.CHANNEL_TYPE]) + + if variant == REQUEST_NONYMOUS or variant == CREATE: + assertEquals(remote_handle, emitted_props[cs.TARGET_HANDLE]) + assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE]) + assertEquals(jt2.peer_bare_jid, emitted_props[cs.TARGET_ID]) + else: + assertEquals(0, emitted_props[cs.TARGET_HANDLE]) + assertEquals(cs.HT_NONE, emitted_props[cs.TARGET_HANDLE_TYPE]) + assertEquals('', emitted_props[cs.TARGET_ID]) + + assertEquals(True, emitted_props[cs.REQUESTED]) + assertEquals(self_handle, emitted_props[cs.INITIATOR_HANDLE]) + assertEquals('test@localhost', emitted_props[cs.INITIATOR_ID]) + + chan = wrap_channel(bus.get_object(conn.bus_name, path), 'StreamedMedia', + ['MediaSignalling']) + + # Exercise basic Channel Properties + channel_props = chan.Properties.GetAll(cs.CHANNEL) + + assertEquals(cs.CHANNEL_TYPE_STREAMED_MEDIA, + channel_props.get('ChannelType')) + + if variant == REQUEST_NONYMOUS or variant == CREATE: + assertEquals(remote_handle, channel_props['TargetHandle']) + assertEquals(cs.HT_CONTACT, channel_props['TargetHandleType']) + assertEquals(jt2.peer_bare_jid, channel_props['TargetID']) + assertEquals((cs.HT_CONTACT, remote_handle), chan.GetHandle()) + else: + assertEquals(0, channel_props['TargetHandle']) + assertEquals(cs.HT_NONE, channel_props['TargetHandleType']) + assertEquals('', channel_props['TargetID']) + assertEquals((cs.HT_NONE, 0), chan.GetHandle()) + + for interface in [ + cs.CHANNEL_IFACE_GROUP, cs.CHANNEL_IFACE_MEDIA_SIGNALLING, + cs.TP_AWKWARD_PROPERTIES, cs.CHANNEL_IFACE_HOLD]: + assertContains(interface, channel_props['Interfaces']) + + assertEquals(True, channel_props['Requested']) + assertEquals('test@localhost', channel_props['InitiatorID']) + assertEquals(conn.GetSelfHandle(), channel_props['InitiatorHandle']) + + # Exercise Group Properties + group_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_GROUP) + + assertEquals([self_handle], group_props['Members']) + assertEquals([], group_props['LocalPendingMembers']) + + if variant == REQUEST_NONYMOUS: + # In this variant, they're meant to be in RP even though we've sent + # nothing + assertEquals([remote_handle], group_props['RemotePendingMembers']) + else: + # For an anonymous channel, the peer isn't yet known; for a Create-d + # channel, the peer only appears in RP when we actually send them the + # session-initiate + assertEquals([], group_props['RemotePendingMembers']) + + if variant == REQUEST_ANONYMOUS_AND_ADD: + # but we should be allowed to add the peer. + chan.Group.AddMembers([remote_handle], 'I love backwards compat') + + base_flags = cs.GF_PROPERTIES | cs.GF_MESSAGE_REMOVE \ + | cs.GF_MESSAGE_REJECT | cs.GF_MESSAGE_RESCIND + + if variant == REQUEST_ANONYMOUS_AND_ADD or variant == REQUEST_ANONYMOUS: + expected_flags = base_flags | cs.GF_CAN_ADD + else: + expected_flags = base_flags + assertEquals(expected_flags, group_props['GroupFlags']) + assertEquals({}, group_props['HandleOwners']) + + assertEquals([], chan.StreamedMedia.ListStreams()) + streams = chan.StreamedMedia.RequestStreams(remote_handle, + [cs.MEDIA_STREAM_TYPE_AUDIO]) + assertEquals(streams, chan.StreamedMedia.ListStreams()) + assertLength(1, streams) + + # streams[0][0] is the stream identifier, which in principle we can't + # make any assertion about (although in practice it's probably 1) + + assertEquals(( + remote_handle, + cs.MEDIA_STREAM_TYPE_AUDIO, + # We haven't connected yet + cs.MEDIA_STREAM_STATE_DISCONNECTED, + # In Gabble, requested streams start off bidirectional + cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, + 0), + streams[0][1:]) + + # S-E gets notified about new session handler, and calls Ready on it + e = q.expect('dbus-signal', signal='NewSessionHandler') + assert e.args[1] == 'rtp' + + session_handler = make_channel_proxy(conn, e.args[0], 'Media.SessionHandler') + session_handler.Ready() + + e = q.expect('dbus-signal', signal='NewStreamHandler') + + stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler') + + stream_handler.NewNativeCandidate("fake", jt2.get_remote_transports_dbus()) + stream_handler.Ready(jt2.get_audio_codecs_dbus()) + stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED) + + sh_props = stream_handler.GetAll( + cs.STREAM_HANDLER, dbus_interface=dbus.PROPERTIES_IFACE) + assertEquals('gtalk-p2p', sh_props['NATTraversal']) + assertEquals(True, sh_props['CreatedLocally']) + + if variant == CREATE: + # When we actually send XML to the peer, they should pop up in remote + # pending. + session_initiate, _ = q.expect_many( + EventPattern('stream-iq', iq_type='set', predicate=lambda e: + jp.match_jingle_action(e.query, 'session-initiate')), + EventPattern('dbus-signal', signal='MembersChanged', + args=["", [], [], [], [remote_handle], self_handle, + cs.GC_REASON_INVITED]), + ) + else: + forbidden = [] + + if peer.split('/', 1)[0] in ( + 'publish@foo.com', 'publish-subscribe@foo.com'): + forbidden = [EventPattern('stream-presence')] + q.forbid_events(forbidden) + else: + # we're calling someone not on our roster, so we'll send directed + # presence first + presence = q.expect('stream-presence') + assert (xpath.queryForNodes('/presence/c', presence.stanza) + is not None) + assert (xpath.queryForNodes( + '/presence/x[@xmlns="vcard-temp:x:update"]', presence.stanza) + is not None) + + session_initiate = q.expect('stream-iq', + predicate=jp.action_predicate('session-initiate')) + + if forbidden: + q.unforbid_events(forbidden) + + jt2.parse_session_initiate(session_initiate.query) + stream.send(jp.xml(jp.ResultIq('test@localhost', session_initiate.stanza, + []))) + + # Check the Group interface's properties again. Regardless of the call + # requesting API in use, the state should be the same here: + group_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_GROUP) + assertContains('HandleOwners', group_props) + assertEquals([self_handle], group_props['Members']) + assertEquals([], group_props['LocalPendingMembers']) + assertEquals([remote_handle], group_props['RemotePendingMembers']) + + if jp.dialect == 'gtalk-v0.4': + node = jp.SetIq(jt2.peer, jt2.jid, [ + jp.Jingle(jt2.sid, jt2.peer, 'transport-accept', [ + jp.TransportGoogleP2P() ]) ]) + stream.send(jp.xml(node)) + + # FIXME: expect transport-info, then if we're gtalk3, send + # candidates, and check that gabble resends transport-info as + # candidates + jt2.accept() + + q.expect_many( + EventPattern('stream-iq', iq_type='result'), + # Call accepted + EventPattern('dbus-signal', signal='MembersChanged', + args=['', [remote_handle], [], [], [], remote_handle, + cs.GC_REASON_NONE]), + ) + + # Time passes ... afterwards we close the chan + + chan.Group.RemoveMembers([self_handle], 'closed') + + # Make sure gabble sends proper terminate action + if jp.dialect.startswith('gtalk'): + terminate = EventPattern('stream-iq', predicate=lambda x: + xpath.queryForNodes("/iq/session[@type='terminate']", + x.stanza)) + else: + terminate = EventPattern('stream-iq', predicate=lambda x: + xpath.queryForNodes("/iq/jingle[@action='session-terminate']", + x.stanza)) + + mc_event, _, _ = q.expect_many( + EventPattern('dbus-signal', signal='MembersChanged'), + EventPattern('dbus-signal', signal='Close'), + terminate, + ) + # Check that we're the actor + assertEquals(self_handle, mc_event.args[5]) + +def rccs(q, bus, conn, stream): + """ + Tests that the connection's RequestableChannelClasses for StreamedMedia are + sane. + """ + conn.Connect() + + q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) + + rccs = conn.Properties.Get(cs.CONN_IFACE_REQUESTS, + 'RequestableChannelClasses') + + # Test Channel.Type.StreamedMedia + media_classes = [ rcc for rcc in rccs + if rcc[0][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_STREAMED_MEDIA ] + + assertLength(1, media_classes) + + fixed, allowed = media_classes[0] + + assertEquals(cs.HT_CONTACT, fixed[cs.TARGET_HANDLE_TYPE]) + + expected_allowed = [ + cs.TARGET_ID, cs.TARGET_HANDLE, + cs.INITIAL_VIDEO, cs.INITIAL_AUDIO + ] + + allowed.sort() + expected_allowed.sort() + assertEquals(expected_allowed, allowed) + + # Test Channel.Type.Call + media_classes = [ rcc for rcc in rccs + if rcc[0][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_CALL ] + assertLength(2, media_classes) + + hts = [] + + for mc in media_classes: + fixed, allowed = mc + + hts.append (fixed[cs.TARGET_HANDLE_TYPE]) + + expected_allowed = [ + cs.TARGET_ID, cs.TARGET_HANDLE, + cs.CALL_INITIAL_VIDEO, cs.CALL_INITIAL_AUDIO, + cs.CALL_MUTABLE_CONTENTS + ] + + allowed.sort() + expected_allowed.sort() + assertEquals(expected_allowed, allowed) + + assertEquals(sorted([cs.HT_CONTACT, cs.HT_ROOM]), sorted(hts)) + +if __name__ == '__main__': + exec_test(rccs) + test_all_dialects(create) + test_all_dialects(request_anonymous) + test_all_dialects(request_anonymous_and_add) + test_all_dialects(request_nonymous) + test_all_dialects(lambda j, q, b, c, s: + create(j, q, b, c, s, peer='foo@gw.bar.com')) + test_all_dialects(lambda j, q, b, c, s: + request_anonymous(j, q, b, c, s, peer='foo@gw.bar.com')) + test_all_dialects(lambda j, q, b, c, s: + request_anonymous_and_add(j, q, b, c, s, peer='foo@gw.bar.com')) + test_all_dialects(lambda j, q, b, c, s: + request_nonymous(j, q, b, c, s, peer='foo@gw.bar.com')) |