multipath: add unit tests for dmevents code
authorBenjamin Marzinski <bmarzins@redhat.com>
Wed, 14 Mar 2018 17:46:45 +0000 (12:46 -0500)
committerChristophe Varoqui <christophe.varoqui@opensvc.com>
Tue, 27 Mar 2018 20:31:19 +0000 (22:31 +0200)
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
tests/dmevents.c [new file with mode: 0644]

index 3450b14..1f36411 100644 (file)
@@ -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 (file)
index 0000000..4442fc2
--- /dev/null
@@ -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;
+}