diff --git a/Src/IACore/imp/cpp/AsyncOps.cpp b/Src/IACore/imp/cpp/AsyncOps.cpp index b8168b2..a3976b8 100644 --- a/Src/IACore/imp/cpp/AsyncOps.cpp +++ b/Src/IACore/imp/cpp/AsyncOps.cpp @@ -139,8 +139,7 @@ auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void { } auto AsyncOps::get_worker_count() -> WorkerId { - // +1 for MainThread (Work Stealing) - return static_cast(s_schedule_workers.size() + 1); + return static_cast(s_schedule_workers.size()); } auto AsyncOps::schedule_worker_loop(std::stop_token stop_token, diff --git a/Src/IACore/imp/cpp/CLI.cpp b/Src/IACore/imp/cpp/CLI.cpp index 33e1675..8e2c59b 100644 --- a/Src/IACore/imp/cpp/CLI.cpp +++ b/Src/IACore/imp/cpp/CLI.cpp @@ -15,16 +15,12 @@ #include -namespace IACore -{ - CLIParser::CLIParser(Span args) : m_argList(args) - { - assert(args.size()); +namespace IACore { +CLIParser::CLIParser(Span args) : m_arg_list(args) { + m_current_arg = m_arg_list.begin(); - m_currentArg = m_argList.begin(); - - // Skip executable path - if (m_currentArg != m_argList.end()) - m_currentArg++; - } + // Skip executable path + if (m_current_arg != m_arg_list.end()) + m_current_arg++; +} } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/imp/cpp/FileOps.cpp b/Src/IACore/imp/cpp/FileOps.cpp index bb8026e..b666191 100644 --- a/Src/IACore/imp/cpp/FileOps.cpp +++ b/Src/IACore/imp/cpp/FileOps.cpp @@ -68,7 +68,7 @@ auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner) (DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF), w_name.c_str()); } else { - h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, w_name.c_str()); + h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, false, w_name.c_str()); } if (h_map == NULL) { @@ -201,7 +201,7 @@ auto FileOps::stream_to_file(const Path &path, bool overwrite) if (!overwrite && std::filesystem::exists(path)) { return fail("File already exists: {}", path.string()); } - return StreamWriter::create(path); + return StreamWriter::create_from_file(path); } auto FileOps::stream_from_file(const Path &path) -> Result { diff --git a/Src/IACore/imp/cpp/ProcessOps.cpp b/Src/IACore/imp/cpp/ProcessOps.cpp index 54cb572..da22b82 100644 --- a/Src/IACore/imp/cpp/ProcessOps.cpp +++ b/Src/IACore/imp/cpp/ProcessOps.cpp @@ -133,7 +133,7 @@ void ProcessOps::terminate_process(const Box &handle) { } #if IA_PLATFORM_WINDOWS - HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + HANDLE h_process = OpenProcess(PROCESS_TERMINATE, false, pid); if (h_process != NULL) { ::TerminateProcess(h_process, 9); CloseHandle(h_process); @@ -150,7 +150,7 @@ auto ProcessOps::spawn_process_windows( std::atomic &id) -> Result { #if IA_PLATFORM_WINDOWS SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL, - TRUE}; // Allow inheritance + true}; // Allow inheritance HANDLE h_read = NULL; HANDLE h_write = NULL; @@ -174,7 +174,7 @@ auto ProcessOps::spawn_process_windows( // Windows command line needs to be mutable and concatenated String command_line = std::format("\"{}\" {}", command, args); - BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, TRUE, 0, + BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, true, 0, NULL, NULL, &si, &pi); // Close write end in parent, otherwise ReadFile never returns EOF! diff --git a/Src/IACore/imp/cpp/StreamReader.cpp b/Src/IACore/imp/cpp/StreamReader.cpp index 387c520..10ebe90 100644 --- a/Src/IACore/imp/cpp/StreamReader.cpp +++ b/Src/IACore/imp/cpp/StreamReader.cpp @@ -40,6 +40,41 @@ StreamReader::StreamReader(Span data) : m_data(data.data()), m_data_size(data.size()), m_storage_type(StorageType::NonOwning) {} +StreamReader::StreamReader(StreamReader &&other) + : m_data(other.m_data), m_cursor(other.m_cursor), + m_data_size(other.m_data_size), + m_owning_vector(std::move(other.m_owning_vector)), + m_storage_type(other.m_storage_type) { + other.m_storage_type = StorageType::NonOwning; + other.m_data = {}; + other.m_data_size = 0; + + if (m_storage_type == StorageType::OwningVector) + m_data = m_owning_vector.data(); +} + +auto StreamReader::operator=(StreamReader &&other) -> StreamReader & { + if (this != &other) { + if (m_storage_type == StorageType::OwningMmap) { + FileOps::unmap_file(m_data); + } + + m_data = other.m_data; + m_cursor = other.m_cursor; + m_data_size = other.m_data_size; + m_owning_vector = std::move(other.m_owning_vector); + m_storage_type = other.m_storage_type; + + if (m_storage_type == StorageType::OwningVector) + m_data = m_owning_vector.data(); + + other.m_storage_type = StorageType::NonOwning; + other.m_data = {}; + other.m_data_size = 0; + } + return *this; +} + StreamReader::~StreamReader() { if (m_storage_type == StorageType::OwningMmap) { FileOps::unmap_file(m_data); diff --git a/Src/IACore/imp/cpp/StreamWriter.cpp b/Src/IACore/imp/cpp/StreamWriter.cpp index 17f4bd5..ac1a4f9 100644 --- a/Src/IACore/imp/cpp/StreamWriter.cpp +++ b/Src/IACore/imp/cpp/StreamWriter.cpp @@ -17,9 +17,7 @@ namespace IACore { -auto StreamWriter::create(const Path &path) -> Result { - // Try to open the file to ensure we have write permissions and to truncate - // it. +auto StreamWriter::create_from_file(const Path &path) -> Result { FILE *f = std::fopen(path.string().c_str(), "wb"); if (!f) { return fail("Failed to open file for writing: {}", path.string()); @@ -43,34 +41,97 @@ StreamWriter::StreamWriter(Span data) : m_buffer(data.data()), m_cursor(0), m_capacity(data.size()), m_storage_type(StorageType::NonOwning) {} -StreamWriter::~StreamWriter() { - if (m_storage_type == StorageType::OwningFile) { - if (m_file_path.empty()) { - return; +StreamWriter::StreamWriter(StreamWriter &&other) + : m_buffer(other.m_buffer), m_cursor(other.m_cursor), + m_capacity(other.m_capacity), m_file_path(other.m_file_path), + m_owning_vector(std::move(other.m_owning_vector)), + m_storage_type(other.m_storage_type) { + other.m_capacity = {}; + other.m_buffer = {}; + other.m_storage_type = StorageType::NonOwning; + + if (m_storage_type == StorageType::OwningVector) + m_buffer = m_owning_vector.data(); +} + +auto StreamWriter::operator=(StreamWriter &&other) -> StreamWriter & { + if (this != &other) { + // Flush current if needed + if (m_storage_type == StorageType::OwningFile) { + if (auto res = flush_to_disk(); !res) { + std::fprintf(stderr, "[IACore] Data loss in StreamWriter move: %s\n", + res.error().c_str()); + } } - FILE *f = std::fopen(m_file_path.string().c_str(), "wb"); - if (f) { - // Write the actual data accumulated in the buffer. - std::fwrite(m_buffer, 1, m_cursor, f); - std::fclose(f); - } else { - // Logger is disabled, print to stderr as a fallback for data loss - // warning. - std::fprintf(stderr, "[IACore] StreamWriter failed to save file: %s\n", - m_file_path.string().c_str()); + m_buffer = other.m_buffer; + m_cursor = other.m_cursor; + m_capacity = other.m_capacity; + m_file_path = std::move(other.m_file_path); // Use move for string/path + m_owning_vector = std::move(other.m_owning_vector); + m_storage_type = other.m_storage_type; + + if (m_storage_type == StorageType::OwningVector) + m_buffer = m_owning_vector.data(); + + other.m_capacity = 0; + other.m_cursor = 0; + other.m_buffer = nullptr; + other.m_storage_type = StorageType::NonOwning; + } + return *this; +} + +StreamWriter::~StreamWriter() { + if (m_storage_type == StorageType::OwningFile) { + // We can't return errors here, so we log them. + // Ideally, the user calls save() before this runs. + if (auto res = flush_to_disk(); !res) { + std::fprintf(stderr, "[IACore] LOST DATA in ~StreamWriter: %s\n", + res.error().c_str()); } } } +auto StreamWriter::flush() -> Result { + auto res = flush_to_disk(); + // Prevent double-write in destructor if save was successful + if (res.has_value()) { + m_storage_type = StorageType::OwningVector; // downgrade to vector so dtor + // doesn't write again + } + return res; +} + +auto StreamWriter::flush_to_disk() -> Result { + if (m_storage_type != StorageType::OwningFile || m_file_path.empty()) { + return {}; + } + + FILE *f = std::fopen(m_file_path.string().c_str(), "wb"); + if (!f) { + return fail("Failed to open file for writing: {}", m_file_path.string()); + } + + usize written = std::fwrite(m_buffer, 1, m_cursor, f); + std::fclose(f); + + if (written != m_cursor) { + return fail("Incomplete write: {} of {} bytes written", written, m_cursor); + } + return {}; +} + auto StreamWriter::write(u8 byte, usize count) -> Result { if (m_cursor + count > m_capacity) { if (m_storage_type == StorageType::NonOwning) { return fail("StreamWriter buffer overflow (NonOwning)"); } - // Growth strategy: Current capacity + (count * 2) - const usize new_capacity = m_capacity + (count << 1); + const usize required = m_cursor + count; + const usize double_cap = m_capacity * 2; + const usize new_capacity = (double_cap > required) ? double_cap : required; + m_owning_vector.resize(new_capacity); m_capacity = m_owning_vector.size(); m_buffer = m_owning_vector.data(); @@ -87,7 +148,12 @@ auto StreamWriter::write(const void *buffer, usize size) -> Result { return fail("StreamWriter buffer overflow (NonOwning)"); } - const usize new_capacity = m_capacity + (size << 1); + // NEW STRATEGY: Max(Double Capacity, Required Size) + // This prevents frequent reallocations for repeated small writes + const usize required = m_cursor + size; + const usize double_cap = m_capacity * 2; + const usize new_capacity = (double_cap > required) ? double_cap : required; + m_owning_vector.resize(new_capacity); m_capacity = m_owning_vector.size(); m_buffer = m_owning_vector.data(); diff --git a/Src/IACore/imp/cpp/StringOps.cpp b/Src/IACore/imp/cpp/StringOps.cpp index ccf0dec..8bea68e 100644 --- a/Src/IACore/imp/cpp/StringOps.cpp +++ b/Src/IACore/imp/cpp/StringOps.cpp @@ -16,28 +16,53 @@ #include namespace IACore { -const String BASE64_CHAR_TABLE = + +static const String BASE64_CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static auto is_base64(u8 c) -> bool { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || (c == '+') || (c == '/'); +} + +static auto get_base64_index(u8 c) -> u8 { + if (c >= 'A' && c <= 'Z') + return c - 'A'; + if (c >= 'a' && c <= 'z') + return c - 'a' + 26; + if (c >= '0' && c <= '9') + return c - '0' + 52; + if (c == '+') + return 62; + if (c == '/') + return 63; + return 0; +} + auto StringOps::encode_base64(Span data) -> String { String result; result.reserve(((data.size() + 2) / 3) * 4); - for (size_t i = 0; i < data.size(); i += 3) { - uint32_t value = 0; - i32 num_bytes = 0; - for (i32 j = 0; j < 3 && (i + j) < data.size(); ++j) { - value = (value << 8) | data[i + j]; - num_bytes++; + + for (usize i = 0; i < data.size(); i += 3) { + u32 b0 = data[i]; + u32 b1 = (i + 1 < data.size()) ? data[i + 1] : 0; + u32 b2 = (i + 2 < data.size()) ? data[i + 2] : 0; + + u32 triple = (b0 << 16) | (b1 << 8) | b2; + + result += BASE64_CHAR_TABLE[(triple >> 18) & 0x3F]; + result += BASE64_CHAR_TABLE[(triple >> 12) & 0x3F]; + + if (i + 1 < data.size()) { + result += BASE64_CHAR_TABLE[(triple >> 6) & 0x3F]; + } else { + result += '='; } - for (i32 j = 0; j < num_bytes + 1; ++j) { - if (j < 4) { - result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F]; - } - } - if (num_bytes < 3) { - for (i32 j = 0; j < (3 - num_bytes); ++j) { - result += '='; - } + + if (i + 2 < data.size()) { + result += BASE64_CHAR_TABLE[triple & 0x3F]; + } else { + result += '='; } } return result; @@ -45,47 +70,53 @@ auto StringOps::encode_base64(Span data) -> String { auto StringOps::decode_base64(const String &data) -> Vec { Vec result; + result.reserve(data.size() * 3 / 4); - const auto is_base64 = [](u8 c) { - return (isalnum(c) || (c == '+') || (c == '/')); - }; + i32 i = 0; + u8 tmp_buf[4]; - i32 in_len = data.size(); - i32 i = 0, j = 0, in = 0; - u8 tmp_buf0[4], tmp_buf1[3]; + for (char c_char : data) { + u8 c = static_cast(c_char); + if (c == '=') { + break; + } + if (!is_base64(c)) { + break; + } - while (in_len-- && (data[in] != '=') && is_base64(data[in])) { - tmp_buf0[i++] = data[in]; - in++; + tmp_buf[i++] = c; if (i == 4) { - for (i = 0; i < 4; i++) - tmp_buf0[i] = BASE64_CHAR_TABLE.find(tmp_buf0[i]); + u8 n0 = get_base64_index(tmp_buf[0]); + u8 n1 = get_base64_index(tmp_buf[1]); + u8 n2 = get_base64_index(tmp_buf[2]); + u8 n3 = get_base64_index(tmp_buf[3]); - tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4); - tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2); - tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3]; + result.push_back((n0 << 2) | ((n1 & 0x30) >> 4)); + result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2)); + result.push_back(((n2 & 0x03) << 6) | n3); - for (i = 0; (i < 3); i++) - result.push_back(tmp_buf1[i]); i = 0; } } - if (i) { - for (j = i; j < 4; j++) - tmp_buf0[j] = 0; + if (i > 0) { + for (i32 j = i; j < 4; ++j) { + tmp_buf[j] = 'A'; // Pad with 'A' (index 0) + } - for (j = 0; j < 4; j++) - tmp_buf0[j] = BASE64_CHAR_TABLE.find(tmp_buf0[j]); + u8 n0 = get_base64_index(tmp_buf[0]); + u8 n1 = get_base64_index(tmp_buf[1]); + u8 n2 = get_base64_index(tmp_buf[2]); - tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4); - tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2); - tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3]; - - for (j = 0; (j < i - 1); j++) - result.push_back(tmp_buf1[j]); + if (i > 1) { + result.push_back((n0 << 2) | ((n1 & 0x30) >> 4)); + } + if (i > 2) { + result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2)); + } } return result; } + } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/ADT/RingBuffer.hpp b/Src/IACore/inc/IACore/ADT/RingBuffer.hpp index 2bdfb6e..7248b15 100644 --- a/Src/IACore/inc/IACore/ADT/RingBuffer.hpp +++ b/Src/IACore/inc/IACore/ADT/RingBuffer.hpp @@ -93,7 +93,7 @@ private: // Implementation // ============================================================================= -auto RingBufferView::default_instance() -> RingBufferView { +inline auto RingBufferView::default_instance() -> RingBufferView { return RingBufferView(nullptr, {}, false); } @@ -262,7 +262,7 @@ inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size) } } -[[nodiscard]] auto RingBufferView::is_valid() const -> bool { +[[nodiscard]] inline auto RingBufferView::is_valid() const -> bool { return m_control_block && m_data_ptr && m_capacity; } diff --git a/Src/IACore/inc/IACore/CLI.hpp b/Src/IACore/inc/IACore/CLI.hpp index 8158964..5b98c08 100644 --- a/Src/IACore/inc/IACore/CLI.hpp +++ b/Src/IACore/inc/IACore/CLI.hpp @@ -17,54 +17,47 @@ #include -namespace IACore -{ - class CLIParser - { - /* - * PLEASE READ - * - * CLIParser is still very much in it's baby stages. - * Subject to heavy and frequent changes, use with - * caution! - */ +namespace IACore { +class CLIParser { + /* + * PLEASE READ + * + * CLIParser is still very much in it's baby stages. + * Subject to heavy and frequent changes, use with + * caution! + */ - public: - CLIParser(Span args); - ~CLIParser() = default; +public: + CLIParser(Span args); + ~CLIParser() = default; - public: - auto remaining() const -> bool - { - return m_currentArg < m_argList.end(); - } +public: + [[nodiscard]] auto remaining() const -> bool { + return m_current_arg < m_arg_list.end(); + } - auto peek() const -> StringView - { - if (!remaining()) - return ""; - return *m_currentArg; - } + [[nodiscard]] auto peek() const -> StringView { + if (!remaining()) + return ""; + return *m_current_arg; + } - auto next() -> StringView - { - if (!remaining()) - return ""; - return *m_currentArg++; - } + auto next() -> StringView { + if (!remaining()) + return ""; + return *m_current_arg++; + } - auto consume(const StringView &expected) -> bool - { - if (peek() == expected) - { - next(); - return true; - } - return false; - } + auto consume(const StringView &expected) -> bool { + if (peek() == expected) { + next(); + return true; + } + return false; + } - private: - const Span m_argList; - Span::const_iterator m_currentArg; - }; +private: + const Span m_arg_list; + Span::const_iterator m_current_arg; +}; } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/IATest.hpp b/Src/IACore/inc/IACore/IATest.hpp index 2b9c287..e519c0f 100644 --- a/Src/IACore/inc/IACore/IATest.hpp +++ b/Src/IACore/inc/IACore/IATest.hpp @@ -21,295 +21,271 @@ // Macros // ----------------------------------------------------------------------------- -#define __iat_micro_test(call) \ - if (!(call)) \ - return false +#define __iat_micro_test(call) \ + if (!(call)) \ + return false #define IAT_CHECK(v) __iat_micro_test(_test((v), #v)) #define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v)) -#define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs)) -#define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs)) +#define IAT_CHECK_EQ(lhs, rhs) \ + __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs)) +#define IAT_CHECK_NEQ(lhs, rhs) \ + __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs)) -#define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs)) +#define IAT_CHECK_APPROX(lhs, rhs) \ + __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs)) #define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func) #define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n) -#define IAT_BLOCK(name) class name : public ia::iatest::Block +#define IAT_BLOCK(name) class name : public IACore::Test::Block -#define IAT_BEGIN_BLOCK(_group, _name) \ - class _group##_##_name : public ia::iatest::Block \ - { \ - public: \ - [[nodiscard]] auto get_name() const -> const char * override \ - { \ - return #_group "::" #_name; \ - } \ - \ - private: - -#define IAT_END_BLOCK() \ - } \ - ; - -#define IAT_BEGIN_TEST_LIST() \ - public: \ - void declare_tests() override \ - { -#define IAT_ADD_TEST(name) IAT_UNIT(name) -#define IAT_END_TEST_LIST() \ - } \ - \ +#define IAT_BEGIN_BLOCK(_group, _name) \ + class _group##_##_name : public IACore::Test::Block { \ + public: \ + [[nodiscard]] auto get_name() const -> const char * override { \ + return #_group "::" #_name; \ + } \ + \ private: -namespace IACore -{ - // ------------------------------------------------------------------------- - // String Conversion Helpers - // ------------------------------------------------------------------------- - template auto to_string(const T &value) -> String - { - if constexpr (std::is_arithmetic_v) - { - return std::to_string(value); - } - else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) - { - return String("\"") + String(value) + "\""; - } - else - { - return "{Object}"; - } +#define IAT_END_BLOCK() \ + } \ + ; + +#define IAT_BEGIN_TEST_LIST() \ +public: \ + void declare_tests() override { +#define IAT_ADD_TEST(name) IAT_UNIT(name) +#define IAT_END_TEST_LIST() \ + } \ + \ +private: + +namespace IACore::Test { +// ------------------------------------------------------------------------- +// String Conversion Helpers +// ------------------------------------------------------------------------- +template auto to_string(const T &value) -> String { + if constexpr (std::is_arithmetic_v) { + return std::to_string(value); + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return String("\"") + String(value) + "\""; + } else { + return "{Object}"; + } +} + +template auto to_string(T *value) -> String { + if (value == nullptr) { + return "nullptr"; + } + return std::format("ptr({})", static_cast(value)); +} + +// ------------------------------------------------------------------------- +// Types +// ------------------------------------------------------------------------- +using TestFunctor = std::function; + +struct TestUnit { + String name; + TestFunctor functor; +}; + +class Block { +public: + virtual ~Block() = default; + [[nodiscard]] virtual auto get_name() const -> const char * = 0; + virtual void declare_tests() = 0; + + auto units() -> Vec & { return m_units; } + +protected: + template + auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool { + if (lhs != rhs) { + print_fail(description, to_string(lhs), to_string(rhs)); + return false; + } + return true; + } + + template + auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) + -> bool { + if (lhs == rhs) { + print_fail(description, to_string(lhs), "NOT " + to_string(rhs)); + return false; + } + return true; + } + + template + auto _test_approx(T lhs, T rhs, const char *description) -> bool { + static_assert(std::is_floating_point_v, + "Approx only works for floats/doubles"); + T diff = std::abs(lhs - rhs); + if (diff > static_cast(0.0001)) { + print_fail(description, to_string(lhs), to_string(rhs)); + return false; + } + return true; + } + + auto _test(bool value, const char *description) -> bool { + if (!value) { + std::cout << console::BLUE << " " << description << "... " + << console::RED << "FAILED" << console::RESET << "\n"; + return false; + } + return true; + } + + auto _test_not(bool value, const char *description) -> bool { + if (value) { + std::cout << console::BLUE << " " << description << "... " + << console::RED << "FAILED" << console::RESET << "\n"; + return false; + } + return true; + } + + void _test_unit(TestFunctor functor, const char *name) { + m_units.push_back({name, std::move(functor)}); + } + +private: + void print_fail(const char *desc, const String &v1, const String &v2) { + std::cout << console::BLUE << " " << desc << "... " << console::RED + << "FAILED" << console::RESET << "\n"; + std::cout << console::RED << " Expected: " << v2 << console::RESET + << "\n"; + std::cout << console::RED << " Actual: " << v1 << console::RESET + << "\n"; + } + + Vec m_units; +}; + +template +concept ValidBlockClass = std::derived_from; + +// ------------------------------------------------------------------------- +// Runner +// ------------------------------------------------------------------------- +template class Runner { +public: + Runner() = default; + + ~Runner() { summarize(); } + + template + requires ValidBlockClass + void test_block(); + +private: + void summarize(); + + usize m_test_count{0}; + usize m_fail_count{0}; + usize m_block_count{0}; +}; + +template +template + requires ValidBlockClass +void Runner::test_block() { + m_block_count++; + BlockClass b; + b.declare_tests(); + + std::cout << console::MAGENTA << "Testing [" << b.get_name() << "]..." + << console::RESET << "\n"; + + for (auto &v : b.units()) { + m_test_count++; + if constexpr (IsVerbose) { + std::cout << console::YELLOW << " Testing " << v.name << "...\n" + << console::RESET; } - template auto to_string(T *value) -> String - { - if (value == nullptr) - { - return "nullptr"; - } - return std::format("ptr({})", static_cast(value)); + // Exceptions are DISABLED. We assume tests do not crash. + // If a test crashes (segfault), the OS handles it. + bool result = v.functor(); + + if (!result) { + m_fail_count++; + if constexpr (StopOnFail) { + summarize(); + std::exit(-1); + } + } + } + std::cout << "\n"; +} + +template +void Runner::summarize() { + std::cout << console::GREEN + << "\n-----------------------------------\n\t " + "SUMMARY\n-----------------------------------\n"; + + if (m_fail_count == 0) { + std::cout << "\n\tALL TESTS PASSED!\n\n"; + } else { + f64 success_rate = (100.0 * static_cast(m_test_count - m_fail_count) / + static_cast(m_test_count)); + std::cout << console::RED << m_fail_count << " OF " << m_test_count + << " TESTS FAILED\n" + << console::YELLOW + << std::format("Success Rate: {:.2f}%\n", success_rate); + } + + std::cout << console::MAGENTA << "Ran " << m_test_count << " test(s) across " + << m_block_count << " block(s)\n" + << console::GREEN << "-----------------------------------" + << console::RESET << "\n"; +} + +using DefaultRunner = Runner; + +// ------------------------------------------------------------------------- +// Registry +// ------------------------------------------------------------------------- +class TestRegistry { +public: + using TestEntry = std::function; + + static auto get_entries() -> Vec & { + static Vec entries; + return entries; + } + + static auto run_all() -> i32 { + DefaultRunner r; + auto &entries = get_entries(); + std::cout << console::CYAN << "[IATest] Discovered " << entries.size() + << " Test Blocks\n\n" + << console::RESET; + + for (auto &entry : entries) { + entry(r); } - // ------------------------------------------------------------------------- - // Types - // ------------------------------------------------------------------------- - using TestFunctor = std::function; + return 0; + } +}; - struct TestUnit - { - String name; - TestFunctor functor; - }; +template struct AutoRegister { + AutoRegister() { + TestRegistry::get_entries().push_back( + [](DefaultRunner &r) { r.test_block(); }); + } +}; +} // namespace IACore::Test - class Block - { - public: - virtual ~Block() = default; - [[nodiscard]] virtual auto get_name() const -> const char * = 0; - virtual void declare_tests() = 0; - - auto units() -> Vec & - { - return m_units; - } - - protected: - template auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool - { - if (lhs != rhs) - { - print_fail(description, to_string(lhs), to_string(rhs)); - return false; - } - return true; - } - - template auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) -> bool - { - if (lhs == rhs) - { - print_fail(description, to_string(lhs), "NOT " + to_string(rhs)); - return false; - } - return true; - } - - template auto _test_approx(T lhs, T rhs, const char *description) -> bool - { - static_assert(std::is_floating_point_v, "Approx only works for floats/doubles"); - T diff = std::abs(lhs - rhs); - if (diff > static_cast(0.0001)) - { - print_fail(description, to_string(lhs), to_string(rhs)); - return false; - } - return true; - } - - auto _test(bool value, const char *description) -> bool - { - if (!value) - { - std::cout << console::blue << " " << description << "... " << console::red << "FAILED" - << console::reset << "\n"; - return false; - } - return true; - } - - auto _test_not(bool value, const char *description) -> bool - { - if (value) - { - std::cout << console::blue << " " << description << "... " << console::red << "FAILED" - << console::reset << "\n"; - return false; - } - return true; - } - - void _test_unit(TestFunctor functor, const char *name) - { - m_units.push_back({name, std::move(functor)}); - } - - private: - void print_fail(const char *desc, const String &v1, const String &v2) - { - std::cout << console::blue << " " << desc << "... " << console::red << "FAILED" << console::reset - << "\n"; - std::cout << console::red << " Expected: " << v2 << console::reset << "\n"; - std::cout << console::red << " Actual: " << v1 << console::reset << "\n"; - } - - Vec m_units; - }; - - template - concept ValidBlockClass = std::derived_from; - - // ------------------------------------------------------------------------- - // Runner - // ------------------------------------------------------------------------- - template class Runner - { - public: - Runner() = default; - - ~Runner() - { - summarize(); - } - - template - requires ValidBlockClass - void test_block(); - - private: - void summarize(); - - usize m_test_count{0}; - usize m_fail_count{0}; - usize m_block_count{0}; - }; - - template - template - requires ValidBlockClass - void Runner::test_block() - { - m_block_count++; - BlockClass b; - b.declare_tests(); - - std::cout << console::magenta << "Testing [" << b.get_name() << "]..." << console::reset << "\n"; - - for (auto &v : b.units()) - { - m_test_count++; - if constexpr (IsVerbose) - { - std::cout << console::yellow << " Testing " << v.name << "...\n" << console::reset; - } - - // Exceptions are DISABLED. We assume tests do not crash. - // If a test crashes (segfault), the OS handles it. - bool result = v.functor(); - - if (!result) - { - m_fail_count++; - if constexpr (StopOnFail) - { - summarize(); - std::exit(-1); - } - } - } - std::cout << "\n"; - } - - template void Runner::summarize() - { - std::cout << console::green - << "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n"; - - if (m_fail_count == 0) - { - std::cout << "\n\tALL TESTS PASSED!\n\n"; - } - else - { - f64 success_rate = (100.0 * static_cast(m_test_count - m_fail_count) / static_cast(m_test_count)); - std::cout << console::red << m_fail_count << " OF " << m_test_count << " TESTS FAILED\n" - << console::yellow << std::format("Success Rate: {:.2f}%\n", success_rate); - } - - std::cout << console::magenta << "Ran " << m_test_count << " test(s) across " << m_block_count << " block(s)\n" - << console::green << "-----------------------------------" << console::reset << "\n"; - } - - using DefaultRunner = Runner; - - // ------------------------------------------------------------------------- - // Registry - // ------------------------------------------------------------------------- - class TestRegistry - { - public: - using TestEntry = std::function; - - static auto get_entries() -> Vec & - { - static Vec entries; - return entries; - } - - static auto run_all() -> i32 - { - DefaultRunner r; - auto &entries = get_entries(); - std::cout << console::cyan << "[IATest] Discovered " << entries.size() << " Test Blocks\n\n" - << console::reset; - - for (auto &entry : entries) - { - entry(r); - } - - return 0; - } - }; - - template struct AutoRegister - { - AutoRegister() - { - TestRegistry::get_entries().push_back([](DefaultRunner &r) { r.test_block(); }); - } - }; -} // namespace IACore - -#define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister _iat_reg_##Group##_##Name; \ No newline at end of file +#define IAT_REGISTER_ENTRY(Group, Name) \ + static IACore::Test::AutoRegister _iat_reg_##Group##_##Name; \ No newline at end of file diff --git a/Src/IACore/inc/IACore/JSON.hpp b/Src/IACore/inc/IACore/JSON.hpp index bc29fc6..2c2c600 100644 --- a/Src/IACore/inc/IACore/JSON.hpp +++ b/Src/IACore/inc/IACore/JSON.hpp @@ -119,8 +119,8 @@ inline auto Json::encode(const nlohmann::json &data) -> String { template inline auto Json::parse_to_struct(const String &json_str) -> Result { T result{}; - // glz::read_json returns an error code (bool-like optional) - const auto err = glz::read_json(result, json_str); + + const auto err = glz::read(result, json_str); if (err) { return fail("JSON Struct Parse Error: {}", diff --git a/Src/IACore/inc/IACore/StreamReader.hpp b/Src/IACore/inc/IACore/StreamReader.hpp index e980e15..5e09bb1 100644 --- a/Src/IACore/inc/IACore/StreamReader.hpp +++ b/Src/IACore/inc/IACore/StreamReader.hpp @@ -34,8 +34,8 @@ public: explicit StreamReader(Span data); ~StreamReader(); - StreamReader(StreamReader &&) = default; - auto operator=(StreamReader &&) -> StreamReader & = default; + StreamReader(StreamReader &&other); + auto operator=(StreamReader &&other) -> StreamReader &; StreamReader(const StreamReader &) = delete; auto operator=(const StreamReader &) -> StreamReader & = delete; @@ -86,6 +86,9 @@ inline auto StreamReader::read(void *buffer, usize size) -> Result { template [[nodiscard("Check for EOF")]] inline auto StreamReader::read() -> Result { + static_assert(std::is_trivially_copyable_v, + "T must be trivially copyable to read via memcpy"); + constexpr usize SIZE = sizeof(T); if (m_cursor + SIZE > m_data_size) [[unlikely]] { diff --git a/Src/IACore/inc/IACore/StreamWriter.hpp b/Src/IACore/inc/IACore/StreamWriter.hpp index 8e95a93..a676ef4 100644 --- a/Src/IACore/inc/IACore/StreamWriter.hpp +++ b/Src/IACore/inc/IACore/StreamWriter.hpp @@ -27,18 +27,19 @@ public: OwningVector, }; - static auto create(const Path &path) -> Result; + static auto create_from_file(const Path &path) -> Result; StreamWriter(); explicit StreamWriter(Span data); - ~StreamWriter(); - StreamWriter(StreamWriter &&) = default; - auto operator=(StreamWriter &&) -> StreamWriter & = default; + StreamWriter(StreamWriter &&other); + auto operator=(StreamWriter &&other) -> StreamWriter &; StreamWriter(const StreamWriter &) = delete; auto operator=(const StreamWriter &) -> StreamWriter & = delete; + ~StreamWriter(); + auto write(u8 byte, usize count) -> Result; auto write(const void *buffer, usize size) -> Result; @@ -48,6 +49,8 @@ public: [[nodiscard]] auto cursor() const -> usize { return m_cursor; } + auto flush() -> Result; + private: u8 *m_buffer = nullptr; usize m_cursor = 0; @@ -55,6 +58,9 @@ private: Path m_file_path; Vec m_owning_vector; StorageType m_storage_type = StorageType::OwningVector; + +private: + auto flush_to_disk() -> Result; }; template diff --git a/Src/IACore/inc/full_repo_context.txt b/Src/IACore/inc/full_repo_context.txt new file mode 100644 index 0000000..5459790 --- /dev/null +++ b/Src/IACore/inc/full_repo_context.txt @@ -0,0 +1,2770 @@ +--- START FILE: full_repo_context.txt --- +``` + +``` +--- END FILE: full_repo_context.txt --- + +--- START FILE: IACore/AsyncOps.hpp --- +``` + +#pragma once + +#include +#include +#include +#include + +namespace IACore { +class AsyncOps { +public: + using TaskTag = u64; + using WorkerId = u16; + + static constexpr WorkerId MAIN_THREAD_WORKER_ID = 0; + + enum class Priority : u8 { High, Normal }; + + struct Schedule { + std::atomic counter{0}; + }; + +public: + static auto initialize_scheduler(u8 worker_count = 0) -> Result; + static auto terminate_scheduler() -> void; + + static auto schedule_task(std::function task, + TaskTag tag, Schedule *schedule, + Priority priority = Priority::Normal) -> void; + + static auto cancel_tasks_of_tag(TaskTag tag) -> void; + + static auto wait_for_schedule_completion(Schedule *schedule) -> void; + + static auto run_task(std::function task) -> void; + + [[nodiscard]] static auto get_worker_count() -> WorkerId; + +private: + struct ScheduledTask { + TaskTag tag{}; + Schedule *schedule_handle{}; + std::function task{}; + }; + + static auto schedule_worker_loop(std::stop_token stop_token, + WorkerId worker_id) -> void; + +private: + static std::mutex s_queue_mutex; + static std::condition_variable s_wake_condition; + static Vec s_schedule_workers; + static std::deque s_high_priority_queue; + static std::deque s_normal_priority_queue; +}; +} +``` +--- END FILE: IACore/AsyncOps.hpp --- + +--- START FILE: IACore/DataOps.hpp --- +``` + +#pragma once + +#include + +namespace IACore { +class DataOps { +public: + enum class CompressionType { None, Gzip, Zlib }; + +public: + static auto hash_fnv1a(const String &string) -> u32; + static auto hash_fnv1a(Span data) -> u32; + + static auto hash_xxhash(const String &string, u32 seed = 0) -> u32; + static auto hash_xxhash(Span data, u32 seed = 0) -> u32; + + static auto crc32(Span data) -> u32; + + static auto detect_compression(Span data) -> CompressionType; + + static auto gzip_inflate(Span data) -> Result>; + static auto gzip_deflate(Span data) -> Result>; + + static auto zlib_inflate(Span data) -> Result>; + static auto zlib_deflate(Span data) -> Result>; + + static auto zstd_inflate(Span data) -> Result>; + static auto zstd_deflate(Span data) -> Result>; +}; +} +``` +--- END FILE: IACore/DataOps.hpp --- + +--- START FILE: IACore/DynamicLib.hpp --- +``` + +#pragma once + +#include + +#if !IA_PLATFORM_WINDOWS +#include +#endif + +namespace ia = IACore; + +namespace IACore { + +class DynamicLib { +public: + [[nodiscard]] static auto load(const String &search_path, const String &name) + -> Result { + namespace fs = std::filesystem; + auto full_path = fs::path(search_path) / name; + + if (!full_path.has_extension()) { +#if IA_PLATFORM_WINDOWS + full_path += ".dll"; +#elif IA_PLATFORM_APPLE + full_path += ".dylib"; +#else + full_path += ".so"; +#endif + } + + DynamicLib lib; + +#if IA_PLATFORM_WINDOWS + const HMODULE h = LoadLibraryA(full_path.string().c_str()); + if (!h) { + return fail(get_windows_error()); + } + lib.m_handle = static_cast(h); +#else + void *h = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_LOCAL); + if (!h) { + const char *err = dlerror(); + return fail(err ? err : "Unknown dlopen error"); + } + lib.m_handle = h; +#endif + + return lib; + } + + DynamicLib() = default; + + DynamicLib(DynamicLib &&other) noexcept : m_handle(other.m_handle) { + other.m_handle = nullptr; + } + + auto operator=(DynamicLib &&other) noexcept -> DynamicLib & { + if (this != &other) { + unload(); + m_handle = other.m_handle; + other.m_handle = nullptr; + } + return *this; + } + + DynamicLib(const DynamicLib &) = delete; + auto operator=(const DynamicLib &) -> DynamicLib & = delete; + + ~DynamicLib() { unload(); } + + [[nodiscard]] auto get_symbol(const String &name) const -> Result { + if (!m_handle) { + return fail("Library not loaded"); + } + + void *sym = nullptr; + +#if IA_PLATFORM_WINDOWS + sym = static_cast( + GetProcAddress(static_cast(m_handle), name.c_str())); + if (!sym) { + return fail(get_windows_error()); + } +#else + dlerror(); + sym = dlsym(m_handle, name.c_str()); + if (const char *err = dlerror()) { + return fail(err); + } +#endif + + return sym; + } + + template + [[nodiscard]] auto get_function(const String &name) const -> Result { + void *sym = nullptr; + IA_TRY(sym, get_symbol(name)); + return reinterpret_cast(sym); + } + + void unload() { + if (m_handle) { +#if IA_PLATFORM_WINDOWS + FreeLibrary(static_cast(m_handle)); +#else + dlclose(m_handle); +#endif + m_handle = nullptr; + } + } + + [[nodiscard]] auto is_loaded() const -> bool { return m_handle != nullptr; } + +private: + void *m_handle = nullptr; + +#if IA_PLATFORM_WINDOWS + static auto get_windows_error() -> String { + const DWORD error_id = ::GetLastError(); + if (error_id == 0) { + return String(); + } + + LPSTR message_buffer = nullptr; + const usize size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_buffer), 0, nullptr); + + String message(message_buffer, size); + LocalFree(message_buffer); + return "Win32 Error: " + message; + } +#endif +}; + +} +``` +--- END FILE: IACore/DynamicLib.hpp --- + +--- START FILE: IACore/Environment.hpp --- +``` + +#pragma once + +#include +#include + +namespace IACore { +class Environment { +public: + static auto find(const String &name) -> Option { +#if IA_PLATFORM_WINDOWS + const DWORD buffer_size = GetEnvironmentVariableA(name.c_str(), nullptr, 0); + + if (buffer_size == 0) { + return std::nullopt; + } + + String result; + result.resize(buffer_size); + + const DWORD actual_size = + GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size); + + if (actual_size == 0 || actual_size > buffer_size) { + return std::nullopt; + } + + result.resize(actual_size); + return result; + +#else + const char *val = std::getenv(name.c_str()); + if (val == nullptr) { + return std::nullopt; + } + return String(val); +#endif + } + + static auto get(const String &name, const String &default_value = "") + -> String { + return find(name).value_or(default_value); + } + + static auto set(const String &name, const String &value) -> Result { + if (name.empty()) { + return fail("Environment variable name cannot be empty"); + } + +#if IA_PLATFORM_WINDOWS + if (SetEnvironmentVariableA(name.c_str(), value.c_str()) == 0) { + return fail("Failed to set environment variable: {}", name); + } +#else + if (setenv(name.c_str(), value.c_str(), 1) != 0) { + return fail("Failed to set environment variable: {}", name); + } +#endif + return {}; + } + + static auto unset(const String &name) -> Result { + if (name.empty()) { + return fail("Environment variable name cannot be empty"); + } + +#if IA_PLATFORM_WINDOWS + if (SetEnvironmentVariableA(name.c_str(), nullptr) == 0) { + return fail("Failed to unset environment variable: {}", name); + } +#else + if (unsetenv(name.c_str()) != 0) { + return fail("Failed to unset environment variable: {}", name); + } +#endif + return {}; + } + + static auto exists(const String &name) -> bool { + return find(name).has_value(); + } +}; +} +``` +--- END FILE: IACore/Environment.hpp --- + +--- START FILE: IACore/FileOps.hpp --- +``` + +#pragma once + +#include +#include +#include +#include + +#if IA_PLATFORM_WINDOWS +using NativeFileHandle = HANDLE; +static constexpr NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE; +#else +using NativeFileHandle = int; +static constexpr NativeFileHandle INVALID_FILE_HANDLE = -1; +#endif + +namespace IACore { + +class FileOps { +public: + class MemoryMappedRegion; + + enum class FileAccess : u8 { + Read, Write, ReadWrite }; + + enum class FileMode : u8 { + OpenExisting, OpenAlways, CreateNew, CreateAlways, TruncateExisting }; + + static auto native_open_file(const Path &path, FileAccess access, + FileMode mode, u32 permissions = 0644) + -> Result; + + static auto native_close_file(NativeFileHandle handle) -> void; + +public: + static auto normalize_executable_path(const Path &path) -> Path; + +public: + static auto unmap_file(const u8 *mapped_ptr) -> void; + + static auto map_file(const Path &path, usize &size) -> Result; + + static auto map_shared_memory(const String &name, usize size, bool is_owner) + -> Result; + + static auto unlink_shared_memory(const String &name) -> void; + + static auto stream_from_file(const Path &path) -> Result; + + static auto stream_to_file(const Path &path, bool overwrite = false) + -> Result; + + static auto read_text_file(const Path &path) -> Result; + + static auto read_binary_file(const Path &path) -> Result>; + + static auto write_text_file(const Path &path, const String &contents, + bool overwrite = false) -> Result; + + static auto write_binary_file(const Path &path, Span contents, + bool overwrite = false) -> Result; + +private: + static HashMap> s_mapped_files; +}; + +class FileOps::MemoryMappedRegion { +public: + MemoryMappedRegion() = default; + ~MemoryMappedRegion(); + + MemoryMappedRegion(const MemoryMappedRegion &) = delete; + auto operator=(const MemoryMappedRegion &) -> MemoryMappedRegion & = delete; + + MemoryMappedRegion(MemoryMappedRegion &&other) noexcept; + auto operator=(MemoryMappedRegion &&other) noexcept -> MemoryMappedRegion &; + + auto map(NativeFileHandle handle, u64 offset, usize size) -> Result; + + auto unmap() -> void; + auto flush() -> void; + + [[nodiscard]] auto get_ptr() const -> u8 * { return m_ptr; } + + [[nodiscard]] auto get_size() const -> usize { return m_size; } + + [[nodiscard]] auto is_valid() const -> bool { return m_ptr != nullptr; } + +private: + u8 *m_ptr = nullptr; + usize m_size = 0; + +#if IA_PLATFORM_WINDOWS + HANDLE m_map_handle = NULL; +#endif +}; + +} +``` +--- END FILE: IACore/FileOps.hpp --- + +--- START FILE: IACore/IATest.hpp --- +``` + +#pragma once + +#include + + +#define __iat_micro_test(call) \ + if (!(call)) \ + return false + +#define IAT_CHECK(v) __iat_micro_test(_test((v), #v)) +#define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v)) +#define IAT_CHECK_EQ(lhs, rhs) \ + __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs)) +#define IAT_CHECK_NEQ(lhs, rhs) \ + __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs)) + +#define IAT_CHECK_APPROX(lhs, rhs) \ + __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs)) + +#define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func) +#define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n) + +#define IAT_BLOCK(name) class name : public IACore::Test::Block + +#define IAT_BEGIN_BLOCK(_group, _name) \ + class _group##_##_name : public IACore::Test::Block { \ + public: \ + [[nodiscard]] auto get_name() const -> const char * override { \ + return #_group "::" #_name; \ + } \ + \ + private: + +#define IAT_END_BLOCK() \ + } \ + ; + +#define IAT_BEGIN_TEST_LIST() \ +public: \ + void declare_tests() override { +#define IAT_ADD_TEST(name) IAT_UNIT(name) +#define IAT_END_TEST_LIST() \ + } \ + \ +private: + +namespace IACore::Test { +template auto to_string(const T &value) -> String { + if constexpr (std::is_arithmetic_v) { + return std::to_string(value); + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return String("\"") + String(value) + "\""; + } else { + return "{Object}"; + } +} + +template auto to_string(T *value) -> String { + if (value == nullptr) { + return "nullptr"; + } + return std::format("ptr({})", static_cast(value)); +} + +using TestFunctor = std::function; + +struct TestUnit { + String name; + TestFunctor functor; +}; + +class Block { +public: + virtual ~Block() = default; + [[nodiscard]] virtual auto get_name() const -> const char * = 0; + virtual void declare_tests() = 0; + + auto units() -> Vec & { return m_units; } + +protected: + template + auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool { + if (lhs != rhs) { + print_fail(description, to_string(lhs), to_string(rhs)); + return false; + } + return true; + } + + template + auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) + -> bool { + if (lhs == rhs) { + print_fail(description, to_string(lhs), "NOT " + to_string(rhs)); + return false; + } + return true; + } + + template + auto _test_approx(T lhs, T rhs, const char *description) -> bool { + static_assert(std::is_floating_point_v, + "Approx only works for floats/doubles"); + T diff = std::abs(lhs - rhs); + if (diff > static_cast(0.0001)) { + print_fail(description, to_string(lhs), to_string(rhs)); + return false; + } + return true; + } + + auto _test(bool value, const char *description) -> bool { + if (!value) { + std::cout << console::BLUE << " " << description << "... " + << console::RED << "FAILED" << console::RESET << "\n"; + return false; + } + return true; + } + + auto _test_not(bool value, const char *description) -> bool { + if (value) { + std::cout << console::BLUE << " " << description << "... " + << console::RED << "FAILED" << console::RESET << "\n"; + return false; + } + return true; + } + + void _test_unit(TestFunctor functor, const char *name) { + m_units.push_back({name, std::move(functor)}); + } + +private: + void print_fail(const char *desc, const String &v1, const String &v2) { + std::cout << console::BLUE << " " << desc << "... " << console::RED + << "FAILED" << console::RESET << "\n"; + std::cout << console::RED << " Expected: " << v2 << console::RESET + << "\n"; + std::cout << console::RED << " Actual: " << v1 << console::RESET + << "\n"; + } + + Vec m_units; +}; + +template +concept ValidBlockClass = std::derived_from; + +template class Runner { +public: + Runner() = default; + + ~Runner() { summarize(); } + + template + requires ValidBlockClass + void test_block(); + +private: + void summarize(); + + usize m_test_count{0}; + usize m_fail_count{0}; + usize m_block_count{0}; +}; + +template +template + requires ValidBlockClass +void Runner::test_block() { + m_block_count++; + BlockClass b; + b.declare_tests(); + + std::cout << console::MAGENTA << "Testing [" << b.get_name() << "]..." + << console::RESET << "\n"; + + for (auto &v : b.units()) { + m_test_count++; + if constexpr (IsVerbose) { + std::cout << console::YELLOW << " Testing " << v.name << "...\n" + << console::RESET; + } + + bool result = v.functor(); + + if (!result) { + m_fail_count++; + if constexpr (StopOnFail) { + summarize(); + std::exit(-1); + } + } + } + std::cout << "\n"; +} + +template +void Runner::summarize() { + std::cout << console::GREEN + << "\n-----------------------------------\n\t " + "SUMMARY\n-----------------------------------\n"; + + if (m_fail_count == 0) { + std::cout << "\n\tALL TESTS PASSED!\n\n"; + } else { + f64 success_rate = (100.0 * static_cast(m_test_count - m_fail_count) / + static_cast(m_test_count)); + std::cout << console::RED << m_fail_count << " OF " << m_test_count + << " TESTS FAILED\n" + << console::YELLOW + << std::format("Success Rate: {:.2f}%\n", success_rate); + } + + std::cout << console::MAGENTA << "Ran " << m_test_count << " test(s) across " + << m_block_count << " block(s)\n" + << console::GREEN << "-----------------------------------" + << console::RESET << "\n"; +} + +using DefaultRunner = Runner; + +class TestRegistry { +public: + using TestEntry = std::function; + + static auto get_entries() -> Vec & { + static Vec entries; + return entries; + } + + static auto run_all() -> i32 { + DefaultRunner r; + auto &entries = get_entries(); + std::cout << console::CYAN << "[IATest] Discovered " << entries.size() + << " Test Blocks\n\n" + << console::RESET; + + for (auto &entry : entries) { + entry(r); + } + + return 0; + } +}; + +template struct AutoRegister { + AutoRegister() { + TestRegistry::get_entries().push_back( + [](DefaultRunner &r) { r.test_block(); }); + } +}; +} +#define IAT_REGISTER_ENTRY(Group, Name) \ + static IACore::Test::AutoRegister _iat_reg_##Group##_##Name; +``` +--- END FILE: IACore/IATest.hpp --- + +--- START FILE: IACore/IPC.hpp --- +``` + +#pragma once + +#include +#include +#include + +namespace IACore { +using IpcPacketHeader = RingBufferView::PacketHeader; + +struct alignas(64) IpcSharedMemoryLayout { + struct Header { + u32 magic; u32 version; u64 total_size; } meta; + + u8 _pad0[64 - sizeof(Header)]; + + + RingBufferView::ControlBlock moni_control; + RingBufferView::ControlBlock mino_control; + + + u64 moni_data_offset; + u64 moni_data_size; + + u64 mino_data_offset; + u64 mino_data_size; + + u8 _pad1[64 - (sizeof(u64) * 4)]; + + static constexpr auto get_header_size() -> usize { + return sizeof(IpcSharedMemoryLayout); + } +}; + +static_assert(sizeof(IpcSharedMemoryLayout) % 64 == 0, + "IPC Layout is not cache-line aligned!"); + +class IpcNode { +public: + virtual ~IpcNode(); + + auto connect(const char *connection_string) -> Result; + + void update(); + + void send_signal(u8 signal); + auto send_packet(u16 packet_id, Span payload) -> Result; + +protected: + virtual void on_signal(u8 signal) = 0; + virtual void on_packet(u16 packet_id, Span payload) = 0; + +private: + String m_shm_name; + u8 *m_shared_memory{}; + Vec m_receive_buffer; + SocketHandle m_socket{INVALID_SOCKET}; + + RingBufferView m_moni; RingBufferView m_mino; }; + +class IpcManager { + struct NodeSession { + std::chrono::system_clock::time_point creation_time{}; + Box node_process; + + std::mutex send_mutex; + + String shared_mem_name; + u8 *mapped_ptr{}; + + SocketHandle listener_socket{INVALID_SOCKET}; + SocketHandle data_socket{INVALID_SOCKET}; + + RingBufferView moni = + RingBufferView::default_instance(); RingBufferView mino = + RingBufferView::default_instance(); + bool is_ready{false}; + + void send_signal(u8 signal); + auto send_packet(u16 packet_id, Span payload) -> Result; + }; + +public: + static constexpr u32 DEFAULT_NODE_SHARED_MEMORY_SIZE = 4 * 1024 * 1024; +public: + virtual ~IpcManager(); + + void update(); + + auto spawn_node(const Path &executable_path, + u32 shared_memory_size = DEFAULT_NODE_SHARED_MEMORY_SIZE) + -> Result; + + auto wait_till_node_is_online(NativeProcessID node) -> bool; + + void shutdown_node(NativeProcessID node); + + void send_signal(NativeProcessID node, u8 signal); + auto send_packet(NativeProcessID node, u16 packet_id, Span payload) + -> Result; + +protected: + virtual void on_signal(NativeProcessID node, u8 signal) = 0; + virtual void on_packet(NativeProcessID node, u16 packet_id, + Span payload) = 0; + +private: + Vec m_receive_buffer; + Vec> m_active_sessions; + Vec> m_pending_sessions; + HashMap m_active_session_map; + +protected: + IpcManager(); +}; +} +``` +--- END FILE: IACore/IPC.hpp --- + +--- START FILE: IACore/ProcessOps.hpp --- +``` + +#pragma once + +#include + +#if IA_PLATFORM_WINDOWS +using NativeProcessID = DWORD; +#elif IA_PLATFORM_UNIX +using NativeProcessID = pid_t; +#else +#error "This platform does not support IACore ProcessOps" +#endif + +namespace IACore { +struct ProcessHandle { + std::atomic id{0}; + std::atomic is_running{false}; + + [[nodiscard]] auto is_active() const -> bool { return is_running && id != 0; } + +private: + std::jthread m_thread_handle; + + friend class ProcessOps; +}; + +class ProcessOps { +public: + static auto get_current_process_id() -> NativeProcessID; + + static auto spawn_process_sync( + const String &command, const String &args, + std::function on_output_line_callback) + -> Result; + + static auto spawn_process_async( + const String &command, const String &args, + std::function on_output_line_callback, + std::function)> on_finish_callback) + -> Result>; + + static auto terminate_process(const Box &handle) -> void; + +private: + static auto + spawn_process_windows(const String &command, const String &args, + std::function on_output_line_callback, + std::atomic &id) -> Result; + + static auto + spawn_process_posix(const String &command, const String &args, + std::function on_output_line_callback, + std::atomic &id) -> Result; +}; +} +``` +--- END FILE: IACore/ProcessOps.hpp --- + +--- START FILE: IACore/SocketOps.hpp --- +``` + +#pragma once + +#include + +#if IA_PLATFORM_WINDOWS +#include +#include +#include +#pragma comment(lib, "ws2_32.lib") +#elif IA_PLATFORM_UNIX +#include +#include +#include +#include +#ifndef INVALID_SOCKET +#define INVALID_SOCKET -1 +#endif +#else +#error "IACore SocketOps is not supported on this platform." +#endif + +namespace IACore { +#if IA_PLATFORM_WINDOWS +using SocketHandle = SOCKET; +#elif IA_PLATFORM_UNIX +using SocketHandle = int; +#endif + +class SocketOps { +public: + static auto initialize() -> Result { + s_init_count++; + if (s_init_count > 1) { + return {}; + } +#if IA_PLATFORM_WINDOWS + WSADATA wsa_data; + const auto res = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (res != 0) { + s_init_count--; + return fail("WSAStartup failed with error: {}", res); + } +#endif + return {}; + } + + static auto terminate() -> void { + s_init_count--; + if (s_init_count > 0) { + return; + } +#if IA_PLATFORM_WINDOWS + WSACleanup(); +#endif + } + + static auto is_initialized() -> bool { return s_init_count > 0; } + + static auto is_port_available_tcp(u16 port) -> bool { + return is_port_available(port, SOCK_STREAM); + } + + static auto is_port_available_udp(u16 port) -> bool { + return is_port_available(port, SOCK_DGRAM); + } + + static auto is_would_block() -> bool; + + static auto close(SocketHandle sock) -> void; + + static auto listen(SocketHandle sock, i32 queue_size = 5) -> Result; + + static auto create_unix_socket() -> Result; + + static auto bind_unix_socket(SocketHandle sock, const char *path) + -> Result; + static auto connect_unix_socket(SocketHandle sock, const char *path) + -> Result; + + static auto unlink_file(const char *path) -> void { +#if IA_PLATFORM_WINDOWS + DeleteFileA(path); +#elif IA_PLATFORM_UNIX + unlink(path); +#endif + } + +private: + static auto is_port_available(u16 port, i32 type) -> bool; + +private: + static i32 s_init_count; +}; +} +``` +--- END FILE: IACore/SocketOps.hpp --- + +--- START FILE: IACore/StreamReader.hpp --- +``` + +#pragma once + +#include +#include +#include + +namespace IACore { +class StreamReader { +public: + enum class StorageType { + NonOwning, + OwningMmap, + OwningVector, + }; + + static auto create_from_file(const Path &path) -> Result; + + explicit StreamReader(Vec &&data); + explicit StreamReader(Span data); + ~StreamReader(); + + StreamReader(StreamReader &&other); + auto operator=(StreamReader &&other) -> StreamReader &; + + StreamReader(const StreamReader &) = delete; + auto operator=(const StreamReader &) -> StreamReader & = delete; + + auto read(void *buffer, usize size) -> Result; + + template + [[nodiscard("Check for EOF")]] + auto read() -> Result; + + auto skip(usize amount) -> void { + m_cursor = std::min(m_cursor + amount, m_data_size); + } + + auto seek(usize pos) -> void { + m_cursor = (pos > m_data_size) ? m_data_size : pos; + } + + [[nodiscard]] auto cursor() const -> usize { return m_cursor; } + + [[nodiscard]] auto size() const -> usize { return m_data_size; } + + [[nodiscard]] auto remaining() const -> usize { + return m_data_size - m_cursor; + } + + [[nodiscard]] auto is_eof() const -> bool { return m_cursor >= m_data_size; } + +private: + const u8 *m_data = nullptr; + usize m_cursor = 0; + usize m_data_size = 0; + Vec m_owning_vector; + StorageType m_storage_type = StorageType::NonOwning; +}; + +inline auto StreamReader::read(void *buffer, usize size) -> Result { + if (m_cursor + size > m_data_size) [[unlikely]] { + return fail("Unexpected EOF while reading"); + } + + std::memcpy(buffer, &m_data[m_cursor], size); + m_cursor += size; + + return {}; +} + +template +[[nodiscard("Check for EOF")]] +inline auto StreamReader::read() -> Result { + static_assert(std::is_trivially_copyable_v, + "T must be trivially copyable to read via memcpy"); + + constexpr usize SIZE = sizeof(T); + + if (m_cursor + SIZE > m_data_size) [[unlikely]] { + return fail("Unexpected EOF while reading"); + } + + T value; + std::memcpy(&value, &m_data[m_cursor], SIZE); + m_cursor += SIZE; + + return value; +} + +} +``` +--- END FILE: IACore/StreamReader.hpp --- + +--- START FILE: IACore/StreamWriter.hpp --- +``` + +#pragma once + +#include + +namespace IACore { + +class StreamWriter { +public: + enum class StorageType { + NonOwning, + OwningFile, + OwningVector, + }; + + static auto create_from_file(const Path &path) -> Result; + + StreamWriter(); + explicit StreamWriter(Span data); + + StreamWriter(StreamWriter &&other); + auto operator=(StreamWriter &&other) -> StreamWriter &; + + StreamWriter(const StreamWriter &) = delete; + auto operator=(const StreamWriter &) -> StreamWriter & = delete; + + ~StreamWriter(); + + auto write(u8 byte, usize count) -> Result; + auto write(const void *buffer, usize size) -> Result; + + template auto write(const T &value) -> Result; + + [[nodiscard]] auto data() const -> const u8 * { return m_buffer; } + + [[nodiscard]] auto cursor() const -> usize { return m_cursor; } + + auto flush() -> Result; + +private: + u8 *m_buffer = nullptr; + usize m_cursor = 0; + usize m_capacity = 0; + Path m_file_path; + Vec m_owning_vector; + StorageType m_storage_type = StorageType::OwningVector; + +private: + auto flush_to_disk() -> Result; +}; + +template +inline auto StreamWriter::write(const T &value) -> Result { + return write(&value, sizeof(T)); +} + +} +``` +--- END FILE: IACore/StreamWriter.hpp --- + +--- START FILE: IACore/StringOps.hpp --- +``` + +#pragma once + +#include + +namespace IACore { +class StringOps { +public: + static auto encode_base64(Span data) -> String; + static auto decode_base64(const String &data) -> Vec; +}; +} +``` +--- END FILE: IACore/StringOps.hpp --- + +--- START FILE: IACore/Utils.hpp --- +``` + +#pragma once + +#include + +#include + +namespace IACore +{ + class Utils + { + public: + static auto get_unix_time() -> u64; + + static auto get_ticks_count() -> u64; + + static auto get_seconds_count() -> f64; + + static auto get_random() -> f32; + static auto get_random(u64 max) -> u64; + static auto get_random(i64 min, i64 max) -> i64; + + static auto sleep(u64 milliseconds) -> void; + + static auto binary_to_hex_string(Span data) -> String; + + static auto hex_string_to_binary(StringView hex) -> Result>; + + template inline static void sort(Range &&range) + { + std::ranges::sort(range); + } + + template inline static auto binary_search_left(Range &&range, const T &value) + { + return std::ranges::lower_bound(range, value); + } + + template inline static auto binary_search_right(Range &&range, const T &value) + { + return std::ranges::upper_bound(range, value); + } + + template inline static void hash_combine(u64 &seed, const T &v) + { + u64 h; + + if constexpr (std::is_constructible_v) + { + StringView sv(v); + auto hasher = ankerl::unordered_dense::hash(); + h = hasher(sv); + } + else + { + auto hasher = ankerl::unordered_dense::hash(); + h = hasher(v); + } + + seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2); + } + + template inline static auto compute_hash(const Args &...args) -> u64 + { + u64 seed = 0; + (hash_combine(seed, args), ...); + return seed; + } + + template + inline static auto compute_hash_flat(const T &obj, MemberPtrs... members) -> u64 + { + u64 seed = 0; + (hash_combine(seed, obj.*members), ...); + return seed; + } + }; +} +#define IA_MAKE_HASHABLE(Type, ...) \ + template<> struct ankerl::unordered_dense::hash \ + { \ + using is_avalanching = void; \ + IA_NODISCARD \ + auto operator()(const Type &v) const noexcept -> IACore::u64 \ + { \ + \ + return IACore::Utils::compute_hash_flat(v, __VA_ARGS__); \ + } \ + }; +``` +--- END FILE: IACore/Utils.hpp --- + +--- START FILE: IACore/XML.hpp --- +``` + +#pragma once + +#include + +#include + +namespace IACore +{ + class XML + { + public: + using Node = pugi::xml_node; + using Document = pugi::xml_document; + + public: + static auto parse_from_string(const String &data) -> Result; + static auto parse_from_file(const Path &path) -> Result; + + static auto serialize_to_string(const Node &node, bool escape = false) -> String; + static auto serialize_to_string(const Document &doc, bool escape = false) -> String; + + static auto escape_xml_string(const String &xml) -> String; + }; +} +``` +--- END FILE: IACore/XML.hpp --- + +--- START FILE: IACore/Platform.hpp --- +``` + +#pragma once + +#include + +#if IA_ARCH_X64 +# ifdef _MSC_VER +# include +# else +# include +# endif +#elif IA_ARCH_ARM64 +# include +#endif + +namespace IACore +{ + class Platform + { + public: + struct Capabilities + { + bool hardware_crc32 = false; + }; + + static auto check_cpu() -> bool; + +#if IA_ARCH_X64 + static auto cpuid(i32 function, i32 sub_function, i32 out[4]) -> void; +#endif + + static auto get_architecture_name() -> const char *; + static auto get_operating_system_name() -> const char *; + + static auto get_capabilities() -> const Capabilities & + { + return s_capabilities; + } + + private: + static Capabilities s_capabilities; + }; +} +``` +--- END FILE: IACore/Platform.hpp --- + +--- START FILE: IACore/SIMD.hpp --- +``` +#pragma once + +#include + +#if defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#endif + +#include + +#if defined(__clang__) +# pragma GCC diagnostic pop +#endif + +namespace IACore +{ + namespace hn = hwy::HWY_NAMESPACE; + +#if HWY_TARGET == HWY_SCALAR +# pragma message("Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)") +#endif + + class alignas(16) IntVec4 + { + public: + IntVec4() = default; + + inline explicit IntVec4(u32 s); + inline explicit IntVec4(const u32 *values); + inline explicit IntVec4(u32 a, u32 b, u32 c, u32 d); + + inline auto operator+(const IntVec4 &other) const -> IntVec4; + inline auto operator-(const IntVec4 &other) const -> IntVec4; + inline auto operator*(const IntVec4 &other) const -> IntVec4; + + inline auto operator&(const IntVec4 &other) const -> IntVec4; + inline auto operator|(const IntVec4 &other) const -> IntVec4; + inline auto operator^(const IntVec4 &other) const -> IntVec4; + inline auto operator~() const -> IntVec4; + + inline auto operator<<(u32 amount) const -> IntVec4; + inline auto operator>>(u32 amount) const -> IntVec4; + + inline auto sat_add(const IntVec4 &other) const -> IntVec4; + inline auto sat_sub(const IntVec4 &other) const -> IntVec4; + + inline auto clamp(u32 min, u32 max) const -> IntVec4; + + inline auto mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4; + + inline auto store(u32 *values) -> void; + static inline auto load(const u32 *values) -> IntVec4; + + private: + using Tag = hn::FixedTag; + + hn::Vec m_data; + + inline explicit IntVec4(hn::Vec v) : m_data(v) + { + } + }; + + class alignas(16) FloatVec4 + { + public: + FloatVec4() = default; + + inline explicit FloatVec4(f32 s); + inline explicit FloatVec4(const f32 *values); + inline explicit FloatVec4(f32 a, f32 b, f32 c, f32 d); + + inline auto operator+(const FloatVec4 &other) const -> FloatVec4; + inline auto operator-(const FloatVec4 &other) const -> FloatVec4; + inline auto operator*(const FloatVec4 &other) const -> FloatVec4; + inline auto operator/(const FloatVec4 &other) const -> FloatVec4; + + inline auto clamp(f32 min, f32 max) const -> FloatVec4; + + inline auto abs() const -> FloatVec4; + inline auto sqrt() const -> FloatVec4; + inline auto rsqrt() const -> FloatVec4; + inline auto normalize() const -> FloatVec4; + + inline auto dot(const FloatVec4 &other) const -> f32; + + inline auto mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4; + + inline auto store(f32 *values) -> void; + static inline auto load(const f32 *values) -> FloatVec4; + + private: + using Tag = hn::FixedTag; + + hn::Vec m_data; + + inline explicit FloatVec4(hn::Vec v) : m_data(v) + { + } + }; +} +namespace IACore +{ + IntVec4::IntVec4(u32 s) + { + const Tag d; + m_data = hn::Set(d, s); + } + + IntVec4::IntVec4(const u32 *values) + { + const Tag data; + m_data = hn::Load(data, values); + } + + IntVec4::IntVec4(u32 a, u32 b, u32 c, u32 d) + { + const Tag data; + alignas(16) u32 values[4] = {a, b, c, d}; + m_data = hn::Load(data, values); + } + + auto IntVec4::operator+(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::Add(m_data, other.m_data)); + } + + auto IntVec4::operator-(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::Sub(m_data, other.m_data)); + } + + auto IntVec4::operator*(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::Mul(m_data, other.m_data)); + } + + auto IntVec4::operator&(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::And(m_data, other.m_data)); + } + + auto IntVec4::operator|(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::Or(m_data, other.m_data)); + } + + auto IntVec4::operator^(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::Xor(m_data, other.m_data)); + } + + auto IntVec4::operator~() const -> IntVec4 + { + return IntVec4(hn::Not(m_data)); + } + + auto IntVec4::operator<<(u32 amount) const -> IntVec4 + { + return IntVec4(hn::ShiftLeftSame(m_data, amount)); + } + + auto IntVec4::operator>>(u32 amount) const -> IntVec4 + { + return IntVec4(hn::ShiftRightSame(m_data, amount)); + } + + auto IntVec4::mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4 + { + return IntVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data)); + } + + auto IntVec4::sat_add(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::SaturatedAdd(m_data, other.m_data)); + } + + auto IntVec4::sat_sub(const IntVec4 &other) const -> IntVec4 + { + return IntVec4(hn::SaturatedSub(m_data, other.m_data)); + } + + auto IntVec4::clamp(u32 min, u32 max) const -> IntVec4 + { + const Tag d; + auto vMin = hn::Set(d, min); + auto vMax = hn::Set(d, max); + return IntVec4(hn::Min(hn::Max(m_data, vMin), vMax)); + } + + auto IntVec4::store(u32 *values) -> void + { + const Tag d; + hn::Store(m_data, d, values); + } + + auto IntVec4::load(const u32 *values) -> IntVec4 + { + const Tag d; + return IntVec4(hn::Load(d, values)); + } +} +namespace IACore +{ + FloatVec4::FloatVec4(f32 s) + { + const Tag d; + m_data = hn::Set(d, s); + } + + FloatVec4::FloatVec4(const f32 *values) + { + const Tag d; + m_data = hn::Load(d, values); + } + + FloatVec4::FloatVec4(f32 a, f32 b, f32 c, f32 d) + { + const Tag data; + alignas(16) f32 temp[4] = {a, b, c, d}; + m_data = hn::Load(data, temp); + } + + auto FloatVec4::operator+(const FloatVec4 &other) const -> FloatVec4 + { + return FloatVec4(hn::Add(m_data, other.m_data)); + } + + auto FloatVec4::operator-(const FloatVec4 &other) const -> FloatVec4 + { + return FloatVec4(hn::Sub(m_data, other.m_data)); + } + + auto FloatVec4::operator*(const FloatVec4 &other) const -> FloatVec4 + { + return FloatVec4(hn::Mul(m_data, other.m_data)); + } + + auto FloatVec4::operator/(const FloatVec4 &other) const -> FloatVec4 + { + return FloatVec4(hn::Div(m_data, other.m_data)); + } + + auto FloatVec4::mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4 + { + return FloatVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data)); + } + + auto FloatVec4::clamp(f32 min, f32 max) const -> FloatVec4 + { + const Tag d; + auto vMin = hn::Set(d, min); + auto vMax = hn::Set(d, max); + return FloatVec4(hn::Min(hn::Max(m_data, vMin), vMax)); + } + + auto FloatVec4::sqrt() const -> FloatVec4 + { + return FloatVec4(hn::Sqrt(m_data)); + } + + auto FloatVec4::rsqrt() const -> FloatVec4 + { + return FloatVec4(hn::ApproximateReciprocalSqrt(m_data)); + } + + auto FloatVec4::abs() const -> FloatVec4 + { + return FloatVec4(hn::Abs(m_data)); + } + + auto FloatVec4::dot(const FloatVec4 &other) const -> f32 + { + const Tag d; + auto vMul = hn::Mul(m_data, other.m_data); + return hn::ReduceSum(d, vMul); + } + + auto FloatVec4::normalize() const -> FloatVec4 + { + const Tag d; + auto vMul = hn::Mul(m_data, m_data); + auto vLenSq = hn::SumOfLanes(d, vMul); + auto vInvLen = hn::ApproximateReciprocalSqrt(vLenSq); + return FloatVec4(hn::Mul(m_data, vInvLen)); + } + + auto FloatVec4::store(f32 *values) -> void + { + const Tag d; + hn::Store(m_data, d, values); + } + + auto FloatVec4::load(const f32 *values) -> FloatVec4 + { + const Tag d; + return FloatVec4(hn::Load(d, values)); + } +} +``` +--- END FILE: IACore/SIMD.hpp --- + +--- START FILE: IACore/IACore.hpp --- +``` + +#pragma once + +#include +#include + +#define IACORE_MAIN() \ + auto _app_entry(const IACore::Vec &args) -> IACore::Result; \ + auto main(int argc, char *argv[]) -> int \ + { \ + IACore::i32 exit_code = 0; \ + IACore::initialize(); \ + IACore::Vec args; \ + args.reserve(static_cast(argc)); \ + for (int i = 0; i < argc; ++i) \ + { \ + args.push_back(argv[i]); \ + } \ + const auto result = _app_entry(args); \ + if (!result) \ + { \ + IACore::Logger::error("Application exited with an error: '{}'.", result.error()); \ + exit_code = -20; \ + } \ + else \ + { \ + exit_code = *result; \ + if (exit_code == 0) \ + { \ + IACore::Logger::info("Application exited successfully."); \ + } \ + else \ + { \ + IACore::Logger::error("Application exited with error code: {}.", exit_code); \ + } \ + } \ + IACore::terminate(); \ + return exit_code; \ + } \ + auto _app_entry(const IACore::Vec &args) -> IACore::Result + +namespace IACore +{ + auto initialize() -> void; + + auto terminate() -> void; + + auto is_initialized() -> bool; + + auto is_main_thread() -> bool; +} +``` +--- END FILE: IACore/IACore.hpp --- + +--- START FILE: IACore/CLI.hpp --- +``` + +#pragma once + +#include + +namespace IACore { +class CLIParser { + + +public: + CLIParser(Span args); + ~CLIParser() = default; + +public: + [[nodiscard]] auto remaining() const -> bool { + return m_current_arg < m_arg_list.end(); + } + + [[nodiscard]] auto peek() const -> StringView { + if (!remaining()) + return ""; + return *m_current_arg; + } + + auto next() -> StringView { + if (!remaining()) + return ""; + return *m_current_arg++; + } + + auto consume(const StringView &expected) -> bool { + if (peek() == expected) { + next(); + return true; + } + return false; + } + +private: + const Span m_arg_list; + Span::const_iterator m_current_arg; +}; +} +``` +--- END FILE: IACore/CLI.hpp --- + +--- START FILE: IACore/Logger.hpp --- +``` +#pragma once + +#include + +#define IA_LOG_SET_FILE(path) IACore::Logger::enable_logging_to_disk(path) +#define IA_LOG_SET_LEVEL(level) \ + IACore::Logger::set_log_level(IACore::Logger::LogLevel::level) + +#define IA_LOG_TRACE(...) IACore::Logger::trace(__VA_ARGS__) +#define IA_LOG_DEBUG(...) IACore::Logger::debug(__VA_ARGS__) +#define IA_LOG_INFO(...) IACore::Logger::info(__VA_ARGS__) +#define IA_LOG_WARN(...) IACore::Logger::warn(__VA_ARGS__) +#define IA_LOG_ERROR(...) IACore::Logger::error(__VA_ARGS__) + +namespace IACore { +class Logger { +public: + enum class LogLevel { Trace, Debug, Info, Warn, Error }; + +public: + static auto enable_logging_to_disk(const char *file_path) -> Result; + static auto set_log_level(LogLevel log_level) -> void; + + template + static auto trace(std::format_string fmt, Args &&...args) -> void { + log_trace(std::vformat(fmt.get(), std::make_format_args(args...))); + } + + template + static auto debug(std::format_string fmt, Args &&...args) -> void { + log_debug(std::vformat(fmt.get(), std::make_format_args(args...))); + } + + template + static auto info(std::format_string fmt, Args &&...args) -> void { + log_info(std::vformat(fmt.get(), std::make_format_args(args...))); + } + + template + static auto warn(std::format_string fmt, Args &&...args) -> void { + log_warn(std::vformat(fmt.get(), std::make_format_args(args...))); + } + + template + static auto error(std::format_string fmt, Args &&...args) -> void { + log_error(std::vformat(fmt.get(), std::make_format_args(args...))); + } + + static auto flush_logs() -> void; + +private: +#if IA_DISABLE_LOGGING > 0 + static auto log_trace(String &&msg) -> void { UNUSED(msg); } + + static auto log_debug(String &&msg) -> void { UNUSED(msg); } + + static auto log_info(String &&msg) -> void { UNUSED(msg); } + + static auto log_warn(String &&msg) -> void { UNUSED(msg); } + + static auto log_error(String &&msg) -> void { UNUSED(msg); } +#else + static auto log_trace(String &&msg) -> void { + if (m_log_level <= LogLevel::Trace) + log_internal(console::RESET, "TRACE", std::move(msg)); + } + + static auto log_debug(String &&msg) -> void { + if (m_log_level <= LogLevel::Debug) + log_internal(console::CYAN, "DEBUG", std::move(msg)); + } + + static auto log_info(String &&msg) -> void { + if (m_log_level <= LogLevel::Info) + log_internal(console::GREEN, "INFO", std::move(msg)); + } + + static auto log_warn(String &&msg) -> void { + if (m_log_level <= LogLevel::Warn) + log_internal(console::YELLOW, "WARN", std::move(msg)); + } + + static auto log_error(String &&msg) -> void { + if (m_log_level <= LogLevel::Error) + log_internal(console::RED, "ERROR", std::move(msg)); + } +#endif + + static auto log_internal(const char *prefix, const char *tag, String &&msg) + -> void; + +private: + static LogLevel m_log_level; + static std::ofstream m_log_file; + + static auto initialize() -> void; + static auto terminate() -> void; + + friend void initialize(); + friend void terminate(); +}; +} +``` +--- END FILE: IACore/Logger.hpp --- + +--- START FILE: IACore/PCH.hpp --- +``` + +#pragma once + +#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64) +#define IA_ARCH_X64 1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define IA_ARCH_ARM64 1 +#elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__) +#define IA_ARCH_WASM 1 +#else +#error "IACore: Unsupported Architecture." +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +#define IA_PLATFORM_WINDOWS 1 +#elif __APPLE__ +#include +#define IA_PLATFORM_APPLE 1 +#define IA_PLATFORM_UNIX 1 +#elif __linux__ +#define IA_PLATFORM_LINUX 1 +#define IA_PLATFORM_UNIX 1 +#elif __wasm__ +#define IA_PLATFORM_WASM 1 +#else +#error "IACore: Unsupported Platform." +#endif + +#if IA_PLATFORM_WINDOWS +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#elif IA_PLATFORM_UNIX +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace IACore { + +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; + +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; + +using f32 = float; +using f64 = double; + +using usize = std::size_t; +using isize = std::ptrdiff_t; + +namespace env { +#if defined(NDEBUG) +constexpr bool is_debug = false; +constexpr bool is_release = true; +#else +constexpr bool is_debug = true; +constexpr bool is_release = false; +#endif + +#if IA_PLATFORM_WINDOWS +constexpr bool is_windows = true; +constexpr bool is_unix = false; +#else +constexpr bool is_windows = false; +constexpr bool is_unix = true; +#endif + +constexpr usize max_path_len = 4096; +} +template using Box = std::unique_ptr; + +template using Arc = std::shared_ptr; + +template using Weak = std::weak_ptr; + +template +[[nodiscard]] inline auto make_box(Args &&...args) -> Box { + return std::make_unique(std::forward(args)...); +} + +template +[[nodiscard]] inline auto make_arc(Args &&...args) -> Arc { + return std::make_shared(std::forward(args)...); +} + +template +using Result = tl::expected; + +template [[nodiscard]] inline auto fail(E &&error) { + return tl::make_unexpected(std::forward(error)); +} + +template +[[nodiscard]] inline auto fail(std::format_string fmt, + Args &&...args) { + return tl::make_unexpected(std::format(fmt, std::forward(args)...)); +} + +template using Option = std::optional; +template using Vec = std::vector; +template using Span = std::span; +template using Pair = std::pair; +template +using HashMap = ankerl::unordered_dense::map; +template using HashSet = ankerl::unordered_dense::set; + +using String = std::string; +using Path = std::filesystem::path; +using StringView = std::string_view; + + +[[noreturn]] inline void +panic(const std::string &msg, + const std::source_location loc = std::source_location::current()) { + std::cerr << "\n[IA_PANIC] " << msg << "\n At: " << loc.file_name() + << ":" << loc.line() << "\n"; + std::abort(); +} + +inline void +ensure(bool condition, const std::string &msg, + const std::source_location loc = std::source_location::current()) { + if (env::is_debug && !condition) { + std::cerr << "\n[assert] " << msg << "\n At: " << loc.file_name() + << ":" << loc.line() << "\n"; + std::abort(); + } +} + +struct Version { + u32 major = 0; + u32 minor = 0; + u32 patch = 0; + + constexpr auto to_u64() const -> u64 { + return (static_cast(major) << 40) | (static_cast(minor) << 16) | + (static_cast(patch)); + } +}; + +namespace console { +constexpr const char *RESET = "\033[0m"; +constexpr const char *RED = "\033[31m"; +constexpr const char *GREEN = "\033[32m"; +constexpr const char *YELLOW = "\033[33m"; +constexpr const char *BLUE = "\033[34m"; +constexpr const char *MAGENTA = "\033[35m"; +constexpr const char *CYAN = "\033[36m"; +} +} + +#define IA_TRY_PURE(expr) \ + { \ + auto _res = expr; \ + if (!_res) { \ + return fail(std::move(_res.error())); \ + } \ + } + +#define IA_TRY(lhs, expr) \ + { \ + auto _res = expr; \ + if (!_res) { \ + return fail(std::move(_res.error())); \ + } \ + lhs = std::move(*_res); \ + } + +#define IA_NODISCARD [[nodiscard]] +#define IA_UNUSED [[maybe_unused]] +``` +--- END FILE: IACore/PCH.hpp --- + +--- START FILE: IACore/JSON.hpp --- +``` + +#pragma once + +#include + +#include +#include +#include + +namespace IACore { +class JsonDocument { +public: + JsonDocument(JsonDocument &&) noexcept = default; + JsonDocument &operator=(JsonDocument &&) noexcept = default; + JsonDocument(const JsonDocument &) = delete; + JsonDocument &operator=(const JsonDocument &) = delete; + + [[nodiscard]] + auto root() const noexcept -> simdjson::dom::element { + return m_root; + } + +private: + friend class Json; + + JsonDocument(Box p, simdjson::dom::element r) + : m_parser(std::move(p)), m_root(r) {} + + Box m_parser; + simdjson::dom::element m_root; +}; + +class Json { +private: + static constexpr auto GLAZE_OPTS = glz::opts{.error_on_unknown_keys = false}; + +public: + static auto parse(const String &json_str) -> Result; + static auto encode(const nlohmann::json &data) -> String; + + static auto parse_read_only(const String &json_str) -> Result; + + template + static auto parse_to_struct(const String &json_str) -> Result; + + template + static auto encode_struct(const T &data) -> Result; +}; + + +inline auto Json::parse(const String &json_str) -> Result { + const auto res = nlohmann::json::parse(json_str, nullptr, false, true); + + if (res.is_discarded()) { + return fail("Failed to parse JSON (Invalid Syntax)"); + } + return res; +} + +inline auto Json::parse_read_only(const String &json_str) + -> Result { + auto parser = make_box(); + + simdjson::dom::element root; + + simdjson::error_code error = parser->parse(json_str).get(root); + + if (error) { + return fail("JSON Error: {}", simdjson::error_message(error)); + } + + return JsonDocument(std::move(parser), root); +} + +inline auto Json::encode(const nlohmann::json &data) -> String { + return data.dump(); +} + +template +inline auto Json::parse_to_struct(const String &json_str) -> Result { + T result{}; + const auto err = glz::read_json(result, json_str); + + if (err) { + return fail("JSON Struct Parse Error: {}", + glz::format_error(err, json_str)); + } + return result; +} + +template +inline auto Json::encode_struct(const T &data) -> Result { + String result; + const auto err = glz::write_json(data, result); + + if (err) { + return fail("JSON Struct Encode Error"); + } + return result; +} +} +``` +--- END FILE: IACore/JSON.hpp --- + +--- START FILE: IACore/ADT/RingBuffer.hpp --- +``` + +#pragma once + +#include +#include + +namespace IACore { +class RingBufferView { +public: + static constexpr u16 PACKET_ID_SKIP = 0; + + struct ControlBlock { + struct alignas(64) { + std::atomic write_offset{0}; + } producer; + + struct alignas(64) { + std::atomic read_offset{0}; + u32 capacity{0}; + } consumer; + }; + + static_assert(offsetof(ControlBlock, consumer) == 64, + "False sharing detected in ControlBlock"); + + struct PacketHeader { + PacketHeader() : id(0), payload_size(0) {} + + PacketHeader(u16 id) : id(id), payload_size(0) {} + + PacketHeader(u16 id, u16 payload_size) + : id(id), payload_size(payload_size) {} + + u16 id{}; + u16 payload_size{}; + }; + +public: + static auto default_instance() -> RingBufferView; + + static auto create(Span buffer, bool is_owner) -> Result; + static auto create(ControlBlock *control_block, Span buffer, + bool is_owner) -> Result; + + auto pop(PacketHeader &out_header, Span out_buffer) + -> Result>; + + auto push(u16 packet_id, Span data) -> Result; + + auto get_control_block() -> ControlBlock *; + + [[nodiscard]] auto is_valid() const -> bool; + +protected: + RingBufferView(Span buffer, bool is_owner); + RingBufferView(ControlBlock *control_block, Span buffer, bool is_owner); + +private: + u8 *m_data_ptr{}; + u32 m_capacity{}; + ControlBlock *m_control_block{}; + +private: + auto write_wrapped(u32 offset, const void *data, u32 size) -> void; + auto read_wrapped(u32 offset, void *out_data, u32 size) -> void; +}; + + +inline auto RingBufferView::default_instance() -> RingBufferView { + return RingBufferView(nullptr, {}, false); +} + +inline auto RingBufferView::create(Span buffer, bool is_owner) + -> Result { + if (buffer.size() <= sizeof(ControlBlock)) { + return fail("Buffer too small for ControlBlock"); + } + + if (!is_owner) { + auto *cb = reinterpret_cast(buffer.data()); + u32 capacity = static_cast(buffer.size()) - sizeof(ControlBlock); + if (cb->consumer.capacity != capacity) { + return fail("Capacity mismatch"); + } + } + + return RingBufferView(buffer, is_owner); +} + +inline auto RingBufferView::create(ControlBlock *control_block, Span buffer, + bool is_owner) -> Result { + if (control_block == nullptr) { + return fail("ControlBlock is null"); + } + if (buffer.empty()) { + return fail("Buffer is empty"); + } + + return RingBufferView(control_block, buffer, is_owner); +} + +inline RingBufferView::RingBufferView(Span buffer, bool is_owner) { + m_control_block = reinterpret_cast(buffer.data()); + m_data_ptr = buffer.data() + sizeof(ControlBlock); + + m_capacity = static_cast(buffer.size()) - sizeof(ControlBlock); + + if (is_owner) { + m_control_block->consumer.capacity = m_capacity; + m_control_block->producer.write_offset.store(0, std::memory_order_release); + m_control_block->consumer.read_offset.store(0, std::memory_order_release); + } +} + +inline RingBufferView::RingBufferView(ControlBlock *control_block, + Span buffer, bool is_owner) { + m_control_block = control_block; + m_data_ptr = buffer.data(); + m_capacity = static_cast(buffer.size()); + + if (is_owner) { + m_control_block->consumer.capacity = m_capacity; + m_control_block->producer.write_offset.store(0, std::memory_order_release); + m_control_block->consumer.read_offset.store(0, std::memory_order_release); + } +} + +inline auto RingBufferView::pop(PacketHeader &out_header, Span out_buffer) + -> Result> { + u32 write = + m_control_block->producer.write_offset.load(std::memory_order_acquire); + u32 read = + m_control_block->consumer.read_offset.load(std::memory_order_relaxed); + u32 cap = m_capacity; + + if (read == write) { + return std::nullopt; + } + + read_wrapped(read, &out_header, sizeof(PacketHeader)); + + if (out_header.payload_size > out_buffer.size()) { + return fail("Buffer too small: needed {}, provided {}", + out_header.payload_size, out_buffer.size()); + } + + if (out_header.payload_size > 0) { + u32 data_read_offset = (read + sizeof(PacketHeader)) % cap; + read_wrapped(data_read_offset, out_buffer.data(), out_header.payload_size); + } + + u32 new_read_offset = + (read + sizeof(PacketHeader) + out_header.payload_size) % cap; + m_control_block->consumer.read_offset.store(new_read_offset, + std::memory_order_release); + + return std::make_optional(static_cast(out_header.payload_size)); +} + +inline auto RingBufferView::push(u16 packet_id, Span data) + -> Result { + if (data.size() > std::numeric_limits::max()) { + return fail("Data size exceeds u16 limit"); + } + + const u32 total_size = sizeof(PacketHeader) + static_cast(data.size()); + + u32 read = + m_control_block->consumer.read_offset.load(std::memory_order_acquire); + u32 write = + m_control_block->producer.write_offset.load(std::memory_order_relaxed); + u32 cap = m_capacity; + + u32 free_space = + (read <= write) ? (m_capacity - write) + read : (read - write); + + if (free_space <= total_size) { + return fail("RingBuffer full"); + } + + PacketHeader header{packet_id, static_cast(data.size())}; + write_wrapped(write, &header, sizeof(PacketHeader)); + + u32 data_write_offset = (write + sizeof(PacketHeader)) % cap; + + if (!data.empty()) { + write_wrapped(data_write_offset, data.data(), + static_cast(data.size())); + } + + u32 new_write_offset = (data_write_offset + data.size()) % cap; + m_control_block->producer.write_offset.store(new_write_offset, + std::memory_order_release); + + return {}; +} + +inline auto RingBufferView::get_control_block() -> ControlBlock * { + return m_control_block; +} + +inline auto RingBufferView::write_wrapped(u32 offset, const void *data, + u32 size) -> void { + if (offset + size <= m_capacity) { + std::memcpy(m_data_ptr + offset, data, size); + } else { + u32 first_chunk = m_capacity - offset; + u32 second_chunk = size - first_chunk; + + const u8 *src = static_cast(data); + + std::memcpy(m_data_ptr + offset, src, first_chunk); + std::memcpy(m_data_ptr, src + first_chunk, second_chunk); + } +} + +inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size) + -> void { + if (offset + size <= m_capacity) { + std::memcpy(out_data, m_data_ptr + offset, size); + } else { + u32 first_chunk = m_capacity - offset; + u32 second_chunk = size - first_chunk; + + u8 *dst = static_cast(out_data); + + std::memcpy(dst, m_data_ptr + offset, first_chunk); + std::memcpy(dst + first_chunk, m_data_ptr, second_chunk); + } +} + +[[nodiscard]] inline auto RingBufferView::is_valid() const -> bool { + return m_control_block && m_data_ptr && m_capacity; +} + +} +``` +--- END FILE: IACore/ADT/RingBuffer.hpp --- + +--- START FILE: IACore/Http/Client.hpp --- +``` + +#pragma once + +#include +#include + +namespace IACore { +class HttpClient : public HttpCommon { +public: + static auto create(const String &host) -> Result>; + + ~HttpClient(); + + HttpClient(HttpClient &&) = default; + HttpClient(const HttpClient &) = delete; + auto operator=(HttpClient &&) -> HttpClient & = default; + auto operator=(const HttpClient &) -> HttpClient & = delete; + +public: + auto raw_get(const String &path, Span headers, + const char *default_content_type = + "application/x-www-form-urlencoded") -> Result; + + auto raw_post( + const String &path, Span headers, const String &body, + const char *default_content_type = "application/x-www-form-urlencoded") + -> Result; + + template + auto json_get(const String &path, Span headers) + -> Result; + + template + auto json_post(const String &path, Span headers, + const PayloadType &body) -> Result; + + void enable_certificate_verification(); + void disable_certificate_verification(); + +public: + auto last_response_code() -> EResponseCode { return m_last_response_code; } + +private: + httplib::Client m_client; + EResponseCode m_last_response_code; + +private: + auto preprocess_response(const String &response) -> String; + +protected: + explicit HttpClient(httplib::Client &&client); +}; + +template +auto HttpClient::json_get(const String &path, Span headers) + -> Result { + String raw_response; + IA_TRY(raw_response, raw_get(path, headers, "application/json")); + + if (last_response_code() != EResponseCode::OK) { + return fail("Server responded with code {}", + static_cast(last_response_code())); + } + return Json::parse_to_struct(raw_response); +} + +template +auto HttpClient::json_post(const String &path, Span headers, + const PayloadType &body) -> Result { + String encoded_body; + IA_TRY(encoded_body, Json::encode_struct(body)); + + String raw_response; + IA_TRY(raw_response, + raw_post(path, headers, encoded_body, "application/json")); + + if (last_response_code() != EResponseCode::OK) { + return fail("Server responded with code {}", + static_cast(last_response_code())); + } + return Json::parse_to_struct(raw_response); +} +} +``` +--- END FILE: IACore/Http/Client.hpp --- + +--- START FILE: IACore/Http/Common.hpp --- +``` + +#pragma once + +#include + +#include + +namespace IACore { +class HttpCommon { +public: + enum class EHeaderType { + ACCEPT, + ACCEPT_CHARSET, + ACCEPT_ENCODING, + ACCEPT_LANGUAGE, + AUTHORIZATION, + CACHE_CONTROL, + CONNECTION, + CONTENT_LENGTH, + CONTENT_TYPE, + COOKIE, + DATE, + EXPECT, + HOST, + IF_MATCH, + IF_MODIFIED_SINCE, + IF_NONE_MATCH, + ORIGIN, + PRAGMA, + PROXY_AUTHORIZATION, + RANGE, + REFERER, + TE, + UPGRADE, + USER_AGENT, + VIA, + WARNING + }; + + enum class EResponseCode : i32 { + CONTINUE = 100, + SWITCHING_PROTOCOLS = 101, + PROCESSING = 102, + EARLY_HINTS = 103, + + OK = 200, + CREATED = 201, + ACCEPTED = 202, + NON_AUTHORITATIVE_INFORMATION = 203, + NO_CONTENT = 204, + RESET_CONTENT = 205, + PARTIAL_CONTENT = 206, + MULTI_STATUS = 207, + ALREADY_REPORTED = 208, + IM_USED = 226, + + MULTIPLE_CHOICES = 300, + MOVED_PERMANENTLY = 301, + FOUND = 302, + SEE_OTHER = 303, + NOT_MODIFIED = 304, + USE_PROXY = 305, + TEMPORARY_REDIRECT = 307, + PERMANENT_REDIRECT = 308, + + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + PAYMENT_REQUIRED = 402, + FORBIDDEN = 403, + NOT_FOUND = 404, + METHOD_NOT_ALLOWED = 405, + NOT_ACCEPTABLE = 406, + PROXY_AUTHENTICATION_REQUIRED = 407, + REQUEST_TIMEOUT = 408, + CONFLICT = 409, + GONE = 410, + LENGTH_REQUIRED = 411, + PRECONDITION_FAILED = 412, + PAYLOAD_TOO_LARGE = 413, + URI_TOO_LONG = 414, + UNSUPPORTED_MEDIA_TYPE = 415, + RANGE_NOT_SATISFIABLE = 416, + EXPECTATION_FAILED = 417, + IM_A_TEAPOT = 418, + MISDIRECTED_REQUEST = 421, + UNPROCESSABLE_ENTITY = 422, + LOCKED = 423, + FAILED_DEPENDENCY = 424, + TOO_EARLY = 425, + UPGRADE_REQUIRED = 426, + PRECONDITION_REQUIRED = 428, + TOO_MANY_REQUESTS = 429, + REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + UNAVAILABLE_FOR_LEGAL_REASONS = 451, + + INTERNAL_SERVER_ERROR = 500, + NOT_IMPLEMENTED = 501, + BAD_GATEWAY = 502, + SERVICE_UNAVAILABLE = 503, + GATEWAY_TIMEOUT = 504, + HTTP_VERSION_NOT_SUPPORTED = 505, + VARIANT_ALSO_NEGOTIATES = 506, + INSUFFICIENT_STORAGE = 507, + LOOP_DETECTED = 508, + NOT_EXTENDED = 510, + NETWORK_AUTHENTICATION_REQUIRED = 511 + }; + + using Header = Pair; + + static auto url_encode(const String &value) -> String; + static auto url_decode(const String &value) -> String; + + static auto header_type_to_string(EHeaderType type) -> String; + + static inline auto create_header(EHeaderType key, const String &value) + -> Header; + static inline auto create_header(const String &key, const String &value) + -> Header; + + static auto is_success_response_code(EResponseCode code) -> bool; + +protected: + HttpCommon() = default; +}; + +auto HttpCommon::create_header(EHeaderType key, const String &value) + -> HttpCommon::Header { + return std::make_pair(header_type_to_string(key), value); +} + +auto HttpCommon::create_header(const String &key, const String &value) + -> HttpCommon::Header { + return std::make_pair(key, value); +} +} +``` +--- END FILE: IACore/Http/Common.hpp --- + +--- START FILE: IACore/Http/Server.hpp --- +``` + +#pragma once + +#include +#include +#include + +namespace IACore { +class HttpServer : public HttpCommon { +public: + struct Request { + String path; + String method; + String body; + HashMap headers; + HashMap params; HashMap path_params; + [[nodiscard]] auto get_header(const String &key) const -> String; + [[nodiscard]] auto get_param(const String &key) const -> String; + [[nodiscard]] auto get_path_param(const String &key) const -> String; + + [[nodiscard]] auto has_header(const String &key) const -> bool; + [[nodiscard]] auto has_param(const String &key) const -> bool; + [[nodiscard]] auto has_path_param(const String &key) const -> bool; + }; + + struct Response { + EResponseCode code = EResponseCode::OK; + String body; + HashMap headers; + String content_type = "text/plain"; + + void set_content(const String &content, const String &type); + void set_status(EResponseCode status_code); + void add_header(const String &key, const String &value); + }; + + using Handler = std::function; + +public: + static auto create() -> Result>; + + ~HttpServer(); + + HttpServer(HttpServer &&) = delete; + HttpServer(const HttpServer &) = delete; + auto operator=(HttpServer &&) -> HttpServer & = delete; + auto operator=(const HttpServer &) -> HttpServer & = delete; + + auto listen(const String &host, u32 port) -> Result; + void stop(); + auto is_running() const -> bool; + + void get(const String &pattern, Handler handler); + void post(const String &pattern, Handler handler); + void put(const String &pattern, Handler handler); + void del(const String &pattern, Handler handler); + void options(const String &pattern, Handler handler); + + template + void json_get(const String &pattern, + std::function(const Request &)> handler); + + template + void + json_post(const String &pattern, + std::function(const PayloadType &)> handler); + +protected: + HttpServer(); + +private: + httplib::Server m_server; + + void register_handler(const String &method, const String &pattern, + Handler handler); +}; + + +template +void HttpServer::json_get( + const String &pattern, + std::function(const Request &)> handler) { + get(pattern, [handler](const Request &req, Response &res) { + auto result = handler(req); + if (!result) { + res.set_status(EResponseCode::INTERNAL_SERVER_ERROR); + res.set_content(result.error(), "text/plain"); + return; + } + + auto json_res = Json::encode_struct(*result); + if (!json_res) { + res.set_status(EResponseCode::INTERNAL_SERVER_ERROR); + res.set_content("Failed to encode JSON response", "text/plain"); + return; + } + + res.set_status(EResponseCode::OK); + res.set_content(*json_res, "application/json"); + }); +} + +template +void HttpServer::json_post( + const String &pattern, + std::function(const PayloadType &)> handler) { + post(pattern, [handler](const Request &req, Response &res) { + auto payload = Json::parse_to_struct(req.body); + if (!payload) { + res.set_status(EResponseCode::BAD_REQUEST); + res.set_content("Invalid JSON Payload", "text/plain"); + return; + } + + auto result = handler(*payload); + if (!result) { + res.set_status(EResponseCode::INTERNAL_SERVER_ERROR); + res.set_content(result.error(), "text/plain"); + return; + } + + auto json_res = Json::encode_struct(*result); + if (!json_res) { + res.set_status(EResponseCode::INTERNAL_SERVER_ERROR); + res.set_content("Failed to encode JSON response", "text/plain"); + return; + } + + res.set_status(EResponseCode::OK); + res.set_content(*json_res, "application/json"); + }); +} + +} +``` +--- END FILE: IACore/Http/Server.hpp --- + diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 9bf5a25..a4bccdc 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -#add_subdirectory(Subjects/) -#add_subdirectory(Unit/) -#add_subdirectory(Regression/) +add_subdirectory(Subjects/) +add_subdirectory(Unit/) +add_subdirectory(Regression/) diff --git a/Tests/Unit/AsyncOps.cpp b/Tests/Unit/AsyncOps.cpp new file mode 100644 index 0000000..775735b --- /dev/null +++ b/Tests/Unit/AsyncOps.cpp @@ -0,0 +1,206 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +using namespace IACore; + +// ----------------------------------------------------------------------------- +// Helper: RAII Scheduler Guard +// ----------------------------------------------------------------------------- +// Ensures the scheduler is terminated at the end of a test scope, +// preventing state pollution between tests. +struct SchedulerGuard { + SchedulerGuard(u8 worker_count = 2) { + // We ignore the result here as we assume the test environment is clean. + // Specific initialization tests will check the result manually. + (void)AsyncOps::initialize_scheduler(worker_count); + } + + ~SchedulerGuard() { AsyncOps::terminate_scheduler(); } +}; + +// ----------------------------------------------------------------------------- +// Test Block Definition +// ----------------------------------------------------------------------------- + +IAT_BEGIN_BLOCK(Core, AsyncOps) + +// ------------------------------------------------------------------------- +// 1. Initialization and Termination +// ------------------------------------------------------------------------- +auto test_initialization() -> bool { + // Ensure clean state + AsyncOps::terminate_scheduler(); + + // Initialize with specific worker countB + const auto res = AsyncOps::initialize_scheduler(4); + IAT_CHECK(res.has_value()); + + IAT_CHECK_EQ(AsyncOps::get_worker_count(), static_cast(4)); + + // Terminate + AsyncOps::terminate_scheduler(); + + // Verify we can re-initialize + const auto res2 = AsyncOps::initialize_scheduler(1); + IAT_CHECK(res2.has_value()); + IAT_CHECK_EQ(AsyncOps::get_worker_count(), static_cast(1)); + + AsyncOps::terminate_scheduler(); + return true; +} + +// ------------------------------------------------------------------------- +// 2. Basic Task Execution +// ------------------------------------------------------------------------- +auto test_basic_execution() -> bool { + SchedulerGuard guard(2); + + AsyncOps::Schedule schedule; + std::atomic run_count{0}; + + // Schedule a simple task + AsyncOps::schedule_task([&](AsyncOps::WorkerId) { run_count++; }, 0, + &schedule); + + // Wait for it to finish + AsyncOps::wait_for_schedule_completion(&schedule); + + IAT_CHECK_EQ(run_count.load(), 1); + + return true; +} + +// ------------------------------------------------------------------------- +// 3. Concurrency and Load +// ------------------------------------------------------------------------- +auto test_concurrency() -> bool { + SchedulerGuard guard(4); + + AsyncOps::Schedule schedule; + std::atomic run_count{0}; + const i32 total_tasks = 100; + + for (i32 i = 0; i < total_tasks; ++i) { + AsyncOps::schedule_task( + [&](AsyncOps::WorkerId) { + // Simulate a tiny bit of work to encourage thread switching + std::this_thread::sleep_for(std::chrono::microseconds(10)); + run_count++; + }, + 0, &schedule); + } + + AsyncOps::wait_for_schedule_completion(&schedule); + + IAT_CHECK_EQ(run_count.load(), total_tasks); + + return true; +} + +// ------------------------------------------------------------------------- +// 4. Priorities +// ------------------------------------------------------------------------- +auto test_priorities() -> bool { + SchedulerGuard guard(2); + AsyncOps::Schedule schedule; + std::atomic high_priority_ran{0}; + std::atomic normal_priority_ran{0}; + + // Verify API accepts priorities and executes them. + // Note: Deterministic priority testing is difficult in unit tests without + // mocking, so we primarily ensure the API functions correctly. + + AsyncOps::schedule_task([&](AsyncOps::WorkerId) { high_priority_ran++; }, 0, + &schedule, AsyncOps::Priority::High); + + AsyncOps::schedule_task([&](AsyncOps::WorkerId) { normal_priority_ran++; }, 0, + &schedule, AsyncOps::Priority::Normal); + + AsyncOps::wait_for_schedule_completion(&schedule); + + IAT_CHECK_EQ(high_priority_ran.load(), 1); + IAT_CHECK_EQ(normal_priority_ran.load(), 1); + + return true; +} + +// ------------------------------------------------------------------------- +// 5. Fire-and-Forget Tasks +// ------------------------------------------------------------------------- +auto test_run_task_fire_and_forget() -> bool { + SchedulerGuard guard(2); + + std::atomic executed{false}; + + // run_task doesn't use a Schedule object, so we must sync manually + AsyncOps::run_task([&]() { executed = true; }); + + // Busy wait with timeout (max 1 second) + for (int i = 0; i < 100; ++i) { + if (executed.load()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + IAT_CHECK(executed.load()); + + return true; +} + +// ------------------------------------------------------------------------- +// 6. Cancellation Safety +// ------------------------------------------------------------------------- +auto test_cancellation_safety() -> bool { + SchedulerGuard guard(2); + + // 1. Cancel non-existent tag (Should be safe) + AsyncOps::cancel_tasks_of_tag(999); + + AsyncOps::Schedule schedule; + std::atomic counter{0}; + + // 2. Schedule a task, wait for it + AsyncOps::schedule_task([&](AsyncOps::WorkerId) { counter++; }, 10, + &schedule); + + AsyncOps::wait_for_schedule_completion(&schedule); + IAT_CHECK_EQ(counter.load(), 1); + + // 3. Cancel tag 10 (already done, should be safe) + AsyncOps::cancel_tasks_of_tag(10); + + return true; +} + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_initialization); +IAT_ADD_TEST(test_basic_execution); +IAT_ADD_TEST(test_concurrency); +IAT_ADD_TEST(test_priorities); +IAT_ADD_TEST(test_run_task_fire_and_forget); +IAT_ADD_TEST(test_cancellation_safety); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, AsyncOps) \ No newline at end of file diff --git a/Tests/Unit/CCompile.c b/Tests/Unit/CCompile.c deleted file mode 100644 index ae28683..0000000 --- a/Tests/Unit/CCompile.c +++ /dev/null @@ -1,40 +0,0 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects -// Copyright (C) 2026 IAS (ias@iasoft.dev) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#if defined(__cplusplus) -#error "CRITICAL: This file MUST be compiled as C to test ABI compatibility." -#endif - -#include - -#if TRUE != 1 -#error "TRUE macro is broken in C mode" -#endif - -int main(void) -{ - IA_VERSION_TYPE version = IA_MAKE_VERSION(1, 0, 0); - - IA_ASSERT(version > 0); - - UNUSED(version); - - int32_t myNumber = 10; - - (void)myNumber; - - return 0; -} diff --git a/Tests/Unit/CLI.cpp b/Tests/Unit/CLI.cpp new file mode 100644 index 0000000..e204a32 --- /dev/null +++ b/Tests/Unit/CLI.cpp @@ -0,0 +1,134 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +using namespace IACore; + +// ----------------------------------------------------------------------------- +// Test Block Definition +// ----------------------------------------------------------------------------- + +IAT_BEGIN_BLOCK(Core, CLI) + +// ------------------------------------------------------------------------- +// 1. Basic Traversal +// ------------------------------------------------------------------------- +auto test_basic_traversal() -> bool { + const Vec args = {"ignored", "one", "two", "three"}; + CLIParser parser(args); + + IAT_CHECK(parser.remaining()); + + // Check sequential access + IAT_CHECK_EQ(String(parser.next()), "one"); + IAT_CHECK(parser.remaining()); + + IAT_CHECK_EQ(String(parser.next()), "two"); + IAT_CHECK(parser.remaining()); + + IAT_CHECK_EQ(String(parser.next()), "three"); + + // Should be exhausted now + IAT_CHECK_NOT(parser.remaining()); + + // Next on exhausted returns empty string view + IAT_CHECK_EQ(String(parser.next()), ""); + + return true; +} + +// ------------------------------------------------------------------------- +// 2. Peek Functionality +// ------------------------------------------------------------------------- +auto test_peek() -> bool { + const Vec args = {"ignored", "peek_val", "next_val"}; + CLIParser parser(args); + + // Peek should return value but not advance + IAT_CHECK_EQ(String(parser.peek()), "peek_val"); + IAT_CHECK(parser.remaining()); + + // Verify we are still at the same element + IAT_CHECK_EQ(String(parser.next()), "peek_val"); + + // Now we should be at next_val + IAT_CHECK_EQ(String(parser.peek()), "next_val"); + IAT_CHECK_EQ(String(parser.next()), "next_val"); + + IAT_CHECK_NOT(parser.remaining()); + + return true; +} + +// ------------------------------------------------------------------------- +// 3. Consume Functionality +// ------------------------------------------------------------------------- +auto test_consume() -> bool { + const Vec args = {"ignored", "-v", "--output", "file.txt"}; + CLIParser parser(args); + + // 1. Try consuming something that isn't there + IAT_CHECK_NOT(parser.consume("-x")); + + // Verify we haven't moved + IAT_CHECK_EQ(String(parser.peek()), "-v"); + + // 2. Consume the correct flag + IAT_CHECK(parser.consume("-v")); + + // 3. Verify advancement + IAT_CHECK_EQ(String(parser.peek()), "--output"); + + // 4. Consume next + IAT_CHECK(parser.consume("--output")); + + // 5. Verify final argument + IAT_CHECK_EQ(String(parser.next()), "file.txt"); + + IAT_CHECK_NOT(parser.remaining()); + + return true; +} + +// ------------------------------------------------------------------------- +// 4. Empty Argument List +// ------------------------------------------------------------------------- +auto test_empty() -> bool { + const Vec args = {}; + CLIParser parser(args); + + IAT_CHECK_NOT(parser.remaining()); + IAT_CHECK_EQ(String(parser.peek()), ""); + IAT_CHECK_EQ(String(parser.next()), ""); + IAT_CHECK_NOT(parser.consume("-help")); + + return true; +} + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_basic_traversal); +IAT_ADD_TEST(test_peek); +IAT_ADD_TEST(test_consume); +IAT_ADD_TEST(test_empty); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, CLI) \ No newline at end of file diff --git a/Tests/Unit/CMakeLists.txt b/Tests/Unit/CMakeLists.txt index 0256cb1..bb8d350 100644 --- a/Tests/Unit/CMakeLists.txt +++ b/Tests/Unit/CMakeLists.txt @@ -1,25 +1,25 @@ -# ------------------------------------------------ -# C Compile Test (Keep separate to verify C ABI) -# ------------------------------------------------ -add_executable(IACore_Test_C_ABI "CCompile.c") -set_target_properties(IACore_Test_C_ABI PROPERTIES - C_STANDARD 99 - C_STANDARD_REQUIRED ON - LINKER_LANGUAGE C -) -target_link_libraries(IACore_Test_C_ABI PRIVATE IACore) - # ------------------------------------------------ # The Unified C++ Test Suite # ------------------------------------------------ set(TEST_SOURCES - Main.cpp - Utils.cpp - Environment.cpp + AsyncOps.cpp + CLI.cpp DataOps.cpp + Environment.cpp + FileOps.cpp + IPC.cpp + JSON.cpp + Logger.cpp + Main.cpp + Platform.cpp ProcessOps.cpp - StreamReader.cpp RingBuffer.cpp + SocketOps.cpp + StreamReader.cpp + StreamWriter.cpp + StringOps.cpp + Utils.cpp + XML.cpp SIMD/IntVec4.cpp SIMD/FloatVec4.cpp diff --git a/Tests/Unit/DataOps.cpp b/Tests/Unit/DataOps.cpp index 5eb2046..a849999 100644 --- a/Tests/Unit/DataOps.cpp +++ b/Tests/Unit/DataOps.cpp @@ -14,7 +14,6 @@ // limitations under the License. #include - #include using namespace IACore; @@ -25,89 +24,92 @@ using namespace IACore; IAT_BEGIN_BLOCK(Core, DataOps) -bool TestCRC32() { +auto test_crc32() -> bool { { - String s = "123456789"; - Span span(reinterpret_cast(s.data()), s.size()); - u32 result = DataOps::CRC32(span); + const String s = "123456789"; + const Span span(reinterpret_cast(s.data()), s.size()); + const u32 result = DataOps::crc32(span); IAT_CHECK_EQ(result, 0xE3069283); } { - u32 result = DataOps::CRC32({}); + const u32 result = DataOps::crc32({}); IAT_CHECK_EQ(result, 0U); } { - std::vector buffer(33); - for (size_t i = 1; i < 33; ++i) - buffer[i] = (u8)i; + Vec buffer(33); + for (usize i = 1; i < 33; ++i) { + buffer[i] = static_cast(i); + } - std::vector refData(32); - for (size_t i = 0; i < 32; ++i) - refData[i] = (u8)(i + 1); + Vec ref_data(32); + for (usize i = 0; i < 32; ++i) { + ref_data[i] = static_cast(i + 1); + } - u32 hashRef = - DataOps::CRC32(Span(refData.data(), refData.size())); + const u32 hash_ref = + DataOps::crc32(Span(ref_data.data(), ref_data.size())); - u32 hashUnaligned = DataOps::CRC32(Span(buffer.data() + 1, 32)); + const u32 hash_unaligned = + DataOps::crc32(Span(buffer.data() + 1, 32)); - IAT_CHECK_EQ(hashRef, hashUnaligned); + IAT_CHECK_EQ(hash_ref, hash_unaligned); } - return TRUE; + return true; } -bool TestHash_xxHash() { +auto test_hash_xxhash() -> bool { { - String s = "123456789"; - u32 result = DataOps::Hash_xxHash(s); + const String s = "123456789"; + const u32 result = DataOps::hash_xxhash(s); IAT_CHECK_EQ(result, 0x937bad67); } { - String s = "The quick brown fox jumps over the lazy dog"; - u32 result = DataOps::Hash_xxHash(s); + const String s = "The quick brown fox jumps over the lazy dog"; + const u32 result = DataOps::hash_xxhash(s); IAT_CHECK_EQ(result, 0xE85EA4DE); } { - String s = "Test"; - u32 r1 = DataOps::Hash_xxHash(s); - u32 r2 = - DataOps::Hash_xxHash(Span((const u8 *)s.data(), s.size())); + const String s = "Test"; + const u32 r1 = DataOps::hash_xxhash(s); + const u32 r2 = DataOps::hash_xxhash( + Span(reinterpret_cast(s.data()), s.size())); IAT_CHECK_EQ(r1, r2); } - return TRUE; + return true; } -bool TestHash_FNV1A() { +auto test_hash_fnv1a() -> bool { { - String s = "123456789"; - u32 result = - DataOps::Hash_FNV1A(Span((const u8 *)s.data(), s.size())); + const String s = "123456789"; + const u32 result = DataOps::hash_fnv1a( + Span(reinterpret_cast(s.data()), s.size())); IAT_CHECK_EQ(result, 0xbb86b11c); } { - u32 result = DataOps::Hash_FNV1A(Span{}); + const u32 result = DataOps::hash_fnv1a(Span{}); IAT_CHECK_EQ(result, 0x811C9DC5); } - return TRUE; + return true; } // ------------------------------------------------------------------------- // Registration // ------------------------------------------------------------------------- IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestCRC32); -IAT_ADD_TEST(TestHash_FNV1A); -IAT_ADD_TEST(TestHash_xxHash); +IAT_ADD_TEST(test_crc32); +IAT_ADD_TEST(test_hash_fnv1a); +IAT_ADD_TEST(test_hash_xxhash); IAT_END_TEST_LIST() IAT_END_BLOCK() -IAT_REGISTER_ENTRY(Core, DataOps) +IAT_REGISTER_ENTRY(Core, DataOps) \ No newline at end of file diff --git a/Tests/Unit/Environment.cpp b/Tests/Unit/Environment.cpp index f72dcec..c64edf0 100644 --- a/Tests/Unit/Environment.cpp +++ b/Tests/Unit/Environment.cpp @@ -14,7 +14,6 @@ // limitations under the License. #include - #include using namespace IACore; @@ -22,8 +21,8 @@ using namespace IACore; // ----------------------------------------------------------------------------- // Constants // ----------------------------------------------------------------------------- -static const char *TEST_KEY = "IA_TEST_ENV_VAR_12345"; -static const char *TEST_VAL = "Hello World"; +static constexpr const char *TEST_KEY = "IA_TEST_ENV_VAR_12345"; +static constexpr const char *TEST_VAL = "Hello World"; // ----------------------------------------------------------------------------- // Test Block Definition @@ -34,87 +33,83 @@ IAT_BEGIN_BLOCK(Core, Environment) // ------------------------------------------------------------------------- // 1. Basic Set and Get (The Happy Path) // ------------------------------------------------------------------------- -bool TestBasicCycle() -{ - // 1. Ensure clean slate - Environment::Unset(TEST_KEY); - IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); +auto test_basic_cycle() -> bool { + // 1. Ensure clean slate + (void)Environment::unset(TEST_KEY); + IAT_CHECK_NOT(Environment::exists(TEST_KEY)); - // 2. Set - bool setRes = Environment::Set(TEST_KEY, TEST_VAL); - IAT_CHECK(setRes); - IAT_CHECK(Environment::Exists(TEST_KEY)); + // 2. Set + const auto set_res = Environment::set(TEST_KEY, TEST_VAL); + IAT_CHECK(set_res.has_value()); + IAT_CHECK(Environment::exists(TEST_KEY)); - // 3. Find (Optional) - auto opt = Environment::Find(TEST_KEY); - IAT_CHECK(opt.has_value()); - IAT_CHECK_EQ(*opt, String(TEST_VAL)); + // 3. Find (Optional) + const auto opt = Environment::find(TEST_KEY); + IAT_CHECK(opt.has_value()); + IAT_CHECK_EQ(*opt, String(TEST_VAL)); - // 4. Get (Direct) - String val = Environment::Get(TEST_KEY); - IAT_CHECK_EQ(val, String(TEST_VAL)); + // 4. Get (Direct) + const String val = Environment::get(TEST_KEY); + IAT_CHECK_EQ(val, String(TEST_VAL)); - // Cleanup - Environment::Unset(TEST_KEY); - return TRUE; + // Cleanup + (void)Environment::unset(TEST_KEY); + return true; } // ------------------------------------------------------------------------- // 2. Overwriting Values // ------------------------------------------------------------------------- -bool TestOverwrite() -{ - Environment::Set(TEST_KEY, "ValueA"); - IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueA")); +auto test_overwrite() -> bool { + (void)Environment::set(TEST_KEY, "ValueA"); + IAT_CHECK_EQ(Environment::get(TEST_KEY), String("ValueA")); - // Overwrite - Environment::Set(TEST_KEY, "ValueB"); - IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueB")); + // Overwrite + (void)Environment::set(TEST_KEY, "ValueB"); + IAT_CHECK_EQ(Environment::get(TEST_KEY), String("ValueB")); - Environment::Unset(TEST_KEY); - return TRUE; + (void)Environment::unset(TEST_KEY); + return true; } // ------------------------------------------------------------------------- // 3. Unset Logic // ------------------------------------------------------------------------- -bool TestUnset() -{ - Environment::Set(TEST_KEY, "To Be Deleted"); - IAT_CHECK(Environment::Exists(TEST_KEY)); +auto test_unset() -> bool { + (void)Environment::set(TEST_KEY, "To Be Deleted"); + IAT_CHECK(Environment::exists(TEST_KEY)); - bool unsetRes = Environment::Unset(TEST_KEY); - IAT_CHECK(unsetRes); + const auto unset_res = Environment::unset(TEST_KEY); + IAT_CHECK(unset_res.has_value()); - // Verify it is actually gone - IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); + // Verify it is actually gone + IAT_CHECK_NOT(Environment::exists(TEST_KEY)); - // Find should return nullopt - auto opt = Environment::Find(TEST_KEY); - IAT_CHECK_NOT(opt.has_value()); + // Find should return nullopt + const auto opt = Environment::find(TEST_KEY); + IAT_CHECK_NOT(opt.has_value()); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 4. Default Value Fallbacks // ------------------------------------------------------------------------- -bool TestDefaults() -{ - const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST"; +auto test_defaults() -> bool { + const char *ghost_key = "IA_THIS_KEY_DOES_NOT_EXIST"; - // Ensure it really doesn't exist - Environment::Unset(ghostKey); + // Ensure it really doesn't exist + (void)Environment::unset(ghost_key); - // Test Get with implicit default ("") - String empty = Environment::Get(ghostKey); - IAT_CHECK(empty.empty()); + // Test Get with implicit default ("") + const String empty = Environment::get(ghost_key); + IAT_CHECK(empty.empty()); - // Test Get with explicit default - String fallback = Environment::Get(ghostKey, "MyDefault"); - IAT_CHECK_EQ(fallback, String("MyDefault")); + // Test Get with explicit default + const String fallback = Environment::get(ghost_key, "MyDefault"); + IAT_CHECK_EQ(fallback, String("MyDefault")); - return TRUE; + return true; } // ------------------------------------------------------------------------- @@ -122,58 +117,56 @@ bool TestDefaults() // ------------------------------------------------------------------------- // Does Set(Key, "") create an existing empty variable, or unset it? // Standard POSIX/Windows API behavior is that it EXISTS, but is empty. -bool TestEmptyValue() -{ - Environment::Set(TEST_KEY, ""); +auto test_empty_value() -> bool { + (void)Environment::set(TEST_KEY, ""); #if IA_PLATFORM_WINDOWS - // Windows behavior: Setting to empty string removes the variable - // IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); - // auto opt = Environment::Find(TEST_KEY); - // IAT_CHECK_NOT(opt.has_value()); + // Windows behavior: Setting to empty string removes the variable + // IAT_CHECK_NOT(Environment::exists(TEST_KEY)); + // auto opt = Environment::find(TEST_KEY); + // IAT_CHECK_NOT(opt.has_value()); #else - // POSIX behavior: Variable exists but is empty - IAT_CHECK(Environment::Exists(TEST_KEY)); - auto opt = Environment::Find(TEST_KEY); - IAT_CHECK(opt.has_value()); - IAT_CHECK(opt->empty()); + // POSIX behavior: Variable exists but is empty + IAT_CHECK(Environment::exists(TEST_KEY)); + const auto opt = Environment::find(TEST_KEY); + IAT_CHECK(opt.has_value()); + IAT_CHECK(opt->empty()); #endif - // Cleanup - Environment::Unset(TEST_KEY); - IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); + // Cleanup + (void)Environment::unset(TEST_KEY); + IAT_CHECK_NOT(Environment::exists(TEST_KEY)); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 6. Validation / Bad Input // ------------------------------------------------------------------------- -bool TestBadInput() -{ - // Setting an empty key should fail gracefully - bool res = Environment::Set("", "Value"); - IAT_CHECK_NOT(res); +auto test_bad_input() -> bool { + // Setting an empty key should fail gracefully + const auto res = Environment::set("", "Value"); + IAT_CHECK_NOT(res.has_value()); - // Unsetting an empty key should fail - bool resUnset = Environment::Unset(""); - IAT_CHECK_NOT(resUnset); + // Unsetting an empty key should fail + const auto res_unset = Environment::unset(""); + IAT_CHECK_NOT(res_unset.has_value()); - return TRUE; + return true; } // ------------------------------------------------------------------------- // Registration // ------------------------------------------------------------------------- IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestBasicCycle); -IAT_ADD_TEST(TestOverwrite); -IAT_ADD_TEST(TestUnset); -IAT_ADD_TEST(TestDefaults); -IAT_ADD_TEST(TestEmptyValue); -IAT_ADD_TEST(TestBadInput); +IAT_ADD_TEST(test_basic_cycle); +IAT_ADD_TEST(test_overwrite); +IAT_ADD_TEST(test_unset); +IAT_ADD_TEST(test_defaults); +IAT_ADD_TEST(test_empty_value); +IAT_ADD_TEST(test_bad_input); IAT_END_TEST_LIST() IAT_END_BLOCK() -IAT_REGISTER_ENTRY(Core, Environment) +IAT_REGISTER_ENTRY(Core, Environment) \ No newline at end of file diff --git a/Tests/Unit/FileOps.cpp b/Tests/Unit/FileOps.cpp new file mode 100644 index 0000000..0913e21 --- /dev/null +++ b/Tests/Unit/FileOps.cpp @@ -0,0 +1,194 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, FileOps) + +// ------------------------------------------------------------------------- +// Helpers +// ------------------------------------------------------------------------- +void cleanup_file(const Path &path) { + std::error_code ec; + if (std::filesystem::exists(path, ec)) { + std::filesystem::remove(path, ec); + } +} + +// ------------------------------------------------------------------------- +// 1. Text File I/O +// ------------------------------------------------------------------------- +auto test_text_io() -> bool { + const Path path = "iatest_fileops_text.txt"; + const String content = "Hello IACore FileOps!\nLine 2"; + + // 1. Write + const auto write_res = FileOps::write_text_file(path, content, true); + IAT_CHECK(write_res.has_value()); + IAT_CHECK_EQ(*write_res, content.size()); + + // 2. Read + const auto read_res = FileOps::read_text_file(path); + IAT_CHECK(read_res.has_value()); + IAT_CHECK_EQ(*read_res, content); + + // Cleanup + cleanup_file(path); + return true; +} + +// ------------------------------------------------------------------------- +// 2. Binary File I/O +// ------------------------------------------------------------------------- +auto test_binary_io() -> bool { + const Path path = "iatest_fileops_bin.bin"; + const Vec content = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF}; + + // 1. Write + const auto write_res = FileOps::write_binary_file(path, content, true); + IAT_CHECK(write_res.has_value()); + IAT_CHECK_EQ(*write_res, content.size()); + + // 2. Read + const auto read_res = FileOps::read_binary_file(path); + IAT_CHECK(read_res.has_value()); + IAT_CHECK_EQ(read_res->size(), content.size()); + + for (usize i = 0; i < content.size(); ++i) { + IAT_CHECK_EQ((*read_res)[i], content[i]); + } + + // Cleanup + cleanup_file(path); + return true; +} + +// ------------------------------------------------------------------------- +// 3. Memory Mapping (File) +// ------------------------------------------------------------------------- +auto test_file_mapping() -> bool { + const Path path = "iatest_fileops_map.txt"; + const String content = "MappedContent"; + + // Setup file + (void)FileOps::write_text_file(path, content, true); + + // Map + usize size = 0; + const auto map_res = FileOps::map_file(path, size); + IAT_CHECK(map_res.has_value()); + IAT_CHECK_EQ(size, content.size()); + + const u8 *ptr = *map_res; + IAT_CHECK(ptr != nullptr); + + // Verify content via pointer + String read_back(reinterpret_cast(ptr), size); + IAT_CHECK_EQ(read_back, content); + + // Unmap + FileOps::unmap_file(ptr); + + cleanup_file(path); + return true; +} + +// ------------------------------------------------------------------------- +// 4. Shared Memory +// ------------------------------------------------------------------------- +auto test_shared_memory() -> bool { + const String shm_name = "iatest_shm_block"; + const usize shm_size = 4096; + + // 1. Create as Owner + auto owner_res = FileOps::map_shared_memory(shm_name, shm_size, true); + IAT_CHECK(owner_res.has_value()); + u8 *owner_ptr = *owner_res; + + // Write data + std::memset(owner_ptr, 0, shm_size); + const String msg = "Shared Memory Message"; + std::memcpy(owner_ptr, msg.data(), msg.size()); + + // 2. Open as Client + auto client_res = FileOps::map_shared_memory(shm_name, shm_size, false); + IAT_CHECK(client_res.has_value()); + u8 *client_ptr = *client_res; + + // Verify data + String read_msg(reinterpret_cast(client_ptr), msg.size()); + IAT_CHECK_EQ(read_msg, msg); + + // 3. Cleanup + FileOps::unmap_file(owner_ptr); + FileOps::unmap_file(client_ptr); + FileOps::unlink_shared_memory(shm_name); + + return true; +} + +// ------------------------------------------------------------------------- +// 5. Stream Integration +// ------------------------------------------------------------------------- +auto test_stream_integration() -> bool { + const Path path = "iatest_fileops_stream.bin"; + cleanup_file(path); + + // Write via StreamWriter + { + auto writer_res = FileOps::stream_to_file(path, true); + IAT_CHECK(writer_res.has_value()); + auto &writer = *writer_res; + + (void)writer.write(0x12345678); + (void)writer.write(0xFF); + } // Destructor should flush/close + + // Read via StreamReader + { + auto reader_res = FileOps::stream_from_file(path); + IAT_CHECK(reader_res.has_value()); + auto &reader = *reader_res; + + auto val_u32 = reader.read(); + IAT_CHECK(val_u32.has_value()); + IAT_CHECK_EQ(*val_u32, 0x12345678); + + auto val_u8 = reader.read(); + IAT_CHECK(val_u8.has_value()); + IAT_CHECK_EQ(*val_u8, 0xFF); + } + + cleanup_file(path); + return true; +} + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_text_io); +IAT_ADD_TEST(test_binary_io); +IAT_ADD_TEST(test_file_mapping); +IAT_ADD_TEST(test_shared_memory); +IAT_ADD_TEST(test_stream_integration); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, FileOps) \ No newline at end of file diff --git a/Tests/Unit/IPC.cpp b/Tests/Unit/IPC.cpp new file mode 100644 index 0000000..ce0bda6 --- /dev/null +++ b/Tests/Unit/IPC.cpp @@ -0,0 +1,163 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, IPC) + +// ------------------------------------------------------------------------- +// 1. Layout Constraints +// ------------------------------------------------------------------------- +auto test_layout_constraints() -> bool { + // Verify alignment + IAT_CHECK_EQ(alignof(IpcSharedMemoryLayout), static_cast(64)); + + // Verify offsets to ensure cache-line isolation + // Header is at 0 + IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, meta), static_cast(0)); + + // moni_control should be at 64 (cache line 1) + IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, moni_control), + static_cast(64)); + + // ControlBlock is 128 bytes (64 for producer, 64 for consumer) + // So mino_control should be at 64 + 128 = 192 + IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, mino_control), + static_cast(192)); + + // Data offsets should follow mino_control (192 + 128 = 320) + IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, moni_data_offset), + static_cast(320)); + + // Ensure the whole struct is a multiple of 64 + IAT_CHECK_EQ(sizeof(IpcSharedMemoryLayout) % 64, static_cast(0)); + + return true; +} + +// ------------------------------------------------------------------------- +// 2. Manual Shared Memory & RingBuffer Setup +// ------------------------------------------------------------------------- +auto test_manual_shm_ringbuffer() -> bool { + const String shm_name = "IA_TEST_IPC_LAYOUT_CHECK"; + const usize shm_size = 16 * 1024; // 16KB + + // 1. Create Shared Memory + // Ensure it doesn't exist first + FileOps::unlink_shared_memory(shm_name); + + auto map_res = FileOps::map_shared_memory(shm_name, shm_size, true); + IAT_CHECK(map_res.has_value()); + + u8 *base_ptr = *map_res; + auto *layout = reinterpret_cast(base_ptr); + + // 2. Initialize Layout + // We simulate what IpcManager would do + layout->meta.magic = 0xDEADBEEF; // Dummy + layout->meta.version = 1; + layout->meta.total_size = shm_size; + + // Define data regions + // Data starts after the layout struct + const usize header_size = IpcSharedMemoryLayout::get_header_size(); + const usize data_available = shm_size - header_size; + const usize half_data = data_available / 2; + + layout->moni_data_offset = header_size; + layout->moni_data_size = half_data; + + layout->mino_data_offset = header_size + half_data; + layout->mino_data_size = half_data; + + // 3. Initialize RingBuffers + // MONI (Manager Out, Node In) + // Manager is Owner of MONI + Span moni_data_span(base_ptr + layout->moni_data_offset, + static_cast(layout->moni_data_size)); + auto moni_res = + RingBufferView::create(&layout->moni_control, moni_data_span, true); + IAT_CHECK(moni_res.has_value()); + auto moni = std::move(*moni_res); + + // MINO (Manager In, Node Out) + // Manager is NOT Owner of MINO (Node writes to it) + // But for this test, let's pretend we are the Node for MINO to test writing + Span mino_data_span(base_ptr + layout->mino_data_offset, + static_cast(layout->mino_data_size)); + auto mino_res = + RingBufferView::create(&layout->mino_control, mino_data_span, true); + IAT_CHECK(mino_res.has_value()); + auto _ = std::move(*mino_res); + + // 4. Test Data Flow + // Write to MONI + String msg = "IPC_TEST_MESSAGE"; + IAT_CHECK( + moni.push(100, Span(reinterpret_cast(msg.data()), + msg.size())) + .has_value()); + + // Read from MONI (Simulate Node reading) + // Create a reader view + auto moni_reader_res = + RingBufferView::create(&layout->moni_control, moni_data_span, false); + IAT_CHECK(moni_reader_res.has_value()); + auto moni_reader = std::move(*moni_reader_res); + + RingBufferView::PacketHeader header; + u8 buffer[128]; + auto pop_res = moni_reader.pop(header, Span(buffer, 128)); + IAT_CHECK(pop_res.has_value()); + IAT_CHECK(pop_res->has_value()); // Should have data + IAT_CHECK_EQ(header.id, static_cast(100)); + + String received((char *)buffer, *pop_res.value()); + IAT_CHECK_EQ(received, msg); + + // Cleanup + FileOps::unmap_file(base_ptr); + FileOps::unlink_shared_memory(shm_name); + + return true; +} + +// ------------------------------------------------------------------------- +// 3. Manager Instantiation +// ------------------------------------------------------------------------- +class TestManager : public IpcManager { +public: + void on_signal(NativeProcessID, u8) override {} + void on_packet(NativeProcessID, u16, Span) override {} +}; + +auto test_manager_instantiation() -> bool { + TestManager mgr; + return true; +} + +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_layout_constraints); +IAT_ADD_TEST(test_manual_shm_ringbuffer); +IAT_ADD_TEST(test_manager_instantiation); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, IPC) \ No newline at end of file diff --git a/Tests/Unit/JSON.cpp b/Tests/Unit/JSON.cpp new file mode 100644 index 0000000..4fbfd5a --- /dev/null +++ b/Tests/Unit/JSON.cpp @@ -0,0 +1,192 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +using namespace IACore; + +// ----------------------------------------------------------------------------- +// Test Structures for Serialization +// ----------------------------------------------------------------------------- +struct UserProfile { + String username; + u32 id; + bool is_active; + Vec roles; + + // Equality operator for verification + bool operator==(const UserProfile &other) const { + return username == other.username && id == other.id && + is_active == other.is_active && roles == other.roles; + } +}; + +// ----------------------------------------------------------------------------- +// Test Block Definition +// ----------------------------------------------------------------------------- + +IAT_BEGIN_BLOCK(Core, JSON) + +// ------------------------------------------------------------------------- +// 1. Dynamic JSON (nlohmann::json) +// ------------------------------------------------------------------------- +auto test_dynamic_parse() -> bool { + const String json_text = R"({ + "string": "Hello World", + "int": 42, + "float": 3.14159, + "bool": true, + "array": [10, 20, 30], + "object": { "key": "value" } + })"; + + auto res = Json::parse(json_text); + IAT_CHECK(res.has_value()); + + const auto &j = *res; + + // Type checks and value retrieval + IAT_CHECK(j["string"].is_string()); + IAT_CHECK_EQ(j["string"].get(), String("Hello World")); + + IAT_CHECK(j["int"].is_number_integer()); + IAT_CHECK_EQ(j["int"].get(), 42); + + IAT_CHECK(j["float"].is_number_float()); + IAT_CHECK_APPROX(j["float"].get(), 3.14159f); + + IAT_CHECK(j["bool"].is_boolean()); + IAT_CHECK_EQ(j["bool"].get(), true); + + IAT_CHECK(j["array"].is_array()); + IAT_CHECK_EQ(j["array"].size(), 3u); + IAT_CHECK_EQ(j["array"][0].get(), 10); + + IAT_CHECK(j["object"].is_object()); + IAT_CHECK_EQ(j["object"]["key"].get(), String("value")); + + return true; +} + +auto test_dynamic_encode() -> bool { + nlohmann::json j; + j["name"] = "IACore"; + j["version"] = 2; + + const String encoded = Json::encode(j); + + // Simple containment check as key order isn't guaranteed + IAT_CHECK(encoded.find("IACore") != String::npos); + IAT_CHECK(encoded.find("version") != String::npos); + IAT_CHECK(encoded.find("2") != String::npos); + + return true; +} + +auto test_parse_invalid() -> bool { + const String bad_json = "{ key: value }"; // Missing quotes + auto res = Json::parse(bad_json); + IAT_CHECK_NOT(res.has_value()); + return true; +} + +// ------------------------------------------------------------------------- +// 2. Struct Serialization (Glaze) +// ------------------------------------------------------------------------- +auto test_struct_round_trip() -> bool { + UserProfile original{.username = "test_user", + .id = 12345, + .is_active = true, + .roles = {"admin", "editor"}}; + + // Struct -> JSON + auto encode_res = Json::encode_struct(original); + IAT_CHECK(encode_res.has_value()); + String json_str = *encode_res; + + // Verify JSON structure roughly + IAT_CHECK(json_str.find("test_user") != String::npos); + IAT_CHECK(json_str.find("roles") != String::npos); + + // JSON -> Struct + auto decode_res = Json::parse_to_struct(json_str); + IAT_CHECK(decode_res.has_value()); + + UserProfile decoded = *decode_res; + IAT_CHECK(decoded == original); + + return true; +} + +auto test_struct_parse_error() -> bool { + const String malformed = "{ broken_json: "; + auto res = Json::parse_to_struct(malformed); + IAT_CHECK_NOT(res.has_value()); + return true; +} + +// ------------------------------------------------------------------------- +// 3. Read-Only Parsing (simdjson) +// ------------------------------------------------------------------------- +auto test_read_only() -> bool { + const String json_text = R"({ + "id": 999, + "name": "Simd", + "scores": [1.1, 2.2] + })"; + + auto res = Json::parse_read_only(json_text); + IAT_CHECK(res.has_value()); + + auto &doc = *res; + simdjson::dom::element root = doc.root(); + + // Check ID + u64 id = 0; + auto err_id = root["id"].get(id); + IAT_CHECK(!err_id); + IAT_CHECK_EQ(id, 999ULL); + + // Check Name + std::string_view name; + auto err_name = root["name"].get(name); + IAT_CHECK(!err_name); + IAT_CHECK_EQ(String(name), String("Simd")); + + // Check Array + simdjson::dom::array scores; + auto err_arr = root["scores"].get(scores); + IAT_CHECK(!err_arr); + IAT_CHECK_EQ(scores.size(), 2u); + + return true; +} + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_dynamic_parse); +IAT_ADD_TEST(test_dynamic_encode); +IAT_ADD_TEST(test_parse_invalid); +IAT_ADD_TEST(test_struct_round_trip); +IAT_ADD_TEST(test_struct_parse_error); +IAT_ADD_TEST(test_read_only); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, JSON) \ No newline at end of file diff --git a/Tests/Unit/Logger.cpp b/Tests/Unit/Logger.cpp new file mode 100644 index 0000000..2711ccc --- /dev/null +++ b/Tests/Unit/Logger.cpp @@ -0,0 +1,124 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, Logger) + +static constexpr const char *LOG_FILE = "iacore_test_log.txt"; + +auto test_file_logging() -> bool { + // 1. Enable logging to disk + const auto res = Logger::enable_logging_to_disk(LOG_FILE); + IAT_CHECK(res.has_value()); + + // 2. Set level to Trace to ensure we capture everything + Logger::set_log_level(Logger::LogLevel::Trace); + + // 3. Log unique messages + const String msg_info = "Test_Info_Msg_123"; + const String msg_err = "Test_Error_Msg_456"; + const String msg_warn = "Test_Warn_Msg_789"; + + Logger::info("{}", msg_info); + Logger::error("{}", msg_err); + Logger::warn("{}", msg_warn); + + // 4. Flush + Logger::flush_logs(); + + // 5. Read back + auto read_res = FileOps::read_text_file(LOG_FILE); + if (!read_res) { + std::cout << console::YELLOW << " Warning: Could not read log file (" + << read_res.error() << "). Skipping verification.\n" + << console::RESET; + return true; + } + + const String content = *read_res; + + IAT_CHECK(content.find(msg_info) != String::npos); + IAT_CHECK(content.find(msg_err) != String::npos); + IAT_CHECK(content.find(msg_warn) != String::npos); + + // Check for log tags + IAT_CHECK(content.find("INFO") != String::npos); + IAT_CHECK(content.find("ERROR") != String::npos); + IAT_CHECK(content.find("WARN") != String::npos); + + return true; +} + +auto test_log_levels() -> bool { + // 1. Set level to Warn (Trace < Debug < Info < Warn < Error) + Logger::set_log_level(Logger::LogLevel::Warn); + + const String unique_info = "Hidden_Info_Msg"; + const String unique_warn = "Visible_Warn_Msg"; + + Logger::info("{}", unique_info); + Logger::warn("{}", unique_warn); + + Logger::flush_logs(); + + auto read_res = FileOps::read_text_file(LOG_FILE); + if (!read_res) { + return true; + } + + const String content = *read_res; + + // Info should NOT be present + IAT_CHECK(content.find(unique_info) == String::npos); + // Warn SHOULD be present + IAT_CHECK(content.find(unique_warn) != String::npos); + + return true; +} + +auto test_formatting() -> bool { + Logger::set_log_level(Logger::LogLevel::Info); + + const String name = "IACore"; + const i32 version = 99; + + Logger::info("System {} online v{}", name, version); + Logger::flush_logs(); + + auto read_res = FileOps::read_text_file(LOG_FILE); + if (!read_res) { + return true; + } + + const String content = *read_res; + IAT_CHECK(content.find("System IACore online v99") != String::npos); + + return true; +} + +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_file_logging); +IAT_ADD_TEST(test_log_levels); +IAT_ADD_TEST(test_formatting); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, Logger) \ No newline at end of file diff --git a/Tests/Unit/Main.cpp b/Tests/Unit/Main.cpp index a2d3bd8..896aba7 100644 --- a/Tests/Unit/Main.cpp +++ b/Tests/Unit/Main.cpp @@ -16,20 +16,26 @@ #include #include -int main(int argc, char *argv[]) -{ - UNUSED(argc); - UNUSED(argv); +#include - printf(__CC_GREEN "\n===============================================================\n"); - printf(" IACore (Independent Architecture Core) - Unit Test Suite\n"); - printf("===============================================================\n" __CC_DEFAULT "\n"); +using namespace IACore; - IACore::Initialize(); +IACORE_MAIN() { + (void)args; - int result = ia::iatest::TestRegistry::RunAll(); + IA_TRY_PURE(SocketOps::initialize()); - IACore::Terminate(); + std::cout + << console::GREEN + << "\n===============================================================\n"; + std::cout << " IACore (Independent Architecture Core) - Unit Test Suite\n"; + std::cout + << "===============================================================\n" + << console::RESET << "\n"; - return result; + const i32 result = Test::TestRegistry::run_all(); + + SocketOps::terminate(); + + return result; } \ No newline at end of file diff --git a/Tests/Unit/Platform.cpp b/Tests/Unit/Platform.cpp new file mode 100644 index 0000000..7812549 --- /dev/null +++ b/Tests/Unit/Platform.cpp @@ -0,0 +1,143 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, Platform) + +// ------------------------------------------------------------------------- +// 1. OS Name Detection +// ------------------------------------------------------------------------- +auto test_os_name() -> bool { + const char *os_name = Platform::get_operating_system_name(); + IAT_CHECK(os_name != nullptr); + + const String os(os_name); + IAT_CHECK(!os.empty()); + +#if IA_PLATFORM_WINDOWS + IAT_CHECK_EQ(os, String("Windows")); +#elif IA_PLATFORM_LINUX + IAT_CHECK_EQ(os, String("Linux")); +#elif IA_PLATFORM_APPLE + IAT_CHECK_EQ(os, String("MacOS")); +#elif IA_PLATFORM_WASM + IAT_CHECK_EQ(os, String("WebAssembly")); +#endif + + return true; +} + +// ------------------------------------------------------------------------- +// 2. Architecture Name Detection +// ------------------------------------------------------------------------- +auto test_arch_name() -> bool { + const char *arch_name = Platform::get_architecture_name(); + IAT_CHECK(arch_name != nullptr); + + const String arch(arch_name); + IAT_CHECK(!arch.empty()); + +#if IA_ARCH_X64 + IAT_CHECK_EQ(arch, String("x86_64")); +#elif IA_ARCH_ARM64 + IAT_CHECK_EQ(arch, String("ARM64")); +#elif IA_ARCH_WASM + IAT_CHECK_EQ(arch, String("WASM")); +#endif + + return true; +} + +// ------------------------------------------------------------------------- +// 3. CPU Capabilities +// ------------------------------------------------------------------------- +auto test_capabilities() -> bool { + // Initialize detection + const bool check_result = Platform::check_cpu(); + IAT_CHECK(check_result); + + const auto &caps = Platform::get_capabilities(); + + // We verify that we can access the capabilities struct. + // The actual value of hardware_crc32 depends on the host machine, + // so we cannot assert true or false, but we ensure no crash occurs. + volatile bool has_crc = caps.hardware_crc32; + (void)has_crc; + + return true; +} + +// ------------------------------------------------------------------------- +// 4. CPUID (x64 Only) +// ------------------------------------------------------------------------- +#if IA_ARCH_X64 +auto test_cpuid() -> bool { + i32 regs[4] = {0}; + + // Call CPUID with Function 0 (Vendor ID) + Platform::cpuid(0, 0, regs); + + // EAX (regs[0]) holds max supported function. Should be > 0 on any modern + // CPU. + IAT_CHECK(regs[0] >= 0); + + // EBX, EDX, ECX hold the vendor string. + // The order for the string is EBX, EDX, ECX. + char vendor[13]; + std::memset(vendor, 0, 13); + + std::memcpy(vendor, ®s[1], 4); // EBX + std::memcpy(vendor + 4, ®s[3], 4); // EDX + std::memcpy(vendor + 8, ®s[2], 4); // ECX + vendor[12] = '\0'; + + const String vendor_str(vendor); + IAT_CHECK(!vendor_str.empty()); + + // Check against common vendors to ensure registers contained valid ASCII + bool is_known = + (vendor_str == "GenuineIntel" || vendor_str == "AuthenticAMD" || + vendor_str == "KVMKVMKVM" || vendor_str == "Microsoft Hv" || + vendor_str == "VBoxVBoxVBox"); + + if (!is_known) { + // Not a failure, just an unknown CPU vendor (or virtualization) + std::cout << " [Info] Unknown CPU Vendor: " << vendor_str << "\n"; + } + + return true; +} +#endif + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_os_name); +IAT_ADD_TEST(test_arch_name); +IAT_ADD_TEST(test_capabilities); +#if IA_ARCH_X64 +IAT_ADD_TEST(test_cpuid); +#endif +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, Platform) \ No newline at end of file diff --git a/Tests/Unit/ProcessOps.cpp b/Tests/Unit/ProcessOps.cpp index 2adb548..7afeae2 100644 --- a/Tests/Unit/ProcessOps.cpp +++ b/Tests/Unit/ProcessOps.cpp @@ -13,9 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include +#include using namespace IACore; @@ -23,13 +22,13 @@ using namespace IACore; // Platform Abstraction for Test Commands // ----------------------------------------------------------------------------- #if IA_PLATFORM_WINDOWS -# define CMD_ECHO_EXE "cmd.exe" -# define CMD_ARG_PREFIX "/c echo" -# define NULL_DEVICE "NUL" +#define CMD_ECHO_EXE "cmd.exe" +#define CMD_ARG_PREFIX "/c echo" +#define NULL_DEVICE "NUL" #else -# define CMD_ECHO_EXE "/bin/echo" -# define CMD_ARG_PREFIX "" -# define NULL_DEVICE "/dev/null" +#define CMD_ECHO_EXE "/bin/echo" +#define CMD_ARG_PREFIX "" +#define NULL_DEVICE "/dev/null" #endif IAT_BEGIN_BLOCK(Core, ProcessOps) @@ -37,217 +36,228 @@ IAT_BEGIN_BLOCK(Core, ProcessOps) // ------------------------------------------------------------------------- // 1. Basic Execution (Exit Code 0) // ------------------------------------------------------------------------- -bool TestBasicRun() -{ - // Simple "echo hello" - String captured; +auto test_basic_run() -> bool { + // Simple "echo hello" + String captured; - auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA", - [&](StringView line) { captured = line; }); + const auto result = + ProcessOps::spawn_process_sync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA", + [&](StringView line) { captured = line; }); - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 0); // Exit code 0 + IAT_CHECK(result.has_value()); + IAT_CHECK_EQ(*result, 0); // Exit code 0 - // We check if "HelloIA" is contained or equal. - IAT_CHECK(captured.find("HelloIA") != String::npos); + // We check if "HelloIA" is contained or equal. + IAT_CHECK(captured.find("HelloIA") != String::npos); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 2. Argument Parsing // ------------------------------------------------------------------------- -bool TestArguments() -{ - Vec lines; +auto test_arguments() -> bool { + Vec lines; - // Echo two distinct words. - // Windows: cmd.exe /c echo one two - // Linux: /bin/echo one two - String args = String(CMD_ARG_PREFIX) + " one two"; - if (args[0] == ' ') - args.erase(0, 1); // cleanup space if prefix empty + // Echo two distinct words. + // Windows: cmd.exe /c echo one two + // Linux: /bin/echo one two + String args = String(CMD_ARG_PREFIX) + " one two"; + if (!args.empty() && args[0] == ' ') { + args.erase(0, 1); // cleanup space if prefix empty + } - auto result = - ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args, [&](StringView line) { lines.push_back(String(line)); }); + const auto result = + ProcessOps::spawn_process_sync(CMD_ECHO_EXE, args, [&](StringView line) { + lines.push_back(String(line)); + }); - IAT_CHECK_EQ(*result, 0); - IAT_CHECK(lines.size() > 0); + IAT_CHECK_EQ(*result, 0); + IAT_CHECK(lines.size() > 0); - // Output should contain "one two" - IAT_CHECK(lines[0].find("one two") != String::npos); + // Output should contain "one two" + IAT_CHECK(lines[0].find("one two") != String::npos); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 3. Error / Non-Zero Exit Codes // ------------------------------------------------------------------------- -bool TestExitCodes() -{ - // We need a command that returns non-zero. - // Windows: cmd /c exit 1 - // Linux: /bin/sh -c "exit 1" +auto test_exit_codes() -> bool { + // We need a command that returns non-zero. + // Windows: cmd /c exit 1 + // Linux: /bin/sh -c "exit 1" - String cmd, arg; + String cmd; + String arg; #if IA_PLATFORM_WINDOWS - cmd = "cmd.exe"; - arg = "/c exit 42"; + cmd = "cmd.exe"; + arg = "/c exit 42"; #else - cmd = "/bin/sh"; - arg = "-c \"exit 42\""; // quotes needed for sh -c + cmd = "/bin/sh"; + arg = "-c \"exit 42\""; // quotes needed for sh -c #endif - auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView) {}); + const auto result = + ProcessOps::spawn_process_sync(cmd, arg, [](StringView) {}); - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 42); + IAT_CHECK(result.has_value()); + IAT_CHECK_EQ(*result, 42); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 4. Missing Executable Handling // ------------------------------------------------------------------------- -bool TestMissingExe() -{ - // Try to run a random string - auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {}); +auto test_missing_exe() -> bool { + // Try to run a random string + const auto result = + ProcessOps::spawn_process_sync("sdflkjghsdflkjg", "", [](StringView) {}); - // Windows: CreateProcess usually fails -> returns unexpected - // Linux: execvp fails inside child, returns 127 via waitpid + // Windows: CreateProcess usually fails -> returns unexpected + // Linux: execvp fails inside child, returns 127 via waitpid #if IA_PLATFORM_WINDOWS - IAT_CHECK_NOT(result.has_value()); // Should be an error string + IAT_CHECK_NOT(result.has_value()); // Should be an error string #else - // Linux fork succeeds, but execvp fails, returning 127 - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 127); + // Linux fork succeeds, but execvp fails, returning 127 + IAT_CHECK(result.has_value()); + IAT_CHECK_EQ(*result, 127); #endif - return TRUE; + return true; } // ------------------------------------------------------------------------- // 5. Line Buffer Logic (The 4096 split test) // ------------------------------------------------------------------------- -bool TestLargeOutput() -{ - // Need to generate output larger than the internal 4096 buffer - // to ensure the "partial line" logic works when a line crosses a buffer boundary. +auto test_large_output() -> bool { + // Need to generate output larger than the internal 4096 buffer + // to ensure the "partial line" logic works when a line crosses a buffer + // boundary. - String massiveString; - massiveString.reserve(5000); - for (int i = 0; i < 500; ++i) - massiveString += "1234567890"; // 5000 chars + String massive_string; + massive_string.reserve(5000); + for (i32 i = 0; i < 500; ++i) { + massive_string += "1234567890"; // 5000 chars + } - String cmd, arg; + String cmd; + String arg; #if IA_PLATFORM_WINDOWS - cmd = "cmd.exe"; - // Windows has command line length limits (~8k), 5k is safe. - arg = "/c echo " + massiveString; + cmd = "cmd.exe"; + // Windows has command line length limits (~8k), 5k is safe. + arg = "/c echo " + massive_string; #else - cmd = "/bin/echo"; - arg = massiveString; + cmd = "/bin/echo"; + arg = massive_string; #endif - String captured; - auto result = ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) { captured += line; }); + String captured; + const auto result = ProcessOps::spawn_process_sync( + cmd, arg, [&](StringView line) { captured += line; }); - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 0); + IAT_CHECK(result.has_value()); + IAT_CHECK_EQ(*result, 0); - // If the LineBuffer failed to stitch chunks, the length wouldn't match - // or we would get multiple callbacks if we expected 1 line. - IAT_CHECK_EQ(captured.length(), massiveString.length()); + // If the LineBuffer failed to stitch chunks, the length wouldn't match + // or we would get multiple callbacks if we expected 1 line. + IAT_CHECK_EQ(captured.length(), massive_string.length()); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 6. Multi-Line Handling // ------------------------------------------------------------------------- -bool TestMultiLine() -{ - // Windows: cmd /c "echo A && echo B" - // Linux: /bin/sh -c "echo A; echo B" +auto test_multi_line() -> bool { + // Windows: cmd /c "echo A && echo B" + // Linux: /bin/sh -c "echo A; echo B" - String cmd, arg; + String cmd; + String arg; #if IA_PLATFORM_WINDOWS - cmd = "cmd.exe"; - arg = "/c \"echo LineA && echo LineB\""; + cmd = "cmd.exe"; + arg = "/c \"echo LineA && echo LineB\""; #else - cmd = "/bin/sh"; - arg = "-c \"echo LineA; echo LineB\""; + cmd = "/bin/sh"; + arg = "-c \"echo LineA; echo LineB\""; #endif - int lineCount = 0; - bool foundA = false; - bool foundB = false; + i32 line_count = 0; + bool found_a = false; + bool found_b = false; - UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) { - lineCount++; - if (line.find("LineA") != String::npos) - foundA = true; - if (line.find("LineB") != String::npos) - foundB = true; - })); + IA_UNUSED const auto res = + ProcessOps::spawn_process_sync(cmd, arg, [&](StringView line) { + line_count++; + if (line.find("LineA") != String::npos) { + found_a = true; + } + if (line.find("LineB") != String::npos) { + found_b = true; + } + }); - IAT_CHECK(foundA); - IAT_CHECK(foundB); - // We expect at least 2 lines. - // (Windows sometimes echoes the command itself depending on echo settings, but we check contents) - IAT_CHECK(lineCount >= 2); + IAT_CHECK(found_a); + IAT_CHECK(found_b); + // We expect at least 2 lines. + // (Windows sometimes echoes the command itself depending on echo settings, + // but we check contents) + IAT_CHECK(line_count >= 2); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 6. Complex Command Line Arguments Handling // ------------------------------------------------------------------------- -bool TestComplexArguments() -{ - // Should parse as 3 arguments: - // 1. -DDEFINED_MSG="Hello World" - // 2. -v - // 3. path/to/file - String complexArgs = "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file"; +auto test_complex_arguments() -> bool { + // Should parse as 3 arguments: + // 1. -DDEFINED_MSG="Hello World" + // 2. -v + // 3. path/to/file + const String complex_args = + "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file"; - String cmd = CMD_ECHO_EXE; + const String cmd = CMD_ECHO_EXE; - String finalArgs; + String final_args; #if IA_PLATFORM_WINDOWS - finalArgs = "/c echo " + complexArgs; + final_args = "/c echo " + complex_args; #else - finalArgs = complexArgs; + final_args = complex_args; #endif - String captured; - auto result = ProcessOps::SpawnProcessSync(cmd, finalArgs, [&](StringView line) { captured += line; }); + String captured; + const auto result = ProcessOps::spawn_process_sync( + cmd, final_args, [&](StringView line) { captured += line; }); - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 0); + IAT_CHECK(result.has_value()); + IAT_CHECK_EQ(*result, 0); - // Verify the quotes were preserved in the output - IAT_CHECK(captured.find("Hello World") != String::npos); - return TRUE; + // Verify the quotes were preserved in the output + IAT_CHECK(captured.find("Hello World") != String::npos); + return true; } // ------------------------------------------------------------------------- // Registration // ------------------------------------------------------------------------- IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestBasicRun); -IAT_ADD_TEST(TestArguments); -IAT_ADD_TEST(TestExitCodes); -IAT_ADD_TEST(TestMissingExe); -IAT_ADD_TEST(TestLargeOutput); -IAT_ADD_TEST(TestMultiLine); -IAT_ADD_TEST(TestComplexArguments); +IAT_ADD_TEST(test_basic_run); +IAT_ADD_TEST(test_arguments); +IAT_ADD_TEST(test_exit_codes); +IAT_ADD_TEST(test_missing_exe); +IAT_ADD_TEST(test_large_output); +IAT_ADD_TEST(test_multi_line); +IAT_ADD_TEST(test_complex_arguments); IAT_END_TEST_LIST() IAT_END_BLOCK() -IAT_REGISTER_ENTRY(Core, ProcessOps) +IAT_REGISTER_ENTRY(Core, ProcessOps) \ No newline at end of file diff --git a/Tests/Unit/RingBuffer.cpp b/Tests/Unit/RingBuffer.cpp index 2a165c3..3db86fa 100644 --- a/Tests/Unit/RingBuffer.cpp +++ b/Tests/Unit/RingBuffer.cpp @@ -23,82 +23,104 @@ IAT_BEGIN_BLOCK(Core, RingBuffer) // ------------------------------------------------------------------------- // 1. Basic Push Pop // ------------------------------------------------------------------------- -bool TestPushPop() { +auto test_push_pop() -> bool { // Allocate raw memory for the ring buffer // ControlBlock (128 bytes) + Data - std::vector memory(sizeof(RingBufferView::ControlBlock) + 1024); + Vec memory(sizeof(RingBufferView::ControlBlock) + 1024); // Initialize as OWNER (Producer) - RingBufferView producer(std::span(memory), TRUE); + auto producer_res = RingBufferView::create(Span(memory), true); + IAT_CHECK(producer_res.has_value()); + auto producer = std::move(*producer_res); // Initialize as CONSUMER (Pointer to same memory) - RingBufferView consumer(std::span(memory), FALSE); + auto consumer_res = RingBufferView::create(Span(memory), false); + IAT_CHECK(consumer_res.has_value()); + auto consumer = std::move(*consumer_res); // Data to send String msg = "Hello RingBuffer"; - bool pushed = producer.Push(1, {(const u8 *)msg.data(), msg.size()}); - IAT_CHECK(pushed); + const auto push_res = producer.push( + 1, Span(reinterpret_cast(msg.data()), msg.size())); + IAT_CHECK(push_res.has_value()); // Read back RingBufferView::PacketHeader header; - u8 readBuf[128]; + u8 read_buf[128]; - i32 bytesRead = consumer.Pop(header, std::span(readBuf, 128)); + const auto pop_res = consumer.pop(header, Span(read_buf, 128)); + IAT_CHECK(pop_res.has_value()); - IAT_CHECK_EQ(header.ID, (u16)1); - IAT_CHECK_EQ(bytesRead, (i32)msg.size()); + const auto bytes_read_opt = *pop_res; + IAT_CHECK(bytes_read_opt.has_value()); - String readMsg((char *)readBuf, bytesRead); - IAT_CHECK_EQ(readMsg, msg); + const usize bytes_read = *bytes_read_opt; - return TRUE; + IAT_CHECK_EQ(header.id, static_cast(1)); + IAT_CHECK_EQ(bytes_read, static_cast(msg.size())); + + String read_msg(reinterpret_cast(read_buf), bytes_read); + IAT_CHECK_EQ(read_msg, msg); + + return true; } // ------------------------------------------------------------------------- // 2. Wrap Around // ------------------------------------------------------------------------- -bool TestWrapAround() { +auto test_wrap_around() -> bool { // Small buffer to force wrapping quickly // Capacity will be 100 bytes - std::vector memory(sizeof(RingBufferView::ControlBlock) + 100); - RingBufferView rb(std::span(memory), TRUE); + Vec memory(sizeof(RingBufferView::ControlBlock) + 100); + + auto rb_res = RingBufferView::create(Span(memory), true); + IAT_CHECK(rb_res.has_value()); + auto rb = std::move(*rb_res); // Fill buffer to near end // Push 80 bytes - std::vector junk(80, 0xFF); - rb.Push(1, junk); + Vec junk(80, 0xFF); + const auto push1 = rb.push(1, junk); + IAT_CHECK(push1.has_value()); // Pop them to advance READ cursor RingBufferView::PacketHeader header; - u8 outBuf[100]; - rb.Pop(header, outBuf); + u8 out_buf[100]; + const auto pop1 = rb.pop(header, out_buf); + IAT_CHECK(pop1.has_value()); + IAT_CHECK(pop1->has_value()); // Now READ and WRITE are near index 80. // Pushing 40 bytes should trigger a wrap-around (split write) - std::vector wrapData(40, 0xAA); - bool pushed = rb.Push(2, wrapData); - IAT_CHECK(pushed); + Vec wrap_data(40, 0xAA); + const auto push2 = rb.push(2, wrap_data); + IAT_CHECK(push2.has_value()); // Pop and verify integrity - i32 popSize = rb.Pop(header, outBuf); - IAT_CHECK_EQ(popSize, 40); + const auto pop2 = rb.pop(header, out_buf); + IAT_CHECK(pop2.has_value()); + IAT_CHECK(pop2->has_value()); + + const usize pop_size = *pop2.value(); + IAT_CHECK_EQ(pop_size, static_cast(40)); // Check if data is intact - bool match = TRUE; - for (int i = 0; i < 40; i++) { - if (outBuf[i] != 0xAA) - match = FALSE; + bool match = true; + for (usize i = 0; i < 40; i++) { + if (out_buf[i] != 0xAA) { + match = false; + } } IAT_CHECK(match); - return TRUE; + return true; } IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestPushPop); -IAT_ADD_TEST(TestWrapAround); +IAT_ADD_TEST(test_push_pop); +IAT_ADD_TEST(test_wrap_around); IAT_END_TEST_LIST() IAT_END_BLOCK() -IAT_REGISTER_ENTRY(Core, RingBuffer) +IAT_REGISTER_ENTRY(Core, RingBuffer) \ No newline at end of file diff --git a/Tests/Unit/SIMD/FloatVec4.cpp b/Tests/Unit/SIMD/FloatVec4.cpp index 99b9d79..57c7631 100644 --- a/Tests/Unit/SIMD/FloatVec4.cpp +++ b/Tests/Unit/SIMD/FloatVec4.cpp @@ -13,92 +13,88 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include +#include using namespace IACore; IAT_BEGIN_BLOCK(Core, FloatVec4) -bool TestFloatArithmetic() -{ - FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f); - FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f); +auto test_float_arithmetic() -> bool { + FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f); + FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f); - alignas(16) f32 res[4]; + alignas(16) f32 res[4]; - (v1 / v2).Store(res); - IAT_CHECK_APPROX(res[0], 5.0f); - IAT_CHECK_APPROX(res[3], 5.0f); + (v1 / v2).store(res); + IAT_CHECK_APPROX(res[0], 5.0f); + IAT_CHECK_APPROX(res[3], 5.0f); - (v1 * v2).Store(res); - IAT_CHECK_APPROX(res[0], 20.0f); + (v1 * v2).store(res); + IAT_CHECK_APPROX(res[0], 20.0f); - (v1 + v2).Store(res); - IAT_CHECK_APPROX(res[0], 12.0f); + (v1 + v2).store(res); + IAT_CHECK_APPROX(res[0], 12.0f); - return TRUE; + return true; } -bool TestMathHelpers() -{ - alignas(16) f32 res[4]; +auto test_math_helpers() -> bool { + alignas(16) f32 res[4]; - FloatVec4 vSq(4.0f, 9.0f, 16.0f, 25.0f); - vSq.Sqrt().Store(res); - IAT_CHECK_APPROX(res[0], 2.0f); - IAT_CHECK_APPROX(res[3], 5.0f); + FloatVec4 v_sq(4.0f, 9.0f, 16.0f, 25.0f); + v_sq.sqrt().store(res); + IAT_CHECK_APPROX(res[0], 2.0f); + IAT_CHECK_APPROX(res[3], 5.0f); - FloatVec4 vNeg(-1.0f, -5.0f, 10.0f, -0.0f); - vNeg.Abs().Store(res); - IAT_CHECK_APPROX(res[0], 1.0f); - IAT_CHECK_APPROX(res[2], 10.0f); + FloatVec4 v_neg(-1.0f, -5.0f, 10.0f, -0.0f); + v_neg.abs().store(res); + IAT_CHECK_APPROX(res[0], 1.0f); + IAT_CHECK_APPROX(res[2], 10.0f); - FloatVec4 vClamp(-100.0f, 0.0f, 50.0f, 200.0f); - vClamp.Clamp(0.0f, 100.0f).Store(res); - IAT_CHECK_APPROX(res[0], 0.0f); - IAT_CHECK_APPROX(res[2], 50.0f); - IAT_CHECK_APPROX(res[3], 100.0f); + FloatVec4 v_clamp(-100.0f, 0.0f, 50.0f, 200.0f); + v_clamp.clamp(0.0f, 100.0f).store(res); + IAT_CHECK_APPROX(res[0], 0.0f); + IAT_CHECK_APPROX(res[2], 50.0f); + IAT_CHECK_APPROX(res[3], 100.0f); - return TRUE; + return true; } -bool TestApproxMath() -{ - alignas(16) f32 res[4]; - FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f); +auto test_approx_math() -> bool { + alignas(16) f32 res[4]; + FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f); - v.Rsqrt().Store(res); + v.rsqrt().store(res); - IAT_CHECK_APPROX(res[0], 0.25f); - IAT_CHECK_APPROX(res[2], 0.1f); + IAT_CHECK_APPROX(res[0], 0.25f); + IAT_CHECK_APPROX(res[2], 0.1f); - return TRUE; + return true; } -bool TestLinearAlgebra() -{ - FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f); - FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f); +auto test_linear_algebra() -> bool { + FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f); + FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f); - f32 dot = v1.Dot(v2); - IAT_CHECK_APPROX(dot, 4.0f); + f32 dot = v1.dot(v2); + IAT_CHECK_APPROX(dot, 4.0f); - FloatVec4 vNorm(10.0f, 0.0f, 0.0f, 0.0f); - alignas(16) f32 res[4]; + FloatVec4 v_norm(10.0f, 0.0f, 0.0f, 0.0f); + alignas(16) f32 res[4]; - vNorm.Normalize().Store(res); - IAT_CHECK_APPROX(res[0], 1.0f); - IAT_CHECK_APPROX(res[1], 0.0f); + v_norm.normalize().store(res); + IAT_CHECK_APPROX(res[0], 1.0f); + IAT_CHECK_APPROX(res[1], 0.0f); - return TRUE; + return true; } IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestFloatArithmetic); -IAT_ADD_TEST(TestMathHelpers); -IAT_ADD_TEST(TestApproxMath); -IAT_ADD_TEST(TestLinearAlgebra); +IAT_ADD_TEST(test_float_arithmetic); +IAT_ADD_TEST(test_math_helpers); +IAT_ADD_TEST(test_approx_math); +IAT_ADD_TEST(test_linear_algebra); IAT_END_TEST_LIST() IAT_END_BLOCK() diff --git a/Tests/Unit/SIMD/IntVec4.cpp b/Tests/Unit/SIMD/IntVec4.cpp index 72d23bc..36c6f0e 100644 --- a/Tests/Unit/SIMD/IntVec4.cpp +++ b/Tests/Unit/SIMD/IntVec4.cpp @@ -13,138 +13,130 @@ // See the License for the specific language governing permissions and // limitations under the License. -// IACore-OSS; The Core Library for All IA Open Source Projects -// Copyright (C) 2026 IAS (ias@iasoft.dev) - -#include #include +#include using namespace IACore; IAT_BEGIN_BLOCK(Core, IntVec4) -bool TestConstructors() -{ - IntVec4 vBroadcast(10); - alignas(16) u32 storeBuf[4]; - vBroadcast.Store(storeBuf); +auto test_constructors() -> bool { + IntVec4 v_broadcast(10); + alignas(16) u32 store_buf[4]; + v_broadcast.store(store_buf); - IAT_CHECK_EQ(storeBuf[0], 10U); - IAT_CHECK_EQ(storeBuf[3], 10U); + IAT_CHECK_EQ(store_buf[0], 10U); + IAT_CHECK_EQ(store_buf[3], 10U); - IntVec4 vComp(1, 2, 3, 4); - vComp.Store(storeBuf); - IAT_CHECK_EQ(storeBuf[0], 1U); - IAT_CHECK_EQ(storeBuf[3], 4U); + IntVec4 v_comp(1, 2, 3, 4); + v_comp.store(store_buf); + IAT_CHECK_EQ(store_buf[0], 1U); + IAT_CHECK_EQ(store_buf[3], 4U); - alignas(16) u32 srcBuf[4] = {100, 200, 300, 400}; - IntVec4 vLoad = IntVec4::Load(srcBuf); - vLoad.Store(storeBuf); - IAT_CHECK_EQ(storeBuf[1], 200U); + alignas(16) u32 src_buf[4] = {100, 200, 300, 400}; + IntVec4 v_load = IntVec4::load(src_buf); + v_load.store(store_buf); + IAT_CHECK_EQ(store_buf[1], 200U); - return TRUE; + return true; } -bool TestArithmetic() -{ - IntVec4 v1(10, 20, 30, 40); - IntVec4 v2(1, 2, 3, 4); +auto test_arithmetic() -> bool { + const IntVec4 v1(10, 20, 30, 40); + const IntVec4 v2(1, 2, 3, 4); - IntVec4 vAdd = v1 + v2; - alignas(16) u32 res[4]; - vAdd.Store(res); - IAT_CHECK_EQ(res[0], 11U); - IAT_CHECK_EQ(res[3], 44U); + IntVec4 v_add = v1 + v2; + alignas(16) u32 res[4]; + v_add.store(res); + IAT_CHECK_EQ(res[0], 11U); + IAT_CHECK_EQ(res[3], 44U); - IntVec4 vSub = v1 - v2; - vSub.Store(res); - IAT_CHECK_EQ(res[0], 9U); + IntVec4 v_sub = v1 - v2; + v_sub.store(res); + IAT_CHECK_EQ(res[0], 9U); - IntVec4 vMul = v1 * v2; - vMul.Store(res); - IAT_CHECK_EQ(res[0], 10U); - IAT_CHECK_EQ(res[2], 90U); - IAT_CHECK_EQ(res[3], 160U); + IntVec4 v_mul = v1 * v2; + v_mul.store(res); + IAT_CHECK_EQ(res[0], 10U); + IAT_CHECK_EQ(res[2], 90U); + IAT_CHECK_EQ(res[3], 160U); - return TRUE; + return true; } -bool TestBitwise() -{ - IntVec4 vAllOnes(0xFFFFFFFF); - IntVec4 vZero((u32) 0); - IntVec4 vPattern(0xAAAAAAAA); +auto test_bitwise() -> bool { + const IntVec4 v_all_ones(0xFFFFFFFF); + const IntVec4 v_zero((u32)0); + const IntVec4 v_pattern(0xAAAAAAAA); - alignas(16) u32 res[4]; + alignas(16) u32 res[4]; - (vAllOnes & vPattern).Store(res); - IAT_CHECK_EQ(res[0], 0xAAAAAAAAU); + (v_all_ones & v_pattern).store(res); + IAT_CHECK_EQ(res[0], 0xAAAAAAAAU); - (vZero | vPattern).Store(res); - IAT_CHECK_EQ(res[0], 0xAAAAAAAAU); + (v_zero | v_pattern).store(res); + IAT_CHECK_EQ(res[0], 0xAAAAAAAAU); - (vAllOnes ^ vPattern).Store(res); - IAT_CHECK_EQ(res[0], 0x55555555U); + (v_all_ones ^ v_pattern).store(res); + IAT_CHECK_EQ(res[0], 0x55555555U); - (~vPattern).Store(res); - IAT_CHECK_EQ(res[0], 0x55555555U); + (~v_pattern).store(res); + IAT_CHECK_EQ(res[0], 0x55555555U); - IntVec4 vShift(1); - (vShift << 1).Store(res); - IAT_CHECK_EQ(res[0], 2U); + const IntVec4 v_shift(1); + (v_shift << 1).store(res); + IAT_CHECK_EQ(res[0], 2U); - IntVec4 vShiftRight(4); - (vShiftRight >> 1).Store(res); - IAT_CHECK_EQ(res[0], 2U); + const IntVec4 v_shift_right(4); + (v_shift_right >> 1).store(res); + IAT_CHECK_EQ(res[0], 2U); - return TRUE; + return true; } -bool TestSaturation() -{ - u32 max = 0xFFFFFFFF; - IntVec4 vHigh(max - 10); - IntVec4 vAdd(20); +auto test_saturation() -> bool { + const u32 max = 0xFFFFFFFF; + const IntVec4 v_high(max - 10); + const IntVec4 v_add(20); - alignas(16) u32 res[4]; + alignas(16) u32 res[4]; - vHigh.SatAdd(vAdd).Store(res); - IAT_CHECK_EQ(res[0], max); + v_high.sat_add(v_add).store(res); + IAT_CHECK_EQ(res[0], max); - IntVec4 vLow(10); - IntVec4 vSub(20); - vLow.SatSub(vSub).Store(res); - IAT_CHECK_EQ(res[0], 0U); + const IntVec4 v_low(10); + const IntVec4 v_sub(20); + v_low.sat_sub(v_sub).store(res); + IAT_CHECK_EQ(res[0], 0U); - return TRUE; + return true; } -bool TestAdvancedOps() -{ - IntVec4 v(0, 50, 100, 150); - alignas(16) u32 res[4]; +auto test_advanced_ops() -> bool { + const IntVec4 v(0, 50, 100, 150); + alignas(16) u32 res[4]; - v.Clamp(40, 110).Store(res); - IAT_CHECK_EQ(res[0], 40U); - IAT_CHECK_EQ(res[1], 50U); - IAT_CHECK_EQ(res[2], 100U); - IAT_CHECK_EQ(res[3], 110U); + v.clamp(40, 110).store(res); + IAT_CHECK_EQ(res[0], 40U); + IAT_CHECK_EQ(res[1], 50U); + IAT_CHECK_EQ(res[2], 100U); + IAT_CHECK_EQ(res[3], 110U); - IntVec4 A(2); - IntVec4 B(10); - IntVec4 C(5); - A.MultAdd(B, C).Store(res); - IAT_CHECK_EQ(res[0], 25U); + const IntVec4 a(2); + const IntVec4 b(10); + const IntVec4 c(5); + a.mult_add(b, c).store(res); + IAT_CHECK_EQ(res[0], 25U); - return TRUE; + return true; } IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestConstructors); -IAT_ADD_TEST(TestArithmetic); -IAT_ADD_TEST(TestBitwise); -IAT_ADD_TEST(TestSaturation); -IAT_ADD_TEST(TestAdvancedOps); +IAT_ADD_TEST(test_constructors); +IAT_ADD_TEST(test_arithmetic); +IAT_ADD_TEST(test_bitwise); +IAT_ADD_TEST(test_saturation); +IAT_ADD_TEST(test_advanced_ops); IAT_END_TEST_LIST() IAT_END_BLOCK() diff --git a/Tests/Unit/SocketOps.cpp b/Tests/Unit/SocketOps.cpp new file mode 100644 index 0000000..5191b7e --- /dev/null +++ b/Tests/Unit/SocketOps.cpp @@ -0,0 +1,141 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, SocketOps) + +// ------------------------------------------------------------------------- +// 1. Initialization Logic +// ------------------------------------------------------------------------- +auto test_initialization() -> bool { + IAT_CHECK(SocketOps::is_initialized()); + + // Increment ref count + const auto res = SocketOps::initialize(); + IAT_CHECK(res.has_value()); + + // Decrement ref count + SocketOps::terminate(); + + // Should still be initialized (ref count > 0) + IAT_CHECK(SocketOps::is_initialized()); + + return true; +} + +// ------------------------------------------------------------------------- +// 2. Port Availability Checks +// ------------------------------------------------------------------------- +auto test_port_availability() -> bool { + // We cannot easily guarantee a port is free or taken without binding it, + // and SocketOps doesn't expose a generic TCP bind in its public API + // (only Unix sockets). + // However, we can verify the functions execute without crashing. + + const u16 port = 54321; + + // These return bools, we just ensure they run. + (void)SocketOps::is_port_available_tcp(port); + (void)SocketOps::is_port_available_udp(port); + + return true; +} + +// ------------------------------------------------------------------------- +// 3. Unix Domain Socket Lifecycle (Create, Bind, Listen, Connect) +// ------------------------------------------------------------------------- +auto test_unix_socket_lifecycle() -> bool { + const String socket_path = "iatest_ipc.sock"; + + // Ensure clean state + SocketOps::unlink_file(socket_path.c_str()); + + // 1. Create Server Socket + auto server_res = SocketOps::create_unix_socket(); + IAT_CHECK(server_res.has_value()); + SocketHandle server = *server_res; + + // 2. Bind + auto bind_res = SocketOps::bind_unix_socket(server, socket_path.c_str()); + if (!bind_res) { + // If bind fails (e.g. permissions), we clean up and fail the test + SocketOps::close(server); + return false; + } + + // 3. Listen + auto listen_res = SocketOps::listen(server); + IAT_CHECK(listen_res.has_value()); + + // 4. Create Client Socket + auto client_res = SocketOps::create_unix_socket(); + IAT_CHECK(client_res.has_value()); + SocketHandle client = *client_res; + + // 5. Connect + // Note: This relies on the OS backlog. We aren't calling accept() on the + // server, but connect() should succeed if the server is listening. + auto connect_res = + SocketOps::connect_unix_socket(client, socket_path.c_str()); + IAT_CHECK(connect_res.has_value()); + + // 6. Cleanup + SocketOps::close(client); + SocketOps::close(server); + SocketOps::unlink_file(socket_path.c_str()); + + return true; +} + +// ------------------------------------------------------------------------- +// 4. Unix Socket Error Handling +// ------------------------------------------------------------------------- +auto test_unix_socket_errors() -> bool { + const String socket_path = "iatest_missing.sock"; + + // Ensure it doesn't exist + SocketOps::unlink_file(socket_path.c_str()); + + auto client_res = SocketOps::create_unix_socket(); + IAT_CHECK(client_res.has_value()); + SocketHandle client = *client_res; + + // Should fail to connect to non-existent file + auto connect_res = + SocketOps::connect_unix_socket(client, socket_path.c_str()); + IAT_CHECK_NOT(connect_res.has_value()); + + SocketOps::close(client); + + return true; +} + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_initialization); +IAT_ADD_TEST(test_port_availability); +IAT_ADD_TEST(test_unix_socket_lifecycle); +IAT_ADD_TEST(test_unix_socket_errors); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, SocketOps) \ No newline at end of file diff --git a/Tests/Unit/StreamReader.cpp b/Tests/Unit/StreamReader.cpp index 5bdcb42..3283030 100644 --- a/Tests/Unit/StreamReader.cpp +++ b/Tests/Unit/StreamReader.cpp @@ -13,9 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include +#include using namespace IACore; @@ -24,71 +23,72 @@ IAT_BEGIN_BLOCK(Core, StreamReader) // ------------------------------------------------------------------------- // 1. Basic Primitive Reading (u8) // ------------------------------------------------------------------------- -bool TestReadUint8() { +auto test_read_uint8() -> bool { u8 data[] = {0xAA, 0xBB, 0xCC}; StreamReader reader(data); // Read First Byte - auto val1 = reader.Read(); + auto val1 = reader.read(); IAT_CHECK(val1.has_value()); IAT_CHECK_EQ(*val1, 0xAA); - IAT_CHECK_EQ(reader.Cursor(), (usize)1); + IAT_CHECK_EQ(reader.cursor(), static_cast(1)); // Read Second Byte - auto val2 = reader.Read(); + auto val2 = reader.read(); + IAT_CHECK(val2.has_value()); IAT_CHECK_EQ(*val2, 0xBB); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 2. Multi-byte Reading (Endianness check) // ------------------------------------------------------------------------- -bool TestReadMultiByte() { +auto test_read_multi_byte() -> bool { // 0x04030201 in Little Endian memory layout // IACore always assumes a Little Endian machine u8 data[] = {0x01, 0x02, 0x03, 0x04}; StreamReader reader(data); - auto val = reader.Read(); + auto val = reader.read(); IAT_CHECK(val.has_value()); - IAT_CHECK_EQ(*val, (u32)0x04030201); + IAT_CHECK_EQ(*val, static_cast(0x04030201)); - IAT_CHECK_EQ(reader.Cursor(), (usize)4); - IAT_CHECK(reader.IsEOF()); + IAT_CHECK_EQ(reader.cursor(), static_cast(4)); + IAT_CHECK(reader.is_eof()); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 3. Floating Point (Approx check) // ------------------------------------------------------------------------- -bool TestReadFloat() { - f32 pi = 3.14159f; +auto test_read_float() -> bool { + const f32 pi = 3.14159f; // Bit-cast float to bytes for setup u8 data[4]; std::memcpy(data, &pi, 4); StreamReader reader(data); - auto val = reader.Read(); + auto val = reader.read(); IAT_CHECK(val.has_value()); IAT_CHECK_APPROX(*val, pi); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 4. Batch Buffer Reading // ------------------------------------------------------------------------- -bool TestReadBuffer() { +auto test_read_buffer() -> bool { u8 src[] = {1, 2, 3, 4, 5}; u8 dst[3] = {0}; StreamReader reader(src); // Read 3 bytes into dst - auto res = reader.Read(dst, 3); + const auto res = reader.read(dst, 3); IAT_CHECK(res.has_value()); // Verify dst content @@ -97,74 +97,74 @@ bool TestReadBuffer() { IAT_CHECK_EQ(dst[2], 3); // Verify cursor - IAT_CHECK_EQ(reader.Cursor(), (usize)3); + IAT_CHECK_EQ(reader.cursor(), static_cast(3)); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 5. Navigation (Seek, Skip, Remaining) // ------------------------------------------------------------------------- -bool TestNavigation() { +auto test_navigation() -> bool { u8 data[10] = {0}; // Zero init StreamReader reader(data); - IAT_CHECK_EQ(reader.Remaining(), (usize)10); + IAT_CHECK_EQ(reader.remaining(), static_cast(10)); // Skip - reader.Skip(5); - IAT_CHECK_EQ(reader.Cursor(), (usize)5); - IAT_CHECK_EQ(reader.Remaining(), (usize)5); + reader.skip(5); + IAT_CHECK_EQ(reader.cursor(), static_cast(5)); + IAT_CHECK_EQ(reader.remaining(), static_cast(5)); // Skip clamping - reader.Skip(100); // Should clamp to 10 - IAT_CHECK_EQ(reader.Cursor(), (usize)10); - IAT_CHECK(reader.IsEOF()); + reader.skip(100); // Should clamp to 10 + IAT_CHECK_EQ(reader.cursor(), static_cast(10)); + IAT_CHECK(reader.is_eof()); // Seek - reader.Seek(2); - IAT_CHECK_EQ(reader.Cursor(), (usize)2); - IAT_CHECK_EQ(reader.Remaining(), (usize)8); - IAT_CHECK_NOT(reader.IsEOF()); + reader.seek(2); + IAT_CHECK_EQ(reader.cursor(), static_cast(2)); + IAT_CHECK_EQ(reader.remaining(), static_cast(8)); + IAT_CHECK_NOT(reader.is_eof()); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 6. Error Handling (EOF Protection) // ------------------------------------------------------------------------- -bool TestBoundaryChecks() { +auto test_boundary_checks() -> bool { u8 data[] = {0x00, 0x00}; StreamReader reader(data); // Valid read - UNUSED(reader.Read()); - IAT_CHECK(reader.IsEOF()); + (void)reader.read(); + IAT_CHECK(reader.is_eof()); // Invalid Read Primitive - auto val = reader.Read(); + auto val = reader.read(); IAT_CHECK_NOT(val.has_value()); // Should be unexpected // Invalid Batch Read u8 buf[1]; - auto batch = reader.Read(buf, 1); + auto batch = reader.read(buf, 1); IAT_CHECK_NOT(batch.has_value()); - return TRUE; + return true; } // ------------------------------------------------------------------------- // Registration // ------------------------------------------------------------------------- IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestReadUint8); -IAT_ADD_TEST(TestReadMultiByte); -IAT_ADD_TEST(TestReadFloat); -IAT_ADD_TEST(TestReadBuffer); -IAT_ADD_TEST(TestNavigation); -IAT_ADD_TEST(TestBoundaryChecks); +IAT_ADD_TEST(test_read_uint8); +IAT_ADD_TEST(test_read_multi_byte); +IAT_ADD_TEST(test_read_float); +IAT_ADD_TEST(test_read_buffer); +IAT_ADD_TEST(test_navigation); +IAT_ADD_TEST(test_boundary_checks); IAT_END_TEST_LIST() IAT_END_BLOCK() -IAT_REGISTER_ENTRY(Core, StreamReader) +IAT_REGISTER_ENTRY(Core, StreamReader) \ No newline at end of file diff --git a/Tests/Unit/StreamWriter.cpp b/Tests/Unit/StreamWriter.cpp new file mode 100644 index 0000000..c74bc6f --- /dev/null +++ b/Tests/Unit/StreamWriter.cpp @@ -0,0 +1,144 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, StreamWriter) + +// ------------------------------------------------------------------------- +// 1. Memory Writer (Dynamic Vector) +// ------------------------------------------------------------------------- +auto test_memory_writer() -> bool { + StreamWriter writer; + + // Write single byte repeated + IAT_CHECK(writer.write(static_cast(0xAA), 1).has_value()); + + // Write primitive (u32) - 0x12345678 + // Little Endian: 78 56 34 12 + const u32 val = 0x12345678; + IAT_CHECK(writer.write(val).has_value()); + + // Check cursor + IAT_CHECK_EQ(writer.cursor(), static_cast(1 + 4)); + + // Check data content + const u8 *ptr = writer.data(); + IAT_CHECK_EQ(ptr[0], 0xAA); + IAT_CHECK_EQ(ptr[1], 0x78); + IAT_CHECK_EQ(ptr[4], 0x12); + + return true; +} + +// ------------------------------------------------------------------------- +// 2. Fixed Buffer Writer (Non-Owning) +// ------------------------------------------------------------------------- +auto test_fixed_buffer() -> bool { + u8 buffer[4] = {0}; + StreamWriter writer(Span(buffer, 4)); + + // Write 2 bytes + IAT_CHECK(writer.write(static_cast(0xFF), 2).has_value()); + IAT_CHECK_EQ(writer.cursor(), static_cast(2)); + + // Write 2 more bytes + IAT_CHECK(writer.write(static_cast(0xEE), 2).has_value()); + IAT_CHECK_EQ(writer.cursor(), static_cast(4)); + + // Write 1 more byte -> Should fail (Out of bounds) + const auto res = writer.write(static_cast(0x00), 1); + IAT_CHECK_NOT(res.has_value()); + + // Verify content + IAT_CHECK_EQ(buffer[0], 0xFF); + IAT_CHECK_EQ(buffer[1], 0xFF); + IAT_CHECK_EQ(buffer[2], 0xEE); + IAT_CHECK_EQ(buffer[3], 0xEE); + + return true; +} + +// ------------------------------------------------------------------------- +// 3. File Writer +// ------------------------------------------------------------------------- +auto test_file_writer() -> bool { + const Path path = "test_stream_writer.bin"; + + // Ensure clean state + if (std::filesystem::exists(path)) { + std::filesystem::remove(path); + } + + { + auto res = StreamWriter::create_from_file(path); + IAT_CHECK(res.has_value()); + StreamWriter writer = std::move(*res); + + const String hello = "Hello World"; + IAT_CHECK(writer.write(hello.data(), hello.size()).has_value()); + + // Explicit flush + IAT_CHECK(writer.flush().has_value()); + } + + // Verify file content via FileOps + auto read_res = FileOps::read_binary_file(path); + IAT_CHECK(read_res.has_value()); + + const String read_str(reinterpret_cast(read_res->data()), + read_res->size()); + IAT_CHECK_EQ(read_str, String("Hello World")); + + // Cleanup + std::filesystem::remove(path); + + return true; +} + +// ------------------------------------------------------------------------- +// 4. Primitive Types +// ------------------------------------------------------------------------- +auto test_primitives() -> bool { + StreamWriter writer; + + const f32 f = 1.5f; + const u64 big = 0xDEADBEEFCAFEBABE; + + IAT_CHECK(writer.write(f).has_value()); + IAT_CHECK(writer.write(big).has_value()); + + IAT_CHECK_EQ(writer.cursor(), sizeof(f32) + sizeof(u64)); + + return true; +} + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_memory_writer); +IAT_ADD_TEST(test_fixed_buffer); +IAT_ADD_TEST(test_file_writer); +IAT_ADD_TEST(test_primitives); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, StreamWriter) \ No newline at end of file diff --git a/Tests/Unit/StringOps.cpp b/Tests/Unit/StringOps.cpp new file mode 100644 index 0000000..12f0d42 --- /dev/null +++ b/Tests/Unit/StringOps.cpp @@ -0,0 +1,125 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, StringOps) + +// ------------------------------------------------------------------------- +// 1. Base64 Encoding +// ------------------------------------------------------------------------- +auto test_base64_encode() -> bool { + // Case 1: Standard text + { + const String s = "Hello World"; + const Span data(reinterpret_cast(s.data()), s.size()); + const String encoded = StringOps::encode_base64(data); + IAT_CHECK_EQ(encoded, String("SGVsbG8gV29ybGQ=")); + } + + // Case 2: Padding Logic (1 byte -> 2 pad) + { + const String s = "M"; + const Span data(reinterpret_cast(s.data()), s.size()); + const String encoded = StringOps::encode_base64(data); + IAT_CHECK_EQ(encoded, String("TQ==")); + } + + // Case 3: Padding Logic (2 bytes -> 1 pad) + { + const String s = "Ma"; + const Span data(reinterpret_cast(s.data()), s.size()); + const String encoded = StringOps::encode_base64(data); + IAT_CHECK_EQ(encoded, String("TWE=")); + } + + // Case 4: Padding Logic (3 bytes -> 0 pad) + { + const String s = "Man"; + const Span data(reinterpret_cast(s.data()), s.size()); + const String encoded = StringOps::encode_base64(data); + IAT_CHECK_EQ(encoded, String("TWFu")); + } + + // Case 5: Empty + { + const String encoded = StringOps::encode_base64({}); + IAT_CHECK(encoded.empty()); + } + + return true; +} + +// ------------------------------------------------------------------------- +// 2. Base64 Decoding +// ------------------------------------------------------------------------- +auto test_base64_decode() -> bool { + // Case 1: Standard text + { + const String encoded = "SGVsbG8gV29ybGQ="; + const Vec decoded = StringOps::decode_base64(encoded); + const String result(reinterpret_cast(decoded.data()), + decoded.size()); + IAT_CHECK_EQ(result, String("Hello World")); + } + + // Case 2: Empty + { + const Vec decoded = StringOps::decode_base64(""); + IAT_CHECK(decoded.empty()); + } + + return true; +} + +// ------------------------------------------------------------------------- +// 3. Round Trip (Binary Data) +// ------------------------------------------------------------------------- +auto test_base64_round_trip() -> bool { + Vec original; + original.reserve(256); + for (usize i = 0; i < 256; ++i) { + original.push_back(static_cast(i)); + } + + const String encoded = StringOps::encode_base64(original); + const Vec decoded = StringOps::decode_base64(encoded); + + IAT_CHECK_EQ(original.size(), decoded.size()); + + bool match = true; + for (usize i = 0; i < original.size(); ++i) { + if (original[i] != decoded[i]) { + match = false; + break; + } + } + IAT_CHECK(match); + + return true; +} + +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_base64_encode); +IAT_ADD_TEST(test_base64_decode); +IAT_ADD_TEST(test_base64_round_trip); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, StringOps) \ No newline at end of file diff --git a/Tests/Unit/Utils.cpp b/Tests/Unit/Utils.cpp index d098329..8ffca62 100644 --- a/Tests/Unit/Utils.cpp +++ b/Tests/Unit/Utils.cpp @@ -13,9 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include +#include using namespace IACore; @@ -23,16 +22,14 @@ using namespace IACore; // Test Structs for Hashing (Must be defined at Global Scope) // ----------------------------------------------------------------------------- -struct TestVec3 -{ - f32 x, y, z; +struct TestVec3 { + f32 x, y, z; - // Equality operator required for hash maps, though strictly - // the hash function itself doesn't need it, it's good practice to test both. - bool operator==(const TestVec3 &other) const - { - return x == other.x && y == other.y && z == other.z; - } + // Equality operator required for hash maps, though strictly + // the hash function itself doesn't need it, it's good practice to test both. + bool operator==(const TestVec3 &other) const { + return x == other.x && y == other.y && z == other.z; + } }; // Inject the hash specialization into the ankerl namespace @@ -48,175 +45,169 @@ IAT_BEGIN_BLOCK(Core, Utils) // ------------------------------------------------------------------------- // 1. Binary <-> Hex String Conversion // ------------------------------------------------------------------------- -bool TestHexConversion() -{ - // A. Binary To Hex - u8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF}; - String hex = IACore::Utils::BinaryToHexString(bin); +auto test_hex_conversion() -> bool { + // A. Binary To Hex + u8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF}; + String hex = Utils::binary_to_hex_string(bin); - IAT_CHECK_EQ(hex, String("DEADBEEF00FF")); + IAT_CHECK_EQ(hex, String("DEADBEEF00FF")); - // B. Hex To Binary (Valid Upper) - auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF"); - IAT_CHECK(resUpper.has_value()); - IAT_CHECK_EQ(resUpper->size(), (usize) 6); - IAT_CHECK_EQ((*resUpper)[0], 0xDE); - IAT_CHECK_EQ((*resUpper)[5], 0xFF); + // B. Hex To Binary (Valid Upper) + auto res_upper = Utils::hex_string_to_binary("DEADBEEF00FF"); + IAT_CHECK(res_upper.has_value()); + IAT_CHECK_EQ(res_upper->size(), static_cast(6)); + IAT_CHECK_EQ((*res_upper)[0], 0xDE); + IAT_CHECK_EQ((*res_upper)[5], 0xFF); - // C. Hex To Binary (Valid Lower/Mixed) - auto resLower = IACore::Utils::HexStringToBinary("deadbeef00ff"); - IAT_CHECK(resLower.has_value()); - IAT_CHECK_EQ((*resLower)[0], 0xDE); + // C. Hex To Binary (Valid Lower/Mixed) + auto res_lower = Utils::hex_string_to_binary("deadbeef00ff"); + IAT_CHECK(res_lower.has_value()); + IAT_CHECK_EQ((*res_lower)[0], 0xDE); - // D. Round Trip Integrity - Vec original = {1, 2, 3, 4, 5}; - String s = IACore::Utils::BinaryToHexString(original); - auto back = IACore::Utils::HexStringToBinary(s); - IAT_CHECK(back.has_value()); - IAT_CHECK_EQ(original.size(), back->size()); - IAT_CHECK_EQ(original[2], (*back)[2]); + // D. Round Trip Integrity + Vec original = {1, 2, 3, 4, 5}; + String s = Utils::binary_to_hex_string(original); + auto back = Utils::hex_string_to_binary(s); + IAT_CHECK(back.has_value()); + IAT_CHECK_EQ(original.size(), back->size()); + IAT_CHECK_EQ(original[2], (*back)[2]); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 2. Hex Error Handling // ------------------------------------------------------------------------- -bool TestHexErrors() -{ - // Odd Length - auto odd = IACore::Utils::HexStringToBinary("ABC"); - IAT_CHECK_NOT(odd.has_value()); +auto test_hex_errors() -> bool { + // Odd Length + auto odd = Utils::hex_string_to_binary("ABC"); + IAT_CHECK_NOT(odd.has_value()); - // Invalid Characters - auto invalid = IACore::Utils::HexStringToBinary("ZZTOP"); - IAT_CHECK_NOT(invalid.has_value()); + // Invalid Characters + auto invalid = Utils::hex_string_to_binary("ZZTOP"); + IAT_CHECK_NOT(invalid.has_value()); - // Empty string is valid (empty vector) - auto empty = IACore::Utils::HexStringToBinary(""); - IAT_CHECK(empty.has_value()); - IAT_CHECK_EQ(empty->size(), (usize) 0); + // Empty string is valid (empty vector) + auto empty = Utils::hex_string_to_binary(""); + IAT_CHECK(empty.has_value()); + IAT_CHECK_EQ(empty->size(), static_cast(0)); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 3. Algorithms: Sorting // ------------------------------------------------------------------------- -bool TestSort() -{ - Vec nums = {5, 1, 4, 2, 3}; +auto test_sort() -> bool { + Vec nums = {5, 1, 4, 2, 3}; - IACore::Utils::Sort(nums); + Utils::sort(nums); - IAT_CHECK_EQ(nums[0], 1); - IAT_CHECK_EQ(nums[1], 2); - IAT_CHECK_EQ(nums[2], 3); - IAT_CHECK_EQ(nums[3], 4); - IAT_CHECK_EQ(nums[4], 5); + IAT_CHECK_EQ(nums[0], 1); + IAT_CHECK_EQ(nums[1], 2); + IAT_CHECK_EQ(nums[2], 3); + IAT_CHECK_EQ(nums[3], 4); + IAT_CHECK_EQ(nums[4], 5); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 4. Algorithms: Binary Search (Left/Right) // ------------------------------------------------------------------------- -bool TestBinarySearch() -{ - // Must be sorted for Binary Search - Vec nums = {10, 20, 20, 20, 30}; +auto test_binary_search() -> bool { + // Must be sorted for Binary Search + Vec nums = {10, 20, 20, 20, 30}; - // Search Left (Lower Bound) -> First element >= value - auto itLeft = IACore::Utils::BinarySearchLeft(nums, 20); - IAT_CHECK(itLeft != nums.end()); - IAT_CHECK_EQ(*itLeft, 20); - IAT_CHECK_EQ(std::distance(nums.begin(), itLeft), 1); // Index 1 is first 20 + // Search Left (Lower Bound) -> First element >= value + auto it_left = Utils::binary_search_left(nums, 20); + IAT_CHECK(it_left != nums.end()); + IAT_CHECK_EQ(*it_left, 20); + IAT_CHECK_EQ(std::distance(nums.begin(), it_left), 1); // Index 1 is first 20 - // Search Right (Upper Bound) -> First element > value - auto itRight = IACore::Utils::BinarySearchRight(nums, 20); - IAT_CHECK(itRight != nums.end()); - IAT_CHECK_EQ(*itRight, 30); // Points to 30 - IAT_CHECK_EQ(std::distance(nums.begin(), itRight), 4); // Index 4 + // Search Right (Upper Bound) -> First element > value + auto it_right = Utils::binary_search_right(nums, 20); + IAT_CHECK(it_right != nums.end()); + IAT_CHECK_EQ(*it_right, 30); // Points to 30 + IAT_CHECK_EQ(std::distance(nums.begin(), it_right), 4); // Index 4 - // Search for non-existent - auto itFail = IACore::Utils::BinarySearchLeft(nums, 99); - IAT_CHECK(itFail == nums.end()); + // Search for non-existent + auto it_fail = Utils::binary_search_left(nums, 99); + IAT_CHECK(it_fail == nums.end()); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 5. Hashing Basics // ------------------------------------------------------------------------- -bool TestHashBasics() -{ - u64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); - u64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); - u64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World"); +auto test_hash_basics() -> bool { + u64 h1 = Utils::compute_hash(10, 20.5f, "Hello"); + u64 h2 = Utils::compute_hash(10, 20.5f, "Hello"); + u64 h3 = Utils::compute_hash(10, 20.5f, "World"); - // Determinism - IAT_CHECK_EQ(h1, h2); + // Determinism + IAT_CHECK_EQ(h1, h2); - // Differentiation - IAT_CHECK_NEQ(h1, h3); + // Differentiation + IAT_CHECK_NEQ(h1, h3); - // Order sensitivity (Golden ratio combine should care about order) - // Hash(A, B) != Hash(B, A) - u64 orderA = IACore::Utils::ComputeHash(1, 2); - u64 orderB = IACore::Utils::ComputeHash(2, 1); - IAT_CHECK_NEQ(orderA, orderB); + // Order sensitivity (Golden ratio combine should care about order) + // Hash(A, B) != Hash(B, A) + u64 order_a = Utils::compute_hash(1, 2); + u64 order_b = Utils::compute_hash(2, 1); + IAT_CHECK_NEQ(order_a, order_b); - return TRUE; + return true; } // ------------------------------------------------------------------------- // 6. Macro Verification (IA_MAKE_HASHABLE) // ------------------------------------------------------------------------- -bool TestHashMacro() -{ - TestVec3 v1{1.0f, 2.0f, 3.0f}; - TestVec3 v2{1.0f, 2.0f, 3.0f}; - TestVec3 v3{1.0f, 2.0f, 4.0f}; +auto test_hash_macro() -> bool { + TestVec3 v1{1.0f, 2.0f, 3.0f}; + TestVec3 v2{1.0f, 2.0f, 3.0f}; + TestVec3 v3{1.0f, 2.0f, 4.0f}; - ankerl::unordered_dense::hash hasher; + ankerl::unordered_dense::hash hasher; - u64 h1 = hasher(v1); - u64 h2 = hasher(v2); - u64 h3 = hasher(v3); + u64 h1 = hasher(v1); + u64 h2 = hasher(v2); + u64 h3 = hasher(v3); - IAT_CHECK_EQ(h1, h2); // Same content = same hash - IAT_CHECK_NEQ(h1, h3); // Different content = different hash + IAT_CHECK_EQ(h1, h2); // Same content = same hash + IAT_CHECK_NEQ(h1, h3); // Different content = different hash - // ------------------------------------------------------------- - // Verify ComputeHash integration - // ------------------------------------------------------------- + // ------------------------------------------------------------- + // Verify ComputeHash integration + // ------------------------------------------------------------- - u64 hManual = 0; - IACore::Utils::HashCombine(hManual, v1); + u64 h_manual = 0; + Utils::hash_combine(h_manual, v1); - u64 hWrapper = IACore::Utils::ComputeHash(v1); + u64 h_wrapper = Utils::compute_hash(v1); - // This proves ComputeHash found the specialization and mixed it correctly - IAT_CHECK_EQ(hManual, hWrapper); + // This proves ComputeHash found the specialization and mixed it correctly + IAT_CHECK_EQ(h_manual, h_wrapper); - // Verify the avalanche effect took place (hWrapper should NOT be h1) - IAT_CHECK_NEQ(h1, hWrapper); + // Verify the avalanche effect took place (hWrapper should NOT be h1) + IAT_CHECK_NEQ(h1, h_wrapper); - return TRUE; + return true; } // ------------------------------------------------------------------------- // Registration // ------------------------------------------------------------------------- IAT_BEGIN_TEST_LIST() -IAT_ADD_TEST(TestHexConversion); -IAT_ADD_TEST(TestHexErrors); -IAT_ADD_TEST(TestSort); -IAT_ADD_TEST(TestBinarySearch); -IAT_ADD_TEST(TestHashBasics); -IAT_ADD_TEST(TestHashMacro); +IAT_ADD_TEST(test_hex_conversion); +IAT_ADD_TEST(test_hex_errors); +IAT_ADD_TEST(test_sort); +IAT_ADD_TEST(test_binary_search); +IAT_ADD_TEST(test_hash_basics); +IAT_ADD_TEST(test_hash_macro); IAT_END_TEST_LIST() IAT_END_BLOCK() -IAT_REGISTER_ENTRY(Core, Utils) +IAT_REGISTER_ENTRY(Core, Utils) \ No newline at end of file diff --git a/Tests/Unit/XML.cpp b/Tests/Unit/XML.cpp new file mode 100644 index 0000000..ad22837 --- /dev/null +++ b/Tests/Unit/XML.cpp @@ -0,0 +1,135 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2026 IAS (ias@iasoft.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, XML) + +// ------------------------------------------------------------------------- +// 1. Basic String Parsing +// ------------------------------------------------------------------------- +auto test_parse_string() -> bool { + const String xml_content = R"( + + Value1 + Value2 + + )"; + + auto res = XML::parse_from_string(xml_content); + IAT_CHECK(res.has_value()); + + auto &doc = *res; + auto root = doc.child("root"); + IAT_CHECK(root); + + auto item1 = root.find_child_by_attribute("item", "id", "1"); + IAT_CHECK(item1); + IAT_CHECK_EQ(String(item1.child_value()), String("Value1")); + + auto item2 = root.find_child_by_attribute("item", "id", "2"); + IAT_CHECK(item2); + IAT_CHECK_EQ(String(item2.child_value()), String("Value2")); + + return true; +} + +// ------------------------------------------------------------------------- +// 2. Error Handling +// ------------------------------------------------------------------------- +auto test_parse_error() -> bool { + const String invalid_xml = ""; + auto res = XML::parse_from_string(invalid_xml); + IAT_CHECK_NOT(res.has_value()); + return true; +} + +// ------------------------------------------------------------------------- +// 3. Serialization +// ------------------------------------------------------------------------- +auto test_serialize() -> bool { + const String xml_content = "Text"; + auto res = XML::parse_from_string(xml_content); + IAT_CHECK(res.has_value()); + + String output = XML::serialize_to_string(*res); + + // Basic containment check as formatting might vary + IAT_CHECK(output.find("") != String::npos); + IAT_CHECK(output.find("Text") != String::npos); + + return true; +} + +// ------------------------------------------------------------------------- +// 4. String Escaping +// ------------------------------------------------------------------------- +auto test_escape() -> bool { + const String raw = "< & > \" '"; + const String escaped = XML::escape_xml_string(raw); + + // Check for standard XML entities + IAT_CHECK(escaped.find("<") != String::npos); + IAT_CHECK(escaped.find("&") != String::npos); + IAT_CHECK(escaped.find(">") != String::npos); + IAT_CHECK(escaped.find(""") != String::npos); + IAT_CHECK(escaped.find("'") != String::npos); + + return true; +} + +// ------------------------------------------------------------------------- +// 5. File I/O Integration +// ------------------------------------------------------------------------- +auto test_file_io() -> bool { + const Path path = "test_temp_xml_doc.xml"; + const String content = "1.0"; + + // 1. Write Test File + auto write_res = FileOps::write_text_file(path, content, true); + IAT_CHECK(write_res.has_value()); + + // 2. Parse from File + auto parse_res = XML::parse_from_file(path); + IAT_CHECK(parse_res.has_value()); + + auto &doc = *parse_res; + IAT_CHECK_EQ(String(doc.child("config").child("ver").child_value()), + String("1.0")); + + // 3. Cleanup + std::filesystem::remove(path); + + return true; +} + +// ------------------------------------------------------------------------- +// Registration +// ------------------------------------------------------------------------- +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(test_parse_string); +IAT_ADD_TEST(test_parse_error); +IAT_ADD_TEST(test_serialize); +IAT_ADD_TEST(test_escape); +IAT_ADD_TEST(test_file_io); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, XML) \ No newline at end of file