This commit is contained in:
@ -139,8 +139,7 @@ auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto AsyncOps::get_worker_count() -> WorkerId {
|
auto AsyncOps::get_worker_count() -> WorkerId {
|
||||||
// +1 for MainThread (Work Stealing)
|
return static_cast<WorkerId>(s_schedule_workers.size());
|
||||||
return static_cast<WorkerId>(s_schedule_workers.size() + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto AsyncOps::schedule_worker_loop(std::stop_token stop_token,
|
auto AsyncOps::schedule_worker_loop(std::stop_token stop_token,
|
||||||
|
|||||||
@ -15,16 +15,12 @@
|
|||||||
|
|
||||||
#include <IACore/CLI.hpp>
|
#include <IACore/CLI.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
CLIParser::CLIParser(Span<const String> args) : m_arg_list(args) {
|
||||||
CLIParser::CLIParser(Span<const String> args) : m_argList(args)
|
m_current_arg = m_arg_list.begin();
|
||||||
{
|
|
||||||
assert(args.size());
|
|
||||||
|
|
||||||
m_currentArg = m_argList.begin();
|
// Skip executable path
|
||||||
|
if (m_current_arg != m_arg_list.end())
|
||||||
// Skip executable path
|
m_current_arg++;
|
||||||
if (m_currentArg != m_argList.end())
|
}
|
||||||
m_currentArg++;
|
|
||||||
}
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -68,7 +68,7 @@ auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
|||||||
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
||||||
w_name.c_str());
|
w_name.c_str());
|
||||||
} else {
|
} else {
|
||||||
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, w_name.c_str());
|
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, false, w_name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (h_map == NULL) {
|
if (h_map == NULL) {
|
||||||
@ -201,7 +201,7 @@ auto FileOps::stream_to_file(const Path &path, bool overwrite)
|
|||||||
if (!overwrite && std::filesystem::exists(path)) {
|
if (!overwrite && std::filesystem::exists(path)) {
|
||||||
return fail("File already exists: {}", path.string());
|
return fail("File already exists: {}", path.string());
|
||||||
}
|
}
|
||||||
return StreamWriter::create(path);
|
return StreamWriter::create_from_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
|
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
|
||||||
|
|||||||
@ -133,7 +133,7 @@ void ProcessOps::terminate_process(const Box<ProcessHandle> &handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, false, pid);
|
||||||
if (h_process != NULL) {
|
if (h_process != NULL) {
|
||||||
::TerminateProcess(h_process, 9);
|
::TerminateProcess(h_process, 9);
|
||||||
CloseHandle(h_process);
|
CloseHandle(h_process);
|
||||||
@ -150,7 +150,7 @@ auto ProcessOps::spawn_process_windows(
|
|||||||
std::atomic<NativeProcessID> &id) -> Result<i32> {
|
std::atomic<NativeProcessID> &id) -> Result<i32> {
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
|
SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
|
||||||
TRUE}; // Allow inheritance
|
true}; // Allow inheritance
|
||||||
HANDLE h_read = NULL;
|
HANDLE h_read = NULL;
|
||||||
HANDLE h_write = NULL;
|
HANDLE h_write = NULL;
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ auto ProcessOps::spawn_process_windows(
|
|||||||
// Windows command line needs to be mutable and concatenated
|
// Windows command line needs to be mutable and concatenated
|
||||||
String command_line = std::format("\"{}\" {}", command, args);
|
String command_line = std::format("\"{}\" {}", command, args);
|
||||||
|
|
||||||
BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, TRUE, 0,
|
BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, true, 0,
|
||||||
NULL, NULL, &si, &pi);
|
NULL, NULL, &si, &pi);
|
||||||
|
|
||||||
// Close write end in parent, otherwise ReadFile never returns EOF!
|
// Close write end in parent, otherwise ReadFile never returns EOF!
|
||||||
|
|||||||
@ -40,6 +40,41 @@ StreamReader::StreamReader(Span<const u8> data)
|
|||||||
: m_data(data.data()), m_data_size(data.size()),
|
: m_data(data.data()), m_data_size(data.size()),
|
||||||
m_storage_type(StorageType::NonOwning) {}
|
m_storage_type(StorageType::NonOwning) {}
|
||||||
|
|
||||||
|
StreamReader::StreamReader(StreamReader &&other)
|
||||||
|
: m_data(other.m_data), m_cursor(other.m_cursor),
|
||||||
|
m_data_size(other.m_data_size),
|
||||||
|
m_owning_vector(std::move(other.m_owning_vector)),
|
||||||
|
m_storage_type(other.m_storage_type) {
|
||||||
|
other.m_storage_type = StorageType::NonOwning;
|
||||||
|
other.m_data = {};
|
||||||
|
other.m_data_size = 0;
|
||||||
|
|
||||||
|
if (m_storage_type == StorageType::OwningVector)
|
||||||
|
m_data = m_owning_vector.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto StreamReader::operator=(StreamReader &&other) -> StreamReader & {
|
||||||
|
if (this != &other) {
|
||||||
|
if (m_storage_type == StorageType::OwningMmap) {
|
||||||
|
FileOps::unmap_file(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_data = other.m_data;
|
||||||
|
m_cursor = other.m_cursor;
|
||||||
|
m_data_size = other.m_data_size;
|
||||||
|
m_owning_vector = std::move(other.m_owning_vector);
|
||||||
|
m_storage_type = other.m_storage_type;
|
||||||
|
|
||||||
|
if (m_storage_type == StorageType::OwningVector)
|
||||||
|
m_data = m_owning_vector.data();
|
||||||
|
|
||||||
|
other.m_storage_type = StorageType::NonOwning;
|
||||||
|
other.m_data = {};
|
||||||
|
other.m_data_size = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
StreamReader::~StreamReader() {
|
StreamReader::~StreamReader() {
|
||||||
if (m_storage_type == StorageType::OwningMmap) {
|
if (m_storage_type == StorageType::OwningMmap) {
|
||||||
FileOps::unmap_file(m_data);
|
FileOps::unmap_file(m_data);
|
||||||
|
|||||||
@ -17,9 +17,7 @@
|
|||||||
|
|
||||||
namespace IACore {
|
namespace IACore {
|
||||||
|
|
||||||
auto StreamWriter::create(const Path &path) -> Result<StreamWriter> {
|
auto StreamWriter::create_from_file(const Path &path) -> Result<StreamWriter> {
|
||||||
// Try to open the file to ensure we have write permissions and to truncate
|
|
||||||
// it.
|
|
||||||
FILE *f = std::fopen(path.string().c_str(), "wb");
|
FILE *f = std::fopen(path.string().c_str(), "wb");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
return fail("Failed to open file for writing: {}", path.string());
|
return fail("Failed to open file for writing: {}", path.string());
|
||||||
@ -43,34 +41,97 @@ StreamWriter::StreamWriter(Span<u8> data)
|
|||||||
: m_buffer(data.data()), m_cursor(0), m_capacity(data.size()),
|
: m_buffer(data.data()), m_cursor(0), m_capacity(data.size()),
|
||||||
m_storage_type(StorageType::NonOwning) {}
|
m_storage_type(StorageType::NonOwning) {}
|
||||||
|
|
||||||
StreamWriter::~StreamWriter() {
|
StreamWriter::StreamWriter(StreamWriter &&other)
|
||||||
if (m_storage_type == StorageType::OwningFile) {
|
: m_buffer(other.m_buffer), m_cursor(other.m_cursor),
|
||||||
if (m_file_path.empty()) {
|
m_capacity(other.m_capacity), m_file_path(other.m_file_path),
|
||||||
return;
|
m_owning_vector(std::move(other.m_owning_vector)),
|
||||||
|
m_storage_type(other.m_storage_type) {
|
||||||
|
other.m_capacity = {};
|
||||||
|
other.m_buffer = {};
|
||||||
|
other.m_storage_type = StorageType::NonOwning;
|
||||||
|
|
||||||
|
if (m_storage_type == StorageType::OwningVector)
|
||||||
|
m_buffer = m_owning_vector.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto StreamWriter::operator=(StreamWriter &&other) -> StreamWriter & {
|
||||||
|
if (this != &other) {
|
||||||
|
// Flush current if needed
|
||||||
|
if (m_storage_type == StorageType::OwningFile) {
|
||||||
|
if (auto res = flush_to_disk(); !res) {
|
||||||
|
std::fprintf(stderr, "[IACore] Data loss in StreamWriter move: %s\n",
|
||||||
|
res.error().c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
|
m_buffer = other.m_buffer;
|
||||||
if (f) {
|
m_cursor = other.m_cursor;
|
||||||
// Write the actual data accumulated in the buffer.
|
m_capacity = other.m_capacity;
|
||||||
std::fwrite(m_buffer, 1, m_cursor, f);
|
m_file_path = std::move(other.m_file_path); // Use move for string/path
|
||||||
std::fclose(f);
|
m_owning_vector = std::move(other.m_owning_vector);
|
||||||
} else {
|
m_storage_type = other.m_storage_type;
|
||||||
// Logger is disabled, print to stderr as a fallback for data loss
|
|
||||||
// warning.
|
if (m_storage_type == StorageType::OwningVector)
|
||||||
std::fprintf(stderr, "[IACore] StreamWriter failed to save file: %s\n",
|
m_buffer = m_owning_vector.data();
|
||||||
m_file_path.string().c_str());
|
|
||||||
|
other.m_capacity = 0;
|
||||||
|
other.m_cursor = 0;
|
||||||
|
other.m_buffer = nullptr;
|
||||||
|
other.m_storage_type = StorageType::NonOwning;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamWriter::~StreamWriter() {
|
||||||
|
if (m_storage_type == StorageType::OwningFile) {
|
||||||
|
// We can't return errors here, so we log them.
|
||||||
|
// Ideally, the user calls save() before this runs.
|
||||||
|
if (auto res = flush_to_disk(); !res) {
|
||||||
|
std::fprintf(stderr, "[IACore] LOST DATA in ~StreamWriter: %s\n",
|
||||||
|
res.error().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto StreamWriter::flush() -> Result<void> {
|
||||||
|
auto res = flush_to_disk();
|
||||||
|
// Prevent double-write in destructor if save was successful
|
||||||
|
if (res.has_value()) {
|
||||||
|
m_storage_type = StorageType::OwningVector; // downgrade to vector so dtor
|
||||||
|
// doesn't write again
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto StreamWriter::flush_to_disk() -> Result<void> {
|
||||||
|
if (m_storage_type != StorageType::OwningFile || m_file_path.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
|
||||||
|
if (!f) {
|
||||||
|
return fail("Failed to open file for writing: {}", m_file_path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
usize written = std::fwrite(m_buffer, 1, m_cursor, f);
|
||||||
|
std::fclose(f);
|
||||||
|
|
||||||
|
if (written != m_cursor) {
|
||||||
|
return fail("Incomplete write: {} of {} bytes written", written, m_cursor);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
auto StreamWriter::write(u8 byte, usize count) -> Result<void> {
|
auto StreamWriter::write(u8 byte, usize count) -> Result<void> {
|
||||||
if (m_cursor + count > m_capacity) {
|
if (m_cursor + count > m_capacity) {
|
||||||
if (m_storage_type == StorageType::NonOwning) {
|
if (m_storage_type == StorageType::NonOwning) {
|
||||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Growth strategy: Current capacity + (count * 2)
|
const usize required = m_cursor + count;
|
||||||
const usize new_capacity = m_capacity + (count << 1);
|
const usize double_cap = m_capacity * 2;
|
||||||
|
const usize new_capacity = (double_cap > required) ? double_cap : required;
|
||||||
|
|
||||||
m_owning_vector.resize(new_capacity);
|
m_owning_vector.resize(new_capacity);
|
||||||
m_capacity = m_owning_vector.size();
|
m_capacity = m_owning_vector.size();
|
||||||
m_buffer = m_owning_vector.data();
|
m_buffer = m_owning_vector.data();
|
||||||
@ -87,7 +148,12 @@ auto StreamWriter::write(const void *buffer, usize size) -> Result<void> {
|
|||||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||||
}
|
}
|
||||||
|
|
||||||
const usize new_capacity = m_capacity + (size << 1);
|
// NEW STRATEGY: Max(Double Capacity, Required Size)
|
||||||
|
// This prevents frequent reallocations for repeated small writes
|
||||||
|
const usize required = m_cursor + size;
|
||||||
|
const usize double_cap = m_capacity * 2;
|
||||||
|
const usize new_capacity = (double_cap > required) ? double_cap : required;
|
||||||
|
|
||||||
m_owning_vector.resize(new_capacity);
|
m_owning_vector.resize(new_capacity);
|
||||||
m_capacity = m_owning_vector.size();
|
m_capacity = m_owning_vector.size();
|
||||||
m_buffer = m_owning_vector.data();
|
m_buffer = m_owning_vector.data();
|
||||||
|
|||||||
@ -16,28 +16,53 @@
|
|||||||
#include <IACore/StringOps.hpp>
|
#include <IACore/StringOps.hpp>
|
||||||
|
|
||||||
namespace IACore {
|
namespace IACore {
|
||||||
const String BASE64_CHAR_TABLE =
|
|
||||||
|
static const String BASE64_CHAR_TABLE =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
static auto is_base64(u8 c) -> bool {
|
||||||
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||||
|
(c >= '0' && c <= '9') || (c == '+') || (c == '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto get_base64_index(u8 c) -> u8 {
|
||||||
|
if (c >= 'A' && c <= 'Z')
|
||||||
|
return c - 'A';
|
||||||
|
if (c >= 'a' && c <= 'z')
|
||||||
|
return c - 'a' + 26;
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return c - '0' + 52;
|
||||||
|
if (c == '+')
|
||||||
|
return 62;
|
||||||
|
if (c == '/')
|
||||||
|
return 63;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
auto StringOps::encode_base64(Span<const u8> data) -> String {
|
auto StringOps::encode_base64(Span<const u8> data) -> String {
|
||||||
String result;
|
String result;
|
||||||
result.reserve(((data.size() + 2) / 3) * 4);
|
result.reserve(((data.size() + 2) / 3) * 4);
|
||||||
for (size_t i = 0; i < data.size(); i += 3) {
|
|
||||||
uint32_t value = 0;
|
for (usize i = 0; i < data.size(); i += 3) {
|
||||||
i32 num_bytes = 0;
|
u32 b0 = data[i];
|
||||||
for (i32 j = 0; j < 3 && (i + j) < data.size(); ++j) {
|
u32 b1 = (i + 1 < data.size()) ? data[i + 1] : 0;
|
||||||
value = (value << 8) | data[i + j];
|
u32 b2 = (i + 2 < data.size()) ? data[i + 2] : 0;
|
||||||
num_bytes++;
|
|
||||||
|
u32 triple = (b0 << 16) | (b1 << 8) | b2;
|
||||||
|
|
||||||
|
result += BASE64_CHAR_TABLE[(triple >> 18) & 0x3F];
|
||||||
|
result += BASE64_CHAR_TABLE[(triple >> 12) & 0x3F];
|
||||||
|
|
||||||
|
if (i + 1 < data.size()) {
|
||||||
|
result += BASE64_CHAR_TABLE[(triple >> 6) & 0x3F];
|
||||||
|
} else {
|
||||||
|
result += '=';
|
||||||
}
|
}
|
||||||
for (i32 j = 0; j < num_bytes + 1; ++j) {
|
|
||||||
if (j < 4) {
|
if (i + 2 < data.size()) {
|
||||||
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
|
result += BASE64_CHAR_TABLE[triple & 0x3F];
|
||||||
}
|
} else {
|
||||||
}
|
result += '=';
|
||||||
if (num_bytes < 3) {
|
|
||||||
for (i32 j = 0; j < (3 - num_bytes); ++j) {
|
|
||||||
result += '=';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -45,47 +70,53 @@ auto StringOps::encode_base64(Span<const u8> data) -> String {
|
|||||||
|
|
||||||
auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
||||||
Vec<u8> result;
|
Vec<u8> result;
|
||||||
|
result.reserve(data.size() * 3 / 4);
|
||||||
|
|
||||||
const auto is_base64 = [](u8 c) {
|
i32 i = 0;
|
||||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
u8 tmp_buf[4];
|
||||||
};
|
|
||||||
|
|
||||||
i32 in_len = data.size();
|
for (char c_char : data) {
|
||||||
i32 i = 0, j = 0, in = 0;
|
u8 c = static_cast<u8>(c_char);
|
||||||
u8 tmp_buf0[4], tmp_buf1[3];
|
if (c == '=') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!is_base64(c)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
while (in_len-- && (data[in] != '=') && is_base64(data[in])) {
|
tmp_buf[i++] = c;
|
||||||
tmp_buf0[i++] = data[in];
|
|
||||||
in++;
|
|
||||||
if (i == 4) {
|
if (i == 4) {
|
||||||
for (i = 0; i < 4; i++)
|
u8 n0 = get_base64_index(tmp_buf[0]);
|
||||||
tmp_buf0[i] = BASE64_CHAR_TABLE.find(tmp_buf0[i]);
|
u8 n1 = get_base64_index(tmp_buf[1]);
|
||||||
|
u8 n2 = get_base64_index(tmp_buf[2]);
|
||||||
|
u8 n3 = get_base64_index(tmp_buf[3]);
|
||||||
|
|
||||||
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||||
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2));
|
||||||
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
result.push_back(((n2 & 0x03) << 6) | n3);
|
||||||
|
|
||||||
for (i = 0; (i < 3); i++)
|
|
||||||
result.push_back(tmp_buf1[i]);
|
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i) {
|
if (i > 0) {
|
||||||
for (j = i; j < 4; j++)
|
for (i32 j = i; j < 4; ++j) {
|
||||||
tmp_buf0[j] = 0;
|
tmp_buf[j] = 'A'; // Pad with 'A' (index 0)
|
||||||
|
}
|
||||||
|
|
||||||
for (j = 0; j < 4; j++)
|
u8 n0 = get_base64_index(tmp_buf[0]);
|
||||||
tmp_buf0[j] = BASE64_CHAR_TABLE.find(tmp_buf0[j]);
|
u8 n1 = get_base64_index(tmp_buf[1]);
|
||||||
|
u8 n2 = get_base64_index(tmp_buf[2]);
|
||||||
|
|
||||||
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
if (i > 1) {
|
||||||
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||||
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
}
|
||||||
|
if (i > 2) {
|
||||||
for (j = 0; (j < i - 1); j++)
|
result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2));
|
||||||
result.push_back(tmp_buf1[j]);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -93,7 +93,7 @@ private:
|
|||||||
// Implementation
|
// Implementation
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
auto RingBufferView::default_instance() -> RingBufferView {
|
inline auto RingBufferView::default_instance() -> RingBufferView {
|
||||||
return RingBufferView(nullptr, {}, false);
|
return RingBufferView(nullptr, {}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +262,7 @@ inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto RingBufferView::is_valid() const -> bool {
|
[[nodiscard]] inline auto RingBufferView::is_valid() const -> bool {
|
||||||
return m_control_block && m_data_ptr && m_capacity;
|
return m_control_block && m_data_ptr && m_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,54 +17,47 @@
|
|||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class CLIParser {
|
||||||
class CLIParser
|
/*
|
||||||
{
|
* PLEASE READ
|
||||||
/*
|
*
|
||||||
* PLEASE READ
|
* CLIParser is still very much in it's baby stages.
|
||||||
*
|
* Subject to heavy and frequent changes, use with
|
||||||
* CLIParser is still very much in it's baby stages.
|
* caution!
|
||||||
* Subject to heavy and frequent changes, use with
|
*/
|
||||||
* caution!
|
|
||||||
*/
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CLIParser(Span<const String> args);
|
CLIParser(Span<const String> args);
|
||||||
~CLIParser() = default;
|
~CLIParser() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto remaining() const -> bool
|
[[nodiscard]] auto remaining() const -> bool {
|
||||||
{
|
return m_current_arg < m_arg_list.end();
|
||||||
return m_currentArg < m_argList.end();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto peek() const -> StringView
|
[[nodiscard]] auto peek() const -> StringView {
|
||||||
{
|
if (!remaining())
|
||||||
if (!remaining())
|
return "";
|
||||||
return "";
|
return *m_current_arg;
|
||||||
return *m_currentArg;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto next() -> StringView
|
auto next() -> StringView {
|
||||||
{
|
if (!remaining())
|
||||||
if (!remaining())
|
return "";
|
||||||
return "";
|
return *m_current_arg++;
|
||||||
return *m_currentArg++;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto consume(const StringView &expected) -> bool
|
auto consume(const StringView &expected) -> bool {
|
||||||
{
|
if (peek() == expected) {
|
||||||
if (peek() == expected)
|
next();
|
||||||
{
|
return true;
|
||||||
next();
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Span<const String> m_argList;
|
const Span<const String> m_arg_list;
|
||||||
Span<const String>::const_iterator m_currentArg;
|
Span<const String>::const_iterator m_current_arg;
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -21,295 +21,271 @@
|
|||||||
// Macros
|
// Macros
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
#define __iat_micro_test(call) \
|
#define __iat_micro_test(call) \
|
||||||
if (!(call)) \
|
if (!(call)) \
|
||||||
return false
|
return false
|
||||||
|
|
||||||
#define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
#define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
||||||
#define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
#define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
||||||
#define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
#define IAT_CHECK_EQ(lhs, rhs) \
|
||||||
#define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
__iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||||
|
#define IAT_CHECK_NEQ(lhs, rhs) \
|
||||||
|
__iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||||
|
|
||||||
#define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
#define IAT_CHECK_APPROX(lhs, rhs) \
|
||||||
|
__iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||||
|
|
||||||
#define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
#define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||||
#define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
#define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
||||||
|
|
||||||
#define IAT_BLOCK(name) class name : public ia::iatest::Block
|
#define IAT_BLOCK(name) class name : public IACore::Test::Block
|
||||||
|
|
||||||
#define IAT_BEGIN_BLOCK(_group, _name) \
|
#define IAT_BEGIN_BLOCK(_group, _name) \
|
||||||
class _group##_##_name : public ia::iatest::Block \
|
class _group##_##_name : public IACore::Test::Block { \
|
||||||
{ \
|
public: \
|
||||||
public: \
|
[[nodiscard]] auto get_name() const -> const char * override { \
|
||||||
[[nodiscard]] auto get_name() const -> const char * override \
|
return #_group "::" #_name; \
|
||||||
{ \
|
} \
|
||||||
return #_group "::" #_name; \
|
\
|
||||||
} \
|
|
||||||
\
|
|
||||||
private:
|
|
||||||
|
|
||||||
#define IAT_END_BLOCK() \
|
|
||||||
} \
|
|
||||||
;
|
|
||||||
|
|
||||||
#define IAT_BEGIN_TEST_LIST() \
|
|
||||||
public: \
|
|
||||||
void declare_tests() override \
|
|
||||||
{
|
|
||||||
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
|
||||||
#define IAT_END_TEST_LIST() \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
namespace IACore
|
#define IAT_END_BLOCK() \
|
||||||
{
|
} \
|
||||||
// -------------------------------------------------------------------------
|
;
|
||||||
// String Conversion Helpers
|
|
||||||
// -------------------------------------------------------------------------
|
#define IAT_BEGIN_TEST_LIST() \
|
||||||
template<typename T> auto to_string(const T &value) -> String
|
public: \
|
||||||
{
|
void declare_tests() override {
|
||||||
if constexpr (std::is_arithmetic_v<T>)
|
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||||
{
|
#define IAT_END_TEST_LIST() \
|
||||||
return std::to_string(value);
|
} \
|
||||||
}
|
\
|
||||||
else if constexpr (std::is_same_v<T, String> || std::is_same_v<T, const char *> || std::is_same_v<T, char *>)
|
private:
|
||||||
{
|
|
||||||
return String("\"") + String(value) + "\"";
|
namespace IACore::Test {
|
||||||
}
|
// -------------------------------------------------------------------------
|
||||||
else
|
// String Conversion Helpers
|
||||||
{
|
// -------------------------------------------------------------------------
|
||||||
return "{Object}";
|
template <typename T> auto to_string(const T &value) -> String {
|
||||||
}
|
if constexpr (std::is_arithmetic_v<T>) {
|
||||||
|
return std::to_string(value);
|
||||||
|
} else if constexpr (std::is_same_v<T, String> ||
|
||||||
|
std::is_same_v<T, const char *> ||
|
||||||
|
std::is_same_v<T, char *>) {
|
||||||
|
return String("\"") + String(value) + "\"";
|
||||||
|
} else {
|
||||||
|
return "{Object}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> auto to_string(T *value) -> String {
|
||||||
|
if (value == nullptr) {
|
||||||
|
return "nullptr";
|
||||||
|
}
|
||||||
|
return std::format("ptr({})", static_cast<const void *>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Types
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
using TestFunctor = std::function<bool()>;
|
||||||
|
|
||||||
|
struct TestUnit {
|
||||||
|
String name;
|
||||||
|
TestFunctor functor;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Block {
|
||||||
|
public:
|
||||||
|
virtual ~Block() = default;
|
||||||
|
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
||||||
|
virtual void declare_tests() = 0;
|
||||||
|
|
||||||
|
auto units() -> Vec<TestUnit> & { return m_units; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool {
|
||||||
|
if (lhs != rhs) {
|
||||||
|
print_fail(description, to_string(lhs), to_string(rhs));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description)
|
||||||
|
-> bool {
|
||||||
|
if (lhs == rhs) {
|
||||||
|
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
auto _test_approx(T lhs, T rhs, const char *description) -> bool {
|
||||||
|
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)) {
|
||||||
|
print_fail(description, to_string(lhs), to_string(rhs));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto _test(bool value, const char *description) -> bool {
|
||||||
|
if (!value) {
|
||||||
|
std::cout << console::BLUE << " " << description << "... "
|
||||||
|
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto _test_not(bool value, const char *description) -> bool {
|
||||||
|
if (value) {
|
||||||
|
std::cout << console::BLUE << " " << description << "... "
|
||||||
|
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _test_unit(TestFunctor functor, const char *name) {
|
||||||
|
m_units.push_back({name, std::move(functor)});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void print_fail(const char *desc, const String &v1, const String &v2) {
|
||||||
|
std::cout << console::BLUE << " " << desc << "... " << console::RED
|
||||||
|
<< "FAILED" << console::RESET << "\n";
|
||||||
|
std::cout << console::RED << " Expected: " << v2 << console::RESET
|
||||||
|
<< "\n";
|
||||||
|
std::cout << console::RED << " Actual: " << v1 << console::RESET
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec<TestUnit> m_units;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept ValidBlockClass = std::derived_from<T, Block>;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Runner
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
template <bool StopOnFail = false, bool IsVerbose = false> class Runner {
|
||||||
|
public:
|
||||||
|
Runner() = default;
|
||||||
|
|
||||||
|
~Runner() { summarize(); }
|
||||||
|
|
||||||
|
template <typename BlockClass>
|
||||||
|
requires ValidBlockClass<BlockClass>
|
||||||
|
void test_block();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void summarize();
|
||||||
|
|
||||||
|
usize m_test_count{0};
|
||||||
|
usize m_fail_count{0};
|
||||||
|
usize m_block_count{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <bool StopOnFail, bool IsVerbose>
|
||||||
|
template <typename BlockClass>
|
||||||
|
requires ValidBlockClass<BlockClass>
|
||||||
|
void Runner<StopOnFail, IsVerbose>::test_block() {
|
||||||
|
m_block_count++;
|
||||||
|
BlockClass b;
|
||||||
|
b.declare_tests();
|
||||||
|
|
||||||
|
std::cout << console::MAGENTA << "Testing [" << b.get_name() << "]..."
|
||||||
|
<< console::RESET << "\n";
|
||||||
|
|
||||||
|
for (auto &v : b.units()) {
|
||||||
|
m_test_count++;
|
||||||
|
if constexpr (IsVerbose) {
|
||||||
|
std::cout << console::YELLOW << " Testing " << v.name << "...\n"
|
||||||
|
<< console::RESET;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> auto to_string(T *value) -> String
|
// Exceptions are DISABLED. We assume tests do not crash.
|
||||||
{
|
// If a test crashes (segfault), the OS handles it.
|
||||||
if (value == nullptr)
|
bool result = v.functor();
|
||||||
{
|
|
||||||
return "nullptr";
|
if (!result) {
|
||||||
}
|
m_fail_count++;
|
||||||
return std::format("ptr({})", static_cast<const void *>(value));
|
if constexpr (StopOnFail) {
|
||||||
|
summarize();
|
||||||
|
std::exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool StopOnFail, bool IsVerbose>
|
||||||
|
void Runner<StopOnFail, IsVerbose>::summarize() {
|
||||||
|
std::cout << console::GREEN
|
||||||
|
<< "\n-----------------------------------\n\t "
|
||||||
|
"SUMMARY\n-----------------------------------\n";
|
||||||
|
|
||||||
|
if (m_fail_count == 0) {
|
||||||
|
std::cout << "\n\tALL TESTS PASSED!\n\n";
|
||||||
|
} else {
|
||||||
|
f64 success_rate = (100.0 * static_cast<f64>(m_test_count - m_fail_count) /
|
||||||
|
static_cast<f64>(m_test_count));
|
||||||
|
std::cout << console::RED << m_fail_count << " OF " << m_test_count
|
||||||
|
<< " TESTS FAILED\n"
|
||||||
|
<< console::YELLOW
|
||||||
|
<< std::format("Success Rate: {:.2f}%\n", success_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << console::MAGENTA << "Ran " << m_test_count << " test(s) across "
|
||||||
|
<< m_block_count << " block(s)\n"
|
||||||
|
<< console::GREEN << "-----------------------------------"
|
||||||
|
<< console::RESET << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
using DefaultRunner = Runner<false, true>;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registry
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
class TestRegistry {
|
||||||
|
public:
|
||||||
|
using TestEntry = std::function<void(DefaultRunner &)>;
|
||||||
|
|
||||||
|
static auto get_entries() -> Vec<TestEntry> & {
|
||||||
|
static Vec<TestEntry> entries;
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto run_all() -> i32 {
|
||||||
|
DefaultRunner r;
|
||||||
|
auto &entries = get_entries();
|
||||||
|
std::cout << console::CYAN << "[IATest] Discovered " << entries.size()
|
||||||
|
<< " Test Blocks\n\n"
|
||||||
|
<< console::RESET;
|
||||||
|
|
||||||
|
for (auto &entry : entries) {
|
||||||
|
entry(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
return 0;
|
||||||
// Types
|
}
|
||||||
// -------------------------------------------------------------------------
|
};
|
||||||
using TestFunctor = std::function<bool()>;
|
|
||||||
|
|
||||||
struct TestUnit
|
template <typename BlockType> struct AutoRegister {
|
||||||
{
|
AutoRegister() {
|
||||||
String name;
|
TestRegistry::get_entries().push_back(
|
||||||
TestFunctor functor;
|
[](DefaultRunner &r) { r.test_block<BlockType>(); });
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
} // namespace IACore::Test
|
||||||
|
|
||||||
class Block
|
#define IAT_REGISTER_ENTRY(Group, Name) \
|
||||||
{
|
static IACore::Test::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||||
public:
|
|
||||||
virtual ~Block() = default;
|
|
||||||
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
|
||||||
virtual void declare_tests() = 0;
|
|
||||||
|
|
||||||
auto units() -> Vec<TestUnit> &
|
|
||||||
{
|
|
||||||
return m_units;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
template<typename T1, typename T2> auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
|
||||||
{
|
|
||||||
if (lhs != rhs)
|
|
||||||
{
|
|
||||||
print_fail(description, to_string(lhs), to_string(rhs));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T1, typename T2> auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
|
||||||
{
|
|
||||||
if (lhs == rhs)
|
|
||||||
{
|
|
||||||
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> auto _test_approx(T lhs, T rhs, const char *description) -> bool
|
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
print_fail(description, to_string(lhs), to_string(rhs));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto _test(bool value, const char *description) -> bool
|
|
||||||
{
|
|
||||||
if (!value)
|
|
||||||
{
|
|
||||||
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
|
||||||
<< console::reset << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto _test_not(bool value, const char *description) -> bool
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
|
||||||
<< console::reset << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _test_unit(TestFunctor functor, const char *name)
|
|
||||||
{
|
|
||||||
m_units.push_back({name, std::move(functor)});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void print_fail(const char *desc, const String &v1, const String &v2)
|
|
||||||
{
|
|
||||||
std::cout << console::blue << " " << desc << "... " << console::red << "FAILED" << console::reset
|
|
||||||
<< "\n";
|
|
||||||
std::cout << console::red << " Expected: " << v2 << console::reset << "\n";
|
|
||||||
std::cout << console::red << " Actual: " << v1 << console::reset << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec<TestUnit> m_units;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
concept ValidBlockClass = std::derived_from<T, Block>;
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Runner
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
template<bool StopOnFail = false, bool IsVerbose = false> class Runner
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Runner() = default;
|
|
||||||
|
|
||||||
~Runner()
|
|
||||||
{
|
|
||||||
summarize();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename BlockClass>
|
|
||||||
requires ValidBlockClass<BlockClass>
|
|
||||||
void test_block();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void summarize();
|
|
||||||
|
|
||||||
usize m_test_count{0};
|
|
||||||
usize m_fail_count{0};
|
|
||||||
usize m_block_count{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
template<bool StopOnFail, bool IsVerbose>
|
|
||||||
template<typename BlockClass>
|
|
||||||
requires ValidBlockClass<BlockClass>
|
|
||||||
void Runner<StopOnFail, IsVerbose>::test_block()
|
|
||||||
{
|
|
||||||
m_block_count++;
|
|
||||||
BlockClass b;
|
|
||||||
b.declare_tests();
|
|
||||||
|
|
||||||
std::cout << console::magenta << "Testing [" << b.get_name() << "]..." << console::reset << "\n";
|
|
||||||
|
|
||||||
for (auto &v : b.units())
|
|
||||||
{
|
|
||||||
m_test_count++;
|
|
||||||
if constexpr (IsVerbose)
|
|
||||||
{
|
|
||||||
std::cout << console::yellow << " Testing " << v.name << "...\n" << console::reset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exceptions are DISABLED. We assume tests do not crash.
|
|
||||||
// If a test crashes (segfault), the OS handles it.
|
|
||||||
bool result = v.functor();
|
|
||||||
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
m_fail_count++;
|
|
||||||
if constexpr (StopOnFail)
|
|
||||||
{
|
|
||||||
summarize();
|
|
||||||
std::exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::cout << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<bool StopOnFail, bool IsVerbose> void Runner<StopOnFail, IsVerbose>::summarize()
|
|
||||||
{
|
|
||||||
std::cout << console::green
|
|
||||||
<< "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n";
|
|
||||||
|
|
||||||
if (m_fail_count == 0)
|
|
||||||
{
|
|
||||||
std::cout << "\n\tALL TESTS PASSED!\n\n";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
f64 success_rate = (100.0 * static_cast<f64>(m_test_count - m_fail_count) / static_cast<f64>(m_test_count));
|
|
||||||
std::cout << console::red << m_fail_count << " OF " << m_test_count << " TESTS FAILED\n"
|
|
||||||
<< console::yellow << std::format("Success Rate: {:.2f}%\n", success_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << console::magenta << "Ran " << m_test_count << " test(s) across " << m_block_count << " block(s)\n"
|
|
||||||
<< console::green << "-----------------------------------" << console::reset << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
using DefaultRunner = Runner<false, true>;
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Registry
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
class TestRegistry
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using TestEntry = std::function<void(DefaultRunner &)>;
|
|
||||||
|
|
||||||
static auto get_entries() -> Vec<TestEntry> &
|
|
||||||
{
|
|
||||||
static Vec<TestEntry> entries;
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto run_all() -> i32
|
|
||||||
{
|
|
||||||
DefaultRunner r;
|
|
||||||
auto &entries = get_entries();
|
|
||||||
std::cout << console::cyan << "[IATest] Discovered " << entries.size() << " Test Blocks\n\n"
|
|
||||||
<< console::reset;
|
|
||||||
|
|
||||||
for (auto &entry : entries)
|
|
||||||
{
|
|
||||||
entry(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename BlockType> struct AutoRegister
|
|
||||||
{
|
|
||||||
AutoRegister()
|
|
||||||
{
|
|
||||||
TestRegistry::get_entries().push_back([](DefaultRunner &r) { r.test_block<BlockType>(); });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
#define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
|
||||||
@ -119,8 +119,8 @@ inline auto Json::encode(const nlohmann::json &data) -> String {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
inline auto Json::parse_to_struct(const String &json_str) -> Result<T> {
|
inline auto Json::parse_to_struct(const String &json_str) -> Result<T> {
|
||||||
T result{};
|
T result{};
|
||||||
// glz::read_json returns an error code (bool-like optional)
|
|
||||||
const auto err = glz::read_json<GLAZE_OPTS>(result, json_str);
|
const auto err = glz::read<GLAZE_OPTS>(result, json_str);
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return fail("JSON Struct Parse Error: {}",
|
return fail("JSON Struct Parse Error: {}",
|
||||||
|
|||||||
@ -34,8 +34,8 @@ public:
|
|||||||
explicit StreamReader(Span<const u8> data);
|
explicit StreamReader(Span<const u8> data);
|
||||||
~StreamReader();
|
~StreamReader();
|
||||||
|
|
||||||
StreamReader(StreamReader &&) = default;
|
StreamReader(StreamReader &&other);
|
||||||
auto operator=(StreamReader &&) -> StreamReader & = default;
|
auto operator=(StreamReader &&other) -> StreamReader &;
|
||||||
|
|
||||||
StreamReader(const StreamReader &) = delete;
|
StreamReader(const StreamReader &) = delete;
|
||||||
auto operator=(const StreamReader &) -> StreamReader & = delete;
|
auto operator=(const StreamReader &) -> StreamReader & = delete;
|
||||||
@ -86,6 +86,9 @@ inline auto StreamReader::read(void *buffer, usize size) -> Result<void> {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
[[nodiscard("Check for EOF")]]
|
[[nodiscard("Check for EOF")]]
|
||||||
inline auto StreamReader::read() -> Result<T> {
|
inline auto StreamReader::read() -> Result<T> {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>,
|
||||||
|
"T must be trivially copyable to read via memcpy");
|
||||||
|
|
||||||
constexpr usize SIZE = sizeof(T);
|
constexpr usize SIZE = sizeof(T);
|
||||||
|
|
||||||
if (m_cursor + SIZE > m_data_size) [[unlikely]] {
|
if (m_cursor + SIZE > m_data_size) [[unlikely]] {
|
||||||
|
|||||||
@ -27,18 +27,19 @@ public:
|
|||||||
OwningVector,
|
OwningVector,
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto create(const Path &path) -> Result<StreamWriter>;
|
static auto create_from_file(const Path &path) -> Result<StreamWriter>;
|
||||||
|
|
||||||
StreamWriter();
|
StreamWriter();
|
||||||
explicit StreamWriter(Span<u8> data);
|
explicit StreamWriter(Span<u8> data);
|
||||||
~StreamWriter();
|
|
||||||
|
|
||||||
StreamWriter(StreamWriter &&) = default;
|
StreamWriter(StreamWriter &&other);
|
||||||
auto operator=(StreamWriter &&) -> StreamWriter & = default;
|
auto operator=(StreamWriter &&other) -> StreamWriter &;
|
||||||
|
|
||||||
StreamWriter(const StreamWriter &) = delete;
|
StreamWriter(const StreamWriter &) = delete;
|
||||||
auto operator=(const StreamWriter &) -> StreamWriter & = delete;
|
auto operator=(const StreamWriter &) -> StreamWriter & = delete;
|
||||||
|
|
||||||
|
~StreamWriter();
|
||||||
|
|
||||||
auto write(u8 byte, usize count) -> Result<void>;
|
auto write(u8 byte, usize count) -> Result<void>;
|
||||||
auto write(const void *buffer, usize size) -> Result<void>;
|
auto write(const void *buffer, usize size) -> Result<void>;
|
||||||
|
|
||||||
@ -48,6 +49,8 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
||||||
|
|
||||||
|
auto flush() -> Result<void>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
u8 *m_buffer = nullptr;
|
u8 *m_buffer = nullptr;
|
||||||
usize m_cursor = 0;
|
usize m_cursor = 0;
|
||||||
@ -55,6 +58,9 @@ private:
|
|||||||
Path m_file_path;
|
Path m_file_path;
|
||||||
Vec<u8> m_owning_vector;
|
Vec<u8> m_owning_vector;
|
||||||
StorageType m_storage_type = StorageType::OwningVector;
|
StorageType m_storage_type = StorageType::OwningVector;
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto flush_to_disk() -> Result<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
2770
Src/IACore/inc/full_repo_context.txt
Normal file
2770
Src/IACore/inc/full_repo_context.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
#add_subdirectory(Subjects/)
|
add_subdirectory(Subjects/)
|
||||||
#add_subdirectory(Unit/)
|
add_subdirectory(Unit/)
|
||||||
#add_subdirectory(Regression/)
|
add_subdirectory(Regression/)
|
||||||
|
|||||||
206
Tests/Unit/AsyncOps.cpp
Normal file
206
Tests/Unit/AsyncOps.cpp
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/AsyncOps.hpp>
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Helper: RAII Scheduler Guard
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Ensures the scheduler is terminated at the end of a test scope,
|
||||||
|
// preventing state pollution between tests.
|
||||||
|
struct SchedulerGuard {
|
||||||
|
SchedulerGuard(u8 worker_count = 2) {
|
||||||
|
// We ignore the result here as we assume the test environment is clean.
|
||||||
|
// Specific initialization tests will check the result manually.
|
||||||
|
(void)AsyncOps::initialize_scheduler(worker_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SchedulerGuard() { AsyncOps::terminate_scheduler(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Test Block Definition
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, AsyncOps)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Initialization and Termination
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_initialization() -> bool {
|
||||||
|
// Ensure clean state
|
||||||
|
AsyncOps::terminate_scheduler();
|
||||||
|
|
||||||
|
// Initialize with specific worker countB
|
||||||
|
const auto res = AsyncOps::initialize_scheduler(4);
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(AsyncOps::get_worker_count(), static_cast<u16>(4));
|
||||||
|
|
||||||
|
// Terminate
|
||||||
|
AsyncOps::terminate_scheduler();
|
||||||
|
|
||||||
|
// Verify we can re-initialize
|
||||||
|
const auto res2 = AsyncOps::initialize_scheduler(1);
|
||||||
|
IAT_CHECK(res2.has_value());
|
||||||
|
IAT_CHECK_EQ(AsyncOps::get_worker_count(), static_cast<u16>(1));
|
||||||
|
|
||||||
|
AsyncOps::terminate_scheduler();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Basic Task Execution
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_basic_execution() -> bool {
|
||||||
|
SchedulerGuard guard(2);
|
||||||
|
|
||||||
|
AsyncOps::Schedule schedule;
|
||||||
|
std::atomic<i32> run_count{0};
|
||||||
|
|
||||||
|
// Schedule a simple task
|
||||||
|
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { run_count++; }, 0,
|
||||||
|
&schedule);
|
||||||
|
|
||||||
|
// Wait for it to finish
|
||||||
|
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(run_count.load(), 1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Concurrency and Load
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_concurrency() -> bool {
|
||||||
|
SchedulerGuard guard(4);
|
||||||
|
|
||||||
|
AsyncOps::Schedule schedule;
|
||||||
|
std::atomic<i32> run_count{0};
|
||||||
|
const i32 total_tasks = 100;
|
||||||
|
|
||||||
|
for (i32 i = 0; i < total_tasks; ++i) {
|
||||||
|
AsyncOps::schedule_task(
|
||||||
|
[&](AsyncOps::WorkerId) {
|
||||||
|
// Simulate a tiny bit of work to encourage thread switching
|
||||||
|
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||||
|
run_count++;
|
||||||
|
},
|
||||||
|
0, &schedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(run_count.load(), total_tasks);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. Priorities
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_priorities() -> bool {
|
||||||
|
SchedulerGuard guard(2);
|
||||||
|
AsyncOps::Schedule schedule;
|
||||||
|
std::atomic<i32> high_priority_ran{0};
|
||||||
|
std::atomic<i32> normal_priority_ran{0};
|
||||||
|
|
||||||
|
// Verify API accepts priorities and executes them.
|
||||||
|
// Note: Deterministic priority testing is difficult in unit tests without
|
||||||
|
// mocking, so we primarily ensure the API functions correctly.
|
||||||
|
|
||||||
|
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { high_priority_ran++; }, 0,
|
||||||
|
&schedule, AsyncOps::Priority::High);
|
||||||
|
|
||||||
|
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { normal_priority_ran++; }, 0,
|
||||||
|
&schedule, AsyncOps::Priority::Normal);
|
||||||
|
|
||||||
|
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(high_priority_ran.load(), 1);
|
||||||
|
IAT_CHECK_EQ(normal_priority_ran.load(), 1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 5. Fire-and-Forget Tasks
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_run_task_fire_and_forget() -> bool {
|
||||||
|
SchedulerGuard guard(2);
|
||||||
|
|
||||||
|
std::atomic<bool> executed{false};
|
||||||
|
|
||||||
|
// run_task doesn't use a Schedule object, so we must sync manually
|
||||||
|
AsyncOps::run_task([&]() { executed = true; });
|
||||||
|
|
||||||
|
// Busy wait with timeout (max 1 second)
|
||||||
|
for (int i = 0; i < 100; ++i) {
|
||||||
|
if (executed.load())
|
||||||
|
break;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
IAT_CHECK(executed.load());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 6. Cancellation Safety
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_cancellation_safety() -> bool {
|
||||||
|
SchedulerGuard guard(2);
|
||||||
|
|
||||||
|
// 1. Cancel non-existent tag (Should be safe)
|
||||||
|
AsyncOps::cancel_tasks_of_tag(999);
|
||||||
|
|
||||||
|
AsyncOps::Schedule schedule;
|
||||||
|
std::atomic<i32> counter{0};
|
||||||
|
|
||||||
|
// 2. Schedule a task, wait for it
|
||||||
|
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { counter++; }, 10,
|
||||||
|
&schedule);
|
||||||
|
|
||||||
|
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||||
|
IAT_CHECK_EQ(counter.load(), 1);
|
||||||
|
|
||||||
|
// 3. Cancel tag 10 (already done, should be safe)
|
||||||
|
AsyncOps::cancel_tasks_of_tag(10);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_initialization);
|
||||||
|
IAT_ADD_TEST(test_basic_execution);
|
||||||
|
IAT_ADD_TEST(test_concurrency);
|
||||||
|
IAT_ADD_TEST(test_priorities);
|
||||||
|
IAT_ADD_TEST(test_run_task_fire_and_forget);
|
||||||
|
IAT_ADD_TEST(test_cancellation_safety);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, AsyncOps)
|
||||||
@ -1,40 +0,0 @@
|
|||||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
|
||||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <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;
|
|
||||||
}
|
|
||||||
134
Tests/Unit/CLI.cpp
Normal file
134
Tests/Unit/CLI.cpp
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/CLI.hpp>
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Test Block Definition
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, CLI)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Basic Traversal
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_basic_traversal() -> bool {
|
||||||
|
const Vec<String> args = {"ignored", "one", "two", "three"};
|
||||||
|
CLIParser parser(args);
|
||||||
|
|
||||||
|
IAT_CHECK(parser.remaining());
|
||||||
|
|
||||||
|
// Check sequential access
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "one");
|
||||||
|
IAT_CHECK(parser.remaining());
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "two");
|
||||||
|
IAT_CHECK(parser.remaining());
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "three");
|
||||||
|
|
||||||
|
// Should be exhausted now
|
||||||
|
IAT_CHECK_NOT(parser.remaining());
|
||||||
|
|
||||||
|
// Next on exhausted returns empty string view
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Peek Functionality
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_peek() -> bool {
|
||||||
|
const Vec<String> args = {"ignored", "peek_val", "next_val"};
|
||||||
|
CLIParser parser(args);
|
||||||
|
|
||||||
|
// Peek should return value but not advance
|
||||||
|
IAT_CHECK_EQ(String(parser.peek()), "peek_val");
|
||||||
|
IAT_CHECK(parser.remaining());
|
||||||
|
|
||||||
|
// Verify we are still at the same element
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "peek_val");
|
||||||
|
|
||||||
|
// Now we should be at next_val
|
||||||
|
IAT_CHECK_EQ(String(parser.peek()), "next_val");
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "next_val");
|
||||||
|
|
||||||
|
IAT_CHECK_NOT(parser.remaining());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Consume Functionality
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_consume() -> bool {
|
||||||
|
const Vec<String> args = {"ignored", "-v", "--output", "file.txt"};
|
||||||
|
CLIParser parser(args);
|
||||||
|
|
||||||
|
// 1. Try consuming something that isn't there
|
||||||
|
IAT_CHECK_NOT(parser.consume("-x"));
|
||||||
|
|
||||||
|
// Verify we haven't moved
|
||||||
|
IAT_CHECK_EQ(String(parser.peek()), "-v");
|
||||||
|
|
||||||
|
// 2. Consume the correct flag
|
||||||
|
IAT_CHECK(parser.consume("-v"));
|
||||||
|
|
||||||
|
// 3. Verify advancement
|
||||||
|
IAT_CHECK_EQ(String(parser.peek()), "--output");
|
||||||
|
|
||||||
|
// 4. Consume next
|
||||||
|
IAT_CHECK(parser.consume("--output"));
|
||||||
|
|
||||||
|
// 5. Verify final argument
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "file.txt");
|
||||||
|
|
||||||
|
IAT_CHECK_NOT(parser.remaining());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. Empty Argument List
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_empty() -> bool {
|
||||||
|
const Vec<String> args = {};
|
||||||
|
CLIParser parser(args);
|
||||||
|
|
||||||
|
IAT_CHECK_NOT(parser.remaining());
|
||||||
|
IAT_CHECK_EQ(String(parser.peek()), "");
|
||||||
|
IAT_CHECK_EQ(String(parser.next()), "");
|
||||||
|
IAT_CHECK_NOT(parser.consume("-help"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_basic_traversal);
|
||||||
|
IAT_ADD_TEST(test_peek);
|
||||||
|
IAT_ADD_TEST(test_consume);
|
||||||
|
IAT_ADD_TEST(test_empty);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, CLI)
|
||||||
@ -1,25 +1,25 @@
|
|||||||
# ------------------------------------------------
|
|
||||||
# C Compile Test (Keep separate to verify C ABI)
|
|
||||||
# ------------------------------------------------
|
|
||||||
add_executable(IACore_Test_C_ABI "CCompile.c")
|
|
||||||
set_target_properties(IACore_Test_C_ABI PROPERTIES
|
|
||||||
C_STANDARD 99
|
|
||||||
C_STANDARD_REQUIRED ON
|
|
||||||
LINKER_LANGUAGE C
|
|
||||||
)
|
|
||||||
target_link_libraries(IACore_Test_C_ABI PRIVATE IACore)
|
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# The Unified C++ Test Suite
|
# The Unified C++ Test Suite
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
set(TEST_SOURCES
|
set(TEST_SOURCES
|
||||||
Main.cpp
|
AsyncOps.cpp
|
||||||
Utils.cpp
|
CLI.cpp
|
||||||
Environment.cpp
|
|
||||||
DataOps.cpp
|
DataOps.cpp
|
||||||
|
Environment.cpp
|
||||||
|
FileOps.cpp
|
||||||
|
IPC.cpp
|
||||||
|
JSON.cpp
|
||||||
|
Logger.cpp
|
||||||
|
Main.cpp
|
||||||
|
Platform.cpp
|
||||||
ProcessOps.cpp
|
ProcessOps.cpp
|
||||||
StreamReader.cpp
|
|
||||||
RingBuffer.cpp
|
RingBuffer.cpp
|
||||||
|
SocketOps.cpp
|
||||||
|
StreamReader.cpp
|
||||||
|
StreamWriter.cpp
|
||||||
|
StringOps.cpp
|
||||||
|
Utils.cpp
|
||||||
|
XML.cpp
|
||||||
|
|
||||||
SIMD/IntVec4.cpp
|
SIMD/IntVec4.cpp
|
||||||
SIMD/FloatVec4.cpp
|
SIMD/FloatVec4.cpp
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/DataOps.hpp>
|
#include <IACore/DataOps.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
using namespace IACore;
|
using namespace IACore;
|
||||||
@ -25,87 +24,90 @@ using namespace IACore;
|
|||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, DataOps)
|
IAT_BEGIN_BLOCK(Core, DataOps)
|
||||||
|
|
||||||
bool TestCRC32() {
|
auto test_crc32() -> bool {
|
||||||
{
|
{
|
||||||
String s = "123456789";
|
const String s = "123456789";
|
||||||
Span<const u8> span(reinterpret_cast<const u8 *>(s.data()), s.size());
|
const Span<const u8> span(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||||
u32 result = DataOps::CRC32(span);
|
const u32 result = DataOps::crc32(span);
|
||||||
|
|
||||||
IAT_CHECK_EQ(result, 0xE3069283);
|
IAT_CHECK_EQ(result, 0xE3069283);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
u32 result = DataOps::CRC32({});
|
const u32 result = DataOps::crc32({});
|
||||||
IAT_CHECK_EQ(result, 0U);
|
IAT_CHECK_EQ(result, 0U);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::vector<u8> buffer(33);
|
Vec<u8> buffer(33);
|
||||||
for (size_t i = 1; i < 33; ++i)
|
for (usize i = 1; i < 33; ++i) {
|
||||||
buffer[i] = (u8)i;
|
buffer[i] = static_cast<u8>(i);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<u8> refData(32);
|
Vec<u8> ref_data(32);
|
||||||
for (size_t i = 0; i < 32; ++i)
|
for (usize i = 0; i < 32; ++i) {
|
||||||
refData[i] = (u8)(i + 1);
|
ref_data[i] = static_cast<u8>(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
u32 hashRef =
|
const u32 hash_ref =
|
||||||
DataOps::CRC32(Span<const u8>(refData.data(), refData.size()));
|
DataOps::crc32(Span<const u8>(ref_data.data(), ref_data.size()));
|
||||||
|
|
||||||
u32 hashUnaligned = DataOps::CRC32(Span<const u8>(buffer.data() + 1, 32));
|
const u32 hash_unaligned =
|
||||||
|
DataOps::crc32(Span<const u8>(buffer.data() + 1, 32));
|
||||||
|
|
||||||
IAT_CHECK_EQ(hashRef, hashUnaligned);
|
IAT_CHECK_EQ(hash_ref, hash_unaligned);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestHash_xxHash() {
|
auto test_hash_xxhash() -> bool {
|
||||||
{
|
{
|
||||||
String s = "123456789";
|
const String s = "123456789";
|
||||||
u32 result = DataOps::Hash_xxHash(s);
|
const u32 result = DataOps::hash_xxhash(s);
|
||||||
IAT_CHECK_EQ(result, 0x937bad67);
|
IAT_CHECK_EQ(result, 0x937bad67);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
String s = "The quick brown fox jumps over the lazy dog";
|
const String s = "The quick brown fox jumps over the lazy dog";
|
||||||
u32 result = DataOps::Hash_xxHash(s);
|
const u32 result = DataOps::hash_xxhash(s);
|
||||||
IAT_CHECK_EQ(result, 0xE85EA4DE);
|
IAT_CHECK_EQ(result, 0xE85EA4DE);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
String s = "Test";
|
const String s = "Test";
|
||||||
u32 r1 = DataOps::Hash_xxHash(s);
|
const u32 r1 = DataOps::hash_xxhash(s);
|
||||||
u32 r2 =
|
const u32 r2 = DataOps::hash_xxhash(
|
||||||
DataOps::Hash_xxHash(Span<const u8>((const u8 *)s.data(), s.size()));
|
Span<const u8>(reinterpret_cast<const u8 *>(s.data()), s.size()));
|
||||||
IAT_CHECK_EQ(r1, r2);
|
IAT_CHECK_EQ(r1, r2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestHash_FNV1A() {
|
auto test_hash_fnv1a() -> bool {
|
||||||
{
|
{
|
||||||
String s = "123456789";
|
const String s = "123456789";
|
||||||
u32 result =
|
const u32 result = DataOps::hash_fnv1a(
|
||||||
DataOps::Hash_FNV1A(Span<const u8>((const u8 *)s.data(), s.size()));
|
Span<const u8>(reinterpret_cast<const u8 *>(s.data()), s.size()));
|
||||||
IAT_CHECK_EQ(result, 0xbb86b11c);
|
IAT_CHECK_EQ(result, 0xbb86b11c);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
u32 result = DataOps::Hash_FNV1A(Span<const u8>{});
|
const u32 result = DataOps::hash_fnv1a(Span<const u8>{});
|
||||||
IAT_CHECK_EQ(result, 0x811C9DC5);
|
IAT_CHECK_EQ(result, 0x811C9DC5);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Registration
|
// Registration
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestCRC32);
|
IAT_ADD_TEST(test_crc32);
|
||||||
IAT_ADD_TEST(TestHash_FNV1A);
|
IAT_ADD_TEST(test_hash_fnv1a);
|
||||||
IAT_ADD_TEST(TestHash_xxHash);
|
IAT_ADD_TEST(test_hash_xxhash);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/Environment.hpp>
|
#include <IACore/Environment.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
using namespace IACore;
|
using namespace IACore;
|
||||||
@ -22,8 +21,8 @@ using namespace IACore;
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Constants
|
// Constants
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
static const char *TEST_KEY = "IA_TEST_ENV_VAR_12345";
|
static constexpr const char *TEST_KEY = "IA_TEST_ENV_VAR_12345";
|
||||||
static const char *TEST_VAL = "Hello World";
|
static constexpr const char *TEST_VAL = "Hello World";
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Test Block Definition
|
// Test Block Definition
|
||||||
@ -34,87 +33,83 @@ IAT_BEGIN_BLOCK(Core, Environment)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Set and Get (The Happy Path)
|
// 1. Basic Set and Get (The Happy Path)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestBasicCycle()
|
auto test_basic_cycle() -> bool {
|
||||||
{
|
// 1. Ensure clean slate
|
||||||
// 1. Ensure clean slate
|
(void)Environment::unset(TEST_KEY);
|
||||||
Environment::Unset(TEST_KEY);
|
IAT_CHECK_NOT(Environment::exists(TEST_KEY));
|
||||||
IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
|
||||||
|
|
||||||
// 2. Set
|
// 2. Set
|
||||||
bool setRes = Environment::Set(TEST_KEY, TEST_VAL);
|
const auto set_res = Environment::set(TEST_KEY, TEST_VAL);
|
||||||
IAT_CHECK(setRes);
|
IAT_CHECK(set_res.has_value());
|
||||||
IAT_CHECK(Environment::Exists(TEST_KEY));
|
IAT_CHECK(Environment::exists(TEST_KEY));
|
||||||
|
|
||||||
// 3. Find (Optional)
|
// 3. Find (Optional)
|
||||||
auto opt = Environment::Find(TEST_KEY);
|
const auto opt = Environment::find(TEST_KEY);
|
||||||
IAT_CHECK(opt.has_value());
|
IAT_CHECK(opt.has_value());
|
||||||
IAT_CHECK_EQ(*opt, String(TEST_VAL));
|
IAT_CHECK_EQ(*opt, String(TEST_VAL));
|
||||||
|
|
||||||
// 4. Get (Direct)
|
// 4. Get (Direct)
|
||||||
String val = Environment::Get(TEST_KEY);
|
const String val = Environment::get(TEST_KEY);
|
||||||
IAT_CHECK_EQ(val, String(TEST_VAL));
|
IAT_CHECK_EQ(val, String(TEST_VAL));
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Environment::Unset(TEST_KEY);
|
(void)Environment::unset(TEST_KEY);
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Overwriting Values
|
// 2. Overwriting Values
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestOverwrite()
|
auto test_overwrite() -> bool {
|
||||||
{
|
(void)Environment::set(TEST_KEY, "ValueA");
|
||||||
Environment::Set(TEST_KEY, "ValueA");
|
IAT_CHECK_EQ(Environment::get(TEST_KEY), String("ValueA"));
|
||||||
IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueA"));
|
|
||||||
|
|
||||||
// Overwrite
|
// Overwrite
|
||||||
Environment::Set(TEST_KEY, "ValueB");
|
(void)Environment::set(TEST_KEY, "ValueB");
|
||||||
IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueB"));
|
IAT_CHECK_EQ(Environment::get(TEST_KEY), String("ValueB"));
|
||||||
|
|
||||||
Environment::Unset(TEST_KEY);
|
(void)Environment::unset(TEST_KEY);
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Unset Logic
|
// 3. Unset Logic
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestUnset()
|
auto test_unset() -> bool {
|
||||||
{
|
(void)Environment::set(TEST_KEY, "To Be Deleted");
|
||||||
Environment::Set(TEST_KEY, "To Be Deleted");
|
IAT_CHECK(Environment::exists(TEST_KEY));
|
||||||
IAT_CHECK(Environment::Exists(TEST_KEY));
|
|
||||||
|
|
||||||
bool unsetRes = Environment::Unset(TEST_KEY);
|
const auto unset_res = Environment::unset(TEST_KEY);
|
||||||
IAT_CHECK(unsetRes);
|
IAT_CHECK(unset_res.has_value());
|
||||||
|
|
||||||
// Verify it is actually gone
|
// Verify it is actually gone
|
||||||
IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
IAT_CHECK_NOT(Environment::exists(TEST_KEY));
|
||||||
|
|
||||||
// Find should return nullopt
|
// Find should return nullopt
|
||||||
auto opt = Environment::Find(TEST_KEY);
|
const auto opt = Environment::find(TEST_KEY);
|
||||||
IAT_CHECK_NOT(opt.has_value());
|
IAT_CHECK_NOT(opt.has_value());
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Default Value Fallbacks
|
// 4. Default Value Fallbacks
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestDefaults()
|
auto test_defaults() -> bool {
|
||||||
{
|
const char *ghost_key = "IA_THIS_KEY_DOES_NOT_EXIST";
|
||||||
const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST";
|
|
||||||
|
|
||||||
// Ensure it really doesn't exist
|
// Ensure it really doesn't exist
|
||||||
Environment::Unset(ghostKey);
|
(void)Environment::unset(ghost_key);
|
||||||
|
|
||||||
// Test Get with implicit default ("")
|
// Test Get with implicit default ("")
|
||||||
String empty = Environment::Get(ghostKey);
|
const String empty = Environment::get(ghost_key);
|
||||||
IAT_CHECK(empty.empty());
|
IAT_CHECK(empty.empty());
|
||||||
|
|
||||||
// Test Get with explicit default
|
// Test Get with explicit default
|
||||||
String fallback = Environment::Get(ghostKey, "MyDefault");
|
const String fallback = Environment::get(ghost_key, "MyDefault");
|
||||||
IAT_CHECK_EQ(fallback, String("MyDefault"));
|
IAT_CHECK_EQ(fallback, String("MyDefault"));
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -122,56 +117,54 @@ bool TestDefaults()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Does Set(Key, "") create an existing empty variable, or unset it?
|
// Does Set(Key, "") create an existing empty variable, or unset it?
|
||||||
// Standard POSIX/Windows API behavior is that it EXISTS, but is empty.
|
// Standard POSIX/Windows API behavior is that it EXISTS, but is empty.
|
||||||
bool TestEmptyValue()
|
auto test_empty_value() -> bool {
|
||||||
{
|
(void)Environment::set(TEST_KEY, "");
|
||||||
Environment::Set(TEST_KEY, "");
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
// Windows behavior: Setting to empty string removes the variable
|
// Windows behavior: Setting to empty string removes the variable
|
||||||
// IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
// IAT_CHECK_NOT(Environment::exists(TEST_KEY));
|
||||||
// auto opt = Environment::Find(TEST_KEY);
|
// auto opt = Environment::find(TEST_KEY);
|
||||||
// IAT_CHECK_NOT(opt.has_value());
|
// IAT_CHECK_NOT(opt.has_value());
|
||||||
#else
|
#else
|
||||||
// POSIX behavior: Variable exists but is empty
|
// POSIX behavior: Variable exists but is empty
|
||||||
IAT_CHECK(Environment::Exists(TEST_KEY));
|
IAT_CHECK(Environment::exists(TEST_KEY));
|
||||||
auto opt = Environment::Find(TEST_KEY);
|
const auto opt = Environment::find(TEST_KEY);
|
||||||
IAT_CHECK(opt.has_value());
|
IAT_CHECK(opt.has_value());
|
||||||
IAT_CHECK(opt->empty());
|
IAT_CHECK(opt->empty());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Environment::Unset(TEST_KEY);
|
(void)Environment::unset(TEST_KEY);
|
||||||
IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
IAT_CHECK_NOT(Environment::exists(TEST_KEY));
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Validation / Bad Input
|
// 6. Validation / Bad Input
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestBadInput()
|
auto test_bad_input() -> bool {
|
||||||
{
|
// Setting an empty key should fail gracefully
|
||||||
// Setting an empty key should fail gracefully
|
const auto res = Environment::set("", "Value");
|
||||||
bool res = Environment::Set("", "Value");
|
IAT_CHECK_NOT(res.has_value());
|
||||||
IAT_CHECK_NOT(res);
|
|
||||||
|
|
||||||
// Unsetting an empty key should fail
|
// Unsetting an empty key should fail
|
||||||
bool resUnset = Environment::Unset("");
|
const auto res_unset = Environment::unset("");
|
||||||
IAT_CHECK_NOT(resUnset);
|
IAT_CHECK_NOT(res_unset.has_value());
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Registration
|
// Registration
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestBasicCycle);
|
IAT_ADD_TEST(test_basic_cycle);
|
||||||
IAT_ADD_TEST(TestOverwrite);
|
IAT_ADD_TEST(test_overwrite);
|
||||||
IAT_ADD_TEST(TestUnset);
|
IAT_ADD_TEST(test_unset);
|
||||||
IAT_ADD_TEST(TestDefaults);
|
IAT_ADD_TEST(test_defaults);
|
||||||
IAT_ADD_TEST(TestEmptyValue);
|
IAT_ADD_TEST(test_empty_value);
|
||||||
IAT_ADD_TEST(TestBadInput);
|
IAT_ADD_TEST(test_bad_input);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
194
Tests/Unit/FileOps.cpp
Normal file
194
Tests/Unit/FileOps.cpp
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, FileOps)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
void cleanup_file(const Path &path) {
|
||||||
|
std::error_code ec;
|
||||||
|
if (std::filesystem::exists(path, ec)) {
|
||||||
|
std::filesystem::remove(path, ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Text File I/O
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_text_io() -> bool {
|
||||||
|
const Path path = "iatest_fileops_text.txt";
|
||||||
|
const String content = "Hello IACore FileOps!\nLine 2";
|
||||||
|
|
||||||
|
// 1. Write
|
||||||
|
const auto write_res = FileOps::write_text_file(path, content, true);
|
||||||
|
IAT_CHECK(write_res.has_value());
|
||||||
|
IAT_CHECK_EQ(*write_res, content.size());
|
||||||
|
|
||||||
|
// 2. Read
|
||||||
|
const auto read_res = FileOps::read_text_file(path);
|
||||||
|
IAT_CHECK(read_res.has_value());
|
||||||
|
IAT_CHECK_EQ(*read_res, content);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanup_file(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Binary File I/O
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_binary_io() -> bool {
|
||||||
|
const Path path = "iatest_fileops_bin.bin";
|
||||||
|
const Vec<u8> content = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
|
||||||
|
|
||||||
|
// 1. Write
|
||||||
|
const auto write_res = FileOps::write_binary_file(path, content, true);
|
||||||
|
IAT_CHECK(write_res.has_value());
|
||||||
|
IAT_CHECK_EQ(*write_res, content.size());
|
||||||
|
|
||||||
|
// 2. Read
|
||||||
|
const auto read_res = FileOps::read_binary_file(path);
|
||||||
|
IAT_CHECK(read_res.has_value());
|
||||||
|
IAT_CHECK_EQ(read_res->size(), content.size());
|
||||||
|
|
||||||
|
for (usize i = 0; i < content.size(); ++i) {
|
||||||
|
IAT_CHECK_EQ((*read_res)[i], content[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanup_file(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Memory Mapping (File)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_file_mapping() -> bool {
|
||||||
|
const Path path = "iatest_fileops_map.txt";
|
||||||
|
const String content = "MappedContent";
|
||||||
|
|
||||||
|
// Setup file
|
||||||
|
(void)FileOps::write_text_file(path, content, true);
|
||||||
|
|
||||||
|
// Map
|
||||||
|
usize size = 0;
|
||||||
|
const auto map_res = FileOps::map_file(path, size);
|
||||||
|
IAT_CHECK(map_res.has_value());
|
||||||
|
IAT_CHECK_EQ(size, content.size());
|
||||||
|
|
||||||
|
const u8 *ptr = *map_res;
|
||||||
|
IAT_CHECK(ptr != nullptr);
|
||||||
|
|
||||||
|
// Verify content via pointer
|
||||||
|
String read_back(reinterpret_cast<const char *>(ptr), size);
|
||||||
|
IAT_CHECK_EQ(read_back, content);
|
||||||
|
|
||||||
|
// Unmap
|
||||||
|
FileOps::unmap_file(ptr);
|
||||||
|
|
||||||
|
cleanup_file(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. Shared Memory
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_shared_memory() -> bool {
|
||||||
|
const String shm_name = "iatest_shm_block";
|
||||||
|
const usize shm_size = 4096;
|
||||||
|
|
||||||
|
// 1. Create as Owner
|
||||||
|
auto owner_res = FileOps::map_shared_memory(shm_name, shm_size, true);
|
||||||
|
IAT_CHECK(owner_res.has_value());
|
||||||
|
u8 *owner_ptr = *owner_res;
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
std::memset(owner_ptr, 0, shm_size);
|
||||||
|
const String msg = "Shared Memory Message";
|
||||||
|
std::memcpy(owner_ptr, msg.data(), msg.size());
|
||||||
|
|
||||||
|
// 2. Open as Client
|
||||||
|
auto client_res = FileOps::map_shared_memory(shm_name, shm_size, false);
|
||||||
|
IAT_CHECK(client_res.has_value());
|
||||||
|
u8 *client_ptr = *client_res;
|
||||||
|
|
||||||
|
// Verify data
|
||||||
|
String read_msg(reinterpret_cast<const char *>(client_ptr), msg.size());
|
||||||
|
IAT_CHECK_EQ(read_msg, msg);
|
||||||
|
|
||||||
|
// 3. Cleanup
|
||||||
|
FileOps::unmap_file(owner_ptr);
|
||||||
|
FileOps::unmap_file(client_ptr);
|
||||||
|
FileOps::unlink_shared_memory(shm_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 5. Stream Integration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_stream_integration() -> bool {
|
||||||
|
const Path path = "iatest_fileops_stream.bin";
|
||||||
|
cleanup_file(path);
|
||||||
|
|
||||||
|
// Write via StreamWriter
|
||||||
|
{
|
||||||
|
auto writer_res = FileOps::stream_to_file(path, true);
|
||||||
|
IAT_CHECK(writer_res.has_value());
|
||||||
|
auto &writer = *writer_res;
|
||||||
|
|
||||||
|
(void)writer.write<u32>(0x12345678);
|
||||||
|
(void)writer.write<u8>(0xFF);
|
||||||
|
} // Destructor should flush/close
|
||||||
|
|
||||||
|
// Read via StreamReader
|
||||||
|
{
|
||||||
|
auto reader_res = FileOps::stream_from_file(path);
|
||||||
|
IAT_CHECK(reader_res.has_value());
|
||||||
|
auto &reader = *reader_res;
|
||||||
|
|
||||||
|
auto val_u32 = reader.read<u32>();
|
||||||
|
IAT_CHECK(val_u32.has_value());
|
||||||
|
IAT_CHECK_EQ(*val_u32, 0x12345678);
|
||||||
|
|
||||||
|
auto val_u8 = reader.read<u8>();
|
||||||
|
IAT_CHECK(val_u8.has_value());
|
||||||
|
IAT_CHECK_EQ(*val_u8, 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_file(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_text_io);
|
||||||
|
IAT_ADD_TEST(test_binary_io);
|
||||||
|
IAT_ADD_TEST(test_file_mapping);
|
||||||
|
IAT_ADD_TEST(test_shared_memory);
|
||||||
|
IAT_ADD_TEST(test_stream_integration);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, FileOps)
|
||||||
163
Tests/Unit/IPC.cpp
Normal file
163
Tests/Unit/IPC.cpp
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/IPC.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, IPC)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Layout Constraints
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_layout_constraints() -> bool {
|
||||||
|
// Verify alignment
|
||||||
|
IAT_CHECK_EQ(alignof(IpcSharedMemoryLayout), static_cast<usize>(64));
|
||||||
|
|
||||||
|
// Verify offsets to ensure cache-line isolation
|
||||||
|
// Header is at 0
|
||||||
|
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, meta), static_cast<usize>(0));
|
||||||
|
|
||||||
|
// moni_control should be at 64 (cache line 1)
|
||||||
|
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, moni_control),
|
||||||
|
static_cast<usize>(64));
|
||||||
|
|
||||||
|
// ControlBlock is 128 bytes (64 for producer, 64 for consumer)
|
||||||
|
// So mino_control should be at 64 + 128 = 192
|
||||||
|
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, mino_control),
|
||||||
|
static_cast<usize>(192));
|
||||||
|
|
||||||
|
// Data offsets should follow mino_control (192 + 128 = 320)
|
||||||
|
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, moni_data_offset),
|
||||||
|
static_cast<usize>(320));
|
||||||
|
|
||||||
|
// Ensure the whole struct is a multiple of 64
|
||||||
|
IAT_CHECK_EQ(sizeof(IpcSharedMemoryLayout) % 64, static_cast<usize>(0));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Manual Shared Memory & RingBuffer Setup
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_manual_shm_ringbuffer() -> bool {
|
||||||
|
const String shm_name = "IA_TEST_IPC_LAYOUT_CHECK";
|
||||||
|
const usize shm_size = 16 * 1024; // 16KB
|
||||||
|
|
||||||
|
// 1. Create Shared Memory
|
||||||
|
// Ensure it doesn't exist first
|
||||||
|
FileOps::unlink_shared_memory(shm_name);
|
||||||
|
|
||||||
|
auto map_res = FileOps::map_shared_memory(shm_name, shm_size, true);
|
||||||
|
IAT_CHECK(map_res.has_value());
|
||||||
|
|
||||||
|
u8 *base_ptr = *map_res;
|
||||||
|
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(base_ptr);
|
||||||
|
|
||||||
|
// 2. Initialize Layout
|
||||||
|
// We simulate what IpcManager would do
|
||||||
|
layout->meta.magic = 0xDEADBEEF; // Dummy
|
||||||
|
layout->meta.version = 1;
|
||||||
|
layout->meta.total_size = shm_size;
|
||||||
|
|
||||||
|
// Define data regions
|
||||||
|
// Data starts after the layout struct
|
||||||
|
const usize header_size = IpcSharedMemoryLayout::get_header_size();
|
||||||
|
const usize data_available = shm_size - header_size;
|
||||||
|
const usize half_data = data_available / 2;
|
||||||
|
|
||||||
|
layout->moni_data_offset = header_size;
|
||||||
|
layout->moni_data_size = half_data;
|
||||||
|
|
||||||
|
layout->mino_data_offset = header_size + half_data;
|
||||||
|
layout->mino_data_size = half_data;
|
||||||
|
|
||||||
|
// 3. Initialize RingBuffers
|
||||||
|
// MONI (Manager Out, Node In)
|
||||||
|
// Manager is Owner of MONI
|
||||||
|
Span<u8> moni_data_span(base_ptr + layout->moni_data_offset,
|
||||||
|
static_cast<usize>(layout->moni_data_size));
|
||||||
|
auto moni_res =
|
||||||
|
RingBufferView::create(&layout->moni_control, moni_data_span, true);
|
||||||
|
IAT_CHECK(moni_res.has_value());
|
||||||
|
auto moni = std::move(*moni_res);
|
||||||
|
|
||||||
|
// MINO (Manager In, Node Out)
|
||||||
|
// Manager is NOT Owner of MINO (Node writes to it)
|
||||||
|
// But for this test, let's pretend we are the Node for MINO to test writing
|
||||||
|
Span<u8> mino_data_span(base_ptr + layout->mino_data_offset,
|
||||||
|
static_cast<usize>(layout->mino_data_size));
|
||||||
|
auto mino_res =
|
||||||
|
RingBufferView::create(&layout->mino_control, mino_data_span, true);
|
||||||
|
IAT_CHECK(mino_res.has_value());
|
||||||
|
auto _ = std::move(*mino_res);
|
||||||
|
|
||||||
|
// 4. Test Data Flow
|
||||||
|
// Write to MONI
|
||||||
|
String msg = "IPC_TEST_MESSAGE";
|
||||||
|
IAT_CHECK(
|
||||||
|
moni.push(100, Span<const u8>(reinterpret_cast<const u8 *>(msg.data()),
|
||||||
|
msg.size()))
|
||||||
|
.has_value());
|
||||||
|
|
||||||
|
// Read from MONI (Simulate Node reading)
|
||||||
|
// Create a reader view
|
||||||
|
auto moni_reader_res =
|
||||||
|
RingBufferView::create(&layout->moni_control, moni_data_span, false);
|
||||||
|
IAT_CHECK(moni_reader_res.has_value());
|
||||||
|
auto moni_reader = std::move(*moni_reader_res);
|
||||||
|
|
||||||
|
RingBufferView::PacketHeader header;
|
||||||
|
u8 buffer[128];
|
||||||
|
auto pop_res = moni_reader.pop(header, Span<u8>(buffer, 128));
|
||||||
|
IAT_CHECK(pop_res.has_value());
|
||||||
|
IAT_CHECK(pop_res->has_value()); // Should have data
|
||||||
|
IAT_CHECK_EQ(header.id, static_cast<u16>(100));
|
||||||
|
|
||||||
|
String received((char *)buffer, *pop_res.value());
|
||||||
|
IAT_CHECK_EQ(received, msg);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
FileOps::unmap_file(base_ptr);
|
||||||
|
FileOps::unlink_shared_memory(shm_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Manager Instantiation
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
class TestManager : public IpcManager {
|
||||||
|
public:
|
||||||
|
void on_signal(NativeProcessID, u8) override {}
|
||||||
|
void on_packet(NativeProcessID, u16, Span<const u8>) override {}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto test_manager_instantiation() -> bool {
|
||||||
|
TestManager mgr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_layout_constraints);
|
||||||
|
IAT_ADD_TEST(test_manual_shm_ringbuffer);
|
||||||
|
IAT_ADD_TEST(test_manager_instantiation);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, IPC)
|
||||||
192
Tests/Unit/JSON.cpp
Normal file
192
Tests/Unit/JSON.cpp
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/JSON.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Test Structures for Serialization
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
struct UserProfile {
|
||||||
|
String username;
|
||||||
|
u32 id;
|
||||||
|
bool is_active;
|
||||||
|
Vec<String> roles;
|
||||||
|
|
||||||
|
// Equality operator for verification
|
||||||
|
bool operator==(const UserProfile &other) const {
|
||||||
|
return username == other.username && id == other.id &&
|
||||||
|
is_active == other.is_active && roles == other.roles;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Test Block Definition
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, JSON)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Dynamic JSON (nlohmann::json)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_dynamic_parse() -> bool {
|
||||||
|
const String json_text = R"({
|
||||||
|
"string": "Hello World",
|
||||||
|
"int": 42,
|
||||||
|
"float": 3.14159,
|
||||||
|
"bool": true,
|
||||||
|
"array": [10, 20, 30],
|
||||||
|
"object": { "key": "value" }
|
||||||
|
})";
|
||||||
|
|
||||||
|
auto res = Json::parse(json_text);
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
|
const auto &j = *res;
|
||||||
|
|
||||||
|
// Type checks and value retrieval
|
||||||
|
IAT_CHECK(j["string"].is_string());
|
||||||
|
IAT_CHECK_EQ(j["string"].get<String>(), String("Hello World"));
|
||||||
|
|
||||||
|
IAT_CHECK(j["int"].is_number_integer());
|
||||||
|
IAT_CHECK_EQ(j["int"].get<i32>(), 42);
|
||||||
|
|
||||||
|
IAT_CHECK(j["float"].is_number_float());
|
||||||
|
IAT_CHECK_APPROX(j["float"].get<f32>(), 3.14159f);
|
||||||
|
|
||||||
|
IAT_CHECK(j["bool"].is_boolean());
|
||||||
|
IAT_CHECK_EQ(j["bool"].get<bool>(), true);
|
||||||
|
|
||||||
|
IAT_CHECK(j["array"].is_array());
|
||||||
|
IAT_CHECK_EQ(j["array"].size(), 3u);
|
||||||
|
IAT_CHECK_EQ(j["array"][0].get<i32>(), 10);
|
||||||
|
|
||||||
|
IAT_CHECK(j["object"].is_object());
|
||||||
|
IAT_CHECK_EQ(j["object"]["key"].get<String>(), String("value"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto test_dynamic_encode() -> bool {
|
||||||
|
nlohmann::json j;
|
||||||
|
j["name"] = "IACore";
|
||||||
|
j["version"] = 2;
|
||||||
|
|
||||||
|
const String encoded = Json::encode(j);
|
||||||
|
|
||||||
|
// Simple containment check as key order isn't guaranteed
|
||||||
|
IAT_CHECK(encoded.find("IACore") != String::npos);
|
||||||
|
IAT_CHECK(encoded.find("version") != String::npos);
|
||||||
|
IAT_CHECK(encoded.find("2") != String::npos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto test_parse_invalid() -> bool {
|
||||||
|
const String bad_json = "{ key: value }"; // Missing quotes
|
||||||
|
auto res = Json::parse(bad_json);
|
||||||
|
IAT_CHECK_NOT(res.has_value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Struct Serialization (Glaze)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_struct_round_trip() -> bool {
|
||||||
|
UserProfile original{.username = "test_user",
|
||||||
|
.id = 12345,
|
||||||
|
.is_active = true,
|
||||||
|
.roles = {"admin", "editor"}};
|
||||||
|
|
||||||
|
// Struct -> JSON
|
||||||
|
auto encode_res = Json::encode_struct(original);
|
||||||
|
IAT_CHECK(encode_res.has_value());
|
||||||
|
String json_str = *encode_res;
|
||||||
|
|
||||||
|
// Verify JSON structure roughly
|
||||||
|
IAT_CHECK(json_str.find("test_user") != String::npos);
|
||||||
|
IAT_CHECK(json_str.find("roles") != String::npos);
|
||||||
|
|
||||||
|
// JSON -> Struct
|
||||||
|
auto decode_res = Json::parse_to_struct<UserProfile>(json_str);
|
||||||
|
IAT_CHECK(decode_res.has_value());
|
||||||
|
|
||||||
|
UserProfile decoded = *decode_res;
|
||||||
|
IAT_CHECK(decoded == original);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto test_struct_parse_error() -> bool {
|
||||||
|
const String malformed = "{ broken_json: ";
|
||||||
|
auto res = Json::parse_to_struct<UserProfile>(malformed);
|
||||||
|
IAT_CHECK_NOT(res.has_value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Read-Only Parsing (simdjson)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_read_only() -> bool {
|
||||||
|
const String json_text = R"({
|
||||||
|
"id": 999,
|
||||||
|
"name": "Simd",
|
||||||
|
"scores": [1.1, 2.2]
|
||||||
|
})";
|
||||||
|
|
||||||
|
auto res = Json::parse_read_only(json_text);
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
|
auto &doc = *res;
|
||||||
|
simdjson::dom::element root = doc.root();
|
||||||
|
|
||||||
|
// Check ID
|
||||||
|
u64 id = 0;
|
||||||
|
auto err_id = root["id"].get(id);
|
||||||
|
IAT_CHECK(!err_id);
|
||||||
|
IAT_CHECK_EQ(id, 999ULL);
|
||||||
|
|
||||||
|
// Check Name
|
||||||
|
std::string_view name;
|
||||||
|
auto err_name = root["name"].get(name);
|
||||||
|
IAT_CHECK(!err_name);
|
||||||
|
IAT_CHECK_EQ(String(name), String("Simd"));
|
||||||
|
|
||||||
|
// Check Array
|
||||||
|
simdjson::dom::array scores;
|
||||||
|
auto err_arr = root["scores"].get(scores);
|
||||||
|
IAT_CHECK(!err_arr);
|
||||||
|
IAT_CHECK_EQ(scores.size(), 2u);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_dynamic_parse);
|
||||||
|
IAT_ADD_TEST(test_dynamic_encode);
|
||||||
|
IAT_ADD_TEST(test_parse_invalid);
|
||||||
|
IAT_ADD_TEST(test_struct_round_trip);
|
||||||
|
IAT_ADD_TEST(test_struct_parse_error);
|
||||||
|
IAT_ADD_TEST(test_read_only);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, JSON)
|
||||||
124
Tests/Unit/Logger.cpp
Normal file
124
Tests/Unit/Logger.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/Logger.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, Logger)
|
||||||
|
|
||||||
|
static constexpr const char *LOG_FILE = "iacore_test_log.txt";
|
||||||
|
|
||||||
|
auto test_file_logging() -> bool {
|
||||||
|
// 1. Enable logging to disk
|
||||||
|
const auto res = Logger::enable_logging_to_disk(LOG_FILE);
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
|
// 2. Set level to Trace to ensure we capture everything
|
||||||
|
Logger::set_log_level(Logger::LogLevel::Trace);
|
||||||
|
|
||||||
|
// 3. Log unique messages
|
||||||
|
const String msg_info = "Test_Info_Msg_123";
|
||||||
|
const String msg_err = "Test_Error_Msg_456";
|
||||||
|
const String msg_warn = "Test_Warn_Msg_789";
|
||||||
|
|
||||||
|
Logger::info("{}", msg_info);
|
||||||
|
Logger::error("{}", msg_err);
|
||||||
|
Logger::warn("{}", msg_warn);
|
||||||
|
|
||||||
|
// 4. Flush
|
||||||
|
Logger::flush_logs();
|
||||||
|
|
||||||
|
// 5. Read back
|
||||||
|
auto read_res = FileOps::read_text_file(LOG_FILE);
|
||||||
|
if (!read_res) {
|
||||||
|
std::cout << console::YELLOW << " Warning: Could not read log file ("
|
||||||
|
<< read_res.error() << "). Skipping verification.\n"
|
||||||
|
<< console::RESET;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String content = *read_res;
|
||||||
|
|
||||||
|
IAT_CHECK(content.find(msg_info) != String::npos);
|
||||||
|
IAT_CHECK(content.find(msg_err) != String::npos);
|
||||||
|
IAT_CHECK(content.find(msg_warn) != String::npos);
|
||||||
|
|
||||||
|
// Check for log tags
|
||||||
|
IAT_CHECK(content.find("INFO") != String::npos);
|
||||||
|
IAT_CHECK(content.find("ERROR") != String::npos);
|
||||||
|
IAT_CHECK(content.find("WARN") != String::npos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto test_log_levels() -> bool {
|
||||||
|
// 1. Set level to Warn (Trace < Debug < Info < Warn < Error)
|
||||||
|
Logger::set_log_level(Logger::LogLevel::Warn);
|
||||||
|
|
||||||
|
const String unique_info = "Hidden_Info_Msg";
|
||||||
|
const String unique_warn = "Visible_Warn_Msg";
|
||||||
|
|
||||||
|
Logger::info("{}", unique_info);
|
||||||
|
Logger::warn("{}", unique_warn);
|
||||||
|
|
||||||
|
Logger::flush_logs();
|
||||||
|
|
||||||
|
auto read_res = FileOps::read_text_file(LOG_FILE);
|
||||||
|
if (!read_res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String content = *read_res;
|
||||||
|
|
||||||
|
// Info should NOT be present
|
||||||
|
IAT_CHECK(content.find(unique_info) == String::npos);
|
||||||
|
// Warn SHOULD be present
|
||||||
|
IAT_CHECK(content.find(unique_warn) != String::npos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto test_formatting() -> bool {
|
||||||
|
Logger::set_log_level(Logger::LogLevel::Info);
|
||||||
|
|
||||||
|
const String name = "IACore";
|
||||||
|
const i32 version = 99;
|
||||||
|
|
||||||
|
Logger::info("System {} online v{}", name, version);
|
||||||
|
Logger::flush_logs();
|
||||||
|
|
||||||
|
auto read_res = FileOps::read_text_file(LOG_FILE);
|
||||||
|
if (!read_res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String content = *read_res;
|
||||||
|
IAT_CHECK(content.find("System IACore online v99") != String::npos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_file_logging);
|
||||||
|
IAT_ADD_TEST(test_log_levels);
|
||||||
|
IAT_ADD_TEST(test_formatting);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, Logger)
|
||||||
@ -16,20 +16,26 @@
|
|||||||
#include <IACore/IACore.hpp>
|
#include <IACore/IACore.hpp>
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
#include <IACore/SocketOps.hpp>
|
||||||
{
|
|
||||||
UNUSED(argc);
|
|
||||||
UNUSED(argv);
|
|
||||||
|
|
||||||
printf(__CC_GREEN "\n===============================================================\n");
|
using namespace IACore;
|
||||||
printf(" IACore (Independent Architecture Core) - Unit Test Suite\n");
|
|
||||||
printf("===============================================================\n" __CC_DEFAULT "\n");
|
|
||||||
|
|
||||||
IACore::Initialize();
|
IACORE_MAIN() {
|
||||||
|
(void)args;
|
||||||
|
|
||||||
int result = ia::iatest::TestRegistry::RunAll();
|
IA_TRY_PURE(SocketOps::initialize());
|
||||||
|
|
||||||
IACore::Terminate();
|
std::cout
|
||||||
|
<< console::GREEN
|
||||||
|
<< "\n===============================================================\n";
|
||||||
|
std::cout << " IACore (Independent Architecture Core) - Unit Test Suite\n";
|
||||||
|
std::cout
|
||||||
|
<< "===============================================================\n"
|
||||||
|
<< console::RESET << "\n";
|
||||||
|
|
||||||
return result;
|
const i32 result = Test::TestRegistry::run_all();
|
||||||
|
|
||||||
|
SocketOps::terminate();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
143
Tests/Unit/Platform.cpp
Normal file
143
Tests/Unit/Platform.cpp
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/Platform.hpp>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, Platform)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. OS Name Detection
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_os_name() -> bool {
|
||||||
|
const char *os_name = Platform::get_operating_system_name();
|
||||||
|
IAT_CHECK(os_name != nullptr);
|
||||||
|
|
||||||
|
const String os(os_name);
|
||||||
|
IAT_CHECK(!os.empty());
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
IAT_CHECK_EQ(os, String("Windows"));
|
||||||
|
#elif IA_PLATFORM_LINUX
|
||||||
|
IAT_CHECK_EQ(os, String("Linux"));
|
||||||
|
#elif IA_PLATFORM_APPLE
|
||||||
|
IAT_CHECK_EQ(os, String("MacOS"));
|
||||||
|
#elif IA_PLATFORM_WASM
|
||||||
|
IAT_CHECK_EQ(os, String("WebAssembly"));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Architecture Name Detection
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_arch_name() -> bool {
|
||||||
|
const char *arch_name = Platform::get_architecture_name();
|
||||||
|
IAT_CHECK(arch_name != nullptr);
|
||||||
|
|
||||||
|
const String arch(arch_name);
|
||||||
|
IAT_CHECK(!arch.empty());
|
||||||
|
|
||||||
|
#if IA_ARCH_X64
|
||||||
|
IAT_CHECK_EQ(arch, String("x86_64"));
|
||||||
|
#elif IA_ARCH_ARM64
|
||||||
|
IAT_CHECK_EQ(arch, String("ARM64"));
|
||||||
|
#elif IA_ARCH_WASM
|
||||||
|
IAT_CHECK_EQ(arch, String("WASM"));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. CPU Capabilities
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_capabilities() -> bool {
|
||||||
|
// Initialize detection
|
||||||
|
const bool check_result = Platform::check_cpu();
|
||||||
|
IAT_CHECK(check_result);
|
||||||
|
|
||||||
|
const auto &caps = Platform::get_capabilities();
|
||||||
|
|
||||||
|
// We verify that we can access the capabilities struct.
|
||||||
|
// The actual value of hardware_crc32 depends on the host machine,
|
||||||
|
// so we cannot assert true or false, but we ensure no crash occurs.
|
||||||
|
volatile bool has_crc = caps.hardware_crc32;
|
||||||
|
(void)has_crc;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. CPUID (x64 Only)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
#if IA_ARCH_X64
|
||||||
|
auto test_cpuid() -> bool {
|
||||||
|
i32 regs[4] = {0};
|
||||||
|
|
||||||
|
// Call CPUID with Function 0 (Vendor ID)
|
||||||
|
Platform::cpuid(0, 0, regs);
|
||||||
|
|
||||||
|
// EAX (regs[0]) holds max supported function. Should be > 0 on any modern
|
||||||
|
// CPU.
|
||||||
|
IAT_CHECK(regs[0] >= 0);
|
||||||
|
|
||||||
|
// EBX, EDX, ECX hold the vendor string.
|
||||||
|
// The order for the string is EBX, EDX, ECX.
|
||||||
|
char vendor[13];
|
||||||
|
std::memset(vendor, 0, 13);
|
||||||
|
|
||||||
|
std::memcpy(vendor, ®s[1], 4); // EBX
|
||||||
|
std::memcpy(vendor + 4, ®s[3], 4); // EDX
|
||||||
|
std::memcpy(vendor + 8, ®s[2], 4); // ECX
|
||||||
|
vendor[12] = '\0';
|
||||||
|
|
||||||
|
const String vendor_str(vendor);
|
||||||
|
IAT_CHECK(!vendor_str.empty());
|
||||||
|
|
||||||
|
// Check against common vendors to ensure registers contained valid ASCII
|
||||||
|
bool is_known =
|
||||||
|
(vendor_str == "GenuineIntel" || vendor_str == "AuthenticAMD" ||
|
||||||
|
vendor_str == "KVMKVMKVM" || vendor_str == "Microsoft Hv" ||
|
||||||
|
vendor_str == "VBoxVBoxVBox");
|
||||||
|
|
||||||
|
if (!is_known) {
|
||||||
|
// Not a failure, just an unknown CPU vendor (or virtualization)
|
||||||
|
std::cout << " [Info] Unknown CPU Vendor: " << vendor_str << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_os_name);
|
||||||
|
IAT_ADD_TEST(test_arch_name);
|
||||||
|
IAT_ADD_TEST(test_capabilities);
|
||||||
|
#if IA_ARCH_X64
|
||||||
|
IAT_ADD_TEST(test_cpuid);
|
||||||
|
#endif
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, Platform)
|
||||||
@ -13,9 +13,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/ProcessOps.hpp>
|
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/ProcessOps.hpp>
|
||||||
|
|
||||||
using namespace IACore;
|
using namespace IACore;
|
||||||
|
|
||||||
@ -23,13 +22,13 @@ using namespace IACore;
|
|||||||
// Platform Abstraction for Test Commands
|
// Platform Abstraction for Test Commands
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
# define CMD_ECHO_EXE "cmd.exe"
|
#define CMD_ECHO_EXE "cmd.exe"
|
||||||
# define CMD_ARG_PREFIX "/c echo"
|
#define CMD_ARG_PREFIX "/c echo"
|
||||||
# define NULL_DEVICE "NUL"
|
#define NULL_DEVICE "NUL"
|
||||||
#else
|
#else
|
||||||
# define CMD_ECHO_EXE "/bin/echo"
|
#define CMD_ECHO_EXE "/bin/echo"
|
||||||
# define CMD_ARG_PREFIX ""
|
#define CMD_ARG_PREFIX ""
|
||||||
# define NULL_DEVICE "/dev/null"
|
#define NULL_DEVICE "/dev/null"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, ProcessOps)
|
IAT_BEGIN_BLOCK(Core, ProcessOps)
|
||||||
@ -37,215 +36,226 @@ IAT_BEGIN_BLOCK(Core, ProcessOps)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Execution (Exit Code 0)
|
// 1. Basic Execution (Exit Code 0)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestBasicRun()
|
auto test_basic_run() -> bool {
|
||||||
{
|
// Simple "echo hello"
|
||||||
// Simple "echo hello"
|
String captured;
|
||||||
String captured;
|
|
||||||
|
|
||||||
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
const auto result =
|
||||||
[&](StringView line) { captured = line; });
|
ProcessOps::spawn_process_sync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
||||||
|
[&](StringView line) { captured = line; });
|
||||||
|
|
||||||
IAT_CHECK(result.has_value());
|
IAT_CHECK(result.has_value());
|
||||||
IAT_CHECK_EQ(*result, 0); // Exit code 0
|
IAT_CHECK_EQ(*result, 0); // Exit code 0
|
||||||
|
|
||||||
// We check if "HelloIA" is contained or equal.
|
// We check if "HelloIA" is contained or equal.
|
||||||
IAT_CHECK(captured.find("HelloIA") != String::npos);
|
IAT_CHECK(captured.find("HelloIA") != String::npos);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Argument Parsing
|
// 2. Argument Parsing
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestArguments()
|
auto test_arguments() -> bool {
|
||||||
{
|
Vec<String> lines;
|
||||||
Vec<String> lines;
|
|
||||||
|
|
||||||
// Echo two distinct words.
|
// Echo two distinct words.
|
||||||
// Windows: cmd.exe /c echo one two
|
// Windows: cmd.exe /c echo one two
|
||||||
// Linux: /bin/echo one two
|
// Linux: /bin/echo one two
|
||||||
String args = String(CMD_ARG_PREFIX) + " one two";
|
String args = String(CMD_ARG_PREFIX) + " one two";
|
||||||
if (args[0] == ' ')
|
if (!args.empty() && args[0] == ' ') {
|
||||||
args.erase(0, 1); // cleanup space if prefix empty
|
args.erase(0, 1); // cleanup space if prefix empty
|
||||||
|
}
|
||||||
|
|
||||||
auto result =
|
const auto result =
|
||||||
ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args, [&](StringView line) { lines.push_back(String(line)); });
|
ProcessOps::spawn_process_sync(CMD_ECHO_EXE, args, [&](StringView line) {
|
||||||
|
lines.push_back(String(line));
|
||||||
|
});
|
||||||
|
|
||||||
IAT_CHECK_EQ(*result, 0);
|
IAT_CHECK_EQ(*result, 0);
|
||||||
IAT_CHECK(lines.size() > 0);
|
IAT_CHECK(lines.size() > 0);
|
||||||
|
|
||||||
// Output should contain "one two"
|
// Output should contain "one two"
|
||||||
IAT_CHECK(lines[0].find("one two") != String::npos);
|
IAT_CHECK(lines[0].find("one two") != String::npos);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Error / Non-Zero Exit Codes
|
// 3. Error / Non-Zero Exit Codes
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestExitCodes()
|
auto test_exit_codes() -> bool {
|
||||||
{
|
// We need a command that returns non-zero.
|
||||||
// We need a command that returns non-zero.
|
// Windows: cmd /c exit 1
|
||||||
// Windows: cmd /c exit 1
|
// Linux: /bin/sh -c "exit 1"
|
||||||
// Linux: /bin/sh -c "exit 1"
|
|
||||||
|
|
||||||
String cmd, arg;
|
String cmd;
|
||||||
|
String arg;
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
cmd = "cmd.exe";
|
cmd = "cmd.exe";
|
||||||
arg = "/c exit 42";
|
arg = "/c exit 42";
|
||||||
#else
|
#else
|
||||||
cmd = "/bin/sh";
|
cmd = "/bin/sh";
|
||||||
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView) {});
|
const auto result =
|
||||||
|
ProcessOps::spawn_process_sync(cmd, arg, [](StringView) {});
|
||||||
|
|
||||||
IAT_CHECK(result.has_value());
|
IAT_CHECK(result.has_value());
|
||||||
IAT_CHECK_EQ(*result, 42);
|
IAT_CHECK_EQ(*result, 42);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Missing Executable Handling
|
// 4. Missing Executable Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestMissingExe()
|
auto test_missing_exe() -> bool {
|
||||||
{
|
// Try to run a random string
|
||||||
// Try to run a random string
|
const auto result =
|
||||||
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {});
|
ProcessOps::spawn_process_sync("sdflkjghsdflkjg", "", [](StringView) {});
|
||||||
|
|
||||||
// Windows: CreateProcess usually fails -> returns unexpected
|
// Windows: CreateProcess usually fails -> returns unexpected
|
||||||
// Linux: execvp fails inside child, returns 127 via waitpid
|
// Linux: execvp fails inside child, returns 127 via waitpid
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
IAT_CHECK_NOT(result.has_value()); // Should be an error string
|
IAT_CHECK_NOT(result.has_value()); // Should be an error string
|
||||||
#else
|
#else
|
||||||
// Linux fork succeeds, but execvp fails, returning 127
|
// Linux fork succeeds, but execvp fails, returning 127
|
||||||
IAT_CHECK(result.has_value());
|
IAT_CHECK(result.has_value());
|
||||||
IAT_CHECK_EQ(*result, 127);
|
IAT_CHECK_EQ(*result, 127);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 5. Line Buffer Logic (The 4096 split test)
|
// 5. Line Buffer Logic (The 4096 split test)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestLargeOutput()
|
auto test_large_output() -> bool {
|
||||||
{
|
// 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
|
||||||
// to ensure the "partial line" logic works when a line crosses a buffer boundary.
|
// boundary.
|
||||||
|
|
||||||
String massiveString;
|
String massive_string;
|
||||||
massiveString.reserve(5000);
|
massive_string.reserve(5000);
|
||||||
for (int i = 0; i < 500; ++i)
|
for (i32 i = 0; i < 500; ++i) {
|
||||||
massiveString += "1234567890"; // 5000 chars
|
massive_string += "1234567890"; // 5000 chars
|
||||||
|
}
|
||||||
|
|
||||||
String cmd, arg;
|
String cmd;
|
||||||
|
String arg;
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
cmd = "cmd.exe";
|
cmd = "cmd.exe";
|
||||||
// Windows has command line length limits (~8k), 5k is safe.
|
// Windows has command line length limits (~8k), 5k is safe.
|
||||||
arg = "/c echo " + massiveString;
|
arg = "/c echo " + massive_string;
|
||||||
#else
|
#else
|
||||||
cmd = "/bin/echo";
|
cmd = "/bin/echo";
|
||||||
arg = massiveString;
|
arg = massive_string;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
String captured;
|
String captured;
|
||||||
auto result = ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) { captured += line; });
|
const auto result = ProcessOps::spawn_process_sync(
|
||||||
|
cmd, arg, [&](StringView line) { captured += line; });
|
||||||
|
|
||||||
IAT_CHECK(result.has_value());
|
IAT_CHECK(result.has_value());
|
||||||
IAT_CHECK_EQ(*result, 0);
|
IAT_CHECK_EQ(*result, 0);
|
||||||
|
|
||||||
// If the LineBuffer failed to stitch chunks, the length wouldn't match
|
// If the LineBuffer failed to stitch chunks, the length wouldn't match
|
||||||
// or we would get multiple callbacks if we expected 1 line.
|
// or we would get multiple callbacks if we expected 1 line.
|
||||||
IAT_CHECK_EQ(captured.length(), massiveString.length());
|
IAT_CHECK_EQ(captured.length(), massive_string.length());
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Multi-Line Handling
|
// 6. Multi-Line Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestMultiLine()
|
auto test_multi_line() -> bool {
|
||||||
{
|
// Windows: cmd /c "echo A && echo B"
|
||||||
// Windows: cmd /c "echo A && echo B"
|
// Linux: /bin/sh -c "echo A; echo B"
|
||||||
// Linux: /bin/sh -c "echo A; echo B"
|
|
||||||
|
|
||||||
String cmd, arg;
|
String cmd;
|
||||||
|
String arg;
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
cmd = "cmd.exe";
|
cmd = "cmd.exe";
|
||||||
arg = "/c \"echo LineA && echo LineB\"";
|
arg = "/c \"echo LineA && echo LineB\"";
|
||||||
#else
|
#else
|
||||||
cmd = "/bin/sh";
|
cmd = "/bin/sh";
|
||||||
arg = "-c \"echo LineA; echo LineB\"";
|
arg = "-c \"echo LineA; echo LineB\"";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int lineCount = 0;
|
i32 line_count = 0;
|
||||||
bool foundA = false;
|
bool found_a = false;
|
||||||
bool foundB = false;
|
bool found_b = false;
|
||||||
|
|
||||||
UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) {
|
IA_UNUSED const auto res =
|
||||||
lineCount++;
|
ProcessOps::spawn_process_sync(cmd, arg, [&](StringView line) {
|
||||||
if (line.find("LineA") != String::npos)
|
line_count++;
|
||||||
foundA = true;
|
if (line.find("LineA") != String::npos) {
|
||||||
if (line.find("LineB") != String::npos)
|
found_a = true;
|
||||||
foundB = true;
|
}
|
||||||
}));
|
if (line.find("LineB") != String::npos) {
|
||||||
|
found_b = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
IAT_CHECK(foundA);
|
IAT_CHECK(found_a);
|
||||||
IAT_CHECK(foundB);
|
IAT_CHECK(found_b);
|
||||||
// We expect at least 2 lines.
|
// We expect at least 2 lines.
|
||||||
// (Windows sometimes echoes the command itself depending on echo settings, but we check contents)
|
// (Windows sometimes echoes the command itself depending on echo settings,
|
||||||
IAT_CHECK(lineCount >= 2);
|
// but we check contents)
|
||||||
|
IAT_CHECK(line_count >= 2);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Complex Command Line Arguments Handling
|
// 6. Complex Command Line Arguments Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestComplexArguments()
|
auto test_complex_arguments() -> bool {
|
||||||
{
|
// Should parse as 3 arguments:
|
||||||
// Should parse as 3 arguments:
|
// 1. -DDEFINED_MSG="Hello World"
|
||||||
// 1. -DDEFINED_MSG="Hello World"
|
// 2. -v
|
||||||
// 2. -v
|
// 3. path/to/file
|
||||||
// 3. path/to/file
|
const String complex_args =
|
||||||
String complexArgs = "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
|
"-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
|
||||||
|
|
||||||
String cmd = CMD_ECHO_EXE;
|
const String cmd = CMD_ECHO_EXE;
|
||||||
|
|
||||||
String finalArgs;
|
String final_args;
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
finalArgs = "/c echo " + complexArgs;
|
final_args = "/c echo " + complex_args;
|
||||||
#else
|
#else
|
||||||
finalArgs = complexArgs;
|
final_args = complex_args;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
String captured;
|
String captured;
|
||||||
auto result = ProcessOps::SpawnProcessSync(cmd, finalArgs, [&](StringView line) { captured += line; });
|
const auto result = ProcessOps::spawn_process_sync(
|
||||||
|
cmd, final_args, [&](StringView line) { captured += line; });
|
||||||
|
|
||||||
IAT_CHECK(result.has_value());
|
IAT_CHECK(result.has_value());
|
||||||
IAT_CHECK_EQ(*result, 0);
|
IAT_CHECK_EQ(*result, 0);
|
||||||
|
|
||||||
// Verify the quotes were preserved in the output
|
// Verify the quotes were preserved in the output
|
||||||
IAT_CHECK(captured.find("Hello World") != String::npos);
|
IAT_CHECK(captured.find("Hello World") != String::npos);
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Registration
|
// Registration
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestBasicRun);
|
IAT_ADD_TEST(test_basic_run);
|
||||||
IAT_ADD_TEST(TestArguments);
|
IAT_ADD_TEST(test_arguments);
|
||||||
IAT_ADD_TEST(TestExitCodes);
|
IAT_ADD_TEST(test_exit_codes);
|
||||||
IAT_ADD_TEST(TestMissingExe);
|
IAT_ADD_TEST(test_missing_exe);
|
||||||
IAT_ADD_TEST(TestLargeOutput);
|
IAT_ADD_TEST(test_large_output);
|
||||||
IAT_ADD_TEST(TestMultiLine);
|
IAT_ADD_TEST(test_multi_line);
|
||||||
IAT_ADD_TEST(TestComplexArguments);
|
IAT_ADD_TEST(test_complex_arguments);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
@ -23,80 +23,102 @@ IAT_BEGIN_BLOCK(Core, RingBuffer)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Push Pop
|
// 1. Basic Push Pop
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestPushPop() {
|
auto test_push_pop() -> bool {
|
||||||
// Allocate raw memory for the ring buffer
|
// Allocate raw memory for the ring buffer
|
||||||
// ControlBlock (128 bytes) + Data
|
// ControlBlock (128 bytes) + Data
|
||||||
std::vector<u8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
|
Vec<u8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
|
||||||
|
|
||||||
// Initialize as OWNER (Producer)
|
// Initialize as OWNER (Producer)
|
||||||
RingBufferView producer(std::span<u8>(memory), TRUE);
|
auto producer_res = RingBufferView::create(Span<u8>(memory), true);
|
||||||
|
IAT_CHECK(producer_res.has_value());
|
||||||
|
auto producer = std::move(*producer_res);
|
||||||
|
|
||||||
// Initialize as CONSUMER (Pointer to same memory)
|
// Initialize as CONSUMER (Pointer to same memory)
|
||||||
RingBufferView consumer(std::span<u8>(memory), FALSE);
|
auto consumer_res = RingBufferView::create(Span<u8>(memory), false);
|
||||||
|
IAT_CHECK(consumer_res.has_value());
|
||||||
|
auto consumer = std::move(*consumer_res);
|
||||||
|
|
||||||
// Data to send
|
// Data to send
|
||||||
String msg = "Hello RingBuffer";
|
String msg = "Hello RingBuffer";
|
||||||
bool pushed = producer.Push(1, {(const u8 *)msg.data(), msg.size()});
|
const auto push_res = producer.push(
|
||||||
IAT_CHECK(pushed);
|
1, Span<const u8>(reinterpret_cast<const u8 *>(msg.data()), msg.size()));
|
||||||
|
IAT_CHECK(push_res.has_value());
|
||||||
|
|
||||||
// Read back
|
// Read back
|
||||||
RingBufferView::PacketHeader header;
|
RingBufferView::PacketHeader header;
|
||||||
u8 readBuf[128];
|
u8 read_buf[128];
|
||||||
|
|
||||||
i32 bytesRead = consumer.Pop(header, std::span<u8>(readBuf, 128));
|
const auto pop_res = consumer.pop(header, Span<u8>(read_buf, 128));
|
||||||
|
IAT_CHECK(pop_res.has_value());
|
||||||
|
|
||||||
IAT_CHECK_EQ(header.ID, (u16)1);
|
const auto bytes_read_opt = *pop_res;
|
||||||
IAT_CHECK_EQ(bytesRead, (i32)msg.size());
|
IAT_CHECK(bytes_read_opt.has_value());
|
||||||
|
|
||||||
String readMsg((char *)readBuf, bytesRead);
|
const usize bytes_read = *bytes_read_opt;
|
||||||
IAT_CHECK_EQ(readMsg, msg);
|
|
||||||
|
|
||||||
return TRUE;
|
IAT_CHECK_EQ(header.id, static_cast<u16>(1));
|
||||||
|
IAT_CHECK_EQ(bytes_read, static_cast<usize>(msg.size()));
|
||||||
|
|
||||||
|
String read_msg(reinterpret_cast<char *>(read_buf), bytes_read);
|
||||||
|
IAT_CHECK_EQ(read_msg, msg);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Wrap Around
|
// 2. Wrap Around
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestWrapAround() {
|
auto test_wrap_around() -> bool {
|
||||||
// Small buffer to force wrapping quickly
|
// Small buffer to force wrapping quickly
|
||||||
// Capacity will be 100 bytes
|
// Capacity will be 100 bytes
|
||||||
std::vector<u8> memory(sizeof(RingBufferView::ControlBlock) + 100);
|
Vec<u8> memory(sizeof(RingBufferView::ControlBlock) + 100);
|
||||||
RingBufferView rb(std::span<u8>(memory), TRUE);
|
|
||||||
|
auto rb_res = RingBufferView::create(Span<u8>(memory), true);
|
||||||
|
IAT_CHECK(rb_res.has_value());
|
||||||
|
auto rb = std::move(*rb_res);
|
||||||
|
|
||||||
// Fill buffer to near end
|
// Fill buffer to near end
|
||||||
// Push 80 bytes
|
// Push 80 bytes
|
||||||
std::vector<u8> junk(80, 0xFF);
|
Vec<u8> junk(80, 0xFF);
|
||||||
rb.Push(1, junk);
|
const auto push1 = rb.push(1, junk);
|
||||||
|
IAT_CHECK(push1.has_value());
|
||||||
|
|
||||||
// Pop them to advance READ cursor
|
// Pop them to advance READ cursor
|
||||||
RingBufferView::PacketHeader header;
|
RingBufferView::PacketHeader header;
|
||||||
u8 outBuf[100];
|
u8 out_buf[100];
|
||||||
rb.Pop(header, outBuf);
|
const auto pop1 = rb.pop(header, out_buf);
|
||||||
|
IAT_CHECK(pop1.has_value());
|
||||||
|
IAT_CHECK(pop1->has_value());
|
||||||
|
|
||||||
// Now READ and WRITE are near index 80.
|
// Now READ and WRITE are near index 80.
|
||||||
// Pushing 40 bytes should trigger a wrap-around (split write)
|
// Pushing 40 bytes should trigger a wrap-around (split write)
|
||||||
std::vector<u8> wrapData(40, 0xAA);
|
Vec<u8> wrap_data(40, 0xAA);
|
||||||
bool pushed = rb.Push(2, wrapData);
|
const auto push2 = rb.push(2, wrap_data);
|
||||||
IAT_CHECK(pushed);
|
IAT_CHECK(push2.has_value());
|
||||||
|
|
||||||
// Pop and verify integrity
|
// Pop and verify integrity
|
||||||
i32 popSize = rb.Pop(header, outBuf);
|
const auto pop2 = rb.pop(header, out_buf);
|
||||||
IAT_CHECK_EQ(popSize, 40);
|
IAT_CHECK(pop2.has_value());
|
||||||
|
IAT_CHECK(pop2->has_value());
|
||||||
|
|
||||||
|
const usize pop_size = *pop2.value();
|
||||||
|
IAT_CHECK_EQ(pop_size, static_cast<usize>(40));
|
||||||
|
|
||||||
// Check if data is intact
|
// Check if data is intact
|
||||||
bool match = TRUE;
|
bool match = true;
|
||||||
for (int i = 0; i < 40; i++) {
|
for (usize i = 0; i < 40; i++) {
|
||||||
if (outBuf[i] != 0xAA)
|
if (out_buf[i] != 0xAA) {
|
||||||
match = FALSE;
|
match = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
IAT_CHECK(match);
|
IAT_CHECK(match);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestPushPop);
|
IAT_ADD_TEST(test_push_pop);
|
||||||
IAT_ADD_TEST(TestWrapAround);
|
IAT_ADD_TEST(test_wrap_around);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
@ -13,92 +13,88 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/SIMD.hpp>
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/SIMD.hpp>
|
||||||
|
|
||||||
using namespace IACore;
|
using namespace IACore;
|
||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, FloatVec4)
|
IAT_BEGIN_BLOCK(Core, FloatVec4)
|
||||||
|
|
||||||
bool TestFloatArithmetic()
|
auto test_float_arithmetic() -> bool {
|
||||||
{
|
FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f);
|
||||||
FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f);
|
FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f);
|
||||||
FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f);
|
|
||||||
|
|
||||||
alignas(16) f32 res[4];
|
alignas(16) f32 res[4];
|
||||||
|
|
||||||
(v1 / v2).Store(res);
|
(v1 / v2).store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 5.0f);
|
IAT_CHECK_APPROX(res[0], 5.0f);
|
||||||
IAT_CHECK_APPROX(res[3], 5.0f);
|
IAT_CHECK_APPROX(res[3], 5.0f);
|
||||||
|
|
||||||
(v1 * v2).Store(res);
|
(v1 * v2).store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 20.0f);
|
IAT_CHECK_APPROX(res[0], 20.0f);
|
||||||
|
|
||||||
(v1 + v2).Store(res);
|
(v1 + v2).store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 12.0f);
|
IAT_CHECK_APPROX(res[0], 12.0f);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestMathHelpers()
|
auto test_math_helpers() -> bool {
|
||||||
{
|
alignas(16) f32 res[4];
|
||||||
alignas(16) f32 res[4];
|
|
||||||
|
|
||||||
FloatVec4 vSq(4.0f, 9.0f, 16.0f, 25.0f);
|
FloatVec4 v_sq(4.0f, 9.0f, 16.0f, 25.0f);
|
||||||
vSq.Sqrt().Store(res);
|
v_sq.sqrt().store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 2.0f);
|
IAT_CHECK_APPROX(res[0], 2.0f);
|
||||||
IAT_CHECK_APPROX(res[3], 5.0f);
|
IAT_CHECK_APPROX(res[3], 5.0f);
|
||||||
|
|
||||||
FloatVec4 vNeg(-1.0f, -5.0f, 10.0f, -0.0f);
|
FloatVec4 v_neg(-1.0f, -5.0f, 10.0f, -0.0f);
|
||||||
vNeg.Abs().Store(res);
|
v_neg.abs().store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 1.0f);
|
IAT_CHECK_APPROX(res[0], 1.0f);
|
||||||
IAT_CHECK_APPROX(res[2], 10.0f);
|
IAT_CHECK_APPROX(res[2], 10.0f);
|
||||||
|
|
||||||
FloatVec4 vClamp(-100.0f, 0.0f, 50.0f, 200.0f);
|
FloatVec4 v_clamp(-100.0f, 0.0f, 50.0f, 200.0f);
|
||||||
vClamp.Clamp(0.0f, 100.0f).Store(res);
|
v_clamp.clamp(0.0f, 100.0f).store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 0.0f);
|
IAT_CHECK_APPROX(res[0], 0.0f);
|
||||||
IAT_CHECK_APPROX(res[2], 50.0f);
|
IAT_CHECK_APPROX(res[2], 50.0f);
|
||||||
IAT_CHECK_APPROX(res[3], 100.0f);
|
IAT_CHECK_APPROX(res[3], 100.0f);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestApproxMath()
|
auto test_approx_math() -> bool {
|
||||||
{
|
alignas(16) f32 res[4];
|
||||||
alignas(16) f32 res[4];
|
FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f);
|
||||||
FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f);
|
|
||||||
|
|
||||||
v.Rsqrt().Store(res);
|
v.rsqrt().store(res);
|
||||||
|
|
||||||
IAT_CHECK_APPROX(res[0], 0.25f);
|
IAT_CHECK_APPROX(res[0], 0.25f);
|
||||||
IAT_CHECK_APPROX(res[2], 0.1f);
|
IAT_CHECK_APPROX(res[2], 0.1f);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestLinearAlgebra()
|
auto test_linear_algebra() -> bool {
|
||||||
{
|
FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f);
|
FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f);
|
||||||
FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f);
|
|
||||||
|
|
||||||
f32 dot = v1.Dot(v2);
|
f32 dot = v1.dot(v2);
|
||||||
IAT_CHECK_APPROX(dot, 4.0f);
|
IAT_CHECK_APPROX(dot, 4.0f);
|
||||||
|
|
||||||
FloatVec4 vNorm(10.0f, 0.0f, 0.0f, 0.0f);
|
FloatVec4 v_norm(10.0f, 0.0f, 0.0f, 0.0f);
|
||||||
alignas(16) f32 res[4];
|
alignas(16) f32 res[4];
|
||||||
|
|
||||||
vNorm.Normalize().Store(res);
|
v_norm.normalize().store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 1.0f);
|
IAT_CHECK_APPROX(res[0], 1.0f);
|
||||||
IAT_CHECK_APPROX(res[1], 0.0f);
|
IAT_CHECK_APPROX(res[1], 0.0f);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestFloatArithmetic);
|
IAT_ADD_TEST(test_float_arithmetic);
|
||||||
IAT_ADD_TEST(TestMathHelpers);
|
IAT_ADD_TEST(test_math_helpers);
|
||||||
IAT_ADD_TEST(TestApproxMath);
|
IAT_ADD_TEST(test_approx_math);
|
||||||
IAT_ADD_TEST(TestLinearAlgebra);
|
IAT_ADD_TEST(test_linear_algebra);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
@ -13,138 +13,130 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
|
||||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
|
||||||
|
|
||||||
#include <IACore/SIMD.hpp>
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/SIMD.hpp>
|
||||||
|
|
||||||
using namespace IACore;
|
using namespace IACore;
|
||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, IntVec4)
|
IAT_BEGIN_BLOCK(Core, IntVec4)
|
||||||
|
|
||||||
bool TestConstructors()
|
auto test_constructors() -> bool {
|
||||||
{
|
IntVec4 v_broadcast(10);
|
||||||
IntVec4 vBroadcast(10);
|
alignas(16) u32 store_buf[4];
|
||||||
alignas(16) u32 storeBuf[4];
|
v_broadcast.store(store_buf);
|
||||||
vBroadcast.Store(storeBuf);
|
|
||||||
|
|
||||||
IAT_CHECK_EQ(storeBuf[0], 10U);
|
IAT_CHECK_EQ(store_buf[0], 10U);
|
||||||
IAT_CHECK_EQ(storeBuf[3], 10U);
|
IAT_CHECK_EQ(store_buf[3], 10U);
|
||||||
|
|
||||||
IntVec4 vComp(1, 2, 3, 4);
|
IntVec4 v_comp(1, 2, 3, 4);
|
||||||
vComp.Store(storeBuf);
|
v_comp.store(store_buf);
|
||||||
IAT_CHECK_EQ(storeBuf[0], 1U);
|
IAT_CHECK_EQ(store_buf[0], 1U);
|
||||||
IAT_CHECK_EQ(storeBuf[3], 4U);
|
IAT_CHECK_EQ(store_buf[3], 4U);
|
||||||
|
|
||||||
alignas(16) u32 srcBuf[4] = {100, 200, 300, 400};
|
alignas(16) u32 src_buf[4] = {100, 200, 300, 400};
|
||||||
IntVec4 vLoad = IntVec4::Load(srcBuf);
|
IntVec4 v_load = IntVec4::load(src_buf);
|
||||||
vLoad.Store(storeBuf);
|
v_load.store(store_buf);
|
||||||
IAT_CHECK_EQ(storeBuf[1], 200U);
|
IAT_CHECK_EQ(store_buf[1], 200U);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestArithmetic()
|
auto test_arithmetic() -> bool {
|
||||||
{
|
const IntVec4 v1(10, 20, 30, 40);
|
||||||
IntVec4 v1(10, 20, 30, 40);
|
const IntVec4 v2(1, 2, 3, 4);
|
||||||
IntVec4 v2(1, 2, 3, 4);
|
|
||||||
|
|
||||||
IntVec4 vAdd = v1 + v2;
|
IntVec4 v_add = v1 + v2;
|
||||||
alignas(16) u32 res[4];
|
alignas(16) u32 res[4];
|
||||||
vAdd.Store(res);
|
v_add.store(res);
|
||||||
IAT_CHECK_EQ(res[0], 11U);
|
IAT_CHECK_EQ(res[0], 11U);
|
||||||
IAT_CHECK_EQ(res[3], 44U);
|
IAT_CHECK_EQ(res[3], 44U);
|
||||||
|
|
||||||
IntVec4 vSub = v1 - v2;
|
IntVec4 v_sub = v1 - v2;
|
||||||
vSub.Store(res);
|
v_sub.store(res);
|
||||||
IAT_CHECK_EQ(res[0], 9U);
|
IAT_CHECK_EQ(res[0], 9U);
|
||||||
|
|
||||||
IntVec4 vMul = v1 * v2;
|
IntVec4 v_mul = v1 * v2;
|
||||||
vMul.Store(res);
|
v_mul.store(res);
|
||||||
IAT_CHECK_EQ(res[0], 10U);
|
IAT_CHECK_EQ(res[0], 10U);
|
||||||
IAT_CHECK_EQ(res[2], 90U);
|
IAT_CHECK_EQ(res[2], 90U);
|
||||||
IAT_CHECK_EQ(res[3], 160U);
|
IAT_CHECK_EQ(res[3], 160U);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestBitwise()
|
auto test_bitwise() -> bool {
|
||||||
{
|
const IntVec4 v_all_ones(0xFFFFFFFF);
|
||||||
IntVec4 vAllOnes(0xFFFFFFFF);
|
const IntVec4 v_zero((u32)0);
|
||||||
IntVec4 vZero((u32) 0);
|
const IntVec4 v_pattern(0xAAAAAAAA);
|
||||||
IntVec4 vPattern(0xAAAAAAAA);
|
|
||||||
|
|
||||||
alignas(16) u32 res[4];
|
alignas(16) u32 res[4];
|
||||||
|
|
||||||
(vAllOnes & vPattern).Store(res);
|
(v_all_ones & v_pattern).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
||||||
|
|
||||||
(vZero | vPattern).Store(res);
|
(v_zero | v_pattern).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
||||||
|
|
||||||
(vAllOnes ^ vPattern).Store(res);
|
(v_all_ones ^ v_pattern).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 0x55555555U);
|
IAT_CHECK_EQ(res[0], 0x55555555U);
|
||||||
|
|
||||||
(~vPattern).Store(res);
|
(~v_pattern).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 0x55555555U);
|
IAT_CHECK_EQ(res[0], 0x55555555U);
|
||||||
|
|
||||||
IntVec4 vShift(1);
|
const IntVec4 v_shift(1);
|
||||||
(vShift << 1).Store(res);
|
(v_shift << 1).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 2U);
|
IAT_CHECK_EQ(res[0], 2U);
|
||||||
|
|
||||||
IntVec4 vShiftRight(4);
|
const IntVec4 v_shift_right(4);
|
||||||
(vShiftRight >> 1).Store(res);
|
(v_shift_right >> 1).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 2U);
|
IAT_CHECK_EQ(res[0], 2U);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestSaturation()
|
auto test_saturation() -> bool {
|
||||||
{
|
const u32 max = 0xFFFFFFFF;
|
||||||
u32 max = 0xFFFFFFFF;
|
const IntVec4 v_high(max - 10);
|
||||||
IntVec4 vHigh(max - 10);
|
const IntVec4 v_add(20);
|
||||||
IntVec4 vAdd(20);
|
|
||||||
|
|
||||||
alignas(16) u32 res[4];
|
alignas(16) u32 res[4];
|
||||||
|
|
||||||
vHigh.SatAdd(vAdd).Store(res);
|
v_high.sat_add(v_add).store(res);
|
||||||
IAT_CHECK_EQ(res[0], max);
|
IAT_CHECK_EQ(res[0], max);
|
||||||
|
|
||||||
IntVec4 vLow(10);
|
const IntVec4 v_low(10);
|
||||||
IntVec4 vSub(20);
|
const IntVec4 v_sub(20);
|
||||||
vLow.SatSub(vSub).Store(res);
|
v_low.sat_sub(v_sub).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 0U);
|
IAT_CHECK_EQ(res[0], 0U);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestAdvancedOps()
|
auto test_advanced_ops() -> bool {
|
||||||
{
|
const IntVec4 v(0, 50, 100, 150);
|
||||||
IntVec4 v(0, 50, 100, 150);
|
alignas(16) u32 res[4];
|
||||||
alignas(16) u32 res[4];
|
|
||||||
|
|
||||||
v.Clamp(40, 110).Store(res);
|
v.clamp(40, 110).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 40U);
|
IAT_CHECK_EQ(res[0], 40U);
|
||||||
IAT_CHECK_EQ(res[1], 50U);
|
IAT_CHECK_EQ(res[1], 50U);
|
||||||
IAT_CHECK_EQ(res[2], 100U);
|
IAT_CHECK_EQ(res[2], 100U);
|
||||||
IAT_CHECK_EQ(res[3], 110U);
|
IAT_CHECK_EQ(res[3], 110U);
|
||||||
|
|
||||||
IntVec4 A(2);
|
const IntVec4 a(2);
|
||||||
IntVec4 B(10);
|
const IntVec4 b(10);
|
||||||
IntVec4 C(5);
|
const IntVec4 c(5);
|
||||||
A.MultAdd(B, C).Store(res);
|
a.mult_add(b, c).store(res);
|
||||||
IAT_CHECK_EQ(res[0], 25U);
|
IAT_CHECK_EQ(res[0], 25U);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestConstructors);
|
IAT_ADD_TEST(test_constructors);
|
||||||
IAT_ADD_TEST(TestArithmetic);
|
IAT_ADD_TEST(test_arithmetic);
|
||||||
IAT_ADD_TEST(TestBitwise);
|
IAT_ADD_TEST(test_bitwise);
|
||||||
IAT_ADD_TEST(TestSaturation);
|
IAT_ADD_TEST(test_saturation);
|
||||||
IAT_ADD_TEST(TestAdvancedOps);
|
IAT_ADD_TEST(test_advanced_ops);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
141
Tests/Unit/SocketOps.cpp
Normal file
141
Tests/Unit/SocketOps.cpp
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/SocketOps.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, SocketOps)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Initialization Logic
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_initialization() -> bool {
|
||||||
|
IAT_CHECK(SocketOps::is_initialized());
|
||||||
|
|
||||||
|
// Increment ref count
|
||||||
|
const auto res = SocketOps::initialize();
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
|
// Decrement ref count
|
||||||
|
SocketOps::terminate();
|
||||||
|
|
||||||
|
// Should still be initialized (ref count > 0)
|
||||||
|
IAT_CHECK(SocketOps::is_initialized());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Port Availability Checks
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_port_availability() -> bool {
|
||||||
|
// We cannot easily guarantee a port is free or taken without binding it,
|
||||||
|
// and SocketOps doesn't expose a generic TCP bind in its public API
|
||||||
|
// (only Unix sockets).
|
||||||
|
// However, we can verify the functions execute without crashing.
|
||||||
|
|
||||||
|
const u16 port = 54321;
|
||||||
|
|
||||||
|
// These return bools, we just ensure they run.
|
||||||
|
(void)SocketOps::is_port_available_tcp(port);
|
||||||
|
(void)SocketOps::is_port_available_udp(port);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Unix Domain Socket Lifecycle (Create, Bind, Listen, Connect)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_unix_socket_lifecycle() -> bool {
|
||||||
|
const String socket_path = "iatest_ipc.sock";
|
||||||
|
|
||||||
|
// Ensure clean state
|
||||||
|
SocketOps::unlink_file(socket_path.c_str());
|
||||||
|
|
||||||
|
// 1. Create Server Socket
|
||||||
|
auto server_res = SocketOps::create_unix_socket();
|
||||||
|
IAT_CHECK(server_res.has_value());
|
||||||
|
SocketHandle server = *server_res;
|
||||||
|
|
||||||
|
// 2. Bind
|
||||||
|
auto bind_res = SocketOps::bind_unix_socket(server, socket_path.c_str());
|
||||||
|
if (!bind_res) {
|
||||||
|
// If bind fails (e.g. permissions), we clean up and fail the test
|
||||||
|
SocketOps::close(server);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Listen
|
||||||
|
auto listen_res = SocketOps::listen(server);
|
||||||
|
IAT_CHECK(listen_res.has_value());
|
||||||
|
|
||||||
|
// 4. Create Client Socket
|
||||||
|
auto client_res = SocketOps::create_unix_socket();
|
||||||
|
IAT_CHECK(client_res.has_value());
|
||||||
|
SocketHandle client = *client_res;
|
||||||
|
|
||||||
|
// 5. Connect
|
||||||
|
// Note: This relies on the OS backlog. We aren't calling accept() on the
|
||||||
|
// server, but connect() should succeed if the server is listening.
|
||||||
|
auto connect_res =
|
||||||
|
SocketOps::connect_unix_socket(client, socket_path.c_str());
|
||||||
|
IAT_CHECK(connect_res.has_value());
|
||||||
|
|
||||||
|
// 6. Cleanup
|
||||||
|
SocketOps::close(client);
|
||||||
|
SocketOps::close(server);
|
||||||
|
SocketOps::unlink_file(socket_path.c_str());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. Unix Socket Error Handling
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_unix_socket_errors() -> bool {
|
||||||
|
const String socket_path = "iatest_missing.sock";
|
||||||
|
|
||||||
|
// Ensure it doesn't exist
|
||||||
|
SocketOps::unlink_file(socket_path.c_str());
|
||||||
|
|
||||||
|
auto client_res = SocketOps::create_unix_socket();
|
||||||
|
IAT_CHECK(client_res.has_value());
|
||||||
|
SocketHandle client = *client_res;
|
||||||
|
|
||||||
|
// Should fail to connect to non-existent file
|
||||||
|
auto connect_res =
|
||||||
|
SocketOps::connect_unix_socket(client, socket_path.c_str());
|
||||||
|
IAT_CHECK_NOT(connect_res.has_value());
|
||||||
|
|
||||||
|
SocketOps::close(client);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_initialization);
|
||||||
|
IAT_ADD_TEST(test_port_availability);
|
||||||
|
IAT_ADD_TEST(test_unix_socket_lifecycle);
|
||||||
|
IAT_ADD_TEST(test_unix_socket_errors);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, SocketOps)
|
||||||
@ -13,9 +13,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/StreamReader.hpp>
|
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/StreamReader.hpp>
|
||||||
|
|
||||||
using namespace IACore;
|
using namespace IACore;
|
||||||
|
|
||||||
@ -24,71 +23,72 @@ IAT_BEGIN_BLOCK(Core, StreamReader)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Primitive Reading (u8)
|
// 1. Basic Primitive Reading (u8)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestReadUint8() {
|
auto test_read_uint8() -> bool {
|
||||||
u8 data[] = {0xAA, 0xBB, 0xCC};
|
u8 data[] = {0xAA, 0xBB, 0xCC};
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
// Read First Byte
|
// Read First Byte
|
||||||
auto val1 = reader.Read<u8>();
|
auto val1 = reader.read<u8>();
|
||||||
IAT_CHECK(val1.has_value());
|
IAT_CHECK(val1.has_value());
|
||||||
IAT_CHECK_EQ(*val1, 0xAA);
|
IAT_CHECK_EQ(*val1, 0xAA);
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (usize)1);
|
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(1));
|
||||||
|
|
||||||
// Read Second Byte
|
// Read Second Byte
|
||||||
auto val2 = reader.Read<u8>();
|
auto val2 = reader.read<u8>();
|
||||||
|
IAT_CHECK(val2.has_value());
|
||||||
IAT_CHECK_EQ(*val2, 0xBB);
|
IAT_CHECK_EQ(*val2, 0xBB);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Multi-byte Reading (Endianness check)
|
// 2. Multi-byte Reading (Endianness check)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestReadMultiByte() {
|
auto test_read_multi_byte() -> bool {
|
||||||
// 0x04030201 in Little Endian memory layout
|
// 0x04030201 in Little Endian memory layout
|
||||||
// IACore always assumes a Little Endian machine
|
// IACore always assumes a Little Endian machine
|
||||||
u8 data[] = {0x01, 0x02, 0x03, 0x04};
|
u8 data[] = {0x01, 0x02, 0x03, 0x04};
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
auto val = reader.Read<u32>();
|
auto val = reader.read<u32>();
|
||||||
IAT_CHECK(val.has_value());
|
IAT_CHECK(val.has_value());
|
||||||
|
|
||||||
IAT_CHECK_EQ(*val, (u32)0x04030201);
|
IAT_CHECK_EQ(*val, static_cast<u32>(0x04030201));
|
||||||
|
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (usize)4);
|
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(4));
|
||||||
IAT_CHECK(reader.IsEOF());
|
IAT_CHECK(reader.is_eof());
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Floating Point (Approx check)
|
// 3. Floating Point (Approx check)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestReadFloat() {
|
auto test_read_float() -> bool {
|
||||||
f32 pi = 3.14159f;
|
const f32 pi = 3.14159f;
|
||||||
// Bit-cast float to bytes for setup
|
// Bit-cast float to bytes for setup
|
||||||
u8 data[4];
|
u8 data[4];
|
||||||
std::memcpy(data, &pi, 4);
|
std::memcpy(data, &pi, 4);
|
||||||
|
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
auto val = reader.Read<f32>();
|
auto val = reader.read<f32>();
|
||||||
|
|
||||||
IAT_CHECK(val.has_value());
|
IAT_CHECK(val.has_value());
|
||||||
IAT_CHECK_APPROX(*val, pi);
|
IAT_CHECK_APPROX(*val, pi);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Batch Buffer Reading
|
// 4. Batch Buffer Reading
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestReadBuffer() {
|
auto test_read_buffer() -> bool {
|
||||||
u8 src[] = {1, 2, 3, 4, 5};
|
u8 src[] = {1, 2, 3, 4, 5};
|
||||||
u8 dst[3] = {0};
|
u8 dst[3] = {0};
|
||||||
StreamReader reader(src);
|
StreamReader reader(src);
|
||||||
|
|
||||||
// Read 3 bytes into dst
|
// Read 3 bytes into dst
|
||||||
auto res = reader.Read(dst, 3);
|
const auto res = reader.read(dst, 3);
|
||||||
IAT_CHECK(res.has_value());
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
// Verify dst content
|
// Verify dst content
|
||||||
@ -97,72 +97,72 @@ bool TestReadBuffer() {
|
|||||||
IAT_CHECK_EQ(dst[2], 3);
|
IAT_CHECK_EQ(dst[2], 3);
|
||||||
|
|
||||||
// Verify cursor
|
// Verify cursor
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (usize)3);
|
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(3));
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 5. Navigation (Seek, Skip, Remaining)
|
// 5. Navigation (Seek, Skip, Remaining)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestNavigation() {
|
auto test_navigation() -> bool {
|
||||||
u8 data[10] = {0}; // Zero init
|
u8 data[10] = {0}; // Zero init
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
IAT_CHECK_EQ(reader.Remaining(), (usize)10);
|
IAT_CHECK_EQ(reader.remaining(), static_cast<usize>(10));
|
||||||
|
|
||||||
// Skip
|
// Skip
|
||||||
reader.Skip(5);
|
reader.skip(5);
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (usize)5);
|
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(5));
|
||||||
IAT_CHECK_EQ(reader.Remaining(), (usize)5);
|
IAT_CHECK_EQ(reader.remaining(), static_cast<usize>(5));
|
||||||
|
|
||||||
// Skip clamping
|
// Skip clamping
|
||||||
reader.Skip(100); // Should clamp to 10
|
reader.skip(100); // Should clamp to 10
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (usize)10);
|
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(10));
|
||||||
IAT_CHECK(reader.IsEOF());
|
IAT_CHECK(reader.is_eof());
|
||||||
|
|
||||||
// Seek
|
// Seek
|
||||||
reader.Seek(2);
|
reader.seek(2);
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (usize)2);
|
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(2));
|
||||||
IAT_CHECK_EQ(reader.Remaining(), (usize)8);
|
IAT_CHECK_EQ(reader.remaining(), static_cast<usize>(8));
|
||||||
IAT_CHECK_NOT(reader.IsEOF());
|
IAT_CHECK_NOT(reader.is_eof());
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Error Handling (EOF Protection)
|
// 6. Error Handling (EOF Protection)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestBoundaryChecks() {
|
auto test_boundary_checks() -> bool {
|
||||||
u8 data[] = {0x00, 0x00};
|
u8 data[] = {0x00, 0x00};
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
// Valid read
|
// Valid read
|
||||||
UNUSED(reader.Read<u16>());
|
(void)reader.read<u16>();
|
||||||
IAT_CHECK(reader.IsEOF());
|
IAT_CHECK(reader.is_eof());
|
||||||
|
|
||||||
// Invalid Read Primitive
|
// Invalid Read Primitive
|
||||||
auto val = reader.Read<u8>();
|
auto val = reader.read<u8>();
|
||||||
IAT_CHECK_NOT(val.has_value()); // Should be unexpected
|
IAT_CHECK_NOT(val.has_value()); // Should be unexpected
|
||||||
|
|
||||||
// Invalid Batch Read
|
// Invalid Batch Read
|
||||||
u8 buf[1];
|
u8 buf[1];
|
||||||
auto batch = reader.Read(buf, 1);
|
auto batch = reader.read(buf, 1);
|
||||||
IAT_CHECK_NOT(batch.has_value());
|
IAT_CHECK_NOT(batch.has_value());
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Registration
|
// Registration
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestReadUint8);
|
IAT_ADD_TEST(test_read_uint8);
|
||||||
IAT_ADD_TEST(TestReadMultiByte);
|
IAT_ADD_TEST(test_read_multi_byte);
|
||||||
IAT_ADD_TEST(TestReadFloat);
|
IAT_ADD_TEST(test_read_float);
|
||||||
IAT_ADD_TEST(TestReadBuffer);
|
IAT_ADD_TEST(test_read_buffer);
|
||||||
IAT_ADD_TEST(TestNavigation);
|
IAT_ADD_TEST(test_navigation);
|
||||||
IAT_ADD_TEST(TestBoundaryChecks);
|
IAT_ADD_TEST(test_boundary_checks);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
144
Tests/Unit/StreamWriter.cpp
Normal file
144
Tests/Unit/StreamWriter.cpp
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/StreamWriter.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, StreamWriter)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Memory Writer (Dynamic Vector)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_memory_writer() -> bool {
|
||||||
|
StreamWriter writer;
|
||||||
|
|
||||||
|
// Write single byte repeated
|
||||||
|
IAT_CHECK(writer.write(static_cast<u8>(0xAA), 1).has_value());
|
||||||
|
|
||||||
|
// Write primitive (u32) - 0x12345678
|
||||||
|
// Little Endian: 78 56 34 12
|
||||||
|
const u32 val = 0x12345678;
|
||||||
|
IAT_CHECK(writer.write(val).has_value());
|
||||||
|
|
||||||
|
// Check cursor
|
||||||
|
IAT_CHECK_EQ(writer.cursor(), static_cast<usize>(1 + 4));
|
||||||
|
|
||||||
|
// Check data content
|
||||||
|
const u8 *ptr = writer.data();
|
||||||
|
IAT_CHECK_EQ(ptr[0], 0xAA);
|
||||||
|
IAT_CHECK_EQ(ptr[1], 0x78);
|
||||||
|
IAT_CHECK_EQ(ptr[4], 0x12);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Fixed Buffer Writer (Non-Owning)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_fixed_buffer() -> bool {
|
||||||
|
u8 buffer[4] = {0};
|
||||||
|
StreamWriter writer(Span<u8>(buffer, 4));
|
||||||
|
|
||||||
|
// Write 2 bytes
|
||||||
|
IAT_CHECK(writer.write(static_cast<u8>(0xFF), 2).has_value());
|
||||||
|
IAT_CHECK_EQ(writer.cursor(), static_cast<usize>(2));
|
||||||
|
|
||||||
|
// Write 2 more bytes
|
||||||
|
IAT_CHECK(writer.write(static_cast<u8>(0xEE), 2).has_value());
|
||||||
|
IAT_CHECK_EQ(writer.cursor(), static_cast<usize>(4));
|
||||||
|
|
||||||
|
// Write 1 more byte -> Should fail (Out of bounds)
|
||||||
|
const auto res = writer.write(static_cast<u8>(0x00), 1);
|
||||||
|
IAT_CHECK_NOT(res.has_value());
|
||||||
|
|
||||||
|
// Verify content
|
||||||
|
IAT_CHECK_EQ(buffer[0], 0xFF);
|
||||||
|
IAT_CHECK_EQ(buffer[1], 0xFF);
|
||||||
|
IAT_CHECK_EQ(buffer[2], 0xEE);
|
||||||
|
IAT_CHECK_EQ(buffer[3], 0xEE);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. File Writer
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_file_writer() -> bool {
|
||||||
|
const Path path = "test_stream_writer.bin";
|
||||||
|
|
||||||
|
// Ensure clean state
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
std::filesystem::remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto res = StreamWriter::create_from_file(path);
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
StreamWriter writer = std::move(*res);
|
||||||
|
|
||||||
|
const String hello = "Hello World";
|
||||||
|
IAT_CHECK(writer.write(hello.data(), hello.size()).has_value());
|
||||||
|
|
||||||
|
// Explicit flush
|
||||||
|
IAT_CHECK(writer.flush().has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file content via FileOps
|
||||||
|
auto read_res = FileOps::read_binary_file(path);
|
||||||
|
IAT_CHECK(read_res.has_value());
|
||||||
|
|
||||||
|
const String read_str(reinterpret_cast<const char *>(read_res->data()),
|
||||||
|
read_res->size());
|
||||||
|
IAT_CHECK_EQ(read_str, String("Hello World"));
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
std::filesystem::remove(path);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. Primitive Types
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_primitives() -> bool {
|
||||||
|
StreamWriter writer;
|
||||||
|
|
||||||
|
const f32 f = 1.5f;
|
||||||
|
const u64 big = 0xDEADBEEFCAFEBABE;
|
||||||
|
|
||||||
|
IAT_CHECK(writer.write(f).has_value());
|
||||||
|
IAT_CHECK(writer.write(big).has_value());
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(writer.cursor(), sizeof(f32) + sizeof(u64));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_memory_writer);
|
||||||
|
IAT_ADD_TEST(test_fixed_buffer);
|
||||||
|
IAT_ADD_TEST(test_file_writer);
|
||||||
|
IAT_ADD_TEST(test_primitives);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, StreamWriter)
|
||||||
125
Tests/Unit/StringOps.cpp
Normal file
125
Tests/Unit/StringOps.cpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/StringOps.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, StringOps)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Base64 Encoding
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_base64_encode() -> bool {
|
||||||
|
// Case 1: Standard text
|
||||||
|
{
|
||||||
|
const String s = "Hello World";
|
||||||
|
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||||
|
const String encoded = StringOps::encode_base64(data);
|
||||||
|
IAT_CHECK_EQ(encoded, String("SGVsbG8gV29ybGQ="));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Padding Logic (1 byte -> 2 pad)
|
||||||
|
{
|
||||||
|
const String s = "M";
|
||||||
|
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||||
|
const String encoded = StringOps::encode_base64(data);
|
||||||
|
IAT_CHECK_EQ(encoded, String("TQ=="));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3: Padding Logic (2 bytes -> 1 pad)
|
||||||
|
{
|
||||||
|
const String s = "Ma";
|
||||||
|
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||||
|
const String encoded = StringOps::encode_base64(data);
|
||||||
|
IAT_CHECK_EQ(encoded, String("TWE="));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 4: Padding Logic (3 bytes -> 0 pad)
|
||||||
|
{
|
||||||
|
const String s = "Man";
|
||||||
|
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||||
|
const String encoded = StringOps::encode_base64(data);
|
||||||
|
IAT_CHECK_EQ(encoded, String("TWFu"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 5: Empty
|
||||||
|
{
|
||||||
|
const String encoded = StringOps::encode_base64({});
|
||||||
|
IAT_CHECK(encoded.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Base64 Decoding
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_base64_decode() -> bool {
|
||||||
|
// Case 1: Standard text
|
||||||
|
{
|
||||||
|
const String encoded = "SGVsbG8gV29ybGQ=";
|
||||||
|
const Vec<u8> decoded = StringOps::decode_base64(encoded);
|
||||||
|
const String result(reinterpret_cast<const char *>(decoded.data()),
|
||||||
|
decoded.size());
|
||||||
|
IAT_CHECK_EQ(result, String("Hello World"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Empty
|
||||||
|
{
|
||||||
|
const Vec<u8> decoded = StringOps::decode_base64("");
|
||||||
|
IAT_CHECK(decoded.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Round Trip (Binary Data)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_base64_round_trip() -> bool {
|
||||||
|
Vec<u8> original;
|
||||||
|
original.reserve(256);
|
||||||
|
for (usize i = 0; i < 256; ++i) {
|
||||||
|
original.push_back(static_cast<u8>(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
const String encoded = StringOps::encode_base64(original);
|
||||||
|
const Vec<u8> decoded = StringOps::decode_base64(encoded);
|
||||||
|
|
||||||
|
IAT_CHECK_EQ(original.size(), decoded.size());
|
||||||
|
|
||||||
|
bool match = true;
|
||||||
|
for (usize i = 0; i < original.size(); ++i) {
|
||||||
|
if (original[i] != decoded[i]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IAT_CHECK(match);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_base64_encode);
|
||||||
|
IAT_ADD_TEST(test_base64_decode);
|
||||||
|
IAT_ADD_TEST(test_base64_round_trip);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, StringOps)
|
||||||
@ -13,9 +13,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/Utils.hpp>
|
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/Utils.hpp>
|
||||||
|
|
||||||
using namespace IACore;
|
using namespace IACore;
|
||||||
|
|
||||||
@ -23,16 +22,14 @@ using namespace IACore;
|
|||||||
// Test Structs for Hashing (Must be defined at Global Scope)
|
// Test Structs for Hashing (Must be defined at Global Scope)
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
struct TestVec3
|
struct TestVec3 {
|
||||||
{
|
f32 x, y, z;
|
||||||
f32 x, y, z;
|
|
||||||
|
|
||||||
// Equality operator required for hash maps, though strictly
|
// Equality operator required for hash maps, though strictly
|
||||||
// the hash function itself doesn't need it, it's good practice to test both.
|
// the hash function itself doesn't need it, it's good practice to test both.
|
||||||
bool operator==(const TestVec3 &other) const
|
bool operator==(const TestVec3 &other) const {
|
||||||
{
|
return x == other.x && y == other.y && z == other.z;
|
||||||
return x == other.x && y == other.y && z == other.z;
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inject the hash specialization into the ankerl namespace
|
// Inject the hash specialization into the ankerl namespace
|
||||||
@ -48,173 +45,167 @@ IAT_BEGIN_BLOCK(Core, Utils)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Binary <-> Hex String Conversion
|
// 1. Binary <-> Hex String Conversion
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestHexConversion()
|
auto test_hex_conversion() -> bool {
|
||||||
{
|
// A. Binary To Hex
|
||||||
// A. Binary To Hex
|
u8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
|
||||||
u8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
|
String hex = Utils::binary_to_hex_string(bin);
|
||||||
String hex = IACore::Utils::BinaryToHexString(bin);
|
|
||||||
|
|
||||||
IAT_CHECK_EQ(hex, String("DEADBEEF00FF"));
|
IAT_CHECK_EQ(hex, String("DEADBEEF00FF"));
|
||||||
|
|
||||||
// B. Hex To Binary (Valid Upper)
|
// B. Hex To Binary (Valid Upper)
|
||||||
auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF");
|
auto res_upper = Utils::hex_string_to_binary("DEADBEEF00FF");
|
||||||
IAT_CHECK(resUpper.has_value());
|
IAT_CHECK(res_upper.has_value());
|
||||||
IAT_CHECK_EQ(resUpper->size(), (usize) 6);
|
IAT_CHECK_EQ(res_upper->size(), static_cast<usize>(6));
|
||||||
IAT_CHECK_EQ((*resUpper)[0], 0xDE);
|
IAT_CHECK_EQ((*res_upper)[0], 0xDE);
|
||||||
IAT_CHECK_EQ((*resUpper)[5], 0xFF);
|
IAT_CHECK_EQ((*res_upper)[5], 0xFF);
|
||||||
|
|
||||||
// C. Hex To Binary (Valid Lower/Mixed)
|
// C. Hex To Binary (Valid Lower/Mixed)
|
||||||
auto resLower = IACore::Utils::HexStringToBinary("deadbeef00ff");
|
auto res_lower = Utils::hex_string_to_binary("deadbeef00ff");
|
||||||
IAT_CHECK(resLower.has_value());
|
IAT_CHECK(res_lower.has_value());
|
||||||
IAT_CHECK_EQ((*resLower)[0], 0xDE);
|
IAT_CHECK_EQ((*res_lower)[0], 0xDE);
|
||||||
|
|
||||||
// D. Round Trip Integrity
|
// D. Round Trip Integrity
|
||||||
Vec<u8> original = {1, 2, 3, 4, 5};
|
Vec<u8> original = {1, 2, 3, 4, 5};
|
||||||
String s = IACore::Utils::BinaryToHexString(original);
|
String s = Utils::binary_to_hex_string(original);
|
||||||
auto back = IACore::Utils::HexStringToBinary(s);
|
auto back = Utils::hex_string_to_binary(s);
|
||||||
IAT_CHECK(back.has_value());
|
IAT_CHECK(back.has_value());
|
||||||
IAT_CHECK_EQ(original.size(), back->size());
|
IAT_CHECK_EQ(original.size(), back->size());
|
||||||
IAT_CHECK_EQ(original[2], (*back)[2]);
|
IAT_CHECK_EQ(original[2], (*back)[2]);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Hex Error Handling
|
// 2. Hex Error Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestHexErrors()
|
auto test_hex_errors() -> bool {
|
||||||
{
|
// Odd Length
|
||||||
// Odd Length
|
auto odd = Utils::hex_string_to_binary("ABC");
|
||||||
auto odd = IACore::Utils::HexStringToBinary("ABC");
|
IAT_CHECK_NOT(odd.has_value());
|
||||||
IAT_CHECK_NOT(odd.has_value());
|
|
||||||
|
|
||||||
// Invalid Characters
|
// Invalid Characters
|
||||||
auto invalid = IACore::Utils::HexStringToBinary("ZZTOP");
|
auto invalid = Utils::hex_string_to_binary("ZZTOP");
|
||||||
IAT_CHECK_NOT(invalid.has_value());
|
IAT_CHECK_NOT(invalid.has_value());
|
||||||
|
|
||||||
// Empty string is valid (empty vector)
|
// Empty string is valid (empty vector)
|
||||||
auto empty = IACore::Utils::HexStringToBinary("");
|
auto empty = Utils::hex_string_to_binary("");
|
||||||
IAT_CHECK(empty.has_value());
|
IAT_CHECK(empty.has_value());
|
||||||
IAT_CHECK_EQ(empty->size(), (usize) 0);
|
IAT_CHECK_EQ(empty->size(), static_cast<usize>(0));
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Algorithms: Sorting
|
// 3. Algorithms: Sorting
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestSort()
|
auto test_sort() -> bool {
|
||||||
{
|
Vec<i32> nums = {5, 1, 4, 2, 3};
|
||||||
Vec<int> nums = {5, 1, 4, 2, 3};
|
|
||||||
|
|
||||||
IACore::Utils::Sort(nums);
|
Utils::sort(nums);
|
||||||
|
|
||||||
IAT_CHECK_EQ(nums[0], 1);
|
IAT_CHECK_EQ(nums[0], 1);
|
||||||
IAT_CHECK_EQ(nums[1], 2);
|
IAT_CHECK_EQ(nums[1], 2);
|
||||||
IAT_CHECK_EQ(nums[2], 3);
|
IAT_CHECK_EQ(nums[2], 3);
|
||||||
IAT_CHECK_EQ(nums[3], 4);
|
IAT_CHECK_EQ(nums[3], 4);
|
||||||
IAT_CHECK_EQ(nums[4], 5);
|
IAT_CHECK_EQ(nums[4], 5);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Algorithms: Binary Search (Left/Right)
|
// 4. Algorithms: Binary Search (Left/Right)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestBinarySearch()
|
auto test_binary_search() -> bool {
|
||||||
{
|
// Must be sorted for Binary Search
|
||||||
// Must be sorted for Binary Search
|
Vec<i32> nums = {10, 20, 20, 20, 30};
|
||||||
Vec<int> nums = {10, 20, 20, 20, 30};
|
|
||||||
|
|
||||||
// Search Left (Lower Bound) -> First element >= value
|
// Search Left (Lower Bound) -> First element >= value
|
||||||
auto itLeft = IACore::Utils::BinarySearchLeft(nums, 20);
|
auto it_left = Utils::binary_search_left(nums, 20);
|
||||||
IAT_CHECK(itLeft != nums.end());
|
IAT_CHECK(it_left != nums.end());
|
||||||
IAT_CHECK_EQ(*itLeft, 20);
|
IAT_CHECK_EQ(*it_left, 20);
|
||||||
IAT_CHECK_EQ(std::distance(nums.begin(), itLeft), 1); // Index 1 is first 20
|
IAT_CHECK_EQ(std::distance(nums.begin(), it_left), 1); // Index 1 is first 20
|
||||||
|
|
||||||
// Search Right (Upper Bound) -> First element > value
|
// Search Right (Upper Bound) -> First element > value
|
||||||
auto itRight = IACore::Utils::BinarySearchRight(nums, 20);
|
auto it_right = Utils::binary_search_right(nums, 20);
|
||||||
IAT_CHECK(itRight != nums.end());
|
IAT_CHECK(it_right != nums.end());
|
||||||
IAT_CHECK_EQ(*itRight, 30); // Points to 30
|
IAT_CHECK_EQ(*it_right, 30); // Points to 30
|
||||||
IAT_CHECK_EQ(std::distance(nums.begin(), itRight), 4); // Index 4
|
IAT_CHECK_EQ(std::distance(nums.begin(), it_right), 4); // Index 4
|
||||||
|
|
||||||
// Search for non-existent
|
// Search for non-existent
|
||||||
auto itFail = IACore::Utils::BinarySearchLeft(nums, 99);
|
auto it_fail = Utils::binary_search_left(nums, 99);
|
||||||
IAT_CHECK(itFail == nums.end());
|
IAT_CHECK(it_fail == nums.end());
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 5. Hashing Basics
|
// 5. Hashing Basics
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestHashBasics()
|
auto test_hash_basics() -> bool {
|
||||||
{
|
u64 h1 = Utils::compute_hash(10, 20.5f, "Hello");
|
||||||
u64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
|
u64 h2 = Utils::compute_hash(10, 20.5f, "Hello");
|
||||||
u64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
|
u64 h3 = Utils::compute_hash(10, 20.5f, "World");
|
||||||
u64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World");
|
|
||||||
|
|
||||||
// Determinism
|
// Determinism
|
||||||
IAT_CHECK_EQ(h1, h2);
|
IAT_CHECK_EQ(h1, h2);
|
||||||
|
|
||||||
// Differentiation
|
// Differentiation
|
||||||
IAT_CHECK_NEQ(h1, h3);
|
IAT_CHECK_NEQ(h1, h3);
|
||||||
|
|
||||||
// Order sensitivity (Golden ratio combine should care about order)
|
// Order sensitivity (Golden ratio combine should care about order)
|
||||||
// Hash(A, B) != Hash(B, A)
|
// Hash(A, B) != Hash(B, A)
|
||||||
u64 orderA = IACore::Utils::ComputeHash(1, 2);
|
u64 order_a = Utils::compute_hash(1, 2);
|
||||||
u64 orderB = IACore::Utils::ComputeHash(2, 1);
|
u64 order_b = Utils::compute_hash(2, 1);
|
||||||
IAT_CHECK_NEQ(orderA, orderB);
|
IAT_CHECK_NEQ(order_a, order_b);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Macro Verification (IA_MAKE_HASHABLE)
|
// 6. Macro Verification (IA_MAKE_HASHABLE)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
bool TestHashMacro()
|
auto test_hash_macro() -> bool {
|
||||||
{
|
TestVec3 v1{1.0f, 2.0f, 3.0f};
|
||||||
TestVec3 v1{1.0f, 2.0f, 3.0f};
|
TestVec3 v2{1.0f, 2.0f, 3.0f};
|
||||||
TestVec3 v2{1.0f, 2.0f, 3.0f};
|
TestVec3 v3{1.0f, 2.0f, 4.0f};
|
||||||
TestVec3 v3{1.0f, 2.0f, 4.0f};
|
|
||||||
|
|
||||||
ankerl::unordered_dense::hash<TestVec3> hasher;
|
ankerl::unordered_dense::hash<TestVec3> hasher;
|
||||||
|
|
||||||
u64 h1 = hasher(v1);
|
u64 h1 = hasher(v1);
|
||||||
u64 h2 = hasher(v2);
|
u64 h2 = hasher(v2);
|
||||||
u64 h3 = hasher(v3);
|
u64 h3 = hasher(v3);
|
||||||
|
|
||||||
IAT_CHECK_EQ(h1, h2); // Same content = same hash
|
IAT_CHECK_EQ(h1, h2); // Same content = same hash
|
||||||
IAT_CHECK_NEQ(h1, h3); // Different content = different hash
|
IAT_CHECK_NEQ(h1, h3); // Different content = different hash
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// Verify ComputeHash integration
|
// Verify ComputeHash integration
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
u64 hManual = 0;
|
u64 h_manual = 0;
|
||||||
IACore::Utils::HashCombine(hManual, v1);
|
Utils::hash_combine(h_manual, v1);
|
||||||
|
|
||||||
u64 hWrapper = IACore::Utils::ComputeHash(v1);
|
u64 h_wrapper = Utils::compute_hash(v1);
|
||||||
|
|
||||||
// This proves ComputeHash found the specialization and mixed it correctly
|
// This proves ComputeHash found the specialization and mixed it correctly
|
||||||
IAT_CHECK_EQ(hManual, hWrapper);
|
IAT_CHECK_EQ(h_manual, h_wrapper);
|
||||||
|
|
||||||
// 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);
|
IAT_CHECK_NEQ(h1, h_wrapper);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Registration
|
// Registration
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
IAT_BEGIN_TEST_LIST()
|
IAT_BEGIN_TEST_LIST()
|
||||||
IAT_ADD_TEST(TestHexConversion);
|
IAT_ADD_TEST(test_hex_conversion);
|
||||||
IAT_ADD_TEST(TestHexErrors);
|
IAT_ADD_TEST(test_hex_errors);
|
||||||
IAT_ADD_TEST(TestSort);
|
IAT_ADD_TEST(test_sort);
|
||||||
IAT_ADD_TEST(TestBinarySearch);
|
IAT_ADD_TEST(test_binary_search);
|
||||||
IAT_ADD_TEST(TestHashBasics);
|
IAT_ADD_TEST(test_hash_basics);
|
||||||
IAT_ADD_TEST(TestHashMacro);
|
IAT_ADD_TEST(test_hash_macro);
|
||||||
IAT_END_TEST_LIST()
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
IAT_END_BLOCK()
|
IAT_END_BLOCK()
|
||||||
|
|||||||
135
Tests/Unit/XML.cpp
Normal file
135
Tests/Unit/XML.cpp
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <IACore/IATest.hpp>
|
||||||
|
#include <IACore/XML.hpp>
|
||||||
|
|
||||||
|
using namespace IACore;
|
||||||
|
|
||||||
|
IAT_BEGIN_BLOCK(Core, XML)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Basic String Parsing
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_parse_string() -> bool {
|
||||||
|
const String xml_content = R"(
|
||||||
|
<root>
|
||||||
|
<item id="1">Value1</item>
|
||||||
|
<item id="2">Value2</item>
|
||||||
|
</root>
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto res = XML::parse_from_string(xml_content);
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
|
auto &doc = *res;
|
||||||
|
auto root = doc.child("root");
|
||||||
|
IAT_CHECK(root);
|
||||||
|
|
||||||
|
auto item1 = root.find_child_by_attribute("item", "id", "1");
|
||||||
|
IAT_CHECK(item1);
|
||||||
|
IAT_CHECK_EQ(String(item1.child_value()), String("Value1"));
|
||||||
|
|
||||||
|
auto item2 = root.find_child_by_attribute("item", "id", "2");
|
||||||
|
IAT_CHECK(item2);
|
||||||
|
IAT_CHECK_EQ(String(item2.child_value()), String("Value2"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Error Handling
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_parse_error() -> bool {
|
||||||
|
const String invalid_xml = "<root><unclosed>";
|
||||||
|
auto res = XML::parse_from_string(invalid_xml);
|
||||||
|
IAT_CHECK_NOT(res.has_value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Serialization
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_serialize() -> bool {
|
||||||
|
const String xml_content = "<root><node>Text</node></root>";
|
||||||
|
auto res = XML::parse_from_string(xml_content);
|
||||||
|
IAT_CHECK(res.has_value());
|
||||||
|
|
||||||
|
String output = XML::serialize_to_string(*res);
|
||||||
|
|
||||||
|
// Basic containment check as formatting might vary
|
||||||
|
IAT_CHECK(output.find("<root>") != String::npos);
|
||||||
|
IAT_CHECK(output.find("<node>Text</node>") != String::npos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. String Escaping
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_escape() -> bool {
|
||||||
|
const String raw = "< & > \" '";
|
||||||
|
const String escaped = XML::escape_xml_string(raw);
|
||||||
|
|
||||||
|
// Check for standard XML entities
|
||||||
|
IAT_CHECK(escaped.find("<") != String::npos);
|
||||||
|
IAT_CHECK(escaped.find("&") != String::npos);
|
||||||
|
IAT_CHECK(escaped.find(">") != String::npos);
|
||||||
|
IAT_CHECK(escaped.find(""") != String::npos);
|
||||||
|
IAT_CHECK(escaped.find("'") != String::npos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 5. File I/O Integration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
auto test_file_io() -> bool {
|
||||||
|
const Path path = "test_temp_xml_doc.xml";
|
||||||
|
const String content = "<config><ver>1.0</ver></config>";
|
||||||
|
|
||||||
|
// 1. Write Test File
|
||||||
|
auto write_res = FileOps::write_text_file(path, content, true);
|
||||||
|
IAT_CHECK(write_res.has_value());
|
||||||
|
|
||||||
|
// 2. Parse from File
|
||||||
|
auto parse_res = XML::parse_from_file(path);
|
||||||
|
IAT_CHECK(parse_res.has_value());
|
||||||
|
|
||||||
|
auto &doc = *parse_res;
|
||||||
|
IAT_CHECK_EQ(String(doc.child("config").child("ver").child_value()),
|
||||||
|
String("1.0"));
|
||||||
|
|
||||||
|
// 3. Cleanup
|
||||||
|
std::filesystem::remove(path);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
IAT_BEGIN_TEST_LIST()
|
||||||
|
IAT_ADD_TEST(test_parse_string);
|
||||||
|
IAT_ADD_TEST(test_parse_error);
|
||||||
|
IAT_ADD_TEST(test_serialize);
|
||||||
|
IAT_ADD_TEST(test_escape);
|
||||||
|
IAT_ADD_TEST(test_file_io);
|
||||||
|
IAT_END_TEST_LIST()
|
||||||
|
|
||||||
|
IAT_END_BLOCK()
|
||||||
|
|
||||||
|
IAT_REGISTER_ENTRY(Core, XML)
|
||||||
Reference in New Issue
Block a user