Index: live-monitor.c
===================================================================
--- live-monitor.c	(revision 0)
+++ live-monitor.c	(revision 0)
@@ -0,0 +1,737 @@
+/* GLib testing framework examples and tests
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Authors: Tomas Bzatek <tbzatek@redhat.com>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include <glib/glib.h>
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define DEFAULT_TEST_DIR               "testdir_live_monitor"
+#define MONITOR_BEFORE_CHANGE_DELAY    5000  /* ms */
+#define MONITOR_BEFORE_ATTRCH_DELAY    2000  /* ms */
+#define MONITOR_BEFORE_DELETE_DELAY    2000  /* ms */
+#define MONITOR_SETTLE_DELAY           5000  /* ms */
+#define CREATE_MISSING_SETTLE_DELAY    8000  /* ms */
+
+static gboolean verbose;
+static gboolean posix_compat;
+static char *target_path;
+
+static GList *active_watches;
+
+#define log(msg...) if (verbose)  g_print (msg)
+
+
+
+enum TestStructureEventFlags
+{
+  TEST_EVENT_CHANGED = 1 << 0,
+  TEST_EVENT_DELETED = 1 << 1,
+  TEST_EVENT_ATTRIBUTE_CHANGED = 1 << 2,
+  TEST_EVENTS_RECEIVE_ONLY = 1 << 3,
+  TEST_MISSING = 1 << 4,  /*  take some time to settle  */
+  TEST_POSIX_ONLY = 1 << 5,
+};
+
+struct TestStructureItem
+{
+  const char *filename;
+  const char *link_to;
+  const char *link_change_to;   /*  specify this to perform symlink target change test  */
+  GFileType file_type;
+  enum TestStructureEventFlags flags;
+};
+
+
+
+
+static const struct TestStructureItem sample_struct[] = {
+/*  filename               link             link_change_to    file_type          flags             */
+    /*  the link "missing_symlink" is created before the target exists --> test for missing watch  */
+    {"missing_symlink",    "dir1/regular",  NULL,     G_FILE_TYPE_SYMBOLIC_LINK, TEST_MISSING | TEST_EVENTS_RECEIVE_ONLY | TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"touch_file",         NULL,            NULL,     G_FILE_TYPE_REGULAR,       TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"dir1",               NULL,            NULL,     G_FILE_TYPE_DIRECTORY,     TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"dir1/subdir",        NULL,            NULL,     G_FILE_TYPE_DIRECTORY,     TEST_EVENT_DELETED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"dir1/regular",       NULL,            NULL,     G_FILE_TYPE_REGULAR,       TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"regular",            NULL,            NULL,     G_FILE_TYPE_REGULAR,       TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"regular_symlink",    "regular",       NULL,     G_FILE_TYPE_SYMBOLIC_LINK, TEST_POSIX_ONLY | TEST_EVENTS_RECEIVE_ONLY | TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"directory",          NULL,            NULL,     G_FILE_TYPE_DIRECTORY,     TEST_EVENT_DELETED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"directory_symlink",  "directory",     NULL,     G_FILE_TYPE_SYMBOLIC_LINK, TEST_POSIX_ONLY | TEST_EVENTS_RECEIVE_ONLY | TEST_EVENT_DELETED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    /*  the file "regular" should receive events with correct directory, i.e. "directory_symlink", not the target "directory"   */
+    {"directory_symlink/regular", NULL,     NULL,     G_FILE_TYPE_REGULAR,       TEST_POSIX_ONLY | TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    /*  files used to test *file* symlink target change  */
+    /*   - the following tests are performed in symlinked directory to make it harder >:D  */
+    {"lnk_dir",            NULL,            NULL,     G_FILE_TYPE_DIRECTORY,     TEST_POSIX_ONLY},
+    {"lnk_file_change_tst", "lnk_dir",      NULL,     G_FILE_TYPE_SYMBOLIC_LINK, TEST_POSIX_ONLY},
+    {"lnk_file_change_tst/lnk_tst_1", NULL, NULL,     G_FILE_TYPE_REGULAR,       TEST_POSIX_ONLY},
+    {"lnk_file_change_tst/link_to_change", "lnk_tst_1", "lnk_tst_2", G_FILE_TYPE_SYMBOLIC_LINK, TEST_POSIX_ONLY | TEST_EVENTS_RECEIVE_ONLY | TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"lnk_file_change_tst/lnk_tst_2", NULL, NULL,     G_FILE_TYPE_REGULAR,       TEST_POSIX_ONLY | TEST_EVENT_DELETED | TEST_EVENT_CHANGED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    /*  files used to test *directory* symlink target change  */
+    {"lnk_dir2",           NULL,            NULL,     G_FILE_TYPE_DIRECTORY,     TEST_POSIX_ONLY},
+    {"lnk_dir_change_tst", "lnk_dir2",      NULL,     G_FILE_TYPE_SYMBOLIC_LINK, TEST_POSIX_ONLY},
+    {"lnk_dir_change_tst/dir1", NULL,       NULL,     G_FILE_TYPE_REGULAR,       TEST_POSIX_ONLY},
+    {"lnk_dir_change_tst/link_to_change", "dir1", "dir2", G_FILE_TYPE_SYMBOLIC_LINK, TEST_POSIX_ONLY | TEST_EVENTS_RECEIVE_ONLY | TEST_EVENT_DELETED | TEST_EVENT_ATTRIBUTE_CHANGED},
+    {"lnk_dir_change_tst/dir2", NULL,       NULL,     G_FILE_TYPE_REGULAR,       TEST_POSIX_ONLY | TEST_EVENT_DELETED | TEST_EVENT_ATTRIBUTE_CHANGED},
+};
+
+
+struct Watch {
+  const struct TestStructureItem *test_item;
+  gboolean is_dir_monitor;
+  char *full_path;
+  char *pinged_path;
+  GFileMonitor *monitor;
+  gboolean created_pong;
+  gboolean changed_pong;
+  gboolean deleted_pong;
+  gboolean attr_changed_pong;
+};
+
+
+
+/*  Forward declaration  */
+static void add_watch (GFile *file, const struct TestStructureItem *test_item);
+
+
+
+static gboolean
+missing_settle_cb (gpointer user_data)
+{
+  g_assert (user_data != NULL);
+  g_main_loop_quit (user_data);
+  
+  return FALSE;    
+}
+
+static void
+test_create_structure (const char *root_path)
+{
+  GFile *root;
+  GFile *child;
+  gboolean res;
+  GError *error;
+  GFileOutputStream *outs;
+  int i;
+  struct TestStructureItem item;
+  GMainLoop *loop;
+
+  g_assert (root_path != NULL);
+  log ("\n  Going to create testing structure in '%s'...\n", root_path);
+
+  root = g_file_new_for_commandline_arg (root_path);
+  g_assert (root != NULL);
+
+  /*  create root directory  */
+  res = g_file_make_directory (root, NULL, NULL);
+  /*  don't care about errors here  */
+
+  /*  create any other items  */
+  for (i = 0; i < G_N_ELEMENTS (sample_struct); i++)
+    {
+      item = sample_struct[i];
+      if (! posix_compat && 
+            (item.file_type == G_FILE_TYPE_SYMBOLIC_LINK ||
+            (item.flags & TEST_POSIX_ONLY) == TEST_POSIX_ONLY))
+	continue;
+
+      child = g_file_get_child (root, item.filename);
+      g_assert (child != NULL);
+
+      add_watch (child, &sample_struct[i]);
+
+      error = NULL;
+      switch (item.file_type)
+	{
+	case G_FILE_TYPE_REGULAR:
+	  log ("    Creating file '%s'...\n", item.filename);
+	  outs = g_file_replace (child, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
+	  g_assert (error == NULL);
+	  g_assert (outs != NULL);
+	  error = NULL;
+	  res = g_output_stream_close (G_OUTPUT_STREAM (outs), NULL, &error);
+	  g_object_unref (outs);
+	  break;
+	case G_FILE_TYPE_DIRECTORY:
+	  log ("    Creating directory '%s'...\n", item.filename);
+	  res = g_file_make_directory (child, NULL, &error);
+	  break;
+	case G_FILE_TYPE_SYMBOLIC_LINK:
+	  log ("    Creating symlink '%s' --> '%s'...\n", item.filename,
+	       item.link_to);
+	  res = g_file_make_symbolic_link (child, item.link_to, NULL, &error);
+	  break;
+	default:
+	  break;
+	}
+
+      g_assert_cmpint (res, ==, TRUE);
+      g_assert (error == NULL);
+      
+      if ((item.flags & TEST_MISSING) == TEST_MISSING ||
+          item.link_change_to != NULL) 
+      {
+        log ("        ...sleeping to settle events down\n");
+        loop = g_main_loop_new (NULL, TRUE);
+        g_timeout_add (CREATE_MISSING_SETTLE_DELAY, missing_settle_cb, loop);
+        g_main_loop_run (loop);
+        g_main_loop_unref (loop);
+      }
+      
+      /*  change the symlink target  */
+      if (item.link_change_to) {
+        log ("    Changing symlink target from '%s' --> '%s'...\n", item.link_to, item.link_change_to);
+        error = NULL;
+        g_file_delete (child, NULL, &error);
+        g_assert (error == NULL);
+        error = NULL;
+        res = g_file_make_symbolic_link (child, item.link_change_to, NULL, &error);
+        g_assert (error == NULL);
+        g_assert_cmpint (res, ==, TRUE);
+      }
+      
+      g_object_unref (child);
+    }
+
+  log (" done.\n");
+
+  g_object_unref (root);
+}
+
+
+static void
+test_change (const char *root_path)
+{
+  GFile *root, *child;
+  gboolean res;
+  GError *error;
+  int i;
+  struct TestStructureItem item;
+  GFileOutputStream *output_stream;
+  GDataOutputStream *outds;
+
+  
+  g_assert (root_path != NULL);
+  log ("\n  Starting change tests in '%s'...\n", root_path);
+
+  root = g_file_new_for_commandline_arg (root_path);
+  g_assert (root != NULL);
+  res = g_file_query_exists (root, NULL);
+  g_assert_cmpint (res, ==, TRUE);
+
+  for (i = 0; i < G_N_ELEMENTS (sample_struct); i++)
+    {
+      item = sample_struct[i];
+
+      if ((! posix_compat && 
+            (item.file_type == G_FILE_TYPE_SYMBOLIC_LINK ||
+            (item.flags & TEST_POSIX_ONLY) == TEST_POSIX_ONLY)) ||
+           ((item.flags & TEST_EVENT_CHANGED) != TEST_EVENT_CHANGED) ||
+           ((item.flags & TEST_EVENTS_RECEIVE_ONLY) == TEST_EVENTS_RECEIVE_ONLY))
+        continue;
+
+      child = g_file_get_child (root, item.filename);
+      g_assert (child != NULL);
+      error = NULL;
+      output_stream = g_file_append_to (child, G_FILE_CREATE_NONE, NULL, &error);
+      g_assert (output_stream != NULL);
+      g_assert (error == NULL);
+      outds = g_data_output_stream_new (G_OUTPUT_STREAM (output_stream));
+      g_assert (outds != NULL);
+
+      error = NULL;
+      res = g_data_output_stream_put_string (outds, "placeholder", NULL, &error);
+      g_assert_cmpint (res, ==, TRUE);
+      g_assert (error == NULL);
+      
+      error = NULL;
+      res =  g_output_stream_close (G_OUTPUT_STREAM (output_stream), NULL, &error);
+      g_assert_cmpint (res, ==, TRUE);
+      g_assert (error == NULL);
+
+      g_object_unref (output_stream);
+      g_object_unref (outds);
+      g_object_unref (child);
+    }
+  g_object_unref (root);
+}
+
+static void
+test_attr_change (const char *root_path)
+{
+  GFile *root, *child;
+  gboolean res;
+  GError *error;
+  int i;
+  struct TestStructureItem item;
+
+  
+  g_assert (root_path != NULL);
+  log ("\n  Starting attribute change tests in '%s'...\n", root_path);
+
+  root = g_file_new_for_commandline_arg (root_path);
+  g_assert (root != NULL);
+  res = g_file_query_exists (root, NULL);
+  g_assert_cmpint (res, ==, TRUE);
+
+  for (i = 0; i < G_N_ELEMENTS (sample_struct); i++)
+    {
+      item = sample_struct[i];
+
+      if ((! posix_compat && 
+            (item.file_type == G_FILE_TYPE_SYMBOLIC_LINK ||
+            (item.flags & TEST_POSIX_ONLY) == TEST_POSIX_ONLY)) ||
+           ((item.flags & TEST_EVENT_ATTRIBUTE_CHANGED) != TEST_EVENT_ATTRIBUTE_CHANGED) ||
+           ((item.flags & TEST_EVENTS_RECEIVE_ONLY) == TEST_EVENTS_RECEIVE_ONLY))
+        continue;
+    
+      child = g_file_get_child (root, item.filename);
+      g_assert (child != NULL);
+
+      if (posix_compat) {
+        error = NULL;
+        res = g_file_set_attribute_uint32 (child, G_FILE_ATTRIBUTE_UNIX_MODE,
+                                       S_IRWXU + S_IRWXG + S_IRWXO,
+                                       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                       NULL, &error);
+        g_assert_cmpint (res, ==, TRUE);
+        g_assert (error == NULL);
+      }
+
+      error = NULL;
+      res = g_file_set_attribute_uint64 (child, G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                     100000,
+                                     G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                     NULL, &error);
+      g_assert_cmpint (res, ==, TRUE);
+      g_assert (error == NULL);
+
+      g_object_unref (child);
+    }
+  g_object_unref (root);
+}
+
+static void
+test_delete (const char *root_path)
+{
+  GFile *root;
+  GFile *child;
+  gboolean res;
+  GError *error;
+  int i;
+  struct TestStructureItem item;
+
+  g_assert (root_path != NULL);
+  log ("\n  Starting delete tests in '%s'...\n", root_path);
+
+  root = g_file_new_for_commandline_arg (root_path);
+  g_assert (root != NULL);
+  res = g_file_query_exists (root, NULL);
+  g_assert_cmpint (res, ==, TRUE);
+
+  for (i = G_N_ELEMENTS (sample_struct) - 1; i >= 0; i--)
+    {
+      item = sample_struct[i];
+
+      if ((! posix_compat && 
+            (item.file_type == G_FILE_TYPE_SYMBOLIC_LINK ||
+            (item.flags & TEST_POSIX_ONLY) == TEST_POSIX_ONLY)) ||
+           ((item.flags & TEST_EVENT_DELETED) != TEST_EVENT_DELETED) ||
+           ((item.flags & TEST_EVENTS_RECEIVE_ONLY) == TEST_EVENTS_RECEIVE_ONLY))
+	continue;
+
+      child = g_file_get_child (root, item.filename);
+      g_assert (child != NULL);
+
+      error = NULL;
+      res = g_file_delete (child, NULL, &error);
+
+      if (error) {
+        log ("      result = %d, error = %s\n", res, error->message);
+        g_error_free (error);
+      }
+
+      g_object_unref (child);
+    }
+  g_object_unref (root);
+}
+
+static void
+cleanup_dir_recurse (GFile *parent, GFile *root)
+{
+  gboolean res;
+  GError *error;
+  GFileEnumerator *enumerator;
+  GFileInfo *info;
+  GFile *descend;
+  char *relative_path;
+
+  g_assert (root != NULL);
+
+  error = NULL;
+  enumerator =
+    g_file_enumerate_children (parent, "*",
+			       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL,
+			       &error);
+  if (! enumerator)
+	  return;
+
+  error = NULL;
+  info = g_file_enumerator_next_file (enumerator, NULL, &error);
+  while ((info) && (!error))
+    {
+      descend = g_file_get_child (parent, g_file_info_get_name (info));
+      g_assert (descend != NULL);
+      relative_path = g_file_get_relative_path (root, descend);
+      g_assert (relative_path != NULL);
+
+      log ("    deleting '%s'\n", g_file_info_get_display_name (info));
+
+      if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+    	  cleanup_dir_recurse (descend, root);
+      
+      error = NULL;
+      res = g_file_delete (descend, NULL, &error);
+      g_assert_cmpint (res, ==, TRUE);
+
+      g_object_unref (descend);
+      error = NULL;
+      info = g_file_enumerator_next_file (enumerator, NULL, &error);
+    }
+  g_assert (error == NULL);
+
+  error = NULL;
+  res = g_file_enumerator_close (enumerator, NULL, &error);
+  g_assert_cmpint (res, ==, TRUE);
+  g_assert (error == NULL);
+}
+
+static void
+prep_clean_structure (gconstpointer test_data)
+{
+  GFile *root;
+  
+  g_assert (test_data != NULL);
+  log ("\n  Cleaning target testing structure in '%s'...\n",
+       (char *) test_data);
+
+  root = g_file_new_for_commandline_arg ((char *) test_data);
+  g_assert (root != NULL);
+  
+  cleanup_dir_recurse (root, root);
+
+  g_file_delete (root, NULL, NULL);
+  
+  g_object_unref (root);
+}
+
+
+static void
+monitor_callback (GFileMonitor     *monitor,
+                  GFile            *file,
+                  GFile            *other_file,
+                  GFileMonitorEvent event_type,
+                  gpointer          user_data)
+{
+  static const char* monitor_event_strings[7] = { 
+      "G_FILE_MONITOR_EVENT_CHANGED",
+      "G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT",
+      "G_FILE_MONITOR_EVENT_DELETED",
+      "G_FILE_MONITOR_EVENT_CREATED",
+      "G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED",
+      "G_FILE_MONITOR_EVENT_PRE_UNMOUNT",
+      "G_FILE_MONITOR_EVENT_UNMOUNTED" };
+
+  struct Watch *watch;
+  char *s;
+
+  watch = user_data;
+  g_assert (watch != NULL);
+
+  
+  s = g_file_get_path (file);
+  
+  log ("     --> monitor_callback spawned (%s), monitor = 0x%lX, file = '%s', event_type = %s, watched path = '%s'\n", watch->is_dir_monitor ? "dir" : "file", 
+       (unsigned long)watch->monitor, s, monitor_event_strings[event_type], watch->full_path);
+
+  if (g_strcmp0 (s, watch->full_path) != 0) {
+    log ("       ---> paths not equal, ignoring!\n");
+  }
+  else {
+    if (! watch->pinged_path)
+      watch->pinged_path = g_strdup (s);
+    switch (event_type) {
+      case G_FILE_MONITOR_EVENT_CHANGED:
+      case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+        watch->changed_pong = TRUE;
+        break;
+      case G_FILE_MONITOR_EVENT_DELETED:
+        watch->deleted_pong = TRUE;
+        break;
+      case G_FILE_MONITOR_EVENT_CREATED:
+        watch->created_pong = TRUE;
+        break;
+      case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+        watch->attr_changed_pong = TRUE;
+        break;
+      case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+      case G_FILE_MONITOR_EVENT_UNMOUNTED:
+        /*  We're not watching mounts (requires root privilegies to test)  */
+        break;
+    }
+  }
+  g_free (s);
+}
+
+/*  This must be spawned *before* a real file is created  */
+static void
+add_watch (GFile *file, const struct TestStructureItem *test_item)
+{
+  struct Watch *watch;
+  GError *error;
+  GFile *parent;
+
+  /*  Create primary file monitor (type autodetected) */
+  watch = g_slice_new0 (struct Watch);
+  watch->test_item = test_item;
+  watch->is_dir_monitor = FALSE;
+  watch->full_path = g_file_get_path (file);
+  error = NULL;
+  watch->monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, &error);
+  log ("  add_watch: added file monitor 0x%lX\n", (unsigned long int) watch->monitor);
+  g_assert (error == NULL);
+  g_assert (watch->monitor != NULL);
+  g_signal_connect (watch->monitor, "changed", G_CALLBACK (monitor_callback), watch);
+  active_watches = g_list_append (active_watches, watch);
+
+  /*  Create directory monitor on parent  */
+  watch = g_slice_new0 (struct Watch);
+  watch->test_item = test_item;
+  watch->is_dir_monitor = TRUE;
+  watch->full_path = g_file_get_path (file);
+  error = NULL;
+  parent = g_file_get_parent (file);
+  g_assert (parent != NULL);
+  watch->monitor = g_file_monitor_directory (parent, G_FILE_MONITOR_NONE, NULL, &error);
+  log ("  add_watch: added dir monitor 0x%lX\n", (unsigned long int) watch->monitor);
+  g_assert (error == NULL);
+  g_assert (watch->monitor != NULL);
+  g_signal_connect (watch->monitor, "changed", G_CALLBACK (monitor_callback), watch);
+  active_watches = g_list_append (active_watches, watch);
+
+  g_object_unref (parent);
+}
+
+static void
+check_collected_events ()
+{
+  GList *list;
+  struct Watch *watch;
+
+  log ("\n");
+  for (list = active_watches; list != NULL; list = g_list_next (list)) {
+    watch = list->data;
+    
+    log ("check_collected_events: [%s monitor] monitor = 0x%lX file '%s', EVENT_CREATED: %d, EVENT_CHANGED: %d, EVENT_DELETED: %d, EVENT_ATTRIBUTE_CHANGED: %d\n",
+          watch->is_dir_monitor ? "directory" : "file", (unsigned long)watch->monitor, watch->test_item->filename, watch->created_pong, watch->changed_pong, watch->deleted_pong, watch->attr_changed_pong);
+
+    /*  directory symlink monitors on parent directory don't get any events by design  */
+    if (watch->test_item->file_type != G_FILE_TYPE_SYMBOLIC_LINK || ! watch->is_dir_monitor) {
+      if ((watch->test_item->flags & TEST_EVENT_CHANGED) == TEST_EVENT_CHANGED)
+        g_assert_cmpint (watch->changed_pong, ==, TRUE);
+      if ((watch->test_item->flags & TEST_EVENT_DELETED) == TEST_EVENT_DELETED)
+        g_assert_cmpint (watch->deleted_pong, ==, TRUE);
+      if ((watch->test_item->flags & TEST_EVENT_ATTRIBUTE_CHANGED) == TEST_EVENT_ATTRIBUTE_CHANGED)
+        g_assert_cmpint (watch->attr_changed_pong, ==, TRUE);
+      g_assert_cmpint (watch->created_pong, ==, TRUE);
+      
+      if ((watch->test_item->flags & TEST_EVENT_CHANGED) == TEST_EVENT_CHANGED ||
+          (watch->test_item->flags & TEST_EVENT_DELETED) == TEST_EVENT_DELETED ||
+          (watch->test_item->flags & TEST_EVENT_ATTRIBUTE_CHANGED) == TEST_EVENT_ATTRIBUTE_CHANGED)
+        g_assert_cmpstr (watch->full_path, ==, watch->pinged_path);
+    }
+    
+#if 1
+    /*  destroy objects  */
+    g_file_monitor_cancel (watch->monitor);
+    g_object_unref (watch->monitor);
+    g_free (watch->full_path);
+    g_free (watch->pinged_path);
+    g_slice_free1 (sizeof (struct Watch), watch);
+#endif
+  }
+  g_list_free (active_watches);
+}
+
+static void
+reset_flags ()
+{
+  GList *list;
+  struct Watch *watch;
+
+  for (list = active_watches; list != NULL; list = g_list_next (list)) {
+    watch = list->data;
+    watch->changed_pong = FALSE;
+    watch->deleted_pong = FALSE;
+    watch->attr_changed_pong = FALSE;
+  }
+}
+
+static gboolean
+collect_events_cb (gpointer user_data)
+{
+  g_assert (user_data != NULL);
+  g_main_loop_quit (user_data);
+  
+  return FALSE;    
+}
+
+static gboolean
+start_delete_test_cb (gpointer user_data)
+{
+  g_assert (user_data != NULL);
+  test_delete (target_path);
+  g_timeout_add (MONITOR_SETTLE_DELAY, collect_events_cb, user_data);
+  
+  return FALSE;    
+}
+
+static gboolean
+start_attr_change_test_cb (gpointer user_data)
+{
+  g_assert (user_data != NULL);
+  test_attr_change (target_path);
+  g_timeout_add (MONITOR_BEFORE_DELETE_DELAY, start_delete_test_cb, user_data);
+  
+  return FALSE;    
+}
+
+static gboolean
+start_change_test_cb (gpointer user_data)
+{
+  g_assert (user_data != NULL);
+  /*  Clear all changed flags, set after file creation  */
+  reset_flags ();
+  test_change (target_path);
+  g_timeout_add (MONITOR_BEFORE_ATTRCH_DELAY, start_attr_change_test_cb, user_data);
+  
+  return FALSE;    
+}
+
+static void
+do_tests (gconstpointer test_data)
+{
+  GMainLoop *loop;
+
+  g_assert (test_data != NULL);
+  
+  /*  Initialize watch list  */
+  active_watches = NULL;
+  
+  /*  Create testing structure (initial create)  */
+  test_create_structure ((char *) test_data);
+  
+  log ("\n  Starting tests in '%s'...\n", (char *) test_data);
+  
+  /*  Settle down to allow monitor events be spawned  */
+  loop = g_main_loop_new (NULL, TRUE);
+  g_timeout_add (MONITOR_BEFORE_CHANGE_DELAY, start_change_test_cb, loop);
+  g_main_loop_run (loop);
+  log ("done.\n");
+  g_main_loop_unref (loop);
+  
+  /*  Compare recorded monitor events with expected ones  */
+  check_collected_events ();
+}
+
+
+
+int
+main (int argc, char *argv[])
+{
+  GError *error;
+  GOptionContext *context;
+
+  static GOptionEntry cmd_entries[] = {
+    {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose [default=yes]", NULL},
+    {"posix", 'x', 0, G_OPTION_ARG_NONE, &posix_compat,
+     "Test POSIX-specific features (unix permissions, symlinks) [default=yes]", NULL},
+    {NULL}
+  };
+
+  verbose = FALSE;
+  target_path = NULL;
+  posix_compat = FALSE;
+
+  /*  strip all gtester-specific args  */
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /*  no extra parameters specified, assume we're executed from glib test suite  */ 
+  if (argc < 2)
+    {
+	  verbose = TRUE;
+	  target_path = DEFAULT_TEST_DIR;
+#ifdef G_PLATFORM_WIN32
+	  posix_compat = FALSE;
+#else
+	  posix_compat = TRUE;
+#endif
+    }
+  
+  /*  add trailing args  */
+  error = NULL;
+  context = g_option_context_new ("target_path");
+  g_option_context_add_main_entries (context, cmd_entries, NULL);
+  if (!g_option_context_parse (context, &argc, &argv, &error))
+    {
+      g_print ("option parsing failed: %s\n", error->message);
+      return g_test_run ();
+    }
+
+  /*  remaining arg should is the target path; we don't care of the extra args here  */ 
+  if (argc >= 2)
+    target_path = strdup (argv[1]);
+  
+  if (! target_path) 
+    {
+      g_print ("error: target path was not specified\n");
+      g_print (g_option_context_get_help (context, TRUE, NULL));
+      return g_test_run ();
+    }
+
+  
+  /*  Clean target directory first  */
+  g_test_add_data_func ("/live-monitor/prep_clean_structure", target_path,
+           	  	  prep_clean_structure);
+  
+  /*  Perform all tests  */
+  g_test_add_data_func ("/live-monitor/do_tests", target_path,
+			  do_tests);
+
+  /*  Final cleanup  */
+  g_test_add_data_func ("/live-monitor/final_clean", target_path,
+    	  	  prep_clean_structure);
+  
+  return g_test_run ();
+}
Index: Makefile.am
===================================================================
--- Makefile.am	(revision 7340)
+++ Makefile.am	(working copy)
@@ -25,7 +25,7 @@
 	data-output-stream
 
 if OS_UNIX
-TEST_PROGS += live-g-file
+TEST_PROGS += live-g-file live-monitor
 endif
 
 memory_input_stream_SOURCES	  = memory-input-stream.c
@@ -48,3 +48,6 @@
 
 live_g_file_SOURCES	  = live-g-file.c
 live_g_file_LDADD	  = $(progs_ldadd)
+
+live_monitor_SOURCES	  = live-monitor.c
+live_monitor_LDADD	  = $(progs_ldadd)
