Fixes
This commit is contained in:
@ -23,8 +23,6 @@ message(STATUS "Configured IACore for Multi-Config (Debug/Release rules generate
|
||||
message(STATUS "Detected Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
|
||||
# Check if the compiler is MSVC (cl.exe), but allow Clang acting like MSVC (clang-cl)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
# Note: clang-cl usually reports itself as "Clang" in newer CMake versions,
|
||||
# but if it reports MSVC with a Clang simulation, we want to allow it.
|
||||
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
message(FATAL_ERROR
|
||||
"\n\n"
|
||||
@ -40,7 +38,6 @@ endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
add_compile_options(
|
||||
-Wall -Wextra -Wpedantic
|
||||
# Suppress warning for the statement expression macro if -pedantic is on
|
||||
-Wno-language-extension-token
|
||||
)
|
||||
endif()
|
||||
|
||||
39
README.md
39
README.md
@ -130,6 +130,45 @@ if (res) {
|
||||
}
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions from the community! However, to maintain the architectural integrity and licensing flexibility of the project, we have specific guidelines for Pull Requests.
|
||||
|
||||
### What we accept immediately:
|
||||
* **📚 Documentation:** Improvements to comments, the README, or external docs.
|
||||
* **🧪 Tests:** New unit tests (in `Tests/`) to improve coverage or reproduce bugs.
|
||||
* **💡 Examples:** New usage examples or sample projects.
|
||||
* **🐛 Bug Reports:** detailed issues describing reproduction steps are highly valued.
|
||||
|
||||
### Core Library Policy (`Src/` Directory)
|
||||
Currently, **we are not accepting Pull Requests that modify the core source code (`Src/`)**.
|
||||
|
||||
**Why?**
|
||||
IACore is a dual-licensed product. To offer commercial licenses to proprietary software vendors in the future, **IASoft (PVT) LTD.** must retain 100% copyright ownership of the core library.
|
||||
|
||||
We are currently establishing a **Contributor License Agreement (CLA)** process. Once that is in place, we will open the core library for contributions, provided the contributor signs the CLA to assign copyright or grant an unlimited license to the project maintainers.
|
||||
|
||||
If you find a critical bug in `Src/`, please open an **Issue** rather than a PR, and the core team will implement the fix to ensure legal compliance.
|
||||
|
||||
## 🤝 Credits & Acknowledgements
|
||||
**IACore** is an architectural effort by **IASoft (PVT) LTD.**, designed and maintained by its lead developers.
|
||||
|
||||
While the core architecture, API design, and final logic verification were strictly human-led, this project leveraged **Google Gemini 3** as an advanced AI thought partner to accelerate development.
|
||||
|
||||
#### AI contributions include:
|
||||
|
||||
* **Boilerplate Generation:** Rapid prototyping of standard C++20 structures and repetitive implementations.
|
||||
|
||||
* **Research & Selection:** Analyzing and comparing open-source libraries (e.g., `glaze` vs. `nlohmann`, `zstd` vs. `lz4`) to select the best-in-class dependencies.
|
||||
|
||||
* **Design Analysis:** Researching and contrasting design patterns (e.g., SPSC Ring Buffers vs. Mutex Queues) for optimal performance.
|
||||
|
||||
* **Documentation:** Drafting comprehensive documentation and this README.
|
||||
|
||||
* **Code Review:** Automated logic checking and static analysis support.
|
||||
|
||||
**Methodology:** Every line of code, whether written by hand or generated by AI, has been manually reviewed, tested, and verified by human engineers to ensure zero hallucinations and maximum reliability. *Trust, but verify.*
|
||||
|
||||
## ⚖️ License
|
||||
|
||||
This project is licensed under the GNU General Public License v3 (GPLv3).
|
||||
@ -111,7 +111,6 @@ namespace IACore
|
||||
|
||||
// Check for ZLIB Magic Number (starts with 0x78)
|
||||
// 0x78 = Deflate compression with 32k window size
|
||||
// Valid second bytes: 0x01 (Fastest), 0x9C (Default), 0xDA (Best)
|
||||
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
|
||||
return CompressionType::Zlib;
|
||||
|
||||
@ -134,7 +133,6 @@ namespace IACore
|
||||
|
||||
Vector<UINT8> outBuffer;
|
||||
// Start with 2x input size.
|
||||
// Small packets compress well, so maybe 4x for very small inputs.
|
||||
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||
outBuffer.resize(guessSize);
|
||||
|
||||
@ -282,7 +280,7 @@ namespace IACore
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
// WindowBits = 15 + 16 (31) -> This forces GZIP encoding
|
||||
// WindowBits = 15 + 16 (31) = Enforce GZIP encoding
|
||||
// MemLevel = 8 (default)
|
||||
// Strategy = Z_DEFAULT_STRATEGY
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
|
||||
@ -182,7 +182,7 @@ namespace IACore
|
||||
|
||||
IPC_Manager::IPC_Manager()
|
||||
{
|
||||
// SocketOps is smart enough to handle multiple inits
|
||||
// SocketOps is smart enough to track multiple inits
|
||||
SocketOps::Initialize();
|
||||
|
||||
m_recieveBuffer.resize(UINT16_MAX + 1);
|
||||
@ -208,7 +208,7 @@ namespace IACore
|
||||
}
|
||||
m_pendingSessions.clear();
|
||||
|
||||
// SocketOps is smart enough to handle multiple terminates
|
||||
// SocketOps is smart enough to track multiple terminates
|
||||
SocketOps::Terminate();
|
||||
}
|
||||
|
||||
|
||||
@ -136,7 +136,7 @@ namespace IACore
|
||||
|
||||
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
||||
|
||||
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
|
||||
// Close write end in parent, otherwise ReadFile never returns EOF!
|
||||
CloseHandle(hWrite);
|
||||
|
||||
if (!success)
|
||||
@ -196,7 +196,6 @@ namespace IACore
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
|
||||
// --- ARGUMENT PARSING START ---
|
||||
std::vector<std::string> argStorage; // To keep strings alive
|
||||
std::vector<char *> argv;
|
||||
|
||||
@ -258,7 +257,6 @@ namespace IACore
|
||||
argv.push_back(s.data());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
// --- ARGUMENT PARSING END ---
|
||||
|
||||
execvp(argv[0], argv.data());
|
||||
_exit(127);
|
||||
@ -312,7 +310,6 @@ namespace IACore
|
||||
}
|
||||
else
|
||||
{
|
||||
// Zero copy optimization for pure lines in one chunk
|
||||
if (i > start)
|
||||
Callback(StringView(data + start, i - start));
|
||||
}
|
||||
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
@ -19,30 +19,31 @@
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
#include <libloaderapi.h>
|
||||
#include <errhandlingapi.h>
|
||||
# include <libloaderapi.h>
|
||||
# include <errhandlingapi.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
# include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
namespace IACore
|
||||
{
|
||||
|
||||
class DynamicLib {
|
||||
public:
|
||||
// ---------------------------------------------------------------------
|
||||
// Constructors / Destructors (Move Only)
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
DynamicLib() : m_handle(nullptr) {}
|
||||
class DynamicLib
|
||||
{
|
||||
public:
|
||||
DynamicLib() : m_handle(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
// Move Constructor: Steal ownership, nullify source
|
||||
DynamicLib(DynamicLib&& other) NOEXCEPT : m_handle(other.m_handle) {
|
||||
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle)
|
||||
{
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
|
||||
// Move Assignment
|
||||
DynamicLib& operator=(DynamicLib&& other) NOEXCEPT {
|
||||
if (this != &other) {
|
||||
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
Unload(); // Free current if exists
|
||||
m_handle = other.m_handle;
|
||||
other.m_handle = nullptr;
|
||||
@ -50,135 +51,129 @@ namespace IACore {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// No Copying allowed (Library handles are unique resources)
|
||||
DynamicLib(CONST DynamicLib&) = delete;
|
||||
DynamicLib& operator=(CONST DynamicLib&) = delete;
|
||||
DynamicLib(CONST DynamicLib &) = delete;
|
||||
DynamicLib &operator=(CONST DynamicLib &) = delete;
|
||||
|
||||
~DynamicLib() {
|
||||
~DynamicLib()
|
||||
{
|
||||
Unload();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Static Loader
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Automatically detects extension (.dll/.so) if not provided
|
||||
NO_DISCARD("Check for load errors")
|
||||
STATIC tl::expected<DynamicLib, String> Load(CONST String& searchPath, CONST String& name) {
|
||||
|
||||
STATIC tl::expected<DynamicLib, String> Load(CONST String &searchPath, CONST String &name)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// 1. Build Path safely
|
||||
fs::path fullPath = fs::path(searchPath) / name;
|
||||
|
||||
// 2. Auto-append extension if missing
|
||||
if (!fullPath.has_extension()) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
fullPath += ".dll";
|
||||
#elif IA_PLATFORM_MAC
|
||||
fullPath += ".dylib";
|
||||
#else
|
||||
fullPath += ".so";
|
||||
#endif
|
||||
if (!fullPath.has_extension())
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
fullPath += ".dll";
|
||||
#elif IA_PLATFORM_MAC
|
||||
fullPath += ".dylib";
|
||||
#else
|
||||
fullPath += ".so";
|
||||
#endif
|
||||
}
|
||||
|
||||
DynamicLib lib;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
// Use LoadLibraryA (ANSI/UTF-8) assuming manifest is set for UTF-8
|
||||
HMODULE h = LoadLibraryA(fullPath.string().c_str());
|
||||
if (!h) {
|
||||
return tl::make_unexpected(GetWindowsError());
|
||||
}
|
||||
lib.m_handle = CAST(h, PVOID);
|
||||
#else
|
||||
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
|
||||
// RTLD_NOW: Resolve all immediately (Safer for strict engines)
|
||||
void* h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!h) {
|
||||
// dlerror returns a string describing the last error
|
||||
const char* err = dlerror();
|
||||
return tl::make_unexpected(String(err ? err : "Unknown dlopen error"));
|
||||
}
|
||||
lib.m_handle = h;
|
||||
#endif
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HMODULE h = LoadLibraryA(fullPath.string().c_str());
|
||||
if (!h)
|
||||
{
|
||||
return tl::make_unexpected(GetWindowsError());
|
||||
}
|
||||
lib.m_handle = CAST(h, PVOID);
|
||||
#else
|
||||
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
|
||||
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!h)
|
||||
{
|
||||
// dlerror returns a string describing the last error
|
||||
const char *err = dlerror();
|
||||
return tl::make_unexpected(String(err ? err : "Unknown dlopen error"));
|
||||
}
|
||||
lib.m_handle = h;
|
||||
#endif
|
||||
|
||||
return IA_MOVE(lib);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Symbol Access
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
NO_DISCARD("Check if symbol exists")
|
||||
tl::expected<PVOID, String> GetSymbol(CONST String& name) CONST {
|
||||
if (!m_handle) return tl::make_unexpected(String("Library not loaded"));
|
||||
|
||||
tl::expected<PVOID, String> GetSymbol(CONST String &name) CONST
|
||||
{
|
||||
if (!m_handle)
|
||||
return tl::make_unexpected(String("Library not loaded"));
|
||||
|
||||
PVOID sym = nullptr;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID);
|
||||
if (!sym) return tl::make_unexpected(GetWindowsError());
|
||||
#else
|
||||
// Clear any previous error
|
||||
dlerror();
|
||||
sym = dlsym(m_handle, name.c_str());
|
||||
const char* err = dlerror();
|
||||
if (err) return tl::make_unexpected(String(err));
|
||||
#endif
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID);
|
||||
if (!sym)
|
||||
return tl::make_unexpected(GetWindowsError());
|
||||
#else
|
||||
// Clear any previous error
|
||||
dlerror();
|
||||
sym = dlsym(m_handle, name.c_str());
|
||||
const char *err = dlerror();
|
||||
if (err)
|
||||
return tl::make_unexpected(String(err));
|
||||
#endif
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
// Template helper for casting
|
||||
template<typename FuncT>
|
||||
tl::expected<FuncT, String> GetFunction(CONST String& name) CONST {
|
||||
template<typename FuncT> tl::expected<FuncT, String> GetFunction(CONST String &name) CONST
|
||||
{
|
||||
auto res = GetSymbol(name);
|
||||
if (!res) return tl::make_unexpected(res.error());
|
||||
if (!res)
|
||||
return tl::make_unexpected(res.error());
|
||||
return REINTERPRET(*res, FuncT);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// State Management
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
VOID Unload() {
|
||||
if (m_handle) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
FreeLibrary(CAST(m_handle, HMODULE));
|
||||
#else
|
||||
dlclose(m_handle);
|
||||
#endif
|
||||
VOID Unload()
|
||||
{
|
||||
if (m_handle)
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
FreeLibrary(CAST(m_handle, HMODULE));
|
||||
#else
|
||||
dlclose(m_handle);
|
||||
#endif
|
||||
m_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL IsLoaded() CONST {
|
||||
BOOL IsLoaded() CONST
|
||||
{
|
||||
return m_handle != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
PVOID m_handle;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Private Helpers
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
STATIC String GetWindowsError() {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
STATIC String GetWindowsError()
|
||||
{
|
||||
DWORD errorID = ::GetLastError();
|
||||
if(errorID == 0) return String();
|
||||
if (errorID == 0)
|
||||
return String();
|
||||
|
||||
LPSTR messageBuffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPSTR)&messageBuffer, 0, NULL
|
||||
);
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
|
||||
errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL);
|
||||
|
||||
String message(messageBuffer, size);
|
||||
LocalFree(messageBuffer);
|
||||
return String("Win32 Error: ") + message;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -23,20 +23,14 @@ namespace IACore
|
||||
class Environment
|
||||
{
|
||||
public:
|
||||
// ---------------------------------------------------------------------
|
||||
// Getters
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Modern approach: Returns nullopt if variable doesn't exist
|
||||
STATIC std::optional<String> Find(CONST String &name)
|
||||
STATIC Optional<String> Find(CONST String &name)
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
// 1. Get required size (result includes null terminator if buffer is null/too small)
|
||||
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
|
||||
|
||||
if (bufferSize == 0)
|
||||
{
|
||||
// DWORD 0 means failed (usually ERROR_ENVVAR_NOT_FOUND)
|
||||
// DWORD 0 means failed
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@ -45,7 +39,7 @@ namespace IACore
|
||||
result.resize(bufferSize);
|
||||
|
||||
// 3. Fetch
|
||||
// Returns num chars written EXCLUDING null terminator
|
||||
// Returns num chars written excluding null terminator
|
||||
DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize);
|
||||
|
||||
if (actualSize == 0 || actualSize > bufferSize)
|
||||
@ -58,8 +52,7 @@ namespace IACore
|
||||
return result;
|
||||
|
||||
#else
|
||||
// POSIX (Linux/Mac)
|
||||
// getenv returns a pointer to the environment area. Do NOT free it.
|
||||
// getenv returns a pointer to the environment area
|
||||
const char *val = std::getenv(name.c_str());
|
||||
if (val == nullptr)
|
||||
{
|
||||
@ -69,17 +62,11 @@ namespace IACore
|
||||
#endif
|
||||
}
|
||||
|
||||
// Classic approach: Returns value or default (defaulting to "")
|
||||
// Matches your old API behavior but is safer
|
||||
STATIC String Get(CONST String &name, CONST String &defaultValue = "")
|
||||
{
|
||||
return Find(name).value_or(defaultValue);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Setters
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
STATIC BOOL Set(CONST String &name, CONST String &value)
|
||||
{
|
||||
if (name.empty())
|
||||
@ -88,7 +75,6 @@ namespace IACore
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return SetEnvironmentVariableA(name.c_str(), value.c_str()) != 0;
|
||||
#else
|
||||
// setenv(name, value, overwrite)
|
||||
// Returns 0 on success, -1 on error
|
||||
return setenv(name.c_str(), value.c_str(), 1) == 0;
|
||||
#endif
|
||||
@ -100,17 +86,12 @@ namespace IACore
|
||||
return FALSE;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
// Windows unsets a variable by setting it to NULL
|
||||
return SetEnvironmentVariableA(name.c_str(), nullptr) != 0;
|
||||
#else
|
||||
return unsetenv(name.c_str()) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Utilities
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
STATIC BOOL Exists(CONST String &name)
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
|
||||
@ -28,7 +28,6 @@
|
||||
|
||||
# define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type
|
||||
|
||||
// Internal macro to handle the return logic
|
||||
# define __iat_micro_test(call) \
|
||||
if (!(call)) \
|
||||
return FALSE
|
||||
@ -38,7 +37,6 @@
|
||||
# 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_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||
@ -46,7 +44,6 @@
|
||||
|
||||
# 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 \
|
||||
{ \
|
||||
@ -74,9 +71,6 @@
|
||||
|
||||
namespace ia::iatest
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// Type Printing Helper (To show WHAT failed)
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename T> std::string ToString(CONST T &value)
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<T>)
|
||||
@ -93,7 +87,6 @@ namespace ia::iatest
|
||||
}
|
||||
}
|
||||
|
||||
// Specialization for pointers
|
||||
template<typename T> std::string ToString(T *value)
|
||||
{
|
||||
if (value == NULLPTR)
|
||||
@ -103,10 +96,6 @@ namespace ia::iatest
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Core Structures
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
DEFINE_TYPE(functor_t, std::function<BOOL()>);
|
||||
|
||||
struct unit_t
|
||||
@ -128,7 +117,6 @@ namespace ia::iatest
|
||||
}
|
||||
|
||||
protected:
|
||||
// Generic Equality
|
||||
template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
||||
{
|
||||
if (lhs != rhs)
|
||||
@ -139,7 +127,6 @@ namespace ia::iatest
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Generic Inequality
|
||||
template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
||||
{
|
||||
if (lhs == rhs)
|
||||
@ -150,13 +137,12 @@ namespace ia::iatest
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Floating Point Approximation (Epsilon check)
|
||||
template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
|
||||
T diff = std::abs(lhs - rhs);
|
||||
if (diff > static_cast<T>(0.0001))
|
||||
{ // Default epsilon
|
||||
{
|
||||
print_fail(description, ToString(lhs), ToString(rhs));
|
||||
return FALSE;
|
||||
}
|
||||
@ -202,10 +188,6 @@ namespace ia::iatest
|
||||
template<typename block_class>
|
||||
concept valid_block_class = std::derived_from<block_class, block>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Runner
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner
|
||||
{
|
||||
public:
|
||||
@ -253,7 +235,6 @@ namespace ia::iatest
|
||||
BOOL result = FALSE;
|
||||
try
|
||||
{
|
||||
// Execute the test function
|
||||
result = v.Functor();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
@ -311,10 +292,6 @@ namespace ia::iatest
|
||||
{
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Global Test Registry
|
||||
// -------------------------------------------------------------------------
|
||||
// Standard runner configuration for the single executable
|
||||
using DefaultRunner = runner<false, true>;
|
||||
|
||||
class TestRegistry
|
||||
@ -352,7 +329,6 @@ namespace ia::iatest
|
||||
};
|
||||
} // namespace ia::iatest
|
||||
|
||||
// Usage: IAT_REGISTER_ENTRY(Core, Utils)
|
||||
# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||
|
||||
#endif // __cplusplus
|
||||
@ -43,7 +43,7 @@ namespace IACore
|
||||
// SECTION 2: RING BUFFER CONTROL BLOCKS
|
||||
// =========================================================
|
||||
|
||||
// RingBufferView::ControlBlock is already 64-byte aligned internally.
|
||||
// RingBufferView ControlBlock is already 64-byte aligned internally.
|
||||
RingBufferView::ControlBlock MONI_Control;
|
||||
RingBufferView::ControlBlock MINO_Control;
|
||||
|
||||
|
||||
@ -54,8 +54,8 @@
|
||||
# define NOMINMAX
|
||||
# endif
|
||||
# include <windows.h>
|
||||
#undef VOID
|
||||
#undef ERROR
|
||||
# undef VOID
|
||||
# undef ERROR
|
||||
#elif IA_PLATFORM_UNIX
|
||||
# include <unistd.h>
|
||||
# include <sys/wait.h>
|
||||
@ -238,7 +238,6 @@
|
||||
|
||||
#define ALIGN(a) __attribute__((aligned(a)))
|
||||
|
||||
// Mark every explicit assembly instruction as volatile
|
||||
#define ASM(...) __asm__ volatile(__VA_ARGS__)
|
||||
|
||||
#ifndef NULL
|
||||
@ -539,7 +538,7 @@ STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
|
||||
#endif
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// C++ Containers and Helpers
|
||||
// Containers and Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
#ifdef __cplusplus
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ using SocketHandle = int;
|
||||
|
||||
#else
|
||||
|
||||
# warning "IACore SocketOps is not supported on this platform."
|
||||
# error "IACore SocketOps is not supported on this platform."
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@ -55,7 +55,6 @@ namespace IACore
|
||||
char high = hex[i];
|
||||
char low = hex[i + 1];
|
||||
|
||||
// Quick helper to decode nibble
|
||||
auto fromHexChar = [](char c) -> int {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
@ -95,16 +94,10 @@ namespace IACore
|
||||
return std::ranges::upper_bound(range, value);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Hash Combination Logic
|
||||
// Uses the "golden ratio" mix (similar to Boost) to preserve entropy.
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
|
||||
{
|
||||
UINT64 h;
|
||||
|
||||
// 1. Compile-Time check: Can this be treated as a string view?
|
||||
// This catches "Literal Strings" (char[N]), const char*, and std::string
|
||||
if constexpr (std::is_constructible_v<std::string_view, T>)
|
||||
{
|
||||
std::string_view sv(v);
|
||||
@ -113,34 +106,24 @@ namespace IACore
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2. Standard types (int, float, custom structs)
|
||||
auto hasher = ankerl::unordered_dense::hash<T>();
|
||||
h = hasher(v);
|
||||
}
|
||||
|
||||
// 0x9e3779b97f4a7c15 is the 64-bit golden ratio (phi) approximation
|
||||
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Variadic Hasher
|
||||
// Allows: IACore::ComputeHash(x, y, z);
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename... Args> INLINE STATIC UINT64 ComputeHash(CONST Args &...args)
|
||||
{
|
||||
UINT64 seed = 0;
|
||||
(HashCombine(seed, args), ...); // C++17/20 Fold Expression
|
||||
(HashCombine(seed, args), ...);
|
||||
return seed;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Flat Hasher
|
||||
// Allows: IACore::ComputeHashFlat(x, y, z);
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename T, typename... MemberPtrs> INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
|
||||
template<typename T, typename... MemberPtrs>
|
||||
INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
|
||||
{
|
||||
UINT64 seed = 0;
|
||||
// C++17 Fold Expression: applies (seed, obj.*ptr) for every ptr in members
|
||||
(HashCombine(seed, obj.*members), ...);
|
||||
return seed;
|
||||
}
|
||||
@ -156,15 +139,14 @@ namespace IACore
|
||||
// struct Vector3 { float x, y, z; };
|
||||
// IA_MAKE_HASHABLE(Vector3, &Vector3::x, &Vector3::y, &Vector3::z)
|
||||
// -----------------------------------------------------------------------------
|
||||
#define IA_MAKE_HASHABLE(Type, ...) \
|
||||
template<> struct ankerl::unordered_dense::hash<Type> \
|
||||
{ \
|
||||
using is_avalanching = void; \
|
||||
NO_DISCARD("Hash value should be used") \
|
||||
UINT64 operator()(CONST Type &v) const NOEXCEPT \
|
||||
{ \
|
||||
/* Pass the object and the list of member pointers */ \
|
||||
return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \
|
||||
} \
|
||||
#define IA_MAKE_HASHABLE(Type, ...) \
|
||||
template<> struct ankerl::unordered_dense::hash<Type> \
|
||||
{ \
|
||||
using is_avalanching = void; \
|
||||
NO_DISCARD("Hash value should be used") \
|
||||
UINT64 operator()(CONST Type &v) const NOEXCEPT \
|
||||
{ \
|
||||
/* Pass the object and the list of member pointers */ \
|
||||
return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \
|
||||
} \
|
||||
};
|
||||
|
||||
|
||||
@ -23,13 +23,13 @@ set(TEST_SOURCES
|
||||
|
||||
add_executable(IACore_Test_Suite ${TEST_SOURCES})
|
||||
|
||||
# Enable exceptions for testing framework (even if Core is no-except)
|
||||
# Enable exceptions for testing framework
|
||||
target_compile_options(IACore_Test_Suite PRIVATE -fexceptions)
|
||||
set_target_properties(IACore_Test_Suite 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)
|
||||
# Copy necessary runtime assets
|
||||
add_custom_command(TARGET IACore_Test_Suite POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_FILE:LongProcess>
|
||||
|
||||
@ -23,8 +23,6 @@ using namespace IACore;
|
||||
// -----------------------------------------------------------------------------
|
||||
// Constants
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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";
|
||||
|
||||
@ -123,7 +121,6 @@ BOOL TestDefaults()
|
||||
// -------------------------------------------------------------------------
|
||||
// 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()
|
||||
|
||||
@ -50,7 +50,6 @@ BOOL TestBasicRun()
|
||||
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);
|
||||
|
||||
@ -137,14 +136,9 @@ BOOL TestMissingExe()
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestLargeOutput()
|
||||
{
|
||||
// We need to generate output larger than the internal 4096 buffer
|
||||
// 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)
|
||||
@ -223,10 +217,6 @@ BOOL TestComplexArguments()
|
||||
// 3. path/to/file
|
||||
String complexArgs = "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
|
||||
|
||||
// We can't easily inspect the child process argv in this unit test framework without
|
||||
// writing a dedicated child program that prints its argv.
|
||||
// However, for now, we ensure it doesn't crash or return error code 127.
|
||||
|
||||
// Use "echo" to verify what it received.
|
||||
// Expected output: -DDEFINED_MSG="Hello World" -v path/to/file
|
||||
String cmd = "/bin/echo";
|
||||
|
||||
@ -21,7 +21,9 @@ using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, RingBuffer)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 1. Basic Push Pop
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestPushPop()
|
||||
{
|
||||
// Allocate raw memory for the ring buffer
|
||||
@ -54,7 +56,9 @@ BOOL TestPushPop()
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 2. Wrap Around (The hardest logic to get right)
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Wrap Around
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestWrapAround()
|
||||
{
|
||||
// Small buffer to force wrapping quickly
|
||||
|
||||
@ -20,10 +20,6 @@
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Test Block Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, StreamReader)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -53,14 +49,13 @@ BOOL TestReadUint8()
|
||||
BOOL TestReadMultiByte()
|
||||
{
|
||||
// 0x04030201 in Little Endian memory layout
|
||||
// IACore always assumes a Little Endian machine
|
||||
UINT8 data[] = {0x01, 0x02, 0x03, 0x04};
|
||||
StreamReader reader(data);
|
||||
|
||||
auto val = reader.Read<UINT32>();
|
||||
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);
|
||||
|
||||
@ -88,7 +88,6 @@ 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");
|
||||
|
||||
// Invalid Characters
|
||||
auto invalid = IACore::Utils::HexStringToBinary("ZZTOP");
|
||||
@ -178,9 +177,8 @@ 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
|
||||
TestVec3 v3{1.0f, 2.0f, 4.0f};
|
||||
|
||||
// Instantiate the hasher manually to verify the struct specialization exists
|
||||
ankerl::unordered_dense::hash<TestVec3> hasher;
|
||||
|
||||
UINT64 h1 = hasher(v1);
|
||||
@ -194,10 +192,6 @@ BOOL TestHashMacro()
|
||||
// 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.
|
||||
|
||||
UINT64 hManual = 0;
|
||||
IACore::Utils::HashCombine(hManual, v1);
|
||||
|
||||
@ -206,7 +200,7 @@ BOOL TestHashMacro()
|
||||
// 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)
|
||||
// Verify the avalanche effect took place (hWrapper should NOT be h1)
|
||||
IAT_CHECK_NEQ(h1, hWrapper);
|
||||
|
||||
return TRUE;
|
||||
|
||||
Reference in New Issue
Block a user