summaryrefslogtreecommitdiff
path: root/na/test/test_dcache.cc
diff options
context:
space:
mode:
Diffstat (limited to 'na/test/test_dcache.cc')
-rw-r--r--na/test/test_dcache.cc232
1 files changed, 232 insertions, 0 deletions
diff --git a/na/test/test_dcache.cc b/na/test/test_dcache.cc
new file mode 100644
index 0000000..9e0eec5
--- /dev/null
+++ b/na/test/test_dcache.cc
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * test_dcache.cc — Self-tests for the dentry cache layer
+ */
+
+#include <lume/selftest.h>
+#include <lume/dcache.h>
+#include <lume/fs.h>
+#include <lume/mount.h>
+#include <lume/errno.h>
+#include <lume/slab.h>
+
+/* Root dentry/mount from VFS init */
+extern dentry* g_root_dentry;
+extern vfsmount* g_root_mnt;
+
+void selftest_dcache()
+{
+ /* ---- d_alloc: basic allocation under root ---- */
+ st_begin("dcache: d_alloc basic");
+ {
+ qstr name{};
+ name.name = "test_child";
+ name.len = 10;
+ name.hash = full_name_hash(name.name, name.len);
+
+ dentry* child = d_alloc(g_root_dentry, &name);
+ ST_ASSERT(child != nullptr);
+ ST_ASSERT_EQ(child->d_name.len, 10u);
+ ST_ASSERT(child->d_parent == g_root_dentry);
+ ST_ASSERT(child->d_inode == nullptr); /* not instantiated yet */
+ ST_ASSERT(child->d_sb == g_root_dentry->d_sb);
+
+ /* Name was copied into inline buffer */
+ bool match = true;
+ for (uint32 i = 0; i < name.len; i++) {
+ if (child->d_name.name[i] != name.name[i]) { match = false; break; }
+ }
+ ST_ASSERT(match);
+
+ d_drop(child);
+ dput(child);
+ }
+ st_pass();
+
+ /* ---- d_lookup: find existing child ---- */
+ st_begin("dcache: d_lookup finds child");
+ {
+ qstr name{};
+ name.name = "lookup_me";
+ name.len = 9;
+ name.hash = full_name_hash(name.name, name.len);
+
+ dentry* child = d_alloc(g_root_dentry, &name);
+ ST_ASSERT(child != nullptr);
+
+ /* d_lookup should find it */
+ dentry* found = d_lookup(g_root_dentry, &name);
+ ST_ASSERT(found != nullptr);
+ ST_ASSERT(found == child);
+ dput(found); /* drop d_lookup ref */
+
+ d_drop(child);
+ dput(child);
+ }
+ st_pass();
+
+ /* ---- d_lookup: miss returns nullptr ---- */
+ st_begin("dcache: d_lookup miss returns nullptr");
+ {
+ qstr name{};
+ name.name = "nonexistent_xyz";
+ name.len = 15;
+ name.hash = full_name_hash(name.name, name.len);
+
+ dentry* found = d_lookup(g_root_dentry, &name);
+ ST_ASSERT(found == nullptr);
+ }
+ st_pass();
+
+ /* ---- d_instantiate: bind inode to dentry ---- */
+ st_begin("dcache: d_instantiate binds inode");
+ {
+ qstr name{};
+ name.name = "has_inode";
+ name.len = 9;
+ name.hash = full_name_hash(name.name, name.len);
+
+ dentry* child = d_alloc(g_root_dentry, &name);
+ ST_ASSERT(child != nullptr);
+ ST_ASSERT(child->d_inode == nullptr);
+
+ /* Create a dummy inode */
+ inode* ip = new_inode(g_root_dentry->d_sb);
+ ST_ASSERT(ip != nullptr);
+ ip->i_mode = S_IFREG | 0644;
+
+ d_instantiate(child, ip);
+ ST_ASSERT(child->d_inode == ip);
+ ST_ASSERT(child->d_flags & DCACHE_REGULAR_TYPE);
+
+ d_drop(child);
+ dput(child);
+ }
+ st_pass();
+
+ /* ---- dget/dput: refcount ---- */
+ st_begin("dcache: dget/dput refcount");
+ {
+ qstr name{};
+ name.name = "reftest";
+ name.len = 7;
+ name.hash = full_name_hash(name.name, name.len);
+
+ dentry* child = d_alloc(g_root_dentry, &name);
+ ST_ASSERT(child != nullptr);
+ /* d_alloc sets refcount = 1 */
+ int rc1 = child->d_lockref.load();
+ ST_ASSERT(rc1 >= 1);
+
+ dget(child);
+ int rc2 = child->d_lockref.load();
+ ST_ASSERT_EQ(rc2, rc1 + 1);
+
+ dput(child);
+ int rc3 = child->d_lockref.load();
+ ST_ASSERT_EQ(rc3, rc1);
+
+ d_drop(child);
+ dput(child);
+ }
+ st_pass();
+
+ /* ---- d_drop: removes from parent children list ---- */
+ st_begin("dcache: d_drop removes from parent");
+ {
+ qstr name{};
+ name.name = "dropme";
+ name.len = 6;
+ name.hash = full_name_hash(name.name, name.len);
+
+ dentry* child = d_alloc(g_root_dentry, &name);
+ ST_ASSERT(child != nullptr);
+
+ /* Should be findable */
+ dentry* f1 = d_lookup(g_root_dentry, &name);
+ ST_ASSERT(f1 != nullptr);
+ dput(f1);
+
+ /* Drop it */
+ d_drop(child);
+
+ /* Should no longer be findable */
+ dentry* f2 = d_lookup(g_root_dentry, &name);
+ ST_ASSERT(f2 == nullptr);
+
+ dput(child);
+ }
+ st_pass();
+
+ /* ---- d_path: root returns "/" ---- */
+ st_begin("dcache: d_path root returns /");
+ {
+ path p{};
+ p.de = g_root_dentry;
+ p.mnt = g_root_mnt;
+
+ char buf[64];
+ char* result = d_path(&p, buf, sizeof(buf));
+ ST_ASSERT(result != nullptr);
+ ST_ASSERT(result[0] == '/');
+ ST_ASSERT(result[1] == '\0');
+ }
+ st_pass();
+
+ /* ---- d_path: child path ---- */
+ st_begin("dcache: d_path child returns /name");
+ {
+ qstr name{};
+ name.name = "pathtest";
+ name.len = 8;
+ name.hash = full_name_hash(name.name, name.len);
+
+ dentry* child = d_alloc(g_root_dentry, &name);
+ ST_ASSERT(child != nullptr);
+
+ path p{};
+ p.de = child;
+ p.mnt = g_root_mnt;
+
+ char buf[64];
+ char* result = d_path(&p, buf, sizeof(buf));
+ ST_ASSERT(result != nullptr);
+
+ /* Should be "/pathtest" */
+ const char* expected = "/pathtest";
+ bool eq = true;
+ for (int i = 0; expected[i]; i++) {
+ if (result[i] != expected[i]) { eq = false; break; }
+ }
+ ST_ASSERT(eq);
+
+ d_drop(child);
+ dput(child);
+ }
+ st_pass();
+
+ /* ---- d_alloc_anon: root-like dentry ---- */
+ st_begin("dcache: d_alloc_anon");
+ {
+ dentry* anon = d_alloc_anon(g_root_dentry->d_sb);
+ ST_ASSERT(anon != nullptr);
+ ST_ASSERT(anon->d_parent == anon); /* root: parent is self */
+ ST_ASSERT(anon->d_sb == g_root_dentry->d_sb);
+ dput(anon);
+ }
+ st_pass();
+
+ /* ---- full_name_hash: deterministic ---- */
+ st_begin("dcache: full_name_hash deterministic");
+ {
+ uint32 h1 = full_name_hash("hello", 5);
+ uint32 h2 = full_name_hash("hello", 5);
+ ST_ASSERT_EQ(h1, h2);
+
+ uint32 h3 = full_name_hash("world", 5);
+ ST_ASSERT_NE(h1, h3);
+ }
+ st_pass();
+}