#define wake_up wake_up_void #include "../src/dbus.c" #include "greatest.h" #include #include #include #include "queues.h" extern const char *base; void wake_up_void(void) { } struct signal_actioninvoked { guint id; gchar *key; guint subscription_id; GDBusConnection *conn; }; struct signal_closed { guint32 id; guint32 reason; guint subscription_id; GDBusConnection *conn; }; void dbus_signal_cb_actioninvoked(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { g_return_if_fail(user_data); guint32 id; gchar *key; struct signal_actioninvoked *sig = (struct signal_actioninvoked*) user_data; g_variant_get(parameters, "(us)", &id, &key); if (id == sig->id) { sig->id = id; sig->key = key; } } void dbus_signal_subscribe_actioninvoked(struct signal_actioninvoked *actioninvoked) { assert(actioninvoked); actioninvoked->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); actioninvoked->subscription_id = g_dbus_connection_signal_subscribe( actioninvoked->conn, FDN_NAME, FDN_IFAC, "ActionInvoked", FDN_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, dbus_signal_cb_actioninvoked, actioninvoked, NULL); } void dbus_signal_unsubscribe_actioninvoked(struct signal_actioninvoked *actioninvoked) { assert(actioninvoked); g_dbus_connection_signal_unsubscribe(actioninvoked->conn, actioninvoked->subscription_id); g_object_unref(actioninvoked->conn); actioninvoked->conn = NULL; actioninvoked->subscription_id = -1; } void dbus_signal_cb_closed(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { g_return_if_fail(user_data); guint32 id; guint32 reason; struct signal_closed *sig = (struct signal_closed*) user_data; g_variant_get(parameters, "(uu)", &id, &reason); if (id == sig->id) { sig->id = id; sig->reason = reason; } } void dbus_signal_subscribe_closed(struct signal_closed *closed) { assert(closed); closed->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); closed->subscription_id = g_dbus_connection_signal_subscribe( closed->conn, FDN_NAME, FDN_IFAC, "NotificationClosed", FDN_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, dbus_signal_cb_closed, closed, NULL); } void dbus_signal_unsubscribe_closed(struct signal_closed *closed) { assert(closed); g_dbus_connection_signal_unsubscribe(closed->conn, closed->subscription_id); g_object_unref(closed->conn); closed->conn = NULL; closed->subscription_id = -1; } GVariant *dbus_invoke(const char *method, GVariant *params) { GDBusConnection *connection_client; GVariant *retdata; GError *error = NULL; connection_client = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); retdata = g_dbus_connection_call_sync( connection_client, FDN_NAME, FDN_PATH, FDN_IFAC, method, params, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (error) { printf("Error while calling GTestDBus instance: %s\n", error->message); g_error_free(error); } g_object_unref(connection_client); return retdata; } struct dbus_notification { const char* app_name; guint replaces_id; const char* app_icon; const char* summary; const char* body; GHashTable *actions; GHashTable *hints; int expire_timeout; }; void g_free_variant_value(gpointer tofree) { g_variant_unref((GVariant*) tofree); } struct dbus_notification *dbus_notification_new(void) { struct dbus_notification *n = g_malloc0(sizeof(struct dbus_notification)); n->expire_timeout = -1; n->replaces_id = 0; n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); n->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free_variant_value); return n; } void dbus_notification_free(struct dbus_notification *n) { g_hash_table_unref(n->hints); g_hash_table_unref(n->actions); g_free(n); } bool dbus_notification_fire(struct dbus_notification *n, uint *id) { assert(n); assert(id); GVariantBuilder b; GVariantType *t; gpointer p_key; gpointer p_value; GHashTableIter iter; t = g_variant_type_new("(susssasa{sv}i)"); g_variant_builder_init(&b, t); g_variant_type_free(t); g_variant_builder_add(&b, "s", n->app_name); g_variant_builder_add(&b, "u", n->replaces_id); g_variant_builder_add(&b, "s", n->app_icon); g_variant_builder_add(&b, "s", n->summary); g_variant_builder_add(&b, "s", n->body); // Add the actions t = g_variant_type_new("as"); g_variant_builder_open(&b, t); g_hash_table_iter_init(&iter, n->actions); while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { g_variant_builder_add(&b, "s", (char*)p_key); g_variant_builder_add(&b, "s", (char*)p_value); } // Add an invalid appendix to cover odd numbered action arrays // Shouldn't interfere with normal testing g_variant_builder_add(&b, "s", "invalid appendix"); g_variant_builder_close(&b); g_variant_type_free(t); // Add the hints t = g_variant_type_new("a{sv}"); g_variant_builder_open(&b, t); g_hash_table_iter_init(&iter, n->hints); while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { g_variant_builder_add(&b, "{sv}", (char*)p_key, (GVariant*)p_value); } g_variant_builder_close(&b); g_variant_type_free(t); g_variant_builder_add(&b, "i", n->expire_timeout); GVariant *reply = dbus_invoke("Notify", g_variant_builder_end(&b)); if (reply) { g_variant_get(reply, "(u)", id); g_variant_unref(reply); return true; } else { return false; } } void dbus_notification_set_raw_image(struct dbus_notification *n_dbus, const char *path) { GdkPixbuf *pb = gdk_pixbuf_new_from_file(path, NULL); if (!pb) return; GVariant *hint_data = g_variant_new_from_data( G_VARIANT_TYPE("ay"), gdk_pixbuf_read_pixels(pb), gdk_pixbuf_get_byte_length(pb), TRUE, (GDestroyNotify) g_object_unref, g_object_ref(pb)); GVariant *hint = g_variant_new( "(iiibii@ay)", gdk_pixbuf_get_width(pb), gdk_pixbuf_get_height(pb), gdk_pixbuf_get_rowstride(pb), gdk_pixbuf_get_has_alpha(pb), gdk_pixbuf_get_bits_per_sample(pb), gdk_pixbuf_get_n_channels(pb), hint_data); g_hash_table_insert(n_dbus->hints, g_strdup("image-data"), g_variant_ref_sink(hint)); g_object_unref(pb); } /////// TESTS gint owner_id; TEST test_dbus_init(void) { owner_id = dbus_init(); uint waiting = 0; while (!dbus_conn && waiting < 2000) { usleep(500); waiting++; } ASSERTm("After 1s, there is still no dbus connection available.", dbus_conn); PASS(); } TEST test_dbus_teardown(void) { dbus_teardown(owner_id); PASS(); } TEST test_invalid_notification(void) { GVariant *faulty = g_variant_new_boolean(true); ASSERT(NULL == dbus_message_to_notification(":123", faulty)); ASSERT(NULL == dbus_invoke("Notify", faulty)); g_variant_unref(faulty); PASS(); } TEST test_empty_notification(void) { struct dbus_notification *n = dbus_notification_new(); gsize len = queues_length_waiting(); guint id; ASSERT(dbus_notification_fire(n, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); dbus_notification_free(n); PASS(); } TEST test_basic_notification(void) { struct dbus_notification *n = dbus_notification_new(); gsize len = queues_length_waiting(); n->app_name = "dunstteststack"; n->app_icon = "NONE"; n->summary = "Headline"; n->body = "Text"; g_hash_table_insert(n->actions, g_strdup("actionid"), g_strdup("Print this text")); g_hash_table_insert(n->hints, g_strdup("x-dunst-stack-tag"), g_variant_ref_sink(g_variant_new_string("volume"))); n->replaces_id = 10; guint id; ASSERT(dbus_notification_fire(n, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); dbus_notification_free(n); PASS(); } TEST test_dbus_notify_colors(void) { const char *color_frame = "I allow all string values for frame!"; const char *color_bg = "I allow all string values for background!"; const char *color_fg = "I allow all string values for foreground!"; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_dbus_notify_colors"; n_dbus->body = "Summary of it"; g_hash_table_insert(n_dbus->hints, g_strdup("frcolor"), g_variant_ref_sink(g_variant_new_string(color_frame))); g_hash_table_insert(n_dbus->hints, g_strdup("bgcolor"), g_variant_ref_sink(g_variant_new_string(color_bg))); g_hash_table_insert(n_dbus->hints, g_strdup("fgcolor"), g_variant_ref_sink(g_variant_new_string(color_fg))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(n->colors.frame, color_frame); ASSERT_STR_EQ(n->colors.fg, color_fg); ASSERT_STR_EQ(n->colors.bg, color_bg); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_transient(void) { static char msg[50]; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_transient"; n_dbus->body = "Summary of it"; bool values[] = { true, true, true, false, false, false, false }; GVariant *variants[] = { g_variant_new_boolean(true), g_variant_new_int32(1), g_variant_new_uint32(1), g_variant_new_boolean(false), g_variant_new_int32(0), g_variant_new_uint32(0), g_variant_new_int32(-1), }; for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { g_hash_table_insert(n_dbus->hints, g_strdup("transient"), g_variant_ref_sink(variants[i])); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); snprintf(msg, sizeof(msg), "In round %ld", i); ASSERT_EQm(msg, values[i], n->transient); } dbus_notification_free(n_dbus); PASS(); } TEST test_hint_progress(void) { static char msg[50]; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_progress"; n_dbus->body = "Summary of it"; int values[] = { 99, 12, 123, 123, -1, -1 }; GVariant *variants[] = { g_variant_new_int32(99), g_variant_new_uint32(12), g_variant_new_int32(123), // allow higher than 100 g_variant_new_uint32(123), g_variant_new_int32(-192), g_variant_new_uint32(-192), }; for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { g_hash_table_insert(n_dbus->hints, g_strdup("value"), g_variant_ref_sink(variants[i])); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); snprintf(msg, sizeof(msg), "In round %ld", i); ASSERT_EQm(msg, values[i], n->progress); } dbus_notification_free(n_dbus); PASS(); } TEST test_hint_icons(void) { struct notification *n; struct dbus_notification *n_dbus; const char *iconname = "NEWICON"; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_icons"; n_dbus->body = "Summary of it"; g_hash_table_insert(n_dbus->hints, g_strdup("image-path"), g_variant_ref_sink(g_variant_new_string(iconname))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(iconname, n->iconname); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_category(void) { struct notification *n; struct dbus_notification *n_dbus; const char *category = "VOLUME"; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_category"; n_dbus->body = "Summary of it"; g_hash_table_insert(n_dbus->hints, g_strdup("category"), g_variant_ref_sink(g_variant_new_string(category))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(category, n->category); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_desktop_entry(void) { struct notification *n; struct dbus_notification *n_dbus; const char *desktop_entry = "org.dunst-project.dunst"; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_desktopentry"; n_dbus->body = "Summary of my desktop_entry"; g_hash_table_insert(n_dbus->hints, g_strdup("desktop-entry"), g_variant_ref_sink(g_variant_new_string(desktop_entry))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(desktop_entry, n->desktop_entry); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_urgency(void) { static char msg[50]; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_urgency"; n_dbus->body = "Summary of it"; enum urgency values[] = { URG_MAX, URG_LOW, URG_NORM, URG_CRIT }; GVariant *variants[] = { g_variant_new_byte(10), g_variant_new_byte(0), g_variant_new_byte(1), g_variant_new_byte(2), }; for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { g_hash_table_insert(n_dbus->hints, g_strdup("urgency"), g_variant_ref_sink(variants[i])); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); snprintf(msg, sizeof(msg), "In round %ld", i); ASSERT_EQm(msg, values[i], n->urgency); queues_notification_close_id(id, REASON_UNDEF); } dbus_notification_free(n_dbus); PASS(); } TEST test_hint_raw_image(void) { guint id; struct notification *n; struct dbus_notification *n_dbus; char *path = g_strconcat(base, "/data/icons/valid.png", NULL); gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); dbus_notification_set_raw_image(n_dbus, path); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_raw_image"; n_dbus->body = "Summary of it"; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT(n->icon); ASSERT(!STR_EQ(n->icon_id, n_dbus->app_icon)); dbus_notification_free(n_dbus); g_free(path); PASS(); } TEST test_server_caps(enum markup_mode markup) { GVariant *reply; GVariant *caps = NULL; const char **capsarray; settings.markup = markup; reply = dbus_invoke("GetCapabilities", NULL); caps = g_variant_get_child_value(reply, 0); capsarray = g_variant_get_strv(caps, NULL); ASSERT(capsarray); ASSERT(g_strv_contains(capsarray, "actions")); ASSERT(g_strv_contains(capsarray, "body")); ASSERT(g_strv_contains(capsarray, "body-hyperlinks")); ASSERT(g_strv_contains(capsarray, "x-dunst-stack-tag")); if (settings.markup != MARKUP_NO) ASSERT(g_strv_contains(capsarray, "body-markup")); else ASSERT_FALSE(g_strv_contains(capsarray, "body-markup")); g_free(capsarray); g_variant_unref(caps); g_variant_unref(reply); PASS(); } TEST test_signal_actioninvoked(void) { const struct notification *n; struct dbus_notification *n_dbus; struct signal_actioninvoked sig = {0, NULL, -1}; dbus_signal_subscribe_actioninvoked(&sig); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE2"; n_dbus->summary = "Headline for New"; n_dbus->body = "Text"; g_hash_table_insert(n_dbus->actions, g_strdup("actionkey"), g_strdup("Print this text")); dbus_notification_fire(n_dbus, &sig.id); n = queues_debug_find_notification_by_id(sig.id); signal_action_invoked(n, "actionkey"); uint waiting = 0; while (!sig.key && waiting < 2000) { usleep(500); waiting++; } ASSERT_STR_EQ("actionkey", sig.key); g_free(sig.key); dbus_notification_free(n_dbus); dbus_signal_unsubscribe_actioninvoked(&sig); PASS(); } TEST test_close_and_signal(void) { GVariant *data, *ret; struct dbus_notification *n; struct signal_closed sig = {0, REASON_MIN-1, -1}; dbus_signal_subscribe_closed(&sig); n = dbus_notification_new(); n->app_name = "dunstteststack"; n->app_icon = "NONE2"; n->summary = "Headline for New"; n->body = "Text"; dbus_notification_fire(n, &sig.id); data = g_variant_new("(u)", sig.id); ret = dbus_invoke("CloseNotification", data); ASSERT(ret); uint waiting = 0; while (sig.reason == REASON_MIN-1 && waiting < 2000) { usleep(500); waiting++; } ASSERT(sig.reason != REASON_MIN-1); dbus_notification_free(n); dbus_signal_unsubscribe_closed(&sig); g_variant_unref(ret); PASS(); } TEST test_get_fdn_daemon_info(void) { unsigned int pid_is; pid_t pid_should; char *name, *vendor; GDBusConnection *conn; pid_should = getpid(); conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); ASSERT(dbus_get_fdn_daemon_info(conn, &pid_is, &name, &vendor)); ASSERT_EQ_FMT(pid_should, pid_is, "%d"); ASSERT_STR_EQ("dunst", name); ASSERT_STR_EQ("knopwob", vendor); g_free(name); g_free(vendor); g_object_unref(conn); PASS(); } TEST assert_methodlists_sorted(void) { for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_fdn); i++) { ASSERT(0 > strcmp( methods_fdn[i].method_name, methods_fdn[i+1].method_name)); } PASS(); } // TESTS END GMainLoop *loop; GThread *thread_tests; gpointer run_threaded_tests(gpointer data) { RUN_TEST(test_dbus_init); RUN_TEST(test_get_fdn_daemon_info); RUN_TEST(test_empty_notification); RUN_TEST(test_basic_notification); RUN_TEST(test_invalid_notification); RUN_TEST(test_hint_transient); RUN_TEST(test_hint_progress); RUN_TEST(test_hint_icons); RUN_TEST(test_hint_category); RUN_TEST(test_hint_desktop_entry); RUN_TEST(test_hint_urgency); RUN_TEST(test_hint_raw_image); RUN_TEST(test_dbus_notify_colors); RUN_TESTp(test_server_caps, MARKUP_FULL); RUN_TESTp(test_server_caps, MARKUP_STRIP); RUN_TESTp(test_server_caps, MARKUP_NO); RUN_TEST(test_close_and_signal); RUN_TEST(test_signal_actioninvoked); RUN_TEST(assert_methodlists_sorted); RUN_TEST(test_dbus_teardown); g_main_loop_quit(loop); return NULL; } SUITE(suite_dbus) { settings.icon_path = ""; GTestDBus *dbus_bus; g_test_dbus_unset(); queues_init(); loop = g_main_loop_new(NULL, false); dbus_bus = g_test_dbus_new(G_TEST_DBUS_NONE); g_test_dbus_up(dbus_bus); thread_tests = g_thread_new("testexecutor", run_threaded_tests, loop); g_main_loop_run(loop); queues_teardown(); g_test_dbus_down(dbus_bus); g_object_unref(dbus_bus); g_thread_unref(thread_tests); g_main_loop_unref(loop); settings.icon_path = NULL; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */