diff mbox

[12/12] multipath: add unit tests for dmevents code

Message ID 1521049605-22050-13-git-send-email-bmarzins@redhat.com (mailing list archive)
State Not Applicable, archived
Delegated to: christophe varoqui
Headers show

Commit Message

Benjamin Marzinski March 14, 2018, 5:46 p.m. UTC
These unit tests do not get complete code coverage. Also, they don't
check for memory errors. To do this through unit tests, instead of
using valgrid, would require adding unit test specific compilation
defines to the code, and compiling a seperate unit-test version.

Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
 tests/Makefile   |   9 +-
 tests/dmevents.c | 847 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 853 insertions(+), 3 deletions(-)
 create mode 100644 tests/dmevents.c

Comments

Martin Wilck March 19, 2018, 12:01 p.m. UTC | #1
On Wed, 2018-03-14 at 12:46 -0500, Benjamin Marzinski wrote:
> These unit tests do not get complete code coverage. Also, they don't
> check for memory errors. To do this through unit tests, instead of
> using valgrid, would require adding unit test specific compilation
> defines to the code, and compiling a seperate unit-test version.
> 
> Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>

I'm impressed, you went much further with cmocka than I did so far.
Nice work.

Reviewed-by: Martin Wilck <mwilck@suse.com>

... but this is pretty dense material. I'd appreciate a follow-up patch
adding some comments about the more complex test cases to make them
easier to understand.
diff mbox

Patch

diff --git a/tests/Makefile b/tests/Makefile
index 3450b14..1f36411 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -3,11 +3,16 @@  include ../Makefile.inc
 CFLAGS += $(BIN_CFLAGS) -I$(multipathdir) -I$(mpathcmddir)
 LIBDEPS += -L$(multipathdir) -lmultipath -lcmocka
 
-TESTS := uevent parser util
+TESTS := uevent parser util dmevents
 
 .SILENT: $(TESTS:%=%.o)
 .PRECIOUS: $(TESTS:%=%-test)
 
+all:	$(TESTS:%=%.out)
+
+dmevents-test: dmevents.o ../multipathd/dmevents.c globals.c $(multipathdir)/libmultipath.so
+	@$(CC) -o $@ $< $(LDFLAGS) $(LIBDEPS) -lpthread -ldevmapper -lurcu -Wl,--wrap=open -Wl,--wrap=close -Wl,--wrap=dm_is_mpath -Wl,--wrap=dm_geteventnr -Wl,--wrap=ioctl -Wl,--wrap=libmp_dm_task_create -Wl,--wrap=dm_task_no_open_count -Wl,--wrap=dm_task_run -Wl,--wrap=dm_task_get_names -Wl,--wrap=dm_task_destroy -Wl,--wrap=poll -Wl,--wrap=remove_map_by_alias -Wl,--wrap=update_multipath
+
 %-test:	%.o globals.c $(multipathdir)/libmultipath.so
 	@$(CC) -o $@ $< $(LDFLAGS) $(LIBDEPS)
 
@@ -15,8 +20,6 @@  TESTS := uevent parser util
 	@echo == running $< ==
 	@LD_LIBRARY_PATH=$(multipathdir):$(mpathcmddir) ./$< >$@
 
-all:	$(TESTS:%=%.out)
-
 clean: dep_clean
 	rm -f $(TESTS:%=%-test) $(TESTS:%=%.out) $(TESTS:%=%.o)
 
