ProcessOps
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -36,7 +36,6 @@
|
|||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/*.code-snippets
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
@ -47,4 +46,5 @@
|
|||||||
*.vsix
|
*.vsix
|
||||||
|
|
||||||
.cache/
|
.cache/
|
||||||
|
.local/
|
||||||
Build/
|
Build/
|
||||||
|
|||||||
@ -50,3 +50,10 @@ add_subdirectory(Src/)
|
|||||||
if(IACore_BUILD_TESTS)
|
if(IACore_BUILD_TESTS)
|
||||||
add_subdirectory(Tests)
|
add_subdirectory(Tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# -------------------------------------------------
|
||||||
|
# Local Development Sandboxes (not included in source control)
|
||||||
|
# -------------------------------------------------
|
||||||
|
if (EXISTS ".local")
|
||||||
|
add_subdirectory(.local)
|
||||||
|
endif()
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
set(SRC_FILES
|
set(SRC_FILES
|
||||||
"imp/cpp/IACore.cpp"
|
"imp/cpp/IACore.cpp"
|
||||||
"imp/cpp/Logger.cpp"
|
"imp/cpp/Logger.cpp"
|
||||||
|
"imp/cpp/FileOps.cpp"
|
||||||
|
"imp/cpp/AsyncOps.cpp"
|
||||||
|
"imp/cpp/SocketOps.cpp"
|
||||||
|
"imp/cpp/ProcessOps.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(IACore STATIC ${SRC_FILES})
|
add_library(IACore STATIC ${SRC_FILES})
|
||||||
|
|||||||
22
Src/IACore/imp/cpp/AsyncOps.cpp
Normal file
22
Src/IACore/imp/cpp/AsyncOps.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/AsyncOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
0
Src/IACore/imp/cpp/FileOps.cpp
Normal file
0
Src/IACore/imp/cpp/FileOps.cpp
Normal file
@ -15,7 +15,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/Logger.hpp>
|
#include <IACore/Logger.hpp>
|
||||||
#include <IACore/File.hpp>
|
#include <IACore/FileOps.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
|
|||||||
308
Src/IACore/imp/cpp/ProcessOps.cpp
Normal file
308
Src/IACore/imp/cpp/ProcessOps.cpp
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/ProcessOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Output Buffering Helper
|
||||||
|
// Splits raw chunks into lines, preserving partial lines across chunks
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
struct LineBuffer
|
||||||
|
{
|
||||||
|
String Accumulator;
|
||||||
|
Function<VOID(StringView)> &Callback;
|
||||||
|
|
||||||
|
VOID Append(IN PCCHAR data, IN SIZE_T size);
|
||||||
|
VOID Flush();
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
Expected<INT32, String> ProcessOps::SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback)
|
||||||
|
{
|
||||||
|
Atomic<NativeProcessID> id;
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
return SpawnProcessWindows(command, args, onOutputLineCallback, id);
|
||||||
|
#else
|
||||||
|
return SpawnProcessPosix(command, args, onOutputLineCallback, id);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPtr<ProcessHandle> ProcessOps::SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback,
|
||||||
|
IN Function<VOID(Expected<INT32, String>)> onFinishCallback)
|
||||||
|
{
|
||||||
|
SharedPtr<ProcessHandle> handle = std::make_shared<ProcessHandle>();
|
||||||
|
handle->IsRunning = true;
|
||||||
|
|
||||||
|
handle->ThreadHandle = JoiningThread([&, cmd = IA_MOVE(command), args = std::move(args)]() mutable {
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
auto result = SpawnProcessWindows(cmd, args, onOutputLineCallback, handle->ID);
|
||||||
|
#else
|
||||||
|
auto result = SpawnProcessPosix(cmd, args, onOutputLineCallback, handle->ID);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
handle->IsRunning = false;
|
||||||
|
if (onFinishCallback)
|
||||||
|
onFinishCallback(IA_MOVE(result));
|
||||||
|
});
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID ProcessOps::TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle)
|
||||||
|
{
|
||||||
|
if (!handle || !handle->IsActive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
NativeProcessID pid = handle->ID.load();
|
||||||
|
if (pid == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||||
|
if (hProcess != NULL)
|
||||||
|
{
|
||||||
|
TerminateProcess(hProcess, 9);
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
Expected<INT32, String> ProcessOps::SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback,
|
||||||
|
OUT Atomic<NativeProcessID> &id)
|
||||||
|
{
|
||||||
|
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
|
||||||
|
HANDLE hRead = NULL, hWrite = NULL;
|
||||||
|
|
||||||
|
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))
|
||||||
|
return tl::make_unexpected("Failed to secure pipe handles");
|
||||||
|
|
||||||
|
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
||||||
|
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
|
si.hStdOutput = hWrite;
|
||||||
|
si.hStdError = hWrite; // Merge stderr
|
||||||
|
si.hStdInput = NULL; // No input
|
||||||
|
|
||||||
|
PROCESS_INFORMATION pi = {0};
|
||||||
|
|
||||||
|
// Windows command line needs to be mutable and concatenated
|
||||||
|
String commandLine = BuildString("\"", command, "\" ", args);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
CloseHandle(hRead);
|
||||||
|
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
id.store(pi.dwProcessId);
|
||||||
|
|
||||||
|
// Read Loop
|
||||||
|
LineBuffer lineBuf{"", onOutputLineCallback};
|
||||||
|
DWORD bytesRead;
|
||||||
|
CHAR buffer[4096];
|
||||||
|
|
||||||
|
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
|
||||||
|
{
|
||||||
|
lineBuf.Append(buffer, bytesRead);
|
||||||
|
}
|
||||||
|
lineBuf.Flush();
|
||||||
|
|
||||||
|
// NOW we wait for exit code
|
||||||
|
DWORD exitCode = 0;
|
||||||
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
|
GetExitCodeProcess(pi.hProcess, &exitCode);
|
||||||
|
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
CloseHandle(hRead);
|
||||||
|
id.store(0);
|
||||||
|
|
||||||
|
return static_cast<INT32>(exitCode);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if IA_PLATFORM_UNIX
|
||||||
|
Expected<INT32, String> ProcessOps::SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback,
|
||||||
|
OUT Atomic<NativeProcessID> &id)
|
||||||
|
{
|
||||||
|
int pipefd[2];
|
||||||
|
if (pipe(pipefd) == -1)
|
||||||
|
return tl::make_unexpected("Failed to create pipe");
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
|
||||||
|
if (pid == -1)
|
||||||
|
{
|
||||||
|
return tl::make_unexpected("Failed to fork process");
|
||||||
|
}
|
||||||
|
else if (pid == 0)
|
||||||
|
{
|
||||||
|
// --- Child Process ---
|
||||||
|
close(pipefd[0]);
|
||||||
|
|
||||||
|
dup2(pipefd[1], STDOUT_FILENO);
|
||||||
|
dup2(pipefd[1], STDERR_FILENO);
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
// --- ARGUMENT PARSING START ---
|
||||||
|
std::vector<std::string> argStorage; // To keep strings alive
|
||||||
|
std::vector<char *> argv;
|
||||||
|
|
||||||
|
std::string cmdStr = command;
|
||||||
|
argv.push_back(cmdStr.data());
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// --- Parent Process ---
|
||||||
|
id.store(pid);
|
||||||
|
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
LineBuffer lineBuf{"", onOutputLineCallback};
|
||||||
|
char buffer[4096];
|
||||||
|
ssize_t count;
|
||||||
|
|
||||||
|
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
|
||||||
|
{
|
||||||
|
lineBuf.Append(buffer, count);
|
||||||
|
}
|
||||||
|
lineBuf.Flush();
|
||||||
|
close(pipefd[0]);
|
||||||
|
|
||||||
|
int status;
|
||||||
|
waitpid(pid, &status, 0);
|
||||||
|
|
||||||
|
id.store(0);
|
||||||
|
if (WIFEXITED(status))
|
||||||
|
return WEXITSTATUS(status);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
VOID LineBuffer::Append(IN PCCHAR data, IN SIZE_T size)
|
||||||
|
{
|
||||||
|
SIZE_T start = 0;
|
||||||
|
for (SIZE_T i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
if (data[i] == '\n' || data[i] == '\r')
|
||||||
|
{
|
||||||
|
// Flush Accumulator + current chunk
|
||||||
|
if (!Accumulator.empty())
|
||||||
|
{
|
||||||
|
Accumulator.append(data + start, i - start);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip \r\n sequence if needed, or just start next
|
||||||
|
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
|
||||||
|
i++;
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save remaining partial line
|
||||||
|
if (start < size)
|
||||||
|
{
|
||||||
|
Accumulator.append(data + start, size - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID LineBuffer::Flush()
|
||||||
|
{
|
||||||
|
if (!Accumulator.empty())
|
||||||
|
{
|
||||||
|
Callback(Accumulator);
|
||||||
|
Accumulator.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
22
Src/IACore/imp/cpp/SocketOps.cpp
Normal file
22
Src/IACore/imp/cpp/SocketOps.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||||
|
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/SocketOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
24
Src/IACore/inc/IACore/AsyncOps.hpp
Normal file
24
Src/IACore/inc/IACore/AsyncOps.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@ -27,6 +27,7 @@
|
|||||||
# include <new>
|
# include <new>
|
||||||
# include <span>
|
# include <span>
|
||||||
# include <atomic>
|
# include <atomic>
|
||||||
|
# include <mutex>
|
||||||
# include <thread>
|
# include <thread>
|
||||||
# include <limits>
|
# include <limits>
|
||||||
# include <cstring>
|
# include <cstring>
|
||||||
@ -501,6 +502,7 @@ STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
|
|||||||
# include <sys/stat.h>
|
# include <sys/stat.h>
|
||||||
# include <fcntl.h>
|
# include <fcntl.h>
|
||||||
# include <spawn.h>
|
# include <spawn.h>
|
||||||
|
# include <signal.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX)
|
#if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX)
|
||||||
@ -524,6 +526,9 @@ template<typename _key_type> using UnorderedSet = ankerl::unordered_dense::set<_
|
|||||||
template<typename _value_type> using Span = std::span<_value_type>;
|
template<typename _value_type> using Span = std::span<_value_type>;
|
||||||
template<typename _key_type, typename _value_type>
|
template<typename _key_type, typename _value_type>
|
||||||
using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>;
|
using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>;
|
||||||
|
template<typename _value_type> using Atomic = std::atomic<_value_type>;
|
||||||
|
template<typename _value_type> using SharedPtr = std::shared_ptr<_value_type>;
|
||||||
|
template<typename _value_type> using UniquePtr = std::unique_ptr<_value_type>;
|
||||||
|
|
||||||
template<typename _expected_type, typename _unexpected_type>
|
template<typename _expected_type, typename _unexpected_type>
|
||||||
using Expected = tl::expected<_expected_type, _unexpected_type>;
|
using Expected = tl::expected<_expected_type, _unexpected_type>;
|
||||||
@ -537,7 +542,9 @@ using HRClock = std::chrono::high_resolution_clock;
|
|||||||
using HRTimePoint = std::chrono::time_point<HRClock>;
|
using HRTimePoint = std::chrono::time_point<HRClock>;
|
||||||
|
|
||||||
using Mutex = std::mutex;
|
using Mutex = std::mutex;
|
||||||
using LockGuard = std::lock_guard<Mutex>;
|
using ScopedLock = std::scoped_lock<Mutex>;
|
||||||
|
using UniqueLock = std::unique_lock<Mutex>;
|
||||||
|
using JoiningThread = std::jthread;
|
||||||
|
|
||||||
template<typename... Args> using FormatterString = std::format_string<Args...>;
|
template<typename... Args> using FormatterString = std::format_string<Args...>;
|
||||||
|
|
||||||
|
|||||||
@ -1,292 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
|
||||||
|
|
||||||
namespace IACore
|
|
||||||
{
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Async Execution
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Returns a jthread.
|
|
||||||
// - Store it if you want to wait for it later (join).
|
|
||||||
// - If you destroy the returned jthread immediately, it will BLOCK (join) by design.
|
|
||||||
// - If you want "fire and forget", call .detach() on the returned thread.
|
|
||||||
STATIC std::jthread RunAsync(String cmd, String args, Function<VOID(StringView)> onOutputLine,
|
|
||||||
Function<VOID(tl::expected<INT32, String>)> onComplete)
|
|
||||||
{
|
|
||||||
// We capture arguments by VALUE (=) to ensure the thread owns its own copies of the strings.
|
|
||||||
return std::jthread([=, cmd = std::move(cmd), args = std::move(args)]() mutable {
|
|
||||||
tl::expected<INT32, String> result = tl::make_unexpected("Not started");
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
|
||||||
result = RunWindows(cmd, args, onOutputLine);
|
|
||||||
#else
|
|
||||||
result = RunPosix(cmd, args, onOutputLine);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Report final result to the callback
|
|
||||||
if (onComplete)
|
|
||||||
{
|
|
||||||
onComplete(std::move(result));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Output Buffering Helper
|
|
||||||
// Splits raw chunks into lines, preserving partial lines across chunks
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
struct LineBuffer
|
|
||||||
{
|
|
||||||
String accumulator;
|
|
||||||
Function<VOID(StringView)> &callback;
|
|
||||||
|
|
||||||
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')
|
|
||||||
{
|
|
||||||
// Flush accumulator + current chunk
|
|
||||||
if (!accumulator.empty())
|
|
||||||
{
|
|
||||||
accumulator.append(data + start, i - start);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip \r\n sequence if needed, or just start next
|
|
||||||
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
|
|
||||||
i++;
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Save remaining partial line
|
|
||||||
if (start < size)
|
|
||||||
{
|
|
||||||
accumulator.append(data + start, size - start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
HANDLE hRead = NULL, hWrite = NULL;
|
|
||||||
|
|
||||||
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))
|
|
||||||
return tl::make_unexpected("Failed to secure pipe handles");
|
|
||||||
|
|
||||||
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
|
||||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
|
||||||
si.hStdOutput = hWrite;
|
|
||||||
si.hStdError = hWrite; // Merge stderr
|
|
||||||
si.hStdInput = NULL; // No input
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
|
|
||||||
CloseHandle(hWrite);
|
|
||||||
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
CloseHandle(hRead);
|
|
||||||
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read Loop
|
|
||||||
LineBuffer lineBuf{"", cb};
|
|
||||||
DWORD bytesRead;
|
|
||||||
CHAR buffer[4096];
|
|
||||||
|
|
||||||
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
|
|
||||||
{
|
|
||||||
lineBuf.Append(buffer, bytesRead);
|
|
||||||
}
|
|
||||||
lineBuf.Flush();
|
|
||||||
|
|
||||||
// NOW we wait for exit code
|
|
||||||
DWORD exitCode = 0;
|
|
||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
||||||
GetExitCodeProcess(pi.hProcess, &exitCode);
|
|
||||||
|
|
||||||
CloseHandle(pi.hProcess);
|
|
||||||
CloseHandle(pi.hThread);
|
|
||||||
CloseHandle(hRead);
|
|
||||||
|
|
||||||
return static_cast<INT32>(exitCode);
|
|
||||||
}
|
|
||||||
#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)
|
|
||||||
{
|
|
||||||
int pipefd[2];
|
|
||||||
if (pipe(pipefd) == -1)
|
|
||||||
return tl::make_unexpected("Failed to create pipe");
|
|
||||||
|
|
||||||
pid_t pid = fork();
|
|
||||||
|
|
||||||
if (pid == -1)
|
|
||||||
{
|
|
||||||
return tl::make_unexpected("Failed to fork process");
|
|
||||||
}
|
|
||||||
else if (pid == 0)
|
|
||||||
{
|
|
||||||
// --- Child Process ---
|
|
||||||
close(pipefd[0]);
|
|
||||||
|
|
||||||
dup2(pipefd[1], STDOUT_FILENO);
|
|
||||||
dup2(pipefd[1], STDERR_FILENO);
|
|
||||||
close(pipefd[1]);
|
|
||||||
|
|
||||||
// --- 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());
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// --- Parent Process ---
|
|
||||||
close(pipefd[1]);
|
|
||||||
|
|
||||||
LineBuffer lineBuf{"", cb};
|
|
||||||
char buffer[4096];
|
|
||||||
ssize_t count;
|
|
||||||
|
|
||||||
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
|
|
||||||
{
|
|
||||||
lineBuf.Append(buffer, count);
|
|
||||||
}
|
|
||||||
lineBuf.Flush();
|
|
||||||
close(pipefd[0]);
|
|
||||||
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
|
|
||||||
if (WIFEXITED(status))
|
|
||||||
return WEXITSTATUS(status);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
} // namespace IACore
|
|
||||||
64
Src/IACore/inc/IACore/ProcessOps.hpp
Normal file
64
Src/IACore/inc/IACore/ProcessOps.hpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
using NativeProcessID = DWORD;
|
||||||
|
#elif IA_PLATFORM_UNIX
|
||||||
|
using NativeProcessID = pid_t;
|
||||||
|
#else
|
||||||
|
# error "This platform does not support IACore ProcessOps"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
struct ProcessHandle
|
||||||
|
{
|
||||||
|
Atomic<NativeProcessID> ID{0};
|
||||||
|
Atomic<BOOL> IsRunning{false};
|
||||||
|
|
||||||
|
BOOL IsActive() CONST
|
||||||
|
{
|
||||||
|
return IsRunning && ID != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JoiningThread ThreadHandle;
|
||||||
|
|
||||||
|
friend class ProcessOps;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProcessOps
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
STATIC Expected<INT32, String> SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback);
|
||||||
|
STATIC SharedPtr<ProcessHandle> SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback,
|
||||||
|
IN Function<VOID(Expected<INT32, String>)> onFinishCallback);
|
||||||
|
|
||||||
|
STATIC VOID TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
STATIC Expected<INT32, String> SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID>& id);
|
||||||
|
STATIC Expected<INT32, String> SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID>& id);
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
|
add_subdirectory(Subjects/)
|
||||||
add_subdirectory(Unit/)
|
add_subdirectory(Unit/)
|
||||||
add_subdirectory(Regression/)
|
add_subdirectory(Regression/)
|
||||||
|
|||||||
1
Tests/Subjects/CMakeLists.txt
Normal file
1
Tests/Subjects/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
add_executable(LongProcess LongProcess/Main.cpp)
|
||||||
12
Tests/Subjects/LongProcess/Main.cpp
Normal file
12
Tests/Subjects/LongProcess/Main.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int, char **)
|
||||||
|
{
|
||||||
|
std::cout << "Started!\n";
|
||||||
|
std::cout.flush();
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
|
std::cout << "Ended!\n";
|
||||||
|
std::cout.flush();
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
@ -38,20 +38,20 @@ target_compile_options(${TEST_NAME_PREFIX}Environment PRIVATE -fexceptions)
|
|||||||
set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON)
|
set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# Unit: File
|
# Unit: FileOps
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
add_executable(${TEST_NAME_PREFIX}File "File.cpp")
|
add_executable(${TEST_NAME_PREFIX}FileOps "FileOps.cpp")
|
||||||
target_link_libraries(${TEST_NAME_PREFIX}File PRIVATE IACore)
|
target_link_libraries(${TEST_NAME_PREFIX}FileOps PRIVATE IACore)
|
||||||
target_compile_options(${TEST_NAME_PREFIX}File PRIVATE -fexceptions)
|
target_compile_options(${TEST_NAME_PREFIX}FileOps PRIVATE -fexceptions)
|
||||||
set_target_properties(${TEST_NAME_PREFIX}File PROPERTIES USE_EXCEPTIONS ON)
|
set_target_properties(${TEST_NAME_PREFIX}FileOps PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# Unit: Process
|
# Unit: ProcessOps
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
add_executable(${TEST_NAME_PREFIX}Process "Process.cpp")
|
add_executable(${TEST_NAME_PREFIX}ProcessOps "ProcessOps.cpp")
|
||||||
target_link_libraries(${TEST_NAME_PREFIX}Process PRIVATE IACore)
|
target_link_libraries(${TEST_NAME_PREFIX}ProcessOps PRIVATE IACore)
|
||||||
target_compile_options(${TEST_NAME_PREFIX}Process PRIVATE -fexceptions)
|
target_compile_options(${TEST_NAME_PREFIX}ProcessOps PRIVATE -fexceptions)
|
||||||
set_target_properties(${TEST_NAME_PREFIX}Process PROPERTIES USE_EXCEPTIONS ON)
|
set_target_properties(${TEST_NAME_PREFIX}ProcessOps PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# Unit: Utils
|
# Unit: Utils
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/File.hpp>
|
#include <IACore/FileOps.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
// 1. Write
|
// 1. Write
|
||||||
{
|
{
|
||||||
File f;
|
File f;
|
||||||
auto res = f.Open(path, flagsWrite);
|
auto res = f.Open(path, (File::EOpenFlags)flagsWrite);
|
||||||
IAT_CHECK(res.has_value());
|
IAT_CHECK(res.has_value());
|
||||||
IAT_CHECK(f.IsOpen());
|
IAT_CHECK(f.IsOpen());
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
UINT32 flagsRead = (UINT32)File::EOpenFlags::Read |
|
UINT32 flagsRead = (UINT32)File::EOpenFlags::Read |
|
||||||
(UINT32)File::EOpenFlags::Binary;
|
(UINT32)File::EOpenFlags::Binary;
|
||||||
|
|
||||||
File f(path, flagsRead); // Test RAII constructor
|
File f(path, (File::EOpenFlags)flagsRead); // Test RAII constructor
|
||||||
IAT_CHECK(f.IsOpen());
|
IAT_CHECK(f.IsOpen());
|
||||||
|
|
||||||
UINT32 magicRead = 0;
|
UINT32 magicRead = 0;
|
||||||
@ -172,7 +172,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
|
|
||||||
// Open in append mode
|
// Open in append mode
|
||||||
File f;
|
File f;
|
||||||
const auto openResult = f.Open(path, flagsAppend);
|
const auto openResult = f.Open(path, (File::EOpenFlags)flagsAppend);
|
||||||
if(!openResult)
|
if(!openResult)
|
||||||
{
|
{
|
||||||
IA_PANIC(openResult.error().c_str())
|
IA_PANIC(openResult.error().c_str())
|
||||||
@ -209,7 +209,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
|
|
||||||
// Test Instance
|
// Test Instance
|
||||||
File f;
|
File f;
|
||||||
auto resInstance = f.Open(ghostPath, (UINT32)File::EOpenFlags::Read);
|
auto resInstance = f.Open(ghostPath, File::EOpenFlags::Read);
|
||||||
IAT_CHECK_NOT(resInstance.has_value());
|
IAT_CHECK_NOT(resInstance.has_value());
|
||||||
IAT_CHECK_NOT(f.IsOpen());
|
IAT_CHECK_NOT(f.IsOpen());
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/Process.hpp>
|
#include <IACore/ProcessOps.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
// Simple "echo hello"
|
// Simple "echo hello"
|
||||||
String captured;
|
String captured;
|
||||||
|
|
||||||
auto result = Process::Run(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
||||||
[&](StringView line) {
|
[&](StringView line) {
|
||||||
captured = line;
|
captured = line;
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
String args = String(CMD_ARG_PREFIX) + " one two";
|
String args = String(CMD_ARG_PREFIX) + " one two";
|
||||||
if(args[0] == ' ') args.erase(0, 1); // cleanup space if prefix empty
|
if(args[0] == ' ') args.erase(0, 1); // cleanup space if prefix empty
|
||||||
|
|
||||||
auto result = Process::Run(CMD_ECHO_EXE, args,
|
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args,
|
||||||
[&](StringView line) {
|
[&](StringView line) {
|
||||||
lines.push_back(String(line));
|
lines.push_back(String(line));
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto result = Process::Run(cmd, arg, [](StringView){});
|
auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView){});
|
||||||
|
|
||||||
IAT_CHECK(result.has_value());
|
IAT_CHECK(result.has_value());
|
||||||
IAT_CHECK_EQ(*result, 42);
|
IAT_CHECK_EQ(*result, 42);
|
||||||
@ -121,7 +121,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
BOOL TestMissingExe()
|
BOOL TestMissingExe()
|
||||||
{
|
{
|
||||||
// Try to run a random string
|
// Try to run a random string
|
||||||
auto result = Process::Run("sdflkjghsdflkjg", "", [](StringView){});
|
auto result = ProcessOps::SpawnProcessSync("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
|
||||||
@ -166,7 +166,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
String captured;
|
String captured;
|
||||||
auto result = Process::Run(cmd, arg,
|
auto result = ProcessOps::SpawnProcessSync(cmd, arg,
|
||||||
[&](StringView line) {
|
[&](StringView line) {
|
||||||
captured += line;
|
captured += line;
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
bool foundA = false;
|
bool foundA = false;
|
||||||
bool foundB = false;
|
bool foundB = false;
|
||||||
|
|
||||||
UNUSED(Process::Run(cmd, arg, [&](StringView line) {
|
UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) {
|
||||||
lineCount++;
|
lineCount++;
|
||||||
if (line.find("LineA") != String::npos) foundA = true;
|
if (line.find("LineA") != String::npos) foundA = true;
|
||||||
if (line.find("LineB") != String::npos) foundB = true;
|
if (line.find("LineB") != String::npos) foundB = true;
|
||||||
Reference in New Issue
Block a user