1 #include "config.h"
2
3 #include <telepathy-glib/dbus.h>
4 #include <telepathy-glib/debug.h>
5 #include <telepathy-glib/errors.h>
6 #include <telepathy-glib/interfaces.h>
7 #include <telepathy-glib/intset.h>
8 #include <telepathy-glib/proxy-subclass.h> /* for _invalidated etc. */
9 #include <telepathy-glib/util.h>
10
11 #include "tests/lib/myassert.h"
12 #include "tests/lib/stub-object.h"
13 #include "tests/lib/util.h"
14
15 /* just for convenience, since it's used a lot */
16 #define PTR(ui) GUINT_TO_POINTER(ui)
17
18 /* state tracking */
19 static GMainLoop *mainloop;
20 static TpDBusDaemon *a;
21 static TpDBusDaemon *b;
22 static TpDBusDaemon *c;
23 static TpDBusDaemon *d;
24 static TpDBusDaemon *e;
25 static TpDBusDaemon *f;
26 static TpDBusDaemon *g;
27 static TpDBusDaemon *h;
28 static TpDBusDaemon *z;
29 static TpIntset *caught_signal;
30 static TpIntset *freed_user_data;
31
32 enum {
33 TEST_A,
34 TEST_B,
35 TEST_C,
36 TEST_D,
37 TEST_E,
38 TEST_F,
39 TEST_G,
40 TEST_H,
41 TEST_Z = 25,
42 N_DAEMONS
43 };
44
45 static void
h_stub_destroyed(gpointer data,GObject * stub)46 h_stub_destroyed (gpointer data,
47 GObject *stub)
48 {
49 TpProxySignalConnection **p = data;
50
51 tp_proxy_signal_connection_disconnect (*p);
52 }
53
54 static void
destroy_user_data(gpointer user_data)55 destroy_user_data (gpointer user_data)
56 {
57 guint which = GPOINTER_TO_UINT (user_data);
58 g_message ("User data %c destroyed", 'A' + which);
59 MYASSERT (!tp_intset_is_member (freed_user_data, which), "");
60 tp_intset_add (freed_user_data, which);
61 }
62
63 static void
requested_name(TpDBusDaemon * proxy,guint result,const GError * error,gpointer user_data,GObject * weak_object)64 requested_name (TpDBusDaemon *proxy,
65 guint result,
66 const GError *error,
67 gpointer user_data,
68 GObject *weak_object)
69 {
70 g_message ("RequestName raised %s",
71 (error == NULL ? "no error" : error->message));
72 /* we're on a private bus, so certainly nobody else should own this name */
73 g_assert_no_error ((GError *) error);
74 MYASSERT (result == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, ": %u", result);
75 }
76
77 static void
prop_changed(TpProxy * proxy,const GPtrArray * properties,gpointer user_data,GObject * weak_object)78 prop_changed (TpProxy *proxy,
79 const GPtrArray *properties,
80 gpointer user_data,
81 GObject *weak_object)
82 {
83 g_error ("prop_changed called - a signal connection which should have "
84 "failed has succeeded. Args: proxy=%p user_data=%p", proxy, user_data);
85 }
86
87 static void
dummy_noc(TpDBusDaemon * proxy,const gchar * name,const gchar * old,const gchar * new,gpointer user_data,GObject * weak_object)88 dummy_noc (TpDBusDaemon *proxy,
89 const gchar *name,
90 const gchar *old,
91 const gchar *new,
92 gpointer user_data,
93 GObject *weak_object)
94 {
95 g_error ("dummy_noc called - a signal connection which should have "
96 "failed has succeeded. Args: proxy=%p user_data=%p", proxy, user_data);
97 }
98
99 static void
noc(TpDBusDaemon * proxy,const gchar * name,const gchar * old,const gchar * new,gpointer user_data,GObject * weak_object)100 noc (TpDBusDaemon *proxy,
101 const gchar *name,
102 const gchar *old,
103 const gchar *new,
104 gpointer user_data,
105 GObject *weak_object)
106 {
107 guint which = GPOINTER_TO_UINT (user_data);
108 TpDBusDaemon *want_proxy = NULL;
109 GObject *want_object = NULL;
110
111 g_message ("Caught signal (%s: %s -> %s) with proxy #%d '%c' according to "
112 "user_data", name, old, new, which, 'a' + which);
113 g_message ("Proxy is %p, weak object is %p", proxy,
114 weak_object);
115 tp_intset_add (caught_signal, which);
116
117 switch (which)
118 {
119 case TEST_A:
120 want_proxy = a;
121 want_object = (GObject *) z;
122 break;
123 case TEST_Z:
124 want_proxy = z;
125 want_object = (GObject *) a;
126 break;
127 default:
128 g_error ("%c (%p) got the signal, which shouldn't have happened",
129 'a' + which, proxy);
130 }
131
132 g_message ("Expecting proxy %p, weak object %p", want_proxy, want_object);
133
134 MYASSERT (proxy == want_proxy, ": %p != %p", proxy, want_proxy);
135 MYASSERT (weak_object == want_object, ": %p != %p", weak_object,
136 want_object);
137
138 if (tp_intset_is_member (caught_signal, TEST_A) &&
139 tp_intset_is_member (caught_signal, TEST_Z))
140 {
141 /* we've had all the signals we're going to */
142 g_main_loop_quit (mainloop);
143 }
144 }
145
146 static void
set_freed(gpointer user_data)147 set_freed (gpointer user_data)
148 {
149 gboolean *boolptr = user_data;
150
151 MYASSERT (*boolptr == FALSE, "");
152 *boolptr = TRUE;
153 }
154
155 int
main(int argc,char ** argv)156 main (int argc,
157 char **argv)
158 {
159 GObject *stub;
160 GError *error_out = NULL;
161 GError err = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Because I said so" };
162 TpProxySignalConnection *sc;
163 gpointer tmp_obj;
164 gboolean freed = FALSE;
165
166 tp_tests_abort_after (10);
167 tp_debug_set_flags ("all");
168
169 freed_user_data = tp_intset_sized_new (N_DAEMONS);
170 caught_signal = tp_intset_sized_new (N_DAEMONS);
171
172 mainloop = g_main_loop_new (NULL, FALSE);
173
174 /* We use TpDBusDaemon because it's a convenient concrete subclass of
175 * TpProxy. */
176 g_message ("Creating proxies");
177 a = tp_tests_dbus_daemon_dup_or_die ();
178 g_message ("a=%p", a);
179 b = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
180 g_message ("b=%p", b);
181 c = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
182 g_message ("c=%p", c);
183 d = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
184 g_message ("d=%p", d);
185 e = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
186 g_message ("e=%p", e);
187 f = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
188 g_message ("f=%p", f);
189 g = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
190 g_message ("g=%p", g);
191 h = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
192 g_message ("h=%p", h);
193 z = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
194 g_message ("z=%p", z);
195
196 /* a survives */
197 g_message ("Connecting signal to a");
198 tp_cli_dbus_daemon_connect_to_name_owner_changed (a, noc, PTR (TEST_A),
199 destroy_user_data, (GObject *) z, &error_out);
200 g_assert_no_error (error_out);
201
202 /* assert that connecting to a signal on an interface we don't have fails */
203 freed = FALSE;
204 tp_cli_properties_interface_connect_to_properties_changed (a, prop_changed,
205 &freed, set_freed, NULL, &error_out);
206 MYASSERT (freed, "");
207 MYASSERT (error_out != NULL, "");
208 MYASSERT (error_out->code == TP_DBUS_ERROR_NO_INTERFACE, "");
209 g_error_free (error_out);
210 error_out = NULL;
211
212 /* b gets its signal connection cancelled because stub is
213 * destroyed */
214 stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (),
215 NULL);
216 g_message ("Connecting signal to b");
217 tp_cli_dbus_daemon_connect_to_name_owner_changed (b, noc, PTR (TEST_B),
218 destroy_user_data, stub, &error_out);
219 g_assert_no_error (error_out);
220 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_B), "");
221 g_object_unref (stub);
222
223 /* c gets its signal connection cancelled because it's explicitly
224 * invalidated */
225 g_message ("Connecting signal to c");
226 tp_cli_dbus_daemon_connect_to_name_owner_changed (c, noc, PTR (TEST_C),
227 destroy_user_data, NULL, &error_out);
228 g_assert_no_error (error_out);
229 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_C), "");
230 g_message ("Forcibly invalidating c");
231 tp_proxy_invalidate ((TpProxy *) c, &err);
232 /* assert that connecting to a signal on an invalid proxy fails */
233 freed = FALSE;
234 tp_cli_dbus_daemon_connect_to_name_owner_changed (c, dummy_noc, &freed,
235 set_freed, NULL, &error_out);
236 MYASSERT (freed, "");
237 MYASSERT (error_out != NULL, "");
238 g_message ("%d: %d: %s", error_out->domain, error_out->code,
239 error_out->message);
240 MYASSERT (error_out->code == err.code, "%d != %d", error_out->code,
241 err.code);
242 g_error_free (error_out);
243 error_out = NULL;
244
245 /* d gets its signal connection cancelled because it's
246 * implicitly invalidated by being destroyed */
247 g_message ("Connecting signal to d");
248 tp_cli_dbus_daemon_connect_to_name_owner_changed (d, noc, PTR (TEST_D),
249 destroy_user_data, NULL, &error_out);
250 g_assert_no_error (error_out);
251 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_D), "");
252 g_message ("Destroying d");
253 tmp_obj = d;
254 g_object_add_weak_pointer (tmp_obj, &tmp_obj);
255 g_object_unref (d);
256 MYASSERT (tmp_obj == NULL, "");
257 d = NULL;
258
259 /* e gets its signal connection cancelled explicitly */
260 g_message ("Connecting signal to e");
261 sc = tp_cli_dbus_daemon_connect_to_name_owner_changed (e, noc, PTR (TEST_E),
262 destroy_user_data, NULL, &error_out);
263 g_assert_no_error (error_out);
264 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_E), "");
265 g_message ("Disconnecting signal from e");
266 tp_proxy_signal_connection_disconnect (sc);
267
268 /* f gets its signal connection cancelled because it's implicitly
269 * invalidated by its DBusGProxy being destroyed.
270 *
271 * Note that this test case exploits implementation details of dbus-glib.
272 * If it stops working after a dbus-glib upgrade, that's probably why. */
273 g_message ("Connecting signal to f");
274 tp_cli_dbus_daemon_connect_to_name_owner_changed (f, noc, PTR (TEST_F),
275 destroy_user_data, NULL, &error_out);
276 g_assert_no_error (error_out);
277 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_F), "");
278 g_message ("Forcibly disposing f's DBusGProxy to simulate name owner loss");
279 tmp_obj = tp_proxy_borrow_interface_by_id ((TpProxy *) f,
280 TP_IFACE_QUARK_DBUS_DAEMON, NULL);
281 MYASSERT (tmp_obj != NULL, "");
282 g_object_run_dispose (tmp_obj);
283 /* assert that connecting to a signal on an invalid proxy fails */
284 freed = FALSE;
285 tp_cli_dbus_daemon_connect_to_name_owner_changed (f, dummy_noc, &freed,
286 set_freed, NULL, &error_out);
287 MYASSERT (freed, "");
288 MYASSERT (error_out != NULL, "");
289 MYASSERT (error_out->code == DBUS_GERROR_NAME_HAS_NO_OWNER, "");
290 g_error_free (error_out);
291 error_out = NULL;
292
293 /* g gets its signal connection cancelled because it's
294 * implicitly invalidated by being destroyed; unlike d, the signal
295 * connection weakly references the proxy. This is never necessary, but is
296 * an interesting corner case that should be tested. */
297 g_message ("Connecting signal to g");
298 tp_cli_dbus_daemon_connect_to_name_owner_changed (g, noc, PTR (TEST_G),
299 destroy_user_data, (GObject *) g, &error_out);
300 g_assert_no_error (error_out);
301 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_G), "");
302 g_message ("Destroying g");
303 tmp_obj = g;
304 g_object_add_weak_pointer (tmp_obj, &tmp_obj);
305 g_object_unref (g);
306 MYASSERT (tmp_obj == NULL, "");
307 g = NULL;
308
309 /* h gets its signal connection cancelled because its weak object is
310 * destroyed, meaning there are simultaneously two reasons for it to become
311 * cancelled (fd.o#14750) */
312 stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (),
313 NULL);
314 g_object_weak_ref (stub, h_stub_destroyed, &sc);
315 g_message ("Connecting signal to h");
316 tp_cli_dbus_daemon_connect_to_name_owner_changed (h, noc, PTR (TEST_H),
317 destroy_user_data, stub, &error_out);
318 g_assert_no_error (error_out);
319 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_H), "");
320 g_object_unref (stub);
321
322 /* z survives; we assume that the signals are delivered in either
323 * forward or reverse order, so if both a and z have had their signal, we
324 * can stop the main loop */
325 g_message ("Connecting signal to z");
326 tp_cli_dbus_daemon_connect_to_name_owner_changed (z, noc, PTR (TEST_Z),
327 destroy_user_data, (GObject *) a, &error_out);
328 g_assert_no_error (error_out);
329
330 /* make sure a NameOwnerChanged signal occurs */
331 g_message ("Requesting name");
332 tp_cli_dbus_daemon_call_request_name (a, -1, "com.example.NameTest",
333 0, requested_name, NULL, NULL, NULL);
334
335 g_message ("Running main loop");
336 g_main_loop_run (mainloop);
337 g_main_loop_unref (mainloop);
338
339 /* Now that the main loop has run, cancelled signal connections have been
340 * freed */
341 MYASSERT (tp_intset_is_member (freed_user_data, TEST_B), "");
342 MYASSERT (tp_intset_is_member (freed_user_data, TEST_C), "");
343 MYASSERT (tp_intset_is_member (freed_user_data, TEST_D), "");
344 MYASSERT (tp_intset_is_member (freed_user_data, TEST_E), "");
345 MYASSERT (tp_intset_is_member (freed_user_data, TEST_F), "");
346 MYASSERT (tp_intset_is_member (freed_user_data, TEST_G), "");
347 MYASSERT (tp_intset_is_member (freed_user_data, TEST_H), "");
348
349 /* both A and Z are still listening for signals, so their user data is
350 * still held */
351 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_A), "");
352 MYASSERT (!tp_intset_is_member (freed_user_data, TEST_Z), "");
353
354 g_message ("Dereferencing remaining proxies");
355 g_object_unref (a);
356 g_object_unref (b);
357 g_object_unref (c);
358 MYASSERT (d == NULL, "");
359 g_object_unref (e);
360 g_object_unref (f);
361 MYASSERT (g == NULL, "");
362 g_object_unref (z);
363
364 /* we should already have checked each of these at least once, but just to
365 * make sure we have a systematic test that all user data is freed... */
366 MYASSERT (tp_intset_is_member (freed_user_data, TEST_A), "");
367 MYASSERT (tp_intset_is_member (freed_user_data, TEST_B), "");
368 MYASSERT (tp_intset_is_member (freed_user_data, TEST_C), "");
369 MYASSERT (tp_intset_is_member (freed_user_data, TEST_D), "");
370 MYASSERT (tp_intset_is_member (freed_user_data, TEST_E), "");
371 MYASSERT (tp_intset_is_member (freed_user_data, TEST_F), "");
372 MYASSERT (tp_intset_is_member (freed_user_data, TEST_G), "");
373 MYASSERT (tp_intset_is_member (freed_user_data, TEST_H), "");
374 MYASSERT (tp_intset_is_member (freed_user_data, TEST_Z), "");
375
376 tp_intset_destroy (freed_user_data);
377 tp_intset_destroy (caught_signal);
378
379 return 0;
380 }
381