diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | docs/reference/libqmi-glib/Makefile.am | 4 | ||||
-rw-r--r-- | src/libqmi-glib/test/Makefile.am | 20 | ||||
-rw-r--r-- | src/libqmi-glib/test/test-fixture.c | 324 | ||||
-rw-r--r-- | src/libqmi-glib/test/test-fixture.h | 54 | ||||
-rw-r--r-- | src/libqmi-glib/test/test-generated.c | 38 | ||||
-rw-r--r-- | src/libqmi-glib/test/test-port-context.c | 442 | ||||
-rw-r--r-- | src/libqmi-glib/test/test-port-context.h | 37 |
8 files changed, 917 insertions, 3 deletions
@@ -52,6 +52,7 @@ src/libqmi-glib/test/Makefile src/libqmi-glib/test/Makefile.in src/libqmi-glib/test/test-utils src/libqmi-glib/test/test-message +src/libqmi-glib/test/test-generated src/qmicli/.deps src/qmicli/.libs diff --git a/docs/reference/libqmi-glib/Makefile.am b/docs/reference/libqmi-glib/Makefile.am index 3eadd5b..2973fe6 100644 --- a/docs/reference/libqmi-glib/Makefile.am +++ b/docs/reference/libqmi-glib/Makefile.am @@ -47,7 +47,9 @@ CFILE_GLOB = \ IGNORE_HFILES = \ qmi-enums-private.h \ qmi-enum-types-private.h \ - qmi-ctl.h + qmi-ctl.h \ + test-port-context.h \ + test-fixture.h # CFLAGS and LDFLAGS for compiling scan program. Only needed # if $(DOC_MODULE).types is non-empty. diff --git a/src/libqmi-glib/test/Makefile.am b/src/libqmi-glib/test/Makefile.am index 6563986..8fedc8c 100644 --- a/src/libqmi-glib/test/Makefile.am +++ b/src/libqmi-glib/test/Makefile.am @@ -2,11 +2,11 @@ include $(top_srcdir)/gtester.make noinst_PROGRAMS = \ test-utils \ - test-message + test-message \ + test-generated TEST_PROGS += $(noinst_PROGRAMS) - test_utils_SOURCES = \ test-utils.c test_utils_CPPFLAGS = \ @@ -34,3 +34,19 @@ test_message_CPPFLAGS = \ test_message_LDADD = \ $(top_builddir)/src/libqmi-glib/libqmi-glib.la \ $(GLIB_LIBS) + +test_generated_SOURCES = \ + test-fixture.h test-fixture.c \ + test-port-context.h test-port-context.c \ + test-generated.c +test_generated_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/src/libqmi-glib \ + -I$(top_srcdir)/src/libqmi-glib/generated \ + -I$(top_builddir)/src/libqmi-glib \ + -I$(top_builddir)/src/libqmi-glib/generated \ + -DLIBQMI_GLIB_COMPILATION +test_generated_LDADD = \ + $(top_builddir)/src/libqmi-glib/libqmi-glib.la \ + $(GLIB_LIBS) diff --git a/src/libqmi-glib/test/test-fixture.c b/src/libqmi-glib/test/test-fixture.c new file mode 100644 index 0000000..376707f --- /dev/null +++ b/src/libqmi-glib/test/test-fixture.c @@ -0,0 +1,324 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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: + * + * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include "test-fixture.h" + +#define VIRTUAL_SOCKET_PATH "virtual-socket-path" + +static const QmiService services [] = { + QMI_SERVICE_DMS, + QMI_SERVICE_NAS, + QMI_SERVICE_WDS, + QMI_SERVICE_PDS +}; + +static void +device_allocate_client_ready (QmiDevice *device, + GAsyncResult *res, + TestFixture *fixture) +{ + GError *error = NULL; + QmiClient *client; + QmiService service; + + client = qmi_device_allocate_client_finish (device, res, &error); + g_assert_no_error (error); + g_assert (QMI_IS_CLIENT (client)); + + service = qmi_client_get_service (client); + g_assert (service > QMI_SERVICE_CTL); + fixture->service_info[service].client = client; + fixture->service_info[service].transaction_id = 0x0001; + test_fixture_loop_stop (fixture); +} + +static void +device_open_ready (QmiDevice *device, + GAsyncResult *res, + TestFixture *fixture) +{ + GError *error = NULL; + gboolean ret; + + ret = qmi_device_open_finish (device, res, &error); + g_assert_no_error (error); + g_assert (ret); + test_fixture_loop_stop (fixture); +} + +static void +device_virtual_new_ready (GObject *source, + GAsyncResult *res, + TestFixture *fixture) +{ + GError *error = NULL; + + fixture->device = qmi_device_new_finish (res, &error); + g_assert_no_error (error); + g_assert (QMI_IS_DEVICE (fixture->device)); + test_fixture_loop_stop (fixture); +} + +void +test_fixture_setup (TestFixture *fixture) +{ + GFile *file; + guint i; + static guint32 num = 0; + + g_debug ("[%lu,%p] fixture setup", (gulong) pthread_self (), g_main_context_get_thread_default ()); + + qmi_utils_set_traces_enabled (TRUE); + + fixture->path = g_strdup_printf ("/dev/virtual/qmi%04u", num++); + fixture->service_info[QMI_SERVICE_CTL].transaction_id = 0x0001; + fixture->ctx = test_port_context_new (fixture->path); + test_port_context_start (fixture->ctx); + + /* Create device */ + file = g_file_new_for_path (fixture->path); + g_async_initable_new_async (QMI_TYPE_DEVICE, + G_PRIORITY_DEFAULT, + NULL, + (GAsyncReadyCallback) device_virtual_new_ready, + fixture, + QMI_DEVICE_FILE, file, + QMI_DEVICE_NO_FILE_CHECK, TRUE, + QMI_DEVICE_PROXY_PATH, fixture->path, + NULL); + g_object_unref (file); + test_fixture_loop_run (fixture); + + /* Open device */ + { + guint8 expected[] = { + 0x01, /* marker */ + /* QMUX */ + 0x22, 0x00, /* length */ + 0x00, /* flags */ + 0x00, /* service CTL */ + 0x00, /* client */ + /* QMI header */ + 0x00, /* flags */ + 0xFF, /* transaction */ + 0x00, 0xFF, /* message: Internal proxy open */ + 0x17, 0x00, /* tlv length */ + /* TLV */ + 0x01, /* type */ + 0x14, 0x00, /* length */ + 0x2F, 0x64, 0x65, 0x76, 0x2F, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6C, 0x2F, 0x71, 0x6D, 0x69, 0x00, 0x00, 0x00, 0x00 + }; + guint8 response[] = { + 0x01, /* marker */ + /* QMUX */ + 0x12, 0x00, /* length */ + 0x00, /* flags */ + 0x00, /* service CTL */ + 0x00, /* client */ + /* QMI header */ + 0x01, /* flags */ + 0xFF, /* transaction */ + 0x00, 0xFF, /* message: Internal proxy open */ + 0x07, 0x00, /* tlv length */ + /* TLV */ + 0x02, /* type: Result */ + 0x04, 0x00, /* length */ + 0x00, 0x00, /* error status */ + 0x00, 0x00, /* error code */ + }; + + g_assert_cmpuint (strlen (fixture->path), ==, 20); + memcpy (&expected[15], fixture->path, strlen (fixture->path)); + + test_port_context_set_command (fixture->ctx, + expected, G_N_ELEMENTS (expected), + response, G_N_ELEMENTS (response), + fixture->service_info[QMI_SERVICE_CTL].transaction_id++); + } + qmi_device_open (fixture->device, QMI_DEVICE_OPEN_FLAGS_PROXY, 1, NULL, + (GAsyncReadyCallback) device_open_ready, + fixture); + test_fixture_loop_run (fixture); + + /* Allocate clients */ + for (i = 0; i < G_N_ELEMENTS (services); i++) { + guint8 expected[] = { + 0x01, /* marker */ + /* QMUX */ + 0x0F, 0x00, /* length */ + 0x00, /* flags */ + 0x00, /* service CTL */ + 0x00, /* client */ + /* QMI header */ + 0x00, /* flags */ + 0xFF, /* transaction */ + 0x22, 0x00, /* message: Allocate CID */ + 0x04, 0x00, /* tlv length */ + /* TLV */ + 0x01, /* type */ + 0x01, 0x00, /* length */ + 0xFF /* UPDATE: service */ + }; + guint8 response[] = { + 0x01, /* marker */ + /* QMUX */ + 0x17, 0x00, /* length */ + 0x00, /* flags */ + 0x00, /* service */ + 0x00, /* client */ + /* QMI header */ + 0x01, /* flags: Response */ + 0xFF, /* transaction */ + 0x22, 0x00, /* message */ + 0x0C, 0x00, /* tlv length */ + /* TLV */ + 0x02, /* type: Result */ + 0x04, 0x00, /* length */ + 0x00, 0x00, /* error status */ + 0x00, 0x00, /* error code */ + /* TLV */ + 0x01, /* type: Allocation info */ + 0x02, 0x00, /* length */ + 0xFF, /* UPDATE: service */ + 0x01, /* cid: 1 */ + }; + + expected[15] = services[i]; + response[22] = services[i]; + + test_port_context_set_command (fixture->ctx, + expected, G_N_ELEMENTS (expected), + response, G_N_ELEMENTS (response), + fixture->service_info[QMI_SERVICE_CTL].transaction_id++); + qmi_device_allocate_client (fixture->device, services[i], QMI_CID_NONE, 10, NULL, + (GAsyncReadyCallback) device_allocate_client_ready, + fixture); + test_fixture_loop_run (fixture); + } +} + +static void +device_release_client_ready (QmiDevice *device, + GAsyncResult *res, + TestFixture *fixture) +{ + GError *error = NULL; + gboolean st; + + st = qmi_device_release_client_finish (device, res, &error); + g_assert_no_error (error); + g_assert (st); + test_fixture_loop_stop (fixture); +} + +void +test_fixture_teardown (TestFixture *fixture) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (services); i++) { + guint8 expected[] = { + 0x01, /* marker */ + /* QMUX */ + 0x10, 0x00, /* length */ + 0x00, /* flags */ + 0x00, /* service CTL */ + 0x00, /* client */ + /* QMI header */ + 0x00, /* flags */ + 0xFF, /* transaction */ + 0x23, 0x00, /* message: Release CID */ + 0x05, 0x00, /* tlv length: 5 bytes */ + /* TLV */ + 0x01, /* type */ + 0x02, 0x00, /* length */ + 0xFF, /* UPDATE: service */ + 0x01 + }; + guint8 response[] = { + 0x01, /* marker */ + /* QMUX */ + 0x17, 0x00, /* length */ + 0x00, /* flags */ + 0x00, /* service */ + 0x00, /* client */ + /* QMI header */ + 0x01, /* flags: Response */ + 0xFF, /* transaction */ + 0x23, 0x00, /* message */ + 0x0C, 0x00, /* tlv length */ + /* TLV */ + 0x02, /* type: Result*/ + 0x04, 0x00, /* length */ + 0x00, 0x00, /* error status */ + 0x00, 0x00, /* error code */ + /* TLV */ + 0x01, /* type: Allocation Info */ + 0x02, 0x00, /* length */ + 0xFF, /* UPDATE: service */ + 0x01, /* cid: 1 */ + }; + + expected[15] = services[i]; + response[22] = services[i]; + test_port_context_set_command (fixture->ctx, + expected, G_N_ELEMENTS (expected), + response, G_N_ELEMENTS (response), + fixture->service_info[QMI_SERVICE_CTL].transaction_id++); + + qmi_device_release_client (fixture->device, fixture->service_info[services[i]].client, QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, 10, NULL, + (GAsyncReadyCallback) device_release_client_ready, + fixture); + + test_fixture_loop_run (fixture); + + g_clear_object (&fixture->service_info[services[i]].client); + fixture->service_info[services[i]].transaction_id = 0x0000; + } + + if (fixture->device) { + GError *error = NULL; + gboolean ret; + + ret = qmi_device_close (fixture->device, &error); + g_assert_no_error (error); + g_assert (ret); + g_object_unref (fixture->device); + } + + g_free (fixture->path); + + /* Stop port context */ + test_port_context_stop (fixture->ctx); + test_port_context_free (fixture->ctx); +} + +void +test_fixture_loop_stop (TestFixture *fixture) +{ + g_assert (fixture->loop); + g_main_loop_quit (fixture->loop); +} + +void +test_fixture_loop_run (TestFixture *fixture) +{ + g_assert (!fixture->loop); + fixture->loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); + g_main_loop_run (fixture->loop); + g_main_loop_unref (fixture->loop); + fixture->loop = NULL; +} diff --git a/src/libqmi-glib/test/test-fixture.h b/src/libqmi-glib/test/test-fixture.h new file mode 100644 index 0000000..6a10fae --- /dev/null +++ b/src/libqmi-glib/test/test-fixture.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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: + * + * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef TEST_FIXTURE_H +#define TEST_FIXTURE_H + +#include <gio/gio.h> +#include <libqmi-glib.h> + +#include "test-port-context.h" + +/*****************************************************************************/ +/* Test fixture setup */ + +typedef struct { + QmiClient *client; + guint16 transaction_id; +} TestServiceInfo; + +typedef struct { + GMainLoop *loop; + gchar *path; + TestPortContext *ctx; + QmiDevice *device; + TestServiceInfo service_info[255]; +} TestFixture; + +void test_fixture_setup (TestFixture *fixture); +void test_fixture_teardown (TestFixture *fixture); +void test_fixture_loop_run (TestFixture *fixture); +void test_fixture_loop_stop (TestFixture *fixture); + +typedef void (*TCFunc) (TestFixture *, gconstpointer); +#define TEST_ADD(path,method) \ + g_test_add (path, \ + TestFixture, \ + NULL, \ + (TCFunc)test_fixture_setup, \ + (TCFunc)method, \ + (TCFunc)test_fixture_teardown) + +#endif /* TEST_FIXTURE_H */ diff --git a/src/libqmi-glib/test/test-generated.c b/src/libqmi-glib/test/test-generated.c new file mode 100644 index 0000000..0a6275c --- /dev/null +++ b/src/libqmi-glib/test/test-generated.c @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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: + * + * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <libqmi-glib.h> + +#include "test-fixture.h" + +static void +test_generated_core (TestFixture *fixture) +{ + /* Noop */ +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + /* Test the setup/teardown test methods */ + TEST_ADD ("/libqmi-glib/generated/core", test_generated_core); + + return g_test_run (); +} diff --git a/src/libqmi-glib/test/test-port-context.c b/src/libqmi-glib/test/test-port-context.c new file mode 100644 index 0000000..e3ed94e --- /dev/null +++ b/src/libqmi-glib/test/test-port-context.c @@ -0,0 +1,442 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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: + * + * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es> + * + * Higly based on the test-port-context setup in ModemManager. + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> + +#include <gio/gio.h> +#include <gio/gunixsocketaddress.h> +#include <libqmi-glib.h> + +#include "test-port-context.h" + +#define BUFFER_SIZE 1024 + +struct _TestPortContext { + gchar *name; + GThread *thread; + gboolean ready; + GCond ready_cond; + GMutex ready_mutex; + GMainLoop *loop; + GSocketService *socket_service; + GList *clients; + GMutex command_mutex; + GByteArray *command; + GByteArray *response; +}; + +/*****************************************************************************/ +/* Helpers */ + +static gchar * +str_hex (gconstpointer mem, + gsize size, + gchar delimiter) +{ + const guint8 *data = mem; + gsize i; + gsize j; + gsize new_str_length; + gchar *new_str; + + /* Get new string length. If input string has N bytes, we need: + * - 1 byte for last NUL char + * - 2N bytes for hexadecimal char representation of each byte... + * - N-1 bytes for the separator ':' + * So... a total of (1+2N+N-1) = 3N bytes are needed... */ + new_str_length = 3 * size; + + /* Allocate memory for new array and initialize contents to NUL */ + new_str = g_malloc0 (new_str_length); + + /* Print hexadecimal representation of each byte... */ + for (i = 0, j = 0; i < size; i++, j += 3) { + /* Print character in output string... */ + snprintf (&new_str[j], 3, "%02X", data[i]); + /* And if needed, add separator */ + if (i != (size - 1) ) + new_str[j + 2] = delimiter; + } + + /* Set output string */ + return new_str; +} + +/*****************************************************************************/ + +void +test_port_context_set_command (TestPortContext *ctx, + const guint8 *command, + gsize command_size, + const guint8 *response, + gsize response_size, + guint16 transaction_id) +{ + g_mutex_lock (&ctx->command_mutex); + { + g_assert (!ctx->command); + ctx->command = g_byte_array_append (g_byte_array_sized_new (command_size), command, command_size); + qmi_message_set_transaction_id ((QmiMessage *)ctx->command, transaction_id); + + g_assert (!ctx->response); + ctx->response = g_byte_array_append (g_byte_array_sized_new (response_size), response, response_size); + qmi_message_set_transaction_id ((QmiMessage *)ctx->response, transaction_id); + } + g_mutex_unlock (&ctx->command_mutex); +} + +static GByteArray * +process_next_command (TestPortContext *ctx, + GByteArray *buffer) +{ + QmiMessage *message; + GError *error = NULL; + const guint8 *message_raw; + gsize message_raw_length; + gchar *expected; + gchar *received; + GByteArray *response; + + /* Every message received must start with the QMUX marker. + * If it doesn't, we broke framing :-/ + * If we broke framing, an error should be reported and the device + * should get closed */ + if (buffer->len > 0 && buffer->data[0] != QMI_MESSAGE_QMUX_MARKER) + g_assert_not_reached (); + + message = qmi_message_new_from_raw (buffer, &error); + if (!message) { + if (!error) + /* More data we need */ + return NULL; + /* Fail */ + g_assert_no_error (error); + } + + /* Process received message */ + message_raw = qmi_message_get_raw (message, &message_raw_length, &error); + g_assert_no_error (error); + g_assert (message_raw); + + /* Get printables to compare (we'll just get a nicer error if they are + * different), compared to a simple memcmp(). */ + g_mutex_lock (&ctx->command_mutex); + { + g_assert (ctx->command); + expected = str_hex (ctx->command->data, ctx->command->len, ':'); + } + g_mutex_unlock (&ctx->command_mutex); + + received = str_hex (message_raw, message_raw_length, ':'); + g_assert_cmpstr (expected, ==, received); + g_free (expected); + g_free (received); + qmi_message_unref (message); + g_byte_array_unref (ctx->command); + ctx->command = NULL; + + /* Command Expected == Received, so now return the Response */ + g_mutex_lock (&ctx->command_mutex); + { + response = ctx->response; + ctx->response = NULL; + } + g_mutex_unlock (&ctx->command_mutex); + + return response; +} + +/*****************************************************************************/ + +typedef struct { + TestPortContext *ctx; + GSocketConnection *connection; + GSource *connection_readable_source; + GByteArray *buffer; +} Client; + +static void +client_free (Client *client) +{ + g_source_destroy (client->connection_readable_source); + g_source_unref (client->connection_readable_source); + g_output_stream_close (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)), NULL, NULL); + if (client->buffer) + g_byte_array_unref (client->buffer); + g_object_unref (client->connection); + g_slice_free (Client, client); +} + +static void +connection_close (Client *client) +{ + client->ctx->clients = g_list_remove (client->ctx->clients, client); + client_free (client); +} + +static void +client_parse_request (Client *client) +{ + GByteArray *response; + + do { + response = process_next_command (client->ctx, client->buffer); + if (response) { + GError *error = NULL; + + if (!g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)), + response->data, + response->len, + NULL, /* bytes_written */ + NULL, /* cancellable */ + &error)) { + g_warning ("Cannot send response to client: %s", error->message); + g_error_free (error); + } + g_byte_array_unref (response); + } + } while (response); +} + +static gboolean +connection_readable_cb (GSocket *socket, + GIOCondition condition, + Client *client) +{ + guint8 buffer[BUFFER_SIZE]; + GError *error = NULL; + gssize r; + + if (condition & G_IO_HUP || condition & G_IO_ERR) { + g_debug ("client connection closed"); + connection_close (client); + return FALSE; + } + + if (!(condition & G_IO_IN || condition & G_IO_PRI)) + return TRUE; + + r = g_input_stream_read (g_io_stream_get_input_stream (G_IO_STREAM (client->connection)), + buffer, + BUFFER_SIZE, + NULL, + &error); + + if (r < 0) { + g_warning ("Error reading from istream: %s", error ? error->message : "unknown"); + if (error) + g_error_free (error); + /* Close the device */ + connection_close (client); + return FALSE; + } + + if (r == 0) + return TRUE; + + /* else, r > 0 */ + if (!G_UNLIKELY (client->buffer)) + client->buffer = g_byte_array_sized_new (r); + g_byte_array_append (client->buffer, buffer, r); + + /* Try to parse input messages */ + client_parse_request (client); + + return TRUE; +} + +static Client * +client_new (TestPortContext *ctx, + GSocketConnection *connection) +{ + Client *client; + + client = g_slice_new0 (Client); + client->ctx = ctx; + client->connection = g_object_ref (connection); + client->connection_readable_source = g_socket_create_source (g_socket_connection_get_socket (client->connection), + G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, + NULL); + g_source_set_callback (client->connection_readable_source, + (GSourceFunc)connection_readable_cb, + client, + NULL); + g_source_attach (client->connection_readable_source, g_main_context_get_thread_default ()); + + return client; +} + +/* /\*****************************************************************************\/ */ + +static void +incoming_cb (GSocketService *service, + GSocketConnection *connection, + GObject *unused, + TestPortContext *ctx) +{ + Client *client; + + client = client_new (ctx, connection); + ctx->clients = g_list_append (ctx->clients, client); +} + +static void +create_socket_service (TestPortContext *ctx) +{ + GError *error = NULL; + GSocketService *service; + GSocketAddress *address; + GSocket *socket; + + g_assert (ctx->socket_service == NULL); + + /* Create socket */ + socket = g_socket_new (G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + if (!socket) + g_error ("Cannot create socket: %s", error->message); + + /* Bind to address */ + address = (g_unix_socket_address_new_with_type ( + ctx->name, + -1, + G_UNIX_SOCKET_ADDRESS_ABSTRACT)); + if (!g_socket_bind (socket, address, TRUE, &error)) + g_error ("Cannot bind socket '%s': %s", ctx->name, error->message); + + /* Listen */ + if (!g_socket_listen (socket, &error)) + g_error ("Cannot listen in socket: %s", error->message); + + /* Create socket service */ + service = g_socket_service_new (); + g_signal_connect (service, "incoming", G_CALLBACK (incoming_cb), ctx); + if (!g_socket_listener_add_socket (G_SOCKET_LISTENER (service), + socket, + NULL, /* don't pass an object, will take a reference */ + &error)) + g_error ("Cannot add listener to socket: %s", error->message); + + /* Start it */ + g_socket_service_start (service); + + /* And store it */ + ctx->socket_service = service; + + /* Signal that the thread is ready */ + g_mutex_lock (&ctx->ready_mutex); + ctx->ready = TRUE; + g_cond_signal (&ctx->ready_cond); + g_mutex_unlock (&ctx->ready_mutex); + + if (socket) + g_object_unref (socket); + if (address) + g_object_unref (address); +} + +/*****************************************************************************/ + +void +test_port_context_stop (TestPortContext *ctx) +{ + g_assert (ctx->thread != NULL); + g_assert (ctx->loop != NULL); + + g_main_loop_quit (ctx->loop); + + g_thread_join (ctx->thread); + ctx->thread = NULL; +} + +static gpointer +port_context_thread_func (TestPortContext *ctx) +{ + GMainContext *thread_context; + + thread_context = g_main_context_new (); + g_main_context_push_thread_default (thread_context); + + create_socket_service (ctx); + + g_assert (ctx->loop == NULL); + ctx->loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); + g_main_loop_run (ctx->loop); + g_main_loop_unref (ctx->loop); + ctx->loop = NULL; + return NULL; +} + +void +test_port_context_start (TestPortContext *ctx) +{ + g_assert (ctx->thread == NULL); + ctx->thread = g_thread_new (ctx->name, + (GThreadFunc)port_context_thread_func, + ctx); + + /* Now wait until the thread has finished its initialization and is + * ready to serve connections */ + g_mutex_lock (&ctx->ready_mutex); + while (!ctx->ready) + g_cond_wait (&ctx->ready_cond, &ctx->ready_mutex); + g_mutex_unlock (&ctx->ready_mutex); +} + +/*****************************************************************************/ + +void +test_port_context_free (TestPortContext *ctx) +{ + g_assert (ctx->thread == NULL); + g_assert (ctx->loop == NULL); + + g_cond_clear (&ctx->ready_cond); + g_mutex_clear (&ctx->ready_mutex); + g_mutex_clear (&ctx->command_mutex); + + g_list_free_full (ctx->clients, (GDestroyNotify)client_free); + if (ctx->socket_service) { + if (g_socket_service_is_active (ctx->socket_service)) + g_socket_service_stop (ctx->socket_service); + g_object_unref (ctx->socket_service); + } + g_free (ctx->name); + if (ctx->command) + g_byte_array_unref (ctx->command); + if (ctx->response) + g_byte_array_unref (ctx->response); + g_slice_free (TestPortContext, ctx); +} + +TestPortContext * +test_port_context_new (const gchar *name) +{ + TestPortContext *ctx; + + ctx = g_slice_new0 (TestPortContext); + ctx->name = g_strdup (name); + g_cond_init (&ctx->ready_cond); + g_mutex_init (&ctx->ready_mutex); + g_mutex_init (&ctx->command_mutex); + return ctx; +} diff --git a/src/libqmi-glib/test/test-port-context.h b/src/libqmi-glib/test/test-port-context.h new file mode 100644 index 0000000..e845f67 --- /dev/null +++ b/src/libqmi-glib/test/test-port-context.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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: + * + * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es> + * + * Higly based on the test-port-context setup in ModemManager. + */ + +#ifndef TEST_PORT_CONTEXT_H +#define TEST_PORT_CONTEXT_H + +#include <glib.h> +#include <glib-object.h> + +typedef struct _TestPortContext TestPortContext; + +TestPortContext *test_port_context_new (const gchar *name); +void test_port_context_start (TestPortContext *ctx); +void test_port_context_stop (TestPortContext *ctx); +void test_port_context_free (TestPortContext *ctx); +void test_port_context_set_command (TestPortContext *ctx, + const guint8 *command, + gsize command_size, + const guint8 *response, + gsize response_size, + guint16 transaction_id); + +#endif /* TEST_PORT_CONTEXT_H */ |