[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 2/2] tootls/tests: introduce unit tests for rangesets



Introduce some basic infrastructure for doing rangeset unit tests, and add
a few tests that ensure correctness of rangeset subtraction.

Signed-off-by: Roger Pau Monné <roger.pau@xxxxxxxxxx>
---
 tools/tests/Makefile                 |   1 +
 tools/tests/rangeset/Makefile        |  45 ++++++
 tools/tests/rangeset/harness.h       |  71 +++++++++
 tools/tests/rangeset/test-rangeset.c | 228 +++++++++++++++++++++++++++
 4 files changed, 345 insertions(+)
 create mode 100644 tools/tests/rangeset/Makefile
 create mode 100644 tools/tests/rangeset/harness.h
 create mode 100644 tools/tests/rangeset/test-rangeset.c

diff --git a/tools/tests/Makefile b/tools/tests/Makefile
index 1319c3a9d88c..3e60ab6b268d 100644
--- a/tools/tests/Makefile
+++ b/tools/tests/Makefile
@@ -10,6 +10,7 @@ SUBDIRS-$(CONFIG_X86) += x86_emulator
 endif
 SUBDIRS-y += xenstore
 SUBDIRS-y += depriv
+SUBDIRS-y += rangeset
 SUBDIRS-y += vpci
 SUBDIRS-y += paging-mempool
 
