From ffbabfe49484b807a0f8712d212b8c64f1ae6018 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 18:29:09 +0200 Subject: [PATCH] test: extend coverage of sfsTest --- src/sfsTest.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/src/sfsTest.c b/src/sfsTest.c index 571bc16..a73b792 100644 --- a/src/sfsTest.c +++ b/src/sfsTest.c @@ -154,3 +154,160 @@ KTEST(sfsFileGrowsThroughIndirectBlock) KT_EQ(t, sfsReadAt(f, 0, back, sfsBigSpan), sfsBigSpan); KT_EQ(t, memcmp(back, pattern, sfsBigSpan), 0); + /* Overwrite a slice that straddles the direct/indirect boundary and + confirm only that slice changes. */ + uint8 patch[64]; + memset(patch, 0xEE, sizeof(patch)); + uint64 patchOffset = 10 * sfsBlockSize - 20; + KT_EQ(t, sfsWriteAt(f, patchOffset, patch, sizeof(patch)), (int64)sizeof(patch)); + + KT_EQ(t, sfsReadAt(f, 0, verify, sfsBigSpan), sfsBigSpan); + KT_EQ(t, memcmp(verify + patchOffset, patch, sizeof(patch)), 0); + KT_EQ(t, memcmp(verify, pattern, patchOffset), 0); + + kfree(pattern); + kfree(back); + kfree(verify); + #undef sfsBigSpan +} + +KTEST(sfsNestedDirectoriesAndLookup) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + KT_TRUE(t, sfsCreateDir("etc") > 0); + KT_TRUE(t, sfsCreateDir("etc/sub") > 0); + int32 f = sfsCreateFile("etc/sub/leaf.txt"); + KT_TRUE(t, f > 0); + KT_EQ(t, sfsWriteAt(f, 0, "leaf", 4), 4); + + int32 viaLookup = sfsLookup("etc/sub/leaf.txt"); + KT_EQ(t, viaLookup, f); + int32 subDir = sfsLookup("etc/sub"); + KT_TRUE(t, subDir > 0); + KT_EQ(t, sfsTypeOf(subDir), sfsTypeDir); + KT_EQ(t, sfsLookup("etc/nope"), -1); + KT_EQ(t, sfsLookup("nope/at/all"), -1); +} + +KTEST(sfsDuplicateCreateRejected) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + KT_TRUE(t, sfsCreateFile("dup.txt") > 0); + KT_EQ(t, sfsCreateFile("dup.txt"), -1); + KT_EQ(t, sfsCreateDir("dup.txt"), -1); +} + +KTEST(sfsRemoveFileFreesBlocksForReuse) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("tofree.bin"); + KT_TRUE(t, f > 0); + #define sfsFreeChunk (4 * sfsBlockSize) + uint8* chunk = (uint8*)kmalloc(sfsFreeChunk); + KT_NOTNULL(t, chunk); + if (!chunk) + return; + memset(chunk, 0x42, sfsFreeChunk); + KT_EQ(t, sfsWriteAt(f, 0, chunk, sfsFreeChunk), sfsFreeChunk); + kfree(chunk); + #undef sfsFreeChunk + + KT_TRUE(t, sfsRemove("tofree.bin")); + KT_EQ(t, sfsLookup("tofree.bin"), -1); + + /* A same-size file created afterward must succeed, proving the blocks + (and the inode slot) actually came back to the free pool rather than + leaking on every create/remove cycle. */ + int32 again = sfsCreateFile("tofree.bin"); + KT_TRUE(t, again > 0); + KT_EQ(t, sfsSizeOf(again), 0); +} + +KTEST(sfsRmdirRequiresEmpty) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + KT_TRUE(t, sfsCreateDir("full") > 0); + KT_TRUE(t, sfsCreateFile("full/child.txt") > 0); + KT_FALSE(t, sfsRemove("full")); + + KT_TRUE(t, sfsRemove("full/child.txt")); + KT_TRUE(t, sfsRemove("full")); + KT_EQ(t, sfsLookup("full"), -1); +} + +KTEST(sfsReaddirListsAllEntriesAndSwapRemoveKeepsRest) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 dir = sfsCreateDir("listing"); + KT_TRUE(t, dir > 0); + KT_TRUE(t, sfsCreateFile("listing/a") > 0); + KT_TRUE(t, sfsCreateFile("listing/b") > 0); + KT_TRUE(t, sfsCreateFile("listing/c") > 0); + + uint32 seen = 0; + char name[sfsNameMax + 1]; + uint32 type; + for (uint32 i = 0; sfsReaddirAt(dir, i, name, sizeof(name), &type); i++) { + KT_EQ(t, type, sfsTypeFile); + seen++; + } + KT_EQ(t, seen, 3); + + KT_TRUE(t, sfsRemove("listing/b")); + KT_NE(t, sfsLookup("listing/a"), -1); + KT_NE(t, sfsLookup("listing/c"), -1); + KT_EQ(t, sfsLookup("listing/b"), -1); + + seen = 0; + for (uint32 i = 0; sfsReaddirAt(dir, i, name, sizeof(name), &type); i++) + seen++; + KT_EQ(t, seen, 2); +} + +KTEST(sfsTruncateShrinksAndFreesBlocks) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("shrink.bin"); + KT_TRUE(t, f > 0); + #define sfsShrinkChunk (5 * sfsBlockSize) + uint8* chunk = (uint8*)kmalloc(sfsShrinkChunk); + KT_NOTNULL(t, chunk); + if (!chunk) + return; + memset(chunk, 0x7A, sfsShrinkChunk); + KT_EQ(t, sfsWriteAt(f, 0, chunk, sfsShrinkChunk), sfsShrinkChunk); + + KT_TRUE(t, sfsTruncate(f, sfsBlockSize)); + KT_EQ(t, sfsSizeOf(f), sfsBlockSize); + + uint8 back[sfsBlockSize]; + KT_EQ(t, sfsReadAt(f, 0, back, sfsBlockSize), sfsBlockSize); + KT_EQ(t, memcmp(back, chunk, sfsBlockSize), 0); + kfree(chunk); + #undef sfsShrinkChunk + + KT_TRUE(t, sfsTruncate(f, 0)); + KT_EQ(t, sfsSizeOf(f), 0); +} + +KTEST(sfsPersistsAcrossUnmountRemount) +{ + /* The real point of this test: sfsUnmount() drops every in-memory + structure (bitmaps freed, mounted flag cleared). If sfsMount() can + still find the file and its exact bytes afterward, the filesystem is + genuinely backed by disk, not by a cache that happens to survive. */ + if (!KT_TRUE(t, sfsFormat())) + return; +