/* * libusb umockdev based tests * * Copyright (C) 2022 Benjamin Berg * * 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 . */ #include #include #include #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(); }