From mboxrd@z Thu Jan 1 00:00:00 1970 From: Martin Pitt Date: Mon, 09 Jul 2012 07:25:21 +0000 Subject: Re: [PATCH] Testbeds for libudev/gudev clients Message-Id: <20120709072521.GD3261@piware.de> MIME-Version: 1 Content-Type: multipart/mixed; boundary="H1spWtNR+x+ondvy" List-Id: References: <20120706050131.GC3109@piware.de> In-Reply-To: <20120706050131.GC3109@piware.de> To: linux-hotplug@vger.kernel.org --H1spWtNR+x+ondvy Content-Type: multipart/mixed; boundary="y0ulUmNC+osPPQO6" Content-Disposition: inline --y0ulUmNC+osPPQO6 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable Martin Pitt [2012-07-06 7:01 +0200]: > * Copy&paste code is obviously bad. It would be much better if the > convenience API to set up a libudev/gudev test bed would be > provided by libudev/gudev itself. Also, it should be in C and > available through introspection, so that you can use it from a > variety of languages; this means it should be in gudev. >=20 > I am currently working on a first patch for this, and will post it > for discussion here when I have something working. I have that working now. The remaining TODO is to remove the temporary tree again in the destructor (if only GLib had a function for that..) This provides a simple to use API for building a sandbox with mock devices, including a new automatic test (covering the sandbox as well as parts of gudev itself), and gtk-doc. What do you think about that? Thanks, Martin --=20 Martin Pitt | http://www.piware.de Ubuntu Developer (www.ubuntu.com) | Debian Developer (www.debian.org) --y0ulUmNC+osPPQO6 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-gudev-Add-GUdevTestbed.patch" Content-Transfer-Encoding: quoted-printable =46rom d8e3de2529b956ac3fc78640ca179fde6e909150 Mon Sep 17 00:00:00 2001 =46rom: Martin Pitt 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 independen= t 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 | 448 +++++++++++++++++++++++++++++++++++++= ++++ src/gudev/gudevtestbed.h | 103 ++++++++++ src/gudev/gudevtypes.h | 1 + src/test/test-gudev.c | 248 +++++++++++++++++++++++ 9 files changed, 851 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 f05c945..3bb74b1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1802,6 +1802,7 @@ libgudev_include_HEADERS =3D \ src/gudev/gudevtypes.h \ src/gudev/gudevclient.h \ src/gudev/gudevdevice.h \ + src/gudev/gudevtestbed.h \ src/gudev/gudevenumerator.h =20 lib_LTLIBRARIES +=3D libgudev-1.0.la @@ -1826,6 +1827,8 @@ libgudev_1_0_la_SOURCES =3D \ src/gudev/gudevdevice.c \ src/gudev/gudevenumerator.h \ src/gudev/gudevenumerator.c \ + src/gudev/gudevtestbed.h \ + src/gudev/gudevtestbed.c \ src/gudev/gudevprivate.h =20 nodist_libgudev_1_0_la_SOURCES =3D \ @@ -1920,7 +1923,10 @@ src_gudev_GUdev_1_0_gir_FILES =3D \ $(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) =20 INTROSPECTION_GIRS =3D src/gudev/GUdev-1.0.gir INTROSPECTION_SCANNER_ARGS =3D --c-include=3Dgudev/gudev.h @@ -1949,6 +1955,23 @@ libgudev-install-move-hook: libgudev-uninstall-move-hook: rm -f $(DESTDIR)$(rootlibdir)/libgudev-1.0.so* =20 +noinst_PROGRAMS +=3D \ + test-gudev + +test_gudev_SOURCES =3D \ + src/test/test-gudev.c + +test_gudev_CFLAGS =3D \ + $(GLIB_CFLAGS) + +test_gudev_LDADD =3D \ + libgudev-1.0.la \ + $(GLIB_LIBS) + +TESTS +=3D \ + test-gudev + + INSTALL_EXEC_HOOKS +=3D libgudev-install-move-hook UNINSTALL_EXEC_HOOKS +=3D 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 @@ + =20 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 GUdevEnumeratorPrivate + +
+gudevtestbed +GUdevTestbed +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 + +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 + +GUdevTestbedPrivate +
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 #include #include +#include #undef _GUDEV_INSIDE_GUDEV_H =20 #endif /* __G_UDEV_H__ */ diff --git a/src/gudev/gudevtestbed.c b/src/gudev/gudevtestbed.c new file mode 100644 index 0000000..04cdbbb --- /dev/null +++ b/src/gudev/gudevtestbed.c @@ -0,0 +1,448 @@ +/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2012 Canonical Ltd. + * Author: Martin Pitt + * + * 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 +#include +#include +#include +#include + +#include "gudevtestbed.h" + +/** + * SECTION:gudevtestbed + * @short_description: Build an udev test bed for testing gudev based prog= rams + * + * 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 in= stead + * 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 =3D 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 !=3D NULL) + (* G_OBJECT_CLASS (g_udev_testbed_parent_class)->finalize) (object); +} + +static void +g_udev_testbed_class_init (GUdevTestbedClass *klass) +{ + GObjectClass *gobject_class =3D (GObjectClass *) klass; + + gobject_class->finalize =3D g_udev_testbed_finalize; + + g_type_class_add_private (klass, sizeof (GUdevTestbedPrivate)); +} + +static void +g_udev_testbed_init (GUdevTestbed *testbed) +{ + GError *error =3D NULL; + + testbed->priv =3D G_TYPE_INSTANCE_GET_PRIVATE (testbed, + G_UDEV_TYPE_TESTBED, + GUdevTestbedPrivate); + + testbed->priv->root_dir =3D g_dir_make_tmp ("udevtestbed.XXXXXX", &error= ); + g_assert_no_error (error); + + testbed->priv->sys_dir =3D g_build_filename (testbed->priv->root_dir, "s= ys", NULL); + g_assert (g_mkdir (testbed->priv->sys_dir, 0755) =3D=3D 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 prop= erty + * list. + */ +static gchar* +uevent_from_property_list (const gchar** properties) +{ + GString *result; + const gchar *key, *value; + + result =3D g_string_sized_new (1024); + + while (*properties !=3D NULL) + { + key =3D *properties; + ++properties; + if (*properties =3D=3D NULL) + { + g_warning ("uevent_from_property_list: Ignoring key '%s' without= value", key); + break; + } + value =3D *properties; + ++properties; + g_string_append (result, key); + g_string_append_c (result, '=3D'); + 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 te= stbed + * @attributes: (transfer none): A list of device sysfs attributes, altern= ating + * names and values, terminated with NULL: + * { "key1", "value1", "key2", "value2", ..., NULL } + * @properties: (transfer none): A list of device udev properties; same fo= rmat + * 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 convenie= nt 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 ma= ke + * assumptions about the name (e. g. a SCSI disk block device should be ca= lled + * sdaN). A device also has an arbitrary number of sysfs attributes and ud= ev + * properties; usually you should specify them upon creation, but it is al= so + * 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_dir; + gchar *class_dir; + gchar *target, *link; + const gchar *key, *value; + gchar *prop_str; + + dev_dir =3D g_build_filename (testbed->priv->sys_dir, "devices", name, N= ULL); + /* 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) =3D=3D 0); + class_dir =3D g_build_filename (testbed->priv->sys_dir, "class", subsyst= em, NULL); + g_assert (g_mkdir_with_parents (class_dir, 0755) =3D=3D 0); + + /* subsystem symlink */ + target =3D g_build_filename ("..", "..", "class", subsystem, NULL); + link =3D g_build_filename (dev_dir, "subsystem", NULL); + g_assert (symlink (target, link) =3D=3D 0); + g_free (target); + g_free (link); + + /* device symlink from class/ */ + target =3D g_build_filename ("..", "..", "devices", name, NULL); + link =3D g_build_filename (class_dir, name, NULL); + g_assert (symlink (target, link) =3D=3D 0); + g_free (target); + g_free (link); + + g_free (class_dir); + + /* attributes */ + while (*attributes !=3D NULL) + { + key =3D *attributes; + ++attributes; + if (*attributes =3D=3D NULL) + { + g_warning ("g_udev_testbed_add_devicev: Ignoring attribute key '= %s' without value", key); + break; + } + value =3D *attributes; + ++attributes; + g_udev_testbed_set_attribute (testbed, dev_dir, key, value); + } + + /* properties; they go into the "uevent" sysfs attribute */ + prop_str =3D uevent_from_property_list (properties); + g_udev_testbed_set_attribute (testbed, dev_dir, "uevent", prop_str); + g_free (prop_str); + + return dev_dir; +} + +/** + * 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 te= stbed + * @...: Arbitrarily many pairs of sysfs attributes (alternating names and + * values), terminated by NULL, followed by arbitrarily many pairs o= f 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 ma= ke + * assumptions about the name (e. g. a SCSI disk block device should be ca= lled + * sdaN). A device also has an arbitrary number of sysfs attributes and ud= ev + * properties; usually you should specify them upon creation, but it is al= so + * 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", N= ULL, + * "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 =3D 0; /* 0 -> attributes, 1 -> properties */ + gchar *syspath; + const gchar *arg; + GArray *attributes; + GArray *properties; + + attributes =3D g_array_new (TRUE, FALSE, sizeof (gchar*)); + properties =3D g_array_new (TRUE, FALSE, sizeof (gchar*)); + + va_start (args, name); + + for (;;) { + arg =3D va_arg (args, const gchar*); + /* we iterate arguments until NULL twice; first for the attributes, th= en + * for the properties */ + if (arg =3D=3D NULL) + { + if (++arg_set > 1) + break; + else + continue; + } + + if (arg_set =3D=3D 0) + g_array_append_val (attributes, arg); + else + g_array_append_val (properties, arg); + } + + syspath =3D 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 (including the sys dir itself) + * @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; + + attr_path =3D g_build_filename (devpath, name, NULL); + g_assert (g_file_set_contents (attr_path, value, -1, NULL)); + g_free (attr_path); +} + +/** + * g_udev_testbed_set_property: + * @testbed: A #GUdevTestbed. + * @devpath: The full device path (including the sys dir itself) + * @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 =3D FALSE; + GString *props; + FILE *f; + char line[4096]; + + name_len =3D strlen (name); + + /* read current properties from the uevent file; if name is already set, + * replace its value with the new one */ + uevent_path =3D g_build_filename (devpath, "uevent", NULL); + f =3D fopen (uevent_path, "r"); + g_assert (f !=3D NULL); + + props =3D g_string_sized_new (1024); + while (fgets (line, sizeof (line), f) !=3D NULL) + { + if (g_str_has_prefix (line, name) && line[name_len] =3D=3D '=3D') + { + existing =3D TRUE; + g_string_append (props, name); + g_string_append_c (props, '=3D'); + 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, '=3D'); + g_string_append (props, value); + g_string_append_c (props, '\n'); + } + + + /* write it back */ + f =3D fopen (uevent_path, "w"); + g_assert (f !=3D NULL); + g_assert_cmpint (fwrite (props->str, sizeof (gchar), props->len, f), =3D= =3D, 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 + * + * 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 can be included directly, this file may disap= pear or change contents." +#endif + +#ifndef __G_UDEV_TESTBED_H__ +#define __G_UDEV_TESTBED_H__ + +#include + +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_UD= EV_TYPE_TESTBED, GUdevTestbed)) +#define G_UDEV_TESTBED_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_T= YPE_TESTBED, GUdevTestbedClass)) +#define G_UDEV_IS_TESTBED(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UD= EV_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_UDE= V_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; =20 /** * 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 + * + * 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 +#include +#include + +typedef struct { + GUdevTestbed *testbed; +} GUdevTestbedFixture; + +static void +gudev_testbed_fixture_setup (GUdevTestbedFixture *fixture, gconstpointer d= ata) +{ + fixture->testbed =3D g_udev_testbed_new(); + g_assert (fixture->testbed !=3D NULL); +} + +static void +gudev_testbed_fixture_teardown (GUdevTestbedFixture *fixture, gconstpointe= r 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 =3D 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 =3D g_udev_enumerator_new (client); + g_assert (enumerator); + result =3D g_udev_enumerator_execute (enumerator); + g_assert_cmpuint (g_list_length (result), >, 0); + + /* check that the entry is an useful GUdevDevice */ + device =3D G_UDEV_DEVICE (result->data); + g_assert (device); + g_assert_cmpstr (g_udev_device_get_name (device), !=3D, ""); + g_assert (strstr (g_udev_device_get_sysfs_path (device), "/sys/") !=3D N= ULL); + + 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 =3D g_udev_client_new (NULL); + g_assert (client); + + enumerator =3D g_udev_enumerator_new (client); + g_assert (enumerator); + result =3D g_udev_enumerator_execute (enumerator); + g_assert_cmpuint (g_list_length (result), =3D=3D, 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 =3D g_udev_client_new (NULL); + g_assert (client); + + enumerator =3D g_udev_enumerator_new (client); + g_assert (enumerator); + result =3D g_udev_enumerator_execute (enumerator); + g_assert_cmpuint (g_list_length (result), =3D=3D, 1); + + /* check that the entry matches what we put into our test bed */ + device =3D G_UDEV_DEVICE (result->data); + g_assert (device); + g_assert_cmpstr (g_udev_device_get_name (device), =3D=3D, "extkeyboard1"= ); + g_assert_cmpstr (g_udev_device_get_sysfs_path (device), =3D=3D, syspath); + + g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idVendor"), =3D= =3D, "0815"); + g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idProduct"), =3D= =3D, "AFFE"); + g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "noSuchAttr"), = =3D=3D, NULL); + + g_assert_cmpstr (g_udev_device_get_property (device, "DEVPATH"), =3D=3D,= "/devices/extkeyboard1"); + g_assert_cmpstr (g_udev_device_get_property (device, "SUBSYSTEM"), =3D= =3D, "usb"); + g_assert_cmpstr (g_udev_device_get_property (device, "ID_INPUT"), =3D=3D= , "1"); + g_assert_cmpstr (g_udev_device_get_property (device, "ID_INPUT_KEYBOARD"= ), =3D=3D, "1"); + g_assert_cmpstr (g_udev_device_get_property (device, "NO_SUCH_PROP"), = =3D=3D, 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 dat= a) +{ + gchar *syspath; + const gchar *attributes[] =3D { "idVendor", "0815", "idProduct", "AFFE",= NULL }; + const gchar *properties[] =3D { "ID_INPUT", "1", "ID_INPUT_KEYBOARD", "1= ", NULL }; + + syspath =3D 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 =3D g_udev_testbed_add_device (fixture->testbed, + "usb", + "extkeyboard1", + /* attributes */ + "idVendor", "0815", "idProduct", "A= FFE", 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 d= ata) +{ + GUdevClient *client; + GUdevDevice *device; + gchar *syspath; + + client =3D g_udev_client_new (NULL); + + syspath =3D g_udev_testbed_add_device (fixture->testbed, + "usb", + "extkeyboard1", + /* attributes */ + "idVendor", "0815", "idProduct", "A= FFE", NULL, + /* properties */ + NULL); + + /* change an existing attribute */ + g_udev_testbed_set_attribute (fixture->testbed, syspath, "idProduct", "B= EEF"); + /* add a new one */ + g_udev_testbed_set_attribute (fixture->testbed, syspath, "color", "yello= w"); + + device =3D g_udev_client_query_by_sysfs_path (client, syspath); + g_assert (device); + g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idVendor"), =3D= =3D, "0815"); + g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "idProduct"), =3D= =3D, "BEEF"); + g_assert_cmpstr (g_udev_device_get_sysfs_attr (device, "color"), =3D=3D,= "yellow"); + g_object_unref (device); + + g_object_unref (client); + g_free (syspath); +} + +static void +gudev_testbed_set_property (GUdevTestbedFixture *fixture, gconstpointer da= ta) +{ + GUdevClient *client; + GUdevDevice *device; + gchar *syspath; + + client =3D g_udev_client_new (NULL); + + syspath =3D 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", "gre= en"); + + device =3D g_udev_client_query_by_sysfs_path (client, syspath); + g_assert (device); + g_assert_cmpstr (g_udev_device_get_property (device, "ID_INPUT"), =3D=3D= , "0"); + g_assert_cmpstr (g_udev_device_get_property (device, "ID_COLOR"), =3D=3D= , "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_tes= tbed_fixture_setup, + gudev_testbed_empty, gudev_testbed_fixture_teardown); + g_test_add ("/gudev-testbed/add_devicev", GUdevTestbedFixture, NULL, gud= ev_testbed_fixture_setup, + gudev_testbed_add_devicev, gudev_testbed_fixture_teardown); + g_test_add ("/gudev-testbed/add_device", GUdevTestbedFixture, NULL, gude= v_testbed_fixture_setup, + gudev_testbed_add_device, gudev_testbed_fixture_teardown); + g_test_add ("/gudev-testbed/set_attribute", GUdevTestbedFixture, NULL, g= udev_testbed_fixture_setup, + gudev_testbed_set_attribute, gudev_testbed_fixture_teardown); + g_test_add ("/gudev-testbed/set_property", GUdevTestbedFixture, NULL, gu= dev_testbed_fixture_setup, + gudev_testbed_set_property, gudev_testbed_fixture_teardown); + + return g_test_run (); +} --=20 1.7.10.4 --y0ulUmNC+osPPQO6-- --H1spWtNR+x+ondvy Content-Type: application/pgp-signature; name="signature.asc" Content-Description: Digital signature -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQIcBAEBCAAGBQJP+odhAAoJEPmIJawmtHufEysP/1PVYXn8baJpOkIIrsJXsSRt BK1+xGmQrFSNQf8koXbBU1A9ix55G79hqIjcCB1MlEl8SH2ZEuYjWSr5gzGhMNEE UuoaWsAnR5uEKhtiiLTSbOGY/ef7Vw7KN3TQ4QEcToVy4csXFD8Of9c3K/mvrtLH q72QHKnWWVzpm1ICsTjEKQRAeFl+FiXNLwQGLnc2MlbkmNq1lBNHJs+qEqSng7Zf NLTw7m2IhkZKxKaktAv4D26CaXbEwo3d7clfFv7PAHlsBMSQjEmK711lzt5731+G NQg0cRuZyO66RA2/ISnoovMw0lJMyoqvkl2UHGjxwU6MWbUn79BJz9JplNPO3OWx weS2MSc/Qh6LdaZ9xxngft/xKVoxtqDtmZH8HJm+02G/ielngfBSW1Ai97Jr6JUq q8+QDACtegyBa2rVC1rNhcnFKAnMKNhUdyl+vP2QsU7RfP/eGakMTaRsdeNeE8o3 AJtFZ4arg+DWRU2nZ6i33WIOBIHfMkFijAwByH+q9TZl/3/FY4azcJfE+7NUvFL8 oRRcEEuuYOy12fvUfBKu4wFIE8k4J9SFWuLA68qI39JBwp05TLxkrIQHuq/Bp0z0 hv6rnt3XwHJbi2D0d4IoG4HyHvATi+M9mTn4Idji05fJDhyfvSq+bLqECqWaRnmf sBWvMs1yjKDN+ucblIbb =MlT1 -----END PGP SIGNATURE----- --H1spWtNR+x+ondvy--