summaryrefslogtreecommitdiff
path: root/egg
diff options
context:
space:
mode:
authorStef Walter <stefw@redhat.com>2014-08-30 07:32:23 +0200
committerStef Walter <stefw@redhat.com>2014-09-13 08:32:03 +0200
commit8c9c1a8fcadff500bc39f71acb411763e7afbe3f (patch)
tree1cc99113c7cfe0ffffecb16de042f8331b32ee07 /egg
parent1dedc7c174c65f9e1680e19b53c0f44295bd03c5 (diff)
downloadgnome-keyring-8c9c1a8fcadff500bc39f71acb411763e7afbe3f.tar.gz
egg: Move file tracker code to egg/ directory
So that it can be used in other parts of gnome-keyring.
Diffstat (limited to 'egg')
-rw-r--r--egg/Makefile.am6
-rw-r--r--egg/egg-file-tracker.c303
-rw-r--r--egg/egg-file-tracker.h59
-rw-r--r--egg/test-file-tracker.c251
4 files changed, 619 insertions, 0 deletions
diff --git a/egg/Makefile.am b/egg/Makefile.am
index f4893578..1e3bc889 100644
--- a/egg/Makefile.am
+++ b/egg/Makefile.am
@@ -25,6 +25,7 @@ libegg_la_SOURCES = \
egg/egg-dh.c egg/egg-dh.h \
egg/egg-dn.c egg/egg-dn.h \
egg/egg-error.h \
+ egg/egg-file-tracker.c egg/egg-file-tracker.h \
egg/egg-hex.c egg/egg-hex.h \
egg/egg-hkdf.c egg/egg-hkdf.h \
egg/egg-libgcrypt.c egg/egg-libgcrypt.h \
@@ -126,6 +127,7 @@ egg_LIBS = \
libegg.la \
$(LIBGCRYPT_LIBS) \
$(GTHREAD_LIBS) \
+ $(GOBJECT_LIBS) \
$(GLIB_LIBS)
egg_TESTS = \
@@ -142,6 +144,7 @@ egg_TESTS = \
test-armor \
test-openssl \
test-dh \
+ test-file-tracker \
test-spawn
test_asn1_SOURCES = egg/test-asn1.c egg/test.asn.h
@@ -156,6 +159,9 @@ test_dn_LDADD = $(egg_LIBS)
test_cleanup_SOURCES = egg/test-cleanup.c
test_cleanup_LDADD = $(egg_LIBS)
+test_file_tracker_SOURCES = egg/test-file-tracker.c
+test_file_tracker_LDADD = $(egg_LIBS)
+
test_hex_SOURCES = egg/test-hex.c
test_hex_LDADD = $(egg_LIBS)
diff --git a/egg/egg-file-tracker.c b/egg/egg-file-tracker.c
new file mode 100644
index 00000000..0dbfbef6
--- /dev/null
+++ b/egg/egg-file-tracker.c
@@ -0,0 +1,303 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* egg-file-tracker.c - Watch for changes in a directory
+
+ Copyright (C) 2008 Stefan Walter
+
+ The Gnome Keyring Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Keyring Library 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. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ <http://www.gnu.org/licenses/>.
+
+ Author: Stef Walter <stef@memberwebs.com>
+*/
+
+#include "config.h"
+
+#include "egg-file-tracker.h"
+
+#include "egg/egg-error.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+typedef struct _UpdateDescendants {
+ EggFileTracker *tracker;
+ GHashTable *checks;
+} UpdateDescendants;
+
+struct _EggFileTracker {
+ GObject parent;
+
+ /* Specification */
+ GPatternSpec *include;
+ GPatternSpec *exclude;
+ gchar *directory_path;
+ time_t directory_mtime;
+
+ /* Matched files */
+ GHashTable *files;
+};
+
+enum {
+ FILE_ADDED,
+ FILE_REMOVED,
+ FILE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (EggFileTracker, egg_file_tracker, G_TYPE_OBJECT);
+
+/* -----------------------------------------------------------------------------
+ * HELPERS
+ */
+
+static void
+copy_key_string (gpointer key, gpointer value, gpointer data)
+{
+ GHashTable *dest = (GHashTable*)data;
+ g_hash_table_replace (dest, g_strdup (key), value);
+}
+
+static void
+remove_files (gpointer key, gpointer value, gpointer data)
+{
+ EggFileTracker *self = EGG_FILE_TRACKER (data);
+
+ g_hash_table_remove (self->files, key);
+ g_signal_emit (self, signals[FILE_REMOVED], 0, key);
+}
+
+static gboolean
+update_file (EggFileTracker *self, gboolean force_all, const gchar *path)
+{
+ time_t old_mtime;
+ struct stat sb;
+
+ if (stat (path, &sb) < 0) {
+ if (errno != ENOENT && errno != ENOTDIR && errno != EPERM)
+ g_warning ("couldn't stat file: %s: %s", path, g_strerror (errno));
+ return FALSE;
+ }
+
+ old_mtime = GPOINTER_TO_UINT (g_hash_table_lookup (self->files, path));
+ g_assert (old_mtime);
+
+ /* See if it has actually changed */
+ if (force_all || old_mtime != sb.st_mtime) {
+ g_assert (g_hash_table_lookup (self->files, path));
+ g_hash_table_insert (self->files, g_strdup (path), GUINT_TO_POINTER (sb.st_mtime));
+ g_signal_emit (self, signals[FILE_CHANGED], 0, path);
+ }
+
+ return TRUE;
+}
+
+static void
+update_each_file (gpointer key, gpointer unused, gpointer data)
+{
+ UpdateDescendants *ctx = (UpdateDescendants*)data;
+ if (update_file (ctx->tracker, FALSE, key))
+ g_hash_table_remove (ctx->checks, key);
+}
+
+static void
+update_directory (EggFileTracker *self, gboolean force_all, GHashTable *checks)
+{
+ UpdateDescendants uctx;
+ struct stat sb;
+ GError *err = NULL;
+ const char *filename;
+ gchar *file;
+ GDir *dir;
+ int ret, lasterr;
+
+ g_assert (checks);
+ g_assert (EGG_IS_FILE_TRACKER (self));
+
+ if (!self->directory_path)
+ return;
+
+ if (stat (self->directory_path, &sb) < 0) {
+ if (errno != ENOENT && errno != ENOTDIR && errno != EPERM)
+ g_message ("couldn't stat directory: %s: %s",
+ self->directory_path, g_strerror (errno));
+ return;
+ }
+
+ /* See if it was updated since last seen or not */
+ if (!force_all && self->directory_mtime == sb.st_mtime) {
+
+ uctx.checks = checks;
+ uctx.tracker = self;
+
+ /* Still need to check for individual file updates */
+ g_hash_table_foreach (self->files, update_each_file, &uctx);
+ return;
+ }
+
+ self->directory_mtime = sb.st_mtime;
+
+ /* Actually list the directory */
+ dir = g_dir_open (self->directory_path, 0, &err);
+ if (dir == NULL) {
+ if (errno != ENOENT && errno != ENOTDIR && errno != EPERM)
+ g_message ("couldn't list keyrings at: %s: %s", self->directory_path,
+ egg_error_message (err));
+ g_error_free (err);
+ return;
+ }
+
+ while ((filename = g_dir_read_name (dir)) != NULL) {
+ if (filename[0] == '.')
+ continue;
+ if (self->include && !g_pattern_match_string (self->include, filename))
+ continue;
+ if (self->exclude && g_pattern_match_string (self->exclude, filename))
+ continue;
+
+ file = g_build_filename (self->directory_path, filename, NULL);
+
+ /* If we hadn't yet seen this, then add it */
+ if (!g_hash_table_remove (checks, file)) {
+
+ /* Get the last modified time for this one */
+ ret = g_stat (file, &sb);
+ lasterr = errno;
+
+ /* Couldn't access the file */
+ if (ret < 0) {
+ g_message ("couldn't stat file: %s: %s", file, g_strerror (lasterr));
+
+ } else {
+
+ /* We don't do directories */
+ if (!(sb.st_mode & S_IFDIR)) {
+ g_hash_table_replace (self->files, g_strdup (file), GINT_TO_POINTER (sb.st_mtime));
+ g_signal_emit (self, signals[FILE_ADDED], 0, file);
+ }
+ }
+
+ /* Otherwise we already had it, see if it needs updating */
+ } else {
+ update_file (self, force_all, file);
+ }
+
+ g_free (file);
+ }
+
+ g_dir_close (dir);
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static void
+egg_file_tracker_init (EggFileTracker *self)
+{
+ self->files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+static void
+egg_file_tracker_finalize (GObject *obj)
+{
+ EggFileTracker *self = EGG_FILE_TRACKER (obj);
+
+ if (self->include)
+ g_pattern_spec_free (self->include);
+ if (self->exclude)
+ g_pattern_spec_free (self->exclude);
+ g_free (self->directory_path);
+
+ g_hash_table_destroy (self->files);
+
+ G_OBJECT_CLASS (egg_file_tracker_parent_class)->finalize (obj);
+}
+
+static void
+egg_file_tracker_class_init (EggFileTrackerClass *klass)
+{
+ GObjectClass *gobject_class;
+ gobject_class = (GObjectClass*) klass;
+
+ egg_file_tracker_parent_class = g_type_class_peek_parent (klass);
+ gobject_class->finalize = egg_file_tracker_finalize;
+
+ signals[FILE_ADDED] = g_signal_new ("file-added", EGG_TYPE_FILE_TRACKER,
+ G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_added),
+ NULL, NULL, g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ signals[FILE_CHANGED] = g_signal_new ("file-changed", EGG_TYPE_FILE_TRACKER,
+ G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_changed),
+ NULL, NULL, g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ signals[FILE_REMOVED] = g_signal_new ("file-removed", EGG_TYPE_FILE_TRACKER,
+ G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_removed),
+ NULL, NULL, g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+EggFileTracker*
+egg_file_tracker_new (const gchar *directory, const gchar *include, const gchar *exclude)
+{
+ EggFileTracker *self;
+ const gchar *homedir;
+
+ g_return_val_if_fail (directory, NULL);
+
+ self = g_object_new (EGG_TYPE_FILE_TRACKER, NULL);
+
+ /* TODO: Use properties */
+
+ if (directory[0] == '~' && directory[1] == '/') {
+ homedir = g_getenv ("HOME");
+ if (!homedir)
+ homedir = g_get_home_dir ();
+ self->directory_path = g_build_filename (homedir, directory + 2, NULL);
+
+ /* A relative or absolute path */
+ } else {
+ self->directory_path = g_strdup (directory);
+ }
+
+ self->include = include ? g_pattern_spec_new (include) : NULL;
+ self->exclude = exclude ? g_pattern_spec_new (exclude) : NULL;
+
+ return self;
+}
+
+void
+egg_file_tracker_refresh (EggFileTracker *self, gboolean force_all)
+{
+ GHashTable *checks;
+
+ g_return_if_fail (EGG_IS_FILE_TRACKER (self));
+
+ /* Copy into our check set */
+ checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ g_hash_table_foreach (self->files, copy_key_string, checks);
+
+ /* If only one volume, then just try and access it directly */
+ update_directory (self, force_all, checks);
+
+ /* Find any keyrings whose paths we didn't see */
+ g_hash_table_foreach (checks, remove_files, self);
+ g_hash_table_destroy (checks);
+}
diff --git a/egg/egg-file-tracker.h b/egg/egg-file-tracker.h
new file mode 100644
index 00000000..9ce2f9b9
--- /dev/null
+++ b/egg/egg-file-tracker.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* egg-file-tracker.h - Watch for changes in a directory
+
+ Copyright (C) 2008, Stefan Walter
+
+ The Gnome Keyring Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Keyring Library 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. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ <http://www.gnu.org/licenses/>.
+
+ Author: Stef Walter <stef@memberwebs.com>
+*/
+
+#ifndef __EGG_FILE_TRACKER_H__
+#define __EGG_FILE_TRACKER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_FILE_TRACKER (egg_file_tracker_get_type ())
+#define EGG_FILE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_FILE_TRACKER, EggFileTracker))
+#define EGG_FILE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_FILE_TRACKER, GObject))
+#define EGG_IS_FILE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_FILE_TRACKER))
+#define EGG_IS_FILE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_FILE_TRACKER))
+#define EGG_FILE_TRACKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_FILE_TRACKER, EggFileTrackerClass))
+
+typedef struct _EggFileTracker EggFileTracker;
+typedef struct _EggFileTrackerClass EggFileTrackerClass;
+
+struct _EggFileTrackerClass {
+ GObjectClass parent_class;
+
+ void (*file_added) (EggFileTracker *locmgr, const gchar *path);
+ void (*file_changed) (EggFileTracker *locmgr, const gchar *path);
+ void (*file_removed) (EggFileTracker *locmgr, const gchar *path);
+};
+
+GType egg_file_tracker_get_type (void) G_GNUC_CONST;
+
+EggFileTracker* egg_file_tracker_new (const gchar *directory,
+ const gchar *include_pattern,
+ const gchar *exclude_pattern);
+
+void egg_file_tracker_refresh (EggFileTracker *self,
+ gboolean force_all);
+
+G_END_DECLS
+
+#endif /* __EGG_FILE_TRACKER_H__ */
diff --git a/egg/test-file-tracker.c b/egg/test-file-tracker.c
new file mode 100644
index 00000000..926b4485
--- /dev/null
+++ b/egg/test-file-tracker.c
@@ -0,0 +1,251 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* test-file-tracker.c: File tracker tests
+
+ Copyright (C) 2007 Stefan Walter
+
+ The Gnome Keyring Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Keyring Library 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. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ <http://www.gnu.org/licenses/>.
+
+ Author: Stef Walter <stef@memberwebs.com>
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "egg/egg-file-tracker.h"
+
+#include <glib/gstdio.h>
+
+#define DATA "test-data"
+#define SUBDIR "test-subdir"
+#define WILDCARD "*.woo?"
+
+typedef struct {
+ EggFileTracker *the_tracker;
+ gchar *test_dir;
+ gchar *test_file;
+ guint n_files_added;
+ gchar* last_file_added;
+ guint n_files_changed;
+ gchar* last_file_changed;
+ guint n_files_removed;
+ gchar* last_file_removed;
+} Test;
+
+static void
+file_added (EggFileTracker *tracker,
+ const gchar *path,
+ gpointer user_data)
+{
+ Test *test = user_data;
+
+ g_assert ("should be a non-null path" && path != NULL);
+
+ ++test->n_files_added;
+ g_free (test->last_file_added);
+ test->last_file_added = g_strdup (path);
+}
+
+static void
+file_changed (EggFileTracker *tracker,
+ const gchar *path,
+ gpointer user_data)
+{
+ Test *test = user_data;
+
+ g_assert ("should be a non-null path" && path != NULL);
+
+ ++test->n_files_changed;
+ g_free (test->last_file_changed);
+ test->last_file_changed = g_strdup (path);
+}
+
+static void
+file_removed (EggFileTracker *tracker,
+ const gchar *path,
+ gpointer user_data)
+{
+ Test *test = user_data;
+
+ g_assert ("should be a non-null path" && path != NULL);
+
+ ++test->n_files_removed;
+ g_free (test->last_file_removed);
+ test->last_file_removed = g_strdup (path);
+}
+
+static void
+file_reset_stats (Test *test)
+{
+ g_free (test->last_file_removed);
+ g_free (test->last_file_added);
+ g_free (test->last_file_changed);
+ test->last_file_removed = test->last_file_added = test->last_file_changed = NULL;
+ test->n_files_added = test->n_files_changed = test->n_files_removed = 0;
+}
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+ /* Make a test directory */
+ test->test_dir = g_build_filename ("/tmp", SUBDIR, NULL);
+
+ test->the_tracker = egg_file_tracker_new (test->test_dir, WILDCARD, NULL);
+ g_signal_connect (test->the_tracker, "file-added", G_CALLBACK (file_added), test);
+ g_signal_connect (test->the_tracker, "file-removed", G_CALLBACK (file_removed), test);
+ g_signal_connect (test->the_tracker, "file-changed", G_CALLBACK (file_changed), test);
+
+ /* Mtime must change so wait between tests */
+ sleep (1);
+
+ test->test_file = g_build_filename (test->test_dir, "my-file.woof", NULL);
+ g_unlink (test->test_file);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+ file_reset_stats (test);
+ g_object_unref (test->the_tracker);
+ g_free (test->test_dir);
+ g_free (test->test_file);
+}
+
+static void
+test_file_watch (Test *test, gconstpointer unused)
+{
+ /* A watch for an non-existant directory, should have no responses */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+
+ g_assert_cmpint (0, ==, test->n_files_added);
+ g_assert_cmpint (0, ==, test->n_files_changed);
+ g_assert_cmpint (0, ==, test->n_files_removed);
+
+ g_mkdir_with_parents (test->test_dir, 0700);
+
+ /* Should still have no responses even though it exists */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+
+ g_assert_cmpint (0, ==, test->n_files_added);
+ g_assert_cmpint (0, ==, test->n_files_changed);
+ g_assert_cmpint (0, ==, test->n_files_removed);
+}
+
+static void
+test_watch_file (Test *test, gconstpointer unused)
+{
+ gboolean ret;
+
+ /* Make sure things are clean */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+
+ test->n_files_added = test->n_files_changed = test->n_files_removed = 0;
+ test->last_file_added = test->last_file_changed = test->last_file_removed = 0;
+
+ ret = g_file_set_contents (test->test_file, DATA, strlen (DATA), NULL);
+ g_assert (ret);
+
+ /* Now make sure that file is located */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+
+ g_assert_cmpint (1, ==, test->n_files_added);
+ g_assert_cmpint (0, ==, test->n_files_changed);
+ g_assert_cmpint (0, ==, test->n_files_removed);
+
+ /* The added one should match our file */
+ g_assert_cmpstr (test->last_file_added, ==, test->test_file);
+
+ file_reset_stats (test);
+ sleep (1);
+
+ /* Shouldn't find the file again */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+ g_assert_cmpint (0, ==, test->n_files_added);
+ g_assert_cmpint (0, ==, test->n_files_changed);
+ g_assert_cmpint (0, ==, test->n_files_removed);
+
+ /* But we should find the file if forced to */
+ egg_file_tracker_refresh (test->the_tracker, TRUE);
+ g_assert_cmpint (0, ==, test->n_files_added);
+ g_assert_cmpint (1, ==, test->n_files_changed);
+ g_assert_cmpint (0, ==, test->n_files_removed);
+ g_assert_cmpstr (test->last_file_changed, ==, test->test_file);
+
+ file_reset_stats (test);
+
+ ret = g_file_set_contents (test->test_file, DATA, strlen (DATA), NULL);
+ g_assert (ret);
+
+ /* File was updated */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+ g_assert_cmpint (0, ==, test->n_files_added);
+ g_assert_cmpint (1, ==, test->n_files_changed);
+ g_assert_cmpint (0, ==, test->n_files_removed);
+ g_assert_cmpstr (test->last_file_changed, ==, test->test_file);
+
+ file_reset_stats (test);
+ g_unlink (test->test_file);
+
+ /* Now file should be removed */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+
+ g_assert_cmpint (0, ==, test->n_files_added);
+ g_assert_cmpint (0, ==, test->n_files_changed);
+ g_assert_cmpint (1, ==, test->n_files_removed);
+ g_assert_cmpstr (test->last_file_removed, ==, test->test_file);
+}
+
+static void
+test_nomatch (Test *test, gconstpointer unused)
+{
+ gchar *file = g_build_filename (test->test_dir, "my-file.toot", NULL);
+ gboolean ret;
+
+ /* Mtime must change so wait between tests */
+ sleep (1);
+
+ ret = g_file_set_contents (file, DATA, strlen (DATA), NULL);
+ g_assert (ret);
+
+ file_reset_stats (test);
+
+ /* Now make sure that file is not located */
+ egg_file_tracker_refresh (test->the_tracker, FALSE);
+
+ g_assert_cmpint (0, ==, test->n_files_added);
+ g_assert_cmpint (0, ==, test->n_files_changed);
+ g_assert_cmpint (0, ==, test->n_files_removed);
+
+ g_unlink (file);
+ g_free (file);
+}
+
+int
+main (int argc, char **argv)
+{
+#if !GLIB_CHECK_VERSION(2,35,0)
+ g_type_init ();
+#endif
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add ("/egg/file-tracker/file_watch", Test, NULL, setup, test_file_watch, teardown);
+ g_test_add ("/egg/file-tracker/watch_file", Test, NULL, setup, test_watch_file, teardown);
+ g_test_add ("/egg/file-tracker/nomatch", Test, NULL, setup, test_nomatch, teardown);
+
+ return g_test_run ();
+}