/* Tests for TpCallChannel, TpCallContent and TpCallStream * * Copyright © 2009-2011 Collabora Ltd. * Copyright © 2009 Nokia Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include "examples/cm/call/cm.h" #include "examples/cm/call/conn.h" #include "examples/cm/call/call-channel.h" #include "examples/cm/call/call-stream.h" #include "tests/lib/util.h" typedef struct { GMainLoop *mainloop; TpDBusDaemon *dbus; GError *error /* statically initialized to NULL */ ; guint wait_count; ExampleCallConnectionManager *service_cm; TpSimpleClientFactory *factory; TpConnectionManager *cm; TpConnection *conn; TpChannel *chan; TpCallChannel *call_chan; TpHandle self_handle; TpHandle peer_handle; GArray *audio_request; GArray *video_request; GArray *invalid_request; GArray *stream_ids; GArray *contacts; TpCallContent *added_content; } Test; static void setup (Test *test, gconstpointer data G_GNUC_UNUSED) { TpBaseConnectionManager *service_cm_as_base; gboolean ok; gchar *bus_name; gchar *object_path; GHashTable *parameters; guint audio = TP_MEDIA_STREAM_TYPE_AUDIO; guint video = TP_MEDIA_STREAM_TYPE_VIDEO; guint not_a_media_type = 31337; GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONNECTED, 0 }; tp_debug_set_flags ("all"); test->mainloop = g_main_loop_new (NULL, FALSE); test->dbus = tp_tests_dbus_daemon_dup_or_die (); test->service_cm = EXAMPLE_CALL_CONNECTION_MANAGER ( tp_tests_object_new_static_class ( EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, NULL)); g_assert (test->service_cm != NULL); service_cm_as_base = TP_BASE_CONNECTION_MANAGER (test->service_cm); g_assert (service_cm_as_base != NULL); ok = tp_base_connection_manager_register (service_cm_as_base); g_assert (ok); test->cm = tp_connection_manager_new (test->dbus, "example_call", NULL, &test->error); g_assert (test->cm != NULL); tp_tests_proxy_run_until_prepared (test->cm, NULL); parameters = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); g_hash_table_insert (parameters, "account", tp_g_value_slice_new_static_string ("me")); g_hash_table_insert (parameters, "simulation-delay", tp_g_value_slice_new_uint (0)); tp_cli_connection_manager_run_request_connection (test->cm, -1, "example", parameters, &bus_name, &object_path, &test->error, NULL); g_assert_no_error (test->error); test->factory = (TpSimpleClientFactory *) tp_automatic_client_factory_new (test->dbus); tp_simple_client_factory_add_channel_features_varargs (test->factory, TP_CHANNEL_FEATURE_CONTACTS, 0); test->conn = tp_simple_client_factory_ensure_connection (test->factory, object_path, NULL, &test->error); g_assert_no_error (test->error); g_assert (test->conn != NULL); tp_cli_connection_call_connect (test->conn, -1, NULL, NULL, NULL, NULL); tp_tests_proxy_run_until_prepared (test->conn, conn_features); test->self_handle = tp_connection_get_self_handle (test->conn); g_assert (test->self_handle != 0); test->audio_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); g_array_append_val (test->audio_request, audio); test->video_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); g_array_append_val (test->video_request, video); test->invalid_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); g_array_append_val (test->invalid_request, not_a_media_type); test->stream_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); g_hash_table_unref (parameters); g_free (bus_name); g_free (object_path); } static void channel_created_cb (TpConnection *connection, const gchar *object_path, GHashTable *immutable_properties, const GError *error, gpointer user_data, GObject *weak_object G_GNUC_UNUSED) { Test *test = user_data; GError *new_error = NULL; g_assert_no_error ((GError *) error); test->chan = tp_simple_client_factory_ensure_channel (test->factory, connection, object_path, immutable_properties, &new_error); g_assert_no_error (new_error); g_assert (TP_IS_CALL_CHANNEL (test->chan)); test->call_chan = (TpCallChannel *) test->chan; test->peer_handle = tp_channel_get_handle (test->chan, NULL); g_main_loop_quit (test->mainloop); } static void outgoing_call (Test *test, const gchar *id, gboolean initial_audio, gboolean initial_video) { GHashTable *request = tp_asv_new ( TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CALL, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT, TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, id, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN, initial_audio, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, G_TYPE_BOOLEAN, initial_video, NULL); tp_cli_connection_interface_requests_call_create_channel (test->conn, -1, request, channel_created_cb, test, NULL, NULL); g_hash_table_unref (request); request = NULL; g_main_loop_run (test->mainloop); tp_tests_proxy_run_until_prepared (test->chan, NULL); } static void assert_call_properties (TpCallChannel *channel, TpCallState call_state, TpHandle actor, TpCallStateChangeReason reason, const gchar *dbus_reason, gboolean check_call_flags, TpCallFlags call_flags, gboolean check_initials, gboolean initial_audio, gboolean initial_video) { TpCallState state; TpCallFlags flags; GHashTable *details; TpCallStateReason *r; state = tp_call_channel_get_state (channel, &flags, &details, &r); /* FIXME: details */ g_assert_cmpuint (state, ==, call_state); g_assert_cmpuint (r->actor, ==, actor); g_assert_cmpuint (r->reason, ==, reason); g_assert_cmpstr (r->dbus_reason, ==, dbus_reason); if (check_call_flags) g_assert_cmpuint (flags, ==, call_flags); /* Hard-coded properties */ g_assert_cmpint (tp_call_channel_has_hardware_streaming (channel), ==, FALSE); g_assert_cmpint (tp_call_channel_has_mutable_contents (channel), ==, TRUE); if (check_initials) { const gchar *initial_audio_name; const gchar *initial_video_name; g_assert_cmpint (tp_call_channel_has_initial_audio (channel, &initial_audio_name), ==, initial_audio); g_assert_cmpint (tp_call_channel_has_initial_video (channel, &initial_video_name), ==, initial_video); g_assert_cmpstr (initial_audio_name, ==, initial_audio ? "audio" : NULL); g_assert_cmpstr (initial_video_name, ==, initial_video ? "video" : NULL); } } static void assert_content_properties (TpCallContent *content, TpMediaStreamType type, TpCallContentDisposition disposition) { g_assert_cmpstr (tp_call_content_get_name (content), !=, NULL); g_assert_cmpuint (tp_call_content_get_media_type (content), ==, type); g_assert_cmpuint (tp_call_content_get_disposition (content), ==, disposition); } static void close_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Test *test = user_data; g_clear_error (&test->error); tp_channel_close_finish (test->chan, result, &test->error); g_main_loop_quit (test->mainloop); } static void assert_ended_and_run_close (Test *test, TpHandle expected_actor, TpCallStateChangeReason expected_reason, const gchar *expected_error) { GPtrArray *contents; tp_tests_proxy_run_until_dbus_queue_processed (test->conn); /* In response to whatever we just did, the call ends... */ assert_call_properties (test->call_chan, TP_CALL_STATE_ENDED, expected_actor, expected_reason, expected_error, FALSE, 0, /* ignore call flags */ FALSE, FALSE, FALSE); /* ignore initial audio/video */ /* ... which means there are no contents ... */ contents = tp_call_channel_get_contents (test->call_chan); g_assert_cmpuint (contents->len, ==, 0); /* ... but the channel doesn't close */ g_assert (tp_proxy_get_invalidated (test->chan) == NULL); /* When we call Close it finally closes */ tp_channel_close_async (test->chan, close_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); tp_tests_proxy_run_until_dbus_queue_processed (test->conn); g_assert (tp_proxy_get_invalidated (test->chan) != NULL); } static void run_until_answered_cb (TpCallChannel *channel, TpCallState state, TpCallFlags flags, TpCallStateReason *reason, GHashTable *details, Test *test) { if (state != TP_CALL_STATE_INITIALISED) g_main_loop_quit (test->mainloop); } static void run_until_answered (Test *test) { TpCallState state; guint id; state = tp_call_channel_get_state (test->call_chan, NULL, NULL, NULL); if (state != TP_CALL_STATE_INITIALISED) return; id = g_signal_connect (test->call_chan, "state-changed", G_CALLBACK (run_until_answered_cb), test); g_main_loop_run (test->mainloop); g_signal_handler_disconnect (test->call_chan, id); } static void run_until_ended_cb (TpCallChannel *channel, TpCallState state, TpCallFlags flags, TpCallStateReason *reason, GHashTable *details, Test *test) { if (state == TP_CALL_STATE_ENDED) g_main_loop_quit (test->mainloop); } static void run_until_ended (Test *test) { TpCallState state; guint id; state = tp_call_channel_get_state (test->call_chan, NULL, NULL, NULL); if (state == TP_CALL_STATE_ENDED) return; id = g_signal_connect (test->call_chan, "state-changed", G_CALLBACK (run_until_ended_cb), test); g_main_loop_run (test->mainloop); g_signal_handler_disconnect (test->call_chan, id); } static void run_until_active_cb (TpCallChannel *channel, TpCallState state, TpCallFlags flags, TpCallStateReason *reason, GHashTable *details, Test *test) { if (state == TP_CALL_STATE_ACTIVE) g_main_loop_quit (test->mainloop); } static void run_until_active_get_all_cb (TpProxy *proxy, GHashTable *properties, const GError *error, gpointer user_data, GObject *weak_object) { GPtrArray *endpoints; guint i; g_assert_no_error (error); tp_asv_dump (properties); endpoints = tp_asv_get_boxed (properties, "Endpoints", TP_ARRAY_TYPE_OBJECT_PATH_LIST); g_assert (endpoints != NULL); g_assert (endpoints->len > 0); for (i = 0; i < endpoints->len; i++) { const gchar *object_path = g_ptr_array_index (endpoints, i); TpProxy *endpoint; endpoint = g_object_new (TP_TYPE_PROXY, "dbus-daemon", tp_proxy_get_dbus_daemon (proxy), "bus-name", tp_proxy_get_bus_name (proxy), "object-path", object_path, NULL); tp_proxy_add_interface_by_id (endpoint, TP_IFACE_QUARK_CALL_STREAM_ENDPOINT); tp_cli_call_stream_endpoint_call_set_endpoint_state (endpoint, -1, TP_STREAM_COMPONENT_DATA, TP_STREAM_ENDPOINT_STATE_FULLY_CONNECTED, NULL, NULL, NULL, NULL); g_object_unref (endpoint); } } static void run_until_active_stream_prepared_cb (GObject *stream, GAsyncResult *res, gpointer test) { GError *error = NULL; if (!tp_proxy_prepare_finish (stream, res, &error)) { g_error ("error %s", error->message); } g_assert (tp_proxy_has_interface_by_id (stream, TP_IFACE_QUARK_CALL_STREAM_INTERFACE_MEDIA)); tp_cli_dbus_properties_call_get_all (stream, -1, TP_IFACE_CALL_STREAM_INTERFACE_MEDIA, run_until_active_get_all_cb, test, NULL, NULL); } static void run_until_active (Test *test) { GPtrArray *contents; guint i, j; guint id; if (tp_call_channel_get_state (test->call_chan, NULL, NULL, NULL) == TP_CALL_STATE_ACTIVE) return; g_assert (tp_call_channel_get_state (test->call_chan, NULL, NULL, NULL) == TP_CALL_STATE_ACCEPTED); contents = tp_call_channel_get_contents (test->call_chan); for (i = 0; i < contents->len; i++) { TpCallContent *content = g_ptr_array_index (contents, i); GPtrArray *streams; streams = tp_call_content_get_streams (content); for (j = 0; j < streams->len; j++) { TpCallStream *stream = g_ptr_array_index (streams, j); tp_proxy_prepare_async (stream, NULL, run_until_active_stream_prepared_cb, test); } } id = g_signal_connect (test->call_chan, "state-changed", G_CALLBACK (run_until_active_cb), test); g_main_loop_run (test->mainloop); g_signal_handler_disconnect (test->call_chan, id); } static void accept_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Test *test = user_data; g_clear_error (&test->error); tp_call_channel_accept_finish (test->call_chan, result, &test->error); g_main_loop_quit (test->mainloop); } static void run_until_accepted_cb (TpCallChannel *channel, TpCallState state, TpCallFlags flags, TpCallStateReason *reason, GHashTable *details, Test *test) { if (state == TP_CALL_STATE_ACCEPTED) g_main_loop_quit (test->mainloop); } static void run_until_accepted (Test *test) { guint id; tp_call_channel_accept_async (test->call_chan, NULL, NULL); id = g_signal_connect (test->call_chan, "state-changed", G_CALLBACK (run_until_accepted_cb), test); g_main_loop_run (test->mainloop); g_signal_handler_disconnect (test->call_chan, id); } static void hangup_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Test *test = user_data; g_clear_error (&test->error); tp_call_channel_hangup_finish (test->call_chan, result, &test->error); g_main_loop_quit (test->mainloop); } static void add_content_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Test *test = user_data; g_clear_error (&test->error); tp_clear_object (&test->added_content); test->added_content = tp_call_channel_add_content_finish (test->call_chan, result, &test->error); g_main_loop_quit (test->mainloop); } /* static void content_remove_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Test *test = user_data; g_clear_error (&test->error); tp_call_content_remove_finish ((TpCallContent *) object, result, &test->error); g_main_loop_quit (test->mainloop); } */ static void test_basics (Test *test, gconstpointer data G_GNUC_UNUSED) { GPtrArray *contents; GPtrArray *streams; TpCallContent *audio_content; TpCallContent *video_content; TpCallStream *audio_stream; TpCallStream *video_stream; GHashTable *remote_members; gpointer v; outgoing_call (test, "basic-test", TRUE, FALSE); assert_call_properties (test->call_chan, TP_CALL_STATE_PENDING_INITIATOR, 0, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", TRUE, 0, /* call flags */ TRUE, TRUE, FALSE); /* initial audio/video must be what we said */ /* We have one audio content but it's not active just yet */ contents = tp_call_channel_get_contents (test->call_chan); g_assert_cmpuint (contents->len, ==, 1); audio_content = g_ptr_array_index (contents, 0); tp_tests_proxy_run_until_prepared (audio_content, NULL); assert_content_properties (audio_content, TP_MEDIA_STREAM_TYPE_AUDIO, TP_CALL_CONTENT_DISPOSITION_INITIAL); streams = tp_call_content_get_streams (audio_content); g_assert_cmpuint (streams->len, ==, 1); audio_stream = g_ptr_array_index (streams, 0); tp_tests_proxy_run_until_prepared (audio_stream, NULL); remote_members = tp_call_stream_get_remote_members (audio_stream); g_assert_cmpuint (g_hash_table_size (remote_members), ==, 1); v = g_hash_table_lookup (remote_members, tp_channel_get_target_contact (test->chan)); g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, TP_SENDING_STATE_PENDING_SEND); g_assert_cmpuint (tp_call_stream_get_local_sending_state (audio_stream), ==, TP_SENDING_STATE_SENDING); /* OK, that looks good. Actually make the call */ tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); /* Calling Accept again makes no sense, but mustn't crash */ tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_error (test->error, TP_ERROR, TP_ERROR_NOT_AVAILABLE); g_clear_error (&test->error); /* Wait for the remote contact to answer, if they haven't already */ run_until_answered (test); /* Calling Accept again makes no sense, but mustn't crash */ tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_error (test->error, TP_ERROR, TP_ERROR_NOT_AVAILABLE); g_clear_error (&test->error); /* Check the call state. */ assert_call_properties (test->call_chan, TP_CALL_STATE_ACCEPTED, tp_channel_get_handle (test->chan, NULL), TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", TRUE, 0, /* call flags */ FALSE, FALSE, FALSE); /* don't care about initial audio/video */ /* Connecting endpoints makes state become active */ run_until_active (test); assert_call_properties (test->call_chan, TP_CALL_STATE_ACTIVE, test->self_handle, TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", TRUE, 0, /* call flags */ FALSE, FALSE, FALSE); /* don't care about initial audio/video */ /* There's still one content */ contents = tp_call_channel_get_contents (test->call_chan); g_assert_cmpuint (contents->len, ==, 1); g_assert (g_ptr_array_index (contents, 0) == audio_content); /* Other contact is sending now */ remote_members = tp_call_stream_get_remote_members (audio_stream); g_assert_cmpuint (g_hash_table_size (remote_members), == , 1); v = g_hash_table_lookup (remote_members, tp_channel_get_target_contact (test->chan)); g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, TP_SENDING_STATE_SENDING); g_assert_cmpuint (tp_call_stream_get_local_sending_state (audio_stream), ==, TP_SENDING_STATE_SENDING); /* AddContent with bad content-type must fail */ tp_call_channel_add_content_async (test->call_chan, "", 31337, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, add_content_cb, test); g_main_loop_run (test->mainloop); g_assert (test->error != NULL); g_assert (test->added_content == NULL); g_clear_error (&test->error); /* AddContent with bad initial-direction must fail */ tp_call_channel_add_content_async (test->call_chan, "", TP_MEDIA_STREAM_TYPE_AUDIO, 31337, add_content_cb, test); g_main_loop_run (test->mainloop); g_assert (test->error != NULL); g_assert (test->added_content == NULL); g_clear_error (&test->error); /* AddContent again, to add a video stream */ tp_call_channel_add_content_async (test->call_chan, "", TP_MEDIA_STREAM_TYPE_VIDEO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, add_content_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); g_assert (TP_IS_CALL_CONTENT (test->added_content)); g_assert (tp_proxy_get_factory (test->added_content) == tp_proxy_get_factory (test->call_chan)); video_content = test->added_content; tp_tests_proxy_run_until_prepared (video_content, NULL); /* There are two Contents, because now we have the video content too */ contents = tp_call_channel_get_contents (test->call_chan); g_assert_cmpuint (contents->len, ==, 2); /* they could be either way round */ if (g_ptr_array_index (contents, 0) == audio_content) { g_assert (g_ptr_array_index (contents, 1) == video_content); } else { g_assert (g_ptr_array_index (contents, 0) == video_content); g_assert (g_ptr_array_index (contents, 1) == audio_content); } assert_content_properties (video_content, TP_MEDIA_STREAM_TYPE_VIDEO, TP_CALL_CONTENT_DISPOSITION_NONE); streams = tp_call_content_get_streams (video_content); g_assert (streams != NULL); g_assert_cmpuint (streams->len, ==, 1); video_stream = g_ptr_array_index (streams, 0); tp_tests_proxy_run_until_prepared (video_stream, NULL); g_assert_cmpuint (tp_call_stream_get_local_sending_state (video_stream), ==, TP_SENDING_STATE_SENDING); remote_members = tp_call_stream_get_remote_members (video_stream); g_assert_cmpuint (g_hash_table_size (remote_members), ==, 1); v = g_hash_table_lookup (remote_members, tp_channel_get_target_contact (test->chan)); /* After a moment, the video stream becomes connected, and the remote user * accepts our proposed direction change. These might happen in either * order, at least in this implementation. */ if (GPOINTER_TO_UINT (v) != TP_SENDING_STATE_SENDING) g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, TP_SENDING_STATE_PENDING_SEND); #if 0 /* FIXME: Content.Remove() is not implemented in example CM */ /* Drop the video content */ tp_call_content_remove_async (video_content, content_remove_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); /* Get contents again: now there's only the audio */ contents = tp_call_channel_get_contents (test->call_chan); g_assert_cmpuint (contents->len, ==, 1); g_assert (g_ptr_array_index (contents, 0) == audio_content); #endif /* Hang up the call in the recommended way */ tp_call_channel_hangup_async (test->call_chan, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "", hangup_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); assert_ended_and_run_close (test, test->self_handle, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, ""); } static void test_no_answer (Test *test, gconstpointer data G_GNUC_UNUSED) { /* This identifier contains the magic string (no answer), which means the * example will never answer. */ outgoing_call (test, "smcv (no answer)", TRUE, FALSE); tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); /* After the initial flurry of D-Bus messages, smcv still hasn't answered */ tp_tests_proxy_run_until_dbus_queue_processed (test->conn); assert_call_properties (test->call_chan, TP_CALL_STATE_INITIALISED, test->self_handle, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", TRUE, 0, /* call flags */ TRUE, TRUE, FALSE); /* initial audio/video must be TRUE, FALSE */ /* assume we're never going to get an answer, and hang up */ tp_call_channel_hangup_async (test->call_chan, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "", hangup_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); assert_ended_and_run_close (test, test->self_handle, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, ""); } static void test_busy (Test *test, gconstpointer data G_GNUC_UNUSED) { /* This identifier contains the magic string (busy), which means the example * will simulate rejection of the call as busy rather than accepting it. */ outgoing_call (test, "Robot101 (busy)", TRUE, FALSE); tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); /* Wait for the remote contact to end the call as busy */ run_until_ended (test); assert_ended_and_run_close (test, tp_channel_get_handle (test->chan, NULL), TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, TP_ERROR_STR_BUSY); } static void test_terminated_by_peer (Test *test, gconstpointer data G_GNUC_UNUSED) { /* This contact contains the magic string "(terminate)", meaning the example * simulates answering the call but then terminating it */ outgoing_call (test, "The Governator (terminate)", TRUE, TRUE); tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); /* Wait for the remote contact to answer, if they haven't already */ run_until_answered (test); /* After that, the remote contact immediately ends the call */ run_until_ended (test); assert_ended_and_run_close (test, tp_channel_get_handle (test->chan, NULL), TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, ""); } static void test_terminate_via_close (Test *test, gconstpointer data G_GNUC_UNUSED) { outgoing_call (test, "basic-test", FALSE, TRUE); tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); /* Wait for the remote contact to answer, if they haven't already */ run_until_answered (test); assert_call_properties (test->call_chan, TP_CALL_STATE_ACCEPTED, test->peer_handle, TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", TRUE, 0, /* call flags */ TRUE, FALSE, TRUE); /* initial audio/video must be FALSE, TRUE */ /* Terminate the call unceremoniously, by calling Close. This is not a * graceful hangup; rather, it's what the ChannelDispatcher would do to * signal a client crash, undispatchability, or whatever */ tp_channel_close_async (test->chan, close_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); /* In response to termination, the channel does genuinely close */ tp_tests_proxy_run_until_dbus_queue_processed (test->conn); g_assert (tp_proxy_get_invalidated (test->chan) != NULL); /* FIXME: when we hook up signals, check for expected call state * transition before invalidation */ } /* FIXME: try removing the last stream, it should fail */ /* FIXME: add a special contact who refuses to have video */ /* FIXME: add a special contact who asks us for video */ /* FIXME: add a special contact whose stream errors */ static void expect_incoming_call_cb (TpConnection *conn, const GPtrArray *channels, gpointer user_data, GObject *weak_object G_GNUC_UNUSED) { Test *test = user_data; guint i; for (i = 0; i < channels->len; i++) { GValueArray *va = g_ptr_array_index (channels, i); const gchar *object_path = g_value_get_boxed (va->values + 0); GHashTable *properties = g_value_get_boxed (va->values + 1); GError *error = NULL; /* we only expect to receive one call */ g_assert (test->chan == NULL); test->chan = tp_simple_client_factory_ensure_channel (test->factory, conn, object_path, properties, &error); g_assert_no_error (error); g_assert (TP_IS_CALL_CHANNEL (test->chan)); test->call_chan = (TpCallChannel *) test->chan; g_assert_cmpint (tp_channel_get_requested (test->chan), ==, FALSE); } } /* In this example connection manager, every time the presence status changes * to available or the message changes, an incoming call is simulated. */ static void trigger_incoming_call (Test *test, const gchar *message, const gchar *expected_caller) { TpProxySignalConnection *new_channels_sig; tp_cli_connection_interface_simple_presence_run_set_presence (test->conn, -1, "away", "preparing for a test", &test->error, NULL); g_assert_no_error (test->error); new_channels_sig = tp_cli_connection_interface_requests_connect_to_new_channels (test->conn, expect_incoming_call_cb, test, NULL, NULL, &test->error); g_assert_no_error (test->error); tp_cli_connection_interface_simple_presence_run_set_presence (test->conn, -1, "available", message, &test->error, NULL); g_assert_no_error (test->error); /* wait for the call to happen if it hasn't already */ while (test->chan == NULL) { g_main_context_iteration (NULL, TRUE); } g_assert_cmpstr (tp_channel_get_identifier (test->chan), ==, expected_caller); test->peer_handle = tp_channel_get_handle (test->chan, NULL); tp_proxy_signal_connection_disconnect (new_channels_sig); tp_tests_proxy_run_until_prepared (test->chan, NULL); } static void test_incoming (Test *test, gconstpointer data G_GNUC_UNUSED) { GPtrArray *contents; TpCallContent *audio_content; trigger_incoming_call (test, "call me?", "caller"); /* ring, ring! */ assert_call_properties (test->call_chan, TP_CALL_STATE_INITIALISED, test->peer_handle, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", TRUE, 0, /* call flags */ TRUE, TRUE, FALSE); /* initial audio/video must be TRUE, FALSE */ /* Get Contents: we have an audio content */ contents = tp_call_channel_get_contents (test->call_chan); g_assert_cmpuint (contents->len, ==, 1); audio_content = g_ptr_array_index (contents, 0); tp_tests_proxy_run_until_prepared (audio_content, NULL); g_assert_cmpuint (tp_call_content_get_media_type (audio_content), ==, TP_MEDIA_STREAM_TYPE_AUDIO); /* FIXME: assert about the properties of the content and the stream */ /* Accept the call */ tp_call_channel_accept_async (test->call_chan, accept_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); assert_call_properties (test->call_chan, TP_CALL_STATE_ACCEPTED, test->self_handle, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", TRUE, 0, /* call flags */ TRUE, TRUE, FALSE); /* initial audio/video are still TRUE, FALSE */ /* FIXME: check for stream directionality changes */ /* Hang up the call */ tp_call_channel_hangup_async (test->call_chan, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "", hangup_cb, test); g_main_loop_run (test->mainloop); g_assert_no_error (test->error); assert_ended_and_run_close (test, test->self_handle, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, ""); } static void send_tones_cb (GObject *source, GAsyncResult *result, gpointer user_data) { Test *test = user_data; GError *error = NULL; tp_call_channel_send_tones_finish (test->call_chan, result, &error); g_assert_no_error (error); test->wait_count--; if (test->wait_count <= 0) g_main_loop_quit (test->mainloop); } static void dtmf_change_requested_cb (TpCallContent *content, guchar event, TpSendingState state, gpointer user_data, GObject *weak_object) { /* Only PENDING states can be requested */ g_assert (state == TP_SENDING_STATE_PENDING_SEND || state == TP_SENDING_STATE_PENDING_STOP_SENDING); if (state == TP_SENDING_STATE_PENDING_SEND) { tp_cli_call_content_interface_media_call_acknowledge_dtmf_change (content, -1, event, TP_SENDING_STATE_SENDING, NULL, NULL, NULL, NULL); } else if (state == TP_SENDING_STATE_PENDING_STOP_SENDING) { tp_cli_call_content_interface_media_call_acknowledge_dtmf_change (content, -1, event, TP_SENDING_STATE_NONE, NULL, NULL, NULL, NULL); } } static void test_dtmf (Test *test, gconstpointer data G_GNUC_UNUSED) { GPtrArray *contents; TpCallContent *content; outgoing_call (test, "dtmf-badger", TRUE, FALSE); run_until_accepted (test); run_until_active (test); contents = tp_call_channel_get_contents (test->call_chan); g_assert (contents->len == 1); content = g_ptr_array_index (contents, 0); tp_cli_call_content_interface_media_connect_to_dtmf_change_requested (content, dtmf_change_requested_cb, test, NULL, NULL, NULL); tp_call_channel_send_tones_async (test->call_chan, "123456789", NULL, send_tones_cb, test); tp_call_channel_send_tones_async (test->call_chan, "ABCD", NULL, send_tones_cb, test); test->wait_count = 2; g_main_loop_run (test->mainloop); g_assert_no_error (test->error); } static void teardown (Test *test, gconstpointer data G_GNUC_UNUSED) { tp_cli_connection_run_disconnect (test->conn, -1, &test->error, NULL); g_assert_no_error (test->error); g_array_unref (test->audio_request); g_array_unref (test->video_request); g_array_unref (test->invalid_request); g_array_unref (test->stream_ids); tp_clear_object (&test->added_content); tp_clear_object (&test->chan); tp_clear_object (&test->conn); tp_clear_object (&test->cm); tp_clear_object (&test->service_cm); /* make sure any pending things have happened */ tp_tests_proxy_run_until_dbus_queue_processed (test->dbus); tp_clear_object (&test->dbus); g_main_loop_unref (test->mainloop); test->mainloop = NULL; } int main (int argc, char **argv) { tp_tests_init (&argc, &argv); g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id="); g_set_prgname ("call-channel"); g_test_add ("/call/basics", Test, NULL, setup, test_basics, teardown); g_test_add ("/call/busy", Test, NULL, setup, test_busy, teardown); g_test_add ("/call/no-answer", Test, NULL, setup, test_no_answer, teardown); g_test_add ("/call/terminated-by-peer", Test, NULL, setup, test_terminated_by_peer, teardown); g_test_add ("/call/terminate-via-close", Test, NULL, setup, test_terminate_via_close, teardown); g_test_add ("/call/incoming", Test, NULL, setup, test_incoming, teardown); g_test_add ("/call/dtmf", Test, NULL, setup, test_dtmf, teardown); return tp_tests_run_with_bus (); }