aboutsummaryrefslogtreecommitdiff
path: root/kernel/src/test_support
diff options
context:
space:
mode:
authorMarcel Braun <marcel.braun@ost.ch>2026-04-02 08:48:00 +0200
committerMarcel Braun <marcel.braun@ost.ch>2026-04-02 08:48:00 +0200
commit0c01a95325b26151ff3c9a70142f5dc83ff7d53f (patch)
tree9bf034f544ae773b653554a54edfce232f835754 /kernel/src/test_support
parent022d3e872de9c5a6a52c67f74af13706552330c0 (diff)
parent3eb680cf5bcef626505cac82820996d8db4170d7 (diff)
downloadteachos-0c01a95325b26151ff3c9a70142f5dc83ff7d53f.tar.xz
teachos-0c01a95325b26151ff3c9a70142f5dc83ff7d53f.zip
Merge branch 'fmorgner/develop-SA-FS26/kernel-bht' into 'develop-BA-FS26'
Add experimental support for kernel tests See merge request teachos/kernel!20
Diffstat (limited to 'kernel/src/test_support')
-rw-r--r--kernel/src/test_support/kapi/cio.cpp43
-rw-r--r--kernel/src/test_support/kapi/cpu.cpp41
-rw-r--r--kernel/src/test_support/kapi/interrupts.cpp11
-rw-r--r--kernel/src/test_support/kapi/memory.cpp71
-rw-r--r--kernel/src/test_support/log_buffer.cpp39
-rw-r--r--kernel/src/test_support/page_mapper.cpp78
-rw-r--r--kernel/src/test_support/simulated_memory.cpp93
-rw-r--r--kernel/src/test_support/state_reset_listener.cpp50
8 files changed, 426 insertions, 0 deletions
diff --git a/kernel/src/test_support/kapi/cio.cpp b/kernel/src/test_support/kapi/cio.cpp
new file mode 100644
index 0000000..35452d4
--- /dev/null
+++ b/kernel/src/test_support/kapi/cio.cpp
@@ -0,0 +1,43 @@
+#include <kapi/cio.hpp>
+
+#include "kernel/test_support/log_buffer.hpp"
+
+#include <iostream>
+#include <string>
+#include <string_view>
+
+namespace kapi::cio
+{
+
+ namespace
+ {
+
+ class test_output_device : public output_device
+ {
+ public:
+ test_output_device() = default;
+
+ auto write(output_stream stream, std::string_view text) -> void override
+ {
+ auto & standard_stream = stream == output_stream::stdout ? std::cout : std::cerr;
+ standard_stream << text;
+ if (text != "\n")
+ {
+ kernel::tests::log_buffer::append(std::string{text});
+ }
+ }
+ } device{};
+
+ } // namespace
+
+ auto init() -> void
+ {
+ set_output_device(device);
+ }
+
+ auto reset() -> void
+ {
+ kernel::tests::log_buffer::clear();
+ }
+
+} // namespace kapi::cio \ No newline at end of file
diff --git a/kernel/src/test_support/kapi/cpu.cpp b/kernel/src/test_support/kapi/cpu.cpp
new file mode 100644
index 0000000..6592d15
--- /dev/null
+++ b/kernel/src/test_support/kapi/cpu.cpp
@@ -0,0 +1,41 @@
+#include "kernel/test_support/cpu.hpp"
+
+#include <kapi/cpu.hpp>
+
+#include <atomic>
+#include <stdexcept>
+
+namespace kapi::cpu
+{
+
+ namespace
+ {
+ auto static initialized = std::atomic_flag{};
+ }
+
+ auto reset() -> void
+ {
+ if (!initialized.test())
+ {
+ throw std::logic_error{"kapi::cpu::reset() called before kapi::cpu::init()"};
+ }
+
+ initialized.clear();
+ }
+
+ auto init() -> void
+ {
+ if (initialized.test_and_set())
+ {
+ throw std::logic_error("kapi::cpu::init() called more than once");
+ }
+
+ // TODO: make sure that simulated interrupt can run.
+ }
+
+ auto halt() -> void
+ {
+ throw kernel::tests::cpu::halt{};
+ }
+
+} // namespace kapi::cpu \ No newline at end of file
diff --git a/kernel/src/test_support/kapi/interrupts.cpp b/kernel/src/test_support/kapi/interrupts.cpp
new file mode 100644
index 0000000..0077266
--- /dev/null
+++ b/kernel/src/test_support/kapi/interrupts.cpp
@@ -0,0 +1,11 @@
+#include <kapi/interrupts.hpp>
+
+namespace kapi::interrupts
+{
+
+ auto enable() -> void
+ {
+ // TODO: enable simulated interrupts.
+ }
+
+} // namespace kapi::interrupts \ No newline at end of file
diff --git a/kernel/src/test_support/kapi/memory.cpp b/kernel/src/test_support/kapi/memory.cpp
new file mode 100644
index 0000000..f244b7f
--- /dev/null
+++ b/kernel/src/test_support/kapi/memory.cpp
@@ -0,0 +1,71 @@
+#include "kapi/memory.hpp"
+
+#include <kapi/memory.hpp>
+
+#include "kernel/test_support/bump_frame_allocator.hpp"
+#include "kernel/test_support/page_mapper.hpp"
+
+#include <kstd/units>
+
+#include <optional>
+
+namespace
+{
+ //! The size of the simulated RAM.
+ constexpr auto memory_size = kstd::units::MiB(32);
+ constexpr auto number_of_frames = memory_size / kapi::memory::frame::size;
+
+ auto constinit bump_allocator = std::optional<kernel::tests::bump_frame_allocator>{};
+ auto constinit test_mapper = std::optional<kernel::tests::page_mapper>{};
+
+ auto constinit old_allocator = std::optional<kapi::memory::frame_allocator *>{};
+ auto constinit old_mapper = std::optional<kapi::memory::page_mapper *>{};
+
+ auto handoff_to_kernel_pmm(kapi::memory::frame_allocator & new_allocator) -> void
+ {
+ auto first_free_frame = bump_allocator->next_free_frame;
+ auto number_of_free_frames = number_of_frames - first_free_frame;
+ new_allocator.release_many({kapi::memory::frame{first_free_frame}, number_of_free_frames});
+ }
+
+} // namespace
+
+namespace kapi::memory
+{
+
+ auto init() -> void
+ {
+ bump_allocator.emplace();
+ test_mapper.emplace(memory_size);
+
+ old_allocator = set_frame_allocator(*bump_allocator);
+ old_mapper = set_page_mapper(*test_mapper);
+
+ init_pmm(memory_size / frame::size, handoff_to_kernel_pmm);
+ }
+
+ auto reset() -> void
+ {
+ if (old_allocator && *old_allocator)
+ {
+ set_frame_allocator(**old_allocator);
+ }
+
+ if (old_mapper && *old_mapper)
+ {
+ set_page_mapper(**old_mapper);
+ }
+
+ bump_allocator.reset();
+ test_mapper.reset();
+ }
+
+} // namespace kapi::memory
+
+namespace kernel::tests::memory
+{
+ auto heap_base() -> kapi::memory::linear_address
+ {
+ return test_mapper->memory.heap_base();
+ }
+} // namespace kernel::tests::memory \ No newline at end of file
diff --git a/kernel/src/test_support/log_buffer.cpp b/kernel/src/test_support/log_buffer.cpp
new file mode 100644
index 0000000..36ed15e
--- /dev/null
+++ b/kernel/src/test_support/log_buffer.cpp
@@ -0,0 +1,39 @@
+#include "kernel/test_support/log_buffer.hpp"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+namespace kernel::tests::log_buffer
+{
+
+ namespace
+ {
+ std::vector<std::string> recorded_messages{};
+ }
+
+ auto append(std::string const & message) -> void
+ {
+ recorded_messages.push_back(message);
+ }
+
+ auto clear() -> void
+ {
+ recorded_messages.clear();
+ }
+
+ auto flat_messages() -> std::string
+ {
+ return std::ranges::fold_left(recorded_messages, std::string{},
+ [](std::string accumulator, std::string const & message) {
+ accumulator += message;
+ return accumulator;
+ });
+ }
+
+ auto messages() -> std::vector<std::string> const &
+ {
+ return recorded_messages;
+ }
+
+} // namespace kernel::tests::log_buffer
diff --git a/kernel/src/test_support/page_mapper.cpp b/kernel/src/test_support/page_mapper.cpp
new file mode 100644
index 0000000..abdcae5
--- /dev/null
+++ b/kernel/src/test_support/page_mapper.cpp
@@ -0,0 +1,78 @@
+#include "kernel/test_support/page_mapper.hpp"
+
+#include "kapi/memory.hpp"
+
+#include <kstd/units>
+
+#include <cstddef>
+#include <format>
+#include <stdexcept>
+#include <sys/mman.h>
+
+namespace kernel::tests
+{
+
+ page_mapper::page_mapper(kstd::units::bytes memory_size)
+ : memory{memory_size}
+ {}
+
+ auto page_mapper::map(kapi::memory::page page, kapi::memory::frame frame, flags) -> std::byte *
+ {
+ auto result = page_mappings.insert({page.number(), frame});
+ if (!result.second)
+ {
+ auto error = std::format("Page {} was already mapped!", page.number());
+ throw std::invalid_argument{error};
+ }
+
+ auto page_address = page.start_address();
+ auto sandbox_start = memory.heap_base();
+ auto sandbox_end = sandbox_start + memory.heap_size();
+
+ if (page_address >= sandbox_start && page_address < sandbox_end)
+ {
+ auto virtual_target = static_cast<std::byte *>(page_address);
+ auto physical_offset = frame.number() * kapi::memory::frame::size;
+ auto mapped_ptr = mmap(virtual_target, kapi::memory::page::size.value, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_FIXED, memory.memory_descriptor(), physical_offset.value);
+ if (mapped_ptr == MAP_FAILED)
+ {
+ throw std::runtime_error("Failed to map page");
+ }
+
+ return static_cast<std::byte *>(mapped_ptr);
+ }
+ else if (page_address >= kapi::memory::mmio_base)
+ {
+ throw std::runtime_error("MMIO mapping not yet supported in testing!");
+ }
+ else if (page_address >= kapi::memory::higher_half_direct_map_base)
+ {
+ auto offset = frame.number() * kapi::memory::frame::size;
+ return memory.ram_base() + offset;
+ }
+
+ return nullptr;
+ }
+
+ auto page_mapper::unmap(kapi::memory::page page) -> void
+ {
+ if (!try_unmap(page))
+ {
+ auto error = std::format("Page {} was never mapped!", page.number());
+ throw std::invalid_argument{error};
+ }
+ }
+
+ auto page_mapper::try_unmap(kapi::memory::page page) noexcept -> bool
+ {
+ if (page_mappings.contains(page.number()))
+ {
+ page_mappings.erase(page.number());
+ return true;
+ }
+
+ return false;
+ }
+
+} // namespace kernel::tests \ No newline at end of file
diff --git a/kernel/src/test_support/simulated_memory.cpp b/kernel/src/test_support/simulated_memory.cpp
new file mode 100644
index 0000000..fa3d36c
--- /dev/null
+++ b/kernel/src/test_support/simulated_memory.cpp
@@ -0,0 +1,93 @@
+#include "kernel/test_support/simulated_memory.hpp"
+
+#include "kapi/memory.hpp"
+
+#include <kstd/units>
+
+#include <cstddef>
+#include <cstring>
+#include <stdexcept>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace kstd::units_literals;
+
+namespace kernel::tests
+{
+ namespace
+ {
+ constexpr auto virtual_size = 1_GiB;
+ }
+
+ simulated_memory::simulated_memory(kstd::units::bytes size)
+ : m_memory_descriptor{memfd_create("teachos_simulated_memory", 0)}
+ , m_size{size}
+ {
+ if (m_memory_descriptor < 0)
+ {
+ throw std::runtime_error("Failed to create simulated memory");
+ }
+
+ if (ftruncate(m_memory_descriptor, static_cast<off_t>(m_size.value)) < 0)
+ {
+ throw std::runtime_error("Failed to resize simulated memory");
+ }
+
+ auto mapped_pointer = mmap(nullptr, m_size.value, PROT_READ | PROT_WRITE, MAP_SHARED, m_memory_descriptor, 0);
+ if (mapped_pointer == MAP_FAILED)
+ {
+ throw std::runtime_error("Failed to map simulated memory");
+ }
+
+ m_physical_base = static_cast<std::byte *>(mapped_pointer);
+
+ auto virtual_pointer = mmap(nullptr, virtual_size.value, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (virtual_pointer == MAP_FAILED)
+ {
+ throw std::runtime_error("Failed to map simulated memory");
+ }
+
+ m_virtual_base = static_cast<std::byte *>(virtual_pointer);
+
+ clear();
+ }
+
+ simulated_memory::~simulated_memory()
+ {
+ munmap(m_physical_base, m_size.value);
+ munmap(m_virtual_base, virtual_size.value);
+ close(m_memory_descriptor);
+ }
+
+ auto simulated_memory::clear() -> void
+ {
+ std::memset(m_physical_base, 0, m_size.value);
+ }
+
+ auto simulated_memory::ram_base() noexcept -> std::byte *
+ {
+ return m_physical_base;
+ }
+
+ auto simulated_memory::ram_base() const noexcept -> std::byte const *
+ {
+ return m_physical_base;
+ }
+
+ auto simulated_memory::heap_base() const noexcept -> kapi::memory::linear_address
+ {
+ return kapi::memory::linear_address{m_virtual_base};
+ }
+
+ auto simulated_memory::heap_size() const noexcept -> kstd::units::bytes
+ {
+ return virtual_size;
+ }
+
+ auto simulated_memory::memory_descriptor() const noexcept -> int
+ {
+ return m_memory_descriptor;
+ }
+
+} // namespace kernel::tests \ No newline at end of file
diff --git a/kernel/src/test_support/state_reset_listener.cpp b/kernel/src/test_support/state_reset_listener.cpp
new file mode 100644
index 0000000..e201a10
--- /dev/null
+++ b/kernel/src/test_support/state_reset_listener.cpp
@@ -0,0 +1,50 @@
+#include "kapi/cio.hpp"
+#include "kapi/cpu.hpp"
+#include "kapi/memory.hpp"
+
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/interfaces/catch_interfaces_reporter.hpp>
+#include <catch2/reporters/catch_reporter_event_listener.hpp>
+#include <catch2/reporters/catch_reporter_registrars.hpp>
+
+namespace kapi
+{
+ namespace cio
+ {
+ auto reset() -> void;
+ }
+
+ namespace cpu
+ {
+ auto reset() -> void;
+ }
+
+ namespace memory
+ {
+ auto reset() -> void;
+ }
+} // namespace kapi
+
+struct state_reset_listener : Catch::EventListenerBase
+{
+ using EventListenerBase::EventListenerBase;
+
+ void testCaseStarting(Catch::TestCaseInfo const &) override
+ {
+ kapi::cio::init();
+ kapi::cpu::init();
+ kapi::memory::init();
+ }
+
+ void testCaseEnded(Catch::TestCaseStats const &) override
+ {
+ kapi::memory::reset();
+ kapi::cpu::reset();
+ kapi::cio::reset();
+ }
+};
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+CATCH_REGISTER_LISTENER(state_reset_listener);
+#pragma GCC diagnostic pop \ No newline at end of file