/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2007-2010 David Zeuthen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "config.h" #include #include #include #include "stc.h" static GMainLoop *loop = NULL; /* ---------------------------------------------------------------------------------------------------- */ static const gchar * item_type_to_str (StcItemType type) { GEnumClass *klass; GEnumValue *value; const gchar *ret; ret = ""; klass = g_type_class_ref (STC_TYPE_ITEM_TYPE); if (klass == NULL) goto out; ret = ""; value = g_enum_get_value (klass, type); if (value == NULL) goto out; ret = value->value_nick; out: if (klass != NULL) g_type_class_unref (klass); return ret; } static void item_append_str (StcItem *item, GString *str) { const gchar* const *keys; const gchar* const *deps; keys = stc_item_get_option_keys (item); deps = stc_item_get_dependencies (item); g_string_append_printf (str, "id `%s'\n" "target `%s'\n" "nick-name `%s'\n" "type %s\n" "options ", stc_item_get_id (item) != NULL ? stc_item_get_id (item) : "(none)", stc_item_get_target (item) != NULL ? stc_item_get_target (item) : "(none)", stc_item_get_nick_name (item) != NULL ? stc_item_get_nick_name (item) : "(none)", item_type_to_str (stc_item_get_item_type (item))); if (g_strv_length ((gchar **) keys) == 0) { g_string_append (str, "(none)"); } else { guint n; for (n = 0; keys[n] != NULL; n++) { const gchar *value; if (n > 0) g_string_append (str, "\n "); value = stc_item_get_option (item, keys[n]); g_string_append_printf (str, "%s -> `%s'", keys[n], value); } } g_string_append (str, "\n"); g_string_append (str, "dependencies "); if (deps == NULL || g_strv_length ((gchar **) deps) == 0) { g_string_append (str, "(none)"); } else { guint n; for (n = 0; deps[n] != NULL; n++) { if (n > 0) g_string_append (str, "\n "); g_string_append (str, deps[n]); } } g_string_append (str, "\n"); g_string_append (str, "\n"); } /* ---------------------------------------------------------------------------------------------------- */ static void on_error_append_str (StcMonitor *monitor, const gchar *filename, gint line_no, GError *error, gpointer user_data) { GString *str = user_data; if (filename != NULL) { if (line_no >= 0) g_string_append_printf (str, "%s:%d: ", filename, line_no); else g_string_append_printf (str, "%s: ", filename); } g_string_append_printf (str, "%s (%s, %d)\n", error->message, g_quark_to_string (error->domain), error->code); } static void test_stc_basic (void) { StcMonitor *monitor; monitor = stc_monitor_new (NULL, NULL); g_assert_cmpstr (stc_monitor_get_config_dir (monitor), ==, PACKAGE_SYSCONF_DIR); g_object_unref (monitor); monitor = stc_monitor_new_for_config_dir (SRCDIR, NULL, NULL); g_assert_cmpstr (stc_monitor_get_config_dir (monitor), ==, SRCDIR); g_object_unref (monitor); } static void test_stc_no_conf (void) { StcMonitor *monitor; GString *str; str = g_string_new (NULL); monitor = stc_monitor_new_for_config_dir (SRCDIR "/nonexistant", on_error_append_str, str); g_assert_cmpstr (stc_monitor_get_config_dir (monitor), ==, SRCDIR "/nonexistant"); g_object_unref (monitor); g_assert_cmpstr (str->str, ==, SRCDIR "/nonexistant/stc.conf.d: Error opening directory '" SRCDIR "/nonexistant/stc.conf.d': No such file or directory (g-file-error-quark, 4)\n" SRCDIR "/nonexistant/stc.conf: No such file or directory (g-file-error-quark, 4)\n" ); g_string_free (str, TRUE); } static void test_stc_semi_valid_conf (void) { StcMonitor *monitor; GString *str; GString *str2; GList *items; GList *l; str = g_string_new (NULL); monitor = stc_monitor_new_for_config_dir (SRCDIR "/testdata/semi_valid_conf", on_error_append_str, str); g_assert_cmpstr (stc_monitor_get_config_dir (monitor), ==, SRCDIR "/testdata/semi_valid_conf"); g_assert_cmpstr (str->str, ==, SRCDIR "/testdata/semi_valid_conf/stc.conf: Error parsing group name `Blah' (stc-error-quark, 1)\n" SRCDIR "/testdata/semi_valid_conf/stc.conf: Error parsing item type `XYZType' in group `XYZType ValidIdentifier' (stc-error-quark, 1)\n" SRCDIR "/testdata/semi_valid_conf/stc.conf: Invalid item identifer `with-dash' in group `Filesystem with-dash'. Only characters in [a-zA-Z0-9_] are supported. (stc-error-quark, 1)\n" SRCDIR "/testdata/semi_valid_conf/stc.conf: Error parsing group name `Filesystem with space' (stc-error-quark, 1)\n" SRCDIR "/testdata/semi_valid_conf/stc.conf: Element 0 of Options, `foo', for item foo of type MDRaid is malformed (no equal sign found). (stc-error-quark, 1)\n" SRCDIR "/testdata/semi_valid_conf/stc.conf: MDRaid item bar has unsupported target type (UUID and Name is supported). (stc-error-quark, 0)\n" SRCDIR "/testdata/semi_valid_conf/stc.conf.d/91.conf: Encountered item with duplicate id `multiple_instances_same_id'. Ignoring previous items. (stc-error-quark, 2)\n" SRCDIR "/testdata/semi_valid_conf/stc.conf: Item ItemWithUnresolved has unresolved dependency NonExisting. (stc-error-quark, 3)\n" ); str2 = g_string_new (NULL); items = stc_monitor_get_items (monitor); for (l = items; l != NULL; l = l->next) { StcItem *item = STC_ITEM (l->data); item_append_str (item, str2); } g_list_foreach (items, (GFunc) g_object_unref, NULL); g_list_free (items); g_assert_cmpstr (str2->str, ==, "id `in_pri_10'\n" "target `Device=/dev/sda'\n" "nick-name `(none)'\n" "type filesystem\n" "options Filesystem:mount_path -> `/mnt/10'\n" "dependencies (none)\n" "\n" "id `in_pri_50'\n" "target `Device=/dev/sda'\n" "nick-name `(none)'\n" "type filesystem\n" "options Filesystem:mount_path -> `/mnt/50'\n" "dependencies (none)\n" "\n" "id `in_pri_90'\n" "target `Device=/dev/sda'\n" "nick-name `(none)'\n" "type filesystem\n" "options Filesystem:mount_path -> `/mnt/90'\n" "dependencies (none)\n" "\n" "id `multiple_instances_same_id'\n" "target `Device=/dev/multiple_instances_same_id/second'\n" "nick-name `(none)'\n" "type filesystem\n" "options Filesystem:mount_path -> `/mnt/m_second'\n" "dependencies (none)\n" "\n" "id `BigStorage'\n" "target `Device=/dev/disk/md-uuid-01234:56789'\n" "nick-name `BigStorage'\n" "type filesystem\n" "options Filesystem:mount_path -> `/mnt/BigStorage'\n" "dependencies BigStorage_mdraid\n" "\n" "id `BigStorage_mdraid'\n" "target `UUID=01234:56789'\n" "nick-name `BigStorage RAID Array'\n" "type md-raid\n" "options (none)\n" "dependencies (none)\n" "\n" "id `SekritStuff'\n" "target `Device=/dev/disk/by-uuid/1234'\n" "nick-name `My Secret Stuff'\n" "type filesystem\n" "options Filesystem:mount_path -> `/mnt/SekritStuff'\n" " Filesystem:options -> `noatime,dirsync'\n" "dependencies SekritStuff_LUKS\n" "\n" "id `SekritStuff_LUKS'\n" "target `Device=/dev/disk/by-uuid/12345'\n" "nick-name `My Secret Stuff (Encrypted)'\n" "type luks\n" "options LUKS:password -> `xyz123'\n" "dependencies (none)\n" "\n" "id `ItemWithUnresolved'\n" "target `Device=/dev/sda'\n" "nick-name `(none)'\n" "type filesystem\n" "options Filesystem:mount_path -> `/mnt/Z'\n" "dependencies NonExisting\n" "\n" "id `OtherRaid'\n" "target `Name=homehost:array_name'\n" "nick-name `Other RAID'\n" "type md-raid\n" "options (none)\n" "dependencies (none)\n" "\n" ); g_string_free (str2, TRUE); g_object_unref (monitor); g_string_free (str, TRUE); } /* ---------------------------------------------------------------------------------------------------- */ static void cleanup_dir (const gchar *dirname) { gchar *command; g_assert (g_str_has_prefix (dirname, "/tmp/stc-monitoring-test-")); g_assert (strstr (dirname, "\"") == NULL); command = g_strdup_printf ("rm -rf \"%s\"", dirname); g_assert_cmpint (system (command), ==, 0); g_free (command); } static gboolean on_timeout (gpointer user_data) { g_assert_not_reached (); return FALSE; } typedef enum { _EVENT_ADDED, _EVENT_REMOVED, _EVENT_CHANGED } _Event; static void check_state (StcMonitor *monitor, StcItem *item, gint state, _Event event) { switch (state) { case 0: g_assert_cmpstr (stc_item_get_id (item), ==, "item1"); g_assert_cmpint (event, ==, _EVENT_ADDED); break; case 1: g_assert_cmpstr (stc_item_get_id (item), ==, "item2"); g_assert_cmpint (event, ==, _EVENT_ADDED); g_assert_cmpstr (stc_item_get_target (item), ==, "Device=/dev/sdc"); break; case 2: g_assert_cmpstr (stc_item_get_id (item), ==, "item1"); g_assert_cmpint (event, ==, _EVENT_REMOVED); break; case 3: g_assert_cmpstr (stc_item_get_id (item), ==, "item2"); g_assert_cmpint (event, ==, _EVENT_CHANGED); g_assert_cmpstr (stc_item_get_target (item), ==, "Device=/dev/sda"); break; case 4: g_assert_cmpstr (stc_item_get_id (item), ==, "item2"); g_assert_cmpint (event, ==, _EVENT_REMOVED); break; case 5: g_assert_cmpstr (stc_item_get_id (item), ==, "item3"); g_assert_cmpint (event, ==, _EVENT_ADDED); break; default: g_assert_not_reached (); break; } } static void on_item_added (StcMonitor *monitor, StcItem *item, gpointer user_data) { gint *state = user_data; check_state (monitor, item, *state, _EVENT_ADDED); *state += 1; g_main_loop_quit (loop); } static void on_item_removed (StcMonitor *monitor, StcItem *item, gpointer user_data) { gint *state = user_data; check_state (monitor, item, *state, _EVENT_REMOVED); *state += 1; g_main_loop_quit (loop); } static void on_item_changed (StcMonitor *monitor, StcItem *item, gpointer user_data) { gint *state = user_data; check_state (monitor, item, *state, _EVENT_CHANGED); *state += 1; g_main_loop_quit (loop); } static void test_stc_monitoring (void) { gchar *dirname; StcMonitor *monitor; GError *error; gboolean rc; gchar *filename; guint timeout_id; gint state; dirname = g_strdup ("/tmp/stc-monitoring-test-XXXXXX"); if (mkdtemp (dirname) == 0) { g_warning ("Error creating temp dir with template %s: %m", dirname); g_assert_not_reached (); } monitor = stc_monitor_new_for_config_dir (dirname, NULL, NULL); g_assert_cmpstr (stc_monitor_get_config_dir (monitor), ==, dirname); /* main timeout */ timeout_id = g_timeout_add_seconds (30, on_timeout, NULL); state = 0; g_signal_connect (monitor, "item-added", G_CALLBACK (on_item_added), &state); g_signal_connect (monitor, "item-removed", G_CALLBACK (on_item_removed), &state); g_signal_connect (monitor, "item-changed", G_CALLBACK (on_item_changed), &state); /* 0: check we get the item-added signal */ filename = g_strdup_printf ("%s/stc.conf", dirname); error = NULL; rc = g_file_set_contents (filename, "[Filesystem item1]\n" "Device=/dev/sdb\n" "Options=Filesystem:mount_path=/item1", -1, &error); g_assert_no_error (error); g_assert (rc); g_free (filename); if (state <= 0) g_main_loop_run (loop); /* 1: check we get the item-added signal (again) */ filename = g_strdup_printf ("%s/stc.conf", dirname); error = NULL; rc = g_file_set_contents (filename, "[Filesystem item1]\n" "Device=/dev/sdb\n" "Options=Filesystem:mount_path=/item1" "\n" "[Filesystem item2]\n" "Device=/dev/sdc\n" "Options=Filesystem:mount_path=/item2", -1, &error); g_assert_no_error (error); g_assert (rc); g_free (filename); if (state <= 1) g_main_loop_run (loop); /* 2: check we get the item-removed signal */ filename = g_strdup_printf ("%s/stc.conf", dirname); error = NULL; rc = g_file_set_contents (filename, "[Filesystem item2]\n" "Device=/dev/sdc\n" "Options=Filesystem:mount_path=/item2", -1, &error); g_assert_no_error (error); g_assert (rc); g_free (filename); if (state <= 2) g_main_loop_run (loop); /* 3: check we get the item-changed signal */ filename = g_strdup_printf ("%s/stc.conf", dirname); error = NULL; rc = g_file_set_contents (filename, "[Filesystem item2]\n" "Device=/dev/sda\n" "Options=Filesystem:mount_path=/item2", -1, &error); g_assert_no_error (error); g_assert (rc); g_free (filename); if (state <= 3) g_main_loop_run (loop); /* 4+5: will give item-removed then item-added */ filename = g_strdup_printf ("%s/stc.conf", dirname); error = NULL; rc = g_file_set_contents (filename, "[Filesystem item3]\n" "Device=/dev/sda\n" "Options=Filesystem:mount_path=/item3", -1, &error); g_assert_no_error (error); g_assert (rc); g_free (filename); if (state <= 5) g_main_loop_run (loop); g_object_unref (monitor); cleanup_dir (dirname); g_source_remove (timeout_id); } /* ---------------------------------------------------------------------------------------------------- */ int main (int argc, char **argv) { gint ret; g_type_init (); g_test_init (&argc, &argv, NULL); loop = g_main_loop_new (NULL, FALSE); g_test_add_func ("/stc/basic", test_stc_basic); g_test_add_func ("/stc/no_conf", test_stc_no_conf); g_test_add_func ("/stc/semi_valid_conf", test_stc_semi_valid_conf); g_test_add_func ("/stc/monitoring", test_stc_monitoring); /* it would be nice if we could easily run tests to check the * returned StcItemState value. Unfortunately that requires * returning fake udev and /proc/self/mountinfo data... */ ret = g_test_run(); g_main_loop_unref (loop); return ret; }