gmacros.h: add G_GNUC_*_IGNORE_DEPRECATIONS macros for clang
[platform/upstream/glib.git] / glib / tests / mainloop.c
index d064492..8abd262 100644 (file)
@@ -21,6 +21,8 @@
  */
 
 #include <glib.h>
+#include "glib-private.h"
+#include <string.h>
 
 static gboolean cb (gpointer data)
 {
@@ -77,7 +79,6 @@ test_maincontext_basic (void)
   g_assert (g_main_context_find_source_by_funcs_user_data (ctx, &funcs, NULL) == NULL);
 
   id = g_source_attach (source, ctx);
-  g_source_unref (source);
   g_assert_cmpint (g_source_get_id (source), ==, id);
   g_assert (g_main_context_find_source_by_id (ctx, id) == source);
 
@@ -85,8 +86,21 @@ test_maincontext_basic (void)
   g_assert_cmpint (g_source_get_priority (source), ==, G_PRIORITY_HIGH);
 
   g_source_destroy (source);
+  g_assert (g_source_get_context (source) == ctx);
+  g_assert (g_main_context_find_source_by_id (ctx, id) == NULL);
+
   g_main_context_unref (ctx);
 
+  if (g_test_undefined ())
+    {
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                             "*assertion*source->context != NULL*failed*");
+      g_assert (g_source_get_context (source) == NULL);
+      g_test_assert_expected_messages ();
+    }
+
+  g_source_unref (source);
+
   ctx = g_main_context_default ();
   source = g_source_new (&funcs, sizeof (GSource));
   g_source_set_funcs (source, &funcs);
@@ -104,6 +118,7 @@ test_maincontext_basic (void)
   id = g_source_attach (source, ctx);
   g_source_unref (source);
   g_assert (g_source_remove_by_user_data (data));
+  g_assert (!g_source_remove_by_user_data ((gpointer)0x1234));
 
   g_idle_add (cb, data);
   g_assert (g_idle_remove_by_data (data));
@@ -179,12 +194,16 @@ test_timeouts (void)
 
   g_main_loop_run (loop);
 
-  /* this is a race condition; under some circumstances we might not get 10
-   * 100ms runs in 1050 ms, so consider 9 as "close enough" */
-  g_assert_cmpint (a, >=, 9);
+  /* We may be delayed for an arbitrary amount of time - for example,
+   * it's possible for all timeouts to fire exactly once.
+   */
+  g_assert_cmpint (a, >, 0);
+  g_assert_cmpint (a, >=, b);
+  g_assert_cmpint (b, >=, c);
+
   g_assert_cmpint (a, <=, 10);
-  g_assert_cmpint (b, ==, 4);
-  g_assert_cmpint (c, ==, 3);
+  g_assert_cmpint (b, <=, 4);
+  g_assert_cmpint (c, <=, 3);
 
   g_main_loop_unref (loop);
   g_main_context_unref (ctx);
@@ -235,6 +254,16 @@ test_priorities (void)
   g_main_context_unref (ctx);
 }
 
+static gboolean
+quit_loop (gpointer data)
+{
+  GMainLoop *loop = data;
+
+  g_main_loop_quit (loop);
+
+  return G_SOURCE_REMOVE;
+}
+
 static gint count;
 
 static gboolean
@@ -264,9 +293,11 @@ static gpointer
 thread_func (gpointer data)
 {
   GMainContext *ctx = data;
+  GMainLoop *loop;
   GSource *source;
 
   g_main_context_push_thread_default (ctx);
+  loop = g_main_loop_new (ctx, FALSE);
 
   g_mutex_lock (&mutex);
   thread_ready = TRUE;
@@ -274,12 +305,14 @@ thread_func (gpointer data)
   g_mutex_unlock (&mutex);
 
   source = g_timeout_source_new (500);
-  g_source_set_callback (source, (GSourceFunc)g_thread_exit, NULL, NULL);
+  g_source_set_callback (source, quit_loop, loop, NULL);
   g_source_attach (source, ctx);
   g_source_unref (source);
 
-  while (TRUE)
-    g_main_context_iteration (ctx, TRUE);
+  g_main_loop_run (loop);
+
+  g_main_context_pop_thread_default (ctx);
+  g_main_loop_unref (loop);
 
   return NULL;
 }
@@ -316,18 +349,79 @@ test_invoke (void)
 
   g_thread_join (thread);
   g_assert_cmpint (count, ==, 3);
+
+  g_main_context_unref (ctx);
 }
 
+/* We can't use timeout sources here because on slow or heavily-loaded
+ * machines, the test program might not get enough cycles to hit the
+ * timeouts at the expected times. So instead we define a source that
+ * is based on the number of GMainContext iterations.
+ */
+
+static gint counter;
+static gint64 last_counter_update;
+
+typedef struct {
+  GSource source;
+  gint    interval;
+  gint    timeout;
+} CounterSource;
+
 static gboolean
-quit_loop (gpointer data)
+counter_source_prepare (GSource *source,
+                        gint    *timeout)
 {
-  GMainLoop *loop = data;
+  CounterSource *csource = (CounterSource *)source;
+  gint64 now;
+
+  now = g_source_get_time (source);
+  if (now != last_counter_update)
+    {
+      last_counter_update = now;
+      counter++;
+    }
+
+  *timeout = 1;
+  return counter >= csource->timeout;
+}
 