diff --git a/tests/dmevents.c b/tests/dmevents.c
new file mode 100644
index 0000000..4442fc2
--- /dev/null
+++ b/tests/dmevents.c
@@ -0,0 +1,847 @@ 
+/*
+ * Copyright (c) 2018 Benjamin Marzinski, Redhat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <cmocka.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "structs.h"
+#include "structs_vec.h"
+
+#include "globals.c"
+/* I have to do this to get at the static variables */
+#include "../multipathd/dmevents.c"
+
+struct dm_device {
+	char name[WWID_SIZE];
+	int is_mpath;
+	uint32_t evt_nr;
+	uint32_t update_nr;
+};
+
+struct test_data {
+	struct vectors vecs;
+	vector dm_devices;
+	struct dm_names *names;
+};
+
+struct test_data data;
+
+int add_dm_device_event(char *name, int is_mpath, uint32_t evt_nr)
+{
+	struct dm_device *dev;
+	int i;
+
+	vector_foreach_slot(data.dm_devices, dev, i) {
+		if (strcmp(name, dev->name) == 0) {
+			dev->evt_nr = evt_nr;
+			return 0;
+		}
+	}
+	dev = (struct dm_device *)malloc(sizeof(struct dm_device));
+	if (!dev){
+		condlog(0, "Testing error mallocing dm_device");
+		return -1;
+	}
+	strncpy(dev->name, name, WWID_SIZE);
+	dev->name[WWID_SIZE - 1] = 0;
+	dev->is_mpath = is_mpath;
+	dev->evt_nr = evt_nr;
+	if (!vector_alloc_slot(data.dm_devices)) {
+		condlog(0, "Testing error setting dm_devices slot");
+		free(dev);
+		return -1;
+	}
+	vector_set_slot(data.dm_devices, dev);
+	return 0;
+}
+
+struct dm_device *find_dm_device(const char *name)
+{
+	struct dm_device *dev;
+	int i;
+
+	vector_foreach_slot(data.dm_devices, dev, i)
+		if (strcmp(name, dev->name) == 0)
+			return dev;
+	return NULL;
+}
+
+int remove_dm_device_event(const char *name)
+{
+	struct dm_device *dev;
+	int i;
+
+	vector_foreach_slot(data.dm_devices, dev, i) {
+		if (strcmp(name, dev->name) == 0) {
+			vector_del_slot(data.dm_devices, i);
+				free(dev);
+			return 0;
+		}
+	}
+	return -1;
+}
+
+void remove_all_dm_device_events(void)
+{
+	struct dm_device *dev;
+	int i;
+
+	vector_foreach_slot(data.dm_devices, dev, i)
+		free(dev);
+	vector_reset(data.dm_devices);
+}
+
+static inline size_t align_val(size_t val)
+{
+        return (val + 7) & ~7;
+}
+static inline void *align_ptr(void *ptr)
+{
+	return (void *)align_val((size_t)ptr);
+}
+
+/* copied off of list_devices in dm-ioctl.c */
+struct dm_names *build_dm_names(void)
+{
+	struct dm_names *names, *np, *old_np = NULL;
+	uint32_t *event_nr;
+	struct dm_device *dev;
+	int i, size = 0;
+
+	if (VECTOR_SIZE(data.dm_devices) == 0) {
+		names = (struct dm_names *)malloc(sizeof(struct dm_names));
+		if (!names) {
+			condlog(0, "Testing error allocating empty dm_names");
+			return NULL;
+		}
+		names->dev = 0;
+		names->next = 0;
+		return names;
+	}
+	vector_foreach_slot(data.dm_devices, dev, i) {
+		size += align_val(offsetof(struct dm_names, name) +
+				  strlen(dev->name) + 1);
+		size += align_val(sizeof(uint32_t));
+	}
+	names = (struct dm_names *)malloc(size);
+	if (!names) {
+		condlog(0, "Testing error allocating dm_names");
+		return NULL;
+	}
+	np = names;
+	vector_foreach_slot(data.dm_devices, dev, i) {
+		if (old_np)
+			old_np->next = (uint32_t) ((uintptr_t) np -
+						   (uintptr_t) old_np);
+		np->dev = 1;
+		np->next = 0;
+		strcpy(np->name, dev->name);
+
+		old_np = np;
+		event_nr = align_ptr(np->name + strlen(dev->name) + 1);
+		*event_nr = dev->evt_nr;
+		np = align_ptr(event_nr + 1);
+	}
+	assert_int_equal((char *)np - (char *)names, size);
+	return names;
+}
+
+static int setup(void **state)
+{
+	if (dmevent_poll_supported()) {
+		data.dm_devices = vector_alloc();
+		*state = &data;
+	} else
+		*state = NULL;
+	return 0;
+}
+
+static int teardown(void **state)
+{
+	struct dm_device *dev;
+	int i;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		return 0;
+	vector_foreach_slot(datap->dm_devices, dev, i)
+		free(dev);
+	vector_free(datap->dm_devices);
+	datap = NULL;
+	return 0;
+}
+
+int __wrap_open(const char *pathname, int flags)
+{
+	assert_ptr_equal(pathname, "/dev/mapper/control");
+	assert_int_equal(flags, O_RDWR);
+	return mock_type(int);
+}
+
+int __wrap_close(int fd)
+{
+	assert_int_equal(fd, waiter->fd);
+	return 0;
+}
+
+int __wrap_dm_is_mpath(const char *name)
+{
+	struct dm_device *dev;
+	int i;
+
+	vector_foreach_slot(data.dm_devices, dev, i)
+		if (strcmp(name, dev->name) == 0)
+			return dev->is_mpath;
+	return 0;
+}
+
+int __wrap_dm_geteventnr(const char *name)
+{
+	struct dm_device *dev;
+	int fail = mock_type(int);
+
+	if (fail)
+		return -1;
+	dev = find_dm_device(name);
+	if (dev) {
+		/* simulate updating device state after adding it */
+		dev->update_nr = dev->evt_nr;
+		return dev->evt_nr;
+	}
+	return -1;
+}
+
+int __wrap_ioctl(int fd, unsigned long request, void *argp)
+{
+	assert_int_equal(fd, waiter->fd);
+	assert_int_equal(request, DM_DEV_ARM_POLL);
+	return mock_type(int);
+}
+
+struct dm_task *__wrap_libmp_dm_task_create(int task)
+{
+	assert_int_equal(task, DM_DEVICE_LIST);
+	return mock_type(struct dm_task *);
+}
+
+int __wrap_dm_task_no_open_count(struct dm_task *dmt)
+{
+	assert_ptr_equal((struct test_data *)dmt, &data);
+	return mock_type(int);
+}
+
+int __wrap_dm_task_run(struct dm_task *dmt)
+{
+	assert_ptr_equal((struct test_data *)dmt, &data);
+	return mock_type(int);
+}
+
+struct dm_names * __wrap_dm_task_get_names(struct dm_task *dmt)
+{
+	int good = mock_type(int);
+	assert_ptr_equal((struct test_data *)dmt, &data);
+
+	if (data.names) {
+		condlog(0, "Testing error. data.names already allocated");
+		return NULL;
+	}
+	if (!good)
+		return NULL;
+	data.names = build_dm_names();
+	return data.names;
+}
+
+void __wrap_dm_task_destroy(struct dm_task *dmt)
+{
+	assert_ptr_equal((struct test_data *)dmt, &data);
+
+	if (data.names) {
+		free(data.names);
+		data.names = NULL;
+	}
+}
+
+int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout)
+{
+	assert_int_equal(nfds, 1);
+	assert_int_equal(timeout, -1);
+	assert_int_equal(fds->fd, waiter->fd);
+	assert_int_equal(fds->events, POLLIN);
+	return mock_type(int);
+}
+
+void __wrap_remove_map_by_alias(const char *alias, struct vectors * vecs,
+				int purge_vec)
+{
+	check_expected(alias);
+	assert_ptr_equal(vecs, waiter->vecs);
+	assert_int_equal(purge_vec, 1);
+}
+
+int __wrap_update_multipath(struct vectors *vecs, char *mapname, int reset)
+{
+	int fail;
+
+	check_expected(mapname);
+	assert_ptr_equal(vecs, waiter->vecs);
+	assert_int_equal(reset, 1);
+	fail = mock_type(int);
+	if (fail) {
+		assert_int_equal(remove_dm_device_event(mapname), 0);
+		return fail;
+	} else {
+		struct dm_device *dev;
+		int i;
+
+		vector_foreach_slot(data.dm_devices, dev, i) {
+			if (strcmp(mapname, dev->name) == 0) {
+				dev->update_nr = dev->evt_nr;
+				return 0;
+			}
+		}
+		fail();
+	}
+	return fail;
+}
+
+struct dev_event *find_dmevents(const char *name)
+{
+	struct dev_event *dev_evt;
+	int i;
+
+	vector_foreach_slot(waiter->events, dev_evt, i)
+		if (!strcmp(dev_evt->name, name))
+			return dev_evt;
+	return NULL;
+}
+
+static void test_init_waiter_bad0(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+	assert_int_equal(init_dmevent_waiter(NULL), -1);
+}
+
+static void test_init_waiter_bad1(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+	will_return(__wrap_open, -1);
+	assert_int_equal(init_dmevent_waiter(&datap->vecs), -1);
+	assert_ptr_equal(waiter, NULL);
+}
+
+static void test_init_waiter_good0(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+	will_return(__wrap_open, 2);
+	assert_int_equal(init_dmevent_waiter(&datap->vecs), 0);
+	assert_ptr_not_equal(waiter, NULL);
+}
+
+static void test_watch_dmevents_bad0(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+	assert_int_equal(watch_dmevents("foo"), -1);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+}
+
+static void test_watch_dmevents_bad1(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	assert_int_equal(add_dm_device_event("foo", 0, 5), 0);
+	assert_int_equal(watch_dmevents("foo"), -1);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+}
+
+static void test_watch_dmevents_bad2(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	remove_all_dm_device_events();
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);
+	will_return(__wrap_dm_geteventnr, -1);
+	assert_int_equal(watch_dmevents("foo"), -1);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+}
+static void test_watch_dmevents_good0(void **state)
+{
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	remove_all_dm_device_events();
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("foo"), 0);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 5);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 1);
+	unwatch_dmevents("foo");
+	assert_int_equal(VECTOR_SIZE(waiter->events), 0);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+}
+
+static void test_watch_dmevents_good1(void **state)
+{
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	remove_all_dm_device_events();
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);	
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("foo"), 0);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 5);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	assert_int_equal(add_dm_device_event("foo", 1, 6), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("foo"), 0);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 6);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 1);
+	unwatch_dmevents("foo");
+	assert_int_equal(VECTOR_SIZE(waiter->events), 0);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+}
+
+static void test_watch_dmevents_good2(void **state)
+{
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	unwatch_all_dmevents();
+	remove_all_dm_device_events();
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);
+	assert_int_equal(add_dm_device_event("bar", 1, 7), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("foo"), 0);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 5);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	assert_ptr_equal(find_dmevents("bar"), NULL);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("bar"), 0);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 5);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev_evt = find_dmevents("bar");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 7);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 2);
+	unwatch_all_dmevents();
+	assert_int_equal(VECTOR_SIZE(waiter->events), 0);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+	assert_ptr_equal(find_dmevents("bar"), NULL);
+}
+
+static void test_get_events_bad0(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	unwatch_all_dmevents();
+	remove_all_dm_device_events();
+
+	will_return(__wrap_libmp_dm_task_create, NULL);
+	assert_int_equal(dm_get_events(), -1);
+}
+
+static void test_get_events_bad1(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 0);
+	assert_int_equal(dm_get_events(), -1);
+}
+
+static void test_get_events_bad2(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 1);
+	will_return(__wrap_dm_task_get_names, 0);
+	assert_int_equal(dm_get_events(), -1);
+}
+
+static void test_get_events_good0(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 1);
+	will_return(__wrap_dm_task_get_names, 1);
+	assert_int_equal(dm_get_events(), 0);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 0);
+}
+
+static void test_get_events_good1(void **state)
+{
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	remove_all_dm_device_events();
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);
+	assert_int_equal(add_dm_device_event("bar", 1, 7), 0);
+	assert_int_equal(add_dm_device_event("baz", 1, 12), 0);
+	assert_int_equal(add_dm_device_event("qux", 0, 4), 0);
+	assert_int_equal(add_dm_device_event("xyzzy", 1, 8), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("foo"), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("bar"), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("xyzzy"), 0);
+	assert_int_equal(add_dm_device_event("foo", 1, 6), 0);
+	assert_int_equal(remove_dm_device_event("xyzzy"), 0);
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 1);
+	will_return(__wrap_dm_task_get_names, 1);
+	assert_int_equal(dm_get_events(), 0);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 6);
+	assert_int_equal(dev_evt->action, EVENT_UPDATE);
+	dev_evt = find_dmevents("bar");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 7);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev_evt = find_dmevents("xyzzy");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 8);
+	assert_int_equal(dev_evt->action, EVENT_REMOVE);
+	assert_ptr_equal(find_dmevents("baz"), NULL);
+	assert_ptr_equal(find_dmevents("qux"), NULL);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 3);
+}
+
+static void test_dmevent_loop_bad0(void **state)
+{
+	struct dm_device *dev;
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	remove_all_dm_device_events();
+	unwatch_all_dmevents();
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("foo"), 0);
+	assert_int_equal(add_dm_device_event("foo", 1, 6), 0);
+	will_return(__wrap_poll, 0);
+	assert_int_equal(dmevent_loop(), 1);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 5);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev = find_dm_device("foo");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 6);
+	assert_int_equal(dev->update_nr, 5);
+}
+
+static void test_dmevent_loop_bad1(void **state)
+{
+	struct dm_device *dev;
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	will_return(__wrap_poll, 1);
+	will_return(__wrap_ioctl, -1);
+	assert_int_equal(dmevent_loop(), 1);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 5);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev = find_dm_device("foo");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 6);
+	assert_int_equal(dev->update_nr, 5);
+}
+
+static void test_dmevent_loop_bad2(void **state)
+{
+	struct dm_device *dev;
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	will_return(__wrap_poll, 1);
+	will_return(__wrap_ioctl, 0);
+	will_return(__wrap_libmp_dm_task_create, NULL);
+	assert_int_equal(dmevent_loop(), 1);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 5);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev = find_dm_device("foo");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 6);
+	assert_int_equal(dev->update_nr, 5);
+}
+
+static void test_dmevent_loop_good0(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	remove_all_dm_device_events();
+	unwatch_all_dmevents();
+	will_return(__wrap_poll, 1);
+	will_return(__wrap_ioctl, 0);
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 1);
+	will_return(__wrap_dm_task_get_names, 1);
+	assert_int_equal(dmevent_loop(), 1);
+}
+
+static void test_dmevent_loop_good1(void **state)
+{
+	struct dm_device *dev;
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	remove_all_dm_device_events();
+	unwatch_all_dmevents();
+	assert_int_equal(add_dm_device_event("foo", 1, 5), 0);
+	assert_int_equal(add_dm_device_event("bar", 1, 7), 0);
+	assert_int_equal(add_dm_device_event("baz", 1, 12), 0);
+	assert_int_equal(add_dm_device_event("xyzzy", 1, 8), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("foo"), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("bar"), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("xyzzy"), 0);
+	assert_int_equal(add_dm_device_event("foo", 1, 6), 0);
+	assert_int_equal(remove_dm_device_event("xyzzy"), 0);
+	will_return(__wrap_poll, 1);
+	will_return(__wrap_ioctl, 0);
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 1);
+	will_return(__wrap_dm_task_get_names, 1);
+	expect_string(__wrap_update_multipath, mapname, "foo");
+	will_return(__wrap_update_multipath, 0);
+	expect_string(__wrap_remove_map_by_alias, alias, "xyzzy");
+	assert_int_equal(dmevent_loop(), 1);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 2);
+	assert_int_equal(VECTOR_SIZE(data.dm_devices), 3);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 6);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev = find_dm_device("foo");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 6);
+	assert_int_equal(dev->update_nr, 6);
+	dev_evt = find_dmevents("bar");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 7);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev = find_dm_device("bar");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 7);
+	assert_int_equal(dev->update_nr, 7);
+	assert_ptr_equal(find_dmevents("xyzzy"), NULL);
+	assert_ptr_equal(find_dm_device("xyzzy"), NULL);
+}
+
+static void test_dmevent_loop_good2(void **state)
+{
+	struct dm_device *dev;
+	struct dev_event *dev_evt;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	assert_int_equal(add_dm_device_event("bar", 1, 9), 0);
+	will_return(__wrap_dm_geteventnr, 0);
+	assert_int_equal(watch_dmevents("baz"), 0);
+	assert_int_equal(add_dm_device_event("baz", 1, 14), 0);
+	will_return(__wrap_poll, 1);
+	will_return(__wrap_ioctl, 0);
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 1);
+	will_return(__wrap_dm_task_get_names, 1);
+	expect_string(__wrap_update_multipath, mapname, "bar");
+	will_return(__wrap_update_multipath, 0);
+	expect_string(__wrap_update_multipath, mapname, "baz");
+	will_return(__wrap_update_multipath, 1);
+	assert_int_equal(dmevent_loop(), 1);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 2);
+	assert_int_equal(VECTOR_SIZE(data.dm_devices), 2);
+	dev_evt = find_dmevents("foo");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 6);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev = find_dm_device("foo");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 6);
+	assert_int_equal(dev->update_nr, 6);
+	dev_evt = find_dmevents("bar");
+	assert_ptr_not_equal(dev_evt, NULL);
+	assert_int_equal(dev_evt->evt_nr, 9);
+	assert_int_equal(dev_evt->action, EVENT_NOTHING);
+	dev = find_dm_device("bar");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 9);
+	assert_int_equal(dev->update_nr, 9);
+	assert_ptr_equal(find_dmevents("baz"), NULL);
+	assert_ptr_equal(find_dm_device("baz"), NULL);
+}
+
+static void test_dmevent_loop_good3(void **state)
+{
+	struct dm_device *dev;
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+
+	assert_int_equal(remove_dm_device_event("foo"), 0);
+	unwatch_dmevents("bar");
+	will_return(__wrap_poll, 1);
+	will_return(__wrap_ioctl, 0);
+	will_return(__wrap_libmp_dm_task_create, &data);
+	will_return(__wrap_dm_task_no_open_count, 1);
+	will_return(__wrap_dm_task_run, 1);
+	will_return(__wrap_dm_task_get_names, 1);
+	expect_string(__wrap_remove_map_by_alias, alias, "foo");
+	assert_int_equal(dmevent_loop(), 1);
+	assert_int_equal(VECTOR_SIZE(waiter->events), 0);
+	assert_int_equal(VECTOR_SIZE(data.dm_devices), 1);
+	dev = find_dm_device("bar");
+	assert_ptr_not_equal(dev, NULL);
+	assert_int_equal(dev->evt_nr, 9);
+	assert_int_equal(dev->update_nr, 9);
+	assert_ptr_equal(find_dmevents("foo"), NULL);
+	assert_ptr_equal(find_dmevents("bar"), NULL);
+	assert_ptr_equal(find_dm_device("foo"), NULL);
+}
+
+static void test_arm_poll(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+	will_return(__wrap_ioctl, 0);
+	assert_int_equal(arm_dm_event_poll(waiter->fd), 0);
+}
+
+static void test_cleanup_waiter(void **state)
+{
+	struct test_data *datap = (struct test_data *)(*state);
+	if (datap == NULL)
+		skip();
+	cleanup_dmevent_waiter();
+	assert_ptr_equal(waiter, NULL);
+}
+
+int test_dmevents(void)
+{
+	const struct CMUnitTest tests[] = {
+		cmocka_unit_test(test_init_waiter_bad0),
+		cmocka_unit_test(test_init_waiter_bad1),
+		cmocka_unit_test(test_init_waiter_good0),
+		cmocka_unit_test(test_watch_dmevents_bad0),
+		cmocka_unit_test(test_watch_dmevents_bad1),
+		cmocka_unit_test(test_watch_dmevents_bad2),
+		cmocka_unit_test(test_watch_dmevents_good0),
+		cmocka_unit_test(test_watch_dmevents_good1),
+		cmocka_unit_test(test_watch_dmevents_good2),
+		cmocka_unit_test(test_get_events_bad0),
+		cmocka_unit_test(test_get_events_bad1),
+		cmocka_unit_test(test_get_events_bad2),
+		cmocka_unit_test(test_get_events_good0),
+		cmocka_unit_test(test_get_events_good1),
+		cmocka_unit_test(test_arm_poll),
+		cmocka_unit_test(test_dmevent_loop_bad0),
+		cmocka_unit_test(test_dmevent_loop_bad1),
+		cmocka_unit_test(test_dmevent_loop_bad2),
+		cmocka_unit_test(test_dmevent_loop_good0),
+		cmocka_unit_test(test_dmevent_loop_good1),
+		cmocka_unit_test(test_dmevent_loop_good2),
+		cmocka_unit_test(test_dmevent_loop_good3),
+		cmocka_unit_test(test_cleanup_waiter),
+	};
+	return cmocka_run_group_tests(tests, setup, teardown);
+}
+
+int main(void)
+{
+	int ret = 0;
+
+	ret += test_dmevents();
+	return ret;
+}