From e0a53d4747bcffa094130dfc6edf1ec6e06c0575 Mon Sep 17 00:00:00 2001
From: Max Kellermann <mk@cm4all.com>
Date: Wed, 10 Apr 2024 09:39:36 +0200
Subject: [PATCH] util/IntrusiveTreeSet: add debug method Check()

Only for the unit test.
---
 src/util/IntrusiveTreeSet.hxx      | 10 ++++++++++
 src/util/RedBlackTree.hxx          | 30 ++++++++++++++++++++++++++++++
 test/util/TestIntrusiveTreeSet.cxx |  4 ++++
 3 files changed, 44 insertions(+)

diff --git a/src/util/IntrusiveTreeSet.hxx b/src/util/IntrusiveTreeSet.hxx
index 8320529a3..3b788a60a 100644
--- a/src/util/IntrusiveTreeSet.hxx
+++ b/src/util/IntrusiveTreeSet.hxx
@@ -176,6 +176,16 @@ public:
 	[[nodiscard]]
 	IntrusiveTreeSet() noexcept = default;
 
+#ifndef NDEBUG
+	/**
+	 * For debugging only: check the integrity of the red-black
+	 * tree.
+	 */
+	void Check() noexcept {
+		RedBlackTreeNode::BlackHeight(GetRoot());
+	}
+#endif
+
 	[[nodiscard]]
 	constexpr bool empty() const noexcept {
 		return GetRoot() == nullptr;
diff --git a/src/util/RedBlackTree.hxx b/src/util/RedBlackTree.hxx
index 8cb205a5a..07012cede 100644
--- a/src/util/RedBlackTree.hxx
+++ b/src/util/RedBlackTree.hxx
@@ -173,6 +173,36 @@ struct RedBlackTreeNode {
 		return GetLeftMost(this);
 	}
 
+#ifndef NDEBUG
+	/**
+	 * Determine the "black height" (the number of black nodes in
+	 * any path from the root to the leaves).  This is for
+	 * debugging only.  It walks the whole tree and aborts if the
+	 * black height is not consistent.
+	 */
+	static unsigned BlackHeight(const RedBlackTreeNode *node) noexcept {
+		if (node == nullptr)
+			/* leaf nodes (NIL / nullptr) count as
+			   black */
+			return 1;
+
+		assert(node->parent != nullptr);
+		assert(node->color != Color::HEAD);
+		assert(node->color != Color::RED || node->parent->color != Color::RED);
+
+		assert(node->children[0] == nullptr || node->children[0]->parent == node);
+		assert(node->children[1] == nullptr || node->children[1]->parent == node);
+
+		const unsigned left_height = BlackHeight(node->children[0]);
+		const unsigned right_height = BlackHeight(node->children[1]);
+
+		/* the black height must be equal in all paths */
+		assert(left_height == right_height);
+
+		return left_height + (node->color == Color::BLACK);
+	}
+#endif
+
 private:
 	[[nodiscard]]
 	constexpr static RedBlackTreeNode *GetLeftHandedParent(RedBlackTreeNode *node) noexcept {
diff --git a/test/util/TestIntrusiveTreeSet.cxx b/test/util/TestIntrusiveTreeSet.cxx
index e2fca6ca7..71e3fab88 100644
--- a/test/util/TestIntrusiveTreeSet.cxx
+++ b/test/util/TestIntrusiveTreeSet.cxx
@@ -182,6 +182,10 @@ TEST(IntrusiveTreeSet, RandomOrder)
 		set.pop_front();
 		EXPECT_FALSE(items[remove].is_linked());
 
+#ifndef NDEBUG
+		set.Check();
+#endif
+
 		expected = remove + 1;
 		for (const auto &i : set) {
 			EXPECT_EQ(i.value, expected++);