-  g_main_loop_quit (loop);
+static gboolean
+counter_source_dispatch (GSource    *source,
+                         GSourceFunc callback,
+                         gpointer    user_data)
+{
+  CounterSource *csource = (CounterSource *) source;
+  gboolean again;
 
-  return G_SOURCE_REMOVE;
+  again = callback (user_data);
+
+  if (again)
+    csource->timeout = counter + csource->interval;
+
+  return again;
+}
+
+static GSourceFuncs counter_source_funcs = {
+  counter_source_prepare,
+  NULL,
+  counter_source_dispatch,
+  NULL,
+};
+
+static GSource *
+counter_source_new (gint interval)
+{
+  GSource *source = g_source_new (&counter_source_funcs, sizeof (CounterSource));
+  CounterSource *csource = (CounterSource *) source;
+
+  csource->interval = interval;
+  csource->timeout = counter + interval;
+
+  return source;
 }
 
+
 static gboolean
 run_inner_loop (gpointer user_data)
 {
@@ -338,9 +432,10 @@ run_inner_loop (gpointer user_data)
   a++;
 
   inner = g_main_loop_new (ctx, FALSE);
-  timeout = g_timeout_source_new (100);
+  timeout = counter_source_new (100);
   g_source_set_callback (timeout, quit_loop, inner, NULL);
   g_source_attach (timeout, ctx);
+  g_source_unref (timeout);
 
   g_main_loop_run (inner);
   g_main_loop_unref (inner);
@@ -360,16 +455,16 @@ test_child_sources (void)
 
   a = b = c = 0;
 
-  parent = g_timeout_source_new (2000);
+  parent = counter_source_new (2000);
   g_source_set_callback (parent, run_inner_loop, ctx, NULL);
   g_source_set_priority (parent, G_PRIORITY_LOW);
   g_source_attach (parent, ctx);
 
-  child_b = g_timeout_source_new (250);
+  child_b = counter_source_new (250);
   g_source_set_callback (child_b, count_calls, &b, NULL);
   g_source_add_child_source (parent, child_b);
 
-  child_c = g_timeout_source_new (330);
+  child_c = counter_source_new (330);
   g_source_set_callback (child_c, count_calls, &c, NULL);
   g_source_set_priority (child_c, G_PRIORITY_HIGH);
   g_source_add_child_source (parent, child_c);
@@ -383,7 +478,7 @@ test_child_sources (void)
   g_assert_cmpint (g_source_get_priority (child_b), ==, G_PRIORITY_DEFAULT);
   g_assert_cmpint (g_source_get_priority (child_c), ==, G_PRIORITY_DEFAULT);
 
-  end = g_timeout_source_new (1050);
+  end = counter_source_new (1050);
   g_source_set_callback (end, quit_loop, loop, NULL);
   g_source_attach (end, ctx);
   g_source_unref (end);
@@ -417,6 +512,7 @@ test_child_sources (void)
   g_assert_cmpint (b, ==, 3);
   g_assert_cmpint (c, ==, 3);
 
+  g_source_destroy (parent);
   g_source_unref (parent);
   g_source_unref (child_b);
   g_source_unref (child_c);
@@ -437,20 +533,20 @@ test_recursive_child_sources (void)
 
   a = b = c = 0;
 
-  parent = g_timeout_source_new (500);
+  parent = counter_source_new (500);
   g_source_set_callback (parent, count_calls, &a, NULL);
 
-  child_b = g_timeout_source_new (220);
+  child_b = counter_source_new (220);
   g_source_set_callback (child_b, count_calls, &b, NULL);
   g_source_add_child_source (parent, child_b);
 
-  child_c = g_timeout_source_new (430);
+  child_c = counter_source_new (430);
   g_source_set_callback (child_c, count_calls, &c, NULL);
   g_source_add_child_source (child_b, child_c);
 
   g_source_attach (parent, ctx);
 
-  end = g_timeout_source_new (2010);
+  end = counter_source_new (2010);
   g_source_set_callback (end, (GSourceFunc)g_main_loop_quit, loop, NULL);
   g_source_attach (end, ctx);
   g_source_unref (end);
@@ -458,21 +554,22 @@ test_recursive_child_sources (void)
   g_main_loop_run (loop);
 
   /* Sequence of events:
-   * 220 b (b = 440, a = 720)
-   * 430 c (c = 860, b = 650, a = 930)
-   * 650 b (b = 870, a = 1150)
-   * 860 c (c = 1290, b = 1080, a = 1360)
-   * 1080 b (b = 1300, a = 1580)
-   * 1290 c (c = 1720, b = 1510, a = 1790)
-   * 1510 b (b = 1730, a = 2010)
-   * 1720 c (c = 2150, b = 1940, a = 2220)
-   * 1940 b (b = 2160, a = 2440)
+   *  220 b (b -> 440, a -> 720)
+   *  430 c (c -> 860, b -> 650, a -> 930)
+   *  650 b (b -> 870, a -> 1150)
+   *  860 c (c -> 1290, b -> 1080, a -> 1360)
+   * 1080 b (b -> 1300, a -> 1580)
+   * 1290 c (c -> 1720, b -> 1510, a -> 1790)
+   * 1510 b (b -> 1730, a -> 2010)
+   * 1720 c (c -> 2150, b -> 1940, a -> 2220)
+   * 1940 b (b -> 2160, a -> 2440)
    */
 
   g_assert_cmpint (a, ==, 9);
   g_assert_cmpint (b, ==, 9);
   g_assert_cmpint (c, ==, 4);
 
+  g_source_destroy (parent);
   g_source_unref (parent);
   g_source_unref (child_b);
   g_source_unref (child_c);
@@ -481,10 +578,997 @@ test_recursive_child_sources (void)
   g_main_context_unref (ctx);
 }
 
