tests: Add some umockdev based tests

Add some basic umockdev tests. The setup in this case is all in one
process, but umockdev in principle could also work between processes.

This is just a start, more tests would make sense.
diff --git a/configure.ac b/configure.ac
index 97712d9..d4f1251 100644
--- a/configure.ac
+++ b/configure.ac
@@ -179,6 +179,14 @@
 		dnl system has udev. use it or fail!
 		AC_CHECK_HEADER([libudev.h], [], [AC_MSG_ERROR([udev support requested but libudev header not installed])])
 		AC_CHECK_LIB([udev], [udev_new], [], [AC_MSG_ERROR([udev support requested but libudev not installed])])
+
+		# We can build umockdev tests (if available)
+		PKG_PROG_PKG_CONFIG
+		PKG_CHECK_MODULES(UMOCKDEV, umockdev-1.0 >= 0.16.0, ac_have_umockdev=yes, ac_have_umockdev=no)
+		PKG_CHECK_MODULES(UMOCKDEV_HOTPLUG, umockdev-1.0 >= 0.17.7, ac_umockdev_hotplug=yes, ac_umockdev_hotplug=no)
+		if test "x$ac_umockdev_hotplug" = xyes; then
+			AC_DEFINE([UMOCKDEV_HOTPLUG], [1], [UMockdev hotplug code is not racy])
+		fi
 	else
 		AC_CHECK_HEADERS([asm/types.h])
 		AC_CHECK_HEADER([linux/netlink.h], [], [AC_MSG_ERROR([Linux netlink header not found])])
@@ -358,6 +366,7 @@
 
 AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$build_examples" != xno])
 AM_CONDITIONAL([BUILD_TESTS], [test "x$build_tests" != xno])
+AM_CONDITIONAL([BUILD_UMOCKDEV_TEST], [test "x$ac_have_umockdev" = xyes -a "x$log_enabled" != xno])
 AM_CONDITIONAL([CREATE_IMPORT_LIB], [test "x$create_import_lib" = xyes])
 AM_CONDITIONAL([OS_DARWIN], [test "x$backend" = xdarwin])
 AM_CONDITIONAL([OS_HAIKU], [test "x$backend" = xhaiku])
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index 2819c18..727861c 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11700
+#define LIBUSB_NANO 11701
diff --git a/tests/Makefile.am b/tests/Makefile.am
index cb8fad9..cf6237e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,6 +2,17 @@
 LDADD = ../libusb/libusb-1.0.la
 LIBS =
 
+stress_SOURCES = stress.c libusb_testlib.h testlib.c
+
 noinst_PROGRAMS = stress
 
