1/* Integration tests for the dbus-daemon
2 *
3 * Author: Simon McVittie <simon.mcvittie@collabora.co.uk>
4 * Copyright �� 2010-2011 Nokia Corporation
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation files
8 * (the "Software"), to deal in the Software without restriction,
9 * including without limitation the rights to use, copy, modify, merge,
10 * publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so,
12 * subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27#include <config.h>
28
29#include <glib.h>
30
31#include <dbus/dbus.h>
32#include <dbus/dbus-glib-lowlevel.h>
33
34#include <string.h>
35
36#ifdef DBUS_WIN
37# include <io.h>
38# include <windows.h>
39#else
40# include <signal.h>
41# include <unistd.h>
42#endif
43
44typedef struct {
45    gboolean skip;
46
47    DBusError e;
48    GError *ge;
49
50    GPid daemon_pid;
51
52    DBusConnection *left_conn;
53
54    DBusConnection *right_conn;
55    gboolean right_conn_echo;
56} Fixture;
57
58#define assert_no_error(e) _assert_no_error (e, __FILE__, __LINE__)
59static void
60_assert_no_error (const DBusError *e,
61    const char *file,
62    int line)
63{
64  if (G_UNLIKELY (dbus_error_is_set (e)))
65    g_error ("%s:%d: expected success but got error: %s: %s",
66        file, line, e->name, e->message);
67}
68
69static gchar *
70spawn_dbus_daemon (gchar *binary,
71    gchar *configuration,
72    GPid *daemon_pid)
73{
74  GError *error = NULL;
75  GString *address;
76  gint address_fd;
77  gchar *argv[] = {
78      binary,
79      configuration,
80      "--nofork",
81      "--print-address=1", /* stdout */
82      NULL
83  };
84
85  g_spawn_async_with_pipes (NULL, /* working directory */
86      argv,
87      NULL, /* envp */
88      G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
89      NULL, /* child_setup */
90      NULL, /* user data */
91      daemon_pid,
92      NULL, /* child's stdin = /dev/null */
93      &address_fd,
94      NULL, /* child's stderr = our stderr */
95      &error);
96  g_assert_no_error (error);
97
98  address = g_string_new (NULL);
99
100  /* polling until the dbus-daemon writes out its address is a bit stupid,
101   * but at least it's simple, unlike dbus-launch... in principle we could
102   * use select() here, but life's too short */
103  while (1)
104    {
105      gssize bytes;
106      gchar buf[4096];
107      gchar *newline;
108
109      bytes = read (address_fd, buf, sizeof (buf));
110
111      if (bytes > 0)
112        g_string_append_len (address, buf, bytes);
113
114      newline = strchr (address->str, '\n');
115
116      if (newline != NULL)
117        {
118          g_string_truncate (address, newline - address->str);
119          break;
120        }
121
122      g_usleep (G_USEC_PER_SEC / 10);
123    }
124
125  return g_string_free (address, FALSE);
126}
127
128static DBusConnection *
129connect_to_bus (const gchar *address)
130{
131  DBusConnection *conn;
132  DBusError error = DBUS_ERROR_INIT;
133  dbus_bool_t ok;
134
135  conn = dbus_connection_open_private (address, &error);
136  assert_no_error (&error);
137  g_assert (conn != NULL);
138
139  ok = dbus_bus_register (conn, &error);
140  assert_no_error (&error);
141  g_assert (ok);
142  g_assert (dbus_bus_get_unique_name (conn) != NULL);
143
144  dbus_connection_setup_with_g_main (conn, NULL);
145  return conn;
146}
147
148static DBusHandlerResult
149echo_filter (DBusConnection *connection,
150    DBusMessage *message,
151    void *user_data)
152{
153  DBusMessage *reply;
154
155  if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
156    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
157
158  reply = dbus_message_new_method_return (message);
159
160  if (reply == NULL)
161    g_error ("OOM");
162
163  if (!dbus_connection_send (connection, reply, NULL))
164    g_error ("OOM");
165
166  dbus_message_unref (reply);
167
168  return DBUS_HANDLER_RESULT_HANDLED;
169}
170
171typedef struct {
172    const char *bug_ref;
173    guint min_messages;
174    const char *config_file;
175} Config;
176
177static void
178setup (Fixture *f,
179    gconstpointer context)
180{
181  const Config *config = context;
182  gchar *dbus_daemon;
183  gchar *arg;
184  gchar *address;
185
186  f->ge = NULL;
187  dbus_error_init (&f->e);
188
189  if (config != NULL && config->config_file != NULL)
190    {
191      if (g_getenv ("DBUS_TEST_DATA") == NULL)
192        {
193          g_message ("SKIP: set DBUS_TEST_DATA to a directory containing %s",
194              config->config_file);
195          f->skip = TRUE;
196          return;
197        }
198
199      arg = g_strdup_printf (
200          "--config-file=%s/%s",
201          g_getenv ("DBUS_TEST_DATA"), config->config_file);
202    }
203  else if (g_getenv ("DBUS_TEST_SYSCONFDIR") != NULL)
204    {
205      arg = g_strdup_printf ("--config-file=%s/dbus-1/session.conf",
206          g_getenv ("DBUS_TEST_SYSCONFDIR"));
207    }
208  else if (g_getenv ("DBUS_TEST_DATA") != NULL)
209    {
210      arg = g_strdup_printf (
211          "--config-file=%s/valid-config-files/session.conf",
212          g_getenv ("DBUS_TEST_DATA"));
213    }
214  else
215    {
216      arg = g_strdup ("--session");
217    }
218
219  dbus_daemon = g_strdup (g_getenv ("DBUS_TEST_DAEMON"));
220
221  if (dbus_daemon == NULL)
222    dbus_daemon = g_strdup ("dbus-daemon");
223
224  address = spawn_dbus_daemon (dbus_daemon, arg, &f->daemon_pid);
225
226  g_free (dbus_daemon);
227  g_free (arg);
228
229  f->left_conn = connect_to_bus (address);
230  f->right_conn = connect_to_bus (address);
231  g_free (address);
232}
233
234static void
235add_echo_filter (Fixture *f)
236{
237  if (!dbus_connection_add_filter (f->right_conn, echo_filter, NULL, NULL))
238    g_error ("OOM");
239
240  f->right_conn_echo = TRUE;
241}
242
243static void
244pc_count (DBusPendingCall *pc,
245    void *data)
246{
247  guint *received_p = data;
248
249  (*received_p)++;
250}
251
252static void
253test_echo (Fixture *f,
254    gconstpointer context)
255{
256  const Config *config = context;
257  guint count = 2000;
258  guint sent;
259  guint received = 0;
260  double elapsed;
261
262  if (f->skip)
263    return;
264
265  if (config != NULL && config->bug_ref != NULL)
266    g_test_bug (config->bug_ref);
267
268  if (g_test_perf ())
269    count = 100000;
270
271  if (config != NULL)
272    count = MAX (config->min_messages, count);
273
274  add_echo_filter (f);
275
276  g_test_timer_start ();
277
278  for (sent = 0; sent < count; sent++)
279    {
280      DBusMessage *m = dbus_message_new_method_call (
281          dbus_bus_get_unique_name (f->right_conn), "/",
282          "com.example", "Spam");
283      DBusPendingCall *pc;
284
285      if (m == NULL)
286        g_error ("OOM");
287
288      if (!dbus_connection_send_with_reply (f->left_conn, m, &pc,
289                                            DBUS_TIMEOUT_INFINITE) ||
290          pc == NULL)
291        g_error ("OOM");
292
293      if (dbus_pending_call_get_completed (pc))
294        pc_count (pc, &received);
295      else if (!dbus_pending_call_set_notify (pc, pc_count, &received,
296            NULL))
297        g_error ("OOM");
298
299      dbus_pending_call_unref (pc);
300      dbus_message_unref (m);
301    }
302
303  while (received < count)
304    g_main_context_iteration (NULL, TRUE);
305
306  elapsed = g_test_timer_elapsed ();
307
308  g_test_maximized_result (count / elapsed, "%u messages / %f seconds",
309      count, elapsed);
310}
311
312static void
313teardown (Fixture *f,
314    gconstpointer context G_GNUC_UNUSED)
315{
316  dbus_error_free (&f->e);
317  g_clear_error (&f->ge);
318
319  if (f->left_conn != NULL)
320    {
321      dbus_connection_close (f->left_conn);
322      dbus_connection_unref (f->left_conn);
323      f->left_conn = NULL;
324    }
325
326  if (f->right_conn != NULL)
327    {
328      if (f->right_conn_echo)
329        {
330          dbus_connection_remove_filter (f->right_conn, echo_filter, NULL);
331          f->right_conn_echo = FALSE;
332        }
333
334      dbus_connection_close (f->right_conn);
335      dbus_connection_unref (f->right_conn);
336      f->right_conn = NULL;
337    }
338
339  if (f->daemon_pid != 0)
340    {
341#ifdef DBUS_WIN
342      TerminateProcess (f->daemon_pid, 1);
343#else
344      kill (f->daemon_pid, SIGTERM);
345#endif
346
347      g_spawn_close_pid (f->daemon_pid);
348      f->daemon_pid = 0;
349    }
350}
351
352static Config limited_config = {
353    "34393", 10000, "valid-config-files/incoming-limit.conf"
354};
355
356int
357main (int argc,
358    char **argv)
359{
360  g_test_init (&argc, &argv, NULL);
361  g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id=");
362
363  g_test_add ("/echo/session", Fixture, NULL, setup, test_echo, teardown);
364  g_test_add ("/echo/limited", Fixture, &limited_config,
365      setup, test_echo, teardown);
366
367  return g_test_run ();
368}
369