diff mbox series

[v2] selftests/input: Introduce basic tests for evdev ioctls

Message ID 20230530102627.87284-1-eballetbo@kernel.org (mailing list archive)
State New
Headers show
Series [v2] selftests/input: Introduce basic tests for evdev ioctls | expand

Commit Message

Enric Balletbo i Serra May 30, 2023, 10:26 a.m. UTC
This provides a basic infrastructure for the creation of tests for the evdev
interface. Most of this code is adapted from the libevdev wrapper library. While
most of evdev ioctls are covered and tested using libevdev tests there are some
evdev ioctls that aren't because are not supported (and will not be supported)
by libevdev [1]. So, adding, at least those tests, would make sense.

The test creates an uinput device (and an evdev device) so you can
call the wanted ioctl from userspace. So, to run those tests you need
to have support for uinput and evdev as well.

[1] For example, libevdev doesn't support setting EV_REP because it's inherently
racy - one libevdev context to set those values via the ioctl would cause all
other libevdev contexts on the same device to be out of sync. Since we do not
get notifications when the values changed, libevdev's buffered values for EV_REP
will remain whatever they were initially.

Signed-off-by: Enric Balletbo i Serra <eballetbo@kernel.org>
---
Test output:

	TAP version 13
	1..3
	# Starting 3 tests from 1 test cases.
	#  RUN           global.eviocgname_get_device_name ...
	#            OK  global.eviocgname_get_device_name
	ok 1 global.eviocgname_get_device_name
	#  RUN           global.eviocgrep_get_repeat_settings ...
	#            OK  global.eviocgrep_get_repeat_settings
	ok 2 global.eviocgrep_get_repeat_settings
	#  RUN           global.eviocsrep_set_repeat_settings ...
	#            OK  global.eviocsrep_set_repeat_settings
	ok 3 global.eviocsrep_set_repeat_settings
	# PASSED: 3 / 3 tests passed.
	# Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0

 tools/testing/selftests/Makefile           |   1 +
 tools/testing/selftests/input/.gitignore   |   2 +
 tools/testing/selftests/input/Makefile     |   5 +
 tools/testing/selftests/input/config       |   3 +
 tools/testing/selftests/input/evioc-test.c | 259 +++++++++++++++++++++
 5 files changed, 270 insertions(+)
 create mode 100644 tools/testing/selftests/input/.gitignore
 create mode 100644 tools/testing/selftests/input/Makefile
 create mode 100644 tools/testing/selftests/input/config
 create mode 100644 tools/testing/selftests/input/evioc-test.c

Comments

Peter Hutterer June 6, 2023, 5:04 a.m. UTC | #1
On Tue, May 30, 2023 at 12:26:27PM +0200, Enric Balletbo i Serra wrote:
> This provides a basic infrastructure for the creation of tests for the evdev
> interface. Most of this code is adapted from the libevdev wrapper library. While
> most of evdev ioctls are covered and tested using libevdev tests there are some
> evdev ioctls that aren't because are not supported (and will not be supported)
> by libevdev [1]. So, adding, at least those tests, would make sense.
> 
> The test creates an uinput device (and an evdev device) so you can
> call the wanted ioctl from userspace. So, to run those tests you need
> to have support for uinput and evdev as well.
> 
> [1] For example, libevdev doesn't support setting EV_REP because it's inherently
> racy - one libevdev context to set those values via the ioctl would cause all
> other libevdev contexts on the same device to be out of sync. Since we do not
> get notifications when the values changed, libevdev's buffered values for EV_REP
> will remain whatever they were initially.
> 
> Signed-off-by: Enric Balletbo i Serra <eballetbo@kernel.org>

thanks, this mostly LGTM but there's still a bug left in the vararg
handling.

[...]

