diff --git a/.gitignore b/.gitignore index 29010d6..e7188e5 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,5 @@ .cache/ .local/ -Build/ +[Bb]uild +out/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c0dc405 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "C_Cpp.clang_format_fallbackStyle": "Microsoft" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d9c8d27..3cd9419 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,13 +1,19 @@ { + "version": "2.0.0", "tasks": [ { - "label": "build", - "type": "shell", - "command": "python3 Tools/Builder/Build.py", + "label": "CMake Build", + "type": "cmake", + "command": "build", "group": { "kind": "build", "isDefault": true - } + }, + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "detail": "Builds the currently selected CMake Preset" } - ], + ] } \ No newline at end of file diff --git a/CMake/FindDeps.cmake b/CMake/FindDeps.cmake index 5d9a111..6ecd6a4 100644 --- a/CMake/FindDeps.cmake +++ b/CMake/FindDeps.cmake @@ -54,16 +54,16 @@ FetchContent_Declare( EXCLUDE_FROM_ALL ) -#FetchContent_Declare( -# mimalloc -# GIT_REPOSITORY https://github.com/microsoft/mimalloc.git -# GIT_TAG v3.0.10 -# SYSTEM -# EXCLUDE_FROM_ALL -# PATCH_COMMAND ${CMAKE_COMMAND} -# -DSOURCE_DIR= -# -P ${CMAKE_CURRENT_SOURCE_DIR}/CMake/PatchMimalloc.cmake -#) +FetchContent_Declare( + mimalloc + GIT_REPOSITORY https://github.com/microsoft/mimalloc.git + GIT_TAG v3.0.10 + SYSTEM + EXCLUDE_FROM_ALL + PATCH_COMMAND ${CMAKE_COMMAND} + -DSOURCE_DIR= + -P ${CMAKE_CURRENT_SOURCE_DIR}/CMake/PatchMimalloc.cmake +) FetchContent_Declare( tl-expected @@ -81,10 +81,10 @@ FetchContent_Declare( EXCLUDE_FROM_ALL ) -#set(MI_OVERRIDE ON CACHE BOOL "" FORCE) -#set(MI_BUILD_STATIC ON CACHE BOOL "" FORCE) -#set(MI_BUILD_TESTS OFF CACHE BOOL "" FORCE) -#set(MI_BUILD_SHARED OFF CACHE BOOL "" FORCE) +set(MI_OVERRIDE ON CACHE BOOL "" FORCE) +set(MI_BUILD_STATIC ON CACHE BOOL "" FORCE) +set(MI_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(MI_BUILD_SHARED OFF CACHE BOOL "" FORCE) set(EXPECTED_BUILD_TESTS OFF CACHE BOOL "" FORCE) @@ -97,7 +97,6 @@ set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE) set(ZLIB_COMPAT ON CACHE BOOL "" FORCE) set(ZLIB_ENABLE_TESTS OFF CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(httplib nlohmann_json glaze simdjson ZLIB zstd tl-expected unordered_dense #mimalloc -) +FetchContent_MakeAvailable(httplib nlohmann_json glaze simdjson ZLIB zstd tl-expected unordered_dense mimalloc) find_package(OpenSSL REQUIRED) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c061ce..f122d6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,17 +45,8 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" ) endif() -add_compile_definitions(-D_ITERATOR_DEBUG_LEVEL=0) - add_subdirectory(Src/) if(IACore_BUILD_TESTS) add_subdirectory(Tests) endif() - -# ------------------------------------------------- -# Local Development Sandboxes (not included in source control) -# ------------------------------------------------- -if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.local") - add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/.local") -endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..c8cd0e5 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,108 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 28 + }, + "configurePresets": [ + { + "name": "base-common", + "hidden": true, + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "windows-base", + "hidden": true, + "inherits": "base-common", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "VCPKG_TARGET_TRIPLET": "x64-windows" + } + }, + { + "name": "linux-base", + "hidden": true, + "inherits": "base-common", + "condition": { + "type": "notEquals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "windows-default", + "displayName": "Windows (VCPKG)", + "description": "Windows build using VCPKG", + "inherits": "windows-base" + }, + { + "name": "linux-default", + "displayName": "Linux (System)", + "description": "Linux build using system compilers", + "inherits": "linux-base" + }, + { + "name": "linux-ci", + "displayName": "Linux CI Build", + "description": "Linux CI Build", + "inherits": "linux-base", + "cacheVariables": { + "SDL_UNIX_CONSOLE_BUILD": "ON", + "IS_CI_BUILD": "ON" + } + } + ], + "buildPresets": [ + { + "name": "windows-debug", + "configurePreset": "windows-default", + "configuration": "debug", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "linux-debug", + "configurePreset": "linux-default", + "configuration": "debug", + "condition": { + "type": "notEquals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "linux-ci-debug", + "configurePreset": "linux-ci", + "configuration": "debug", + "condition": { + "type": "notEquals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "linux-ci-release", + "configurePreset": "linux-ci", + "configuration": "release", + "condition": { + "type": "notEquals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 765222d..4106a26 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,94 @@ -# IACore +# IACore (Independent Architecture Core) +![License](https://img.shields.io/badge/license-GPLv3-blue.svg) +![Standard](https://img.shields.io/badge/C%2B%2B-20-yellow.svg) +![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey.svg) + +IACore is a high-performance, battery-included C++20 foundation library designed to eliminate "dependency hell." It bundles essential systems—IPC, Logging, Networking, Compression, and Async Scheduling—into a single, coherent API. + +Originally developed as the internal core for IASoft (PVT) LTD., it is now open-source to provide a standardized bedrock for C++ applications. + +## ✨ Features + +* **🚀 High-Performance IPC:** Shared-Memory Ring Buffers with wait-free SPSC synchronization. +* **🌐 Networking:** Integrated HTTP/HTTPS client (wrapper around `cpp-httplib` with automatic Zlib/Gzip/Brotli handling). +* **🧵 Async Scheduler:** A job system with high/normal priority queues and work stealing. +* **💾 File I/O:** Memory-mapped file operations and optimized binary stream readers/writers. +* **📦 Compression:** Unified API for Zlib, Gzip, and Zstd. +* **📜 Logging:** Thread-safe, colored console and disk logging. +* **⚡ Modern C++:** Heavily uses C++20 concepts, `std::span`, and `tl::expected` for error handling. + +## 🛠️ Integration + +IACore is built with CMake. You can include it in your project via `FetchContent` or by adding it as a subdirectory. + +### CMake Example +```cmake +add_subdirectory(IACore) + +add_executable(MyApp Main.cpp) +target_link_libraries(MyApp PRIVATE IACore) +``` + +## 📦 Dependencies +IACore manages its own dependencies via CMake's FetchContent. You do not need to install these manually: + +* nlohmann_json & glaze (JSON Parsing) +* cpp-httplib (Networking) +* zlib-ng & zstd (Compression) +* tl-expected (Error Handling) + +## 💡 Usage Examples +### 1. IPC (Manager & Node) +IACore provides a manager/node architecture using shared memory. + +Manager: + +```C++ +#include + +// Spawns a child process and connects via Shared Memory +auto nodeID = manager.SpawnNode("MyChildNodeExe"); +manager.WaitTillNodeIsOnline(*nodeID); + +// Send data with zero-copy overhead +String msg = "Hello Node"; +manager.SendPacket(*nodeID, 100, {(PCUINT8)msg.data(), msg.size()}); +``` + +### 2. Async Jobs +```C++ + +#include + +// Initialize worker threads (hardware_concurrency - 2) +IACore::AsyncOps::InitializeScheduler(); + +// Schedule a task +IACore::AsyncOps::Schedule *mySchedule = new IACore::AsyncOps::Schedule(); + +IACore::AsyncOps::ScheduleTask([](auto workerID) { + printf("Running on worker %d\n", workerID); +}, 0, mySchedule); + +// Wait for completion +IACore::AsyncOps::WaitForScheduleCompletion(mySchedule); +``` + +### 3. HTTP Request +```C++ +#include + +IACore::HttpClient client("[https://api.example.com](https://api.example.com)"); +auto res = client.JsonGet("/data", {}); + +if (res) { + std::cout << "Data: " << res->value << "\n"; +} else { + std::cerr << "Error: " << res.error() << "\n"; +} +``` + +## ⚖️ License + +This project is licensed under the GNU General Public License v3 (GPLv3). \ No newline at end of file diff --git a/Src/IACore/CMakeLists.txt b/Src/IACore/CMakeLists.txt index 18098a6..f67b46e 100644 --- a/Src/IACore/CMakeLists.txt +++ b/Src/IACore/CMakeLists.txt @@ -35,17 +35,7 @@ target_link_libraries(IACore PRIVATE OpenSSL::Crypto ) -#if(WIN32) -# target_link_libraries(IACore PUBLIC mimalloc-static) -# -# if(MSVC) -# target_link_options(IACore PUBLIC "/INCLUDE:mi_version") -# else() -# target_link_options(IACore PUBLIC "") -# endif() -#else() -# target_link_libraries(IACore PUBLIC mimalloc) -#endif() +target_link_libraries(IACore PUBLIC mimalloc-static) target_precompile_headers(IACore PUBLIC inc/IACore/PCH.hpp) diff --git a/Src/IACore/imp/cpp/IACore.cpp b/Src/IACore/imp/cpp/IACore.cpp index ce2e797..6db6dc6 100644 --- a/Src/IACore/imp/cpp/IACore.cpp +++ b/Src/IACore/imp/cpp/IACore.cpp @@ -17,8 +17,6 @@ #include #include -// #include - namespace IACore { HighResTimePoint g_startTime{}; diff --git a/Src/IACore/inc/IACore/IATest.hpp b/Src/IACore/inc/IACore/IATest.hpp index 896944f..107a598 100644 --- a/Src/IACore/inc/IACore/IATest.hpp +++ b/Src/IACore/inc/IACore/IATest.hpp @@ -1,16 +1,16 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects +// IACore-OSS; The Core Library for All IA Open Source Projects // Copyright (C) 2025 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 . @@ -20,63 +20,86 @@ #ifdef __cplusplus -#include +# include // ----------------------------------------------------------------------------- // Macros // ----------------------------------------------------------------------------- -#define valid_iatest_runner(type) iatest::_valid_iatest_runner::value_type +# define valid_iatest_runner(type) iatest::_valid_iatest_runner::value_type // Internal macro to handle the return logic -#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(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)) // Float specific checks (Game dev essential) -#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_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 ia::iatest::block // Concatenation fix for macros -#define IAT_BEGIN_BLOCK(_group, _name) class _group##_##_name : public ia::iatest::block { \ - public: PCCHAR name() CONST OVERRIDE { return #_group "::" #_name; } private: +# define IAT_BEGIN_BLOCK(_group, _name) \ + class _group##_##_name : public ia::iatest::block \ + { \ + public: \ + PCCHAR name() CONST OVERRIDE \ + { \ + return #_group "::" #_name; \ + } \ + \ + private: -#define IAT_END_BLOCK() }; +# define IAT_END_BLOCK() \ + } \ + ; -#define IAT_BEGIN_TEST_LIST() public: VOID declareTests() OVERRIDE { -#define IAT_ADD_TEST(name) IAT_UNIT(name) -#define IAT_END_TEST_LIST() } private: +# define IAT_BEGIN_TEST_LIST() \ + public: \ + VOID declareTests() OVERRIDE \ + { +# define IAT_ADD_TEST(name) IAT_UNIT(name) +# define IAT_END_TEST_LIST() \ + } \ + \ + private: namespace ia::iatest { // ------------------------------------------------------------------------- // Type Printing Helper (To show WHAT failed) // ------------------------------------------------------------------------- - template - std::string ToString(CONST T& value) { - if constexpr (std::is_arithmetic_v) { + template std::string ToString(CONST T &value) + { + if constexpr (std::is_arithmetic_v) + { return std::to_string(value); - } else if constexpr (std::is_same_v || std::is_same_v) { + } + else if constexpr (std::is_same_v || std::is_same_v) + { return std::string("\"") + value + "\""; - } else { + } + else + { return "{Object}"; // Fallback for complex types } } // Specialization for pointers - template - std::string ToString(T* value) { - if (value == NULLPTR) return "nullptr"; + template std::string ToString(T *value) + { + if (value == NULLPTR) + return "nullptr"; std::stringstream ss; - ss << "ptr(" << (void*)value << ")"; + ss << "ptr(" << (void *) value << ")"; return ss.str(); } @@ -86,26 +109,30 @@ namespace ia::iatest DEFINE_TYPE(functor_t, std::function); - struct unit_t { + struct unit_t + { std::string Name; - functor_t Functor; + functor_t Functor; }; class block { - public: + public: virtual ~block() = default; PURE_VIRTUAL(PCCHAR name() CONST); PURE_VIRTUAL(VOID declareTests()); - std::vector& units() { return m_units; } - - protected: - // Generic Equality - template - BOOL _test_eq(IN CONST T1& lhs, IN CONST T2& rhs, IN PCCHAR description) + std::vector &units() { - if(lhs != rhs) { + return m_units; + } + + protected: + // Generic Equality + template BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description) + { + if (lhs != rhs) + { print_fail(description, ToString(lhs), ToString(rhs)); return FALSE; } @@ -113,10 +140,10 @@ namespace ia::iatest } // Generic Inequality - template - BOOL _test_neq(IN CONST T1& lhs, IN CONST T2& rhs, IN PCCHAR description) + template BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description) { - if(lhs == rhs) { + if (lhs == rhs) + { print_fail(description, ToString(lhs), "NOT " + ToString(rhs)); return FALSE; } @@ -124,12 +151,12 @@ namespace ia::iatest } // Floating Point Approximation (Epsilon check) - template - BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description) + template BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description) { 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)) { // Default epsilon + if (diff > static_cast(0.0001)) + { // Default epsilon print_fail(description, ToString(lhs), ToString(rhs)); return FALSE; } @@ -138,7 +165,8 @@ namespace ia::iatest BOOL _test(IN BOOL value, IN PCCHAR description) { - if(!value) { + if (!value) + { printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description); return FALSE; } @@ -147,7 +175,8 @@ namespace ia::iatest BOOL _test_not(IN BOOL value, IN PCCHAR description) { - if(value) { + if (value) + { printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description); return FALSE; } @@ -159,8 +188,9 @@ namespace ia::iatest m_units.push_back({name, functor}); } - private: - VOID print_fail(PCCHAR desc, std::string v1, std::string v2) { + private: + VOID print_fail(PCCHAR desc, std::string v1, std::string v2) + { printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", desc); printf(__CC_RED " Expected: %s" __CC_DEFAULT "\n", v2.c_str()); printf(__CC_RED " Actual: %s" __CC_DEFAULT "\n", v1.c_str()); @@ -171,91 +201,158 @@ namespace ia::iatest template concept valid_block_class = std::derived_from; - + // ------------------------------------------------------------------------- // Runner // ------------------------------------------------------------------------- - template - class runner + template class runner { - public: - runner(){} - ~runner() { summarize(); } - + public: + runner() + { + } + + ~runner() + { + summarize(); + } + template - requires valid_block_class + requires valid_block_class VOID testBlock(); - - private: + + private: VOID summarize(); - - private: - SIZE_T m_testCount { 0 }; - SIZE_T m_failCount { 0 }; - SIZE_T m_blockCount { 0 }; + + private: + SIZE_T m_testCount{0}; + SIZE_T m_failCount{0}; + SIZE_T m_blockCount{0}; }; template template - requires valid_block_class + requires valid_block_class VOID runner::testBlock() { m_blockCount++; block_class b; b.declareTests(); - + printf(__CC_MAGENTA "Testing [%s]..." __CC_DEFAULT "\n", b.name()); - - for(auto& v: b.units()) + + for (auto &v : b.units()) { m_testCount++; - if constexpr(isVerbose) { + if constexpr (isVerbose) + { printf(__CC_YELLOW " Testing %s...\n" __CC_DEFAULT, v.Name.c_str()); } BOOL result = FALSE; - try { + try + { // Execute the test function result = v.Functor(); } - catch (const std::exception& e) { + catch (const std::exception &e) + { printf(__CC_RED " CRITICAL EXCEPTION in %s: %s\n" __CC_DEFAULT, v.Name.c_str(), e.what()); result = FALSE; } - catch (...) { + catch (...) + { printf(__CC_RED " UNKNOWN CRITICAL EXCEPTION in %s\n" __CC_DEFAULT, v.Name.c_str()); result = FALSE; } - if(!result) + if (!result) { m_failCount++; - if constexpr(stopOnFail) { summarize(); exit(-1); } + if constexpr (stopOnFail) + { + summarize(); + exit(-1); + } } } fputs("\n", stdout); } - - template - VOID runner::summarize() + + template VOID runner::summarize() { - printf(__CC_GREEN "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n"); - - if(!m_failCount) { + printf(__CC_GREEN + "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n"); + + if (!m_failCount) + { printf("\n\tALL TESTS PASSED!\n\n"); - } else { - FLOAT64 successRate = (100.0 * static_cast(m_testCount - m_failCount)/static_cast(m_testCount)); - printf(__CC_RED "%zu OUT OF %zu TESTS FAILED\n" __CC_YELLOW "Success Rate: %.2f%%\n", m_failCount, m_testCount, successRate); } - - printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN "-----------------------------------" __CC_DEFAULT "\n", m_testCount, m_blockCount); + else + { + FLOAT64 successRate = + (100.0 * static_cast(m_testCount - m_failCount) / static_cast(m_testCount)); + printf(__CC_RED "%zu OUT OF %zu TESTS FAILED\n" __CC_YELLOW "Success Rate: %.2f%%\n", m_failCount, + m_testCount, successRate); + } + + printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN + "-----------------------------------" __CC_DEFAULT "\n", + m_testCount, m_blockCount); } - template - struct _valid_iatest_runner : std::false_type {}; + template struct _valid_iatest_runner : std::false_type + { + }; + template - struct _valid_iatest_runner> : std::true_type {}; -} + struct _valid_iatest_runner> : std::true_type + { + }; + + // ------------------------------------------------------------------------- + // Global Test Registry + // ------------------------------------------------------------------------- + // Standard runner configuration for the single executable + using DefaultRunner = runner; + + class TestRegistry + { + public: + using TestEntry = std::function; + + static std::vector &GetEntries() + { + static std::vector entries; + return entries; + } + + static int RunAll() + { + DefaultRunner r; + auto &entries = GetEntries(); + printf(__CC_CYAN "[IATest] Discovered %zu Test Blocks\n\n" __CC_DEFAULT, entries.size()); + + for (auto &entry : entries) + { + entry(r); + } + // The destructor of 'r' will automatically print the summary + return 0; + } + }; + + template struct AutoRegister + { + AutoRegister() + { + TestRegistry::GetEntries().push_back([](DefaultRunner &r) { r.testBlock(); }); + } + }; +} // namespace ia::iatest + +// Usage: IAT_REGISTER_ENTRY(Core, Utils) +# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister _iat_reg_##Group##_##Name; #endif // __cplusplus \ No newline at end of file diff --git a/Tests/Unit/BinaryStreamReader.cpp b/Tests/Unit/BinaryStreamReader.cpp deleted file mode 100644 index 5840a79..0000000 --- a/Tests/Unit/BinaryStreamReader.cpp +++ /dev/null @@ -1,220 +0,0 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects -// Copyright (C) 2025 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 . - -#include - -#include - -using namespace IACore; - -// ----------------------------------------------------------------------------- -// Test Block Definition -// ----------------------------------------------------------------------------- - -IAT_BEGIN_BLOCK(Core, BinaryStreamReader) - - // ------------------------------------------------------------------------- - // 1. Basic Primitive Reading (UINT8) - // ------------------------------------------------------------------------- - BOOL TestReadUint8() - { - UINT8 data[] = { 0xAA, 0xBB, 0xCC }; - BinaryStreamReader reader(data); - - // Read First Byte - auto val1 = reader.Read(); - IAT_CHECK(val1.has_value()); - IAT_CHECK_EQ(*val1, 0xAA); - IAT_CHECK_EQ(reader.Cursor(), (SIZE_T)1); - - // Read Second Byte - auto val2 = reader.Read(); - IAT_CHECK_EQ(*val2, 0xBB); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 2. Multi-byte Reading (Endianness check) - // ------------------------------------------------------------------------- - BOOL TestReadMultiByte() - { - // 0x04030201 in Little Endian memory layout - UINT8 data[] = { 0x01, 0x02, 0x03, 0x04 }; - BinaryStreamReader reader(data); - - auto val = reader.Read(); - IAT_CHECK(val.has_value()); - - // Assuming standard x86/ARM Little Endian for this test - // If your engine supports Big Endian, you'd check architecture here - IAT_CHECK_EQ(*val, (UINT32)0x04030201); - - IAT_CHECK_EQ(reader.Cursor(), (SIZE_T)4); - IAT_CHECK(reader.IsEOF()); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 3. Floating Point (Approx check) - // ------------------------------------------------------------------------- - BOOL TestReadFloat() - { - FLOAT32 pi = 3.14159f; - // Bit-cast float to bytes for setup - UINT8 data[4]; - std::memcpy(data, &pi, 4); - - BinaryStreamReader reader(data); - auto val = reader.Read(); - - IAT_CHECK(val.has_value()); - IAT_CHECK_APPROX(*val, pi); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 4. String Reading - // ------------------------------------------------------------------------- - BOOL TestReadString() - { - const char* raw = "HelloIA"; - BinaryStreamReader reader(std::span((const UINT8*)raw, 7)); - - // Read "Hello" (5 bytes) - auto str = reader.ReadString(5); - IAT_CHECK(str.has_value()); - IAT_CHECK_EQ(*str, String("Hello")); - IAT_CHECK_EQ(reader.Cursor(), (SIZE_T)5); - - // Read remaining "IA" - auto str2 = reader.ReadString(2); - IAT_CHECK_EQ(*str2, String("IA")); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 5. Batch Buffer Reading - // ------------------------------------------------------------------------- - BOOL TestReadBuffer() - { - UINT8 src[] = { 1, 2, 3, 4, 5 }; - UINT8 dst[3] = { 0 }; - BinaryStreamReader reader(src); - - // Read 3 bytes into dst - auto res = reader.Read(dst, 3); - IAT_CHECK(res.has_value()); - - // Verify dst content - IAT_CHECK_EQ(dst[0], 1); - IAT_CHECK_EQ(dst[1], 2); - IAT_CHECK_EQ(dst[2], 3); - - // Verify cursor - IAT_CHECK_EQ(reader.Cursor(), (SIZE_T)3); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 6. Navigation (Seek, Skip, Remaining) - // ------------------------------------------------------------------------- - BOOL TestNavigation() - { - UINT8 data[10] = { 0 }; // Zero init - BinaryStreamReader reader(data); - - IAT_CHECK_EQ(reader.Remaining(), (SIZE_T)10); - - // Skip - reader.Skip(5); - IAT_CHECK_EQ(reader.Cursor(), (SIZE_T)5); - IAT_CHECK_EQ(reader.Remaining(), (SIZE_T)5); - - // Skip clamping - reader.Skip(100); // Should clamp to 10 - IAT_CHECK_EQ(reader.Cursor(), (SIZE_T)10); - IAT_CHECK(reader.IsEOF()); - - // Seek - reader.Seek(2); - IAT_CHECK_EQ(reader.Cursor(), (SIZE_T)2); - IAT_CHECK_EQ(reader.Remaining(), (SIZE_T)8); - IAT_CHECK_NOT(reader.IsEOF()); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 7. Error Handling (EOF Protection) - // ------------------------------------------------------------------------- - BOOL TestBoundaryChecks() - { - UINT8 data[] = { 0x00, 0x00 }; - BinaryStreamReader reader(data); - - // Valid read - UNUSED(reader.Read()); - IAT_CHECK(reader.IsEOF()); - - // Invalid Read Primitive - auto val = reader.Read(); - IAT_CHECK_NOT(val.has_value()); // Should be unexpected - - // Invalid Read String - auto str = reader.ReadString(1); - IAT_CHECK_NOT(str.has_value()); - - // Invalid Batch Read - UINT8 buf[1]; - auto batch = reader.Read(buf, 1); - IAT_CHECK_NOT(batch.has_value()); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // Registration - // ------------------------------------------------------------------------- - IAT_BEGIN_TEST_LIST() - IAT_ADD_TEST(TestReadUint8); - IAT_ADD_TEST(TestReadMultiByte); - IAT_ADD_TEST(TestReadFloat); - IAT_ADD_TEST(TestReadString); - IAT_ADD_TEST(TestReadBuffer); - IAT_ADD_TEST(TestNavigation); - IAT_ADD_TEST(TestBoundaryChecks); - IAT_END_TEST_LIST() - -IAT_END_BLOCK() - -int main(int argc, char* argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - // Define runner (StopOnFail=false, Verbose=true) - ia::iatest::runner testRunner; - - // Run the BinaryStreamReader block - testRunner.testBlock(); - - return 0; -} \ No newline at end of file diff --git a/Tests/Unit/BinaryStreamWriter.cpp b/Tests/Unit/BinaryStreamWriter.cpp deleted file mode 100644 index 11dd941..0000000 --- a/Tests/Unit/BinaryStreamWriter.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects -// Copyright (C) 2025 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 . - -#include - -#include - -using namespace IACore; - -// ----------------------------------------------------------------------------- -// Test Block Definition -// ----------------------------------------------------------------------------- - -IAT_BEGIN_BLOCK(Core, BinaryStreamWriter) - - // ------------------------------------------------------------------------- - // 1. Vector Mode (Dynamic Growth) - // ------------------------------------------------------------------------- - BOOL TestVectorGrowth() - { - std::vector buffer; - // Start empty - BinaryStreamWriter writer(buffer); - - // Write 1 Byte - writer.Write(0xAA); - IAT_CHECK_EQ(buffer.size(), (SIZE_T)1); - IAT_CHECK_EQ(buffer[0], 0xAA); - - // Write 4 Bytes (UINT32) - // 0xDDCCBBAA -> Little Endian: AA BB CC DD - writer.Write(0xDDCCBBAA); - - IAT_CHECK_EQ(buffer.size(), (SIZE_T)5); - - // Verify Memory Layout - IAT_CHECK_EQ(buffer[1], 0xAA); - IAT_CHECK_EQ(buffer[2], 0xBB); - IAT_CHECK_EQ(buffer[3], 0xCC); - IAT_CHECK_EQ(buffer[4], 0xDD); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 2. Vector Mode (Appending to Existing Data) - // ------------------------------------------------------------------------- - BOOL TestVectorAppend() - { - // Vector starts with existing data - std::vector buffer = { 0x01, 0x02 }; - BinaryStreamWriter writer(buffer); - - // Should append to end, not overwrite 0x01 - writer.Write(0x03); - - IAT_CHECK_EQ(buffer.size(), (SIZE_T)3); - IAT_CHECK_EQ(buffer[0], 0x01); - IAT_CHECK_EQ(buffer[2], 0x03); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 3. Fixed Mode (Span / No Allocation) - // ------------------------------------------------------------------------- - BOOL TestFixedBuffer() - { - UINT8 rawData[10]; - // Initialize with zeros - std::memset(rawData, 0, 10); - - BinaryStreamWriter writer(rawData); // Implicit conversion to span - - // Write UINT8 - writer.Write(0xFF); - IAT_CHECK_EQ(rawData[0], 0xFF); - - // Write UINT16 - writer.Write(0x2211); - IAT_CHECK_EQ(rawData[1], 0x11); - IAT_CHECK_EQ(rawData[2], 0x22); - - // Verify we haven't touched the rest - IAT_CHECK_EQ(rawData[3], 0x00); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 4. WriteBytes (Bulk Write) - // ------------------------------------------------------------------------- - BOOL TestWriteBytes() - { - std::vector buffer; - BinaryStreamWriter writer(buffer); - - const char* msg = "IA"; - writer.WriteBytes((PVOID)msg, 2); - writer.Write(0x00); // Null term - - IAT_CHECK_EQ(buffer.size(), (SIZE_T)3); - IAT_CHECK_EQ(buffer[0], 'I'); - IAT_CHECK_EQ(buffer[1], 'A'); - IAT_CHECK_EQ(buffer[2], 0x00); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 5. Random Access (WriteAt) - // ------------------------------------------------------------------------- - BOOL TestRandomAccess() - { - std::vector buffer; - BinaryStreamWriter writer(buffer); - - // Fill with placeholders - writer.Write(0xFFFFFFFF); - writer.Write(0xFFFFFFFF); - - // Overwrite the first integer with 0x00000000 - writer.WriteAt(0, 0); - - // Overwrite the byte at index 4 (start of second int) - writer.WriteAt(4, 0xAA); - - IAT_CHECK_EQ(buffer[0], 0x00); - IAT_CHECK_EQ(buffer[3], 0x00); - IAT_CHECK_EQ(buffer[4], 0xAA); // Modified - IAT_CHECK_EQ(buffer[5], 0xFF); // Unmodified - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 6. Floating Point Writing - // ------------------------------------------------------------------------- - BOOL TestWriteFloat() - { - std::vector buffer; - BinaryStreamWriter writer(buffer); - - FLOAT32 val = 1.234f; - writer.Write(val); - - IAT_CHECK_EQ(buffer.size(), (SIZE_T)4); - - // Read it back via memcpy to verify - FLOAT32 readBack; - std::memcpy(&readBack, buffer.data(), 4); - - IAT_CHECK_APPROX(readBack, val); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 7. GetPtrAt (Pointer Arithmetic Check) - // ------------------------------------------------------------------------- - BOOL TestGetPtr() - { - std::vector buffer = { 0x10, 0x20, 0x30 }; - BinaryStreamWriter writer(buffer); - - PUINT8 p1 = writer.GetPtrAt(1); - IAT_CHECK(p1 != nullptr); - IAT_CHECK_EQ(*p1, 0x20); - - // Bounds check (Valid boundary) - PUINT8 pLast = writer.GetPtrAt(2); - IAT_CHECK(pLast != nullptr); - - // Bounds check (Invalid) - // Note: We don't test Panic here, but we check if it returns valid ptrs - PUINT8 pInvalid = writer.GetPtrAt(3); - IAT_CHECK(pInvalid == nullptr); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // Registration - // ------------------------------------------------------------------------- - IAT_BEGIN_TEST_LIST() - IAT_ADD_TEST(TestVectorGrowth); - IAT_ADD_TEST(TestVectorAppend); - IAT_ADD_TEST(TestFixedBuffer); - IAT_ADD_TEST(TestWriteBytes); - IAT_ADD_TEST(TestRandomAccess); - IAT_ADD_TEST(TestWriteFloat); - IAT_ADD_TEST(TestGetPtr); - IAT_END_TEST_LIST() - -IAT_END_BLOCK() - -int main(int argc, char* argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - // Define runner (StopOnFail=false, Verbose=true) - ia::iatest::runner testRunner; - - // Run the BinaryReader block - testRunner.testBlock(); - - return 0; -} \ No newline at end of file diff --git a/Tests/Unit/CMakeLists.txt b/Tests/Unit/CMakeLists.txt index e53768e..c27036b 100644 --- a/Tests/Unit/CMakeLists.txt +++ b/Tests/Unit/CMakeLists.txt @@ -1,62 +1,37 @@ -set(TEST_NAME_PREFIX "IACore_Test_Unit_") - # ------------------------------------------------ -# C Compile Test +# C Compile Test (Keep separate to verify C ABI) # ------------------------------------------------ -add_executable(${TEST_NAME_PREFIX}CCompile "CCompile.c") - -set_target_properties(${TEST_NAME_PREFIX}CCompile PROPERTIES +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(${TEST_NAME_PREFIX}CCompile PRIVATE IACore) - -## ------------------------------------------------ -## Unit: BinaryReader -## ------------------------------------------------ -#add_executable(${TEST_NAME_PREFIX}BinaryReader "BinaryReader.cpp") -#target_link_libraries(${TEST_NAME_PREFIX}BinaryReader PRIVATE IACore) -#target_compile_options(${TEST_NAME_PREFIX}BinaryReader PRIVATE -fexceptions) -#set_target_properties(${TEST_NAME_PREFIX}BinaryReader PROPERTIES USE_EXCEPTIONS ON) -# -## ------------------------------------------------ -## Unit: BinaryWriter -## ------------------------------------------------ -#add_executable(${TEST_NAME_PREFIX}BinaryWriter "BinaryWriter.cpp") -#target_link_libraries(${TEST_NAME_PREFIX}BinaryWriter PRIVATE IACore) -#target_compile_options(${TEST_NAME_PREFIX}BinaryWriter PRIVATE -fexceptions) -#set_target_properties(${TEST_NAME_PREFIX}BinaryWriter PROPERTIES USE_EXCEPTIONS ON) +target_link_libraries(IACore_Test_C_ABI PRIVATE IACore) # ------------------------------------------------ -# Unit: Environment +# The Unified C++ Test Suite # ------------------------------------------------ -add_executable(${TEST_NAME_PREFIX}Environment "Environment.cpp") -target_link_libraries(${TEST_NAME_PREFIX}Environment PRIVATE IACore) -target_compile_options(${TEST_NAME_PREFIX}Environment PRIVATE -fexceptions) -set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON) +set(TEST_SOURCES + Main.cpp + Utils.cpp + Environment.cpp + ProcessOps.cpp + StreamReader.cpp + RingBuffer.cpp +) -# ------------------------------------------------ -# Unit: FileOps -# ------------------------------------------------ -#add_executable(${TEST_NAME_PREFIX}FileOps "FileOps.cpp") -#target_link_libraries(${TEST_NAME_PREFIX}FileOps PRIVATE IACore) -#target_compile_options(${TEST_NAME_PREFIX}FileOps PRIVATE -fexceptions) -#set_target_properties(${TEST_NAME_PREFIX}FileOps PROPERTIES USE_EXCEPTIONS ON) +add_executable(IACore_Test_Suite ${TEST_SOURCES}) -# ------------------------------------------------ -# Unit: ProcessOps -# ------------------------------------------------ -add_executable(${TEST_NAME_PREFIX}ProcessOps "ProcessOps.cpp") -target_link_libraries(${TEST_NAME_PREFIX}ProcessOps PRIVATE IACore) -target_compile_options(${TEST_NAME_PREFIX}ProcessOps PRIVATE -fexceptions) -set_target_properties(${TEST_NAME_PREFIX}ProcessOps PROPERTIES USE_EXCEPTIONS ON) +# Enable exceptions for testing framework (even if Core is no-except) +target_compile_options(IACore_Test_Suite PRIVATE -fexceptions) +set_target_properties(IACore_Test_Suite PROPERTIES USE_EXCEPTIONS ON) -# ------------------------------------------------ -# Unit: Utils -# ------------------------------------------------ -add_executable(${TEST_NAME_PREFIX}Utils "Utils.cpp") -target_link_libraries(${TEST_NAME_PREFIX}Utils PRIVATE IACore) -target_compile_options(${TEST_NAME_PREFIX}Utils PRIVATE -fexceptions) -set_target_properties(${TEST_NAME_PREFIX}Utils PROPERTIES USE_EXCEPTIONS ON) +target_link_libraries(IACore_Test_Suite PRIVATE IACore) + +# Copy necessary runtime assets if you have them (like the LongProcess test subject) +add_custom_command(TARGET IACore_Test_Suite POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $/LongProcess${CMAKE_EXECUTABLE_SUFFIX} +) \ No newline at end of file diff --git a/Tests/Unit/Environment.cpp b/Tests/Unit/Environment.cpp index cd203ab..e9be987 100644 --- a/Tests/Unit/Environment.cpp +++ b/Tests/Unit/Environment.cpp @@ -1,16 +1,16 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects +// IACore-OSS; The Core Library for All IA Open Source Projects // Copyright (C) 2025 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 . @@ -25,8 +25,8 @@ using namespace IACore; // ----------------------------------------------------------------------------- // We use a unique prefix to ensure we don't accidentally mess with real // system variables like PATH or HOME. -static const char* TEST_KEY = "IA_TEST_ENV_VAR_12345"; -static const char* TEST_VAL = "Hello World"; +static const char *TEST_KEY = "IA_TEST_ENV_VAR_12345"; +static const char *TEST_VAL = "Hello World"; // ----------------------------------------------------------------------------- // Test Block Definition @@ -34,157 +34,145 @@ static const char* TEST_VAL = "Hello World"; 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)); +// ------------------------------------------------------------------------- +// 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)); - // 2. Set - BOOL setRes = Environment::Set(TEST_KEY, TEST_VAL); - IAT_CHECK(setRes); - IAT_CHECK(Environment::Exists(TEST_KEY)); + // 2. Set + BOOL setRes = Environment::Set(TEST_KEY, TEST_VAL); + IAT_CHECK(setRes); + 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) + 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) + String val = Environment::Get(TEST_KEY); + IAT_CHECK_EQ(val, String(TEST_VAL)); - // Cleanup - Environment::Unset(TEST_KEY); - return TRUE; - } + // Cleanup + 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")); +// ------------------------------------------------------------------------- +// 2. Overwriting Values +// ------------------------------------------------------------------------- +BOOL TestOverwrite() +{ + 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 + Environment::Set(TEST_KEY, "ValueB"); + IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueB")); - Environment::Unset(TEST_KEY); - return TRUE; - } + Environment::Unset(TEST_KEY); + return TRUE; +} - // ------------------------------------------------------------------------- - // 3. Unset Logic - // ------------------------------------------------------------------------- - BOOL TestUnset() - { - Environment::Set(TEST_KEY, "To Be Deleted"); - IAT_CHECK(Environment::Exists(TEST_KEY)); +// ------------------------------------------------------------------------- +// 3. Unset Logic +// ------------------------------------------------------------------------- +BOOL TestUnset() +{ + Environment::Set(TEST_KEY, "To Be Deleted"); + IAT_CHECK(Environment::Exists(TEST_KEY)); - BOOL unsetRes = Environment::Unset(TEST_KEY); - IAT_CHECK(unsetRes); + BOOL unsetRes = Environment::Unset(TEST_KEY); + IAT_CHECK(unsetRes); - // 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()); + // Verify it is actually gone + IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); - return TRUE; - } + // Find should return nullopt + auto opt = Environment::Find(TEST_KEY); + IAT_CHECK_NOT(opt.has_value()); - // ------------------------------------------------------------------------- - // 4. Default Value Fallbacks - // ------------------------------------------------------------------------- - BOOL TestDefaults() - { - const char* ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST"; - - // Ensure it really doesn't exist - Environment::Unset(ghostKey); + return TRUE; +} - // Test Get with implicit default ("") - String empty = Environment::Get(ghostKey); - IAT_CHECK(empty.empty()); +// ------------------------------------------------------------------------- +// 4. Default Value Fallbacks +// ------------------------------------------------------------------------- +BOOL TestDefaults() +{ + const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST"; - // Test Get with explicit default - String fallback = Environment::Get(ghostKey, "MyDefault"); - IAT_CHECK_EQ(fallback, String("MyDefault")); + // Ensure it really doesn't exist + Environment::Unset(ghostKey); - return TRUE; - } + // Test Get with implicit default ("") + String empty = Environment::Get(ghostKey); + IAT_CHECK(empty.empty()); - // ------------------------------------------------------------------------- - // 5. Empty Strings vs Null/Unset - // ------------------------------------------------------------------------- - // This is a critical edge case. - // 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, ""); + // Test Get with explicit default + String fallback = Environment::Get(ghostKey, "MyDefault"); + IAT_CHECK_EQ(fallback, String("MyDefault")); - // It should exist - IAT_CHECK(Environment::Exists(TEST_KEY)); + return TRUE; +} - // It should be retrievable as empty string - auto opt = Environment::Find(TEST_KEY); - IAT_CHECK(opt.has_value()); - IAT_CHECK(opt->empty()); +// ------------------------------------------------------------------------- +// 5. Empty Strings vs Null/Unset +// ------------------------------------------------------------------------- +// This is a critical edge case. +// 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, ""); - // Cleanup - Environment::Unset(TEST_KEY); - IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); + // It should exist + IAT_CHECK(Environment::Exists(TEST_KEY)); - return TRUE; - } + // It should be retrievable as empty string + auto opt = Environment::Find(TEST_KEY); + IAT_CHECK(opt.has_value()); + IAT_CHECK(opt->empty()); - // ------------------------------------------------------------------------- - // 6. Validation / Bad Input - // ------------------------------------------------------------------------- - BOOL TestBadInput() - { - // Setting an empty key should fail gracefully - BOOL res = Environment::Set("", "Value"); - IAT_CHECK_NOT(res); + // Cleanup + Environment::Unset(TEST_KEY); + IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); - // Unsetting an empty key should fail - BOOL resUnset = Environment::Unset(""); - IAT_CHECK_NOT(resUnset); + 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); - // ------------------------------------------------------------------------- - // 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_END_TEST_LIST() + // Unsetting an empty key should fail + BOOL resUnset = Environment::Unset(""); + IAT_CHECK_NOT(resUnset); + + 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_END_TEST_LIST() IAT_END_BLOCK() -int main(int argc, char* argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - // Define runner (StopOnFail=false, Verbose=true) - ia::iatest::runner testRunner; - - // Run the BinaryReader block - testRunner.testBlock(); - - return 0; -} \ No newline at end of file +IAT_REGISTER_ENTRY(Core, Environment) diff --git a/Tests/Unit/FileOps.cpp b/Tests/Unit/FileOps.cpp deleted file mode 100644 index 070e034..0000000 --- a/Tests/Unit/FileOps.cpp +++ /dev/null @@ -1,246 +0,0 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects -// Copyright (C) 2025 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 . - -#include - -#include - -using namespace IACore; - -// ----------------------------------------------------------------------------- -// Test Block Definition -// ----------------------------------------------------------------------------- - -IAT_BEGIN_BLOCK(Core, File) - - // Helper to generate a temp path for testing - String GetTempPath(const String& filename) { - auto path = std::filesystem::temp_directory_path() / filename; - return path.string(); - } - - // Helper to write raw data for setup (bypassing the class we are testing) - void CreateDummyFile(const String& path, const String& content) { - std::ofstream out(path); - out << content; - out.close(); - } - - // ------------------------------------------------------------------------- - // 1. Path Utilities (Pure Logic) - // ------------------------------------------------------------------------- - BOOL TestPathUtils() - { - // We use forward slashes as std::filesystem handles them cross-platform - String complexPath = "assets/textures/hero_diffuse.png"; - - // Test ExtractDirectory - String dir = File::ExtractDirectory(complexPath); - // Note: On Windows this might return "assets\textures", check specific impl if strict - // But std::filesystem usually normalizes based on the input string format. - IAT_CHECK(dir.find("textures") != String::npos); - - // Test ExtractFilename (With Extension) - String nameWithExt = File::ExtractFilename(complexPath); - IAT_CHECK_EQ(nameWithExt, String("hero_diffuse.png")); - - // Test ExtractFilename (No Extension / Stem) - String nameNoExt = File::ExtractFilename(complexPath); - IAT_CHECK_EQ(nameNoExt, String("hero_diffuse")); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // 2. Static Read String - // ------------------------------------------------------------------------- - BOOL TestStaticReadString() - { - String path = GetTempPath("ia_test_text.txt"); - String content = "Hello IA Engine"; - - // Arrange - CreateDummyFile(path, content); - - // Act - auto result = File::ReadToString(path); - - // Assert - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, content); - - // Cleanup - std::filesystem::remove(path); - return TRUE; - } - - // ------------------------------------------------------------------------- - // 3. Static Read Binary (Vector) - // ------------------------------------------------------------------------- - BOOL TestStaticReadVector() - { - String path = GetTempPath("ia_test_bin.dat"); - - // Arrange: Create a binary file manually - std::ofstream out(path, std::ios::binary); - UINT8 rawBytes[] = { 0xDE, 0xAD, 0xBE, 0xEF }; - out.write((char*)rawBytes, 4); - out.close(); - - // Act - auto result = File::ReadToVector(path); - - // Assert - IAT_CHECK(result.has_value()); - Vector& vec = *result; - - IAT_CHECK_EQ(vec.size(), (SIZE_T)4); - IAT_CHECK_EQ(vec[0], 0xDE); - IAT_CHECK_EQ(vec[3], 0xEF); - - // Cleanup - std::filesystem::remove(path); - return TRUE; - } - - // ------------------------------------------------------------------------- - // 4. Instance Write & Read Loop - // ------------------------------------------------------------------------- - BOOL TestInstanceWriteRead() - { - String path = GetTempPath("ia_instance_io.bin"); - UINT32 flagsWrite = (UINT32)File::EOpenFlags::Write | - (UINT32)File::EOpenFlags::Binary | - (UINT32)File::EOpenFlags::Trunc; - - // 1. Write - { - File f; - auto res = f.Open(path, (File::EOpenFlags)flagsWrite); - IAT_CHECK(res.has_value()); - IAT_CHECK(f.IsOpen()); - - UINT32 magic = 12345; - f.Write(&magic, sizeof(magic)); - - f.Close(); - IAT_CHECK_NOT(f.IsOpen()); - } - - // 2. Read Back - { - UINT32 flagsRead = (UINT32)File::EOpenFlags::Read | - (UINT32)File::EOpenFlags::Binary; - - File f(path, (File::EOpenFlags)flagsRead); // Test RAII constructor - IAT_CHECK(f.IsOpen()); - - UINT32 magicRead = 0; - SIZE_T bytesRead = f.Read(&magicRead, sizeof(magicRead)); - - IAT_CHECK_EQ(bytesRead, sizeof(UINT32)); - IAT_CHECK_EQ(magicRead, (UINT32)12345); - } - - std::filesystem::remove(path); - return TRUE; - } - - // ------------------------------------------------------------------------- - // 5. Append Mode - // ------------------------------------------------------------------------- - BOOL TestAppendMode() - { - String path = GetTempPath("ia_append.txt"); - UINT32 flagsAppend = (UINT32)File::EOpenFlags::Write | (UINT32)File::EOpenFlags::Append; - - // Create initial file - CreateDummyFile(path, "A"); - - // Open in append mode - File f; - const auto openResult = f.Open(path, (File::EOpenFlags)flagsAppend); - if(!openResult) - { - IA_PANIC(openResult.error().c_str()) - return FALSE; - } - char c = 'B'; - f.Write(&c, 1); - f.Close(); - - // Verify content is "AB" - auto content = File::ReadToString(path); - IAT_CHECK(content.has_value()); - IAT_CHECK_EQ(*content, String("AB")); - - std::filesystem::remove(path); - return TRUE; - } - - // ------------------------------------------------------------------------- - // 6. Error Handling - // ------------------------------------------------------------------------- - BOOL TestNonExistentFile() - { - String ghostPath = GetTempPath("this_does_not_exist.ghost"); - - // Ensure it really doesn't exist - if(File::Exists(ghostPath)) std::filesystem::remove(ghostPath); - - // Test Static - auto resStatic = File::ReadToString(ghostPath); - IAT_CHECK_NOT(resStatic.has_value()); - // Optional: Check error message content - // IAT_CHECK(resStatic.error().find("not found") != String::npos); - - // Test Instance - File f; - auto resInstance = f.Open(ghostPath, File::EOpenFlags::Read); - IAT_CHECK_NOT(resInstance.has_value()); - IAT_CHECK_NOT(f.IsOpen()); - - return TRUE; - } - - // ------------------------------------------------------------------------- - // Registration - // ------------------------------------------------------------------------- - IAT_BEGIN_TEST_LIST() - IAT_ADD_TEST(TestPathUtils); - IAT_ADD_TEST(TestStaticReadString); - IAT_ADD_TEST(TestStaticReadVector); - IAT_ADD_TEST(TestInstanceWriteRead); - IAT_ADD_TEST(TestAppendMode); - IAT_ADD_TEST(TestNonExistentFile); - IAT_END_TEST_LIST() - -IAT_END_BLOCK() - - -int main(int argc, char* argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - // Define runner (StopOnFail=false, Verbose=true) - ia::iatest::runner testRunner; - - // Run the BinaryReader block - testRunner.testBlock(); - - return 0; -} \ No newline at end of file diff --git a/Tests/Unit/Main.cpp b/Tests/Unit/Main.cpp new file mode 100644 index 0000000..26abe97 --- /dev/null +++ b/Tests/Unit/Main.cpp @@ -0,0 +1,36 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 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 . + +#include +#include + +int main(int argc, char *argv[]) +{ + UNUSED(argc); + UNUSED(argv); + + printf(__CC_GREEN "\n===============================================================\n"); + printf(" IACore (Independent Architecture Core) - Unit Test Suite\n"); + printf("===============================================================\n" __CC_DEFAULT "\n"); + + IACore::Initialize(); + + int result = ia::iatest::TestRegistry::RunAll(); + + IACore::Terminate(); + + return result; +} \ No newline at end of file diff --git a/Tests/Unit/ProcessOps.cpp b/Tests/Unit/ProcessOps.cpp index 563e64a..959fbce 100644 --- a/Tests/Unit/ProcessOps.cpp +++ b/Tests/Unit/ProcessOps.cpp @@ -1,16 +1,16 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects +// IACore-OSS; The Core Library for All IA Open Source Projects // Copyright (C) 2025 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 . @@ -24,224 +24,206 @@ using namespace IACore; // Platform Abstraction for Test Commands // ----------------------------------------------------------------------------- #if IA_PLATFORM_WINDOWS - #define CMD_ECHO_EXE "cmd.exe" - // Windows requires /c to run a command string - #define CMD_ARG_PREFIX "/c" - #define NULL_DEVICE "NUL" +# define CMD_ECHO_EXE "cmd.exe" +// Windows requires /c to run a command string +# define CMD_ARG_PREFIX "/c" +# 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, Process) +IAT_BEGIN_BLOCK(Core, ProcessOps) - // ------------------------------------------------------------------------- - // 1. Basic Execution (Exit Code 0) - // ------------------------------------------------------------------------- - BOOL TestBasicRun() - { - // Simple "echo hello" - String captured; - - auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA", - [&](StringView line) { - captured = line; - } - ); +// ------------------------------------------------------------------------- +// 1. Basic Execution (Exit Code 0) +// ------------------------------------------------------------------------- +BOOL TestBasicRun() +{ + // Simple "echo hello" + String captured; - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 0); // Exit code 0 - - // Note: Echo might add newline, but your LineBuffer trims/handles it. - // We check if "HelloIA" is contained or equal. - IAT_CHECK(captured.find("HelloIA") != String::npos); + auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA", + [&](StringView line) { captured = line; }); - return TRUE; - } + IAT_CHECK(result.has_value()); + IAT_CHECK_EQ(*result, 0); // Exit code 0 - // ------------------------------------------------------------------------- - // 2. Argument Parsing - // ------------------------------------------------------------------------- - BOOL TestArguments() - { - Vector 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 + // Note: Echo might add newline, but your LineBuffer trims/handles it. + // We check if "HelloIA" is contained or equal. + IAT_CHECK(captured.find("HelloIA") != String::npos); - auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args, - [&](StringView line) { - lines.push_back(String(line)); - } - ); + return TRUE; +} - IAT_CHECK_EQ(*result, 0); - IAT_CHECK(lines.size() > 0); - - // Output should contain "one two" - IAT_CHECK(lines[0].find("one two") != String::npos); +// ------------------------------------------------------------------------- +// 2. Argument Parsing +// ------------------------------------------------------------------------- +BOOL TestArguments() +{ + Vector lines; - return TRUE; - } + // 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 - // ------------------------------------------------------------------------- - // 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" - - String cmd, arg; - - #if IA_PLATFORM_WINDOWS - cmd = "cmd.exe"; - arg = "/c exit 42"; - #else - cmd = "/bin/sh"; - arg = "-c \"exit 42\""; // quotes needed for sh -c - #endif + auto result = + ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args, [&](StringView line) { lines.push_back(String(line)); }); - auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView){}); + IAT_CHECK_EQ(*result, 0); + IAT_CHECK(lines.size() > 0); - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 42); + // Output should contain "one two" + IAT_CHECK(lines[0].find("one two") != String::npos); - return TRUE; - } + return TRUE; +} - // ------------------------------------------------------------------------- - // 4. Missing Executable Handling - // ------------------------------------------------------------------------- - BOOL TestMissingExe() - { - // Try to run a random string - auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView){}); +// ------------------------------------------------------------------------- +// 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" - // 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 - #else - // Linux fork succeeds, but execvp fails, returning 127 - IAT_CHECK(result.has_value()); - IAT_CHECK_EQ(*result, 127); - #endif + String cmd, arg; - return TRUE; - } +#if IA_PLATFORM_WINDOWS + cmd = "cmd.exe"; + arg = "/c exit 42"; +#else + cmd = "/bin/sh"; + arg = "-c \"exit 42\""; // quotes needed for sh -c +#endif - // ------------------------------------------------------------------------- - // 5. Line Buffer Logic (The 4096 split test) - // ------------------------------------------------------------------------- - BOOL TestLargeOutput() - { - // We need to generate output larger than the internal 4096 buffer - // to ensure the "partial line" logic works when a line crosses a buffer boundary. - - // We will construct a python script or shell command to print a massive line. - // Cross platform approach: Use Python if available, or just a long echo. - // Let's assume 'python3' or 'python' is in path, otherwise skip? - // Safer: Use pure shell loop if possible, or just a massive command line arg. - - String massiveString; - massiveString.reserve(5000); - for(int i=0; i<500; ++i) massiveString += "1234567890"; // 5000 chars + auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView) {}); - String cmd, arg; - - #if IA_PLATFORM_WINDOWS - cmd = "cmd.exe"; - // Windows has command line length limits (~8k), 5k is safe. - arg = "/c echo " + massiveString; - #else - cmd = "/bin/echo"; - arg = massiveString; - #endif + IAT_CHECK(result.has_value()); + IAT_CHECK_EQ(*result, 42); - String captured; - auto result = ProcessOps::SpawnProcessSync(cmd, arg, - [&](StringView line) { - captured += line; - } - ); + return TRUE; +} - 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()); +// ------------------------------------------------------------------------- +// 4. Missing Executable Handling +// ------------------------------------------------------------------------- +BOOL TestMissingExe() +{ + // Try to run a random string + auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {}); - return TRUE; - } + // Windows: CreateProcess usually fails -> returns unexpected + // Linux: execvp fails inside child, returns 127 via waitpid - // ------------------------------------------------------------------------- - // 6. Multi-Line Handling - // ------------------------------------------------------------------------- - BOOL TestMultiLine() - { - // Windows: cmd /c "echo A && echo B" - // Linux: /bin/sh -c "echo A; echo B" - - String cmd, arg; - #if IA_PLATFORM_WINDOWS - cmd = "cmd.exe"; - arg = "/c \"echo LineA && echo LineB\""; - #else - cmd = "/bin/sh"; - arg = "-c \"echo LineA; echo LineB\""; - #endif +#if IA_PLATFORM_WINDOWS + 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); +#endif - int lineCount = 0; - bool foundA = false; - bool foundB = false; + return TRUE; +} - UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) { - lineCount++; - if (line.find("LineA") != String::npos) foundA = true; - if (line.find("LineB") != String::npos) foundB = true; - })); +// ------------------------------------------------------------------------- +// 5. Line Buffer Logic (The 4096 split test) +// ------------------------------------------------------------------------- +BOOL TestLargeOutput() +{ + // We need to generate output larger than the internal 4096 buffer + // to ensure the "partial line" logic works when a line crosses a buffer boundary. - 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); + // We will construct a python script or shell command to print a massive line. + // Cross platform approach: Use Python if available, or just a long echo. + // Let's assume 'python3' or 'python' is in path, otherwise skip? + // Safer: Use pure shell loop if possible, or just a massive command line arg. - return TRUE; - } + String massiveString; + massiveString.reserve(5000); + for (int i = 0; i < 500; ++i) + massiveString += "1234567890"; // 5000 chars - // ------------------------------------------------------------------------- - // 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_END_TEST_LIST() + String cmd, arg; + +#if IA_PLATFORM_WINDOWS + cmd = "cmd.exe"; + // Windows has command line length limits (~8k), 5k is safe. + arg = "/c echo " + massiveString; +#else + cmd = "/bin/echo"; + arg = massiveString; +#endif + + String captured; + auto result = ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) { captured += line; }); + + 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()); + + return TRUE; +} + +// ------------------------------------------------------------------------- +// 6. Multi-Line Handling +// ------------------------------------------------------------------------- +BOOL TestMultiLine() +{ + // Windows: cmd /c "echo A && echo B" + // Linux: /bin/sh -c "echo A; echo B" + + String cmd, arg; +#if IA_PLATFORM_WINDOWS + cmd = "cmd.exe"; + arg = "/c \"echo LineA && echo LineB\""; +#else + cmd = "/bin/sh"; + arg = "-c \"echo LineA; echo LineB\""; +#endif + + int lineCount = 0; + bool foundA = false; + bool foundB = 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; + })); + + 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); + + 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_END_TEST_LIST() IAT_END_BLOCK() -int main(int argc, char* argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - // Define runner (StopOnFail=false, Verbose=true) - ia::iatest::runner testRunner; - - // Run the BinaryReader block - testRunner.testBlock(); - - return 0; -} \ No newline at end of file +IAT_REGISTER_ENTRY(Core, ProcessOps) diff --git a/Tests/Unit/RingBuffer.cpp b/Tests/Unit/RingBuffer.cpp new file mode 100644 index 0000000..989f33e --- /dev/null +++ b/Tests/Unit/RingBuffer.cpp @@ -0,0 +1,104 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 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 . + +#include +#include + +using namespace IACore; + +IAT_BEGIN_BLOCK(Core, RingBuffer) + +// 1. Basic Push Pop +BOOL TestPushPop() +{ + // Allocate raw memory for the ring buffer + // ControlBlock (128 bytes) + Data + std::vector memory(sizeof(RingBufferView::ControlBlock) + 1024); + + // Initialize as OWNER (Producer) + RingBufferView producer(std::span(memory), TRUE); + + // Initialize as CONSUMER (Pointer to same memory) + RingBufferView consumer(std::span(memory), FALSE); + + // Data to send + String msg = "Hello RingBuffer"; + BOOL pushed = producer.Push(1, {(const UINT8 *) msg.data(), msg.size()}); + IAT_CHECK(pushed); + + // Read back + RingBufferView::PacketHeader header; + UINT8 readBuf[128]; + + INT32 bytesRead = consumer.Pop(header, std::span(readBuf, 128)); + + IAT_CHECK_EQ(header.ID, (UINT16) 1); + IAT_CHECK_EQ(bytesRead, (INT32) msg.size()); + + String readMsg((char *) readBuf, bytesRead); + IAT_CHECK_EQ(readMsg, msg); + + return TRUE; +} + +// 2. Wrap Around (The hardest logic to get right) +BOOL TestWrapAround() +{ + // Small buffer to force wrapping quickly + // Capacity will be 100 bytes + std::vector memory(sizeof(RingBufferView::ControlBlock) + 100); + RingBufferView rb(std::span(memory), TRUE); + + // Fill buffer to near end + // Push 80 bytes + std::vector junk(80, 0xFF); + rb.Push(1, junk); + + // Pop them to advance READ cursor + RingBufferView::PacketHeader header; + UINT8 outBuf[100]; + rb.Pop(header, outBuf); + + // 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); + + // Pop and verify integrity + INT32 popSize = rb.Pop(header, outBuf); + IAT_CHECK_EQ(popSize, 40); + + // Check if data is intact + BOOL match = TRUE; + for (int i = 0; i < 40; i++) + { + if (outBuf[i] != 0xAA) + match = FALSE; + } + IAT_CHECK(match); + + return TRUE; +} + +IAT_BEGIN_TEST_LIST() +IAT_ADD_TEST(TestPushPop); +IAT_ADD_TEST(TestWrapAround); +IAT_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, RingBuffer) diff --git a/Tests/Unit/StreamReader.cpp b/Tests/Unit/StreamReader.cpp new file mode 100644 index 0000000..bcdb5df --- /dev/null +++ b/Tests/Unit/StreamReader.cpp @@ -0,0 +1,182 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 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 . + +#include + +#include + +using namespace IACore; + +// ----------------------------------------------------------------------------- +// Test Block Definition +// ----------------------------------------------------------------------------- + +IAT_BEGIN_BLOCK(Core, StreamReader) + +// ------------------------------------------------------------------------- +// 1. Basic Primitive Reading (UINT8) +// ------------------------------------------------------------------------- +BOOL TestReadUint8() +{ + UINT8 data[] = {0xAA, 0xBB, 0xCC}; + StreamReader reader(data); + + // Read First Byte + auto val1 = reader.Read(); + IAT_CHECK(val1.has_value()); + IAT_CHECK_EQ(*val1, 0xAA); + IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 1); + + // Read Second Byte + auto val2 = reader.Read(); + IAT_CHECK_EQ(*val2, 0xBB); + + return TRUE; +} + +// ------------------------------------------------------------------------- +// 2. Multi-byte Reading (Endianness check) +// ------------------------------------------------------------------------- +BOOL TestReadMultiByte() +{ + // 0x04030201 in Little Endian memory layout + UINT8 data[] = {0x01, 0x02, 0x03, 0x04}; + StreamReader reader(data); + + auto val = reader.Read(); + IAT_CHECK(val.has_value()); + + // Assuming standard x86/ARM Little Endian for this test + // If your engine supports Big Endian, you'd check architecture here + IAT_CHECK_EQ(*val, (UINT32) 0x04030201); + + IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 4); + IAT_CHECK(reader.IsEOF()); + + return TRUE; +} + +// ------------------------------------------------------------------------- +// 3. Floating Point (Approx check) +// ------------------------------------------------------------------------- +BOOL TestReadFloat() +{ + FLOAT32 pi = 3.14159f; + // Bit-cast float to bytes for setup + UINT8 data[4]; + std::memcpy(data, &pi, 4); + + StreamReader reader(data); + auto val = reader.Read(); + + IAT_CHECK(val.has_value()); + IAT_CHECK_APPROX(*val, pi); + + return TRUE; +} + +// ------------------------------------------------------------------------- +// 4. Batch Buffer Reading +// ------------------------------------------------------------------------- +BOOL TestReadBuffer() +{ + UINT8 src[] = {1, 2, 3, 4, 5}; + UINT8 dst[3] = {0}; + StreamReader reader(src); + + // Read 3 bytes into dst + auto res = reader.Read(dst, 3); + IAT_CHECK(res.has_value()); + + // Verify dst content + IAT_CHECK_EQ(dst[0], 1); + IAT_CHECK_EQ(dst[1], 2); + IAT_CHECK_EQ(dst[2], 3); + + // Verify cursor + IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 3); + + return TRUE; +} + +// ------------------------------------------------------------------------- +// 5. Navigation (Seek, Skip, Remaining) +// ------------------------------------------------------------------------- +BOOL TestNavigation() +{ + UINT8 data[10] = {0}; // Zero init + StreamReader reader(data); + + IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 10); + + // Skip + reader.Skip(5); + IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 5); + IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 5); + + // Skip clamping + reader.Skip(100); // Should clamp to 10 + IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 10); + IAT_CHECK(reader.IsEOF()); + + // Seek + reader.Seek(2); + IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 2); + IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 8); + IAT_CHECK_NOT(reader.IsEOF()); + + return TRUE; +} + +// ------------------------------------------------------------------------- +// 6. Error Handling (EOF Protection) +// ------------------------------------------------------------------------- +BOOL TestBoundaryChecks() +{ + UINT8 data[] = {0x00, 0x00}; + StreamReader reader(data); + + // Valid read + UNUSED(reader.Read()); + IAT_CHECK(reader.IsEOF()); + + // Invalid Read Primitive + auto val = reader.Read(); + IAT_CHECK_NOT(val.has_value()); // Should be unexpected + + // Invalid Batch Read + UINT8 buf[1]; + auto batch = reader.Read(buf, 1); + IAT_CHECK_NOT(batch.has_value()); + + 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_END_TEST_LIST() + +IAT_END_BLOCK() + +IAT_REGISTER_ENTRY(Core, StreamReader) diff --git a/Tests/Unit/Utils.cpp b/Tests/Unit/Utils.cpp index 7230320..d2c2930 100644 --- a/Tests/Unit/Utils.cpp +++ b/Tests/Unit/Utils.cpp @@ -1,16 +1,16 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects +// IACore-OSS; The Core Library for All IA Open Source Projects // Copyright (C) 2025 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 . @@ -24,12 +24,14 @@ using namespace IACore; // Test Structs for Hashing (Must be defined at Global Scope) // ----------------------------------------------------------------------------- -struct TestVec3 { +struct TestVec3 +{ FLOAT32 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 { + bool operator==(const TestVec3 &other) const + { return x == other.x && y == other.y && z == other.z; } }; @@ -38,203 +40,190 @@ struct TestVec3 { // This proves the macro works structurally IA_MAKE_HASHABLE(TestVec3, &TestVec3::x, &TestVec3::y, &TestVec3::z); - // ----------------------------------------------------------------------------- // Test Block Definition // ----------------------------------------------------------------------------- IAT_BEGIN_BLOCK(Core, Utils) - // ------------------------------------------------------------------------- - // 1. Binary <-> Hex String Conversion - // ------------------------------------------------------------------------- - BOOL TestHexConversion() - { - // A. Binary To Hex - UINT8 bin[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF }; - String hex = IACore::Utils::BinaryToHexString(bin); - - IAT_CHECK_EQ(hex, String("DEADBEEF00FF")); +// ------------------------------------------------------------------------- +// 1. Binary <-> Hex String Conversion +// ------------------------------------------------------------------------- +BOOL TestHexConversion() +{ + // A. Binary To Hex + UINT8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF}; + String hex = IACore::Utils::BinaryToHexString(bin); - // B. Hex To Binary (Valid Upper) - auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF"); - IAT_CHECK(resUpper.has_value()); - IAT_CHECK_EQ(resUpper->size(), (SIZE_T)6); - IAT_CHECK_EQ((*resUpper)[0], 0xDE); - IAT_CHECK_EQ((*resUpper)[5], 0xFF); + IAT_CHECK_EQ(hex, String("DEADBEEF00FF")); - // C. Hex To Binary (Valid Lower/Mixed) - auto resLower = IACore::Utils::HexStringToBinary("deadbeef00ff"); - IAT_CHECK(resLower.has_value()); - IAT_CHECK_EQ((*resLower)[0], 0xDE); + // B. Hex To Binary (Valid Upper) + auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF"); + IAT_CHECK(resUpper.has_value()); + IAT_CHECK_EQ(resUpper->size(), (SIZE_T) 6); + IAT_CHECK_EQ((*resUpper)[0], 0xDE); + IAT_CHECK_EQ((*resUpper)[5], 0xFF); - // D. Round Trip Integrity - Vector 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]); + // C. Hex To Binary (Valid Lower/Mixed) + auto resLower = IACore::Utils::HexStringToBinary("deadbeef00ff"); + IAT_CHECK(resLower.has_value()); + IAT_CHECK_EQ((*resLower)[0], 0xDE); - return TRUE; - } + // D. Round Trip Integrity + Vector 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]); - // ------------------------------------------------------------------------- - // 2. Hex Error Handling - // ------------------------------------------------------------------------- - BOOL TestHexErrors() - { - // Odd Length - auto odd = IACore::Utils::HexStringToBinary("ABC"); - IAT_CHECK_NOT(odd.has_value()); - // Optional: IAT_CHECK_EQ(odd.error(), "Hex string must have even length"); + return TRUE; +} - // Invalid Characters - auto invalid = IACore::Utils::HexStringToBinary("ZZTOP"); - IAT_CHECK_NOT(invalid.has_value()); +// ------------------------------------------------------------------------- +// 2. Hex Error Handling +// ------------------------------------------------------------------------- +BOOL TestHexErrors() +{ + // Odd Length + auto odd = IACore::Utils::HexStringToBinary("ABC"); + IAT_CHECK_NOT(odd.has_value()); + // Optional: IAT_CHECK_EQ(odd.error(), "Hex string must have even length"); - // Empty string is valid (empty vector) - auto empty = IACore::Utils::HexStringToBinary(""); - IAT_CHECK(empty.has_value()); - IAT_CHECK_EQ(empty->size(), (SIZE_T)0); + // Invalid Characters + auto invalid = IACore::Utils::HexStringToBinary("ZZTOP"); + IAT_CHECK_NOT(invalid.has_value()); - return TRUE; - } + // Empty string is valid (empty vector) + auto empty = IACore::Utils::HexStringToBinary(""); + IAT_CHECK(empty.has_value()); + IAT_CHECK_EQ(empty->size(), (SIZE_T) 0); - // ------------------------------------------------------------------------- - // 3. Algorithms: Sorting - // ------------------------------------------------------------------------- - BOOL TestSort() - { - Vector nums = { 5, 1, 4, 2, 3 }; - - IACore::Utils::Sort(nums); + return TRUE; +} - 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); +// ------------------------------------------------------------------------- +// 3. Algorithms: Sorting +// ------------------------------------------------------------------------- +BOOL TestSort() +{ + Vector nums = {5, 1, 4, 2, 3}; - return TRUE; - } + IACore::Utils::Sort(nums); - // ------------------------------------------------------------------------- - // 4. Algorithms: Binary Search (Left/Right) - // ------------------------------------------------------------------------- - BOOL TestBinarySearch() - { - // Must be sorted for Binary Search - Vector nums = { 10, 20, 20, 20, 30 }; + 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); - // 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 + return TRUE; +} - // 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 +// ------------------------------------------------------------------------- +// 4. Algorithms: Binary Search (Left/Right) +// ------------------------------------------------------------------------- +BOOL TestBinarySearch() +{ + // Must be sorted for Binary Search + Vector nums = {10, 20, 20, 20, 30}; - // Search for non-existent - auto itFail = IACore::Utils::BinarySearchLeft(nums, 99); - IAT_CHECK(itFail == nums.end()); + // 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 - return TRUE; - } + // 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 - // ------------------------------------------------------------------------- - // 5. Hashing Basics - // ------------------------------------------------------------------------- - BOOL TestHashBasics() - { - UINT64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); - UINT64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); - UINT64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World"); + // Search for non-existent + auto itFail = IACore::Utils::BinarySearchLeft(nums, 99); + IAT_CHECK(itFail == nums.end()); - // Determinism - IAT_CHECK_EQ(h1, h2); + return TRUE; +} - // Differentiation - IAT_CHECK_NEQ(h1, h3); +// ------------------------------------------------------------------------- +// 5. Hashing Basics +// ------------------------------------------------------------------------- +BOOL TestHashBasics() +{ + UINT64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); + UINT64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); + UINT64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World"); - // Order sensitivity (Golden ratio combine should care about order) - // Hash(A, B) != Hash(B, A) - UINT64 orderA = IACore::Utils::ComputeHash(1, 2); - UINT64 orderB = IACore::Utils::ComputeHash(2, 1); - IAT_CHECK_NEQ(orderA, orderB); + // Determinism + IAT_CHECK_EQ(h1, h2); - return TRUE; - } + // Differentiation + IAT_CHECK_NEQ(h1, h3); - // ------------------------------------------------------------------------- - // 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 }; // Slight change + // Order sensitivity (Golden ratio combine should care about order) + // Hash(A, B) != Hash(B, A) + UINT64 orderA = IACore::Utils::ComputeHash(1, 2); + UINT64 orderB = IACore::Utils::ComputeHash(2, 1); + IAT_CHECK_NEQ(orderA, orderB); - // Instantiate the hasher manually to verify the struct specialization exists - ankerl::unordered_dense::hash hasher; - - UINT64 h1 = hasher(v1); - UINT64 h2 = hasher(v2); - UINT64 h3 = hasher(v3); + return TRUE; +} - IAT_CHECK_EQ(h1, h2); // Same content = same hash - IAT_CHECK_NEQ(h1, h3); // Different content = different hash +// ------------------------------------------------------------------------- +// 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}; // Slight change - // ------------------------------------------------------------- - // Verify ComputeHash integration - // ------------------------------------------------------------- - - // We cannot check EQ(h1, ComputeHash(v1)) because ComputeHash applies - // one extra layer of "Golden Ratio Mixing" on top of the object's hash. - // Instead, we verify that ComputeHash behaves exactly like a manual HashCombine. + // Instantiate the hasher manually to verify the struct specialization exists + ankerl::unordered_dense::hash hasher; - UINT64 hManual = 0; - IACore::Utils::HashCombine(hManual, v1); + UINT64 h1 = hasher(v1); + UINT64 h2 = hasher(v2); + UINT64 h3 = hasher(v3); - UINT64 hWrapper = IACore::Utils::ComputeHash(v1); - - // This proves ComputeHash found the specialization and mixed it correctly - IAT_CHECK_EQ(hManual, hWrapper); - - // Optional: Verify the avalanche effect took place (hWrapper should NOT be h1) - IAT_CHECK_NEQ(h1, hWrapper); + IAT_CHECK_EQ(h1, h2); // Same content = same hash + IAT_CHECK_NEQ(h1, h3); // Different content = different hash - return TRUE; - } + // ------------------------------------------------------------- + // Verify ComputeHash integration + // ------------------------------------------------------------- - // ------------------------------------------------------------------------- - // 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_END_TEST_LIST() + // We cannot check EQ(h1, ComputeHash(v1)) because ComputeHash applies + // one extra layer of "Golden Ratio Mixing" on top of the object's hash. + // Instead, we verify that ComputeHash behaves exactly like a manual HashCombine. + + UINT64 hManual = 0; + IACore::Utils::HashCombine(hManual, v1); + + UINT64 hWrapper = IACore::Utils::ComputeHash(v1); + + // This proves ComputeHash found the specialization and mixed it correctly + IAT_CHECK_EQ(hManual, hWrapper); + + // Optional: Verify the avalanche effect took place (hWrapper should NOT be h1) + IAT_CHECK_NEQ(h1, hWrapper); + + 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_END_TEST_LIST() IAT_END_BLOCK() -int main(int argc, char* argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - // Define runner (StopOnFail=false, Verbose=true) - ia::iatest::runner testRunner; - - // Run the BinaryReader block - testRunner.testBlock(); - - return 0; -} \ No newline at end of file +IAT_REGISTER_ENTRY(Core, Utils) diff --git a/Tools/Builder/Build.py b/Tools/Builder/Build.py deleted file mode 100644 index ced6493..0000000 --- a/Tools/Builder/Build.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# IACore-OSS; The Core Library for All IA Open Source Projects -# Copyright (C) 20245IAS (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 . - -import os -import sys - -def main(args: list[str]): - os.system("cmake -S. -B./Build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug") - os.system("cmake --build ./Build") - -main(sys.argv)