/* Simple utility code used by the regression tests.
*
* Copyright © 2008-2010 Collabora Ltd.
* Copyright © 2008 Nokia Corporation
*
* Copying and distribution of this file, with or without modification,
* are permitted in any medium without royalty provided the copyright
* notice and this notice are preserved.
*/
#include "config.h"
#include "util.h"
#include
#include
#include
#include
#ifdef G_OS_UNIX
# include /* for alarm() */
#endif
#ifdef HAVE_GIO_UNIX
#include
#include
#endif
#include "debug.h"
void
tp_tests_proxy_run_until_prepared (gpointer proxy,
const GQuark *features)
{
GError *error = NULL;
tp_tests_proxy_run_until_prepared_or_failed (proxy, features, &error);
g_assert_no_error (error);
}
/* A GAsyncReadyCallback whose user_data is a GAsyncResult **. It writes a
* reference to the result into that pointer. */
void
tp_tests_result_ready_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
GAsyncResult **result = user_data;
*result = g_object_ref (res);
}
/* Run until *result contains a result. Intended to be used with a pending
* async call that uses tp_tests_result_ready_cb. */
void
tp_tests_run_until_result (GAsyncResult **result)
{
/* not synchronous */
g_assert (*result == NULL);
while (*result == NULL)
g_main_context_iteration (NULL, TRUE);
}
gboolean
tp_tests_proxy_run_until_prepared_or_failed (gpointer proxy,
const GQuark *features,
GError **error)
{
GAsyncResult *result = NULL;
gboolean r;
tp_proxy_prepare_async (proxy, features, tp_tests_result_ready_cb, &result);
tp_tests_run_until_result (&result);
r = tp_proxy_prepare_finish (proxy, result, error);
g_object_unref (result);
return r;
}
gint
tp_tests_run_with_bus (void)
{
GTestDBus *test_dbus = NULL;
gint ret;
g_test_dbus_unset ();
test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
g_test_dbus_add_service_dir (test_dbus, g_getenv ("TP_TESTS_SERVICES_DIR"));
g_test_dbus_up (test_dbus);
ret = g_test_run ();
g_test_dbus_down (test_dbus);
tp_tests_assert_last_unref (&test_dbus);
return ret;
}
GDBusConnection *
tp_tests_dbus_dup_or_die (void)
{
GDBusConnection *d;
GError *error = NULL;
d = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
return d;
}
static void
queue_get_all_cb (TpProxy *proxy G_GNUC_UNUSED,
GHashTable *out G_GNUC_UNUSED,
const GError *error G_GNUC_UNUSED,
gpointer user_data,
GObject *weak_object G_GNUC_UNUSED)
{
g_main_loop_quit (user_data);
}
static void
queue_gdbus_call_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_main_loop_quit (user_data);
}
void
tp_tests_proxy_run_until_dbus_queue_processed (gpointer proxy)
{
GMainLoop *loop = g_main_loop_new (NULL, FALSE);
if (!G_IS_DBUS_CONNECTION (proxy))
{
g_assert (TP_IS_PROXY (proxy));
g_assert (tp_proxy_get_invalidated (proxy) == NULL);
}
/* We used to use Introspect() on @proxy here, but because GDBus implements
* them internally without using a GDBusMethodInvocation, the replies to
* Introspectable, Peer and Properties can "jump the queue" and be
* sent back before things that were already queued for sending.
* There is no other interface that all objects are expected to have,
* so we have to cheat.
*
* I'm relying here on the fact that Properties calls on an interface
* that the object *does* implement don't jump the queue.
*
* https://bugzilla.gnome.org/show_bug.cgi?id=726259 */
if (G_IS_DBUS_CONNECTION (proxy))
{
g_dbus_connection_call (proxy,
"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
g_variant_new ("()"),
G_VARIANT_TYPE ("(s)"),
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, queue_gdbus_call_cb, loop);
}
else if (TP_IS_ACCOUNT (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1, TP_IFACE_ACCOUNT,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_ACCOUNT_MANAGER (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1, TP_IFACE_ACCOUNT_MANAGER,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_CHANNEL (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1, TP_IFACE_CHANNEL,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_CHANNEL_DISPATCHER (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1,
TP_IFACE_CHANNEL_DISPATCHER,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_CHANNEL_DISPATCH_OPERATION (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1,
TP_IFACE_CHANNEL_DISPATCH_OPERATION,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_CHANNEL_REQUEST (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1, TP_IFACE_CHANNEL_REQUEST,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_CLIENT (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1, TP_IFACE_CLIENT,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_CONNECTION (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1, TP_IFACE_CONNECTION,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_CONNECTION_MANAGER (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1,
TP_IFACE_CONNECTION_MANAGER,
queue_get_all_cb, loop, NULL, NULL);
else if (TP_IS_PROTOCOL (proxy))
tp_cli_dbus_properties_call_get_all (proxy, -1, TP_IFACE_PROTOCOL,
queue_get_all_cb, loop, NULL, NULL);
else
g_error ("Don't know how to sync GDBus queue for %s",
G_OBJECT_TYPE_NAME (proxy));
g_main_loop_run (loop);
g_main_loop_unref (loop);
}
void
_test_assert_empty_strv (const char *file,
int line,
gconstpointer strv)
{
const gchar * const *strings = strv;
if (strv != NULL && strings[0] != NULL)
{
guint i;
g_message ("%s:%d: expected empty strv, but got:", file, line);
for (i = 0; strings[i] != NULL; i++)
{
g_message ("* \"%s\"", strings[i]);
}
g_error ("%s:%d: strv wasn't empty (see above for contents",
file, line);
}
}
void
_tp_tests_assert_strv_equals (const char *file,
int line,
const char *expected_desc,
gconstpointer expected_strv,
const char *actual_desc,
gconstpointer actual_strv)
{
const gchar * const *expected = expected_strv;
const gchar * const *actual = actual_strv;
guint i;
g_assert (expected != NULL);
g_assert (actual != NULL);
for (i = 0; expected[i] != NULL || actual[i] != NULL; i++)
{
if (expected[i] == NULL)
{
g_error ("%s:%d: assertion failed: (%s)[%u] == (%s)[%u]: "
"NULL == %s", file, line, expected_desc, i,
actual_desc, i, actual[i]);
}
else if (actual[i] == NULL)
{
g_error ("%s:%d: assertion failed: (%s)[%u] == (%s)[%u]: "
"%s == NULL", file, line, expected_desc, i,
actual_desc, i, expected[i]);
}
else if (tp_strdiff (expected[i], actual[i]))
{
g_error ("%s:%d: assertion failed: (%s)[%u] == (%s)[%u]: "
"%s == %s", file, line, expected_desc, i,
actual_desc, i, expected[i], actual[i]);
}
}
}
void
_tp_tests_assert_bytes_equal (const gchar *file, int line,
GBytes *actual, gconstpointer expected_data,
gsize expected_length)
{
if (expected_length != g_bytes_get_size (actual))
{
g_error ("%s:%d: assertion failed: expected %"G_GSIZE_FORMAT
" bytes, got %"G_GSIZE_FORMAT,
file, line, expected_length, g_bytes_get_size (actual));
}
else if (memcmp (g_bytes_get_data (actual, NULL),
expected_data, expected_length) != 0)
{
g_error (
"%s:%d: assertion failed: expected data didn't match the actual data",
file, line);
}
}
void
tp_tests_create_conn (GType conn_type,
const gchar *account,
gboolean connect,
TpBaseConnection **service_conn,
TpConnection **client_conn)
{
GDBusConnection *dbus;
gchar *name;
gchar *conn_path;
GError *error = NULL;
gboolean ok;
g_assert (service_conn != NULL);
g_assert (client_conn != NULL);
dbus = tp_tests_dbus_dup_or_die ();
*service_conn = tp_tests_object_new_static_class (
conn_type,
"account", account,
"protocol", "simple",
NULL);
g_assert (*service_conn != NULL);
ok = tp_base_connection_register (*service_conn, "simple",
&name, &conn_path, &error);
g_assert_no_error (error);
g_assert (ok);
*client_conn = tp_tests_connection_new (dbus, NULL, conn_path, &error);
g_assert (*client_conn != NULL);
g_assert_no_error (error);
if (connect)
{
GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONNECTED, 0 };
tp_cli_connection_call_connect (*client_conn, -1, NULL, NULL, NULL, NULL);
tp_tests_proxy_run_until_prepared (*client_conn, conn_features);
}
g_free (name);
g_free (conn_path);
g_object_unref (dbus);
}
void
tp_tests_create_and_connect_conn (GType conn_type,
const gchar *account,
TpBaseConnection **service_conn,
TpConnection **client_conn)
{
tp_tests_create_conn (conn_type, account, TRUE, service_conn, client_conn);
}
/* This object exists solely so that tests/tests.supp can ignore "leaked"
* classes. */
gpointer
tp_tests_object_new_static_class (GType type,
...)
{
va_list ap;
GObject *object;
const gchar *first_property;
va_start (ap, type);
first_property = va_arg (ap, const gchar *);
object = g_object_new_valist (type, first_property, ap);
va_end (ap);
return object;
}
static gboolean
time_out (gpointer nil G_GNUC_UNUSED)
{
g_error ("Timed out");
g_assert_not_reached ();
return FALSE;
}
void
tp_tests_abort_after (guint sec)
{
gboolean debugger = FALSE;
gchar *contents;
if (g_file_get_contents ("/proc/self/status", &contents, NULL, NULL))
{
/* http://www.youtube.com/watch?v=SXmv8quf_xM */
#define TRACER_T "\nTracerPid:\t"
gchar *line = strstr (contents, TRACER_T);
if (line != NULL)
{
gchar *value = line + strlen (TRACER_T);
if (value[0] != '0' || value[1] != '\n')
debugger = TRUE;
}
g_free (contents);
}
if (g_getenv ("TP_TESTS_NO_TIMEOUT") != NULL || debugger)
return;
g_timeout_add_seconds (sec, time_out, NULL);
#ifdef G_OS_UNIX
/* On Unix, we can kill the process more reliably; this is a safety-catch
* in case it deadlocks or something, in which case the main loop won't be
* processed. The default handler for SIGALRM is process termination. */
alarm (sec + 2);
#endif
}
void
tp_tests_init (int *argc,
char ***argv)
{
tp_tests_abort_after (30);
tp_debug_set_flags ("all");
g_test_init (argc, argv, NULL);
}
void
_tp_destroy_socket_control_list (gpointer data)
{
GArray *tab = data;
g_array_unref (tab);
}
GValue *
_tp_create_local_socket (TpSocketAddressType address_type,
TpSocketAccessControl access_control,
GSocketService **service,
gchar **unix_address,
gchar **unix_tmpdir,
GError **error)
{
gboolean success;
GSocketAddress *address, *effective_address;
GValue *address_gvalue;
g_assert (service != NULL);
g_assert (unix_address != NULL);
switch (access_control)
{
case TP_SOCKET_ACCESS_CONTROL_LOCALHOST:
case TP_SOCKET_ACCESS_CONTROL_CREDENTIALS:
case TP_SOCKET_ACCESS_CONTROL_PORT:
break;
default:
g_assert_not_reached ();
}
switch (address_type)
{
#ifdef HAVE_GIO_UNIX
case TP_SOCKET_ADDRESS_TYPE_UNIX:
{
GError *e = NULL;
gchar *dir = g_dir_make_tmp ("tp-glib-tests.XXXXXX", &e);
gchar *name;
g_assert_no_error (e);
name = g_build_filename (dir, "s", NULL);
address = g_unix_socket_address_new (name);
g_free (name);
if (unix_tmpdir != NULL)
*unix_tmpdir = dir;
else
g_free (dir);
break;
}
#endif
case TP_SOCKET_ADDRESS_TYPE_IPV4:
case TP_SOCKET_ADDRESS_TYPE_IPV6:
{
GInetAddress *localhost;
localhost = g_inet_address_new_loopback (
address_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ?
G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
address = g_inet_socket_address_new (localhost, 0);
g_object_unref (localhost);
break;
}
default:
g_assert_not_reached ();
}
*service = g_socket_service_new ();
success = g_socket_listener_add_address (
G_SOCKET_LISTENER (*service),
address, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT,
NULL, &effective_address, NULL);
g_assert (success);
switch (address_type)
{
#ifdef HAVE_GIO_UNIX
case TP_SOCKET_ADDRESS_TYPE_UNIX:
*unix_address = g_strdup (g_unix_socket_address_get_path (
G_UNIX_SOCKET_ADDRESS (effective_address)));
address_gvalue = tp_g_value_slice_new_bytes (
g_unix_socket_address_get_path_len (
G_UNIX_SOCKET_ADDRESS (effective_address)),
g_unix_socket_address_get_path (
G_UNIX_SOCKET_ADDRESS (effective_address)));
break;
#endif
case TP_SOCKET_ADDRESS_TYPE_IPV4:
case TP_SOCKET_ADDRESS_TYPE_IPV6:
*unix_address = NULL;
address_gvalue = tp_g_value_slice_new_take_boxed (
TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4,
dbus_g_type_specialized_construct (
TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4));
dbus_g_type_struct_set (address_gvalue,
0, address_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ?
"127.0.0.1" : "::1",
1, g_inet_socket_address_get_port (
G_INET_SOCKET_ADDRESS (effective_address)),
G_MAXUINT);
break;
default:
g_assert_not_reached ();
}
g_object_unref (address);
g_object_unref (effective_address);
return address_gvalue;
}
void
tp_tests_connection_assert_disconnect_succeeds (TpConnection *connection)
{
GAsyncResult *result = NULL;
GError *error = NULL;
gboolean ok;
tp_connection_disconnect_async (connection, tp_tests_result_ready_cb,
&result);
tp_tests_run_until_result (&result);
ok = tp_connection_disconnect_finish (connection, result, &error);
g_assert_no_error (error);
g_assert (ok);
g_object_unref (result);
}
static void
one_contact_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
TpConnection *connection = (TpConnection *) object;
TpContact **contact_loc = user_data;
GError *error = NULL;
*contact_loc = tp_connection_dup_contact_by_id_finish (connection, result,
&error);
g_assert_no_error (error);
g_assert (TP_IS_CONTACT (*contact_loc));
}
TpContact *
tp_tests_connection_run_until_contact_by_id (TpConnection *connection,
const gchar *id,
const GQuark *features)
{
TpContact *contact = NULL;
tp_connection_dup_contact_by_id_async (connection, id, features,
one_contact_cb, &contact);
while (contact == NULL)
g_main_context_iteration (NULL, TRUE);
return contact;
}
void
tp_tests_channel_assert_expect_members (TpChannel *channel,
TpIntset *expected_members)
{
GPtrArray *contacts;
TpIntset *members;
guint i;
members = tp_intset_new ();
contacts = tp_channel_group_dup_members (channel);
if (contacts != NULL)
{
for (i = 0; i < contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (contacts, i);
tp_intset_add (members, tp_contact_get_handle (contact));
}
}
g_assert (tp_intset_is_equal (members, expected_members));
g_ptr_array_unref (contacts);
tp_intset_destroy (members);
}
TpConnection *
tp_tests_connection_new (GDBusConnection *dbus,
const gchar *bus_name,
const gchar *object_path,
GError **error)
{
TpClientFactory *factory;
gchar *dup_path = NULL;
TpConnection *ret = NULL;
g_return_val_if_fail (G_IS_DBUS_CONNECTION (dbus), NULL);
g_return_val_if_fail (object_path != NULL ||
(bus_name != NULL && bus_name[0] != ':'), NULL);
if (object_path == NULL)
{
dup_path = g_strdelimit (g_strdup_printf ("/%s", bus_name), ".", '/');
object_path = dup_path;
}
if (!tp_dbus_check_valid_object_path (object_path, error))
goto finally;
factory = tp_automatic_client_factory_new (dbus);
ret = tp_client_factory_ensure_connection (factory,
object_path, NULL, error);
g_object_unref (factory);
finally:
g_free (dup_path);
return ret;
}
TpAccount *
tp_tests_account_new (GDBusConnection *dbus,
const gchar *object_path,
GError **error)
{
TpClientFactory *factory;
TpAccount *ret;
if (!tp_dbus_check_valid_object_path (object_path, error))
return NULL;
factory = tp_automatic_client_factory_new (dbus);
ret = tp_client_factory_ensure_account (factory,
object_path, NULL, error);
g_object_unref (factory);
return ret;
}
TpChannel *
tp_tests_channel_new (TpConnection *conn,
const gchar *object_path,
const gchar *optional_channel_type,
TpEntityType optional_entity_type,
TpHandle optional_handle,
GError **error)
{
TpChannel *ret;
GHashTable *asv;
asv = tp_asv_new (NULL, NULL);
if (optional_channel_type != NULL)
{
tp_asv_set_string (asv,
TP_PROP_CHANNEL_CHANNEL_TYPE, optional_channel_type);
}
if (optional_entity_type != TP_ENTITY_TYPE_NONE)
{
tp_asv_set_uint32 (asv,
TP_PROP_CHANNEL_TARGET_ENTITY_TYPE, optional_entity_type);
}
if (optional_handle != 0)
{
tp_asv_set_uint32 (asv,
TP_PROP_CHANNEL_TARGET_HANDLE, optional_handle);
}
ret = tp_tests_channel_new_from_properties (conn, object_path, asv, error);
g_hash_table_unref (asv);
return ret;
}
TpChannel *
tp_tests_channel_new_from_properties (TpConnection *conn,
const gchar *object_path,
const GHashTable *immutable_properties,
GError **error)
{
TpClientFactory *factory;
if (!tp_dbus_check_valid_object_path (object_path, error))
return NULL;
factory = tp_proxy_get_factory (conn);
return tp_client_factory_ensure_channel (factory, conn,
object_path, tp_asv_to_vardict (immutable_properties), error);
}
GHashTable *
tp_tests_dup_channel_props_asv (TpChannel *channel)
{
GVariant *variant;
GHashTable *asv;
GValue v = G_VALUE_INIT;
g_assert (channel != NULL);
variant = tp_channel_dup_immutable_properties (channel);
dbus_g_value_parse_g_variant (variant, &v);
asv = g_value_dup_boxed (&v);
g_variant_unref (variant);
g_value_unset (&v);
return asv;
}
void
_tp_tests_assert_last_unref (gpointer obj,
const gchar *file,
int line)
{
GWeakRef weak;
g_weak_ref_init (&weak, obj);
g_object_unref (obj);
obj = g_weak_ref_get (&weak);
if (obj != NULL)
g_error ("%s:%d: %s %p should not have had any more references",
file, line, G_OBJECT_TYPE_NAME (obj), obj);
}
/*
* tp_tests_await_last_unref:
* @op: a pointer to a #GObject
*
* Set @op to point to %NULL, release one reference to the object to which
* it previously pointed, and wait for that object to be freed by iterating
* the default (NULL) main-context.
*
* For instance, suppose you have this code, and you want to adapt it to
* assert that @obj is not leaked:
*
* |[
* obj = my_object_new ();
* my_object_do_thing_async (obj, NULL, NULL);
* g_clear_object (&obj);
* ]|
*
* Because #GAsyncResult async calls take a ref to the
* source object for the duration of the async call, this will cause
* an assertion failure:
*
* |[
* obj = my_object_new ();
* my_object_do_thing_async (obj, NULL, NULL);
* tp_tests_assert_last_unref (&obj);
* ]|
*
* but this is OK, and will wait for the `do_thing_async` call to finish:
*
* |[
* obj = my_object_new ();
* my_object_do_thing_async (obj, NULL, NULL);
* tp_tests_await_last_unref (&obj);
* ]|
*/
/* Really a macro, this is its implementation. @obj is the original `*op` */
void
_tp_tests_await_last_unref (gpointer obj,
const gchar *file,
int line)
{
GWeakRef weak;
g_weak_ref_init (&weak, obj);
g_object_unref (obj);
obj = g_weak_ref_get (&weak);
while (obj != NULL)
{
DEBUG ("%s %p still has references, waiting...",
G_OBJECT_TYPE_NAME (obj), obj);
g_object_unref (obj);
g_main_context_iteration (NULL, TRUE);
obj = g_weak_ref_get (&weak);
}
}
GDBusConnection *
tp_tests_get_private_bus (void)
{
GDBusConnection *ret;
GError *error = NULL;
ret = g_dbus_connection_new_for_address_sync (
g_getenv ("DBUS_SESSION_BUS_ADDRESS"),
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
NULL, NULL, &error);
g_assert_no_error (error);
return ret;
}