Release v1.1.0
This commit is contained in:
39
Tests/Unit/CCompile.c
Normal file
39
Tests/Unit/CCompile.c
Normal file
@ -0,0 +1,39 @@
|
||||
// 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/>.
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#error "CRITICAL: This file MUST be compiled as C to test ABI compatibility."
|
||||
#endif
|
||||
|
||||
#include <IACore/IACore.hpp>
|
||||
|
||||
#if TRUE != 1
|
||||
#error "TRUE macro is broken in C mode"
|
||||
#endif
|
||||
|
||||
int main(void) {
|
||||
IA_VERSION_TYPE version = IA_MAKE_VERSION(1, 0, 0);
|
||||
|
||||
IA_ASSERT(version > 0);
|
||||
|
||||
UNUSED(version);
|
||||
|
||||
int32_t myNumber = 10;
|
||||
|
||||
(void)myNumber;
|
||||
|
||||
return 0;
|
||||
}
|
||||
37
Tests/Unit/CMakeLists.txt
Normal file
37
Tests/Unit/CMakeLists.txt
Normal file
@ -0,0 +1,37 @@
|
||||
# ------------------------------------------------
|
||||
# C Compile Test (Keep separate to verify C ABI)
|
||||
# ------------------------------------------------
|
||||
add_executable(IACore_Test_C_ABI "CCompile.c")
|
||||
set_target_properties(IACore_Test_C_ABI PROPERTIES
|
||||
C_STANDARD 99
|
||||
C_STANDARD_REQUIRED ON
|
||||
LINKER_LANGUAGE C
|
||||
)
|
||||
target_link_libraries(IACore_Test_C_ABI PRIVATE IACore)
|
||||
|
||||
# ------------------------------------------------
|
||||
# The Unified C++ Test Suite
|
||||
# ------------------------------------------------
|
||||
set(TEST_SOURCES
|
||||
Main.cpp
|
||||
Utils.cpp
|
||||
Environment.cpp
|
||||
ProcessOps.cpp
|
||||
StreamReader.cpp
|
||||
RingBuffer.cpp
|
||||
)
|
||||
|
||||
add_executable(IACore_Test_Suite ${TEST_SOURCES})
|
||||
|
||||
# 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
|
||||
add_custom_command(TARGET IACore_Test_Suite POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_FILE:LongProcess>
|
||||
$<TARGET_FILE_DIR:IACore_Test_Suite>/LongProcess${CMAKE_EXECUTABLE_SUFFIX}
|
||||
)
|
||||
179
Tests/Unit/Environment.cpp
Normal file
179
Tests/Unit/Environment.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Environment.hpp>
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Constants
|
||||
// -----------------------------------------------------------------------------
|
||||
static const char *TEST_KEY = "IA_TEST_ENV_VAR_12345";
|
||||
static const char *TEST_VAL = "Hello World";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Test Block Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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));
|
||||
|
||||
// 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));
|
||||
|
||||
// 4. Get (Direct)
|
||||
String val = Environment::Get(TEST_KEY);
|
||||
IAT_CHECK_EQ(val, String(TEST_VAL));
|
||||
|
||||
// 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"));
|
||||
|
||||
// Overwrite
|
||||
Environment::Set(TEST_KEY, "ValueB");
|
||||
IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueB"));
|
||||
|
||||
Environment::Unset(TEST_KEY);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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);
|
||||
|
||||
// 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());
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 4. Default Value Fallbacks
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestDefaults()
|
||||
{
|
||||
const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST";
|
||||
|
||||
// Ensure it really doesn't exist
|
||||
Environment::Unset(ghostKey);
|
||||
|
||||
// Test Get with implicit default ("")
|
||||
String empty = Environment::Get(ghostKey);
|
||||
IAT_CHECK(empty.empty());
|
||||
|
||||
// Test Get with explicit default
|
||||
String fallback = Environment::Get(ghostKey, "MyDefault");
|
||||
IAT_CHECK_EQ(fallback, String("MyDefault"));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 5. Empty Strings vs Null/Unset
|
||||
// -------------------------------------------------------------------------
|
||||
// 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, "");
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
// Windows behavior: Setting to empty string removes the variable
|
||||
// IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
||||
// auto opt = Environment::Find(TEST_KEY);
|
||||
// IAT_CHECK_NOT(opt.has_value());
|
||||
#else
|
||||
// POSIX behavior: Variable exists but is empty
|
||||
IAT_CHECK(Environment::Exists(TEST_KEY));
|
||||
auto opt = Environment::Find(TEST_KEY);
|
||||
IAT_CHECK(opt.has_value());
|
||||
IAT_CHECK(opt->empty());
|
||||
#endif
|
||||
|
||||
// Cleanup
|
||||
Environment::Unset(TEST_KEY);
|
||||
IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 6. Validation / Bad Input
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestBadInput()
|
||||
{
|
||||
// Setting an empty key should fail gracefully
|
||||
BOOL res = Environment::Set("", "Value");
|
||||
IAT_CHECK_NOT(res);
|
||||
|
||||
// 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()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, Environment)
|
||||
35
Tests/Unit/Main.cpp
Normal file
35
Tests/Unit/Main.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IACore.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
253
Tests/Unit/ProcessOps.cpp
Normal file
253
Tests/Unit/ProcessOps.cpp
Normal file
@ -0,0 +1,253 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Platform Abstraction for Test Commands
|
||||
// -----------------------------------------------------------------------------
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
# define CMD_ECHO_EXE "cmd.exe"
|
||||
# define CMD_ARG_PREFIX "/c echo"
|
||||
# define NULL_DEVICE "NUL"
|
||||
#else
|
||||
# define CMD_ECHO_EXE "/bin/echo"
|
||||
# define CMD_ARG_PREFIX ""
|
||||
# define NULL_DEVICE "/dev/null"
|
||||
#endif
|
||||
|
||||
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; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0); // Exit code 0
|
||||
|
||||
// We check if "HelloIA" is contained or equal.
|
||||
IAT_CHECK(captured.find("HelloIA") != String::npos);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Argument Parsing
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestArguments()
|
||||
{
|
||||
Vector<String> 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
|
||||
|
||||
auto result =
|
||||
ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args, [&](StringView line) { lines.push_back(String(line)); });
|
||||
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
IAT_CHECK(lines.size() > 0);
|
||||
|
||||
// Output should contain "one two"
|
||||
IAT_CHECK(lines[0].find("one two") != String::npos);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 3. Error / Non-Zero Exit Codes
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestExitCodes()
|
||||
{
|
||||
// We need a command that returns non-zero.
|
||||
// Windows: cmd /c exit 1
|
||||
// Linux: /bin/sh -c "exit 1"
|
||||
|
||||
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, arg, [](StringView) {});
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 42);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 4. Missing Executable Handling
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestMissingExe()
|
||||
{
|
||||
// Try to run a random string
|
||||
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {});
|
||||
|
||||
// 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
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 5. Line Buffer Logic (The 4096 split test)
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestLargeOutput()
|
||||
{
|
||||
// Need to generate output larger than the internal 4096 buffer
|
||||
// to ensure the "partial line" logic works when a line crosses a buffer boundary.
|
||||
|
||||
String massiveString;
|
||||
massiveString.reserve(5000);
|
||||
for (int i = 0; i < 500; ++i)
|
||||
massiveString += "1234567890"; // 5000 chars
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 6. Complex Command Line Arguments Handling
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestComplexArguments()
|
||||
{
|
||||
// Should parse as 3 arguments:
|
||||
// 1. -DDEFINED_MSG="Hello World"
|
||||
// 2. -v
|
||||
// 3. path/to/file
|
||||
String complexArgs = "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
|
||||
|
||||
String cmd = CMD_ECHO_EXE;
|
||||
|
||||
String finalArgs;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
finalArgs = "/c echo " + complexArgs;
|
||||
#else
|
||||
finalArgs = complexArgs;
|
||||
#endif
|
||||
|
||||
String captured;
|
||||
auto result = ProcessOps::SpawnProcessSync(cmd, finalArgs, [&](StringView line) { captured += line; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
|
||||
// Verify the quotes were preserved in the output
|
||||
IAT_CHECK(captured.find("Hello World") != String::npos);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registration
|
||||
// -------------------------------------------------------------------------
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(TestBasicRun);
|
||||
IAT_ADD_TEST(TestArguments);
|
||||
IAT_ADD_TEST(TestExitCodes);
|
||||
IAT_ADD_TEST(TestMissingExe);
|
||||
IAT_ADD_TEST(TestLargeOutput);
|
||||
IAT_ADD_TEST(TestMultiLine);
|
||||
IAT_ADD_TEST(TestComplexArguments);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, ProcessOps)
|
||||
107
Tests/Unit/RingBuffer.cpp
Normal file
107
Tests/Unit/RingBuffer.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/ADT/RingBuffer.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
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<UINT8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
|
||||
|
||||
// Initialize as OWNER (Producer)
|
||||
RingBufferView producer(std::span<UINT8>(memory), TRUE);
|
||||
|
||||
// Initialize as CONSUMER (Pointer to same memory)
|
||||
RingBufferView consumer(std::span<UINT8>(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<UINT8>(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
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestWrapAround()
|
||||
{
|
||||
// Small buffer to force wrapping quickly
|
||||
// Capacity will be 100 bytes
|
||||
std::vector<UINT8> memory(sizeof(RingBufferView::ControlBlock) + 100);
|
||||
RingBufferView rb(std::span<UINT8>(memory), TRUE);
|
||||
|
||||
// Fill buffer to near end
|
||||
// Push 80 bytes
|
||||
std::vector<UINT8> 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<UINT8> 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)
|
||||
176
Tests/Unit/StreamReader.cpp
Normal file
176
Tests/Unit/StreamReader.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/StreamReader.hpp>
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
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<UINT8>();
|
||||
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<UINT8>();
|
||||
IAT_CHECK_EQ(*val2, 0xBB);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Multi-byte Reading (Endianness check)
|
||||
// -------------------------------------------------------------------------
|
||||
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());
|
||||
|
||||
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<FLOAT32>();
|
||||
|
||||
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<UINT16>());
|
||||
IAT_CHECK(reader.IsEOF());
|
||||
|
||||
// Invalid Read Primitive
|
||||
auto val = reader.Read<UINT8>();
|
||||
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)
|
||||
222
Tests/Unit/Utils.cpp
Normal file
222
Tests/Unit/Utils.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Utils.hpp>
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Test Structs for Hashing (Must be defined at Global Scope)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
};
|
||||
|
||||
// Inject the hash specialization into the ankerl namespace
|
||||
// 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"));
|
||||
|
||||
// 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);
|
||||
|
||||
// C. Hex To Binary (Valid Lower/Mixed)
|
||||
auto resLower = IACore::Utils::HexStringToBinary("deadbeef00ff");
|
||||
IAT_CHECK(resLower.has_value());
|
||||
IAT_CHECK_EQ((*resLower)[0], 0xDE);
|
||||
|
||||
// D. Round Trip Integrity
|
||||
Vector<UINT8> 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]);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Hex Error Handling
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestHexErrors()
|
||||
{
|
||||
// Odd Length
|
||||
auto odd = IACore::Utils::HexStringToBinary("ABC");
|
||||
IAT_CHECK_NOT(odd.has_value());
|
||||
|
||||
// Invalid Characters
|
||||
auto invalid = IACore::Utils::HexStringToBinary("ZZTOP");
|
||||
IAT_CHECK_NOT(invalid.has_value());
|
||||
|
||||
// Empty string is valid (empty vector)
|
||||
auto empty = IACore::Utils::HexStringToBinary("");
|
||||
IAT_CHECK(empty.has_value());
|
||||
IAT_CHECK_EQ(empty->size(), (SIZE_T) 0);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 3. Algorithms: Sorting
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestSort()
|
||||
{
|
||||
Vector<int> nums = {5, 1, 4, 2, 3};
|
||||
|
||||
IACore::Utils::Sort(nums);
|
||||
|
||||
IAT_CHECK_EQ(nums[0], 1);
|
||||
IAT_CHECK_EQ(nums[1], 2);
|
||||
IAT_CHECK_EQ(nums[2], 3);
|
||||
IAT_CHECK_EQ(nums[3], 4);
|
||||
IAT_CHECK_EQ(nums[4], 5);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 4. Algorithms: Binary Search (Left/Right)
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestBinarySearch()
|
||||
{
|
||||
// Must be sorted for Binary Search
|
||||
Vector<int> nums = {10, 20, 20, 20, 30};
|
||||
|
||||
// Search Left (Lower Bound) -> First element >= value
|
||||
auto itLeft = IACore::Utils::BinarySearchLeft(nums, 20);
|
||||
IAT_CHECK(itLeft != nums.end());
|
||||
IAT_CHECK_EQ(*itLeft, 20);
|
||||
IAT_CHECK_EQ(std::distance(nums.begin(), itLeft), 1); // Index 1 is first 20
|
||||
|
||||
// Search Right (Upper Bound) -> First element > value
|
||||
auto itRight = IACore::Utils::BinarySearchRight(nums, 20);
|
||||
IAT_CHECK(itRight != nums.end());
|
||||
IAT_CHECK_EQ(*itRight, 30); // Points to 30
|
||||
IAT_CHECK_EQ(std::distance(nums.begin(), itRight), 4); // Index 4
|
||||
|
||||
// Search for non-existent
|
||||
auto itFail = IACore::Utils::BinarySearchLeft(nums, 99);
|
||||
IAT_CHECK(itFail == nums.end());
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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");
|
||||
|
||||
// Determinism
|
||||
IAT_CHECK_EQ(h1, h2);
|
||||
|
||||
// Differentiation
|
||||
IAT_CHECK_NEQ(h1, h3);
|
||||
|
||||
// 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);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 6. Macro Verification (IA_MAKE_HASHABLE)
|
||||
// -------------------------------------------------------------------------
|
||||
BOOL TestHashMacro()
|
||||
{
|
||||
TestVec3 v1{1.0f, 2.0f, 3.0f};
|
||||
TestVec3 v2{1.0f, 2.0f, 3.0f};
|
||||
TestVec3 v3{1.0f, 2.0f, 4.0f};
|
||||
|
||||
ankerl::unordered_dense::hash<TestVec3> hasher;
|
||||
|
||||
UINT64 h1 = hasher(v1);
|
||||
UINT64 h2 = hasher(v2);
|
||||
UINT64 h3 = hasher(v3);
|
||||
|
||||
IAT_CHECK_EQ(h1, h2); // Same content = same hash
|
||||
IAT_CHECK_NEQ(h1, h3); // Different content = different hash
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Verify ComputeHash integration
|
||||
// -------------------------------------------------------------
|
||||
|
||||
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);
|
||||
|
||||
// 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()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, Utils)
|
||||
Reference in New Issue
Block a user