aboutsummaryrefslogtreecommitdiff
path: root/kernel/src/filesystem/ext2/inode.tests.cpp
blob: 795ff1011c0b45daa941eb1302eb3437a4f1c341 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include "kernel/filesystem/ext2/inode.hpp"

#include "kernel/devices/storage/management.hpp"
#include "kernel/filesystem/ext2/filesystem.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 <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{};

    THEN("the inode is initialized and has the kind regular")
    {
      auto inode = kernel::filesystem::ext2::inode{&fs};
      REQUIRE(inode.is_regular());
      REQUIRE(!inode.is_directory());
      REQUIRE(!inode.is_device());
    }
  }

  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 fs = kernel::filesystem::ext2::filesystem{};
    REQUIRE(fs.mount(boot_device) == 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 read stops when block mapping resolves to zero", "[filesystem][ext2][inode]")
{
  auto const block_size = 1024;
  GIVEN("an ext2 inode without 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 fs = kernel::filesystem::ext2::filesystem{};
    REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success);

    auto inode = kernel::filesystem::ext2::inode{&fs};
    inode.m_data.blocks = 2;
    inode.m_data.block[0] = 0;

    auto buffer = kstd::vector<std::byte>(32, std::byte{0xAB});

    THEN("no bytes are read")
    {
      auto const bytes_read = inode.read(buffer.data(), 0, buffer.size());
      REQUIRE(bytes_read == 0);
    }
  }
}

SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]")
{
  auto const block_size = 1024;
  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 fs = kernel::filesystem::ext2::filesystem{};
    REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success);

    auto inode = kernel::filesystem::ext2::inode{&fs};
    inode.m_data.blocks = 2;
    inode.m_data.block[0] = 20;
    kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6);
    inode.m_data.block[1] = 21;
    kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6);

    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};

    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);
    }
  }
}