--- /dev/null
+/* 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:
+ */
--- /dev/null
+/* 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;
+}
+
+static 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:
+ */