diff --git a/tools/tests/rangeset/Makefile b/tools/tests/rangeset/Makefile
new file mode 100644
index 000000000000..451f67e1920f
--- /dev/null
+++ b/tools/tests/rangeset/Makefile
@@ -0,0 +1,45 @@
+XEN_ROOT=$(CURDIR)/../../..
+include $(XEN_ROOT)/tools/Rules.mk
+
+TARGET := test-rangeset
+
+.PHONY: all
+all: $(TARGET)
+
+.PHONY: run
+run: $(TARGET)
+       ./$<
+
+.PHONY: clean
+clean:
+       $(RM) -- *.o $(TARGET) $(DEPS_RM)
+
+.PHONY: distclean
+distclean: clean
+       $(RM) -- *~
+
+.PHONY: install
+install: all
+       $(INSTALL_DIR) $(DESTDIR)$(LIBEXEC_BIN)
+       $(INSTALL_PROG) $(TARGET) $(DESTDIR)$(LIBEXEC_BIN)
+
+.PHONY: uninstall
+uninstall:
+       $(RM) -- $(addprefix $(DESTDIR)$(LIBEXEC_BIN)/,$(TARGET))
+
+list.h: $(XEN_ROOT)/xen/include/xen/list.h
+rangeset.h: $(XEN_ROOT)/xen/include/xen/rangeset.h
+list.h rangeset.h:
+       sed -e '/#include/d' <$< >$@
+
+rangeset.c: $(XEN_ROOT)/xen/common/rangeset.c list.h rangeset.h
+       # Remove includes and add the test harness header
+       sed -e '/#include/d' -e '1s/^/#include "harness.h"/' <$< >$@
+
+CFLAGS  += -D__XEN_TOOLS__
+CFLAGS  += $(APPEND_CFLAGS)
+CFLAGS += $(CFLAGS_xeninclude)
+LDFLAGS += $(APPEND_LDFLAGS)
+
+test-rangeset: rangeset.o test-rangeset.o
+       $(CC) $^ -o $@ $(LDFLAGS)
diff --git a/tools/tests/rangeset/harness.h b/tools/tests/rangeset/harness.h
new file mode 100644
index 000000000000..a60ebff72d0f
--- /dev/null
+++ b/tools/tests/rangeset/harness.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Unit tests for rangesets.
+ *
+ * Copyright (C) 2025 Cloud Software Group
+ */
+
+#ifndef _TEST_HARNESS_
+#define _TEST_HARNESS_
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xen-tools/common-macros.h>
+
+#define smp_wmb()
+#define __must_check __attribute__((__warn_unused_result__))
+#define cf_check
+
+#define BUG_ON(x) assert(!(x))
+#define ASSERT(x) assert(x)
+
+#include "list.h"
+#include "rangeset.h"
+
+typedef bool rwlock_t;
+typedef bool spinlock_t;
+
+struct domain {
+    unsigned int     domain_id;
+    struct list_head rangesets;
+    spinlock_t       rangesets_lock;
+};
+
+/* For rangeset_domain_{initialize,printk}() */
+#define spin_lock_init(l) (*(l) = false)
+#define spin_lock(l)      (*(l) = true)
+#define spin_unlock(l)    (*(l) = false)
+
+/* For rangeset->lock */
+#define rwlock_init(l)    (*(l) = false)
+#define read_lock(l)      (*(l) = true)
+#define read_unlock(l)    (*(l) = false)
+#define write_lock(l)     (*(l) = true)
+#define write_unlock(l)   (*(l) = false)
+
+#define xmalloc(type) ((type *)malloc(sizeof(type)))
+#define xfree free
+
+#define unlikely
+
+#define safe_strcpy strcpy
+
+#define printk printf
+
+#endif
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/tools/tests/rangeset/test-rangeset.c 
b/tools/tests/rangeset/test-rangeset.c
new file mode 100644
index 000000000000..b44cd217a050
--- /dev/null
+++ b/tools/tests/rangeset/test-rangeset.c
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Unit tests for rangesets.
+ *
+ * Copyright (C) 2025 Cloud Software Group
+ */
+
+#include "harness.h"
+
+struct range {
+    unsigned long start, end;
+};
+
+struct action {
+    enum {
+        ADD,
+        REMOVE,
+    } action;
+    struct range r;
+};
+
+#define DECLARE_ACTIONS(nr) static const struct action actions ## nr []
+#define DECLARE_RESULTS(nr) static const struct range results ## nr []
+
+/*
+ * Subtract range with tail overlap on existing range:
+ *
+ * { 0, 1, 4, 5 } - { 3, 4 } = { 0, 1, 5, 5 }
+ */
+DECLARE_ACTIONS(0) = {
+    { ADD,    { 0, 1 } },
+    { ADD,    { 4, 5 } },
+    { REMOVE, { 3, 4 } },
+};
+DECLARE_RESULTS(0) = {
+    { 0, 1 }, { 5, 5 },
+};
+
+/*
+ * Subtract range with complete and tail overlap on existing ranges:
+ *
+ * { 0, 1, 4, 5, 7, 8 } - { 3, 4, 5, 6, 7 } = { 0, 1, 8 }
+ */
+DECLARE_ACTIONS(1) = {
+    { ADD,    { 0, 1 } },
+    { ADD,    { 4, 5 } },
+    { ADD,    { 7, 8 } },
+    { REMOVE, { 3, 7 } },
+};
+DECLARE_RESULTS(1) = {
+    { 0, 1 }, { 8, 8 },
+};
+
+/*
+ * Subtract range with no overlap:
+ *
+ * { 0, 1, 4, 5 } - { 2, 3 } = { 0, 1, 4, 5 }
+ */
+DECLARE_ACTIONS(2) = {
+    { ADD,    { 0, 1 } },
+    { ADD,    { 4, 5 } },
+    { REMOVE, { 2, 3 } },
+};
+DECLARE_RESULTS(2) = {
+    { 0, 1 }, { 4, 5 },
+};
+
+/*
+ * Subtract range with partial overlap on two existing ranges:
+ *
+ * { 0, 1, 4, 5 } - { 1, 4 } = { 0, 5 }
+ */
+DECLARE_ACTIONS(3) = {
+    { ADD,    { 0, 1 } },
+    { ADD,    { 4, 5 } },
+    { REMOVE, { 1, 4 } },
+};
+DECLARE_RESULTS(3) = {
+    { 0, 0 }, { 5, 5 },
+};
+
+static const struct test {
+    unsigned int nr_actions, nr_results;
+    const struct action *actions;
+    const struct range *result;
+} tests[] = {
+#define DECLARE_TEST(nr)                                \
+    {                                                   \
+        .actions = actions ## nr,                       \
+        .nr_actions = ARRAY_SIZE(actions ## nr),        \
+        .result  = results ## nr,                       \
+        .nr_results = ARRAY_SIZE(results ## nr),        \
+    }
+
+    DECLARE_TEST(0),
+    DECLARE_TEST(1),
+    DECLARE_TEST(2),
+    DECLARE_TEST(3),
+
+#undef DECLARE_TEST
+};
+
+static int print_range(unsigned long s, unsigned long e, void *data)
+{
+    printf("[%ld, %ld]\n", s, e);
+
+    return 0;
+}
+
+static int count_ranges(unsigned long s, unsigned long e, void *data)
+{
+    unsigned int *nr = data;
+
+    ++*nr;
+    return 0;
+}
+
+const struct range *expected;
+static int check_ranges(unsigned long s, unsigned long e, void *data)
+{
+    unsigned int *nr = data;
+    int rc = 0;
+
+    if ( s != expected[*nr].start || e != expected[*nr].end )
+        rc = -EINVAL;
+
+    ++*nr;
+    return rc;
+}
+
+static void print_both(struct rangeset *r, const struct range *expected,
+                       unsigned int nr_expected)
+{
+    unsigned int i;
+
+    printf("Result:\n");
+    rangeset_report_ranges(r, 0, ~0UL, print_range, NULL);
+    printf("Expected:\n");
+    for ( i = 0; i < nr_expected; i++ )
+        printf("[%ld, %ld]\n", expected[i].start, expected[i].end);
+}
+
+int main(int argc, char **argv)
+{
+    struct rangeset *r = rangeset_new(NULL, NULL, 0);
+    unsigned int i;
+    int ret_code = 0;
+
+    ASSERT(r);
+
+    for ( i = 0 ; i < ARRAY_SIZE(tests); i++ )
+    {
+        unsigned int j, nr = 0;
+        int rc = 0;
+
+        rangeset_purge(r);
+        for ( j = 0; j < tests[i].nr_actions; j++ )
+        {
+            const struct action *a = &tests[i].actions[j];
+
+            switch ( a->action )
+            {
+            case ADD:
+                rc = rangeset_add_range(r, a->r.start, a->r.end);
+                break;
+
+            case REMOVE:
+                rc = rangeset_remove_range(r, a->r.start, a->r.end);
+                break;
+            }
+
+            if ( rc )
+            {
+                printf("Test %u failed to %s range [%ld, %ld]\n",
+                       i, a->action == ADD ? "add" : "remove",
+                       a->r.start, a->r.end);
+                rangeset_report_ranges(r, 0, ~0UL, print_range, NULL);
+                break;
+            }
+        }
+
+        if ( rc )
+        {
+            /* Action failed, skip this test and set exit code to failure. */
+            ret_code = EXIT_FAILURE;
+            continue;
+        }
+
+        rc = rangeset_report_ranges(r, 0, ~0UL, count_ranges, &nr);
+        if ( rc )
+        {
+            printf("Test %u unable to count number of result ranges\n", i);
+            rangeset_report_ranges(r, 0, ~0UL, print_range, NULL);
+            ret_code = EXIT_FAILURE;
+            continue;
+        }
+        if ( nr != tests[i].nr_results )
+        {
+            printf("Test %u unexpected number of result ranges, expected: %u 
got: %u\n",
+                   i, tests[i].nr_results, nr);
+            print_both(r, tests[i].result, tests[i].nr_results);
+            ret_code = EXIT_FAILURE;
+            continue;
+        }
+
+        nr = 0;
+        expected = tests[i].result;
+        rc = rangeset_report_ranges(r, 0, ~0UL, check_ranges, &nr);
+        if ( rc )
+        {
+            printf("Test %u range checking failed\n", i);
+            print_both(r, tests[i].result, tests[i].nr_results);
+            ret_code = EXIT_FAILURE;
+            continue;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
-- 
2.48.1




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.