+typedef struct {
+  GSource *parent, *old_child, *new_child;
+  GMainLoop *loop;
+} SwappingTestData;
+
+static gboolean
+swap_sources (gpointer user_data)
+{
+  SwappingTestData *data = user_data;
+
+  if (data->old_child)
+    {
+      g_source_remove_child_source (data->parent, data->old_child);
+      g_clear_pointer (&data->old_child, g_source_unref);
+    }
+
+  if (!data->new_child)
+    {
+      data->new_child = g_timeout_source_new (0);
+      g_source_set_callback (data->new_child, quit_loop, data->loop, NULL);
+      g_source_add_child_source (data->parent, data->new_child);
+    }
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+assert_not_reached_callback (gpointer user_data)
+{
+  g_assert_not_reached ();
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+test_swapping_child_sources (void)
+{
+  GMainContext *ctx;
+  GMainLoop *loop;
+  SwappingTestData data;
+
+  ctx = g_main_context_new ();
+  loop = g_main_loop_new (ctx, FALSE);
+
+  data.parent = counter_source_new (50);
+  data.loop = loop;
+  g_source_set_callback (data.parent, swap_sources, &data, NULL);
+  g_source_attach (data.parent, ctx);
+
+  data.old_child = counter_source_new (100);
+  g_source_add_child_source (data.parent, data.old_child);
+  g_source_set_callback (data.old_child, assert_not_reached_callback, NULL, NULL);
+
+  data.new_child = NULL;
+  g_main_loop_run (loop);
+
+  g_source_destroy (data.parent);
+  g_source_unref (data.parent);
+  g_source_unref (data.new_child);
+
+  g_main_loop_unref (loop);
+  g_main_context_unref (ctx);
+}
+
+static gboolean
+add_source_callback (gpointer user_data)
+{
+  GMainLoop *loop = user_data;
+  GSource *self = g_main_current_source (), *child;
+  GIOChannel *io;
+
+  /* It doesn't matter whether this is a valid fd or not; it never
+   * actually gets polled; the test is just checking that
+   * g_source_add_child_source() doesn't crash.
+   */
+  io = g_io_channel_unix_new (0);
+  child = g_io_create_watch (io, G_IO_IN);
+  g_source_add_child_source (self, child);
+  g_source_unref (child);
+  g_io_channel_unref (io);
+
+  g_main_loop_quit (loop);
+  return FALSE;
+}
+
+static void
+test_blocked_child_sources (void)
+{
+  GMainContext *ctx;
+  GMainLoop *loop;
+  GSource *source;
+
+  g_test_bug ("701283");
+
+  ctx = g_main_context_new ();
+  loop = g_main_loop_new (ctx, FALSE);
+
+  source = g_idle_source_new ();
+  g_source_set_callback (source, add_source_callback, loop, NULL);
+  g_source_attach (source, ctx);
+
+  g_main_loop_run (loop);
+
+  g_source_destroy (source);
+  g_source_unref (source);
+
+  g_main_loop_unref (loop);
+  g_main_context_unref (ctx);
+}
+
+typedef struct {
+  GMainContext *ctx;
+  GMainLoop *loop;
+
+  GSource *timeout1, *timeout2;
+  gint64 time1;
+  GTimeVal tv;
+} TimeTestData;
+
+static gboolean
+timeout1_callback (gpointer user_data)
+{
+  TimeTestData *data = user_data;
+  GSource *source;
+  gint64 mtime1, mtime2, time2;
+
+  source = g_main_current_source ();
+  g_assert (source == data->timeout1);
+
+  if (data->time1 == -1)
+    {
+      /* First iteration */
+      g_assert (!g_source_is_destroyed (data->timeout2));
+
+      mtime1 = g_get_monotonic_time ();
+      data->time1 = g_source_get_time (source);
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+      g_source_get_current_time (source, &data->tv);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+      /* g_source_get_time() does not change during a single callback */
+      g_usleep (1000000);
+      mtime2 = g_get_monotonic_time ();
+      time2 = g_source_get_time (source);
+
+      g_assert_cmpint (mtime1, <, mtime2);
+      g_assert_cmpint (data->time1, ==, time2);
+    }
+  else
+    {
+      GTimeVal tv;
+
+      /* Second iteration */
+      g_assert (g_source_is_destroyed (data->timeout2));
+
+      /* g_source_get_time() MAY change between iterations; in this
+       * case we know for sure that it did because of the g_usleep()
+       * last time.
+       */
+      time2 = g_source_get_time (source);
+      g_assert_cmpint (data->time1, <, time2);
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+      g_source_get_current_time (source, &tv);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+      g_assert (tv.tv_sec > data->tv.tv_sec ||
+                (tv.tv_sec == data->tv.tv_sec &&
+                 tv.tv_usec > data->tv.tv_usec));
+
+      g_main_loop_quit (data->loop);
+    }
+
+  return TRUE;
+}
+
+static gboolean
+timeout2_callback (gpointer user_data)
+{
+  TimeTestData *data = user_data;
+  GSource *source;
+  gint64 time2, time3;
+
+  source = g_main_current_source ();
+  g_assert (source == data->timeout2);
+
+  g_assert (!g_source_is_destroyed (data->timeout1));
+
+  /* g_source_get_time() does not change between different sources in
+   * a single iteration of the mainloop.
+   */
+  time2 = g_source_get_time (source);
+  g_assert_cmpint (data->time1, ==, time2);
+
+  /* The source should still have a valid time even after being
+   * destroyed, since it's currently running.
+   */
+  g_source_destroy (source);
+  time3 = g_source_get_time (source);
+  g_assert_cmpint (time2, ==, time3);
+
+  return FALSE;
+}
+
+static void
+test_source_time (void)
+{
+  TimeTestData data;
+
+  data.ctx = g_main_context_new ();
+  data.loop = g_main_loop_new (data.ctx, FALSE);
+
+  data.timeout1 = g_timeout_source_new (0);
+  g_source_set_callback (data.timeout1, timeout1_callback, &data, NULL);
+  g_source_attach (data.timeout1, data.ctx);
+
+  data.timeout2 = g_timeout_source_new (0);
+  g_source_set_callback (data.timeout2, timeout2_callback, &data, NULL);
+  g_source_attach (data.timeout2, data.ctx);
+
+  data.time1 = -1;
+
+  g_main_loop_run (data.loop);
+
+  g_assert (!g_source_is_destroyed (data.timeout1));
+  g_assert (g_source_is_destroyed (data.timeout2));
+
+  g_source_destroy (data.timeout1);
+  g_source_unref (data.timeout1);
+  g_source_unref (data.timeout2);
+
+  g_main_loop_unref (data.loop);
+  g_main_context_unref (data.ctx);
+}
+
+typedef struct {
+  guint outstanding_ops;
+  GMainLoop *loop;
+} TestOverflowData;
+
+static gboolean
+on_source_fired_cb (gpointer user_data)
+{
+  TestOverflowData *data = user_data;
+  GSource *current_source;
+  GMainContext *current_context;
+  guint source_id;
+
+  data->outstanding_ops--;
+
+  current_source = g_main_current_source ();
+  current_context = g_source_get_context (current_source);
+  source_id = g_source_get_id (current_source);
+  g_assert (g_main_context_find_source_by_id (current_context, source_id) != NULL);
+  g_source_destroy (current_source);
+  g_assert (g_main_context_find_source_by_id (current_context, source_id) == NULL);
+
+  if (data->outstanding_ops == 0)
+    g_main_loop_quit (data->loop);
+  return FALSE;
+}
+
+static GSource *
+add_idle_source (GMainContext *ctx,
+                 TestOverflowData *data)
+{
+  GSource *source;
+
+  source = g_idle_source_new ();
+  g_source_set_callback (source, on_source_fired_cb, data, NULL);
+  g_source_attach (source, ctx);
+  g_source_unref (source);
+  data->outstanding_ops++;
+
+  return source;
+}
+
+static void
+test_mainloop_overflow (void)
+{
+  GMainContext *ctx;
+  GMainLoop *loop;
+  GSource *source;
+  TestOverflowData data;
+  guint i;
+
+  g_test_bug ("687098");
+
+  memset (&data, 0, sizeof (data));
+
+  ctx = GLIB_PRIVATE_CALL (g_main_context_new_with_next_id) (G_MAXUINT-1);
+
+  loop = g_main_loop_new (ctx, TRUE);
+  data.outstanding_ops = 0;
+  data.loop = loop;
+
+  source = add_idle_source (ctx, &data);
+  g_assert_cmpint (source->source_id, ==, G_MAXUINT-1);
+
+  source = add_idle_source (ctx, &data);
+  g_assert_cmpint (source->source_id, ==, G_MAXUINT);
+
+  source = add_idle_source (ctx, &data);
+  g_assert_cmpint (source->source_id, !=, 0);
+
+  /* Now, a lot more sources */
+  for (i = 0; i < 50; i++)
+    {
+      source = add_idle_source (ctx, &data);
+      g_assert_cmpint (source->source_id, !=, 0);
+    }
+
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.outstanding_ops, ==, 0);
+
+  g_main_loop_unref (loop);
+  g_main_context_unref (ctx);
+}
+
+static volatile gint ready_time_dispatched;
+
+static gboolean
+ready_time_dispatch (GSource     *source,
+                     GSourceFunc  callback,
+                     gpointer     user_data)
+{
+  g_atomic_int_set (&ready_time_dispatched, TRUE);
+
+  g_source_set_ready_time (source, -1);
+
+  return TRUE;
+}
+
+static gpointer
+run_context (gpointer user_data)
+{
+  g_main_loop_run (user_data);
+
+  return NULL;
+}
+
+static void
+test_ready_time (void)
+{
+  GThread *thread;
+  GSource *source;
+  GSourceFuncs source_funcs = {
+    NULL, NULL, ready_time_dispatch
+  };
+  GMainLoop *loop;
+
+  source = g_source_new (&source_funcs, sizeof (GSource));
+  g_source_attach (source, NULL);
+  g_source_unref (source);
+
+  /* Unfortunately we can't do too many things with respect to timing
+   * without getting into trouble on slow systems or heavily loaded
+   * builders.
+   *
+   * We can test that the basics are working, though.
+   */
+
+  /* A source with no ready time set should not fire */
+  g_assert_cmpint (g_source_get_ready_time (source), ==, -1);
+  while (g_main_context_iteration (NULL, FALSE));
+  g_assert (!ready_time_dispatched);
+
+  /* The ready time should not have been changed */
+  g_assert_cmpint (g_source_get_ready_time (source), ==, -1);
+
+  /* Of course this shouldn't change anything either */
+  g_source_set_ready_time (source, -1);
+  g_assert_cmpint (g_source_get_ready_time (source), ==, -1);
+
+  /* A source with a ready time set to tomorrow should not fire on any
+   * builder, no matter how badly loaded...
+   */
+  g_source_set_ready_time (source, g_get_monotonic_time () + G_TIME_SPAN_DAY);
+  while (g_main_context_iteration (NULL, FALSE));
+  g_assert (!ready_time_dispatched);
+  /* Make sure it didn't get reset */
+  g_assert_cmpint (g_source_get_ready_time (source), !=, -1);
+
+  /* Ready time of -1 -> don't fire */
+  g_source_set_ready_time (source, -1);
+  while (g_main_context_iteration (NULL, FALSE));
+  g_assert (!ready_time_dispatched);
+  /* Not reset, but should still be -1 from above */
+  g_assert_cmpint (g_source_get_ready_time (source), ==, -1);
+
+  /* A ready time of the current time should fire immediately */
+  g_source_set_ready_time (source, g_get_monotonic_time ());
+  while (g_main_context_iteration (NULL, FALSE));
+  g_assert (ready_time_dispatched);
+  ready_time_dispatched = FALSE;
+  /* Should have gotten reset by the handler function */
+  g_assert_cmpint (g_source_get_ready_time (source), ==, -1);
+
+  /* As well as one in the recent past... */
+  g_source_set_ready_time (source, g_get_monotonic_time () - G_TIME_SPAN_SECOND);
+  while (g_main_context_iteration (NULL, FALSE));
+  g_assert (ready_time_dispatched);
+  ready_time_dispatched = FALSE;
+  g_assert_cmpint (g_source_get_ready_time (source), ==, -1);
+
+  /* Zero is the 'official' way to get a source to fire immediately */
+  g_source_set_ready_time (source, 0);
+  while (g_main_context_iteration (NULL, FALSE));
+  g_assert (ready_time_dispatched);
+  ready_time_dispatched = FALSE;
+  g_assert_cmpint (g_source_get_ready_time (source), ==, -1);
+
+  /* Now do some tests of cross-thread wakeups.
+   *
+   * Make sure it wakes up right away from the start.
+   */
+  g_source_set_ready_time (source, 0);
+  loop = g_main_loop_new (NULL, FALSE);
+  thread = g_thread_new ("context thread", run_context, loop);
+  while (!g_atomic_int_get (&ready_time_dispatched));
+
+  /* Now let's see if it can wake up from sleeping. */
+  g_usleep (G_TIME_SPAN_SECOND / 2);
+  g_atomic_int_set (&ready_time_dispatched, FALSE);
+  g_source_set_ready_time (source, 0);
+  while (!g_atomic_int_get (&ready_time_dispatched));
+
+  /* kill the thread */
+  g_main_loop_quit (loop);
+  g_thread_join (thread);
+  g_main_loop_unref (loop);
+
+  g_source_destroy (source);
+}
+
+static void
+test_wakeup(void)
+{
+  GMainContext *ctx;
+  int i;
+
+  ctx = g_main_context_new ();
+
+  /* run a random large enough number of times because 
+   * main contexts tend to wake up a few times after creation.
+   */
+  for (i = 0; i < 100; i++)
+    {
+      /* This is the invariant we care about:
+       * g_main_context_wakeup(ctx,) ensures that the next call to
+       * g_main_context_iteration (ctx, TRUE) returns and doesn't
+       * block.
+       * This is important in threaded apps where we might not know
+       * if the thread calls g_main_context_wakeup() before or after
+       * we enter g_main_context_iteration().
+       */
+      g_main_context_wakeup (ctx);
+      g_main_context_iteration (ctx, TRUE);
+    }
+
+  g_main_context_unref (ctx);
+}
+
+static void
+test_remove_invalid (void)
+{
+  g_test_expect_message ("GLib", G_LOG_LEVEL_CRITICAL, "Source ID 3000000000 was not found*");
+  g_source_remove (3000000000u);
+  g_test_assert_expected_messages ();
+}
+
+static gboolean
+trivial_prepare (GSource *source,
+                 gint    *timeout)
+{
+  *timeout = 0;
+  return TRUE;
+}
+
+static gint n_finalized;
+
+static void
+trivial_finalize (GSource *source)
+{
+  n_finalized++;
+}
+
+static void
+test_unref_while_pending (void)
+{
+  static GSourceFuncs funcs = { trivial_prepare, NULL, NULL, trivial_finalize };
+  GMainContext *context;
+  GSource *source;
+
+  context = g_main_context_new ();
+
+  source = g_source_new (&funcs, sizeof (GSource));
+  g_source_attach (source, context);
+  g_source_unref (source);
+
+  /* Do incomplete main iteration -- get a pending source but don't dispatch it. */
+  g_main_context_prepare (context, NULL);
+  g_main_context_query (context, 0, NULL, NULL, 0);
+  g_main_context_check (context, 1000, NULL, 0);
+
+  /* Destroy the context */
+  g_main_context_unref (context);
+
+  /* Make sure we didn't leak the source */
+  g_assert_cmpint (n_finalized, ==, 1);
+}
+
+#ifdef G_OS_UNIX
+
+#include <glib-unix.h>
+#include <unistd.h>
+
+static gchar zeros[1024];
+
+static gsize
+fill_a_pipe (gint fd)
+{
+  gsize written = 0;
+  GPollFD pfd;
+
+  pfd.fd = fd;
+  pfd.events = G_IO_OUT;
+  while (g_poll (&pfd, 1, 0) == 1)
+    /* we should never see -1 here */
+    written += write (fd, zeros, sizeof zeros);
+
+  return written;
+}
+
+static gboolean
+write_bytes (gint         fd,
+             GIOCondition condition,
+             gpointer     user_data)
+{
+  gssize *to_write = user_data;
+  gint limit;
+
+  if (*to_write == 0)
+    return FALSE;
+
+  /* Detect if we run before we should */
+  g_assert (*to_write >= 0);
+
+  limit = MIN (*to_write, sizeof zeros);
+  *to_write -= write (fd, zeros, limit);
+
+  return TRUE;
+}
+
+static gboolean
+read_bytes (gint         fd,
+            GIOCondition condition,
+            gpointer     user_data)
+{
+  static gchar buffer[1024];
+  gssize *to_read = user_data;
+
+  *to_read -= read (fd, buffer, sizeof buffer);
+
+  /* The loop will exit when there is nothing else to read, then we will
+   * use g_source_remove() to destroy this source.
+   */
+  return TRUE;
+}
+
+static void
+test_unix_fd (void)
+{
+  gssize to_write = -1;
+  gssize to_read;
+  gint fds[2];
+  gint a, b;
+  gint s;
+  GSource *source_a;
+  GSource *source_b;
+
+  s = pipe (fds);
+  g_assert (s == 0);
+
+  to_read = fill_a_pipe (fds[1]);
+  /* write at higher priority to keep the pipe full... */
+  a = g_unix_fd_add_full (G_PRIORITY_HIGH, fds[1], G_IO_OUT, write_bytes, &to_write, NULL);
+  source_a = g_source_ref (g_main_context_find_source_by_id (NULL, a));
+  /* make sure no 'writes' get dispatched yet */
+  while (g_main_context_iteration (NULL, FALSE));
+
+  to_read += 128 * 1024 * 1024;
+  to_write = 128 * 1024 * 1024;
+  b = g_unix_fd_add (fds[0], G_IO_IN, read_bytes, &to_read);
+  source_b = g_source_ref (g_main_context_find_source_by_id (NULL, b));
+
+  /* Assuming the kernel isn't internally 'laggy' then there will always
+   * be either data to read or room in which to write.  That will keep
+   * the loop running until all data has been read and written.
+   */
+  while (TRUE)
+    {
+      gssize to_write_was = to_write;
+      gssize to_read_was = to_read;
+
+      if (!g_main_context_iteration (NULL, FALSE))
+        break;
+
+      /* Since the sources are at different priority, only one of them
+       * should possibly have run.
+       */
+      g_assert (to_write == to_write_was || to_read == to_read_was);
+    }
+
+  g_assert (to_write == 0);
+  g_assert (to_read == 0);
+
+  /* 'a' is already removed by itself */
+  g_assert (g_source_is_destroyed (source_a));
+  g_source_unref (source_a);
+  g_source_remove (b);
+  g_assert (g_source_is_destroyed (source_b));
+  g_source_unref (source_b);
+  close (fds[1]);
+  close (fds[0]);
+}
+
+static void
+assert_main_context_state (gint n_to_poll,
+                           ...)
+{
+  GMainContext *context;
+  gboolean consumed[10] = { };
+  GPollFD poll_fds[10];
+  gboolean acquired;
+  gboolean immediate;
+  gint max_priority;
+  gint timeout;
+  gint n;
+  gint i, j;
+  va_list ap;
+
+  context = g_main_context_default ();
+
+  acquired = g_main_context_acquire (context);
+  g_assert (acquired);
+
+  immediate = g_main_context_prepare (context, &max_priority);
+  g_assert (!immediate);
+  n = g_main_context_query (context, max_priority, &timeout, poll_fds, 10);
+  g_assert_cmpint (n, ==, n_to_poll + 1); /* one will be the gwakeup */
+
+  va_start (ap, n_to_poll);
+  for (i = 0; i < n_to_poll; i++)
+    {
+      gint expected_fd = va_arg (ap, gint);
+      GIOCondition expected_events = va_arg (ap, GIOCondition);
+      GIOCondition report_events = va_arg (ap, GIOCondition);
+
+      for (j = 0; j < n; j++)
+        if (!consumed[j] && poll_fds[j].fd == expected_fd && poll_fds[j].events == expected_events)
+          {
+            poll_fds[j].revents = report_events;
+            consumed[j] = TRUE;
+            break;
+          }
+
+      if (j == n)
+        g_error ("Unable to find fd %d (index %d) with events 0x%x\n", expected_fd, i, (guint) expected_events);
+    }
+  va_end (ap);
+
+  /* find the gwakeup, flag as non-ready */
+  for (i = 0; i < n; i++)
+    if (!consumed[i])
+      poll_fds[i].revents = 0;
+
+  if (g_main_context_check (context, max_priority, poll_fds, n))
+    g_main_context_dispatch (context);
+
+  g_main_context_release (context);
+}
+
+static gboolean
+flag_bool (gint         fd,
+           GIOCondition condition,
+           gpointer     user_data)
+{
+  gboolean *flag = user_data;
+
+  *flag = TRUE;
+
+  return TRUE;
+}
+
+static void
+test_unix_fd_source (void)
+{
+  GSource *out_source;
+  GSource *in_source;
+  GSource *source;
+  gboolean out, in;
+  gint fds[2];
+  gint s;
+
+  assert_main_context_state (0);
+
+  s = pipe (fds);
+  g_assert (s == 0);
+
+  source = g_unix_fd_source_new (fds[1], G_IO_OUT);
+  g_source_attach (source, NULL);
+
+  /* Check that a source with no callback gets successfully detached
+   * with a warning printed.
+   */
+  g_test_expect_message ("GLib", G_LOG_LEVEL_WARNING, "*GUnixFDSource dispatched without callback*");
+  while (g_main_context_iteration (NULL, FALSE));
+  g_test_assert_expected_messages ();
+  g_assert (g_source_is_destroyed (source));
+  g_source_unref (source);
+
+  out = in = FALSE;
+  out_source = g_unix_fd_source_new (fds[1], G_IO_OUT);
+  g_source_set_callback (out_source, (GSourceFunc) flag_bool, &out, NULL);
+  g_source_attach (out_source, NULL);
+  assert_main_context_state (1,
+                             fds[1], G_IO_OUT, 0);
+  g_assert (!in && !out);
+
+  in_source = g_unix_fd_source_new (fds[0], G_IO_IN);
+  g_source_set_callback (in_source, (GSourceFunc) flag_bool, &in, NULL);
+  g_source_set_priority (in_source, G_PRIORITY_DEFAULT_IDLE);
+  g_source_attach (in_source, NULL);
+  assert_main_context_state (2,
+                             fds[0], G_IO_IN, G_IO_IN,
+                             fds[1], G_IO_OUT, G_IO_OUT);
+  /* out is higher priority so only it should fire */
+  g_assert (!in && out);
+
+  /* raise the priority of the in source to higher than out*/
+  in = out = FALSE;
+  g_source_set_priority (in_source, G_PRIORITY_HIGH);
+  assert_main_context_state (2,
+                             fds[0], G_IO_IN, G_IO_IN,
+                             fds[1], G_IO_OUT, G_IO_OUT);
+  g_assert (in && !out);
+
+  /* now, let them be equal */
+  in = out = FALSE;
+  g_source_set_priority (in_source, G_PRIORITY_DEFAULT);
+  assert_main_context_state (2,
+                             fds[0], G_IO_IN, G_IO_IN,
+                             fds[1], G_IO_OUT, G_IO_OUT);
+  g_assert (in && out);
+
+  g_source_destroy (out_source);
+  g_source_unref (out_source);
+  g_source_destroy (in_source);
+  g_source_unref (in_source);
+  close (fds[1]);
+  close (fds[0]);
+}
+
+typedef struct
+{
+  GSource parent;
+  gboolean flagged;
+} FlagSource;
+
+static gboolean
+return_true (GSource *source, GSourceFunc callback, gpointer user_data)
+{
+  FlagSource *flag_source = (FlagSource *) source;
+
+  flag_source->flagged = TRUE;
+
+  return TRUE;
+}
+
+#define assert_flagged(s) g_assert (((FlagSource *) (s))->flagged);
+#define assert_not_flagged(s) g_assert (!((FlagSource *) (s))->flagged);
+#define clear_flag(s) ((FlagSource *) (s))->flagged = 0
+
+static void
+test_source_unix_fd_api (void)
+{
+  GSourceFuncs no_funcs = {
+    NULL, NULL, return_true
+  };
+  GSource *source_a;
+  GSource *source_b;
+  gpointer tag1, tag2;
+  gint fds_a[2];
+  gint fds_b[2];
+
+  pipe (fds_a);
+  pipe (fds_b);
+
+  source_a = g_source_new (&no_funcs, sizeof (FlagSource));
+  source_b = g_source_new (&no_funcs, sizeof (FlagSource));
+
+  /* attach a source with more than one fd */
+  g_source_add_unix_fd (source_a, fds_a[0], G_IO_IN);
+  g_source_add_unix_fd (source_a, fds_a[1], G_IO_OUT);
+  g_source_attach (source_a, NULL);
+  assert_main_context_state (2,
+                             fds_a[0], G_IO_IN, 0,
+                             fds_a[1], G_IO_OUT, 0);
+  assert_not_flagged (source_a);
+
+  /* attach a higher priority source with no fds */
+  g_source_set_priority (source_b, G_PRIORITY_HIGH);
+  g_source_attach (source_b, NULL);
+  assert_main_context_state (2,
+                             fds_a[0], G_IO_IN, G_IO_IN,
+                             fds_a[1], G_IO_OUT, 0);
+  assert_flagged (source_a);
+  assert_not_flagged (source_b);
+  clear_flag (source_a);
+
+  /* add some fds to the second source, while attached */
+  tag1 = g_source_add_unix_fd (source_b, fds_b[0], G_IO_IN);
+  tag2 = g_source_add_unix_fd (source_b, fds_b[1], G_IO_OUT);
+  assert_main_context_state (4,
+                             fds_a[0], G_IO_IN, 0,
+                             fds_a[1], G_IO_OUT, G_IO_OUT,
+                             fds_b[0], G_IO_IN, 0,
+                             fds_b[1], G_IO_OUT, G_IO_OUT);
+  /* only 'b' (higher priority) should have dispatched */
+  assert_not_flagged (source_a);
+  assert_flagged (source_b);
+  clear_flag (source_b);
+
+  /* change our events on b to the same as they were before */
+  g_source_modify_unix_fd (source_b, tag1, G_IO_IN);
+  g_source_modify_unix_fd (source_b, tag2, G_IO_OUT);
+  assert_main_context_state (4,
+                             fds_a[0], G_IO_IN, 0,
+                             fds_a[1], G_IO_OUT, G_IO_OUT,
+                             fds_b[0], G_IO_IN, 0,
+                             fds_b[1], G_IO_OUT, G_IO_OUT);
+  assert_not_flagged (source_a);
+  assert_flagged (source_b);
+  clear_flag (source_b);
+
+  /* now reverse them */
+  g_source_modify_unix_fd (source_b, tag1, G_IO_OUT);
+  g_source_modify_unix_fd (source_b, tag2, G_IO_IN);
+  assert_main_context_state (4,
+                             fds_a[0], G_IO_IN, 0,
+                             fds_a[1], G_IO_OUT, G_IO_OUT,
+                             fds_b[0], G_IO_OUT, 0,
+                             fds_b[1], G_IO_IN, 0);
+  /* 'b' had no events, so 'a' can go this time */
+  assert_flagged (source_a);
+  assert_not_flagged (source_b);
+  clear_flag (source_a);
+
+  /* remove one of the fds from 'b' */
+  g_source_remove_unix_fd (source_b, tag1);
+  assert_main_context_state (3,
+                             fds_a[0], G_IO_IN, 0,
+                             fds_a[1], G_IO_OUT, 0,
+                             fds_b[1], G_IO_IN, 0);
+  assert_not_flagged (source_a);
+  assert_not_flagged (source_b);
+
+  /* remove the other */
+  g_source_remove_unix_fd (source_b, tag2);
+  assert_main_context_state (2,
+                             fds_a[0], G_IO_IN, 0,
+                             fds_a[1], G_IO_OUT, 0);
+  assert_not_flagged (source_a);
+  assert_not_flagged (source_b);
+
+  /* destroy the sources */
+  g_source_destroy (source_a);
+  g_source_destroy (source_b);
+  assert_main_context_state (0);
+
+  g_source_unref (source_a);
+  g_source_unref (source_b);
+  close (fds_a[0]);
+  close (fds_a[1]);
+  close (fds_b[0]);
+  close (fds_b[1]);
+}
+
+static gboolean
+unixfd_quit_loop (gint         fd,
+                  GIOCondition condition,
+                  gpointer     user_data)
+{
+  GMainLoop *loop = user_data;
+
+  g_main_loop_quit (loop);
+
+  return FALSE;
+}
+
+static void
+test_unix_file_poll (void)
+{
+  gint fd;
+  GSource *source;
+  GMainLoop *loop;
+
+  fd = open ("/dev/null", O_RDONLY);
+  g_assert (fd >= 0);
+
+  loop = g_main_loop_new (NULL, FALSE);
+
+  source = g_unix_fd_source_new (fd, G_IO_IN);
+  g_source_set_callback (source, (GSourceFunc) unixfd_quit_loop, loop, NULL);
+  g_source_attach (source, NULL);
+
+  /* Should not block */
+  g_main_loop_run (loop);
+
+  g_source_destroy (source);
+
+  assert_main_context_state (0);
+
+  g_source_unref (source);
+
+  g_main_loop_unref (loop);
+
+  close (fd);
+}
+
+#endif
+
+static gboolean
+timeout_cb (gpointer data)
+{
+  GMainLoop *loop = data;
+  GMainContext *context;
+
+  context = g_main_loop_get_context (loop);
+  g_assert (g_main_loop_is_running (loop));
+  g_assert (g_main_context_is_owner (context));
+
+  g_main_loop_quit (loop);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gpointer
+threadf (gpointer data)
+{
+  GMainContext *context = data;
+  GMainLoop *loop;
+  GSource *source;
+
+  loop = g_main_loop_new (context, FALSE);
+  source = g_timeout_source_new (250);
+  g_source_set_callback (source, timeout_cb, loop, NULL);
+  g_source_attach (source, context);
+  g_source_unref (source);
+  g_main_loop_run (loop);
+
+  g_main_loop_unref (loop);
+
+  return NULL;
+}
+
+static void
+test_mainloop_wait (void)
+{
+  GMainContext *context;
+  GThread *t1, *t2;
+
+  context = g_main_context_new ();
+
+  t1 = g_thread_new ("t1", threadf, context);
+  t2 = g_thread_new ("t2", threadf, context);
+
+  g_thread_join (t1);
+  g_thread_join (t2);
+
+  g_main_context_unref (context);
+}
+
 int
 main (int argc, char *argv[])
 {
   g_test_init (&argc, &argv, NULL);
+  g_test_bug_base ("http://bugzilla.gnome.org/");
 
   g_test_add_func ("/maincontext/basic", test_maincontext_basic);
   g_test_add_func ("/mainloop/basic", test_mainloop_basic);
@@ -493,6 +1577,21 @@ main (int argc, char *argv[])
   g_test_add_func ("/mainloop/invoke", test_invoke);
   g_test_add_func ("/mainloop/child_sources", test_child_sources);
   g_test_add_func ("/mainloop/recursive_child_sources", test_recursive_child_sources);
+  g_test_add_func ("/mainloop/swapping_child_sources", test_swapping_child_sources);
+  g_test_add_func ("/mainloop/blocked_child_sources", test_blocked_child_sources);
+  g_test_add_func ("/mainloop/source_time", test_source_time);
+  g_test_add_func ("/mainloop/overflow", test_mainloop_overflow);
+  g_test_add_func ("/mainloop/ready-time", test_ready_time);
+  g_test_add_func ("/mainloop/wakeup", test_wakeup);
+  g_test_add_func ("/mainloop/remove-invalid", test_remove_invalid);
+  g_test_add_func ("/mainloop/unref-while-pending", test_unref_while_pending);
+#ifdef G_OS_UNIX
+  g_test_add_func ("/mainloop/unix-fd", test_unix_fd);
+  g_test_add_func ("/mainloop/unix-fd-source", test_unix_fd_source);
+  g_test_add_func ("/mainloop/source-unix-fd-api", test_source_unix_fd_api);
+  g_test_add_func ("/mainloop/wait", test_mainloop_wait);
+  g_test_add_func ("/mainloop/unix-file-poll", test_unix_file_poll);
+#endif
 
   return g_test_run ();
 }