From fa7045e8c34269ee4c43b25a7242246cd30d94cd Mon Sep 17 00:00:00 2001 From: dev0 Date: Sun, 23 Nov 2025 10:00:38 +0530 Subject: [PATCH] Test 2/2 --- Src/IACore/inc/IACore/Process.hpp | 232 ++++++++++++++++++------------ Src/IACore/inc/IACore/Utils.hpp | 23 ++- Tests/Unit/Utils.cpp | 23 ++- 3 files changed, 177 insertions(+), 101 deletions(-) diff --git a/Src/IACore/inc/IACore/Process.hpp b/Src/IACore/inc/IACore/Process.hpp index d0c894e..299ab63 100644 --- a/Src/IACore/inc/IACore/Process.hpp +++ b/Src/IACore/inc/IACore/Process.hpp @@ -1,16 +1,16 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects +// IACore-OSS; The Core Library for All IA Open Source Projects // Copyright (C) 2025 IAS (ias@iasoft.dev) -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -18,114 +18,129 @@ #include -namespace IACore { +namespace IACore +{ - class Process { - public: + class Process + { + public: // --------------------------------------------------------------------- // Static One-Shot Execution // --------------------------------------------------------------------- - + // Returns Exit Code or Error String // callback receives distinct lines of output (stdout + stderr merged) - STATIC tl::expected Run( - CONST String& cmd, - CONST String& args, - Function onOutputLine - ) { - #if IA_PLATFORM_WINDOWS - return RunWindows(cmd, args, onOutputLine); - #else - return RunPosix(cmd, args, onOutputLine); - #endif + STATIC tl::expected Run(CONST String &cmd, CONST String &args, + Function onOutputLine) + { +#if IA_PLATFORM_WINDOWS + return RunWindows(cmd, args, onOutputLine); +#else + return RunPosix(cmd, args, onOutputLine); +#endif } - private: + private: // --------------------------------------------------------------------- // Output Buffering Helper // Splits raw chunks into lines, preserving partial lines across chunks // --------------------------------------------------------------------- - struct LineBuffer { + struct LineBuffer + { String accumulator; - Function& callback; + Function &callback; - VOID Append(const char* data, SIZE_T size) { + VOID Append(const char *data, SIZE_T size) + { SIZE_T start = 0; - for (SIZE_T i = 0; i < size; ++i) { - if (data[i] == '\n' || data[i] == '\r') { + for (SIZE_T i = 0; i < size; ++i) + { + if (data[i] == '\n' || data[i] == '\r') + { // Flush accumulator + current chunk - if (!accumulator.empty()) { + if (!accumulator.empty()) + { accumulator.append(data + start, i - start); - if (!accumulator.empty()) callback(accumulator); + if (!accumulator.empty()) + callback(accumulator); accumulator.clear(); - } else { - // Zero copy optimization for pure lines in one chunk - if (i > start) callback(StringView(data + start, i - start)); } - + else + { + // Zero copy optimization for pure lines in one chunk + if (i > start) + callback(StringView(data + start, i - start)); + } + // Skip \r\n sequence if needed, or just start next - if (data[i] == '\r' && i + 1 < size && data[i+1] == '\n') i++; + if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n') + i++; start = i + 1; } } // Save remaining partial line - if (start < size) { + if (start < size) + { accumulator.append(data + start, size - start); } } - VOID Flush() { - if (!accumulator.empty()) { + VOID Flush() + { + if (!accumulator.empty()) + { callback(accumulator); accumulator.clear(); } } }; - // --------------------------------------------------------------------- - // Windows Implementation - // --------------------------------------------------------------------- - #if IA_PLATFORM_WINDOWS - STATIC tl::expected RunWindows(CONST String& cmd, CONST String& args, Function cb) { - SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; // Allow inheritance +// --------------------------------------------------------------------- +// Windows Implementation +// --------------------------------------------------------------------- +#if IA_PLATFORM_WINDOWS + STATIC tl::expected RunWindows(CONST String &cmd, CONST String &args, + Function cb) + { + SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance HANDLE hRead = NULL, hWrite = NULL; - if (!CreatePipe(&hRead, &hWrite, &saAttr, 0)) + if (!CreatePipe(&hRead, &hWrite, &saAttr, 0)) return tl::make_unexpected("Failed to create pipe"); // Ensure the read handle to the pipe for STDOUT is NOT inherited - if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0)) + if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0)) return tl::make_unexpected("Failed to secure pipe handles"); - STARTUPINFOA si = { sizeof(STARTUPINFOA) }; + STARTUPINFOA si = {sizeof(STARTUPINFOA)}; si.dwFlags |= STARTF_USESTDHANDLES; si.hStdOutput = hWrite; - si.hStdError = hWrite; // Merge stderr - si.hStdInput = NULL; // No input + si.hStdError = hWrite; // Merge stderr + si.hStdInput = NULL; // No input + + PROCESS_INFORMATION pi = {0}; - PROCESS_INFORMATION pi = { 0 }; - // Windows command line needs to be mutable and concatenated String commandLine = BuildString("\"", cmd, "\" ", args); - BOOL success = CreateProcessA( - NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi - ); + BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); // Important: Close write end in parent, otherwise ReadFile never returns EOF! - CloseHandle(hWrite); + CloseHandle(hWrite); - if (!success) { + if (!success) + { CloseHandle(hRead); return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError())); } // Read Loop - LineBuffer lineBuf{ "", cb }; + LineBuffer lineBuf{"", cb}; DWORD bytesRead; CHAR buffer[4096]; - while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0) { + while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0) + { lineBuf.Append(buffer, bytesRead); } lineBuf.Flush(); @@ -141,59 +156,95 @@ namespace IACore { return static_cast(exitCode); } - #endif +#endif - // --------------------------------------------------------------------- - // POSIX (Linux/Mac) Implementation - // --------------------------------------------------------------------- - #if IA_PLATFORM_UNIX - STATIC tl::expected RunPosix(CONST String& cmd, CONST String& args, Function cb) { +// --------------------------------------------------------------------- +// POSIX (Linux/Mac) Implementation +// --------------------------------------------------------------------- +#if IA_PLATFORM_UNIX + STATIC tl::expected RunPosix(CONST String &cmd, CONST String &args, + Function cb) + { int pipefd[2]; - if (pipe(pipefd) == -1) return tl::make_unexpected("Failed to create pipe"); + if (pipe(pipefd) == -1) + return tl::make_unexpected("Failed to create pipe"); pid_t pid = fork(); - if (pid == -1) { + if (pid == -1) + { return tl::make_unexpected("Failed to fork process"); - } - else if (pid == 0) { + } + else if (pid == 0) + { // --- Child Process --- - close(pipefd[0]); // Close read end + close(pipefd[0]); - // Redirect stdout/stderr to pipe dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); - close(pipefd[1]); // Close original write end + close(pipefd[1]); - // Prepare Args. execvp requires an array of char* - // This is quick and dirty splitting. - // In a real engine you might pass args as a vector. - std::vector argv; - std::string cmdStr = cmd; // copy to modify if needed + // --- ARGUMENT PARSING START --- + std::vector argStorage; // To keep strings alive + std::vector argv; + + std::string cmdStr = cmd; argv.push_back(cmdStr.data()); - // Split args string by space (simplistic) - // Better: Pass vector to Run() - std::string argsCopy = args; - char* token = strtok(argsCopy.data(), " "); - while (token) { - argv.push_back(token); - token = strtok(NULL, " "); + // Manual Quote-Aware Splitter + std::string currentToken; + bool inQuotes = false; + + for (char c : args) + { + if (c == '\"') + { + inQuotes = !inQuotes; + // Determine if you want to keep the quotes or strip them. + // Usually for execvp, you strip them so the shell receives the raw content. + continue; + } + + if (c == ' ' && !inQuotes) + { + if (!currentToken.empty()) + { + argStorage.push_back(currentToken); + currentToken.clear(); + } + } + else + { + currentToken += c; + } + } + if (!currentToken.empty()) + { + argStorage.push_back(currentToken); + } + + // Build char* array from the std::string storage + for (auto &s : argStorage) + { + argv.push_back(s.data()); } argv.push_back(nullptr); + // --- ARGUMENT PARSING END --- execvp(argv[0], argv.data()); - _exit(127); // If execvp fails - } - else { + _exit(127); + } + else + { // --- Parent Process --- - close(pipefd[1]); // Close write end + close(pipefd[1]); - LineBuffer lineBuf{ "", cb }; + LineBuffer lineBuf{"", cb}; char buffer[4096]; ssize_t count; - while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) { + while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) + { lineBuf.Append(buffer, count); } lineBuf.Flush(); @@ -201,11 +252,12 @@ namespace IACore { int status; waitpid(pid, &status, 0); - - if (WIFEXITED(status)) return WEXITSTATUS(status); - return -1; // Crashed or killed + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + return -1; } } - #endif +#endif }; -} \ No newline at end of file +} // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/Utils.hpp b/Src/IACore/inc/IACore/Utils.hpp index 658968e..8fedd95 100644 --- a/Src/IACore/inc/IACore/Utils.hpp +++ b/Src/IACore/inc/IACore/Utils.hpp @@ -101,13 +101,24 @@ namespace IACore // ------------------------------------------------------------------------- template INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v) { - // Use Ankerl's high-speed hasher for the individual type - // This automatically handles ints, floats, strings, etc. efficiently. - auto hasher = ankerl::unordered_dense::hash(); - UINT64 h = hasher(v); + UINT64 h; + + // 1. Compile-Time check: Can this be treated as a string view? + // This catches "Literal Strings" (char[N]), const char*, and std::string + if constexpr (std::is_constructible_v) + { + std::string_view sv(v); + auto hasher = ankerl::unordered_dense::hash(); + h = hasher(sv); + } + else + { + // 2. Standard types (int, float, custom structs) + auto hasher = ankerl::unordered_dense::hash(); + h = hasher(v); + } // 0x9e3779b97f4a7c15 is the 64-bit golden ratio (phi) approximation - // This spreads bits to avoid collisions in the hash table. seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2); } @@ -123,7 +134,7 @@ namespace IACore } // ------------------------------------------------------------------------- - // F;pat Hasher + // Flat Hasher // Allows: IACore::ComputeHashFlat(x, y, z); // ------------------------------------------------------------------------- template INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members) diff --git a/Tests/Unit/Utils.cpp b/Tests/Unit/Utils.cpp index 37b9b67..3a38ffa 100644 --- a/Tests/Unit/Utils.cpp +++ b/Tests/Unit/Utils.cpp @@ -59,7 +59,7 @@ IAT_BEGIN_BLOCK(Core, Utils) // B. Hex To Binary (Valid Upper) auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF"); IAT_CHECK(resUpper.has_value()); - IAT_CHECK_EQ(resUpper->size(), 6); + IAT_CHECK_EQ(resUpper->size(), (SIZE_T)6); IAT_CHECK_EQ((*resUpper)[0], 0xDE); IAT_CHECK_EQ((*resUpper)[5], 0xFF); @@ -189,11 +189,24 @@ IAT_BEGIN_BLOCK(Core, Utils) IAT_CHECK_EQ(h1, h2); // Same content = same hash IAT_CHECK_NEQ(h1, h3); // Different content = different hash - // Verify it works with ComputeHash when passed as object - // (Assuming you extend ComputeHash to handle custom types via the specialized hasher) - // Since ComputeHash uses ankerl::unordered_dense::hash internally, this should work: + // ------------------------------------------------------------- + // Verify ComputeHash integration + // ------------------------------------------------------------- + + // We cannot check EQ(h1, ComputeHash(v1)) because ComputeHash applies + // one extra layer of "Golden Ratio Mixing" on top of the object's hash. + // Instead, we verify that ComputeHash behaves exactly like a manual HashCombine. + + UINT64 hManual = 0; + IACore::Utils::HashCombine(hManual, v1); + UINT64 hWrapper = IACore::Utils::ComputeHash(v1); - IAT_CHECK_EQ(h1, hWrapper); + + // This proves ComputeHash found the specialization and mixed it correctly + IAT_CHECK_EQ(hManual, hWrapper); + + // Optional: Verify the avalanche effect took place (hWrapper should NOT be h1) + IAT_CHECK_NEQ(h1, hWrapper); return TRUE; }