[PATCH v2] Testbeds for libudev/gudev clients

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello again,

Martin Pitt [2012-07-06  7:01 +0200]:
>    I have a patch attached to generalize this to use
>    $UDEV_TEST_PREFIX, replacing the current compile-time TEST_PREFIX
>    macro. This will also allow us in the future to create mock dev and
>    run/rules.d files without having to change the environment variable
>    and temporary directory structure again. Once that (or a variant of
>    it) is in, I'll adapt upower's test suite to also set
>    UDEV_TEST_PREFIX so that it works with both old and new libudev
>    versions.

As the original patch that changed libudev to respect
$UDEV_TEST_PREFIX was not very palatable, I now changed the approach
to use an external LD_PRELOAD wrapper, as discussed with Lucas. This
is the "no privileges" and "do not do any permanent changes to the
system" variant of Kay's namespacing proposal, and does not touch the
existing code at all. I based libudev-testbed.so on kmod's path.c,
simplified it a bit, added some missing functions, and integrated it
into the build system together with a simple "libudev-testbed" wrapper
script that you need to run test suites under.

I actually like this approach, as it allows us to extend the
functionality later on. E. g. this can easily intercept access to
/dev/ and sysctls as well.

>    With that patch you can now e. g. call
> 
>      UDEV_TEST_PREFIX=test ./udevadm info --export-db
>      UDEV_TEST_PREFIX=test ./udevadm info --query=all --path=/devices/virtual/block/md0

This now becomes

  LD_LIBRARY_PATH=.libs UDEV_TEST_PREFIX=test src/test/libudev-testbed  ./udevadm info --export-db

