summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Berg <bberg@redhat.com>2022-06-13 22:59:54 +0200
committerRichard Hughes <richard@hughsie.com>2022-06-15 16:59:45 +0100
commitdf0640f1a35f36d923cf11b2b778a5fbbe47f40f (patch)
treeef4f4d8bd7362f3e6dc129b088cc159087471671
parent0ff5cca64c4ca45053ded5a81eecd1e5745c411d (diff)
downloadgusb-df0640f1a35f36d923cf11b2b778a5fbbe47f40f.tar.gz
gusb: Add some umockdev based tests
The primary purpose for this is to test the hotplug codepaths.
-rw-r--r--gusb/gusb-umockdev-test.c211
-rw-r--r--gusb/meson.build13
-rw-r--r--meson.build7
-rw-r--r--meson_options.txt1
4 files changed, 231 insertions, 1 deletions
diff --git a/gusb/gusb-umockdev-test.c b/gusb/gusb-umockdev-test.c
new file mode 100644
index 0000000..23ee491
--- /dev/null
+++ b/gusb/gusb-umockdev-test.c
@@ -0,0 +1,211 @@
+/*
+ * libusb umockdev based tests
+ *
+ * Copyright (C) 2022 Benjamin Berg <bberg@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gusb.h"
+
+#include "umockdev.h"
+
+#define UNUSED_DATA __attribute__ ((unused)) gconstpointer unused_data
+
+/* avoid leak reports inside assertions; leaking stuff on assertion failures does not matter in tests */
+#if !defined(__clang__)
+#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
+#pragma GCC diagnostic ignored "-Wanalyzer-file-leak"
+#endif
+
+typedef struct {
+ UMockdevTestbed *testbed;
+ GUsbContext *ctx;
+} UMockdevTestbedFixture;
+
+static void
+test_fixture_setup (UMockdevTestbedFixture *fixture, UNUSED_DATA)
+{
+ fixture->testbed = umockdev_testbed_new ();
+ g_assert (fixture->testbed != NULL);
+}
+
+static void
+test_fixture_setup_empty (UMockdevTestbedFixture *fixture, UNUSED_DATA)
+{
+ test_fixture_setup (fixture, NULL);
+ fixture->ctx = g_usb_context_new (NULL);
+}
+
+static void
+test_fixture_teardown (UMockdevTestbedFixture *fixture, UNUSED_DATA)
+{
+ /* Break context -> device -> context cycle */
+ if (fixture->ctx)
+ g_object_run_dispose (G_OBJECT (fixture->ctx));
+ g_clear_object (&fixture->ctx);
+ g_clear_object (&fixture->testbed);
+
+ /* Running the mainloop is needed to ensure everything is cleaned up. */
+ while (g_main_context_iteration (NULL, FALSE)) { }
+}
+
+static void
+test_fixture_add_canon (UMockdevTestbedFixture *fixture)
+{
+ /* NOTE: There is no device file, so cannot be opened */
+
+ /* NOTE: add_device would not create a file, needed for device emulation */
+ /* XXX: Racy, see https://github.com/martinpitt/umockdev/issues/173 */
+ umockdev_testbed_add_from_string (fixture->testbed,
+ "P: /devices/usb1\n"
+ "N: bus/usb/001/001\n"
+ "E: SUBSYSTEM=usb\n"
+ "E: DRIVER=usb\n"
+ "E: BUSNUM=001\n"
+ "E: DEVNUM=001\n"
+ "E: DEVNAME=/dev/bus/usb/001/001\n"
+ "E: DEVTYPE=usb_device\n"
+ "A: bConfigurationValue=1\\n\n"
+ "A: busnum=1\\n\n"
+ "A: devnum=1\\n\n"
+ "A: bConfigurationValue=1\\n\n"
+ "A: speed=480\\n\n"
+ /* descriptor from a Canon PowerShot SX200; VID 04a9 PID 31c0 */
+ "H: descriptors="
+ "1201000200000040a904c03102000102"
+ "030109022700010100c0010904000003"
+ "06010100070581020002000705020200"
+ "020007058303080009\n",
+ NULL);
+}
+
+static void
+test_ctx_enumerate (UMockdevTestbedFixture *fixture, UNUSED_DATA)
+{
+ g_autoptr(GPtrArray) devices = NULL;
+
+ test_fixture_add_canon (fixture);
+
+ g_usb_context_enumerate (fixture->ctx);
+
+ devices = g_usb_context_get_devices (fixture->ctx);
+ g_assert_cmpint (devices->len, ==, 1);
+}
+
+static void
+count_hotplug_event_cb (GUsbContext *context, GUsbDevice *device, gpointer user_data)
+{
+ int *counter = user_data;
+
+ *counter += 1;
+}
+
+static void
+test_ctx_hotplug (UMockdevTestbedFixture *fixture, UNUSED_DATA)
+{
+ g_autoptr(GPtrArray) devices = NULL;
+ gint events = 0;
+
+ g_signal_connect (fixture->ctx, "device-added", G_CALLBACK (count_hotplug_event_cb), &events);
+
+ g_usb_context_enumerate (fixture->ctx);
+
+ devices = g_usb_context_get_devices (fixture->ctx);
+ g_assert_cmpint (devices->len, ==, 0);
+ g_assert_cmpint (events, ==, 0);
+ g_clear_pointer (&devices, g_ptr_array_unref);
+
+ test_fixture_add_canon (fixture);
+ /* Ensure the event was processed by helper thread. */
+ g_usleep (G_USEC_PER_SEC / 2);
+
+ /* Still not returned (and no event fired). */
+ devices = g_usb_context_get_devices (fixture->ctx);
+ g_assert_cmpint (devices->len, ==, 0);
+ g_assert_cmpint (events, ==, 0);
+ g_clear_pointer (&devices, g_ptr_array_unref);
+
+ /* Run mainloop, which causes the event to be processed. */
+ while (g_main_context_iteration (NULL, FALSE)) { }
+
+ devices = g_usb_context_get_devices (fixture->ctx);
+ g_assert_cmpint (events, ==, 1);
+ g_assert_cmpint (devices->len, ==, 1);
+ g_clear_pointer (&devices, g_ptr_array_unref);
+}
+
+static void
+test_ctx_hotplug_dispose (UMockdevTestbedFixture *fixture, UNUSED_DATA)
+{
+ g_autoptr(GPtrArray) devices = NULL;
+ gint events = 0;
+
+ g_signal_connect (fixture->ctx, "device-added", G_CALLBACK (count_hotplug_event_cb), &events);
+
+ g_usb_context_enumerate (fixture->ctx);
+ devices = g_usb_context_get_devices (fixture->ctx);
+ g_assert_cmpint (devices->len, ==, 0);
+ g_assert_cmpint (events, ==, 0);
+ g_clear_pointer (&devices, g_ptr_array_unref);
+
+ test_fixture_add_canon (fixture);
+ /* Ensure the event was processed by helper thread. */
+ g_usleep (G_USEC_PER_SEC / 2);
+
+ /* Still not returned (and no event fired). */
+ g_usb_context_enumerate (fixture->ctx);
+ devices = g_usb_context_get_devices (fixture->ctx);
+ g_assert_cmpint (devices->len, ==, 0);
+ g_assert_cmpint (events, ==, 0);
+ g_clear_pointer (&devices, g_ptr_array_unref);
+
+ /* The idle handler is pending, we dispose our context reference */
+ g_object_run_dispose (G_OBJECT (fixture->ctx));
+
+ /* Run mainloop, which causes the event to be processed. */
+ while (g_main_context_iteration (NULL, FALSE)) { }
+
+ /* But no signal is fired. */
+ g_assert_cmpint (events, ==, 0);
+
+ g_clear_object (&fixture->ctx);
+}
+
+int
+main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add("/gusb/ctx/enumerate", UMockdevTestbedFixture, NULL,
+ test_fixture_setup_empty,
+ test_ctx_enumerate,
+ test_fixture_teardown);
+
+ g_test_add("/gusb/ctx/hotplug", UMockdevTestbedFixture, NULL,
+ test_fixture_setup_empty,
+ test_ctx_hotplug,
+ test_fixture_teardown);
+
+ g_test_add("/gusb/ctx/hotplug-dispose", UMockdevTestbedFixture, NULL,
+ test_fixture_setup_empty,
+ test_ctx_hotplug_dispose,
+ test_fixture_teardown);
+
+ return g_test_run();
+}
diff --git a/gusb/meson.build b/gusb/meson.build
index b4fa382..243b0be 100644
--- a/gusb/meson.build
+++ b/gusb/meson.build
@@ -214,4 +214,17 @@ if get_option('tests')
],
)
test('gusb-self-test', e)
+
+ # Umockdev based tests
+ test_env = environment()
+ test_env.prepend('LD_PRELOAD', 'libumockdev-preload.so.0')
+
+ test_umockdev = executable(
+ 'gusb-umockdev-test',
+ 'gusb-umockdev-test.c',
+ dependencies: [ umockdev, gusb_dep ])
+
+ test('gusb-umockdev-test',
+ test_umockdev,
+ env: test_env)
endif
diff --git a/meson.build b/meson.build
index 7fca7e9..22555d2 100644
--- a/meson.build
+++ b/meson.build
@@ -1,7 +1,7 @@
project('libgusb', 'c',
version : '0.3.10',
license : 'LGPL-2.1+',
- meson_version : '>=0.46.0',
+ meson_version : '>=0.49.0',
default_options : ['c_std=c99']
)
@@ -93,6 +93,11 @@ add_project_link_arguments(
language: 'c'
)
+if get_option('tests')
+ umockdev = dependency('umockdev-1.0', version: '>= 0.17.7', disabler: true, required: get_option('umockdev'))
+else
+ umockdev = disabler()
+endif
libgio = dependency('gio-2.0', version : '>= 2.44.0')
libusb = dependency('libusb-1.0', version : '>= 1.0.9')
if cc.has_header_symbol('libusb.h', 'LIBUSB_CAP_HAS_HOTPLUG', dependencies: libusb)
diff --git a/meson_options.txt b/meson_options.txt
index fc56b60..5c956d0 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -3,3 +3,4 @@ option('vapi', type : 'boolean', value : true, description : 'Build VAPI')
option('usb_ids', type : 'string', value : '/usr/share/hwdata/usb.ids', description : 'Path to usb.ids file')
option('docs', type : 'boolean', value : true, description : 'Generate documentation')
option('introspection', type : 'boolean', value : true, description : 'Generate gobject introspection data')
+option('umockdev', type : 'feature', value : 'auto', description : 'Build and run umockdev based tests')