Test 2/2
This commit is contained in:
@ -1,16 +1,16 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
@ -18,114 +18,129 @@
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
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<INT32, String> Run(
|
||||
CONST String& cmd,
|
||||
CONST String& args,
|
||||
Function<VOID(StringView)> onOutputLine
|
||||
) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return RunWindows(cmd, args, onOutputLine);
|
||||
#else
|
||||
return RunPosix(cmd, args, onOutputLine);
|
||||
#endif
|
||||
STATIC tl::expected<INT32, String> Run(CONST String &cmd, CONST String &args,
|
||||
Function<VOID(StringView)> 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<VOID(StringView)>& callback;
|
||||
Function<VOID(StringView)> &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<INT32, String> RunWindows(CONST String& cmd, CONST String& args, Function<VOID(StringView)> cb) {
|
||||
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; // Allow inheritance
|
||||
// ---------------------------------------------------------------------
|
||||
// Windows Implementation
|
||||
// ---------------------------------------------------------------------
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
STATIC tl::expected<INT32, String> RunWindows(CONST String &cmd, CONST String &args,
|
||||
Function<VOID(StringView)> 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<INT32>(exitCode);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// POSIX (Linux/Mac) Implementation
|
||||
// ---------------------------------------------------------------------
|
||||
#if IA_PLATFORM_UNIX
|
||||
STATIC tl::expected<INT32, String> RunPosix(CONST String& cmd, CONST String& args, Function<VOID(StringView)> cb) {
|
||||
// ---------------------------------------------------------------------
|
||||
// POSIX (Linux/Mac) Implementation
|
||||
// ---------------------------------------------------------------------
|
||||
#if IA_PLATFORM_UNIX
|
||||
STATIC tl::expected<INT32, String> RunPosix(CONST String &cmd, CONST String &args,
|
||||
Function<VOID(StringView)> 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<string>.
|
||||
std::vector<char*> argv;
|
||||
std::string cmdStr = cmd; // copy to modify if needed
|
||||
// --- ARGUMENT PARSING START ---
|
||||
std::vector<std::string> argStorage; // To keep strings alive
|
||||
std::vector<char *> argv;
|
||||
|
||||
std::string cmdStr = cmd;
|
||||
argv.push_back(cmdStr.data());
|
||||
|
||||
// Split args string by space (simplistic)
|
||||
// Better: Pass vector<string> 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
|
||||
};
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -101,13 +101,24 @@ namespace IACore
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename T> 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<T>();
|
||||
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, T>)
|
||||
{
|
||||
std::string_view sv(v);
|
||||
auto hasher = ankerl::unordered_dense::hash<std::string_view>();
|
||||
h = hasher(sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2. Standard types (int, float, custom structs)
|
||||
auto hasher = ankerl::unordered_dense::hash<T>();
|
||||
h = hasher(v);
|
||||
}
|
||||
|
||||
// 0x9e3779b97f4a7c15 is the 64-bit golden ratio (phi) approximation
|
||||
// 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<typename T, typename... MemberPtrs> INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
|
||||
|
||||
@ -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<T> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user