#include #include #include #include #include #include /* for _invalidated etc. */ #include #include "tests/lib/myassert.h" #include "tests/lib/stub-object.h" #include "tests/lib/util.h" /* just for convenience, since it's used a lot */ #define PTR(ui) GUINT_TO_POINTER(ui) /* state tracking */ static GMainLoop *mainloop; static TpDBusDaemon *a; static TpDBusDaemon *b; static TpDBusDaemon *c; static TpDBusDaemon *d; static TpDBusDaemon *e; static TpDBusDaemon *f; static TpDBusDaemon *g; static TpDBusDaemon *h; static TpDBusDaemon *i; static TpDBusDaemon *j; static TpDBusDaemon *k; static TpDBusDaemon *z; static TpIntset *method_ok; static TpIntset *method_error; static TpIntset *freed_user_data; static gpointer copy_of_d; static gpointer copy_of_g; static gpointer copy_of_h; static gpointer copy_of_i; enum { TEST_A, TEST_B, TEST_C, TEST_D, TEST_E, TEST_F, TEST_G, TEST_H, TEST_I, TEST_J, TEST_K, TEST_Z = 25, N_DAEMONS }; static void destroy_user_data (gpointer user_data) { guint which = GPOINTER_TO_UINT (user_data); g_message ("User data %c destroyed", 'A' + which); tp_intset_add (freed_user_data, which); } static void j_stub_destroyed (gpointer data, GObject *stub) { destroy_user_data (data); } static void k_stub_destroyed (gpointer data, GObject *stub) { TpProxyPendingCall **p = data; tp_proxy_pending_call_cancel (*p); } static void listed_names (TpDBusDaemon *proxy, const gchar **names, const GError *error, gpointer user_data, GObject *weak_object) { guint which = GPOINTER_TO_UINT (user_data); TpDBusDaemon *want_proxy = NULL; GObject *want_object = NULL; if (error == NULL) { g_message ("ListNames() succeeded (first name: %s), according to " "user_data this was on proxy #%d '%c'", *names, which, 'a' + which); tp_intset_add (method_ok, which); switch (which) { case TEST_A: want_proxy = a; want_object = (GObject *) z; break; case TEST_C: want_proxy = c; want_object = NULL; break; case TEST_D: want_proxy = copy_of_d; want_object = NULL; break; case TEST_G: want_proxy = copy_of_g; want_object = (GObject *) copy_of_g; break; case TEST_Z: want_proxy = z; want_object = (GObject *) a; break; default: MYASSERT (FALSE, ": %c (%p) method call succeeded, which shouldn't " "happen", 'a' + which, proxy); return; } } else { g_message ("ListNames() failed (%s), according to " "user_data this was on proxy #%d '%c'", error->message, which, 'a' + which); tp_intset_add (method_error, which); switch (which) { case TEST_C: want_proxy = c; want_object = NULL; break; case TEST_F: want_proxy = f; want_object = NULL; break; default: MYASSERT (FALSE, ": %c (%p) method call failed, which shouldn't " "happen", 'a' + which, proxy); } } MYASSERT (proxy == want_proxy, ": Proxy is %p, expected %p", proxy, want_proxy); MYASSERT (weak_object == want_object, ": Weak object is %p, expected %p", weak_object, want_object); if (which == TEST_Z) g_main_loop_quit (mainloop); } static void noc (TpDBusDaemon *proxy, const gchar *name, const gchar *old, const gchar *new, gpointer user_data, GObject *weak_object) { /* do nothing */ } int main (int argc, char **argv) { GObject *b_stub, *i_stub, *j_stub, *k_stub; GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Because I said so" }; TpProxyPendingCall *pc; gpointer tmp_obj; g_type_init (); tp_tests_abort_after (10); tp_debug_set_flags ("all"); freed_user_data = tp_intset_sized_new (N_DAEMONS); method_ok = tp_intset_sized_new (N_DAEMONS); method_error = tp_intset_sized_new (N_DAEMONS); mainloop = g_main_loop_new (NULL, FALSE); /* We use TpDBusDaemon because it's a convenient concrete subclass of * TpProxy. */ g_message ("Creating proxies"); a = tp_tests_dbus_daemon_dup_or_die (); g_message ("a=%p", a); b = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("b=%p", b); c = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("c=%p", c); d = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("d=%p", d); e = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("e=%p", e); f = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("f=%p", f); g = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("g=%p", g); h = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("h=%p", h); i = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("i=%p", i); j = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("j=%p", j); k = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("k=%p", k); z = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a)); g_message ("z=%p", z); /* a survives */ g_message ("Starting call on a"); tp_cli_dbus_daemon_call_list_names (a, -1, listed_names, PTR (TEST_A), destroy_user_data, (GObject *) z); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_A), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_A), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_A), ""); /* b gets its pending call cancelled because the weak object is * destroyed */ b_stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (), NULL); g_message ("Starting call on b"); tp_cli_dbus_daemon_call_list_names (b, -1, listed_names, PTR (TEST_B), destroy_user_data, b_stub); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_B), ""); g_object_unref (b_stub); MYASSERT (!tp_intset_is_member (method_ok, TEST_B), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_B), ""); /* c is explicitly invalidated for an application-specific reason, * but its call still proceeds */ g_message ("Starting call on c"); tp_cli_dbus_daemon_call_list_names (c, -1, listed_names, PTR (TEST_C), destroy_user_data, NULL); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_C), ""); g_message ("Forcibly invalidating c"); tp_proxy_invalidate ((TpProxy *) c, &err); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_C), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_C), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_C), ""); /* d gets unreferenced, but survives long enough for the call to complete * successfully later, because the pending call holds a reference */ g_message ("Starting call on d"); tp_cli_dbus_daemon_call_list_names (d, -1, listed_names, PTR (TEST_D), destroy_user_data, NULL); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_D), ""); g_message ("Unreferencing d"); copy_of_d = d; g_object_add_weak_pointer (copy_of_d, ©_of_d); g_object_unref (d); d = NULL; MYASSERT (copy_of_d != NULL, ""); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_D), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_D), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_D), ""); /* e gets its method call cancelled explicitly */ g_message ("Starting call on e"); pc = tp_cli_dbus_daemon_call_list_names (e, -1, listed_names, PTR (TEST_E), destroy_user_data, NULL); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_E), ""); g_message ("Cancelling call on e"); tp_proxy_pending_call_cancel (pc); MYASSERT (!tp_intset_is_member (method_ok, TEST_E), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_E), ""); /* f's method call fails with an error, because it's implicitly * invalidated by its DBusGProxy being destroyed. * * Note that this test case exploits implementation details of dbus-glib. * If it stops working after a dbus-glib upgrade, that's probably why. */ g_message ("Starting call on f"); tp_cli_dbus_daemon_call_list_names (f, -1, listed_names, PTR (TEST_F), destroy_user_data, NULL); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_F), ""); g_message ("Forcibly disposing f's DBusGProxy to simulate name owner loss"); tmp_obj = tp_proxy_borrow_interface_by_id ((TpProxy *) f, TP_IFACE_QUARK_DBUS_DAEMON, NULL); MYASSERT (tmp_obj != NULL, ""); g_object_run_dispose (tmp_obj); /* the callback will be queued (to avoid reentrancy), so we don't get it * until the main loop runs */ MYASSERT (!tp_intset_is_member (freed_user_data, TEST_F), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_F), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_F), ""); /* g gets unreferenced, but survives long enough for the call to complete * successfully later, because the pending call holds a reference; * however, unlike case D, here the pending call weakly references the * proxy. This is never necessary, but is an interesting corner case that * should be tested. */ g_message ("Starting call on g"); tp_cli_dbus_daemon_call_list_names (g, -1, listed_names, PTR (TEST_G), destroy_user_data, (GObject *) g); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_G), ""); g_message ("Unreferencing g"); copy_of_g = g; g_object_add_weak_pointer (copy_of_g, ©_of_g); g_object_unref (g); g = NULL; MYASSERT (copy_of_g != NULL, ""); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_G), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_G), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_G), ""); /* h gets unreferenced, *and* the call is cancelled (regression test for * fd.o #14576) */ g_message ("Starting call on h"); pc = tp_cli_dbus_daemon_call_list_names (h, -1, listed_names, PTR (TEST_H), destroy_user_data, NULL); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_H), ""); g_message ("Unreferencing h"); copy_of_h = h; g_object_add_weak_pointer (copy_of_h, ©_of_h); g_object_unref (h); h = NULL; MYASSERT (copy_of_h != NULL, ""); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_H), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_H), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_H), ""); g_message ("Cancelling call on h"); tp_proxy_pending_call_cancel (pc); MYASSERT (!tp_intset_is_member (method_ok, TEST_H), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_H), ""); /* i gets its pending call cancelled because i_stub is * destroyed, *and* the pending call holds the last reference to it, * *and* there is a signal connection * (used to reproduce fd.o #14750 - see case h in test-disconnection.c * for the minimal regression test) */ i_stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (), NULL); tp_cli_dbus_daemon_connect_to_name_owner_changed (i, noc, PTR (TEST_I), NULL, i_stub, NULL); g_message ("Starting call on i"); tp_cli_dbus_daemon_call_list_names (i, -1, listed_names, PTR (TEST_I), destroy_user_data, i_stub); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_I), ""); tp_cli_dbus_daemon_connect_to_name_owner_changed (i, noc, PTR (TEST_I), NULL, i_stub, NULL); g_message ("Unreferencing i"); copy_of_i = i; g_object_add_weak_pointer (copy_of_i, ©_of_i); g_object_unref (i); i = NULL; MYASSERT (copy_of_i != NULL, ""); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_I), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_I), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_I), ""); g_object_unref (i_stub); MYASSERT (!tp_intset_is_member (method_ok, TEST_I), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_I), ""); /* j gets its pending call cancelled explicitly, and j_stub is * destroyed in response (related to fd.o #14750) */ j_stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (), NULL); g_object_weak_ref (j_stub, j_stub_destroyed, PTR (TEST_J)); g_message ("Starting call on j"); pc = tp_cli_dbus_daemon_call_list_names (j, -1, listed_names, j_stub, g_object_unref, j_stub); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_J), ""); g_message ("Cancelling call on j"); tp_proxy_pending_call_cancel (pc); MYASSERT (!tp_intset_is_member (method_ok, TEST_J), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_J), ""); /* k gets its pending call cancelled explicitly because its weak object * is destroyed, meaning there are simultaneously two reasons for it * to become cancelled (equivalent to fd.o#14750, but for pending calls * rather than signal connections) */ k_stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (), NULL); g_message ("Starting call on k"); g_object_weak_ref (k_stub, k_stub_destroyed, &pc); tp_cli_dbus_daemon_call_list_names (k, -1, listed_names, PTR (TEST_K), destroy_user_data, k_stub); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_K), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_K), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_K), ""); g_object_unref (k_stub); MYASSERT (!tp_intset_is_member (method_ok, TEST_K), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_K), ""); /* z survives too; we assume that method calls succeed in order, * so when z has had its reply, we can stop the main loop */ g_message ("Starting call on z"); tp_cli_dbus_daemon_call_list_names (z, -1, listed_names, PTR (TEST_Z), destroy_user_data, (GObject *) a); MYASSERT (!tp_intset_is_member (freed_user_data, TEST_Z), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_Z), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_Z), ""); g_message ("Running main loop"); g_main_loop_run (mainloop); g_main_loop_unref (mainloop); /* now that the calls have been delivered, d will finally have gone away */ MYASSERT (tp_intset_is_member (freed_user_data, TEST_D), ""); MYASSERT (tp_intset_is_member (method_ok, TEST_D), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_D), ""); MYASSERT (copy_of_d == NULL, ""); /* ... and g too */ MYASSERT (tp_intset_is_member (freed_user_data, TEST_G), ""); MYASSERT (tp_intset_is_member (method_ok, TEST_G), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_G), ""); MYASSERT (copy_of_g == NULL, ""); /* also, F will have been invalidated */ MYASSERT (tp_intset_is_member (freed_user_data, TEST_F), ""); MYASSERT (!tp_intset_is_member (method_ok, TEST_F), ""); MYASSERT (tp_intset_is_member (method_error, TEST_F), ""); /* Now that its call has been cancelled, h will have gone away. Likewise * for i */ MYASSERT (copy_of_h == NULL, ""); MYASSERT (copy_of_i == NULL, ""); /* User data for all the cancelled calls has also gone away */ MYASSERT (tp_intset_is_member (freed_user_data, TEST_B), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_E), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_H), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_I), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_J), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_K), ""); /* the calls have been delivered to A, C and Z by now */ MYASSERT (tp_intset_is_member (freed_user_data, TEST_A), ""); MYASSERT (tp_intset_is_member (method_ok, TEST_A), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_A), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_C), ""); MYASSERT (tp_intset_is_member (method_ok, TEST_C), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_C), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_Z), ""); MYASSERT (tp_intset_is_member (method_ok, TEST_Z), ""); MYASSERT (!tp_intset_is_member (method_error, TEST_Z), ""); g_message ("Dereferencing remaining proxies"); g_object_unref (a); a = NULL; g_object_unref (b); b = NULL; g_object_unref (c); c = NULL; MYASSERT (d == NULL, ""); g_object_unref (e); e = NULL; g_object_unref (f); f = NULL; MYASSERT (g == NULL, ""); MYASSERT (h == NULL, ""); MYASSERT (i == NULL, ""); g_object_unref (j); j = NULL; g_object_unref (k); k = NULL; g_object_unref (z); z = NULL; /* we should already have checked each of these at least once, but just to * make sure we have a systematic test that all user data is freed... */ MYASSERT (tp_intset_is_member (freed_user_data, TEST_A), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_B), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_C), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_D), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_E), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_F), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_G), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_H), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_I), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_J), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_K), ""); MYASSERT (tp_intset_is_member (freed_user_data, TEST_Z), ""); tp_intset_destroy (freed_user_data); tp_intset_destroy (method_ok); tp_intset_destroy (method_error); return 0; }