summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2022-01-23 16:13:03 +0100
committerJens Georg <mail@jensge.org>2022-01-23 16:13:03 +0100
commit17943f1ba17d192e867fc819b7a42b6982b2dee4 (patch)
tree1e3a781970bba00716d654ffa220e306041f016c /tests
parenta23561d3e17f0d44aeda79495083220e95951aa8 (diff)
downloadgupnp-17943f1ba17d192e867fc819b7a42b6982b2dee4.tar.gz
tests: Add ServiceProxy test
Diffstat (limited to 'tests')
-rw-r--r--tests/meson.build2
-rw-r--r--tests/test-service-proxy.c598
2 files changed, 599 insertions, 1 deletions
diff --git a/tests/meson.build b/tests/meson.build
index b976e8a..deae7ba 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,4 +1,4 @@
-foreach program : ['context', 'bugs', 'service', 'acl']
+foreach program : ['context', 'bugs', 'service', 'acl', 'service-proxy']
test(
program,
executable(
diff --git a/tests/test-service-proxy.c b/tests/test-service-proxy.c
new file mode 100644
index 0000000..347ea73
--- /dev/null
+++ b/tests/test-service-proxy.c
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2021 Jens Georg.
+ *
+ * Author: Jens Georg <mail@jensge.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <config.h>
+
+#include "libgupnp/gupnp.h"
+#include "libgupnp/gupnp-service-private.h"
+
+#include <string.h>
+#include <libsoup/soup.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+static GUPnPContext *
+create_context (const char *localhost, guint16 port, GError **error)
+{
+ return GUPNP_CONTEXT (g_initable_new (GUPNP_TYPE_CONTEXT,
+ NULL,
+ error,
+ "host-ip",
+ localhost,
+ "port",
+ port,
+ NULL));
+}
+
+typedef struct {
+ GMainLoop *loop;
+ GUPnPContext *server_context;
+ GUPnPContext *client_context;
+ GUPnPRootDevice *rd;
+ GUPnPServiceInfo *service;
+ GUPnPControlPoint *cp;
+ GUPnPServiceProxy *proxy;
+ gpointer payload;
+} ProxyTestFixture;
+
+static gboolean
+test_on_timeout (gpointer user_data)
+{
+ g_print ("Timeout in %s\n", (const char *) user_data);
+ g_assert_not_reached ();
+
+ return FALSE;
+}
+
+static void
+test_run_loop (GMainLoop *loop, const char *name)
+{
+ guint timeout_id = 0;
+ int timeout = 2;
+
+ const char *timeout_str = g_getenv ("GUPNP_TEST_TIMEOUT");
+ if (timeout_str != NULL) {
+ long t = atol (timeout_str);
+ if (t != 0)
+ timeout = t;
+ }
+
+ timeout_id = g_timeout_add_seconds (timeout,
+ test_on_timeout,
+ (gpointer) name);
+ g_main_loop_run (loop);
+ g_source_remove (timeout_id);
+}
+
+static void
+on_proxy_available (G_GNUC_UNUSED GUPnPControlPoint *cp,
+ GUPnPServiceProxy *proxy,
+ gpointer user_data)
+{
+ ProxyTestFixture *tf = user_data;
+
+ tf->proxy = g_object_ref (proxy);
+ g_main_loop_quit (tf->loop);
+}
+
+static void
+test_fixture_setup (ProxyTestFixture *tf, gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ tf->loop = g_main_loop_new (NULL, FALSE);
+ g_assert_nonnull (tf->loop);
+
+ // Create server part
+ tf->server_context =
+ create_context ((const char *) user_data, 0, &error);
+ g_assert_nonnull (tf->server_context);
+ g_assert_no_error (error);
+ GUPnPResourceFactory *factory = gupnp_resource_factory_new ();
+ tf->rd = gupnp_root_device_new_full (tf->server_context,
+ factory,
+ NULL,
+ "TestDevice.xml",
+ DATA_PATH,
+ &error);
+ g_object_unref (factory);
+ g_assert_no_error (error);
+ g_assert_nonnull (tf->rd);
+ tf->service = gupnp_device_info_get_service (
+ GUPNP_DEVICE_INFO (tf->rd),
+ "urn:test-gupnp-org:service:TestService:1");
+
+ // Create client part
+ tf->client_context =
+ create_context ((const char *) user_data, 0, &error);
+
+ g_assert_nonnull (tf->client_context);
+ g_assert_no_error (error);
+ tf->cp = gupnp_control_point_new (
+ tf->client_context,
+ "urn:test-gupnp-org:service:TestService:1");
+
+ gulong id = g_signal_connect (tf->cp,
+ "service-proxy-available",
+ G_CALLBACK (on_proxy_available),
+ tf);
+ gupnp_root_device_set_available (tf->rd, TRUE);
+ gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (tf->cp),
+ TRUE);
+ test_run_loop (tf->loop, "Test fixture setup");
+ g_signal_handler_disconnect (tf->cp, id);
+}
+
+static gboolean
+delayed_loop_quitter (gpointer user_data)
+{
+ g_main_loop_quit (user_data);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+test_fixture_teardown (ProxyTestFixture *tf, gconstpointer user_data)
+{
+ g_clear_object (&tf->proxy);
+ g_object_unref (tf->cp);
+ g_object_unref (tf->client_context);
+
+ g_object_unref (tf->service);
+ g_object_unref (tf->rd);
+ g_object_unref (tf->server_context);
+
+ // Make sure the source teardown handlers get run so we don't confuse valgrind
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+ g_main_loop_unref (tf->loop);
+}
+
+// Test that calls a remote function and does not even connect to any callback
+// Is is mainly useful to check in combination with ASAN/Valgrind to make sure
+// that nothing gets leaked on the way
+void
+test_fire_and_forget (ProxyTestFixture *tf,
+ G_GNUC_UNUSED gconstpointer user_data)
+{
+ // Run fire and forget for action that does not have any kind of arguments
+ GUPnPServiceProxyAction *action =
+ gupnp_service_proxy_action_new ("Ping", NULL);
+
+ gupnp_service_proxy_call_action_async (tf->proxy,
+ action,
+ NULL,
+ NULL,
+ NULL);
+ gupnp_service_proxy_action_unref (action);
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+
+ // Run fire and forget for a more complex action
+ action = gupnp_service_proxy_action_new ("Browse",
+ "ObjectID",
+ G_TYPE_STRING,
+ "0",
+ "BrowseFlag",
+ G_TYPE_STRING,
+ "BrowseDirectChildren",
+ "Filter",
+ G_TYPE_STRING,
+ "res,dc:date,res@size",
+ "StartingIndex",
+ G_TYPE_UINT,
+ 0,
+ "RequestedCount",
+ G_TYPE_UINT,
+ 0,
+ "SortCriteria",
+ G_TYPE_STRING,
+ "",
+ NULL);
+
+ gupnp_service_proxy_call_action_async (tf->proxy,
+ action,
+ NULL,
+ NULL,
+ NULL);
+ gupnp_service_proxy_action_unref (action);
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+}
+
+void
+on_test_async_call_ping_success (G_GNUC_UNUSED GUPnPService *service,
+ G_GNUC_UNUSED GUPnPServiceAction *action,
+ G_GNUC_UNUSED gpointer user_data)
+{
+ gupnp_service_action_return_success (action);
+}
+
+void
+on_test_async_call (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ GError *error = NULL;
+ g_assert_no_error (error);
+ g_assert_nonnull (user_data);
+
+ gupnp_service_proxy_call_action_finish (GUPNP_SERVICE_PROXY (source),
+ res,
+ &error);
+
+ ProxyTestFixture *tf = (ProxyTestFixture *) user_data;
+ g_main_loop_quit (tf->loop);
+}
+
+void
+test_async_call (ProxyTestFixture *tf, G_GNUC_UNUSED gconstpointer user_data)
+{
+ // Run fire and forget for action that does not have any kind of arguments
+ GUPnPServiceProxyAction *action =
+ gupnp_service_proxy_action_new ("Ping", NULL);
+
+ g_signal_connect (tf->service,
+ "action-invoked::Ping",
+ G_CALLBACK (on_test_async_call_ping_success),
+ tf);
+
+ gupnp_service_proxy_call_action_async (tf->proxy,
+ action,
+ NULL,
+ on_test_async_call,
+ tf);
+ gupnp_service_proxy_action_unref (action);
+ test_run_loop(tf->loop, g_test_get_path());
+
+ // Run fire and forget for a more complex action
+ action = gupnp_service_proxy_action_new ("Browse",
+ "ObjectID",
+ G_TYPE_STRING,
+ "0",
+ "BrowseFlag",
+ G_TYPE_STRING,
+ "BrowseDirectChildren",
+ "Filter",
+ G_TYPE_STRING,
+ "res,dc:date,res@size",
+ "StartingIndex",
+ G_TYPE_UINT,
+ 0,
+ "RequestedCount",
+ G_TYPE_UINT,
+ 0,
+ "SortCriteria",
+ G_TYPE_STRING,
+ "",
+ NULL);
+
+ gupnp_service_proxy_call_action_async (tf->proxy,
+ action,
+ NULL,
+ on_test_async_call,
+ tf);
+ gupnp_service_proxy_action_unref (action);
+ test_run_loop (tf->loop, g_test_get_path ());
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+}
+
+void
+on_test_async_call_ping_delay (G_GNUC_UNUSED GUPnPService *service,
+ G_GNUC_UNUSED GUPnPServiceAction *action,
+ gpointer user_data)
+{
+ g_debug ("=> Ping delay");
+ ProxyTestFixture *tf = (ProxyTestFixture *) user_data;
+ tf->payload = action;
+ g_main_loop_quit (tf->loop);
+}
+
+void
+on_test_async_cancel_call (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ g_assert_nonnull (user_data);
+
+ gupnp_service_proxy_call_action_finish (GUPNP_SERVICE_PROXY (source),
+ res,
+ &error);
+
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+
+ ProxyTestFixture *tf = (ProxyTestFixture *) user_data;
+ g_main_loop_quit (tf->loop);
+}
+
+void
+test_async_cancel_call (ProxyTestFixture *tf,
+ G_GNUC_UNUSED gconstpointer user_data)
+{
+ GUPnPServiceProxyAction *action =
+ gupnp_service_proxy_action_new ("Ping", NULL);
+
+ g_signal_connect (tf->service,
+ "action-invoked::Ping",
+ G_CALLBACK (on_test_async_call_ping_delay),
+ tf);
+
+ GCancellable *cancellable = g_cancellable_new ();
+ gupnp_service_proxy_call_action_async (tf->proxy,
+ action,
+ cancellable,
+ on_test_async_cancel_call,
+ tf);
+
+ // This should be called by the action callback
+ test_run_loop (tf->loop, g_test_get_path ());
+ g_cancellable_cancel (cancellable);
+
+ // This should be finished by the now-cancelled proxy call
+ test_run_loop (tf->loop, g_test_get_path ());
+
+ // Free action. There should not be any callback
+ gupnp_service_action_return_success (
+ (GUPnPServiceAction *) tf->payload);
+
+ GError *error = NULL;
+ gupnp_service_proxy_action_get_result (action, &error, NULL);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+
+ gupnp_service_proxy_action_unref (action);
+ g_object_unref (cancellable);
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+}
+
+
+void
+test_async_call_destroy_with_pending (ProxyTestFixture *tf,
+ gconstpointer user_data)
+{
+
+ GList *actions = NULL;
+ // Since the session in the context is using defaults, we can only have
+ // two concurrent connections on a remote host
+ for (int i = 0; i < 2; i++) {
+ GUPnPServiceProxyAction *action =
+ gupnp_service_proxy_action_new ("Ping", NULL);
+
+ gupnp_service_proxy_call_action_async (tf->proxy,
+ action,
+ NULL,
+ NULL,
+ NULL);
+ gupnp_service_proxy_action_unref (action);
+
+ // This should be called by the action callback
+ gulong id = g_signal_connect (
+ tf->service,
+ "action-invoked::Ping",
+ G_CALLBACK (on_test_async_call_ping_delay),
+ tf);
+ test_run_loop (tf->loop, g_test_get_path ());
+ g_signal_handler_disconnect (tf->service, id);
+
+ actions = g_list_prepend (actions, tf->payload);
+ }
+
+ // free the actions
+ g_list_free_full (actions, (GDestroyNotify) gupnp_service_action_unref);
+
+ g_clear_object (&tf->proxy);
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+}
+
+typedef struct {
+ GMainLoop *outer_loop;
+ GMainContext *outer_context;
+ const char *address;
+ GCancellable *cancellable;
+} ThreadData;
+
+typedef struct {
+ GMainLoop *loop;
+ GUPnPServiceProxy *p;
+} GetProxyData;
+
+void
+thead_on_proxy_available (GUPnPControlPoint *cp,
+ GUPnPServiceProxy *p,
+ gpointer user_data)
+{
+ GetProxyData *d = (GetProxyData *) user_data;
+ d->p = g_object_ref (p);
+ g_main_loop_quit (d->loop);
+}
+
+gboolean
+exit_outer_loop (gpointer user_data)
+{
+ ThreadData *d = (ThreadData *) user_data;
+ g_main_loop_quit (d->outer_loop);
+
+ return G_SOURCE_REMOVE;
+}
+
+gpointer
+thread_func (gpointer data)
+{
+ ThreadData *d = (ThreadData *) data;
+ GMainContext *context = g_main_context_new ();
+ GError *error = NULL;
+ g_main_context_push_thread_default (context);
+
+ GUPnPContext *ctx = create_context (d->address, 0, &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (ctx);
+
+ GUPnPControlPoint *cp = gupnp_control_point_new (
+ ctx,
+ "urn:test-gupnp-org:service:TestService:1");
+ GetProxyData gpd;
+ gulong id = g_signal_connect (cp,
+ "service-proxy-available",
+ G_CALLBACK (thead_on_proxy_available),
+ &gpd);
+ gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
+
+ gpd.loop = g_main_loop_new (context, FALSE);
+ test_run_loop (gpd.loop, "Test thread setup");
+ g_signal_handler_disconnect (cp, id);
+
+ GUPnPServiceProxyAction *action =
+ gupnp_service_proxy_action_new ("Ping", NULL);
+
+ gupnp_service_proxy_call_action (gpd.p, action, d->cancellable, &error);
+ gupnp_service_proxy_action_unref (action);
+
+ if (d->cancellable == NULL)
+ g_assert_no_error (error);
+ else
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+
+ g_object_unref (gpd.p);
+ g_object_unref (cp);
+ g_object_unref (ctx);
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, gpd.loop);
+ g_main_loop_run (gpd.loop);
+
+ g_main_loop_unref (gpd.loop);
+ g_main_context_pop_thread_default (context);
+ g_main_context_unref (context);
+
+ g_main_context_invoke (d->outer_context, exit_outer_loop, d);
+
+ return NULL;
+}
+
+void
+test_sync_call (ProxyTestFixture *tf, gconstpointer user_data)
+{
+ g_signal_connect (tf->service,
+ "action-invoked::Ping",
+ G_CALLBACK (on_test_async_call_ping_success),
+ tf);
+ ThreadData d;
+ d.address = (const char *) user_data;
+ d.outer_context = g_main_context_get_thread_default ();
+ d.outer_loop = tf->loop;
+ d.cancellable = NULL;
+
+ GThread *t = g_thread_new ("Sync call test", thread_func, &d);
+ test_run_loop (tf->loop, g_test_get_path());
+ g_thread_join (t);
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+}
+
+gboolean
+cancel_sync_call (gpointer user_data)
+{
+ ThreadData *d = (ThreadData *) user_data;
+
+ g_print ("Cancelling...\n");
+ g_cancellable_cancel (d->cancellable);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+test_cancel_sync_call (ProxyTestFixture *tf, gconstpointer user_data)
+{
+ g_signal_connect (tf->service,
+ "action-invoked::Ping",
+ G_CALLBACK (on_test_async_call_ping_delay),
+ tf);
+ ThreadData d;
+ d.address = (const char *) user_data;
+ d.outer_context = g_main_context_get_thread_default ();
+ d.outer_loop = tf->loop;
+ d.cancellable = g_cancellable_new ();
+
+ GThread *t = g_thread_new ("Sync call cancel test", thread_func, &d);
+ test_run_loop (tf->loop, g_test_get_path());
+
+ g_timeout_add_seconds (1, (GSourceFunc) cancel_sync_call, &d);
+ test_run_loop (tf->loop, g_test_get_path());
+
+ g_thread_join (t);
+
+ // Spin the loop for a bit...
+ g_timeout_add (500, (GSourceFunc) delayed_loop_quitter, tf->loop);
+ g_main_loop_run (tf->loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add ("/service-proxy/async/fire-and-forget",
+ ProxyTestFixture,
+ "127.0.0.1",
+ test_fixture_setup,
+ test_fire_and_forget,
+ test_fixture_teardown);
+
+ g_test_add ("/service-proxy/async/call",
+ ProxyTestFixture,
+ "127.0.0.1",
+ test_fixture_setup,
+ test_async_call,
+ test_fixture_teardown);
+
+ g_test_add ("/service-proxy/async/cancel",
+ ProxyTestFixture,
+ "127.0.0.1",
+ test_fixture_setup,
+ test_async_cancel_call,
+ test_fixture_teardown);
+
+ g_test_add ("/service-proxy/async/destroy-with-pending",
+ ProxyTestFixture,
+ "127.0.0.1",
+ test_fixture_setup,
+ test_async_call_destroy_with_pending,
+ test_fixture_teardown);
+
+ g_test_add ("/service-proxy/sync/call",
+ ProxyTestFixture,
+ "127.0.0.1",
+ test_fixture_setup,
+ test_sync_call,
+ test_fixture_teardown);
+
+ g_test_add ("/service-proxy/sync/cancel-call",
+ ProxyTestFixture,
+ "127.0.0.1",
+ test_fixture_setup,
+ test_cancel_sync_call,
+ test_fixture_teardown);
+
+ return g_test_run ();
+}