[libc-commits] [libc] [libc][tsearch] add tsearch functions (PR #172625)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Mon Feb 23 20:15:20 PST 2026
================
@@ -0,0 +1,517 @@
+//===-- Unittests for tsearch ---------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/posix_tnode.h"
+#include "src/search/tdelete.h"
+#include "src/search/tdestroy.h"
+#include "src/search/tfind.h"
+#include "src/search/tsearch.h"
+#include "src/search/twalk.h"
+#include "src/search/twalk_r.h"
+#include "src/string/strcmp.h"
+#include "src/string/strcpy.h"
+#include "src/string/strlen.h"
+#include "test/UnitTest/Test.h"
+
+// ---------------------------------------------------------------------------
+// Helpers: encode integers as void* keys (no heap allocation needed).
+// ---------------------------------------------------------------------------
+static void *encode(int val) {
+ return reinterpret_cast<void *>(static_cast<intptr_t>(val));
+}
+
+static int decode(const void *val) {
+ return static_cast<int>(reinterpret_cast<intptr_t>(val));
+}
+
+static int read_node(const __llvm_libc_tnode *node) {
+ return static_cast<int>(*static_cast<const intptr_t *>(node));
+}
+
+static int int_compare(const void *a, const void *b) {
+ int x = decode(a);
+ int y = decode(b);
+ return (x > y) - (x < y);
+}
+
+// ---------------------------------------------------------------------------
+// OOP-style RAII int_box around the POSIX tsearch family.
+//
+// The int_box owns the tree root and calls tdestroy in its destructor.
+// It is parameterised on a free-function so callers can track or customise
+// destruction.
+// ---------------------------------------------------------------------------
+using FreeFn = void (*)(void *);
+
+static void noop_free(void *) {}
+
+class TTree {
+ void *root;
+ FreeFn free_fn;
+
+ // Non-copyable.
+ TTree(const TTree &) = delete;
+ TTree &operator=(const TTree &) = delete;
+
+public:
+ explicit TTree(FreeFn f = noop_free) : root(nullptr), free_fn(f) {}
+
+ ~TTree() {
+ if (root)
+ LIBC_NAMESPACE::tdestroy(root, free_fn);
+ }
+
+ // Insert key. Returns the node pointer on success, nullptr on failure.
+ void *insert(void *key) {
+ return LIBC_NAMESPACE::tsearch(key, &root, int_compare);
+ }
+
+ // Find key. Returns nullptr when not found.
+ void *find(void *key) const {
+ return LIBC_NAMESPACE::tfind(key, &root, int_compare);
+ }
+
+ // Delete key. Returns the parent node (or the new root) on success,
+ // nullptr when the key is not in the tree.
+ void *remove(void *key) {
+ return LIBC_NAMESPACE::tdelete(key, &root, int_compare);
+ }
+
+ void walk(void (*action)(const __llvm_libc_tnode *, VISIT, int)) const {
+ LIBC_NAMESPACE::twalk(root, action);
+ }
+
+ void walk_r(void (*action)(const __llvm_libc_tnode *, VISIT, void *),
+ void *closure) const {
+ LIBC_NAMESPACE::twalk_r(root, action, closure);
+ }
+
+ // Release ownership so the destructor will not call tdestroy.
+ void *release() {
+ void *r = root;
+ root = nullptr;
+ return r;
+ }
+
+ bool empty() const { return root == nullptr; }
+ void *get_root() const { return root; }
+};
+
+// ===== tsearch tests =======================================================
+
+TEST(LlvmLibcTSearchTest, InsertIntoEmptyTree) {
+ TTree tree;
+ void *r = tree.insert(encode(42));
+ ASSERT_NE(r, nullptr);
+ ASSERT_EQ(read_node(r), 42);
+}
+
+TEST(LlvmLibcTSearchTest, InsertMultiple) {
+ TTree tree;
+ for (int i = 0; i < 8; ++i) {
+ void *r = tree.insert(encode(i));
+ ASSERT_NE(r, nullptr);
+ ASSERT_EQ(read_node(r), i);
+ }
+}
+
+TEST(LlvmLibcTSearchTest, InsertDuplicateReturnsSameNode) {
+ TTree tree;
+ void *first = tree.insert(encode(7));
+ ASSERT_NE(first, nullptr);
+
+ // Inserting the same key again must return the existing node.
+ void *second = tree.insert(encode(7));
+ ASSERT_EQ(first, second);
+ ASSERT_EQ(read_node(second), 7);
+}
+
+// ===== tfind tests =========================================================
+
+TEST(LlvmLibcTSearchTest, FindInEmptyTree) {
+ TTree tree;
+ void *r = tree.find(encode(1));
+ ASSERT_EQ(r, nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, FindExistingKey) {
+ TTree tree;
+ tree.insert(encode(5));
+ tree.insert(encode(10));
+ tree.insert(encode(15));
+
+ void *r = tree.find(encode(10));
+ ASSERT_NE(r, nullptr);
+ ASSERT_EQ(read_node(r), 10);
+}
+
+TEST(LlvmLibcTSearchTest, FindNonExistentKey) {
+ TTree tree;
+ tree.insert(encode(1));
+ tree.insert(encode(3));
+
+ void *r = tree.find(encode(2));
+ ASSERT_EQ(r, nullptr);
+}
+
+// ===== tdelete tests =======================================================
+
+TEST(LlvmLibcTSearchTest, DeleteFromEmptyTree) {
+ void *root = nullptr;
+ void *r = LIBC_NAMESPACE::tdelete(encode(1), &root, int_compare);
+ ASSERT_EQ(r, nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteNonExistentKey) {
+ TTree tree;
+ tree.insert(encode(10));
+ void *r = tree.remove(encode(99));
+ ASSERT_EQ(r, nullptr);
+ // Original key must still be findable.
+ ASSERT_NE(tree.find(encode(10)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteOnlyElement) {
+ void *root = nullptr;
+ LIBC_NAMESPACE::tsearch(encode(42), &root, int_compare);
+ void *r = LIBC_NAMESPACE::tdelete(encode(42), &root, int_compare);
+ // POSIX: on success tdelete returns a non-null value.
+ ASSERT_NE(r, nullptr);
+ // The tree is now empty.
+ ASSERT_EQ(root, nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteLeaf) {
+ TTree tree;
+ tree.insert(encode(10));
+ tree.insert(encode(5));
+ tree.insert(encode(15));
+
+ void *r = tree.remove(encode(5));
+ ASSERT_NE(r, nullptr);
+ ASSERT_EQ(tree.find(encode(5)), nullptr);
+ // Siblings remain.
+ ASSERT_NE(tree.find(encode(10)), nullptr);
+ ASSERT_NE(tree.find(encode(15)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteNodeWithOneChild) {
+ TTree tree;
+ // Build a small chain: 10 -> 5 -> 3
+ tree.insert(encode(10));
+ tree.insert(encode(5));
+ tree.insert(encode(3));
+
+ void *r = tree.remove(encode(5));
+ ASSERT_NE(r, nullptr);
+ ASSERT_EQ(tree.find(encode(5)), nullptr);
+ ASSERT_NE(tree.find(encode(3)), nullptr);
+ ASSERT_NE(tree.find(encode(10)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteNodeWithTwoChildren) {
+ TTree tree;
+ tree.insert(encode(10));
+ tree.insert(encode(5));
+ tree.insert(encode(15));
+ tree.insert(encode(3));
+ tree.insert(encode(7));
+
+ void *r = tree.remove(encode(5));
+ ASSERT_NE(r, nullptr);
+ ASSERT_EQ(tree.find(encode(5)), nullptr);
+ // All other keys survive.
+ ASSERT_NE(tree.find(encode(3)), nullptr);
+ ASSERT_NE(tree.find(encode(7)), nullptr);
+ ASSERT_NE(tree.find(encode(10)), nullptr);
+ ASSERT_NE(tree.find(encode(15)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, InsertAndDeleteMany) {
+ TTree tree;
+ constexpr int N = 64;
+ // Insert in pseudo-random order.
+ for (int i = 0; i < N; ++i)
+ ASSERT_NE(tree.insert(encode((i * 37) % N)), nullptr);
+
+ // Delete every other element.
+ for (int i = 0; i < N; i += 2) {
+ void *r = tree.remove(encode(i));
+ ASSERT_NE(r, nullptr);
+ }
+
+ // Verify survivors and absences.
+ for (int i = 0; i < N; ++i) {
+ void *r = tree.find(encode(i));
+ if (i % 2 == 0)
+ ASSERT_EQ(r, nullptr);
+ else
+ ASSERT_NE(r, nullptr);
+ }
+}
----------------
SchrodingerZhu wrote:
Removed
https://github.com/llvm/llvm-project/pull/172625
More information about the libc-commits
mailing list