-stress_SOURCES = stress.c libusb_testlib.h testlib.c
+if BUILD_UMOCKDEV_TEST
+# NOTE: We add libumockdev-preload.so so that we can run tests in-process
+#       We also use -Wl,-lxxx as the compiler doesn't need it and libtool
+#       would reorder the flags otherwise.
+umockdev_CPPFLAGS = ${UMOCKDEV_CFLAGS} -I$(top_srcdir)/libusb
+umockdev_LDFLAGS = -Wl,--push-state,--no-as-needed -Wl,-lumockdev-preload -Wl,--pop-state ${UMOCKDEV_LIBS}
+umockdev_SOURCES = umockdev.c
+
+noinst_PROGRAMS += umockdev
+endif
diff --git a/tests/umockdev.c b/tests/umockdev.c
new file mode 100644
index 0000000..1eda532
--- /dev/null
+++ b/tests/umockdev.c
@@ -0,0 +1,1144 @@
+/*
+ * 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 "config.h"
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+#include "libusb.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 {
+	pid_t thread;
+	libusb_context *ctx;
+	enum libusb_log_level level;
+	char *str;
+} LogMessage;
+
+static void
+log_message_free(LogMessage *msg)
+{
+	g_free(msg->str);
+	g_free(msg);
+}
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(LogMessage, log_message_free)
+
+typedef struct _UsbChat UsbChat;
+
+struct _UsbChat {
+	gboolean submit;
+	gboolean reap;
+	UsbChat *reaps;
+	UsbChat *next;
+
+	/* struct usbdevfs_urb */
+	unsigned char type;
+	unsigned char endpoint;
+	int status;
+	unsigned int flags;
+	const unsigned char *buffer;
+	int buffer_length;
+	int actual_length;
+
+	/* <submit urb> */
+	UMockdevIoctlData *submit_urb;
+};
+
+typedef struct {
+	UMockdevTestbed *testbed;
+	UMockdevIoctlBase *handler;
+	struct libusb_context *ctx;
+
+	gchar *root_dir;
+	gchar *sys_dir;
+
+	gboolean libusb_log_silence;
+	GList *libusb_log;
+
+	UsbChat *chat;
+	GList *flying_urbs;
+	GList *discarded_urbs;
+
+	/* GMutex confuses tsan unecessarily */
+	pthread_mutex_t mutex;
+} UMockdevTestbedFixture;
+
+/* Global for log handler */
+static UMockdevTestbedFixture *cur_fixture = NULL;
+
+static void
+log_handler(libusb_context *ctx, enum libusb_log_level level, const char *str)
+{
+	/* May be called from different threads without synchronization! */
+	LogMessage *msg;
+	pid_t tid = gettid();
+
+	g_assert (cur_fixture != NULL);
+	g_assert(pthread_mutex_lock(&cur_fixture->mutex) == 0);
+
+	msg = g_new0(LogMessage, 1);
+	msg->ctx = ctx;
+	msg->level = level;
+	msg->str = g_strchomp (g_strdup(str));
+	msg->thread = tid;
+
+	if (!cur_fixture->libusb_log_silence)
+		g_printerr("%s\n", msg->str);
+
+	cur_fixture->libusb_log = g_list_append(cur_fixture->libusb_log, msg);
+	pthread_mutex_unlock(&cur_fixture->mutex);
+}
+
+static void
+log_handler_null(libusb_context *ctx, enum libusb_log_level level, const char *str)
+{
+	(void) ctx;
+	(void) level;
+	(void) str;
+}
+
+static void
+clear_libusb_log(UMockdevTestbedFixture * fixture, enum libusb_log_level level)
+{
+	g_assert(pthread_mutex_lock(&fixture->mutex) == 0);
+
+	while (fixture->libusb_log) {
+		LogMessage *msg = fixture->libusb_log->data;
+
+		g_assert(msg->ctx == fixture->ctx);
+
+		if (msg->level < level) {
+			pthread_mutex_unlock(&fixture->mutex);
+			return;
+		}
+
+		fixture->libusb_log = g_list_delete_link(fixture->libusb_log, fixture->libusb_log);
+		log_message_free(msg);
+	}
+	pthread_mutex_unlock(&fixture->mutex);
+}
+
+static void
+assert_libusb_log_msg(UMockdevTestbedFixture * fixture, enum libusb_log_level level, const char *re)
+{
+	g_assert(pthread_mutex_lock(&fixture->mutex) == 0);
+
+	while (fixture->libusb_log) {
+		g_autoptr(LogMessage) msg = NULL;
+
+		if (fixture->libusb_log == NULL)
+			g_error ("No level %d message found searching for %s", level, re);
+
+		msg = fixture->libusb_log->data;
+		fixture->libusb_log = g_list_delete_link(fixture->libusb_log, fixture->libusb_log);
+
+		if (msg->ctx != fixture->ctx)
+			g_error ("Saw unexpected message \"%s\" from context %p while %p was expected",
+				 msg->str, msg->ctx, fixture->ctx);
+
+		if (msg->level == level && g_regex_match_simple(re, msg->str, 0, 0)) {
+			pthread_mutex_unlock(&fixture->mutex);
+			return;
+		}
+
+		/* Allow skipping INFO and DEBUG messages */
+		if (msg->level >= LIBUSB_LOG_LEVEL_INFO)
+			continue;
+
+		g_error ("Searched for \"%s\" (%d) but found \"%s\" (%d)", re, level, msg->str, msg->level);
+	}
+
+	pthread_mutex_unlock(&fixture->mutex);
+	g_error ("Searched for \"%s\" (%d) but no message matched", re, level);
+}
+
+static void
+assert_libusb_no_log_msg(UMockdevTestbedFixture * fixture, enum libusb_log_level level, const char *re)
+{
+	g_assert(pthread_mutex_lock(&fixture->mutex) == 0);
+
+	while (fixture->libusb_log) {
+		g_autoptr(LogMessage) msg = NULL;
+		gboolean matching;
+
+		msg = fixture->libusb_log->data;
+		fixture->libusb_log = g_list_delete_link(fixture->libusb_log, fixture->libusb_log);
+
+		g_assert(msg->ctx == fixture->ctx);
+
+		matching = (msg->level == level && g_regex_match_simple(re, msg->str, 0, 0));
+
+		/* Allow skipping INFO and DEBUG messages */
+		if (!matching && msg->level >= LIBUSB_LOG_LEVEL_INFO)
+			continue;
+
+		g_error ("Asserting \"%s\" (%d) not logged and found \"%s\" (%d)", re, level, msg->str, msg->level);
+	}
+
+	pthread_mutex_unlock(&fixture->mutex);
+}
+
+static void
+dump_buffer(const unsigned char *buffer, int len)
+{
+	g_autoptr(GString) line = NULL;
+
+	line = g_string_new ("");
+	for (gint i = 0; i < len; i++) {
+		g_string_append_printf(line, "%02x ", buffer[i]);
+		if ((i + 1) % 16 == 0) {
+			g_printerr("    %s\n", line->str);
+			g_string_set_size(line, 0);
+		}
+	}
+
+	if (line->len)
+		g_printerr("    %s\n", line->str);
+}
+
+static gint
+cmp_ioctl_data_addr(const void *data, const void *addr)
+{
+	return ((const UMockdevIoctlData*) data)->client_addr != (gulong) addr;
+}
+
+static gboolean
+handle_ioctl_cb (UMockdevIoctlBase *handler, UMockdevIoctlClient *client, UMockdevTestbedFixture *fixture)
+{
+	UMockdevIoctlData *ioctl_arg;
+	long int request;
+	struct usbdevfs_urb *urb;
+
+	(void) handler;
+
+	request = umockdev_ioctl_client_get_request (client);
+	ioctl_arg = umockdev_ioctl_client_get_arg (client);
+
+	/* NOTE: We share the address space, dereferencing pointers *will* work.
+	 * However, to make tsan work, we still stick to the API that resolves
+	 * the data into a local copy! */
+
+	switch (request) {
+	case USBDEVFS_GET_CAPABILITIES: {
+		g_autoptr(UMockdevIoctlData) d = NULL;
+		d = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(guint32), NULL);
+
+		*(guint32*) d->data = USBDEVFS_CAP_BULK_SCATTER_GATHER |
+				      USBDEVFS_CAP_BULK_CONTINUATION |
+				      USBDEVFS_CAP_NO_PACKET_SIZE_LIM |
+				      USBDEVFS_CAP_REAP_AFTER_DISCONNECT |
+				      USBDEVFS_CAP_ZERO_PACKET;
+
+		umockdev_ioctl_client_complete(client, 0, 0);
+		return TRUE;
+	}
+
+	case USBDEVFS_CLAIMINTERFACE:
+	case USBDEVFS_RELEASEINTERFACE:
+	case USBDEVFS_CLEAR_HALT:
+	case USBDEVFS_RESET:
+	case USBDEVFS_RESETEP:
+		umockdev_ioctl_client_complete(client, 0, 0);
+		return TRUE;
+
+	case USBDEVFS_SUBMITURB: {
+		g_autoptr(UMockdevIoctlData) urb_buffer = NULL;
+		g_autoptr(UMockdevIoctlData) urb_data = NULL;
+		gsize buflen;
+
+		if (!fixture->chat || !fixture->chat->submit)
+			return FALSE;
+
+		buflen = fixture->chat->buffer_length;
+		if (fixture->chat->type == USBDEVFS_URB_TYPE_CONTROL)
+			buflen = 8;
+
+		urb_data = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(struct usbdevfs_urb), NULL);
+		urb = (struct usbdevfs_urb*) urb_data->data;
+		urb_buffer = umockdev_ioctl_data_resolve(urb_data, G_STRUCT_OFFSET(struct usbdevfs_urb, buffer), urb->buffer_length, NULL);
+
+		if (fixture->chat->type == urb->type &&
+		    fixture->chat->endpoint == urb->endpoint &&
+		    fixture->chat->buffer_length == urb->buffer_length &&
+		    (fixture->chat->buffer == NULL || memcmp (fixture->chat->buffer, urb_buffer->data, buflen) == 0)) {
+			fixture->flying_urbs = g_list_append (fixture->flying_urbs, umockdev_ioctl_data_ref(urb_data));
+
+			if (fixture->chat->reaps)
+				fixture->chat->reaps->submit_urb = urb_data;
+
+			if (fixture->chat->status)
+				umockdev_ioctl_client_complete(client, -1, -fixture->chat->status);
+			else
+				umockdev_ioctl_client_complete(client, 0, 0);
+
+			if (fixture->chat->next)
+				fixture->chat = fixture->chat->next;
+			else
+				fixture->chat += 1;
+			return TRUE;
+		}
+
+		/* chat message didn't match, don't accept it */
+		g_printerr("Could not process submit urb:\n");
+		g_printerr(" t: %d, ep: %d, actual_length: %d, buffer_length: %d\n",
+			   urb->type, urb->endpoint, urb->actual_length, urb->buffer_length);
+		if (urb->type == USBDEVFS_URB_TYPE_CONTROL || urb->endpoint & LIBUSB_ENDPOINT_IN)
+			dump_buffer(urb->buffer, urb->buffer_length);
+		g_printerr("Looking for:\n");
+		g_printerr(" t: %d, ep: %d, actual_length: %d, buffer_length: %d\n",
+			   fixture->chat->type, fixture->chat->endpoint,
+			   fixture->chat->actual_length, fixture->chat->buffer_length);
+		if (fixture->chat->buffer)
+			dump_buffer(fixture->chat->buffer, buflen);
+
+		return FALSE;
+	}
+
+	case USBDEVFS_REAPURB:
+	case USBDEVFS_REAPURBNDELAY: {
+		g_autoptr(UMockdevIoctlData) urb_ptr = NULL;
+		g_autoptr(UMockdevIoctlData) urb_data = NULL;
+
+		if (fixture->discarded_urbs) {
+			urb_data = fixture->discarded_urbs->data;
+			urb = (struct usbdevfs_urb*) urb_data->data;
+			fixture->discarded_urbs = g_list_delete_link(fixture->discarded_urbs, fixture->discarded_urbs);
+			urb->status = -ENOENT;
+
+			urb_ptr = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(gpointer), NULL);
+			umockdev_ioctl_data_set_ptr(urb_ptr, 0, urb_data);
+
+			umockdev_ioctl_client_complete(client, 0, 0);
+			return TRUE;
+		}
+
+		if (fixture->chat && fixture->chat->reap) {
+			GList *l = g_list_find(fixture->flying_urbs, fixture->chat->submit_urb);
+
+			if (l) {
+				fixture->flying_urbs = g_list_remove_link(fixture->flying_urbs, fixture->flying_urbs);
+
+				urb_data = fixture->chat->submit_urb;
+				urb = (struct usbdevfs_urb*) urb_data->data;
+				urb->actual_length = fixture->chat->actual_length;
+				if (urb->type == USBDEVFS_URB_TYPE_CONTROL && urb->actual_length)
+					urb->actual_length -= 8;
+				if (fixture->chat->buffer)
+					memcpy(urb->buffer, fixture->chat->buffer, fixture->chat->actual_length);
+				urb->status = fixture->chat->status;
+
+				urb_ptr = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(gpointer), NULL);
+				umockdev_ioctl_data_set_ptr(urb_ptr, 0, urb_data);
+				if (fixture->chat->next)
+					fixture->chat = fixture->chat->next;
+				else
+					fixture->chat += 1;
+				umockdev_ioctl_client_complete(client, 0, 0);
+				return TRUE;
+			}
+		}
+
+		/* Nothing to reap */
+		umockdev_ioctl_client_complete(client, -1, EAGAIN);
+		return TRUE;
+	}
+
+	case USBDEVFS_DISCARDURB: {
+		GList *l = g_list_find_custom(fixture->flying_urbs, *(void**) ioctl_arg->data, cmp_ioctl_data_addr);
+
+		if (l) {
+			fixture->discarded_urbs = g_list_append(fixture->discarded_urbs, l->data);
+			fixture->flying_urbs = g_list_delete_link(fixture->flying_urbs, l);
+			umockdev_ioctl_client_complete(client, 0, 0);
+		} else {
+			umockdev_ioctl_client_complete(client, -1, EINVAL);
+		}
+
+		return TRUE;
+	}
+
+	default:
+		return FALSE;
+	}
+}
+
+static void
+test_fixture_add_canon(UMockdevTestbedFixture * fixture)
+{
+	/* Setup first, so we can be sure libusb_open works when the add uevent
+	 * happens.
+	 */
+	g_assert_cmpint(umockdev_testbed_attach_ioctl(fixture->testbed, "/dev/bus/usb/001/001", fixture->handler, NULL), ==, 1);
+
+	/* 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_fixture_setup_libusb(UMockdevTestbedFixture * fixture, int devcount)
+{
+	libusb_device **devs = NULL;
+
+	libusb_init (&fixture->ctx);
+
+	/* Supress global log messages completely
+	 * (though, in some tests it might be interesting to check there are no real ones).
+	 */
+	libusb_set_log_cb (NULL, log_handler_null, LIBUSB_LOG_CB_GLOBAL);
+	libusb_set_option (fixture->ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, devcount);
+	libusb_free_device_list(devs, TRUE);
+	libusb_set_log_cb (fixture->ctx, log_handler, LIBUSB_LOG_CB_CONTEXT);
+}
+
+static void
+test_fixture_setup_common(UMockdevTestbedFixture * fixture)
+{
+	g_assert(cur_fixture == NULL);
+	cur_fixture = fixture;
+
+	pthread_mutex_init(&fixture->mutex, NULL);
+
+	fixture->testbed = umockdev_testbed_new();
+	g_assert(fixture->testbed != NULL);
+	fixture->root_dir = umockdev_testbed_get_root_dir(fixture->testbed);
+	fixture->sys_dir = umockdev_testbed_get_sys_dir(fixture->testbed);
+
+	fixture->handler = umockdev_ioctl_base_new();
+	g_object_connect(fixture->handler, "signal-after::handle-ioctl", handle_ioctl_cb, fixture, NULL);
+}
+
+static void
+test_fixture_setup_empty(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	test_fixture_setup_common(fixture);
+
+	test_fixture_setup_libusb(fixture, 0);
+}
+
+static void
+test_fixture_setup_with_canon(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	test_fixture_setup_common(fixture);
+
+	test_fixture_add_canon(fixture);
+
+	test_fixture_setup_libusb(fixture, 1);
+}
+
+static void
+test_fixture_teardown(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	g_assert(cur_fixture == fixture);
+
+	/* Abort if there are any warnings/errors in the log */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+
+	if (fixture->ctx) {
+		libusb_device **devs = NULL;
+		int count = libusb_get_device_list(fixture->ctx, &devs);
+		libusb_free_device_list(devs, TRUE);
+
+		libusb_exit (fixture->ctx);
+
+		/* libusb_exit should result in the correct number of devices being destroyed */
+		for (int i = 0; i < count; i++)
+			assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_unref_device");
+
+		assert_libusb_no_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_unref_device");
+	}
+	libusb_set_log_cb (NULL, NULL, LIBUSB_LOG_CB_GLOBAL);
+	cur_fixture = NULL;
+
+	/* Abort if there are any warnings/errors in the log */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+	fixture->ctx = NULL;
+	g_assert_null(fixture->libusb_log);
+
+	g_clear_object(&fixture->handler);
+	g_clear_object(&fixture->testbed);
+
+	/* verify that temp dir gets cleaned up properly */
+	g_assert(!g_file_test(fixture->root_dir, G_FILE_TEST_EXISTS));
+	g_free(fixture->root_dir);
+	g_free(fixture->sys_dir);
+
+	while (fixture->flying_urbs) {
+		umockdev_ioctl_data_unref (fixture->flying_urbs->data);
+		fixture->flying_urbs = g_list_delete_link (fixture->flying_urbs, fixture->flying_urbs);
+	}
+
+	pthread_mutex_destroy(&fixture->mutex);
+}
+
+static void
+test_open_close(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	libusb_device **devs = NULL;
+	struct libusb_device_descriptor desc;
+	libusb_device_handle *handle = NULL;
+
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, 1);
+	/* The linux_enumerate_device may happen from a different thread */
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_get_device_list");
+	/* We have exactly one device */
+	g_assert_cmpint(libusb_get_bus_number(devs[0]), ==, 1);
+	g_assert_cmpint(libusb_get_device_address(devs[0]), ==, 1);
+
+	/* Get/Check descriptor */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+	libusb_get_device_descriptor (devs[0], &desc);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_get_device_descriptor");
+	g_assert_cmpint(desc.idVendor, ==, 0x04a9);
+	g_assert_cmpint(desc.idProduct, ==, 0x31c0);
+
+	/* Open and close */
+	g_assert_cmpint(libusb_open(devs[0], &handle), ==, 0);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "usbi_add_event_source");
+	g_assert_nonnull(handle);
+	libusb_close(handle);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "usbi_remove_event_source");
+
+	libusb_free_device_list(devs, TRUE);
+
+	/* Open and close using vid/pid */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+	libusb_close(handle);
+}
+
+static void
+test_close_flying(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{ .submit = FALSE }
+	};
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	/* Open */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  NULL,
+				  NULL,
+				  1);
+
+	/* Submit */
+	libusb_submit_transfer(transfer);
+
+	/* Closing logs fat error (two lines) */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+	libusb_close(handle);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[do_close\\] .*connected as far as we know");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[do_close\\] .*cancellation hasn't even been scheduled");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "\\[do_close\\] Removed transfer");
+
+	/* Free'ing the transfer works, and logs to the right context */
+	libusb_free_transfer(transfer);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "\\[libusb_free_transfer\\]");
+}
+
+static void
+test_close_cancelled(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{ .submit = FALSE }
+	};
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	/* Open */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  NULL,
+				  NULL,
+				  1);
+
+	/* Submit */
+	libusb_submit_transfer(transfer);
+	libusb_cancel_transfer(transfer);
+
+	/* Closing logs fat error (two lines) */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+	libusb_close(handle);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[do_close\\] .*connected as far as we know");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_WARNING, "\\[do_close\\] .*cancellation.*hasn't completed");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "\\[do_close\\] Removed transfer");
+
+	libusb_free_transfer(transfer);
+}
+
+static void
+test_ctx_destroy(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{ .submit = FALSE }
+	};
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	/* Open */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  NULL,
+				  NULL,
+				  1);
+
+	/* Submit */
+	libusb_submit_transfer(transfer);
+
+	/* Now we are evil and destroy the ctx! */
+	libusb_exit(fixture->ctx);
+
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_WARNING, "\\[libusb_exit\\] device.*still referenced");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_WARNING, "\\[libusb_exit\\] application left some devices open");
+
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+	fixture->ctx = NULL;
+
+	/* XXX: Closing crashes the application as it unref's the NULL pointer */
+	/* libusb_close(handle); */
+
+	libusb_free_transfer(transfer);
+}
+
+static void
+test_get_string_descriptor(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	unsigned char data[255] = { 0, };
+	libusb_device_handle *handle = NULL;
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .reaps = &chat[1],
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 12, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00",
+		}, {
+		  /* String with content 0x0409 (en_US) */
+		  .reap = TRUE,
+		  .actual_length = 12,
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00\x04\x03\x09\x04",
+		}, {
+		  .submit = TRUE,
+		  .reaps = &chat[3],
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 263, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x01\x03\x09\x04\xff\x00",
+		}, {
+		  /* 4 byte string, "ab" */
+		  .reap = TRUE,
+		  .actual_length = 14,
+		  .buffer = (const unsigned char*) "\x80\x06\x01\x03\x09\x04\xff\x00\x06\x03\x61\x00\x62\x00",
+		}, {
+		  .submit = TRUE,
+		  .reaps = &chat[5],
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 12, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00",
+		}, {
+		  .reap = TRUE,
+		  .status = -ENOENT,
+		}, {
+		  .submit = TRUE,
+		  .status = -ENOENT,
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 12, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00",
+		}, {
+		  .submit = FALSE,
+		}
+	};
+
+	fixture->chat = chat;
+
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	/* The chat allows us to fetch the descriptor */
+	g_assert_cmpint(libusb_get_string_descriptor_ascii(handle, 1, data, sizeof(data)), ==, 2);
+	g_assert_cmpint(memcmp(data, "ab", 2), ==, 0);
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+
+	/* Again, but the URB fails with ENOENT when reaping */
+	g_assert_cmpint(libusb_get_string_descriptor_ascii(handle, 1, data, sizeof(data)), ==, -1);
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+
+	/* Again, but the URB fails to submit with ENOENT */
+	g_assert_cmpint(libusb_get_string_descriptor_ascii(handle, 1, data, sizeof(data)), ==, -1);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[submit_control_transfer\\] submiturb failed, errno=2");
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+
+	libusb_close(handle);
+}
+
+static void
+transfer_cb_inc_user_data(struct libusb_transfer *transfer)
+{
+	*(int*)transfer->user_data += 1;
+}
+
+static void
+test_timeout(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{
+		  .submit = FALSE,
+		}
+	};
+	int completed = 0;
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  transfer_cb_inc_user_data,
+				  &completed,
+				  10);
+
+	libusb_submit_transfer(transfer);
+	while (!completed) {
+		g_assert_cmpint(libusb_handle_events_completed(fixture->ctx, &completed), ==, 0);
+		/* Silence after one iteration. */
+		fixture->libusb_log_silence = TRUE;
+	}
+	fixture->libusb_log_silence = FALSE;
+
+	g_assert_cmpint(transfer->status, ==, LIBUSB_TRANSFER_TIMED_OUT);
+	libusb_free_transfer(transfer);
+
+	libusb_close(handle);
+}
+
+#define THREADED_SUBMIT_URB_SETS 64
+#define THREADED_SUBMIT_URB_IN_FLIGHT 64
+typedef struct {
+	struct libusb_transfer *transfers[THREADED_SUBMIT_URB_IN_FLIGHT * THREADED_SUBMIT_URB_SETS];
+	int submitted;
+	int completed;
+	int done;
+	UMockdevTestbedFixture *fixture;
+} TestThreadedSubmit;
+
+static gpointer
+transfer_submit_all_retry(TestThreadedSubmit *data)
+{
+	for (guint i = 0; i < G_N_ELEMENTS(data->transfers); i++) {
+		while (libusb_submit_transfer(data->transfers[i]) < 0) {
+			assert_libusb_log_msg(data->fixture, LIBUSB_LOG_LEVEL_ERROR, "submit_bulk_transfer");
+			continue;
+		}
+
+		data->submitted += 1;
+	}
+
+	return NULL;
+}
+
+static void
+test_threaded_submit_transfer_cb(struct libusb_transfer *transfer)
+{
+	TestThreadedSubmit *data = transfer->user_data;
+
+	/* We should only be receiving packets in the main thread */
+	g_assert_cmpint (getpid(), ==, gettid());
+
+	/* Check that the transfer buffer has the expected value */
+	g_assert_cmpint (*(int*)transfer->buffer, ==, data->completed);
+	data->completed += 1;
+
+	if (data->completed == G_N_ELEMENTS(data->transfers))
+		data->done = TRUE;
+}
+
+static void
+test_threaded_submit(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	GThread *thread = NULL;
+	TestThreadedSubmit data = { .fixture = fixture };
+	UsbChat out_msg = {
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_IN,
+		  .buffer_length = sizeof(int),
+	};
+	UsbChat in_msg = {
+		  .reap = TRUE,
+		  .actual_length = 4,
+	};
+	UsbChat *c;
+	libusb_device_handle *handle = NULL;
+	int urb;
+
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	fixture->libusb_log_silence = TRUE;
+
+	c = fixture->chat = g_new0(UsbChat, G_N_ELEMENTS(data.transfers) * 2 + 1);
+	urb = 0;
+	for (int i = 0; i < THREADED_SUBMIT_URB_SETS; i++) {
+		for (int j = 0; j < THREADED_SUBMIT_URB_IN_FLIGHT; j++) {
+			c[i*2*THREADED_SUBMIT_URB_IN_FLIGHT + j] = out_msg;
+			c[i*2*THREADED_SUBMIT_URB_IN_FLIGHT + j].reaps = &c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j];
+			c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j] = in_msg;
+			c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j].buffer = (unsigned char*) g_new0(int, 1);
+			*(int*) c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j].buffer = urb;
+
+			data.transfers[urb] = libusb_alloc_transfer(0);
+			libusb_fill_bulk_transfer(data.transfers[urb],
+						  handle,
+						  LIBUSB_ENDPOINT_IN,
+						  g_malloc(out_msg.buffer_length),
+						  out_msg.buffer_length,
+						  test_threaded_submit_transfer_cb,
+						  &data,
+						  G_MAXUINT);
+			data.transfers[urb]->flags = LIBUSB_TRANSFER_FREE_BUFFER | LIBUSB_TRANSFER_FREE_TRANSFER;
+			urb++;
+		}
+	}
+
+	thread = g_thread_new("transfer all", (GThreadFunc) transfer_submit_all_retry, &data);
+
+	while (!data.done)
+		g_assert_cmpint(libusb_handle_events_completed(fixture->ctx, &data.done), ==, 0);
+
+	g_thread_join(thread);
+
+	fixture->libusb_log_silence = FALSE;
+	libusb_close(handle);
+
+	for (int i = 0; i < 2 * THREADED_SUBMIT_URB_SETS * THREADED_SUBMIT_URB_SETS; i++)
+		g_clear_pointer ((void**) &c->buffer, g_free);
+	g_free (c);
+}
+
+static int
+hotplug_count_arrival_cb(libusb_context *ctx,
+                         libusb_device  *device,
+                         libusb_hotplug_event event,
+                         void *user_data)
+{
+	g_assert_cmpint(event, ==, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
+
+	(void) ctx;
+	(void) device;
+
+	*(int*) user_data += 1;
+
+	return 0;
+}
+
+#ifdef UMOCKDEV_HOTPLUG
+static int
+hotplug_count_removal_cb(libusb_context *ctx,
+                         libusb_device  *device,
+                         libusb_hotplug_event event,
+                         void *user_data)
+{
+	g_assert_cmpint(event, ==, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT);
+
+	(void) ctx;
+	(void) device;
+
+	*(int*) user_data += 1;
+
+	return 0;
+}
+#endif
+
+static void
+test_hotplug_enumerate(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	libusb_hotplug_callback_handle handle_enumerate;
+	libusb_hotplug_callback_handle handle_no_enumerate;
+	int event_count_enumerate = 0;
+	int event_count_no_enumerate = 0;
+	struct timeval zero_tv = { 0 };
+	int r;
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+	                                     LIBUSB_HOTPLUG_ENUMERATE,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_arrival_cb,
+	                                     &event_count_enumerate,
+	                                     &handle_enumerate);
+	g_assert_cmpint(r, ==, 0);
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+	                                     0,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_arrival_cb,
+	                                     &event_count_no_enumerate,
+	                                     &handle_no_enumerate);
+	g_assert_cmpint(r, ==, 0);
+
+	g_assert_cmpint(event_count_enumerate, ==, 1);
+	g_assert_cmpint(event_count_no_enumerate, ==, 0);
+
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+
+	g_assert_cmpint(event_count_enumerate, ==, 1);
+	g_assert_cmpint(event_count_no_enumerate, ==, 0);
+
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_enumerate);
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_no_enumerate);
+
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+
+	g_assert_cmpint(event_count_enumerate, ==, 1);
+	g_assert_cmpint(event_count_no_enumerate, ==, 0);
+}
+
+static void
+test_hotplug_add_remove(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+#ifdef UMOCKDEV_HOTPLUG
+	libusb_device **devs = NULL;
+	libusb_hotplug_callback_handle handle_add;
+	libusb_hotplug_callback_handle handle_remove;
+	int event_count_add = 0;
+	int event_count_remove = 0;
+	struct timeval zero_tv = { 0 };
+	int r;
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
+	                                     LIBUSB_HOTPLUG_ENUMERATE,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_arrival_cb,
+	                                     &event_count_add,
+	                                     &handle_add);
+	g_assert_cmpint(r, ==, 0);
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+	                                     LIBUSB_HOTPLUG_ENUMERATE,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_removal_cb,
+	                                     &event_count_remove,
+	                                     &handle_remove);
+	g_assert_cmpint(r, ==, 0);
+
+	/* No device, even going into the mainloop will not call cb. */
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+	g_assert_cmpint(event_count_add, ==, 0);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	/* Add a device */
+	test_fixture_add_canon(fixture);
+
+	/* Either the thread has picked it up already, or we do so now. */
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, 1);
+	libusb_free_device_list(devs, TRUE);
+
+	/* The hotplug event is pending now, but has not yet fired. */
+	g_assert_cmpint(event_count_add, ==, 0);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	/* Fire hotplug event. */
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+	g_assert_cmpint(event_count_add, ==, 1);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	umockdev_testbed_uevent(fixture->testbed, "/sys/devices/usb1", "remove");
+	//umockdev_testbed_remove_device(fixture->testbed, "/devices/usb1");
+
+	/* Either the thread has picked it up already, or we do so now. */
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, 0);
+	libusb_free_device_list(devs, TRUE);
+
+	/* The hotplug event is pending now, but has not yet fired. */
+	g_assert_cmpint(event_count_add, ==, 1);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	/* Fire hotplug event. */
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+	g_assert_cmpint(event_count_add, ==, 1);
+	g_assert_cmpint(event_count_remove, ==, 1);
+
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_add);
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_remove);
+#else
+	(void) fixture;
+	g_test_skip("UMockdev is too old to test hotplug");
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add("/libusb/open-close", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_open_close,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/close-flying", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_close_flying,
+	           test_fixture_teardown);
+	g_test_add("/libusb/close-cancelled", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_close_cancelled,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/ctx-destroy", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_ctx_destroy,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/string-descriptor", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_get_string_descriptor,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/timeout", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_timeout,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/threaded-submit", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_threaded_submit,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/hotplug/enumerate", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_hotplug_enumerate,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/hotplug/add-remove", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_empty,
+	           test_hotplug_add_remove,
+	           test_fixture_teardown);
+
+	return g_test_run();
+}