* [PATCH] Testbeds for libudev/gudev clients
@ 2012-07-06 5:01 Martin Pitt
2012-07-09 7:25 ` Martin Pitt
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Martin Pitt @ 2012-07-06 5:01 UTC (permalink / raw)
To: linux-hotplug
[-- Attachment #1.1: Type: text/plain, Size: 3789 bytes --]
Hello friends of udev,
I would like to make it vastly easier, and for some aspects possible
at all, to write automatic tests for libudev/gudev client software.
For example, upower's test suite builds a temporary mock sysfs tree:
http://cgit.freedesktop.org/upower/tree/src/linux/integration-test
(line 106 ff.) so that we can test its logic with having various
combinations of empty or full batteries, UPSes, ACs, power supplies
that power the whole platform vs. only particular devices, and so on.
Often developers do not have that hardware, or it's not easy to set up
a situation to reproduce a bug (waiting a couple of hours for your
battery to drain, etc.), so it is much more convenient to reproduce
the sysfs attributes in a small test bed.
Another example is the test suite for Ubuntu's driver package
detection which implements the corresponding PackageKit API; it also
uses a "fake sys" module largely derived from upower's:
https://github.com/tseliot/ubuntu-drivers-common/blob/master/tests/fakesysfs.py
There are several problems with these approaches:
* 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.
I am currently working on a first patch for this, and will post it
for discussion here when I have something working.
* upower's test suite is not working any more with current
systemd/udev versions because the support for $SYSFS_PATH was
removed.
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.
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
in the built systemd tree and it will DTRT. I tested with
./test-libudev, which also works fine.
* You can currently only control the coldplug part. That's why
upower's test suite starts the daemon again in each test case. But
you cannot test the dynamic parts with that, such as updating the
properties correctly when changing power sources. What's missing is
to emulate corresponding uevents for the $UDEV_TEST_PREFIX tree.
My initial idea about this was to make udev_monitor_send_device()
public API and relax the uid/pid checks to allow non-root messages
when the corresponding sysfs dir is writable for the calling user
(or something like that). But as non-root users are not allowed to
send NETLINK_KOBJECT_UEVENT messages, this is moot anyway as we do
not want to require root privileges for tests, at least not in all
cases.
So my current idea about this to have gudev read events from a
simple GIO socket in $UDEV_TEST_PREFIX/uevent instead from
libudev_monitor_* if $UDEV_TEST_PREFIX is given, as in that case
the real uevents won't coincide with the sysfs anyway. The "uevent"
signal would then be raised on messages from that socket. Do you
think that's too crazy?
Thanks,
Martin
--
Martin Pitt | http://www.piware.de
Ubuntu Developer (www.ubuntu.com) | Debian Developer (www.debian.org)
[-- Attachment #1.2: 0001-udev-Replace-compile-time-TEST_PREFIX-with-runtime-U.patch --]
[-- Type: text/x-diff, Size: 32938 bytes --]
From eebd02e708cc73e08f6ff13b49ac4f99716c6777 Mon Sep 17 00:00:00 2001
From: Martin Pitt <martinpitt@gnome.org>
Date: Thu, 5 Jul 2012 13:34:23 +0200
Subject: [PATCH] udev: Replace compile-time TEST_PREFIX with runtime $UDEV_TEST_PREFIX
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 which replaces the compile-time TEST_PREFIX macro.
Also adjust udevadm to respect the test prefix.
---
Makefile.am | 7 +++--
src/libudev/libudev-device-private.c | 11 +++++---
src/libudev/libudev-device.c | 50 ++++++++++++++++++++--------------
src/libudev/libudev-enumerate.c | 19 +++++++++----
src/libudev/libudev-private.h | 4 ---
src/libudev/libudev.c | 23 ++++++++++++++++
src/libudev/libudev.h | 1 +
src/test/test-udev.c | 2 +-
src/udev/udev-event.c | 14 ++++++----
src/udev/udev-node.c | 11 +++++---
src/udev/udev-rules.c | 26 ++++++++++++------
src/udev/udevadm-info.c | 17 +++++++++---
12 files changed, 125 insertions(+), 60 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 3b7ec0b..6d941a0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1630,6 +1630,10 @@ TESTS += \
test/udev-test.pl \
test/rules-test.sh
+# The test-udev program should run with relative path names for /sys, /dev,
+# /run prefixed, pointing to our test/ directory.
+TESTS_ENVIRONMENT = UDEV_TEST_PREFIX=test
+
noinst_PROGRAMS += \
test-libudev \
test-udev
@@ -1647,10 +1651,7 @@ test_udev_SOURCES = \
$(libudev_core_la_SOURCES) \
$(libudev_private_la_SOURCES)
-# The test-udev program needs everything compiled with relative path
-# names for /sys, /dev, /run prefixed, pointing to our test/ directory.
test_udev_CFLAGS = \
- -DTEST_PREFIX=\"test\" \
$(libudev_core_la_CFLAGS) \
$(libudev_private_la_CFLAGS)
diff --git a/src/libudev/libudev-device-private.c b/src/libudev/libudev-device-private.c
index 2347736..f6cbb46 100644
--- a/src/libudev/libudev-device-private.c
+++ b/src/libudev/libudev-device-private.c
@@ -30,7 +30,8 @@ static void udev_device_tag(struct udev_device *dev, const char *tag, bool add)
id = udev_device_get_id_filename(dev);
if (id == NULL)
return;
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/run/udev/tags/", tag, "/", id, NULL);
+ util_strscpyl(filename, sizeof(filename), udev_get_test_prefix(udev_device_get_udev(dev)),
+ "/run/udev/tags/", tag, "/", id, NULL);
if (add) {
int fd;
@@ -107,7 +108,8 @@ int udev_device_update_db(struct udev_device *udev_device)
return -1;
has_info = device_has_info(udev_device);
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/run/udev/data/", id, NULL);
+ util_strscpyl(filename, sizeof(filename), udev_get_test_prefix(udev_device_get_udev(udev_device)),
+ "/run/udev/data/", id, NULL);
/* do not store anything for otherwise empty devices */
if (!has_info &&
@@ -138,7 +140,7 @@ int udev_device_update_db(struct udev_device *udev_device)
if (major(udev_device_get_devnum(udev_device)) > 0) {
udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(udev_device))
- fprintf(f, "S:%s\n", udev_list_entry_get_name(list_entry) + strlen(TEST_PREFIX "/dev/"));
+ fprintf(f, "S:%s\n", udev_list_entry_get_name(list_entry) + strlen(udev_get_test_prefix(udev)) + strlen("/dev/"));
if (udev_device_get_devlink_priority(udev_device) != 0)
fprintf(f, "L:%i\n", udev_device_get_devlink_priority(udev_device));
if (udev_device_get_watch_handle(udev_device) >= 0)
@@ -175,7 +177,8 @@ int udev_device_delete_db(struct udev_device *udev_device)
id = udev_device_get_id_filename(udev_device);
if (id == NULL)
return -1;
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/run/udev/data/", id, NULL);
+ util_strscpyl(filename, sizeof(filename), udev_get_test_prefix(udev_device_get_udev(udev_device)),
+ "/run/udev/data/", id, NULL);
unlink(filename);
return 0;
}
diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c
index a8277d1..665619a 100644
--- a/src/libudev/libudev-device.c
+++ b/src/libudev/libudev-device.c
@@ -360,7 +360,8 @@ void udev_device_add_property_from_string_parse(struct udev_device *udev_device,
if (startswith(property, "DEVPATH=")) {
char path[UTIL_PATH_SIZE];
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys", &property[8], NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev_device_get_udev(udev_device)),
+ "/sys", &property[8], NULL);
udev_device_set_syspath(udev_device, path);
} else if (startswith(property, "SUBSYSTEM=")) {
udev_device_set_subsystem(udev_device, &property[10]);
@@ -467,6 +468,9 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
char filename[UTIL_PATH_SIZE];
char line[UTIL_LINE_SIZE];
FILE *f;
+ const char *prefix;
+
+ prefix = udev_get_test_prefix(udev_device_get_udev(udev_device));
/* providing a database file will always force-load it */
if (dbfile == NULL) {
@@ -479,7 +483,7 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
id = udev_device_get_id_filename(udev_device);
if (id == NULL)
return -1;
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/run/udev/data/", id, NULL);
+ util_strscpyl(filename, sizeof(filename), prefix, "/run/udev/data/", id, NULL);
dbfile = filename;
}
@@ -502,7 +506,7 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
val = &line[2];
switch(line[0]) {
case 'S':
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/dev/", val, NULL);
+ util_strscpyl(filename, sizeof(filename), prefix, "/dev/", val, NULL);
udev_device_add_devlink(udev_device, filename, 0);
break;
case 'L':
@@ -635,6 +639,11 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con
char *pos;
struct stat statbuf;
struct udev_device *udev_device;
+ const char *prefix;
+ int prefix_sys_len;
+
+ prefix = udev_get_test_prefix(udev);
+ prefix_sys_len = strlen(prefix) + strlen("/sys");
if (udev == NULL)
return NULL;
@@ -642,13 +651,14 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con
return NULL;
/* path starts in sys */
- if (!startswith(syspath, TEST_PREFIX "/sys")) {
+ if (!startswith(syspath, udev_get_test_prefix(udev)) ||
+ !startswith(syspath + strlen(udev_get_test_prefix(udev)), "/sys")) {
udev_dbg(udev, "not in sys :%s\n", syspath);
return NULL;
}
/* path is not a root directory */
- subdir = syspath + strlen(TEST_PREFIX "/sys");
+ subdir = syspath + prefix_sys_len;
pos = strrchr(subdir, '/');
if (pos == NULL || pos[1] == '\0' || pos < &subdir[2])
return NULL;
@@ -657,7 +667,7 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con
util_strscpy(path, sizeof(path), syspath);
util_resolve_sys_link(udev, path, sizeof(path));
- if (startswith(path + strlen(TEST_PREFIX "/sys"), "/devices/")) {
+ if (startswith(path + prefix_sys_len, "/devices/")) {
char file[UTIL_PATH_SIZE];
/* all "devices" require a "uevent" file */
@@ -709,8 +719,8 @@ _public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char
return NULL;
/* use /sys/dev/{block,char}/<maj>:<min> link */
- snprintf(path, sizeof(path), TEST_PREFIX "/sys/dev/%s/%u:%u",
- type_str, major(devnum), minor(devnum));
+ snprintf(path, sizeof(path), "%s/sys/dev/%s/%u:%u",
+ udev_get_test_prefix(udev), type_str, major(devnum), minor(devnum));
return udev_device_new_from_syspath(udev, path);
}
@@ -790,22 +800,22 @@ _public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev
struct stat statbuf;
if (streq(subsystem, "subsystem")) {
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/subsystem/", sysname, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/subsystem/", sysname, NULL);
if (stat(path, &statbuf) == 0)
goto found;
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/bus/", sysname, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/bus/", sysname, NULL);
if (stat(path, &statbuf) == 0)
goto found;
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/class/", sysname, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/class/", sysname, NULL);
if (stat(path, &statbuf) == 0)
goto found;
goto out;
}
if (streq(subsystem, "module")) {
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/module/", sysname, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/module/", sysname, NULL);
if (stat(path, &statbuf) == 0)
goto found;
goto out;
@@ -821,26 +831,26 @@ _public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev
driver[0] = '\0';
driver = &driver[1];
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/subsystem/", subsys, "/drivers/", driver, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/subsystem/", subsys, "/drivers/", driver, NULL);
if (stat(path, &statbuf) == 0)
goto found;
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/bus/", subsys, "/drivers/", driver, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/bus/", subsys, "/drivers/", driver, NULL);
if (stat(path, &statbuf) == 0)
goto found;
}
goto out;
}
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/subsystem/", subsystem, "/devices/", sysname, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/subsystem/", subsystem, "/devices/", sysname, NULL);
if (stat(path, &statbuf) == 0)
goto found;
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/bus/", subsystem, "/devices/", sysname, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/bus/", subsystem, "/devices/", sysname, NULL);
if (stat(path, &statbuf) == 0)
goto found;
- util_strscpyl(path, sizeof(path), TEST_PREFIX "/sys/class/", subsystem, "/", sysname, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev), "/sys/class/", subsystem, "/", sysname, NULL);
if (stat(path, &statbuf) == 0)
goto found;
out:
@@ -892,7 +902,7 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic
const char *subdir;
util_strscpy(path, sizeof(path), udev_device->syspath);
- subdir = path + strlen(TEST_PREFIX "/sys/");
+ subdir = path + strlen(udev_get_test_prefix(udev_device_get_udev(udev_device))) + strlen("/sys/");
for (;;) {
char *pos;
@@ -1443,7 +1453,7 @@ int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath
udev_device->syspath = strdup(syspath);
if (udev_device->syspath == NULL)
return -ENOMEM;
- udev_device->devpath = udev_device->syspath + strlen(TEST_PREFIX "/sys");
+ udev_device->devpath = udev_device->syspath + strlen(udev_get_test_prefix(udev_device_get_udev(udev_device))) + strlen("/sys");
udev_device_add_property(udev_device, "DEVPATH", udev_device->devpath);
pos = strrchr(udev_device->syspath, '/');
@@ -1476,7 +1486,7 @@ int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode
{
free(udev_device->devnode);
if (devnode[0] != '/') {
- if (asprintf(&udev_device->devnode, TEST_PREFIX "/dev/%s", devnode) < 0)
+ if (asprintf(&udev_device->devnode, "%s/dev/%s", udev_get_test_prefix(udev_device_get_udev(udev_device)), devnode) < 0)
udev_device->devnode = NULL;
} else {
udev_device->devnode = strdup(devnode);
diff --git a/src/libudev/libudev-enumerate.c b/src/libudev/libudev-enumerate.c
index a945758..1fb9208 100644
--- a/src/libudev/libudev-enumerate.c
+++ b/src/libudev/libudev-enumerate.c
@@ -214,7 +214,7 @@ static bool devices_delay_end(struct udev *udev, const char *syspath)
int i;
for (i = 0; delay_device_list[i] != NULL; i++) {
- if (strstr(syspath + strlen("/sys"), delay_device_list[i]) != NULL)
+ if (strstr(syspath + strlen(udev_get_test_prefix(udev)) + strlen("/sys"), delay_device_list[i]) != NULL)
return true;
}
return false;
@@ -649,7 +649,8 @@ static int scan_dir_and_add_devices(struct udev_enumerate *udev_enumerate,
struct dirent *dent;
s = path;
- l = util_strpcpyl(&s, sizeof(path), "/sys/", basedir, NULL);
+ l = util_strpcpyl(&s, sizeof(path), udev_get_test_prefix(udev_enumerate_get_udev(udev_enumerate)),
+ "/sys/", basedir, NULL);
if (subdir1 != NULL)
l = util_strpcpyl(&s, l, "/", subdir1, NULL);
if (subdir2 != NULL)
@@ -728,7 +729,8 @@ static int scan_dir(struct udev_enumerate *udev_enumerate, const char *basedir,
DIR *dir;
struct dirent *dent;
- util_strscpyl(path, sizeof(path), "/sys/", basedir, NULL);
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev_enumerate_get_udev(udev_enumerate)),
+ "/sys/", basedir, NULL);
dir = opendir(path);
if (dir == NULL)
return -1;
@@ -875,8 +877,12 @@ static int scan_devices_children(struct udev_enumerate *enumerate)
static int scan_devices_all(struct udev_enumerate *udev_enumerate)
{
struct stat statbuf;
+ char path[UTIL_PATH_SIZE];
+
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev_enumerate_get_udev(udev_enumerate)),
+ "/sys/subsystem", NULL);
- if (stat("/sys/subsystem", &statbuf) == 0) {
+ if (stat(path, &statbuf) == 0) {
/* we have /subsystem/, forget all the old stuff */
scan_dir(udev_enumerate, "subsystem", "devices", NULL);
} else {
@@ -924,6 +930,7 @@ _public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerat
{
struct stat statbuf;
const char *subsysdir;
+ char path[UTIL_PATH_SIZE];
if (udev_enumerate == NULL)
return -EINVAL;
@@ -932,7 +939,9 @@ _public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerat
if (match_subsystem(udev_enumerate, "module"))
scan_dir_and_add_devices(udev_enumerate, "module", NULL, NULL);
- if (stat("/sys/subsystem", &statbuf) == 0)
+ util_strscpyl(path, sizeof(path), udev_get_test_prefix(udev_enumerate_get_udev(udev_enumerate)),
+ "/sys/subsystem", NULL);
+ if (stat(path, &statbuf) == 0)
subsysdir = "subsystem";
else
subsysdir = "bus";
diff --git a/src/libudev/libudev-private.h b/src/libudev/libudev-private.h
index 4eb4a59..af3338f 100644
--- a/src/libudev/libudev-private.h
+++ b/src/libudev/libudev-private.h
@@ -25,10 +25,6 @@
#define READ_END 0
#define WRITE_END 1
-#ifndef TEST_PREFIX
-#define TEST_PREFIX ""
-#endif
-
/* avoid (sometimes expensive) calculations of parameters for debug output */
#define udev_log_cond(udev, prio, arg...) \
do { \
diff --git a/src/libudev/libudev.c b/src/libudev/libudev.c
index 07a24d5..7be9c89 100644
--- a/src/libudev/libudev.c
+++ b/src/libudev/libudev.c
@@ -43,6 +43,7 @@ struct udev {
void *userdata;
struct udev_list properties_list;
int log_priority;
+ char *test_prefix;
};
void udev_log(struct udev *udev,
@@ -95,6 +96,23 @@ _public_ void udev_set_userdata(struct udev *udev, void *userdata)
}
/**
+ * udev_get_test_prefix:
+ * @udev: udev library context
+ *
+ * Retrieve the file system root prefix for accessing /sys, /dev, etc. The
+ * default is empty, meaning the normal file system root. For testing purposes,
+ * it can be overridden with the environment variable $UDEV_TEST_PREFIX.
+ *
+ * Returns: the test prefix
+ **/
+_public_ const char *udev_get_test_prefix(struct udev *udev)
+{
+ if (udev == NULL)
+ return NULL;
+ return udev->test_prefix;
+}
+
+/**
* udev_new:
*
* Create udev library context. This reads the udev configuration
@@ -190,6 +208,10 @@ _public_ struct udev *udev_new(void)
fclose(f);
}
+ /* test prefix in environment? */
+ env = getenv("UDEV_TEST_PREFIX");
+ udev->test_prefix = strdup(env ? env : "");
+
/* environment overrides config */
env = getenv("UDEV_LOG");
if (env != NULL)
@@ -231,6 +253,7 @@ _public_ struct udev *udev_unref(struct udev *udev)
if (udev->refcount > 0)
return udev;
udev_list_cleanup(&udev->properties_list);
+ free(udev->test_prefix);
free(udev);
return NULL;
}
diff --git a/src/libudev/libudev.h b/src/libudev/libudev.h
index 8643984..5dd3f98 100644
--- a/src/libudev/libudev.h
+++ b/src/libudev/libudev.h
@@ -38,6 +38,7 @@ int udev_get_log_priority(struct udev *udev);
void udev_set_log_priority(struct udev *udev, int priority);
void *udev_get_userdata(struct udev *udev);
void udev_set_userdata(struct udev *udev, void *userdata);
+const char *udev_get_test_prefix(struct udev *udev);
/*
* udev_list
diff --git a/src/test/test-udev.c b/src/test/test-udev.c
index 414eabc..46e8ce6 100644
--- a/src/test/test-udev.c
+++ b/src/test/test-udev.c
@@ -68,7 +68,7 @@ int main(int argc, char *argv[])
rules = udev_rules_new(udev, 1);
- util_strscpyl(syspath, sizeof(syspath), TEST_PREFIX "/sys", devpath, NULL);
+ util_strscpyl(syspath, sizeof(syspath), udev_get_test_prefix(udev) , "/sys", devpath, NULL);
dev = udev_device_new_from_syspath(udev, syspath);
if (dev == NULL) {
log_debug("unknown device '%s'\n", devpath);
diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c
index e6f405b..228b16c 100644
--- a/src/udev/udev-event.c
+++ b/src/udev/udev-event.c
@@ -112,6 +112,8 @@ size_t udev_event_apply_format(struct udev_event *event, const char *src, char *
const char *from;
char *s;
size_t l;
+ const char *prefix = udev_get_test_prefix(udev_device_get_udev(dev));
+ const int devpath_len = strlen(prefix) + strlen("/dev/");
from = src;
s = dest;
@@ -319,7 +321,7 @@ subst:
break;
devnode = udev_device_get_devnode(dev_parent);
if (devnode != NULL)
- l = util_strpcpy(&s, l, devnode + strlen(TEST_PREFIX "/dev/"));
+ l = util_strpcpy(&s, l, devnode + devpath_len);
break;
}
case SUBST_DEVNODE:
@@ -330,7 +332,7 @@ subst:
if (event->name != NULL)
l = util_strpcpy(&s, l, event->name);
else if (udev_device_get_devnode(dev) != NULL)
- l = util_strpcpy(&s, l, udev_device_get_devnode(dev) + strlen(TEST_PREFIX "/dev/"));
+ l = util_strpcpy(&s, l, udev_device_get_devnode(dev) + devpath_len);
else
l = util_strpcpy(&s, l, udev_device_get_sysname(dev));
break;
@@ -340,16 +342,16 @@ subst:
list_entry = udev_device_get_devlinks_list_entry(dev);
if (list_entry == NULL)
break;
- l = util_strpcpy(&s, l, udev_list_entry_get_name(list_entry) + strlen(TEST_PREFIX "/dev/"));
+ l = util_strpcpy(&s, l, udev_list_entry_get_name(list_entry) + devpath_len);
udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry))
- l = util_strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry) + strlen(TEST_PREFIX "/dev/"), NULL);
+ l = util_strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry) + devpath_len, NULL);
break;
}
case SUBST_ROOT:
- l = util_strpcpy(&s, l, TEST_PREFIX "/dev");
+ l = util_strpcpyl(&s, l, prefix, "/dev", NULL);
break;
case SUBST_SYS:
- l = util_strpcpy(&s, l, TEST_PREFIX "/sys");
+ l = util_strpcpyl(&s, l, prefix, "/sys", NULL);
break;
case SUBST_ENV:
if (attr == NULL) {
diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c
index 1bef521..18a75c3 100644
--- a/src/udev/udev-node.c
+++ b/src/udev/udev-node.c
@@ -202,9 +202,10 @@ static void link_update(struct udev_device *dev, const char *slink, bool add)
char dirname[UTIL_PATH_SIZE];
const char *target;
char buf[UTIL_PATH_SIZE];
+ const char *prefix = udev_get_test_prefix(udev_device_get_udev(dev));
- util_path_encode(slink + strlen(TEST_PREFIX "/dev"), name_enc, sizeof(name_enc));
- util_strscpyl(dirname, sizeof(dirname), TEST_PREFIX "/run/udev/links/", name_enc, NULL);
+ util_path_encode(slink + strlen(prefix) + strlen("/dev"), name_enc, sizeof(name_enc));
+ util_strscpyl(dirname, sizeof(dirname), prefix, "/run/udev/links/", name_enc, NULL);
util_strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL);
if (!add && unlink(filename) == 0)
@@ -328,7 +329,8 @@ void udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid)
return;
/* always add /dev/{block,char}/$major:$minor */
- snprintf(filename, sizeof(filename), TEST_PREFIX "/dev/%s/%u:%u",
+ snprintf(filename, sizeof(filename), "%s/dev/%s/%u:%u",
+ udev_get_test_prefix(udev),
strcmp(udev_device_get_subsystem(dev), "block") == 0 ? "block" : "char",
major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev)));
node_symlink(udev, udev_device_get_devnode(dev), filename);
@@ -353,7 +355,8 @@ void udev_node_remove(struct udev_device *dev)
link_update(dev, udev_list_entry_get_name(list_entry), 0);
/* remove /dev/{block,char}/$major:$minor */
- snprintf(filename, sizeof(filename), TEST_PREFIX "/dev/%s/%u:%u",
+ snprintf(filename, sizeof(filename), "%s/dev/%s/%u:%u",
+ udev_get_test_prefix(udev_device_get_udev(dev)),
strcmp(udev_device_get_subsystem(dev), "block") == 0 ? "block" : "char",
major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev)));
unlink(filename);
diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
index b5b54dd..d098ced 100644
--- a/src/udev/udev-rules.c
+++ b/src/udev/udev-rules.c
@@ -1718,6 +1718,9 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
struct token end_token;
char **files, **f;
int r;
+ char sysconf_path[UTIL_PATH_SIZE];
+ char run_path[UTIL_PATH_SIZE];
+ char libexec_path[UTIL_PATH_SIZE];
rules = calloc(1, sizeof(struct udev_rules));
if (rules == NULL)
@@ -1757,10 +1760,14 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
memset(rules->trie_nodes, 0x00, sizeof(struct trie_node));
rules->trie_nodes_cur = 1;
- rules->dirs = strv_new(TEST_PREFIX SYSCONFDIR "/udev/rules.d",
- TEST_PREFIX "/run/udev/rules.d",
- TEST_PREFIX UDEVLIBEXECDIR "/rules.d",
- NULL);
+ util_strscpyl(sysconf_path, sizeof(sysconf_path), udev_get_test_prefix(udev),
+ SYSCONFDIR "/udev/rules.d", NULL);
+ util_strscpyl(run_path, sizeof(run_path), udev_get_test_prefix(udev),
+ "/run/udev/rules.d", NULL);
+ util_strscpyl(libexec_path, sizeof(libexec_path), udev_get_test_prefix(udev),
+ UDEVLIBEXECDIR "/rules.d", NULL);
+ rules->dirs = strv_new(sysconf_path, run_path, libexec_path, NULL);
+
if (!rules->dirs) {
log_error("failed to build config directory array");
return NULL;
@@ -2018,6 +2025,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
struct token *rule;
enum escape_type esc = ESCAPE_UNSET;
bool can_set_name;
+ const char *prefix = udev_get_test_prefix (event->udev);
if (rules->tokens == NULL)
return -1;
@@ -2059,7 +2067,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(event->dev)) {
const char *devlink;
- devlink = udev_list_entry_get_name(list_entry) + strlen(TEST_PREFIX "/dev/");
+ devlink = udev_list_entry_get_name(list_entry) + strlen(prefix) + strlen("/dev/");
if (match_key(rules, cur, devlink) == 0) {
match = true;
break;
@@ -2533,7 +2541,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
log_debug("%i character(s) replaced\n", count);
}
if (major(udev_device_get_devnum(event->dev)) &&
- (!streq(name_str, udev_device_get_devnode(event->dev) + strlen(TEST_PREFIX "/dev/")))) {
+ (!streq(name_str, udev_device_get_devnode(event->dev) + strlen(prefix) + strlen("/dev/")))) {
log_error("NAME=\"%s\" ignored, kernel device nodes "
"can not be renamed; please fix it in %s:%u\n", name,
&rules->buf[rule->rule.filename_off], rule->rule.filename_line);
@@ -2578,7 +2586,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
next[0] = '\0';
log_debug("LINK '%s' %s:%u\n", pos,
&rules->buf[rule->rule.filename_off], rule->rule.filename_line);
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/dev/", pos, NULL);
+ util_strscpyl(filename, sizeof(filename), prefix, "/dev/", pos, NULL);
udev_device_add_devlink(event->dev, filename, cur->key.devlink_unique);
while (isspace(next[1]))
next++;
@@ -2588,7 +2596,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
if (pos[0] != '\0') {
log_debug("LINK '%s' %s:%u\n", pos,
&rules->buf[rule->rule.filename_off], rule->rule.filename_line);
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/dev/", pos, NULL);
+ util_strscpyl(filename, sizeof(filename), prefix, "/dev/", pos, NULL);
udev_device_add_devlink(event->dev, filename, cur->key.devlink_unique);
}
break;
@@ -2698,7 +2706,7 @@ void udev_rules_apply_static_dev_perms(struct udev_rules *rules)
/* we assure, that the permissions tokens are sorted before the static token */
if (mode == 0 && uid == 0 && gid == 0)
goto next;
- util_strscpyl(filename, sizeof(filename), TEST_PREFIX "/dev/",
+ util_strscpyl(filename, sizeof(filename), udev_get_test_prefix(rules->udev), "/dev/",
&rules->buf[cur->key.value_off], NULL);
if (stat(filename, &stats) != 0)
goto next;
diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c
index 907e961..81a9ce7 100644
--- a/src/udev/udevadm-info.c
+++ b/src/udev/udevadm-info.c
@@ -267,13 +267,22 @@ static void cleanup_db(struct udev *udev)
static struct udev_device *find_device(struct udev *udev, const char *id, const char *prefix)
{
char name[UTIL_PATH_SIZE];
+ char fullprefix[UTIL_PATH_SIZE];
+ char devdir[UTIL_PATH_SIZE];
+ char sysdir[UTIL_PATH_SIZE];
- if (prefix && !startswith(id, prefix)) {
- util_strscpyl(name, sizeof(name), prefix, id, NULL);
+ if (prefix)
+ util_strscpyl(fullprefix, sizeof(fullprefix), udev_get_test_prefix(udev), prefix, NULL);
+
+ if (prefix && !startswith(id, fullprefix)) {
+ util_strscpyl(name, sizeof(name), fullprefix, id, NULL);
id = name;
}
- if (startswith(id, "/dev/")) {
+ util_strscpyl(devdir, sizeof(devdir), udev_get_test_prefix(udev), "/dev/", NULL);
+ util_strscpyl(sysdir, sizeof(sysdir), udev_get_test_prefix(udev), "/sys/", NULL);
+
+ if (startswith(id, devdir)) {
struct stat statbuf;
char type;
@@ -288,7 +297,7 @@ static struct udev_device *find_device(struct udev *udev, const char *id, const
return NULL;
return udev_device_new_from_devnum(udev, type, statbuf.st_rdev);
- } else if (startswith(id, "/sys/"))
+ } else if (startswith(id, sysdir))
return udev_device_new_from_syspath(udev, id);
else
return NULL;
--
1.7.10.4
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] Testbeds for libudev/gudev clients
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
@ 2012-07-09 7:25 ` Martin Pitt
2012-07-09 13:50 ` Kay Sievers
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Martin Pitt @ 2012-07-09 7:25 UTC (permalink / raw)
To: linux-hotplug
[-- Attachment #1.1: Type: text/plain, Size: 1027 bytes --]
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.
>
> 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
--
Martin Pitt | http://www.piware.de
Ubuntu Developer (www.ubuntu.com) | Debian Developer (www.debian.org)
[-- Attachment #1.2: 0002-gudev-Add-GUdevTestbed.patch --]
[-- Type: text/x-diff, Size: 33942 bytes --]
From d8e3de2529b956ac3fc78640ca179fde6e909150 Mon Sep 17 00:00:00 2001
From: Martin Pitt <martinpitt@gnome.org>
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 | 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 = \
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
@@ -1826,6 +1827,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 = \
@@ -1920,7 +1923,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
@@ -1949,6 +1955,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..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 <martin.pitt@ubuntu.com>
+ *
+ * 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_dir;
+ gchar *class_dir;
+ gchar *target, *link;
+ const gchar *key, *value;
+ gchar *prop_str;
+
+ dev_dir = g_build_filename (testbed->priv->sys_dir, "devices", name, 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);
+
+ /* 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_dir, key, value);
+ }
+
+ /* properties; they go into the "uevent" sysfs attribute */
+ prop_str = 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 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 (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 = 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 = 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 (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@ubuntu.com>
+ *
+ * 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@ubuntu.com>
+ *
+ * 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 #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] Testbeds for libudev/gudev clients
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
2012-07-09 7:25 ` Martin Pitt
@ 2012-07-09 13:50 ` Kay Sievers
2012-07-09 14:22 ` Martin Pitt
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kay Sievers @ 2012-07-09 13:50 UTC (permalink / raw)
To: linux-hotplug
On Mon, Jul 9, 2012 at 9:25 AM, Martin Pitt <martin.pitt@ubuntu.com> wrote:
> 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.
>>
>> 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?
Urks, the patch isn't really pretty. It was actually nice to get rid
of all these otherwise totally useless knobs.
Can't the tests just use a fs namespace and run the test in it with a
bind-mount of /sys in it? Maybe check the unshare(1) tool to get the
idea, or it might already be able to do that.
If that works, it would not require any patching of tools and could
possibly offer even more useful options for testing in general.
Thanks,
Kay
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] Testbeds for libudev/gudev clients
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
2012-07-09 7:25 ` Martin Pitt
2012-07-09 13:50 ` Kay Sievers
@ 2012-07-09 14:22 ` Martin Pitt
2012-07-09 19:54 ` Lucas De Marchi
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Martin Pitt @ 2012-07-09 14:22 UTC (permalink / raw)
To: linux-hotplug
Kay Sievers [2012-07-09 15:50 +0200]:
> Can't the tests just use a fs namespace and run the test in it with a
> bind-mount of /sys in it? Maybe check the unshare(1) tool to get the
> idea, or it might already be able to do that.
Most alternatives would require root privileges, which make tests a
lot less useful: You cannot run them in environments like jhbuild or
"make distcheck", and are also both inconvenient and potentially
ruining your system when you run them during development.
Another alternative I can think of that avoids root privs is a
fakechroot like LD_PRELOAD wrapper which intercepts all gazillion
variants of open and stat-like calls and redirects them to the
testbed. But given how fakechroot breaks with every other new glibc
release this is not something I'm very keen to do.
The third option that comes to my mind is to change the build system
to build a libudev-test.so with a hardcoded TEST_PREFIX of '.', so
that upower and friends can preload/link to that instead of the real
udev, and run the daemon in the directory of the test bed. Daemons
must not chdir() then (chdir('/') actually used to be best practice
for daemons), but that would not be a too hard limitation.
Other ideas greatly appreciated.
If you veto all those, we'll need to live with tests needing root
privileges. It still allows continuous integration test servers to run
stuff in VMs, but that's a lot harder for developers to set up.
Thanks,
Martin
--
Martin Pitt | http://www.piware.de
Ubuntu Developer (www.ubuntu.com) | Debian Developer (www.debian.org)
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] Testbeds for libudev/gudev clients
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
` (2 preceding siblings ...)
2012-07-09 14:22 ` Martin Pitt
@ 2012-07-09 19:54 ` Lucas De Marchi
2012-07-09 20:09 ` Martin Pitt
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Lucas De Marchi @ 2012-07-09 19:54 UTC (permalink / raw)
To: linux-hotplug
On Mon, Jul 9, 2012 at 11:22 AM, Martin Pitt <martin.pitt@ubuntu.com> wrote:
> Kay Sievers [2012-07-09 15:50 +0200]:
>> Can't the tests just use a fs namespace and run the test in it with a
>> bind-mount of /sys in it? Maybe check the unshare(1) tool to get the
>> idea, or it might already be able to do that.
>
> Most alternatives would require root privileges, which make tests a
> lot less useful: You cannot run them in environments like jhbuild or
> "make distcheck", and are also both inconvenient and potentially
> ruining your system when you run them during development.
>
> Another alternative I can think of that avoids root privs is a
> fakechroot like LD_PRELOAD wrapper which intercepts all gazillion
> variants of open and stat-like calls and redirects them to the
> testbed. But given how fakechroot breaks with every other new glibc
> release this is not something I'm very keen to do.
You might want to look into kmod's testsuite. We do exactly that and
until there's a better alternative I plan to support this for newer
glibcs. There's a path.so that you can copy and paste to your project
- there are even other traps like the ones to uname(), init_module(),
delete_module() etc.
When I implemented that I tried what you are trying now and it didn't
look right and it's ugly to touch all the calls with path strings and
also very error prone.
It's not perfect, it could be simplified I think, but it doesn't touch
any libkmod/tools code like your approach, it doesn't require root
privileges and it's working quite well for all needs of kmod until
now, which I think are more than the ones for testing udev.
Lucas De Marchi
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] Testbeds for libudev/gudev clients
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
` (3 preceding siblings ...)
2012-07-09 19:54 ` Lucas De Marchi
@ 2012-07-09 20:09 ` Martin Pitt
2012-07-09 20:21 ` Lucas De Marchi
2012-07-10 3:40 ` Martin Pitt
6 siblings, 0 replies; 8+ messages in thread
From: Martin Pitt @ 2012-07-09 20:09 UTC (permalink / raw)
To: linux-hotplug
Hello Lucas,
Lucas De Marchi [2012-07-09 16:54 -0300]:
> You might want to look into kmod's testsuite. We do exactly that and
> until there's a better alternative I plan to support this for newer
> glibcs.
Thanks for pointing out! I'll do that. When it fails with a newer
glibc, we should get test case failures and thus it should be rather
obvious where things need fixing.
I guess that still means we'd either need a libudev-test.so or a shell
wrapper and the libpath.so thing around all tests, and thus
build/ship the two as part of a libudev install. That's something
which I considered to be more ugly, but if Kay prefers that, I'll look
into this.
> When I implemented that I tried what you are trying now and it didn't
> look right and it's ugly to touch all the calls with path strings and
> also very error prone.
libudev already has something like that, it has the TEST_PREFIX macro
everywhere. So I don't think it's actually getting much worse, but
with a preloaded library we wouldn't need the TEST_PREFIX thing any
more either.
So the trade is "add the get_prefix() calls to the path name build
calls" vs. "maintain/build/install a preload library".
Thanks,
Martin
--
Martin Pitt | http://www.piware.de
Ubuntu Developer (www.ubuntu.com) | Debian Developer (www.debian.org)
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] Testbeds for libudev/gudev clients
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
` (4 preceding siblings ...)
2012-07-09 20:09 ` Martin Pitt
@ 2012-07-09 20:21 ` Lucas De Marchi
2012-07-10 3:40 ` Martin Pitt
6 siblings, 0 replies; 8+ messages in thread
From: Lucas De Marchi @ 2012-07-09 20:21 UTC (permalink / raw)
To: linux-hotplug
On Mon, Jul 9, 2012 at 5:09 PM, Martin Pitt <martin.pitt@ubuntu.com> wrote:
> Hello Lucas,
>
> Lucas De Marchi [2012-07-09 16:54 -0300]:
>> You might want to look into kmod's testsuite. We do exactly that and
>> until there's a better alternative I plan to support this for newer
>> glibcs.
>
> Thanks for pointing out! I'll do that. When it fails with a newer
> glibc, we should get test case failures and thus it should be rather
> obvious where things need fixing.
>
> I guess that still means we'd either need a libudev-test.so or a shell
> wrapper and the libpath.so thing around all tests, and thus
> build/ship the two as part of a libudev install. That's something
> which I considered to be more ugly, but if Kay prefers that, I'll look
> into this.
It's part of my "make check" running the testsuite.
>
>> When I implemented that I tried what you are trying now and it didn't
>> look right and it's ugly to touch all the calls with path strings and
>> also very error prone.
>
> libudev already has something like that, it has the TEST_PREFIX macro
> everywhere. So I don't think it's actually getting much worse, but
> with a preloaded library we wouldn't need the TEST_PREFIX thing any
> more either.
>
> So the trade is "add the get_prefix() calls to the path name build
> calls" vs. "maintain/build/install a preload library".
I never ever install that, and doing so is just wrong IMO. Just make
it part of the "make check". The final user is not supposed to be
running a testsuite.
Lucas De Marchi
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] Testbeds for libudev/gudev clients
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
` (5 preceding siblings ...)
2012-07-09 20:21 ` Lucas De Marchi
@ 2012-07-10 3:40 ` Martin Pitt
6 siblings, 0 replies; 8+ messages in thread
From: Martin Pitt @ 2012-07-10 3:40 UTC (permalink / raw)
To: linux-hotplug
Lucas De Marchi [2012-07-09 17:21 -0300]:
> > So the trade is "add the get_prefix() calls to the path name build
> > calls" vs. "maintain/build/install a preload library".
>
> I never ever install that, and doing so is just wrong IMO. Just make
> it part of the "make check". The final user is not supposed to be
> running a testsuite.
This is not (primarily) for testing libudev/gudev itself. The whole
point of this is to make the functionality available to users of
libudev/gudev so that you can write tests for _those_ easily. I
already pointed out two existing examples (upower and
ubuntu-drivers-common) in my initial mail. This is also why I wrote
that entire GUdevTestbed; testing gudev itself would be a lot simpler,
but that's not the reason why we need it.
So we do have to ship it, and document how upower and friends have to
use it (presumably an "udevtestbed" wrapper script which finds and
preloads the test library).
Thanks,
Martin
--
Martin Pitt | http://www.piware.de
Ubuntu Developer (www.ubuntu.com) | Debian Developer (www.debian.org)
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2012-07-10 3:40 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-07-06 5:01 [PATCH] Testbeds for libudev/gudev clients Martin Pitt
2012-07-09 7:25 ` Martin Pitt
2012-07-09 13:50 ` Kay Sievers
2012-07-09 14:22 ` Martin Pitt
2012-07-09 19:54 ` Lucas De Marchi
2012-07-09 20:09 ` Martin Pitt
2012-07-09 20:21 ` Lucas De Marchi
2012-07-10 3:40 ` Martin Pitt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).