diff options
| author | Lukas Oesch <lukas.oesch@ost.ch> | 2026-06-10 10:40:46 +0200 |
|---|---|---|
| committer | Lukas Oesch <lukas.oesch@ost.ch> | 2026-06-10 10:40:46 +0200 |
| commit | 33abd5cf264cb9e34121082105b0bc17b3cf7a36 (patch) | |
| tree | 36b15d53fea04f4f9d9af817100f7ad013bd9b5c /kernel/src/filesystem/ext2/inode.tests.cpp | |
| parent | d01caf1c4aef3c89c68b9d1cc9fe56445f0860b5 (diff) | |
| parent | 7e27130c342b7299a1d2188a7192a7f17b5ac2ad (diff) | |
| download | kernel-33abd5cf264cb9e34121082105b0bc17b3cf7a36.tar.xz kernel-33abd5cf264cb9e34121082105b0bc17b3cf7a36.zip | |
Merge of BA-FS26 branch into develop
See merge request teachos/kernel!49
Diffstat (limited to 'kernel/src/filesystem/ext2/inode.tests.cpp')
| -rw-r--r-- | kernel/src/filesystem/ext2/inode.tests.cpp | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/kernel/src/filesystem/ext2/inode.tests.cpp b/kernel/src/filesystem/ext2/inode.tests.cpp new file mode 100644 index 0000000..4aecc04 --- /dev/null +++ b/kernel/src/filesystem/ext2/inode.tests.cpp @@ -0,0 +1,376 @@ +#include <kernel/filesystem/ext2/inode.hpp> + +#include <kernel/devices/storage/management.hpp> +#include <kernel/filesystem/device_inode.hpp> +#include <kernel/filesystem/ext2/filesystem.hpp> +#include <kernel/filesystem/ext2/superblock.hpp> +#include <kernel/filesystem/filesystem.hpp> +#include <kernel/test_support/cpu.hpp> +#include <kernel/test_support/devices/block_device.hpp> +#include <kernel/test_support/filesystem/ext2.hpp> +#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp> + +#include <kstd/memory> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <algorithm> +#include <cstddef> +#include <filesystem> +#include <string_view> + +SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]") +{ + GIVEN("an ext2 filesystem") + { + auto fs = kernel::filesystem::ext2::filesystem{}; + auto data = kernel::filesystem::ext2::inode_data{}; + + THEN("the inode is initialized with regular file mode in data and has the kind regular") + { + data.mode = kernel::filesystem::ext2::constants::mode_regular; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + + THEN("the inode is initialized with directory mode in data and has the kind directory") + { + data.mode = kernel::filesystem::ext2::constants::mode_directory; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + + THEN("the inode is initialized with symbolic link mode in data and has the kind symbolic link") + { + data.mode = kernel::filesystem::ext2::constants::mode_symbolic_link; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE(inode.is_symbolic_link()); + } + + THEN("the inode is initialized with zero mode in data and has no specific kind") + { + data.mode = 0; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + } + + GIVEN("no filesystem (null pointer)") + { + THEN("constructing an inode with a null filesystem pointer panics") + { + REQUIRE_THROWS_AS(kernel::filesystem::ext2::inode(nullptr, {}), kernel::tests::cpu::halt); + } + } +} + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode reads from real image", + "[filesystem][ext2][inode][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem and a regular file inode") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::management::get().determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto information = fs.lookup(fs.root_inode(), "information"); + REQUIRE(information != nullptr); + auto file = fs.lookup(information, "info_1.txt"); + REQUIRE(file != nullptr); + REQUIRE(file->is_regular()); + + THEN("reading from offset zero returns expected file prefix") + { + auto buffer = kstd::vector<std::byte>(6); + auto const bytes_read = file->read(buffer.data(), 0, buffer.size()); + + REQUIRE(bytes_read == 6); + + auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read}; + REQUIRE(text == "info_1"); + } + + THEN("reading with an offset returns the expected byte") + { + auto buffer = kstd::vector<std::byte>(1); + auto const bytes_read = file->read(buffer.data(), 5, buffer.size()); + + REQUIRE(bytes_read == 1); + REQUIRE(static_cast<char>(buffer[0]) == '1'); + } + } +} + +SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024uz; + GIVEN("an ext2 inode with only direct mapped data blocks") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.block[0] = 30; + data.block[1] = 0; + data.block[2] = 31; + data.size = block_size * 3; + + kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5); + kernel::tests::filesystem::ext2::write_bytes(*device, 31 * block_size, "World!", 6); + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + auto buffer = kstd::vector<std::byte>(data.size, std::byte{0xAB}); + + THEN("correct number of bytes are read and holes are returned as zeros") + { + auto const bytes_read = inode.read(buffer.data(), 0, buffer.size()); + REQUIRE(bytes_read == data.size); + + auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read}; + REQUIRE(text.substr(0, 5) == "Hello"); + REQUIRE(std::ranges::all_of(text.substr(5, block_size - 5), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(2 * block_size, 6) == "World!"); + REQUIRE(std::ranges::all_of(text.substr(2 * block_size + 6, 3 * block_size), [](char c) { return c == '\0'; })); + } + } + + GIVEN("an ext2 indode with file holes in singly indirect blocks") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.block[0] = 30; + data.block[12] = 31; + data.size = block_size * 15; + + kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size, 50); + kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 4, 0); + kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 8, 51); + + kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5); + kernel::tests::filesystem::ext2::write_bytes(*device, 50 * block_size, "Blub", 4); + kernel::tests::filesystem::ext2::write_bytes(*device, 51 * block_size, "World!", 6); + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + auto buffer = kstd::vector<std::byte>(data.size, std::byte{0xAB}); + + THEN("correct number of bytes are read and holes are returned as zeros") + { + auto const bytes_read = inode.read(buffer.data(), 0, buffer.size()); + REQUIRE(bytes_read == data.size); + + auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read}; + REQUIRE(text.substr(0, 5) == "Hello"); + REQUIRE(std::ranges::all_of(text.substr(5, 12 * block_size - 5), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(12 * block_size, 4) == "Blub"); + REQUIRE( + std::ranges::all_of(text.substr(12 * block_size + 4, 2 * block_size - 4), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(14 * block_size, 6) == "World!"); + REQUIRE( + std::ranges::all_of(text.substr(14 * block_size + 6, 1 * block_size - 6), [](char c) { return c == '\0'; })); + } + } + + GIVEN("an ext2 inode with zero singly indirect block pointer") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.block[12] = 0; + data.size = block_size * 15; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + auto buffer = kstd::vector<std::byte>(block_size * 15, std::byte{0xAB}); + + THEN("all direct blocks are zero when singly indirect block pointer is zero") + { + auto const bytes_read = inode.read(buffer.data(), 0, buffer.size()); + REQUIRE(bytes_read == buffer.size()); + REQUIRE(std::ranges::all_of(buffer, [](std::byte c) { return c == std::byte{0x00}; })); + } + } +} + +SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024uz; + GIVEN("an ext2 inode with two direct blocks and a block size of 1024 bytes") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto inode_data = kernel::filesystem::ext2::inode_data{}; + inode_data.size = block_size * 2; + inode_data.block[0] = 20; + kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6); + inode_data.block[1] = 21; + kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6); + auto inode = kernel::filesystem::ext2::inode{&fs, inode_data}; + + auto buffer = kstd::vector<std::byte>(12, std::byte{0x00}); + + THEN("reading across the block boundary returns the combined content") + { + auto const bytes_read = inode.read(buffer.data(), block_size - 6, buffer.size()); + REQUIRE(bytes_read == 12); + + auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read}; + REQUIRE(text == "Hello World!"); + } + } +} + +SCENARIO("Ext2 inode write is not implemented", "[filesystem][ext2][inode]") +{ + GIVEN("an ext2 inode") + { + auto fs = kernel::filesystem::ext2::filesystem{}; + auto inode = kernel::filesystem::ext2::inode{&fs, kernel::filesystem::ext2::inode_data{}}; + + THEN("writing to the inode panics") + { + auto buffer = kstd::vector<std::byte>(32, std::byte{0x00}); + REQUIRE_THROWS_AS(inode.write(buffer.data(), 0, buffer.size()), kernel::tests::cpu::halt); + } + } +} + +SCENARIO("Ext2 inode get_size() correctly returns size depending on revision level", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024uz; + + auto superblock = kernel::filesystem::ext2::superblock{}; + superblock.magic = kernel::filesystem::ext2::constants::magic_number; + superblock.log_block_size = 0; + superblock.blocks_count = 64; + superblock.blocks_per_group = 64; + superblock.inodes_per_group = 32; + superblock.inode_size = 128; + + GIVEN("an ext2 inode with good old revision and inode_data.size = 256, inode_data.dir_acl = 32") + { + superblock.rev_level = 0; + + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.size = 256; + data.dir_acl = 32; + + THEN("the inode size is 256 if mode = regular") + { + data.mode = kernel::filesystem::ext2::constants::mode_regular; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 256); + } + + THEN("the inode size is 256 if mode = directory") + { + data.mode = kernel::filesystem::ext2::constants::mode_directory; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 256); + } + } + + GIVEN("an ext2 inode with good dynamic revision and inode_data.size = 256, inode_data.dir_acl = 32") + { + superblock.rev_level = 1; + + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.size = 256; + data.dir_acl = 32; + + THEN("the inode size is 256 if mode = regular") + { + data.mode = kernel::filesystem::ext2::constants::mode_regular; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 0x0000'0020'0000'0100); + } + + THEN("the inode size is 256 if mode = directory") + { + data.mode = kernel::filesystem::ext2::constants::mode_directory; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 256); + } + } +}
\ No newline at end of file |