> +#include <dirent.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <linux/uinput.h>
> +#include <poll.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +#include <time.h>
> +#include <unistd.h>
> +
> +#include "../kselftest_harness.h"
> +
> +#define TEST_DEVICE_NAME "selftest input device"
> +
> +struct selftest_uinput {
> +	int uinput_fd; /** file descriptor to uinput */
> +	int evdev_fd; /** file descriptor to evdev */
> +	char *name; /** device name */
> +	char *syspath; /** /sys path */
> +	char *devnode; /** device node */

nitpick: none of name, syspath, devnode are used in the tests and it's
likely they'll never need to be so there's no reason to strdup them
here. You could change fetch_syspath_and_devnode() to open_devnode() and
return the opened fd, meaning you can reduce the code even more.

[...]

> +
> +TEST(eviocgname_get_device_name)
> +{
> +	struct selftest_uinput *uidev;
> +	char buf[256];
> +	int rc;
> +
> +	rc = selftest_uinput_create_device(&uidev);

this one and the others without extra arguments need to be:

	rc = selftest_uinput_create_device(&uidev, -1);

otherwise the vararg loop is going to keep the room warm for no reason.

Cheers,
  Peter
Muhammad Usama Anjum June 6, 2023, 8:08 a.m. UTC | #2
On 5/30/23 3:26 PM, Enric Balletbo i Serra wrote:
> This provides a basic infrastructure for the creation of tests for the evdev
> interface. Most of this code is adapted from the libevdev wrapper library. While
> most of evdev ioctls are covered and tested using libevdev tests there are some
> evdev ioctls that aren't because are not supported (and will not be supported)
> by libevdev [1]. So, adding, at least those tests, would make sense.
> 
> The test creates an uinput device (and an evdev device) so you can
> call the wanted ioctl from userspace. So, to run those tests you need
> to have support for uinput and evdev as well.
> 
> [1] For example, libevdev doesn't support setting EV_REP because it's inherently
> racy - one libevdev context to set those values via the ioctl would cause all
> other libevdev contexts on the same device to be out of sync. Since we do not
> get notifications when the values changed, libevdev's buffered values for EV_REP
> will remain whatever they were initially.
> 
> Signed-off-by: Enric Balletbo i Serra <eballetbo@kernel.org>
Acked-by: Muhammad Usama Anjum <usama.anjum@collabora.com>

> ---
> Test output:
> 
> 	TAP version 13
> 	1..3
> 	# Starting 3 tests from 1 test cases.
> 	#  RUN           global.eviocgname_get_device_name ...
> 	#            OK  global.eviocgname_get_device_name
> 	ok 1 global.eviocgname_get_device_name
> 	#  RUN           global.eviocgrep_get_repeat_settings ...
> 	#            OK  global.eviocgrep_get_repeat_settings
> 	ok 2 global.eviocgrep_get_repeat_settings
> 	#  RUN           global.eviocsrep_set_repeat_settings ...
> 	#            OK  global.eviocsrep_set_repeat_settings
> 	ok 3 global.eviocsrep_set_repeat_settings
> 	# PASSED: 3 / 3 tests passed.
> 	# Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0
> 
>  tools/testing/selftests/Makefile           |   1 +
>  tools/testing/selftests/input/.gitignore   |   2 +
>  tools/testing/selftests/input/Makefile     |   5 +
>  tools/testing/selftests/input/config       |   3 +
>  tools/testing/selftests/input/evioc-test.c | 259 +++++++++++++++++++++
>  5 files changed, 270 insertions(+)
>  create mode 100644 tools/testing/selftests/input/.gitignore
>  create mode 100644 tools/testing/selftests/input/Makefile
>  create mode 100644 tools/testing/selftests/input/config
>  create mode 100644 tools/testing/selftests/input/evioc-test.c
> 
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 90a62cf75008..29fc77168aa7 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -28,6 +28,7 @@ TARGETS += futex
>  TARGETS += gpio
>  TARGETS += hid
>  TARGETS += intel_pstate
> +TARGETS += input
>  TARGETS += iommu
>  TARGETS += ipc
>  TARGETS += ir
> diff --git a/tools/testing/selftests/input/.gitignore b/tools/testing/selftests/input/.gitignore
> new file mode 100644
> index 000000000000..37f5dff3255b
> --- /dev/null
> +++ b/tools/testing/selftests/input/.gitignore
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0
> +evioc-test
> diff --git a/tools/testing/selftests/input/Makefile b/tools/testing/selftests/input/Makefile
> new file mode 100644
> index 000000000000..031729be0628
> --- /dev/null
> +++ b/tools/testing/selftests/input/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +CFLAGS +=  -D_GNU_SOURCE -std=gnu99 -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
> +
> +TEST_GEN_PROGS := evioc-test
> +include ../lib.mk
> diff --git a/tools/testing/selftests/input/config b/tools/testing/selftests/input/config
> new file mode 100644
> index 000000000000..b7512f3e6d8d
> --- /dev/null
> +++ b/tools/testing/selftests/input/config
> @@ -0,0 +1,3 @@
> +CONFIG_INPUT=y
> +CONFIG_INPUT_EVDEV=y
> +CONFIG_INPUT_UINPUT=m
> diff --git a/tools/testing/selftests/input/evioc-test.c b/tools/testing/selftests/input/evioc-test.c
> new file mode 100644
> index 000000000000..1b951d41ee47
> --- /dev/null
> +++ b/tools/testing/selftests/input/evioc-test.c
> @@ -0,0 +1,259 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright © 2023 Red Hat, Inc.
> + *
> + * Part of the code in this file is inspired and copied from the libevdev wrapper library
> + * for evdev devices written by Peter Hutterer.
> + */
> +
> +#include <dirent.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <linux/uinput.h>
> +#include <poll.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +#include <time.h>
> +#include <unistd.h>
> +
> +#include "../kselftest_harness.h"
> +
> +#define TEST_DEVICE_NAME "selftest input device"
> +
> +struct selftest_uinput {
> +	int uinput_fd; /** file descriptor to uinput */
> +	int evdev_fd; /** file descriptor to evdev */
> +	char *name; /** device name */
> +	char *syspath; /** /sys path */
> +	char *devnode; /** device node */
> +};
> +
> +static int is_event_device(const struct dirent *dent)
> +{
> +	return strncmp("event", dent->d_name, 5) == 0;
> +}
> +
> +static char *fetch_device_node(const char *path)
> +{
> +	struct dirent **namelist;
> +	char *devnode = NULL;
> +	int ndev, i;
> +
> +	ndev = scandir(path, &namelist, is_event_device, alphasort);
> +	if (ndev <= 0)
> +		return NULL;
> +
> +	/* ndev should only ever be 1 */
> +
> +	for (i = 0; i < ndev; i++) {
> +		if (!devnode && asprintf(&devnode, "/dev/input/%s",
> +					 namelist[i]->d_name) == -1)
> +			devnode = NULL;
> +		free(namelist[i]);
> +	}
> +
> +	free(namelist);
> +
> +	return devnode;
> +}
> +
> +static int fetch_syspath_and_devnode(struct selftest_uinput *uidev)
> +{
> +#define SYS_INPUT_DIR "/sys/devices/virtual/input/"
> +	char buf[sizeof(SYS_INPUT_DIR) + 64] = SYS_INPUT_DIR;
> +	int rc;
> +
> +	rc = ioctl(uidev->uinput_fd,
> +		   UI_GET_SYSNAME(sizeof(buf) - strlen(SYS_INPUT_DIR)),
> +		   &buf[strlen(SYS_INPUT_DIR)]);
> +	if (rc == -1) {
> +		fprintf(stderr, "cannot get the sysfs name of the uinput device (%d)\n", rc);
> +		return rc;
> +	}
> +
> +	uidev->syspath = strdup(buf);
> +	uidev->devnode = fetch_device_node(buf);
> +
> +	return uidev->devnode ? 0 : -1;
> +#undef SYS_INPUT_DIR
> +}
> +
> +static void selftest_uinput_destroy(struct selftest_uinput *uidev)
> +{
> +	if (!uidev)
> +		return;
> +
> +	if (uidev->uinput_fd >= 0)
> +		ioctl(uidev->uinput_fd, UI_DEV_DESTROY, NULL);
> +
> +	close(uidev->evdev_fd);
> +	close(uidev->uinput_fd);
> +
> +	free(uidev->syspath);
> +	free(uidev->devnode);
> +	free(uidev->name);
> +	free(uidev);
> +}
> +
> +static int selftest_uinput_create_device(struct selftest_uinput **uidev, ...)
> +{
> +	struct selftest_uinput *new_device;
> +	struct uinput_setup setup;
> +	va_list args;
> +	int rc, fd;
> +	int type;
> +
> +	new_device = calloc(1, sizeof(struct selftest_uinput));
> +	if (!new_device)
> +		return -ENOMEM;
> +
> +	memset(&setup, 0, sizeof(setup));
> +	strncpy(setup.name, TEST_DEVICE_NAME, UINPUT_MAX_NAME_SIZE - 1);
> +	setup.id.vendor = 0x1234; /* sample vendor */
> +	setup.id.product = 0x5678; /* sample product */
> +	setup.id.bustype = BUS_USB;
> +
> +	fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
> +	if (fd < 0) {
> +		fprintf(stderr, "cannot open uinput (%d): %m\n", errno);
> +		goto error;
> +	}
> +
> +	va_start(args, uidev);
> +	rc = 0;
> +	do {
> +		type = va_arg(args, int);
> +		if (type == -1)
> +			break;
> +		rc = ioctl(fd, UI_SET_EVBIT, type);
> +	} while (rc == 0);
> +	va_end(args);
> +
> +	rc = ioctl(fd, UI_DEV_SETUP, &setup);
> +	if (rc == -1)
> +		goto error;
> +
> +	rc = ioctl(fd, UI_DEV_CREATE, NULL);
> +	if (rc == -1)
> +		goto error;
> +
> +	new_device->name = strdup(TEST_DEVICE_NAME);
> +	new_device->uinput_fd = fd;
> +
> +	if (fetch_syspath_and_devnode(new_device) == -1) {
> +		fprintf(stderr, "unable to fetch syspath or device node.\n");
> +		errno = ENODEV;
> +		goto error;
> +	}
> +
> +	fd = open(new_device->devnode, O_RDONLY);
> +	if (fd < 0) {
> +		fprintf(stderr, "cannot open uinput (%d): %m\n", errno);
> +		goto error;
> +	}
> +	new_device->evdev_fd = fd;
> +
> +	*uidev = new_device;
> +
> +	return 0;
> +
> +error:
> +	rc = -errno;
> +	selftest_uinput_destroy(new_device);
> +	return rc;
> +}
> +
> +TEST(eviocgname_get_device_name)
> +{
> +	struct selftest_uinput *uidev;
> +	char buf[256];
> +	int rc;
> +
> +	rc = selftest_uinput_create_device(&uidev);
> +	ASSERT_EQ(0, rc);
> +	ASSERT_NE(NULL, uidev);
> +
> +	memset(buf, 0, sizeof(buf));
> +	/* ioctl to get the name */
> +	rc = ioctl(uidev->evdev_fd, EVIOCGNAME(sizeof(buf) - 1), buf);
> +	ASSERT_GE(rc, 0);
> +	ASSERT_STREQ(TEST_DEVICE_NAME, buf);
> +
> +	selftest_uinput_destroy(uidev);
> +}
> +
> +TEST(eviocgrep_get_repeat_settings)
> +{
> +	struct selftest_uinput *uidev;
> +	int rep_values[2];
> +	int rc;
> +
> +	memset(rep_values, 0, sizeof(rep_values));
> +
> +	rc = selftest_uinput_create_device(&uidev);
> +	ASSERT_EQ(0, rc);
> +	ASSERT_NE(NULL, uidev);
> +
> +	/* ioctl to get the repeat rates values */
> +	rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
> +	/* should fail because EV_REP is not set */
> +	ASSERT_EQ(-1, rc);
> +
> +	selftest_uinput_destroy(uidev);
> +
> +	rc = selftest_uinput_create_device(&uidev, EV_REP, -1);
> +	ASSERT_EQ(0, rc);
> +	ASSERT_NE(NULL, uidev);
> +
> +	/* ioctl to get the repeat rates values */
> +	rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
> +	ASSERT_EQ(0, rc);
> +	/* should get the default delay and period values set by the kernel */
> +	ASSERT_EQ(rep_values[0], 250);
> +	ASSERT_EQ(rep_values[1], 33);
> +
> +	selftest_uinput_destroy(uidev);
> +}
> +
> +TEST(eviocsrep_set_repeat_settings)
> +{
> +	struct selftest_uinput *uidev;
> +	int rep_values[2];
> +	int rc;
> +
> +	memset(rep_values, 0, sizeof(rep_values));
> +
> +	rc = selftest_uinput_create_device(&uidev);
> +	ASSERT_EQ(0, rc);
> +	ASSERT_NE(NULL, uidev);
> +
> +	/* ioctl to set the repeat rates values */
> +	rc = ioctl(uidev->evdev_fd, EVIOCSREP, rep_values);
> +	/* should fail because EV_REP is not set */
> +	ASSERT_EQ(-1, rc);
> +
> +	selftest_uinput_destroy(uidev);
> +
> +	rc = selftest_uinput_create_device(&uidev, EV_REP, -1);
> +	ASSERT_EQ(0, rc);
> +	ASSERT_NE(NULL, uidev);
> +
> +	/* ioctl to set the repeat rates values */
> +	rep_values[0] = 500;
> +	rep_values[1] = 200;
> +	rc = ioctl(uidev->evdev_fd, EVIOCSREP, rep_values);
> +	ASSERT_EQ(0, rc);
> +
> +	/* ioctl to get the repeat rates values */
> +	rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
> +	ASSERT_EQ(0, rc);
> +	/* should get the delay and period values set previously */
> +	ASSERT_EQ(rep_values[0], 500);
> +	ASSERT_EQ(rep_values[1], 200);
> +
> +	selftest_uinput_destroy(uidev);
> +}
> +
> +TEST_HARNESS_MAIN
diff mbox series

Patch

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 90a62cf75008..29fc77168aa7 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -28,6 +28,7 @@  TARGETS += futex
 TARGETS += gpio
 TARGETS += hid
 TARGETS += intel_pstate
+TARGETS += input
 TARGETS += iommu
 TARGETS += ipc
 TARGETS += ir
diff --git a/tools/testing/selftests/input/.gitignore b/tools/testing/selftests/input/.gitignore
new file mode 100644
index 000000000000..37f5dff3255b
--- /dev/null
+++ b/tools/testing/selftests/input/.gitignore
@@ -0,0 +1,2 @@ 
+# SPDX-License-Identifier: GPL-2.0
+evioc-test
diff --git a/tools/testing/selftests/input/Makefile b/tools/testing/selftests/input/Makefile
new file mode 100644
index 000000000000..031729be0628
--- /dev/null
+++ b/tools/testing/selftests/input/Makefile
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS +=  -D_GNU_SOURCE -std=gnu99 -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
+
+TEST_GEN_PROGS := evioc-test
+include ../lib.mk
diff --git a/tools/testing/selftests/input/config b/tools/testing/selftests/input/config
new file mode 100644
index 000000000000..b7512f3e6d8d
--- /dev/null
+++ b/tools/testing/selftests/input/config
@@ -0,0 +1,3 @@ 
+CONFIG_INPUT=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_INPUT_UINPUT=m
diff --git a/tools/testing/selftests/input/evioc-test.c b/tools/testing/selftests/input/evioc-test.c
new file mode 100644
index 000000000000..1b951d41ee47
--- /dev/null
+++ b/tools/testing/selftests/input/evioc-test.c
@@ -0,0 +1,259 @@ 
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2023 Red Hat, Inc.
+ *
+ * Part of the code in this file is inspired and copied from the libevdev wrapper library
+ * for evdev devices written by Peter Hutterer.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/uinput.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define TEST_DEVICE_NAME "selftest input device"
+
+struct selftest_uinput {
+	int uinput_fd; /** file descriptor to uinput */
+	int evdev_fd; /** file descriptor to evdev */
+	char *name; /** device name */
+	char *syspath; /** /sys path */
+	char *devnode; /** device node */
+};
+
+static int is_event_device(const struct dirent *dent)
+{
+	return strncmp("event", dent->d_name, 5) == 0;
+}
+
+static char *fetch_device_node(const char *path)
+{
+	struct dirent **namelist;
+	char *devnode = NULL;
+	int ndev, i;
+
+	ndev = scandir(path, &namelist, is_event_device, alphasort);
+	if (ndev <= 0)
+		return NULL;
+
+	/* ndev should only ever be 1 */
+
+	for (i = 0; i < ndev; i++) {
+		if (!devnode && asprintf(&devnode, "/dev/input/%s",
+					 namelist[i]->d_name) == -1)
+			devnode = NULL;
+		free(namelist[i]);
+	}
+
+	free(namelist);
+
+	return devnode;
+}
+
+static int fetch_syspath_and_devnode(struct selftest_uinput *uidev)
+{
+#define SYS_INPUT_DIR "/sys/devices/virtual/input/"
+	char buf[sizeof(SYS_INPUT_DIR) + 64] = SYS_INPUT_DIR;
+	int rc;
+
+	rc = ioctl(uidev->uinput_fd,
+		   UI_GET_SYSNAME(sizeof(buf) - strlen(SYS_INPUT_DIR)),
+		   &buf[strlen(SYS_INPUT_DIR)]);
+	if (rc == -1) {
+		fprintf(stderr, "cannot get the sysfs name of the uinput device (%d)\n", rc);
+		return rc;
+	}
+
+	uidev->syspath = strdup(buf);
+	uidev->devnode = fetch_device_node(buf);
+
+	return uidev->devnode ? 0 : -1;
+#undef SYS_INPUT_DIR
+}
+
+static void selftest_uinput_destroy(struct selftest_uinput *uidev)
+{
+	if (!uidev)
+		return;
+
+	if (uidev->uinput_fd >= 0)
+		ioctl(uidev->uinput_fd, UI_DEV_DESTROY, NULL);
+
+	close(uidev->evdev_fd);
+	close(uidev->uinput_fd);
+
+	free(uidev->syspath);
+	free(uidev->devnode);
+	free(uidev->name);
+	free(uidev);
+}
+
+static int selftest_uinput_create_device(struct selftest_uinput **uidev, ...)
+{
+	struct selftest_uinput *new_device;
+	struct uinput_setup setup;
+	va_list args;
+	int rc, fd;
+	int type;
+
+	new_device = calloc(1, sizeof(struct selftest_uinput));
+	if (!new_device)
+		return -ENOMEM;
+
+	memset(&setup, 0, sizeof(setup));
+	strncpy(setup.name, TEST_DEVICE_NAME, UINPUT_MAX_NAME_SIZE - 1);
+	setup.id.vendor = 0x1234; /* sample vendor */
+	setup.id.product = 0x5678; /* sample product */
+	setup.id.bustype = BUS_USB;
+
+	fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
+	if (fd < 0) {
+		fprintf(stderr, "cannot open uinput (%d): %m\n", errno);
+		goto error;
+	}
+
+	va_start(args, uidev);
+	rc = 0;
+	do {
+		type = va_arg(args, int);
+		if (type == -1)
+			break;
+		rc = ioctl(fd, UI_SET_EVBIT, type);
+	} while (rc == 0);
+	va_end(args);
+
+	rc = ioctl(fd, UI_DEV_SETUP, &setup);
+	if (rc == -1)
+		goto error;
+
+	rc = ioctl(fd, UI_DEV_CREATE, NULL);
+	if (rc == -1)
+		goto error;
+
+	new_device->name = strdup(TEST_DEVICE_NAME);
+	new_device->uinput_fd = fd;
+
+	if (fetch_syspath_and_devnode(new_device) == -1) {
+		fprintf(stderr, "unable to fetch syspath or device node.\n");
+		errno = ENODEV;
+		goto error;
+	}
+
+	fd = open(new_device->devnode, O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr, "cannot open uinput (%d): %m\n", errno);
+		goto error;
+	}
+	new_device->evdev_fd = fd;
+
+	*uidev = new_device;
+
+	return 0;
+
+error:
+	rc = -errno;
+	selftest_uinput_destroy(new_device);
+	return rc;
+}
+
+TEST(eviocgname_get_device_name)
+{
+	struct selftest_uinput *uidev;
+	char buf[256];
+	int rc;
+
+	rc = selftest_uinput_create_device(&uidev);
+	ASSERT_EQ(0, rc);
+	ASSERT_NE(NULL, uidev);
+
+	memset(buf, 0, sizeof(buf));
+	/* ioctl to get the name */
+	rc = ioctl(uidev->evdev_fd, EVIOCGNAME(sizeof(buf) - 1), buf);
+	ASSERT_GE(rc, 0);
+	ASSERT_STREQ(TEST_DEVICE_NAME, buf);
+
+	selftest_uinput_destroy(uidev);
+}
+
+TEST(eviocgrep_get_repeat_settings)
+{
+	struct selftest_uinput *uidev;
+	int rep_values[2];
+	int rc;
+
+	memset(rep_values, 0, sizeof(rep_values));
+
+	rc = selftest_uinput_create_device(&uidev);
+	ASSERT_EQ(0, rc);
+	ASSERT_NE(NULL, uidev);
+
+	/* ioctl to get the repeat rates values */
+	rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
+	/* should fail because EV_REP is not set */
+	ASSERT_EQ(-1, rc);
+
+	selftest_uinput_destroy(uidev);
+
+	rc = selftest_uinput_create_device(&uidev, EV_REP, -1);
+	ASSERT_EQ(0, rc);
+	ASSERT_NE(NULL, uidev);
+
+	/* ioctl to get the repeat rates values */
+	rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
+	ASSERT_EQ(0, rc);
+	/* should get the default delay and period values set by the kernel */
+	ASSERT_EQ(rep_values[0], 250);
+	ASSERT_EQ(rep_values[1], 33);
+
+	selftest_uinput_destroy(uidev);
+}
+
+TEST(eviocsrep_set_repeat_settings)
+{
+	struct selftest_uinput *uidev;
+	int rep_values[2];
+	int rc;
+
+	memset(rep_values, 0, sizeof(rep_values));
+
+	rc = selftest_uinput_create_device(&uidev);
+	ASSERT_EQ(0, rc);
+	ASSERT_NE(NULL, uidev);
+
+	/* ioctl to set the repeat rates values */
+	rc = ioctl(uidev->evdev_fd, EVIOCSREP, rep_values);
+	/* should fail because EV_REP is not set */
+	ASSERT_EQ(-1, rc);
+
+	selftest_uinput_destroy(uidev);
+
+	rc = selftest_uinput_create_device(&uidev, EV_REP, -1);
+	ASSERT_EQ(0, rc);
+	ASSERT_NE(NULL, uidev);
+
+	/* ioctl to set the repeat rates values */
+	rep_values[0] = 500;
+	rep_values[1] = 200;
+	rc = ioctl(uidev->evdev_fd, EVIOCSREP, rep_values);
+	ASSERT_EQ(0, rc);
+
+	/* ioctl to get the repeat rates values */
+	rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
+	ASSERT_EQ(0, rc);
+	/* should get the delay and period values set previously */
+	ASSERT_EQ(rep_values[0], 500);
+	ASSERT_EQ(rep_values[1], 200);
+
+	selftest_uinput_destroy(uidev);
+}
+
+TEST_HARNESS_MAIN