The LD_LIBRARY_PATH is just for the build tree; when using the
installed version (e. g. upower's test suite), you would use

  UDEV_TEST_PREFIX=/path/to/testbed libudev-testbed /udevadm info --export-db

I also adjusted the GUdevTestbed patch a bit for the new approach.

TODOs that I am aware of which need to be fixed before committing:

 - libudev-testbed needs a manpage
 - GUdevTestbed needs to remove its temporary directory in the
   destructor
 - Write some documentation how to use this stuff (in
   libudev-testbed's manpage, and some general "how to write tests for
   hardware handling" wiki page on fd.o)

I'll do these once we have a general agreement about the approach in
these patches.

Thanks,

Martin

-- 
Martin Pitt                        | http://www.piware.de
Ubuntu Developer (www.ubuntu.com)  | Debian Developer  (www.debian.org)
From 695119a0a324de98d340ca956e92c60fba949048 Mon Sep 17 00:00:00 2001
From: Martin Pitt <martinpitt@xxxxxxxxx>
Date: Tue, 10 Jul 2012 08:48:34 +0200
Subject: [PATCH 1/2] Add libudev test bed wrapper

The removal of $SYSFS_PATH and configurable paths made it impossible for test
suites to set up a local sysfs tree and point libudev to that. Restore and
generalize the functionality with checking the $UDEV_TEST_PREFIX environment
variable, so that test suites can continue to run without root privileges and
the danger of breaking the system.

Add a small LD_PRELOAD library (based on path.c from kmod) to wrap the libc
functions that libudev needs for prefixing the paths with $UDEV_TEST_PREFIX.
Cover sysfs for now (which mostly needs open(), readlink(), and stat()), but
this approach is extensible for intercepting sysctls, device node access, and
similar things in the future as well.

Also add a small shell wrapper "libudev-testbed" which runs the argument under
the appropriate LD_PRELOAD. This is more robust for third party test suites and
also allows us to change the test bed implementation in the future. Run all
tests under this wrapper; it does not change behaviour if $UDEV_TEST_PREFIX is
not set, and will make future selftests of libudev/gudev using the new test bed
just work.
---
 Makefile.am                |   27 ++++++
 src/test/libudev-testbed   |    5 ++
 src/test/libudev-testbed.c |  214 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 246 insertions(+)
 create mode 100755 src/test/libudev-testbed
 create mode 100644 src/test/libudev-testbed.c

diff --git a/Makefile.am b/Makefile.am
index 70b8b09..e4b8166 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -104,6 +104,7 @@ check_PROGRAMS =
 check_DATA =
 noinst_PROGRAMS =
 TESTS =
+TESTS_ENVIRONMENT = LD_LIBRARY_PATH=.libs:$$LD_LIBRARY_PATH src/test/libudev-testbed
 udevlibexec_PROGRAMS =
 
 AM_CPPFLAGS = \
@@ -1508,6 +1509,32 @@ libudev_private_la_LIBADD = \
 	libsystemd-shared.la
 
 # ------------------------------------------------------------------------------
+
+lib_LTLIBRARIES += \
+	libudev-testbed.la
+
+libudev_testbed_la_SOURCES = \
+	src/test/libudev-testbed.c
+
+libudev_testbed_la_CFLAGS = \
+	$(AM_CFLAGS) \
+	-fvisibility=default
+
+libudev_testbed_la_LDFLAGS = \
+	$(AM_LDFLAGS)
+
+# this is an LD_PRELOAD library, so remove static library and libtool wrappers
+libudev-testbed-install-hook:
+	rm $(DESTDIR)$(libdir)/libudev-testbed.a
+	rm $(DESTDIR)$(libdir)/libudev-testbed.la
+
+INSTALL_EXEC_HOOKS += \
+	libudev-testbed-install-hook
+
+bin_SCRIPTS = \
+	src/test/libudev-testbed
+
+# ------------------------------------------------------------------------------
 MANPAGES += \
 	man/udev.7 \
 	man/udevadm.8 \
diff --git a/src/test/libudev-testbed b/src/test/libudev-testbed
new file mode 100755
index 0000000..44b8258
--- /dev/null
+++ b/src/test/libudev-testbed
@@ -0,0 +1,5 @@
+#!/bin/sh
+# Wrapper program to preload the libudev-testbed shared library, so that test
+# programs can set $UDEV_TEST_PREFIX for redirecting sysfs and other queries to
+# a test bed.
+env LD_PRELOAD=libudev-testbed.so.0:$LD_PRELOAD "$@"
diff --git a/src/test/libudev-testbed.c b/src/test/libudev-testbed.c
new file mode 100644
index 0000000..0ecd211
--- /dev/null
+++ b/src/test/libudev-testbed.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2012  ProFUSION embedded systems
+ * Portions Copyright (C) 2012  Canonical Ltd.
+ * Authors:
+ * Lucas De Marchi <lucas.demarchi@xxxxxxxxxxxxxx>
+ * Martin Pitt <martin.pitt@xxxxxxxxxx>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+static void *nextlib;
+
+static inline int need_trap(const char *path)
+{
+	return path != NULL && strncmp(path, "/sys/", 5) == 0;
+}
+
+static const char *trap_path(const char *path)
+{
+	static char buf[PATH_MAX * 2];
+	const char *prefix;
+	size_t path_len, prefix_len;
+
+	if (!need_trap(path))
+		return path;
+
+	prefix = getenv("UDEV_TEST_PREFIX");
+	if (prefix == NULL)
+		return path;
+
+	path_len = strlen(path);
+	prefix_len = strlen(prefix);
+
+	if (path_len + prefix_len >= sizeof(buf)) {
+		errno = ENAMETOOLONG;
+		return NULL;
+	}
+
+	strcpy(buf, prefix);
+	strcpy(buf + prefix_len, path);
+	return buf;
+}
+
+static void *get_libc_func(const char *f)
+{
+	void *fp;
+
+	if (nextlib == NULL) {
+#ifdef RTLD_NEXT
+		nextlib = RTLD_NEXT;
+#else
+		nextlib = dlopen("libc.so.6", RTLD_LAZY);
+#endif
+	}
+
+	fp = dlsym(nextlib, f);
+	assert(fp);
+
+	return fp;
+}
+
+FILE *fopen(const char *path, const char *mode)
+{
+	const char *p;
+	static FILE* (*_fopen)(const char *path, const char *mode);
+
+	_fopen = get_libc_func("fopen");
+
+	p = trap_path(path);
+	if (p == NULL)
+		return NULL;
+
+	return (*_fopen)(p, mode);
+}
+
+int open(const char *path, int flags, ...)
+{
+	const char *p;
+	static int (*_open)(const char *path, int flags, ...);
+
+	_open = get_libc_func("open");
+	p = trap_path(path);
+	if (p == NULL)
+		return -1;
+
+	if (flags & O_CREAT) {
+		mode_t mode;
+		va_list ap;
+
+		va_start(ap, flags);
+		mode = va_arg(ap, mode_t);
+		va_end(ap);
+		return _open(p, flags, mode);
+	}
+
+	return _open(p, flags);
+}
+
+int mkdir(const char *path, mode_t mode)
+{
+	const char *p;
+	static int (*_mkdir)(const char *path, mode_t mode);
+
+	_mkdir = get_libc_func("mkdir");
+	p = trap_path(path);
+	if (p == NULL)
+		return -1;
+
+	return _mkdir(p, mode);
+}
+
+/* stat() comes in umpteen different flavours, so define them with templates */
+#define WRAP_STAT(prefix, suffix) \
+int prefix ## stat ## suffix (const char *path, struct stat ## suffix *st) \
+{ \
+	const char *p;							\
+	static int (*_fn)(const char *path, struct stat ## suffix *buf);\
+	_fn = get_libc_func(#prefix "stat" #suffix);			\
+	p = trap_path(path);						\
+        /* printf("testbed wrapped " #prefix "stat" #suffix "(%s) -> %s\n", path, p);*/	\
+	if (p == NULL)							\
+		return -1;						\
+	return _fn(p, st);						\
+}
+
+WRAP_STAT(,);
+WRAP_STAT(,64);
+WRAP_STAT(l,);
+WRAP_STAT(l,64);
+
+#define WRAP_VERSTAT(prefix, suffix) \
+int prefix ## stat ## suffix (int ver, const char *path, struct stat ## suffix *st) \
+{ \
+	const char *p;								    \
+	static int (*_fn)(int ver, const char *path, struct stat ## suffix *buf);   \
+	_fn = get_libc_func(#prefix "stat" #suffix);				    \
+	p = trap_path(path);							    \
+        /* printf("testbed wrapped " #prefix "stat" #suffix "(%s) -> %s\n", path, p);*/	\
+	if (p == NULL)								    \
+		return -1;							    \
+	return _fn(ver, p, st);							    \
+}
+
+WRAP_VERSTAT(__x,);
+WRAP_VERSTAT(__x,64);
+WRAP_VERSTAT(__lx,);
+WRAP_VERSTAT(__lx,64);
+
+int access(const char *path, int mode)
+{
+	const char *p;
+	static int (*_access)(const char *path, int mode);
+
+	_access = get_libc_func("access");
+
+	p = trap_path(path);
+	if (p == NULL)
+		return -1;
+
+	return _access(p, mode);
+}
+
+DIR *opendir(const char *path)
+{
+	const char *p;
+	static DIR* (*_opendir)(const char *path);
+
+	_opendir = get_libc_func("opendir");
+
+	p = trap_path(path);
+	if (p == NULL)
+		return NULL;
+
+	return (*_opendir)(p);
+}
+
+ssize_t readlink(const char *path, char *buf, size_t bufsiz)
+{
+	const char *p;
+	static int (*_readlink)(const char *path, char *buf, size_t bufsiz);
+
+	_readlink = get_libc_func("readlink");
+
+	p = trap_path(path);
+	if (p == NULL)
+		return -1;
+
+	return _readlink(p, buf, bufsiz);
+}
-- 
1.7.10.4

From 2b76a9b27c730864eafd7e83848e04e39af4fa2a Mon Sep 17 00:00:00 2001
From: Martin Pitt <martinpitt@xxxxxxxxx>
Date: Fri, 6 Jul 2012 13:27:51 +0200
Subject: [PATCH 2/2] gudev: Add GUdevTestbed

The GUdevTestbed class is used to build a temporary sysfs file system. You can
add a number of devices including arbitrary sysfs attributes and udev
properties, and then run a gudev client in that test bed that is independent of
the actual hardware it is running on. With this you can simulate particular
hardware in virtual environments up to some degree (e. g. sysctls will fail).

Also add a test-gudev check which tests both GUDev itself and GUdevTestbed.
---
 .gitignore                    |    1 +
 Makefile.am                   |   25 ++-
 docs/gudev/gudev-docs.xml     |    1 +
 docs/gudev/gudev-sections.txt |   24 +++
 src/gudev/gudev.h             |    1 +
 src/gudev/gudevtestbed.c      |  456 +++++++++++++++++++++++++++++++++++++++++
 src/gudev/gudevtestbed.h      |  103 ++++++++++
 src/gudev/gudevtypes.h        |    1 +
 src/test/test-gudev.c         |  248 ++++++++++++++++++++++
 9 files changed, 859 insertions(+), 1 deletion(-)
 create mode 100644 src/gudev/gudevtestbed.c
 create mode 100644 src/gudev/gudevtestbed.h
 create mode 100644 src/test/test-gudev.c

diff --git a/.gitignore b/.gitignore
index 0732176..1109778 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,3 +123,4 @@ stamp-*
 /v4l_id
 /test-libudev
 /test-udev
+/test-gudev
diff --git a/Makefile.am b/Makefile.am
index e4b8166..8435592 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1829,6 +1829,7 @@ libgudev_include_HEADERS = \
 	src/gudev/gudevtypes.h \
 	src/gudev/gudevclient.h \
 	src/gudev/gudevdevice.h \
+	src/gudev/gudevtestbed.h \
 	src/gudev/gudevenumerator.h
 
 lib_LTLIBRARIES += libgudev-1.0.la
@@ -1853,6 +1854,8 @@ libgudev_1_0_la_SOURCES = \
 	src/gudev/gudevdevice.c \
 	src/gudev/gudevenumerator.h \
 	src/gudev/gudevenumerator.c \
+	src/gudev/gudevtestbed.h \
+	src/gudev/gudevtestbed.c \
 	src/gudev/gudevprivate.h
 
 nodist_libgudev_1_0_la_SOURCES = \
@@ -1947,7 +1950,10 @@ src_gudev_GUdev_1_0_gir_FILES = \
 	$(top_srcdir)/src/gudev/gudevenumerator.h \
 	$(top_srcdir)/src/gudev/gudevclient.c \
 	$(top_srcdir)/src/gudev/gudevdevice.c \
-	$(top_srcdir)/src/gudev/gudevenumerator.c
+	$(top_srcdir)/src/gudev/gudevenumerator.c \
+	$(top_srcdir)/src/gudev/gudevtestbed.h \
+	$(top_srcdir)/src/gudev/gudevtestbed.c \
+	$(NULL)
 
 INTROSPECTION_GIRS = src/gudev/GUdev-1.0.gir
 INTROSPECTION_SCANNER_ARGS = --c-include=gudev/gudev.h
@@ -1976,6 +1982,23 @@ libgudev-install-move-hook:
 libgudev-uninstall-move-hook:
 	rm -f $(DESTDIR)$(rootlibdir)/libgudev-1.0.so*
 
+noinst_PROGRAMS += \
+	test-gudev
+
+test_gudev_SOURCES = \
+	src/test/test-gudev.c
+
+test_gudev_CFLAGS = \
+	$(GLIB_CFLAGS)
+
+test_gudev_LDADD = \
+	libgudev-1.0.la \
+	$(GLIB_LIBS)
+
+TESTS += \
+	test-gudev
+
+
 INSTALL_EXEC_HOOKS += libgudev-install-move-hook
 UNINSTALL_EXEC_HOOKS += libgudev-uninstall-move-hook
 endif
diff --git a/docs/gudev/gudev-docs.xml b/docs/gudev/gudev-docs.xml
index 3e7e50a..43cc9cb 100644
--- a/docs/gudev/gudev-docs.xml
+++ b/docs/gudev/gudev-docs.xml
@@ -26,6 +26,7 @@
     <xi:include href="xml/gudevclient.xml"/>
     <xi:include href="xml/gudevdevice.xml"/>
     <xi:include href="xml/gudevenumerator.xml"/>
+    <xi:include href="xml/gudevtestbed.xml"/>
   </chapter>
 
   <chapter id="gudev-hierarchy">
diff --git a/docs/gudev/gudev-sections.txt b/docs/gudev/gudev-sections.txt
index b25c13b..ab2ccfb 100644
--- a/docs/gudev/gudev-sections.txt
+++ b/docs/gudev/gudev-sections.txt
@@ -98,3 +98,27 @@ G_UDEV_ENUMERATOR_GET_CLASS
 <SUBSECTION Private>
 GUdevEnumeratorPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gudevtestbed</FILE>
+<TITLE>GUdevTestbed</TITLE>
+GUdevTestbed
+GUdevTestbedClass
+g_udev_testbed_new
+g_udev_testbed_add_device
+g_udev_testbed_add_devicev
+g_udev_testbed_get_root_dir
+g_udev_testbed_get_sys_dir
+g_udev_testbed_set_attribute
+g_udev_testbed_set_property
+<SUBSECTION Standard>
+g_udev_testbed_get_type
+G_UDEV_IS_TESTBED
+G_UDEV_IS_TESTBED_CLASS
+G_UDEV_TESTBED
+G_UDEV_TESTBED_CLASS
+G_UDEV_TESTBED_GET_CLASS
+G_UDEV_TYPE_TESTBED
+<SUBSECTION Private>
+GUdevTestbedPrivate
+</SECTION>
diff --git a/src/gudev/gudev.h b/src/gudev/gudev.h
index 6ae01f2..a8f9102 100644
--- a/src/gudev/gudev.h
+++ b/src/gudev/gudev.h
@@ -28,6 +28,7 @@
 #include <gudev/gudevclient.h>
 #include <gudev/gudevdevice.h>
 #include <gudev/gudevenumerator.h>
+#include <gudev/gudevtestbed.h>
 #undef _GUDEV_INSIDE_GUDEV_H
 
 #endif /* __G_UDEV_H__ */
diff --git a/src/gudev/gudevtestbed.c b/src/gudev/gudevtestbed.c
new file mode 100644
index 0000000..6d732de
--- /dev/null
+++ b/src/gudev/gudevtestbed.c
@@ -0,0 +1,456 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Canonical Ltd.
+ * Author: Martin Pitt <martin.pitt@xxxxxxxxxx>
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "gudevtestbed.h"
+
+/**
+ * SECTION:gudevtestbed
+ * @short_description: Build an udev test bed for testing gudev based programs
+ *
+ * The #GUdevTestbed class is used to build a temporary sysfs file
+ * system. You can add a number of devices including arbitrary sysfs
+ * attributes and udev properties, and then run a libudev or gudev client in
+ * that test bed that is independent of the actual hardware it is running on.
+ * With this you can simulate particular hardware in virtual environments up to
+ * some degree (e. g. accessing the nodes in /dev/ and sysctls will fail).
+ *
+ * Instantiating a #GUdevTestbed object creates a temporary directory with a
+ * sysfs tree and sets the $UDEV_TEST_PREFIX environment variable so that
+ * subsequently started programs that use libudev will use the test bed instead
+ * of the system's real sysfs.
+ */
+
+struct _GUdevTestbedPrivate
+{
+  gchar *root_dir;
+  gchar *sys_dir;
+};
+
+G_DEFINE_TYPE (GUdevTestbed, g_udev_testbed, G_TYPE_OBJECT)
+
+static void
+g_udev_testbed_finalize (GObject *object)
+{
+  GUdevTestbed *testbed = G_UDEV_TESTBED (object);
+
+  /* TODO: rm -r root_dir */
+
+  g_debug ("Removing udev test bed %s", testbed->priv->root_dir);
+  g_unsetenv ("UDEV_TEST_PREFIX");
+
+  g_free (testbed->priv->root_dir);
+  g_free (testbed->priv->sys_dir);
+
+  if (G_OBJECT_CLASS (g_udev_testbed_parent_class)->finalize != NULL)
+    (* G_OBJECT_CLASS (g_udev_testbed_parent_class)->finalize) (object);
+}
+
+static void
+g_udev_testbed_class_init (GUdevTestbedClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->finalize = g_udev_testbed_finalize;
+
+  g_type_class_add_private (klass, sizeof (GUdevTestbedPrivate));
+}
+
+static void
+g_udev_testbed_init (GUdevTestbed *testbed)
+{
+  GError *error = NULL;
+
+  testbed->priv = G_TYPE_INSTANCE_GET_PRIVATE (testbed,
+                                               G_UDEV_TYPE_TESTBED,
+                                               GUdevTestbedPrivate);
+
+  testbed->priv->root_dir = g_dir_make_tmp ("udevtestbed.XXXXXX", &error);
+  g_assert_no_error (error);
+
+  testbed->priv->sys_dir = g_build_filename (testbed->priv->root_dir, "sys", NULL);
+  g_assert (g_mkdir (testbed->priv->sys_dir, 0755) == 0);
+
+  g_assert (g_setenv ("UDEV_TEST_PREFIX", testbed->priv->root_dir, TRUE));
+
+  g_debug ("Created udev test bed %s", testbed->priv->root_dir);
+}
+
+/**
+ * g_udev_testbed_new:
+ *
+ * Construct a #GUdevTestbed object with no devices. Use
+ * #g_udev_testbed_add_device to populate it. This automatically sets the
+ * UDEV_TEST_PREFIX environment variable so that subsequently started gudev
+ * clients will use the test bed.
+ *
+ * Returns: A new #GUdevTestbed object. Free with g_object_unref().
+ */
+GUdevTestbed *
+g_udev_testbed_new (void)
+{
+  return G_UDEV_TESTBED (g_object_new (G_UDEV_TYPE_TESTBED, NULL));
+}
+
+/**
+ * g_udev_testbed_get_root_dir:
+ * @testbed: A #GUdevTestbed.
+ *
+ * Gets the root directory for @testbed.
+ *
+ * Returns: (transfer none): The root directory for @testbed. Do not free or
+ * modify.
+ */
+const gchar *
+g_udev_testbed_get_root_dir (GUdevTestbed *testbed)
+{
+  g_return_val_if_fail (G_UDEV_IS_TESTBED (testbed), NULL);
+  return testbed->priv->root_dir;
+}
+
+/**
+ * g_udev_testbed_get_sys_dir:
+ * @testbed: A #GUdevTestbed.
+ *
+ * Gets the sysfs directory for @testbed.
+ *
+ * Returns: (transfer none): The sysfs directory for @testbed. Do not free or
+ * modify.
+ */
+const gchar *
+g_udev_testbed_get_sys_dir (GUdevTestbed *testbed)
+{
+  g_return_val_if_fail (G_UDEV_IS_TESTBED (testbed), NULL);
+  return testbed->priv->sys_dir;
+}
+
+/**
+ * uevent_from_property_list:
+ *
+ * Build the contents of an uevent file (with udev properties) from a property
+ * list.
+ */
+static gchar*
+uevent_from_property_list (const gchar** properties)
+{
+  GString *result;
+  const gchar *key, *value;
+
+  result = g_string_sized_new (1024);
+
+  while (*properties != NULL)
+    {
+      key = *properties;
+      ++properties;
+      if (*properties == NULL)
+        {
+          g_warning ("uevent_from_property_list: Ignoring key '%s' without value", key);
+          break;
+        }
+      value = *properties;
+      ++properties;
+      g_string_append (result, key);
+      g_string_append_c (result, '=');
+      g_string_append (result, value);
+      g_string_append_c (result, '\n');
+    }
+
+  return g_string_free (result, FALSE);
+}
+
+/**
+ * g_udev_testbed_add_devicev:
+ * @testbed: A #GUdevTestbed.
+ * @subsystem: The subsystem name, e. g. "usb"
+ * @name: The device name; arbitrary, but needs to be unique within the testbed
+ * @attributes: (transfer none): A list of device sysfs attributes, alternating
+ *              names and values, terminated with NULL:
+ *              { "key1", "value1", "key2", "value2", ..., NULL }
+ * @properties: (transfer none): A list of device udev properties; same format
+ *              as @attributes
+ *
+ * This method is mostly meant for language bindings (where it is named
+ * #g_udev_testbed_add_device). For C programs it is usually more convenient to
+ * use #g_udev_testbed_add_device.
+ *
+ * Add a new device to the @testbed. A Linux kernel device always has a
+ * subsystem (such as "usb" or "pci"), and a device name. The test bed only
+ * builds a very simple sysfs structure without nested namespaces, so it
+ * requires device names to be unique. Some gudev client programs might make
+ * assumptions about the name (e. g. a SCSI disk block device should be called
+ * sdaN). A device also has an arbitrary number of sysfs attributes and udev
+ * properties; usually you should specify them upon creation, but it is also
+ * possible to change them later on with #g_udev_testbed_set_attribute and
+ * #g_udev_testbed_set_property.
+ *
+ * Returns: (transfer full): The sysfs path for the newly created device. Free
+ *          with g_free().
+ *
+ * Rename to: g_udev_testbed_add_device
+ */
+gchar*
+g_udev_testbed_add_devicev (GUdevTestbed    *testbed,
+                            const gchar     *subsystem,
+                            const gchar     *name,
+                            const gchar    **attributes,
+                            const gchar    **properties)
+{
+  gchar *dev_path;
+  gchar *dev_dir;
+  gchar *class_dir;
+  gchar *target, *link;
+  const gchar *key, *value;
+  gchar *prop_str;
+
+  dev_path = g_build_filename ("/sys/devices", name, NULL);
+  dev_dir = g_build_filename (testbed->priv->root_dir, dev_path, NULL);
+
+  /* must not exist yet */
+  g_return_val_if_fail (!g_file_test (dev_dir, G_FILE_TEST_EXISTS), NULL);
+
+  /* create device and corresponding subsystem dir */
+  g_assert (g_mkdir_with_parents (dev_dir, 0755) == 0);
+  class_dir = g_build_filename (testbed->priv->sys_dir, "class", subsystem, NULL);
+  g_assert (g_mkdir_with_parents (class_dir, 0755) == 0);
+
+  /* subsystem symlink */
+  target = g_build_filename ("..", "..", "class", subsystem, NULL);
+  link = g_build_filename (dev_dir, "subsystem", NULL);
+  g_assert (symlink (target, link) == 0);
+  g_free (target);
+  g_free (link);
+
+  /* device symlink from class/ */
+  target = g_build_filename ("..", "..", "devices", name, NULL);
+  link = g_build_filename (class_dir, name, NULL);
+  g_assert (symlink (target, link) == 0);
+  g_free (target);
+  g_free (link);
+
+  g_free (class_dir);
+  g_free (dev_dir);
+
+  /* attributes */
+  while (*attributes != NULL)
+    {
+      key = *attributes;
+      ++attributes;
+      if (*attributes == NULL)
+        {
+          g_warning ("g_udev_testbed_add_devicev: Ignoring attribute key '%s' without value", key);
+          break;
+        }
+      value = *attributes;
+      ++attributes;
+      g_udev_testbed_set_attribute (testbed, dev_path, key, value);
+    }
+
+  /* properties; they go into the "uevent" sysfs attribute */
+  prop_str = uevent_from_property_list (properties);
+  g_udev_testbed_set_attribute (testbed, dev_path, "uevent", prop_str);
+  g_free (prop_str);
+
+  /* we want to return a realistic device path, not one starting with the the
+   * testbed prefix */
+  return dev_path;
+}
+
+/**
+ * g_udev_testbed_add_device: (skip)
+ * @testbed: A #GUdevTestbed.
+ * @subsystem: The subsystem name, e. g. "usb"
+ * @name: The device name; arbitrary, but needs to be unique within the testbed
+ * @...: Arbitrarily many pairs of sysfs attributes (alternating names and
+ *       values), terminated by NULL, followed by arbitrarily many pairs of udev
+ *       properties, terminated by another NULL.
+ *
+ * Add a new device to the @testbed. A Linux kernel device always has a
+ * subsystem (such as "usb" or "pci"), and a device name. The test bed only
+ * builds a very simple sysfs structure without nested namespaces, so it
+ * requires device names to be unique. Some gudev client programs might make
+ * assumptions about the name (e. g. a SCSI disk block device should be called
+ * sdaN). A device also has an arbitrary number of sysfs attributes and udev
+ * properties; usually you should specify them upon creation, but it is also
+ * possible to change them later on with #g_udev_testbed_set_attribute and
+ * #g_udev_testbed_set_property.
+ *
+ * Example:
+ *   |[
+ *   g_udev_testbed_add_device (testbed, "usb", "dev1",
+ *                              "idVendor", "0815", "idProduct", "AFFE", NULL,
+ *                              "ID_MODEL", "KoolGadget", NULL);
+ *   ]|
+ *
+ * Returns: (transfer full): The sysfs path for the newly created device. Free
+ *          with g_free().
+ */
+gchar*
+g_udev_testbed_add_device (GUdevTestbed    *testbed,
+                           const gchar     *subsystem,
+                           const gchar     *name,
+                           ...)
+{
+  va_list args;
+  int arg_set = 0; /* 0 -> attributes, 1 -> properties */
+  gchar *syspath;
+  const gchar *arg;
+  GArray *attributes;
+  GArray *properties;
+
+  attributes = g_array_new (TRUE, FALSE, sizeof (gchar*));
+  properties = g_array_new (TRUE, FALSE, sizeof (gchar*));
+
+  va_start (args, name);
+
+  for (;;) {
+    arg = va_arg (args, const gchar*);
+    /* we iterate arguments until NULL twice; first for the attributes, then
+     * for the properties */
+    if (arg == NULL)
+      {
+        if (++arg_set > 1)
+          break;
+        else
+          continue;
+      }
+
+    if (arg_set == 0)
+      g_array_append_val (attributes, arg);
+    else
+      g_array_append_val (properties, arg);
+  }
+
+  syspath = g_udev_testbed_add_devicev (testbed,
+                                        subsystem,
+                                        name,
+                                        (const gchar**) attributes->data,
+                                        (const gchar**) properties->data);
+
+  g_array_free (attributes, FALSE);
+  g_array_free (properties, FALSE);
+
+  va_end (args);
+
+  return syspath;
+}
+
+
+/**
+ * g_udev_testbed_set_attribute:
+ * @testbed: A #GUdevTestbed.
+ * @devpath: The full device path, as returned by #g_udev_testbed_add_device
+ * @name: The attribute name
+ * @value: The attribute value
+ *
+ * Set a sysfs attribute of a device.
+ */
+void
+g_udev_testbed_set_attribute (GUdevTestbed    *testbed,
+                              const gchar     *devpath,
+                              const gchar     *name,
+                              const gchar     *value)
+{
+  gchar *attr_path;
+  GError *error = NULL;
+
+  attr_path = g_build_filename (testbed->priv->root_dir, devpath, name, NULL);
+  g_file_set_contents (attr_path, value, -1, &error);
+  g_assert_no_error (error);
+  g_free (attr_path);
+}
+
+/**
+ * g_udev_testbed_set_property:
+ * @testbed: A #GUdevTestbed.
+ * @devpath: The full device path, as returned by #g_udev_testbed_add_device
+ * @name: The property name
+ * @value: The property value
+ *
+ * Set an udev property of a device.
+ */
+void
+g_udev_testbed_set_property (GUdevTestbed    *testbed,
+                             const gchar     *devpath,
+                             const gchar     *name,
+                             const gchar     *value)
+{
+  size_t name_len;
+  gchar *uevent_path;
+  gboolean existing = FALSE;
+  GString *props;
+  FILE *f;
+  char line[4096];
+
+  name_len = strlen (name);
+
+  /* read current properties from the uevent file; if name is already set,
+   * replace its value with the new one */
+  uevent_path = g_build_filename (testbed->priv->root_dir, devpath, "uevent", NULL);
+  f = fopen (uevent_path, "r");
+  g_assert (f != NULL);
+
+  props = g_string_sized_new (1024);
+  while (fgets (line, sizeof (line), f) != NULL)
+    {
+      if (g_str_has_prefix (line, name) && line[name_len] == '=')
+        {
+          existing = TRUE;
+          g_string_append (props, name);
+          g_string_append_c (props, '=');
+          g_string_append (props, value);
+          g_string_append_c (props, '\n');
+        }
+      else
+        {
+          g_string_append (props, line);
+        }
+    }
+  fclose (f);
+
+  /* if property name does not yet exist, append it */
+  if (!existing)
+    {
+      g_string_append (props, name);
+      g_string_append_c (props, '=');
+      g_string_append (props, value);
+      g_string_append_c (props, '\n');
+    }
+
+
+  /* write it back */
+  f = fopen (uevent_path, "w");
+  g_assert (f != NULL);
+  g_assert_cmpint (fwrite (props->str, sizeof (gchar), props->len, f), ==, props->len);
+  fclose (f);
+
+  g_string_free (props, TRUE);
+  g_free (uevent_path);
+}
diff --git a/src/gudev/gudevtestbed.h b/src/gudev/gudevtestbed.h
new file mode 100644
index 0000000..dc934dd
--- /dev/null
+++ b/src/gudev/gudevtestbed.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Canonical Ltd.
+ * Author: Martin Pitt <martin.pitt@xxxxxxxxxx>
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (_GUDEV_COMPILATION) && !defined(_GUDEV_INSIDE_GUDEV_H)
+#error "Only <gudev/gudev.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __G_UDEV_TESTBED_H__
+#define __G_UDEV_TESTBED_H__
+
+#include <gudev/gudevtypes.h>
+
+G_BEGIN_DECLS
+
+#define G_UDEV_TYPE_TESTBED         (g_udev_testbed_get_type ())
+#define G_UDEV_TESTBED(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_UDEV_TYPE_TESTBED, GUdevTestbed))
+#define G_UDEV_TESTBED_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_TESTBED, GUdevTestbedClass))
+#define G_UDEV_IS_TESTBED(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UDEV_TYPE_TESTBED))
+#define G_UDEV_IS_TESTBED_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_UDEV_TYPE_TESTBED))
+#define G_UDEV_TESTBED_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_UDEV_TYPE_TESTBED, GUdevTestbedClass))
+
+typedef struct _GUdevTestbedClass   GUdevTestbedClass;
+typedef struct _GUdevTestbedPrivate GUdevTestbedPrivate;
+
+/**
+ * GUdevTestbed:
+ * @parent: Parent object
+ *
+ * The #GUdevTestbed struct is opaque and should not be accessed directly.
+ */
+struct _GUdevTestbed
+{
+  GObject              parent;
+
+  /*< private >*/
+  GUdevTestbedPrivate *priv;
+};
+
+/**
+ * GUdevTestbedClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #GUdevTestbed.
+ */
+struct _GUdevTestbedClass
+{
+  GObjectClass   parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*reserved1) (void);
+  void (*reserved2) (void);
+  void (*reserved3) (void);
+  void (*reserved4) (void);
+  void (*reserved5) (void);
+  void (*reserved6) (void);
+  void (*reserved7) (void);
+  void (*reserved8) (void);
+};
+
+GType         g_udev_testbed_get_type         (void) G_GNUC_CONST;
+GUdevTestbed *g_udev_testbed_new              (void);
+const gchar  *g_udev_testbed_get_root_dir     (GUdevTestbed    *testbed);
+const gchar  *g_udev_testbed_get_sys_dir      (GUdevTestbed    *testbed);
+gchar        *g_udev_testbed_add_devicev      (GUdevTestbed    *testbed,
+                                               const gchar     *subsystem,
+                                               const gchar     *name,
+                                               const gchar    **attributes,
+                                               const gchar    **properties);
+gchar        *g_udev_testbed_add_device       (GUdevTestbed    *testbed,
+                                               const gchar     *subsystem,
+                                               const gchar     *name,
+                                               ...);
+void          g_udev_testbed_set_attribute    (GUdevTestbed    *testbed,
+                                               const gchar     *devpath,
+                                               const gchar     *name,
+                                               const gchar     *value);
+void          g_udev_testbed_set_property     (GUdevTestbed    *testbed,
+                                               const gchar     *devpath,
+                                               const gchar     *name,
+                                               const gchar     *value);
+
+G_END_DECLS
+
+#endif /* __G_UDEV_TESTBED_H__ */
diff --git a/src/gudev/gudevtypes.h b/src/gudev/gudevtypes.h
index 8884827..f97751e 100644
--- a/src/gudev/gudevtypes.h
+++ b/src/gudev/gudevtypes.h
@@ -33,6 +33,7 @@ G_BEGIN_DECLS
 typedef struct _GUdevClient GUdevClient;
 typedef struct _GUdevDevice GUdevDevice;
 typedef struct _GUdevEnumerator GUdevEnumerator;
+typedef struct _GUdevTestbed GUdevTestbed;
 
 /**
  * GUdevDeviceNumber:
diff --git a/src/test/test-gudev.c b/src/test/test-gudev.c
new file mode 100644
index 0000000..b38eaef
--- /dev/null
+++ b/src/test/test-gudev.c
@@ -0,0 +1,248 @@
+/*
+ * test-gudev
+ *
+ * Copyright (C) 2012 Canonical Ltd.
+ * Author: Martin Pitt <martin.pitt@xxxxxxxxxx>
+ *
+ * This library 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.
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <gudev/gudev.h>
+
+typedef struct {
+    GUdevTestbed *testbed;
+} GUdevTestbedFixture;
+
+static void
+gudev_testbed_fixture_setup (GUdevTestbedFixture *fixture, gconstpointer data)
+{
+  fixture->testbed = g_udev_testbed_new();
+  g_assert (fixture->testbed != NULL);
+}
+
+static void
+gudev_testbed_fixture_teardown (GUdevTestbedFixture *fixture, gconstpointer data)
+{
+  g_object_unref (fixture->testbed);
+}
+
+/* enumeration on the system picks up some devices */
+static void
+gudev_system_enumerate (void)
+{
+  GUdevClient *client;
+  GUdevEnumerator *enumerator;
+  GList *result;
+  GUdevDevice *device;
+
+  client = g_udev_client_new (NULL);
+  g_assert (client);
+
+  /* there ought to be at least one device on every system; e. g. a CPU is
+   * always handy to have */
+  enumerator = g_udev_enumerator_new (client);
+  g_assert (enumerator);
+  result = g_udev_enumerator_execute (enumerator);
+  g_assert_cmpuint (g_list_length (result), >, 0);
+
+  /* check that the entry is an useful GUdevDevice */
+  device = G_UDEV_DEVICE (result->data);
+  g_assert (device);
+  g_assert_cmpstr (g_udev_device_get_name (device), !=, "");
+  g_assert (strstr (g_udev_device_get_sysfs_path (device), "/sys/") != NULL);
+
+  g_list_free_full (result, g_object_unref);
+  g_object_unref (enumerator);
+  g_object_unref (client);
+}
+
+/* Empty GUdevTestbed without any devices */
+static void
+gudev_testbed_empty (GUdevTestbedFixture *fixture, gconstpointer data)
+{
+  GUdevClient *client;
+  GUdevEnumerator *enumerator;
+  GList *result;
+
+  client = g_udev_client_new (NULL);
+  g_assert (client);
+
+  enumerator = g_udev_enumerator_new (client);
+  g_assert (enumerator);
+  result = g_udev_enumerator_execute (enumerator);
+  g_assert_cmpuint (g_list_length (result), ==, 0);
+
+  g_object_unref (enumerator);
+  g_object_unref (client);
+}
+
+/* common checks for gudev_testbed_add_device{,v}() */
+static void
+_gudev_testbed_check_extkeyboard1 (const gchar* syspath)
+{
+  GUdevClient *client;
+  GUdevEnumerator *enumerator;
+  GList *result;
+  GUdevDevice *device;
+  client = g_udev_client_new (NULL);
+  g_assert (client);
+
+  enumerator = g_udev_enumerator_new (client);
+  g_assert (enumerator);
+  result = g_udev_enumerator_execute (enumerator);
+  g_assert_cmpuint (g_list_length (result), ==, 1);
+
+  /* check that the entry matches what we put into our test bed */
+  device = G_UDEV_DEVICE (result->data);
+  g_assert (device);
+  g_assert_cmpstr (g_udev_device_get_name (device), ==, "extkeyboard1");
+  g_assert_cmpstr (g_udev_device_get_sysfs_path (device), ==, syspath);
+
+  g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idVendor"), ==, "0815");
+  g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idProduct"), ==, "AFFE");
+  g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "noSuchAttr"), ==, NULL);
+
+  g_assert_cmpstr (g_udev_device_get_property (device, "DEVPATH"), ==, "/devices/extkeyboard1");
+  g_assert_cmpstr (g_udev_device_get_property (device, "SUBSYSTEM"), ==, "usb");
+  g_assert_cmpstr (g_udev_device_get_property (device, "ID_INPUT"), ==, "1");
+  g_assert_cmpstr (g_udev_device_get_property (device, "ID_INPUT_KEYBOARD"), ==, "1");
+  g_assert_cmpstr (g_udev_device_get_property (device, "NO_SUCH_PROP"), ==, NULL);
+
+  g_list_free_full (result, g_object_unref);
+  g_object_unref (enumerator);
+  g_object_unref (client);
+}
+
+/* GUdevTestbed add_devicev() with adding one device */
+static void
+gudev_testbed_add_devicev (GUdevTestbedFixture *fixture, gconstpointer data)
+{
+  gchar *syspath;
+  const gchar *attributes[] = { "idVendor", "0815", "idProduct", "AFFE", NULL };
+  const gchar *properties[] = { "ID_INPUT", "1", "ID_INPUT_KEYBOARD", "1", NULL };
+
+  syspath = g_udev_testbed_add_devicev (fixture->testbed,
+                                        "usb",
+                                        "extkeyboard1",
+                                        attributes,
+                                        properties);
+  g_assert (syspath);
+  g_assert (g_str_has_suffix (syspath, "/sys/devices/extkeyboard1"));
+
+  _gudev_testbed_check_extkeyboard1(syspath);
+  g_free (syspath);
+}
+
+/* GUdevTestbed add_device() with adding one device */
+static void
+gudev_testbed_add_device (GUdevTestbedFixture *fixture, gconstpointer data)
+{
+  gchar *syspath;
+
+  syspath = g_udev_testbed_add_device (fixture->testbed,
+                                       "usb",
+                                       "extkeyboard1",
+                                       /* attributes */
+                                       "idVendor", "0815", "idProduct", "AFFE", NULL,
+                                       /* properties */
+                                       "ID_INPUT", "1", "ID_INPUT_KEYBOARD", "1", NULL);
+  g_assert (syspath);
+  g_assert (g_str_has_suffix (syspath, "/sys/devices/extkeyboard1"));
+
+  _gudev_testbed_check_extkeyboard1(syspath);
+  g_free (syspath);
+}
+
+static void
+gudev_testbed_set_attribute (GUdevTestbedFixture *fixture, gconstpointer data)
+{
+  GUdevClient *client;
+  GUdevDevice *device;
+  gchar *syspath;
+
+  client = g_udev_client_new (NULL);
+
+  syspath = g_udev_testbed_add_device (fixture->testbed,
+                                       "usb",
+                                       "extkeyboard1",
+                                       /* attributes */
+                                       "idVendor", "0815", "idProduct", "AFFE", NULL,
+                                       /* properties */
+                                       NULL);
+
+  /* change an existing attribute */
+  g_udev_testbed_set_attribute (fixture->testbed, syspath, "idProduct", "BEEF");
+  /* add a new one */
+  g_udev_testbed_set_attribute (fixture->testbed, syspath, "color", "yellow");
+
+  device = g_udev_client_query_by_sysfs_path (client, syspath);
+  g_assert (device);
+  g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idVendor"), ==, "0815");
+  g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idProduct"), ==, "BEEF");
+  g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "color"), ==, "yellow");
+  g_object_unref (device);
+
+  g_object_unref (client);
+  g_free (syspath);
+}
+
+static void
+gudev_testbed_set_property (GUdevTestbedFixture *fixture, gconstpointer data)
+{
+  GUdevClient *client;
+  GUdevDevice *device;
+  gchar *syspath;
+
+  client = g_udev_client_new (NULL);
+
+  syspath = g_udev_testbed_add_device (fixture->testbed,
+                                       "usb",
+                                       "extkeyboard1",
+                                       /* attributes */
+                                       NULL,
+                                       /* properties */
+                                       "ID_INPUT", "1", NULL);
+
+  /* change an existing property */
+  g_udev_testbed_set_property (fixture->testbed, syspath, "ID_INPUT", "0");
+  /* add a new one */
+  g_udev_testbed_set_property (fixture->testbed, syspath, "ID_COLOR", "green");
+
+  device = g_udev_client_query_by_sysfs_path (client, syspath);
+  g_assert (device);
+  g_assert_cmpstr (g_udev_device_get_property (device, "ID_INPUT"), ==, "0");
+  g_assert_cmpstr (g_udev_device_get_property (device, "ID_COLOR"), ==, "green");
+  g_object_unref (device);
+
+  g_object_unref (client);
+  g_free (syspath);
+}
+
+int
+main (int argc, char **argv)
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* tests on system */
+  g_test_add_func ("/gudev-system/enumerate", gudev_system_enumerate);
+
+  /* tests with GUdevTestbed */
+  g_test_add ("/gudev-testbed/empty", GUdevTestbedFixture, NULL, gudev_testbed_fixture_setup,
+              gudev_testbed_empty, gudev_testbed_fixture_teardown);
+  g_test_add ("/gudev-testbed/add_devicev", GUdevTestbedFixture, NULL, gudev_testbed_fixture_setup,
+              gudev_testbed_add_devicev, gudev_testbed_fixture_teardown);
+  g_test_add ("/gudev-testbed/add_device", GUdevTestbedFixture, NULL, gudev_testbed_fixture_setup,
+              gudev_testbed_add_device, gudev_testbed_fixture_teardown);
+  g_test_add ("/gudev-testbed/set_attribute", GUdevTestbedFixture, NULL, gudev_testbed_fixture_setup,
+              gudev_testbed_set_attribute, gudev_testbed_fixture_teardown);
+  g_test_add ("/gudev-testbed/set_property", GUdevTestbedFixture, NULL, gudev_testbed_fixture_setup,
+              gudev_testbed_set_property, gudev_testbed_fixture_teardown);
+
+  return g_test_run ();
+}
-- 
1.7.10.4

Attachment: signature.asc
Description: Digital signature


[Index of Archives]     [Linux Kernel]     [Linux DVB]     [Asterisk Internet PBX]     [DCCP]     [Netdev]     [X.org]     [Util Linux NG]     [Fedora Women]     [ALSA Devel]     [Linux USB]

  Powered by Linux