Tests 1/2

This commit is contained in:
2025-11-23 02:06:57 +05:30
commit d1b1ba12c0
36 changed files with 3785 additions and 0 deletions

3
Tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
add_subdirectory(Unit/)
add_subdirectory(Regression/)

View File

220
Tests/Unit/BinaryReader.cpp Normal file
View File

@ -0,0 +1,220 @@
// 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/>.
#include <IACore/BinaryReader.hpp>
#include <IACore/IATest.hpp>
using namespace IACore;
// -----------------------------------------------------------------------------
// Test Block Definition
// -----------------------------------------------------------------------------
IAT_BEGIN_BLOCK(Core, BinaryReader)
// -------------------------------------------------------------------------
// 1. Basic Primitive Reading (UINT8)
// -------------------------------------------------------------------------
BOOL TestReadUint8()
{
UINT8 data[] = { 0xAA, 0xBB, 0xCC };
BinaryReader 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(), 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
UINT8 data[] = { 0x01, 0x02, 0x03, 0x04 };
BinaryReader 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, 0x04030201);
IAT_CHECK_EQ(reader.Cursor(), 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);
BinaryReader reader(data);
auto val = reader.Read<FLOAT32>();
IAT_CHECK(val.has_value());
IAT_CHECK_APPROX(*val, pi);
return TRUE;
}
// -------------------------------------------------------------------------
// 4. String Reading
// -------------------------------------------------------------------------
BOOL TestReadString()
{
const char* raw = "HelloIA";
BinaryReader reader(std::span<const UINT8>((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(), 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 };
BinaryReader 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(), 3);
return TRUE;
}
// -------------------------------------------------------------------------
// 6. Navigation (Seek, Skip, Remaining)
// -------------------------------------------------------------------------
BOOL TestNavigation()
{
UINT8 data[10] = { 0 }; // Zero init
BinaryReader reader(data);
IAT_CHECK_EQ(reader.Remaining(), 10);
// Skip
reader.Skip(5);
IAT_CHECK_EQ(reader.Cursor(), 5);
IAT_CHECK_EQ(reader.Remaining(), 5);
// Skip clamping
reader.Skip(100); // Should clamp to 10
IAT_CHECK_EQ(reader.Cursor(), 10);
IAT_CHECK(reader.IsEOF());
// Seek
reader.Seek(2);
IAT_CHECK_EQ(reader.Cursor(), 2);
IAT_CHECK_EQ(reader.Remaining(), 8);
IAT_CHECK_NOT(reader.IsEOF());
return TRUE;
}
// -------------------------------------------------------------------------
// 7. Error Handling (EOF Protection)
// -------------------------------------------------------------------------
BOOL TestBoundaryChecks()
{
UINT8 data[] = { 0x00, 0x00 };
BinaryReader 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 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<false, true> testRunner;
// Run the BinaryReader block
testRunner.testBlock<Core_BinaryReader>();
return 0;
}

222
Tests/Unit/BinaryWriter.cpp Normal file
View File

@ -0,0 +1,222 @@
// 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/>.
#include <IACore/BinaryWriter.hpp>
#include <IACore/IATest.hpp>
using namespace IACore;
// -----------------------------------------------------------------------------
// Test Block Definition
// -----------------------------------------------------------------------------
IAT_BEGIN_BLOCK(Core, BinaryWriter)
// -------------------------------------------------------------------------
// 1. Vector Mode (Dynamic Growth)
// -------------------------------------------------------------------------
BOOL TestVectorGrowth()
{
std::vector<UINT8> buffer;
// Start empty
BinaryWriter writer(buffer);
// Write 1 Byte
writer.Write<UINT8>(0xAA);
IAT_CHECK_EQ(buffer.size(), 1);
IAT_CHECK_EQ(buffer[0], 0xAA);
// Write 4 Bytes (UINT32)
// 0xDDCCBBAA -> Little Endian: AA BB CC DD
writer.Write<UINT32>(0xDDCCBBAA);
IAT_CHECK_EQ(buffer.size(), 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<UINT8> buffer = { 0x01, 0x02 };
BinaryWriter writer(buffer);
// Should append to end, not overwrite 0x01
writer.Write<UINT8>(0x03);
IAT_CHECK_EQ(buffer.size(), 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);
BinaryWriter writer(rawData); // Implicit conversion to span
// Write UINT8
writer.Write<UINT8>(0xFF);
IAT_CHECK_EQ(rawData[0], 0xFF);
// Write UINT16
writer.Write<UINT16>(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<UINT8> buffer;
BinaryWriter writer(buffer);
const char* msg = "IA";
writer.WriteBytes((PVOID)msg, 2);
writer.Write<UINT8>(0x00); // Null term
IAT_CHECK_EQ(buffer.size(), 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<UINT8> buffer;
BinaryWriter writer(buffer);
// Fill with placeholders
writer.Write<UINT32>(0xFFFFFFFF);
writer.Write<UINT32>(0xFFFFFFFF);
// Overwrite the first integer with 0x00000000
writer.WriteAt<UINT32>(0, 0);
// Overwrite the byte at index 4 (start of second int)
writer.WriteAt<UINT8>(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<UINT8> buffer;
BinaryWriter writer(buffer);
FLOAT32 val = 1.234f;
writer.Write<FLOAT32>(val);
IAT_CHECK_EQ(buffer.size(), 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<UINT8> buffer = { 0x10, 0x20, 0x30 };
BinaryWriter 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<false, true> testRunner;
// Run the BinaryReader block
testRunner.testBlock<Core_BinaryWriter>();
return 0;
}

39
Tests/Unit/CCompile.c Normal file
View 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;
}

62
Tests/Unit/CMakeLists.txt Normal file
View File

@ -0,0 +1,62 @@
# ------------------------------------------------
# C Compile Test
# ------------------------------------------------
enable_language(C)
add_executable(Test_Unit_CCompile "CCompile.c")
set_target_properties(Test_Unit_CCompile PROPERTIES
C_STANDARD 99
C_STANDARD_REQUIRED ON
LINKER_LANGUAGE C
)
target_link_libraries(Test_Unit_CCompile PRIVATE IACore)
# ------------------------------------------------
# Unit: BinaryReader
# ------------------------------------------------
add_executable(Test_Unit_BinaryReader "BinaryReader.cpp")
target_link_libraries(Test_Unit_BinaryReader PRIVATE IACore)
target_compile_options(Test_Unit_BinaryReader PRIVATE -fexceptions)
set_target_properties(Test_Unit_BinaryReader PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: BinaryWriter
# ------------------------------------------------
add_executable(Test_Unit_BinaryWriter "BinaryWriter.cpp")
target_link_libraries(Test_Unit_BinaryWriter PRIVATE IACore)
target_compile_options(Test_Unit_BinaryWriter PRIVATE -fexceptions)
set_target_properties(Test_Unit_BinaryWriter PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: Environment
# ------------------------------------------------
add_executable(Test_Unit_Environment "Environment.cpp")
target_link_libraries(Test_Unit_Environment PRIVATE IACore)
target_compile_options(Test_Unit_Environment PRIVATE -fexceptions)
set_target_properties(Test_Unit_Environment PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: File
# ------------------------------------------------
add_executable(Test_Unit_File "File.cpp")
target_link_libraries(Test_Unit_File PRIVATE IACore)
target_compile_options(Test_Unit_File PRIVATE -fexceptions)
set_target_properties(Test_Unit_File PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: Process
# ------------------------------------------------
add_executable(Test_Unit_Process "Process.cpp")
target_link_libraries(Test_Unit_Process PRIVATE IACore)
target_compile_options(Test_Unit_Process PRIVATE -fexceptions)
set_target_properties(Test_Unit_Process PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: Utils
# ------------------------------------------------
add_executable(Test_Unit_Utils "Utils.cpp")
target_link_libraries(Test_Unit_Utils PRIVATE IACore)
target_compile_options(Test_Unit_Utils PRIVATE -fexceptions)
set_target_properties(Test_Unit_Utils PROPERTIES USE_EXCEPTIONS ON)

190
Tests/Unit/Environment.cpp Normal file
View File

@ -0,0 +1,190 @@
// 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/>.
#include <IACore/Environment.hpp>
#include <IACore/IATest.hpp>
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";
// -----------------------------------------------------------------------------
// 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
// -------------------------------------------------------------------------
// 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, "");
// It should exist
IAT_CHECK(Environment::Exists(TEST_KEY));
// It should be retrievable as empty string
auto opt = Environment::Find(TEST_KEY);
IAT_CHECK(opt.has_value());
IAT_CHECK(opt->empty());
// 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()
int main(int argc, char* argv[])
{
UNUSED(argc);
UNUSED(argv);
// Define runner (StopOnFail=false, Verbose=true)
ia::iatest::runner<false, true> testRunner;
// Run the BinaryReader block
testRunner.testBlock<Core_Environment>();
return 0;
}

246
Tests/Unit/File.cpp Normal file
View File

@ -0,0 +1,246 @@
// 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/>.
#include <IACore/File.hpp>
#include <IACore/IATest.hpp>
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<true>(complexPath);
IAT_CHECK_EQ(nameWithExt, String("hero_diffuse.png"));
// Test ExtractFilename (No Extension / Stem)
String nameNoExt = File::ExtractFilename<false>(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<UINT8>& vec = *result;
IAT_CHECK_EQ(vec.size(), 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, 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, 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, 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, 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, (UINT32)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<false, true> testRunner;
// Run the BinaryReader block
testRunner.testBlock<Core_File>();
return 0;
}

247
Tests/Unit/Process.cpp Normal file
View File

@ -0,0 +1,247 @@
// 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/>.
#include <IACore/Process.hpp>
#include <IACore/IATest.hpp>
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"
#else
#define CMD_ECHO_EXE "/bin/echo"
#define CMD_ARG_PREFIX ""
#define NULL_DEVICE "/dev/null"
#endif
IAT_BEGIN_BLOCK(Core, Process)
// -------------------------------------------------------------------------
// 1. Basic Execution (Exit Code 0)
// -------------------------------------------------------------------------
BOOL TestBasicRun()
{
// Simple "echo hello"
String captured;
auto result = Process::Run(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
[&](StringView line) {
captured = line;
}
);
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);
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 = Process::Run(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 = Process::Run(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 = Process::Run("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()
{
// 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
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 = Process::Run(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(Process::Run(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<false, true> testRunner;
// Run the BinaryReader block
testRunner.testBlock<Core_Process>();
return 0;
}

227
Tests/Unit/Utils.cpp Normal file
View File

@ -0,0 +1,227 @@
// 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/>.
#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(), 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());
// Optional: IAT_CHECK_EQ(odd.error(), "Hex string must have even length");
// 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(), 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 }; // Slight change
// Instantiate the hasher manually to verify the struct specialization exists
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 it works with ComputeHash when passed as object
// (Assuming you extend ComputeHash to handle custom types via the specialized hasher)
// Since ComputeHash uses ankerl::unordered_dense::hash<T> internally, this should work:
UINT64 hWrapper = IACore::Utils::ComputeHash(v1);
IAT_CHECK_EQ(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<false, true> testRunner;
// Run the BinaryReader block
testRunner.testBlock<Core_Utils>();
return 0;
}