Merge pull request 'piprocess' (#189) from piprocess into master

Reviewed-on: #189
This commit was merged in pull request #189.
This commit is contained in:
2025-08-14 10:05:23 +03:00
8 changed files with 508 additions and 222 deletions

View File

@@ -615,7 +615,7 @@ if(NOT PIP_FREERTOS)
add_subdirectory("utils/translator")
add_subdirectory("utils/value_tree_translator")
if(PIP_UTILS AND (NOT CROSSTOOLS))
add_subdirectory("utils/system_test")
add_subdirectory("utils/system_calib")
add_subdirectory("utils/udp_file_transfer")
if(sodium_FOUND)
add_subdirectory("utils/system_daemon")

View File

@@ -17,16 +17,17 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "piliterals_time.h"
#include "pitime.h"
#include "pitranslator.h"
#ifndef MICRO_PIP
# include "piincludes_p.h"
# include "piliterals_bytes.h"
# include "piprocess.h"
# include "pitranslator.h"
# ifndef WINDOWS
# include <csignal>
# include <sys/wait.h>
# include <fcntl.h>
# endif
# ifdef MAC_OS
# include <crt_externs.h>
@@ -63,14 +64,189 @@
//!
PRIVATE_DEFINITION_START(PIProcess)
namespace {
enum PipeDirection {
PipeRead,
PipeWrite,
PipeLast = PipeWrite
};
enum StdFile {
StdIn,
StdOut,
StdErr,
StdLast = StdErr
};
constexpr int PipesDirections = PipeLast + 1;
constexpr int StdFileCount = StdLast + 1;
# ifdef WINDOWS
using PipeHandleType = HANDLE;
using SizeType = DWORD;
# else
using SizeType = ssize_t;
using PipeHandleType = int;
# endif
# ifdef WINDOWS
PIString convertWindowsCmd(PIStringList sl) {
if (sl.isNotEmpty()) {
sl[0].replaceAll('/', '\\');
sl[0].quote();
}
return sl.join(' ');
}
# else
char * const * convertToCharArrays(const PIStringList & sl) {
char ** cc = new char *[sl.size() + 1];
for (int i = 0; i < sl.size_s(); ++i) {
cc[i] = const_cast<char *>(sl[i].data());
}
cc[sl.size()] = 0;
return cc;
}
# endif
} // namespace
PRIVATE_DEFINITION_START(PIProcess)
# ifdef WINDOWS
STARTUPINFOA si;
PROCESS_INFORMATION pi;
# else
pid_t pid;
# endif
FILE *tf_in, *tf_out, *tf_err;
PipeHandleType pipes[StdFileCount][PipesDirections];
bool grab[StdFileCount];
void forEachPipe(std::function<void(PipeHandleType &)> func) {
for (int i = 0; i < StdFileCount; ++i) {
for (int j = 0; j < PipesDirections; ++j) {
func(pipes[i][j]);
}
}
}
void initGrab() {
for (int i = 0; i < StdFileCount; ++i) {
grab[i] = false;
}
}
bool createPipe(StdFile pipe_type) {
const int pt = pipe_type;
# ifdef WINDOWS
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&(pipes[pt][PipeRead]), &(pipes[pt][PipeWrite]), &saAttr, 0)) return false;
return true;
# else
int ret = pipe(pipes[pt]);
return ret != -1;
# endif
return false;
}
bool createPipes() {
for (int i = 0; i < StdFileCount; ++i) {
if (grab[i]) {
if (!createPipe(static_cast<StdFile>(i))) {
piCout << "CreatePipe failed";
# ifdef WINDOWS
piCout << GetLastError();
# endif
return false;
}
}
}
# ifdef WINDOWS
if (grab[StdIn]) SetHandleInformation(pipes[StdIn][PipeWrite], HANDLE_FLAG_INHERIT, 0);
if (grab[StdOut]) SetHandleInformation(pipes[StdOut][PipeRead], HANDLE_FLAG_INHERIT, 0);
if (grab[StdErr]) SetHandleInformation(pipes[StdErr][PipeRead], HANDLE_FLAG_INHERIT, 0);
# endif
return true;
}
void closePipe(StdFile pipe_type, PipeDirection direction) {
closePipe(pipes[pipe_type][direction]);
}
void closePipe(PipeHandleType & hpipe) {
# ifdef WINDOWS
if (hpipe != 0) {
CloseHandle(hpipe);
hpipe = 0;
}
# else
if (hpipe != -1) {
::close(hpipe);
hpipe = -1;
}
# endif
}
void closeAllPipes() {
forEachPipe([this](PipeHandleType & hpipe) { closePipe(hpipe); });
}
PIByteArray readPipe(StdFile pipe_type) {
if (!grab[pipe_type]) return {};
constexpr size_t read_buffer_size = 4_KiB;
PIByteArray read_buffer;
read_buffer.resize(read_buffer_size);
SizeType bytes_read = 0;
size_t offset = 0;
while (1) {
# ifdef WINDOWS
DWORD available = 0;
bytes_read = 0;
PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &available, nullptr);
if (available > 0) {
BOOL ok = ReadFile(pipes[pipe_type][PipeRead],
read_buffer.data(offset),
piMini(available, read_buffer.size() - offset),
&bytes_read,
nullptr);
if (!ok) bytes_read = 0;
}
# else
bytes_read = ::read(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset);
# endif
if (bytes_read > 0) {
offset += bytes_read;
read_buffer.resize(offset + read_buffer_size);
} else {
read_buffer.resize(offset);
break;
}
}
return read_buffer;
}
bool writePipe(const PIByteArray & data) {
SizeType sz = 0;
# ifdef WINDOWS
BOOL ok = WriteFile(pipes[StdIn][PipeWrite], data.data(), data.size(), &sz, NULL);
if (!ok) sz = 0;
# else
sz = ::write(pipes[StdIn][PipeWrite], data.data(), data.size());
# endif
return sz == data.size_s();
}
PRIVATE_DEFINITION_END(PIProcess)
@@ -80,177 +256,129 @@ PIProcess::PIProcess(): PIThread() {
PRIVATE->pi.dwProcessId = 0;
# else
PRIVATE->pid = 0;
PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; });
# endif
is_exec = false;
g_in = g_out = g_err = false;
t_in = t_out = t_err = false;
PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0;
env = PIProcess::currentEnvironment();
exec_start = false;
exec_finished = false;
PRIVATE->initGrab();
env = PIProcess::currentEnvironment();
}
PIProcess::~PIProcess() {
PIThread::stopAndWait();
if (t_in) f_in.remove();
if (t_out) f_out.remove();
if (t_err) f_err.remove();
PRIVATE->closeAllPipes();
}
void PIProcess::exec_() {
is_exec = false;
exec_finished = false;
exec_start = false;
PRIVATE->closeAllPipes();
startOnce();
// cout << "exec wait" << endl;
while (!is_exec)
piMinSleep();
// cout << "exec end" << endl;
}
void PIProcess::startProc(bool detached) {
// cout << "run" << endl;
PIString str;
/// arguments convertion
int as = 0;
const char * argscc[args.size() + 1];
int argsl[args.size()];
for (int i = 0; i < args.size_s(); ++i) {
argscc[i] = args[i].data();
argsl[i] = strlen(argscc[i]);
as += argsl[i] + 3;
}
argscc[args.size()] = 0;
# ifdef WINDOWS
char * a = new char[as];
memset(a, ' ', as - 1);
as = 0;
for (int i = 0; i < args.size_s(); ++i) {
str = args[i];
a[as] = '"';
memcpy(&(a[as + 1]), argscc[i], argsl[i]);
a[as + argsl[i] + 1] = '"';
as += argsl[i] + 3;
}
a[as - 1] = 0;
// piCout << a;
# endif
# ifndef WINDOWS
/// environment convertion
const char * envcc[env.size() + 1];
envcc[env.size_s()] = 0;
for (int i = 0; i < env.size_s(); ++i) {
envcc[i] = env[i].data();
}
# endif
/// files for stdin/out/err
t_in = t_out = t_err = false;
if (f_in.path().isEmpty()) {
f_in.openTemporary(PIIODevice::ReadWrite);
t_in = true;
}
if (f_out.path().isEmpty()) {
f_out.openTemporary(PIIODevice::ReadWrite);
t_out = true;
}
if (f_err.path().isEmpty()) {
f_err.openTemporary(PIIODevice::ReadWrite);
t_err = true;
}
str = args.front();
is_exec = true;
const PIString & str = args.front();
if (!detached) execStarted(str);
# ifndef WINDOWS
int pid_ = fork();
if (!PRIVATE->createPipes()) return;
# ifdef WINDOWS
STARTUPINFOA si;
piZeroMemory(si);
si.cb = sizeof(STARTUPINFOA);
if (PRIVATE->grab[StdIn]) si.hStdInput = PRIVATE->pipes[StdIn][PipeRead];
if (PRIVATE->grab[StdOut]) si.hStdOutput = PRIVATE->pipes[StdOut][PipeWrite];
if (PRIVATE->grab[StdErr]) si.hStdError = PRIVATE->pipes[StdErr][PipeWrite];
si.dwFlags |= STARTF_USESTDHANDLES;
const auto cmd = convertWindowsCmd(args);
if (CreateProcessA(0, // No module name (use command line)
(LPSTR)cmd.data(), // Command line
0, // Process handle not inheritable
0, // Thread handle not inheritable
true, // Set handle inheritance to FALSE
detached ? DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ : 0, // Creation flags
0, // Use environment
wd.isEmpty() ? 0 : wd.data(), // Use working directory
&si, // Pointer to STARTUPINFO structure
&(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure
{
exec_start = true;
if (!detached) {
WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE);
DWORD code = -1;
if (GetExitCodeProcess(PRIVATE->pi.hProcess, &code) != 0) exit_code = code;
exec_finished = true;
}
CloseHandle(PRIVATE->pi.hThread);
CloseHandle(PRIVATE->pi.hProcess);
} else {
piCoutObj << "\"CreateProcess\" error: %1"_tr("PIProcess").arg(errorString());
}
# else
auto largs = convertToCharArrays(args);
auto lenv = convertToCharArrays(env);
int pid_ = fork();
if (!detached) PRIVATE->pid = pid_;
if (pid_ == 0) {
# endif
PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0;
// cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl;
// cout << f_out.path() << endl;
if (g_in) PRIVATE->tf_in = freopen(f_in.path().data(), "r", stdin);
if (g_out) PRIVATE->tf_out = freopen(f_out.path().data(), "w", stdout);
if (g_err) PRIVATE->tf_err = freopen(f_err.path().data(), "w", stderr);
# ifdef WINDOWS
GetStartupInfoA(&(PRIVATE->si));
piZeroMemory(PRIVATE->pi);
if (CreateProcessA(0, // No module name (use command line)
a, // Command line
0, // Process handle not inheritable
0, // Thread handle not inheritable
false, // Set handle inheritance to FALSE
detached ? DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ : 0, // Creation flags
0, // envcc, // Use environment
wd.isEmpty() ? 0 : wd.data(), // Use working directory
&(PRIVATE->si), // Pointer to STARTUPINFO structure
&(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure
{
if (!detached) {
WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE);
DWORD code = -1;
if (GetExitCodeProcess(PRIVATE->pi.hProcess, &code) != 0) exit_code = code;
}
CloseHandle(PRIVATE->pi.hThread);
CloseHandle(PRIVATE->pi.hProcess);
} else {
piCoutObj << "\"CreateProcess\" error: %1"_tr("PIProcess").arg(errorString());
}
# endif
# ifndef WINDOWS
if (!wd.isEmpty()) {
if (!chdir(wd.data())) piCoutObj << "Error while set working directory";
}
// cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl;
if (execve(str.data(), (char * const *)argscc, (char * const *)envcc) < 0) {
piCoutObj << "\"execve" << str << args << "\" error :" << errorString();
}
PRIVATE->closePipe(StdIn, PipeWrite);
PRIVATE->closePipe(StdOut, PipeRead);
PRIVATE->closePipe(StdErr, PipeRead);
if (PRIVATE->grab[StdIn]) dup2(PRIVATE->pipes[StdIn][PipeRead], STDIN_FILENO);
if (PRIVATE->grab[StdOut]) dup2(PRIVATE->pipes[StdOut][PipeWrite], STDOUT_FILENO);
if (PRIVATE->grab[StdErr]) dup2(PRIVATE->pipes[StdErr][PipeWrite], STDERR_FILENO);
PRIVATE->closePipe(StdIn, PipeRead);
PRIVATE->closePipe(StdOut, PipeWrite);
PRIVATE->closePipe(StdErr, PipeWrite);
execve(str.data(), largs, lenv);
// normaly execve can't return, if it returns - error occured
piCoutObj << "\"execve" << str << args << "\" error :" << errorString();
exit(127);
} else {
piMinSleep();
// cout << "wait" << endl;
PRIVATE->closePipe(StdIn, PipeRead);
PRIVATE->closePipe(StdOut, PipeWrite);
PRIVATE->closePipe(StdErr, PipeWrite);
if (PRIVATE->grab[StdOut]) fcntl(PRIVATE->pipes[StdOut][PipeRead], F_SETFL, O_NONBLOCK);
if (PRIVATE->grab[StdErr]) fcntl(PRIVATE->pipes[StdErr][PipeRead], F_SETFL, O_NONBLOCK);
exec_start = true;
if (!detached) {
waitpid(pid_, &exit_code, 0);
pid_ = 0;
if (WIFEXITED(exit_code)) {
exec_finished = WEXITSTATUS(exit_code) != 127;
}
pid_ = 0;
if (!detached) PRIVATE->pid = pid_;
// cout << "wait done" << endl;
}
}
delete[] largs;
delete[] lenv;
# endif
if (!detached) execFinished(str, exit_code);
is_exec = false;
# ifdef WINDOWS
delete[] a;
# endif
}
PIByteArray PIProcess::readFile(PIFile & f, bool clear)
{
f.open(PIIODevice::ReadOnly);
const auto ret = f.readAll();
if (clear) {
f.clear();
}
return ret;
exec_start = false;
}
void PIProcess::terminate() {
# ifdef WINDOWS
if (is_exec)
if (exec_start) {
if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return;
}
PRIVATE->pi.dwProcessId = 0;
# else
if (is_exec) kill(PRIVATE->pid, SIGKILL);
if (exec_start) kill(PRIVATE->pid, SIGKILL);
PRIVATE->pid = 0;
# endif
}
bool PIProcess::waitForFinish() {
return PIThread::waitForFinish(1_m);
}
void PIProcess::execIndependent(const PIString & program, const PIStringList & args_) {
PIProcess p;
p.args << program << args_;
@@ -266,12 +394,45 @@ int PIProcess::pID() const {
# endif
}
PIByteArray PIProcess::readOutput(bool clear) {
return readFile(f_out, clear);
PIByteArray PIProcess::readOutput() {
return PRIVATE->readPipe(StdOut);
}
PIByteArray PIProcess::readError(bool clear) {
return readFile(f_err, clear);
PIByteArray PIProcess::readError() {
return PRIVATE->readPipe(StdErr);
}
bool PIProcess::writeInput(const PIByteArray & data) {
if (PRIVATE->grab[StdIn]) return PRIVATE->writePipe(data);
return false;
}
void PIProcess::closeInput() {
if (PRIVATE->grab[StdIn]) {
# ifdef WINDOWS
PRIVATE->writePipe({0x1A});
# endif
PRIVATE->closePipe(StdIn, PipeWrite);
}
}
void PIProcess::enableWriteStdIn(bool on) {
PRIVATE->grab[StdIn] = on;
}
void PIProcess::enableReadStdOut(bool on) {
PRIVATE->grab[StdOut] = on;
}
void PIProcess::enableReadStdErr(bool on) {
PRIVATE->grab[StdErr] = on;
}

View File

@@ -9,18 +9,18 @@
Process
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PIPROCESS_H
@@ -28,14 +28,45 @@
#ifndef MICRO_PIP
# include "pifile.h"
# include "pithread.h"
//! \class PIProcess
//! \ingroup System
//! \~\brief
//! \~english External process.
//! \~russian Внешний процесс.
//! \~english
//! \brief Class for managing external processes
//! \details
//! The PIProcess class provides functionality to create, control and interact with external processes.
//! It allows both attached execution (with full control over input/output streams) and detached execution.
//!
//! Key features:
//! - Start processes with arguments and environment variables
//! - Monitor process state (running/finished)
//! - Read from stdout/stderr streams
//! - Write to stdin stream
//! - Set custom working directory
//! - Modify environment variables
//! - Wait for process completion
//! - Terminate processes
//! - Retrieve exit codes and process IDs
//!
//! This class inherits from PIThread and provides event-based notifications for process lifecycle events.
//! \~russian
//! \brief Класс для управления внешними процессами
//! \details
//! Класс PIProcess предоставляет функциональность для создания, управления и взаимодействия с внешними процессами.
//! Поддерживает как присоединенное выполнение (с полным контролем потоков ввода/вывода), так и независимое выполнение.
//!
//! Основные возможности:
//! - Запуск процессов с аргументами и переменными окружения
//! - Мониторинг состояния процесса (запущен/завершен)
//! - Чтение из потоков stdout/stderr
//! - Запись в поток stdin
//! - Установка рабочей директории
//! - Изменение переменных окружения
//! - Ожидание завершения процесса
//! - Завершение процессов
//! - Получение кодов завершения и идентификаторов процессов
//!
class PIP_EXPORT PIProcess: public PIThread {
PIOBJECT_SUBCLASS(PIProcess, PIThread);
@@ -55,74 +86,64 @@ public:
//! \~russian Возвращает ID процесса текущего выполнения
int pID() const;
void setGrabInput(bool yes) { g_in = yes; }
//! \~english Set attached execution grab output stream enabled
//! \~russian
void setGrabOutput(bool yes) { g_out = yes; }
//! \~english Set attached execution grab error stream enabled
//! \~russian
void setGrabError(bool yes) { g_err = yes; }
void setInputFile(const PIString & path) { f_in.setPath(path); }
//! \~english Set attached execution grab output stream file
//! \~russian
void setOutputFile(const PIString & path) { f_out.setPath(path); }
//! \~english Set attached execution grab error stream file
//! \~russian
void setErrorFile(const PIString & path) { f_err.setPath(path); }
void unsetInputFile() { f_in.setPath(""); }
//! \~english Reset attached execution grab output stream file
//! \~russian
void unsetOutputFile() { f_out.setPath(""); }
//! \~english Reset attached execution grab error stream file
//! \~russian
void unsetErrorFile() { f_err.setPath(""); }
//! \~english Returns current attached execution working directory or empty string if it wasn`t set
//! \~russian
//! \~russian Возвращает рабочую директорию выполнения или пустую строку, если не установлена
PIString workingDirectory() const { return wd; }
//! \~english Set attached execution working directory
//! \~russian
//! \~russian Устанавливает рабочую директорию для выполнения
void setWorkingDirectory(const PIString & path) { wd = path; }
//! \~english Rseet attached execution working directory, application working dir will be used
//! \~russian
//! \~russian Сбрасывает рабочую директорию, будет использоваться директория приложения
void resetWorkingDirectory() { wd.clear(); }
//! \~english Returns all attached execution output stream
//! \~russian
PIByteArray readOutput(bool clear = false);
//! \~russian Возвращает весь вывод из стандартного потока вывода (stdout)
PIByteArray readOutput();
//! \~english Returns all attached execution error stream
//! \~russian
PIByteArray readError(bool clear = false);
//! \~russian Возвращает весь вывод из потока ошибок (stderr)
PIByteArray readError();
//! \~english Write data to attached execution input stream
//! \~russian Записывает данные в стандартный поток ввода (stdin)
bool writeInput(const PIByteArray & data);
//! \~english Close attached execution input stream and send EOF
//! \~russian Закрывает поток ввода (stdin) и отправляет EOF
void closeInput();
//! \~english Enable or disable writing to process stdin
//! \~russian Включает или отключает запись в стандартный поток ввода (stdin) процесса
void enableWriteStdIn(bool on = true);
//! \~english Enable or disable reading from process stdout
//! \~russian Включает или отключает чтение из стандартного потока вывода (stdout) процесса
void enableReadStdOut(bool on = true);
//! \~english Enable or disable reading from process stderr
//! \~russian Включает или отключает чтение из потока ошибок (stderr) процесса
void enableReadStdErr(bool on = true);
//! \~english Returns current attached execution environment
//! \~russian
//! \~russian Возвращает текущее окружение выполнения
PIStringList environment() { return env; }
//! \~english Clear current attached execution environment. Call before \a exec()
//! \~russian
//! \~russian Очищает окружение выполнения. Вызывать перед \a exec()
void clearEnvironment() { env.clear(); }
//! \~english Remove variable "variable" from current attached execution environment. Call before \a exec()
//! \~russian
//! \~russian Удаляет переменную "variable" из окружения выполнения. Вызывать перед \a exec()
void removeEnvironmentVariable(const PIString & variable);
//! \~english Set variable "variable" to "value" in current attached execution environment. Call before \a exec()
//! \~russian
//! \~russian Устанавливает значение "value" для переменной "variable" в окружении выполнения. Вызывать перед \a exec()
void setEnvironmentVariable(const PIString & variable, const PIString & value);
//! \~english Start attached execution "program" with one argument "arg"
//! \~russian
//! \~russian Запускает выполнение "program" с одним аргументом "arg"
void exec(const PIString & program, const PIString & arg) {
args.clear();
args << program << arg;
@@ -140,36 +161,43 @@ public:
exec_();
}
EVENT_HANDLER(void, terminate);
EVENT_HANDLER(bool, waitForFinish);
EVENT_HANDLER(bool, waitForFinish) { return PIThread::waitForFinish(); }
EVENT_HANDLER1(bool, waitForFinish, PISystemTime, timeout) { return PIThread::waitForFinish(timeout); }
EVENT1(execStarted, PIString, program);
EVENT2(execFinished, PIString, program, int, exit_code);
//! \~english Check if attached execution has finished
//! \~russian Проверяет, завершилось ли выполнение процесса
bool isExecFinished() const { return exec_finished; }
//! \~english Check if attached execution has started
//! \~russian Проверяет, запущен ли процесс выполнения
bool isExecStarted() const { return exec_start; }
//! \~english Start detached execution "program" without arguments
//! \~russian
//! \~russian Запускает независимое выполнение "program" без аргументов
static void execIndependent(const PIString & program) { execIndependent(program, PIStringList()); }
//! \~english Start detached execution "program" with one argument "arg"
//! \~russian
//! \~russian Запускает независимое выполнение "program" с одним аргументом "arg"
static void execIndependent(const PIString & program, const PIString & arg) { execIndependent(program, PIStringList() << arg); }
//! \~english Start detached execution "program" with arguments "args"
//! \~russian
//! \~russian Запускает независимое выполнение "program" с аргументами "args"
static void execIndependent(const PIString & program, const PIStringList & args);
//! \~english Returns application environment
//! \~russian
//! \~russian Возвращает окружение текущего приложения
static PIStringList currentEnvironment();
//! \~english Returns application process ID
//! \~russian
//! \~russian Возвращает ID процесса текущего приложения
static int currentPID();
//! \~english Returns variable "variable" value from application environment
//! \~russian
//! \~russian Возвращает значение переменной "variable" из окружения приложения
static PIString getEnvironmentVariable(const PIString & variable);
//! \handlers
@@ -178,27 +206,27 @@ public:
//! \fn void exec(const PIString & program)
//! \brief
//! \~english Start attached execution "program" without arguments
//! \~russian
//! \~russian Запускает выполнение "program" без аргументов
//! \fn void exec(const PIString & program, const PIStringList & args)
//! \brief
//! \~english Start attached execution "program" with arguments "args"
//! \~russian
//! \~russian Запускает выполнение "program" с аргументами "args"
//! \fn void terminate()
//! \brief
//! \~english Immediately terminate attached execution
//! \~russian
//! \~russian Немедленно завершает выполнение
//! \fn bool waitForFinish()
//! \brief
//! \~english Wait for attached execution finish maximum for 60 seconds
//! \~russian
//! \~russian Ожидает завершения выполнения (максимум 60 секунд)
//! \fn bool waitForFinish(PISystemTime timeout)
//! \brief
//! \~english Wait for attached execution finish maximum for "timeout_"
//! \~russian
//! \~russian Ожидает завершения выполнения в течение "timeout_"
//! \}
//! \events
@@ -207,12 +235,12 @@ public:
//! \fn void execStarted(PIString program)
//! \brief
//! \~english Raise on attached execution start
//! \~russian
//! \~russian Генерируется при запуске выполнения
//! \fn void execFinished(PIString program)
//! \brief
//! \~english Raise on attached execution finish
//! \~russian
//! \~russian Генерируется при завершении выполнения
//! \}
@@ -220,16 +248,14 @@ private:
void run() override;
void exec_();
void startProc(bool detached);
PIByteArray readFile(PIFile & f, bool clear);
private:
PRIVATE_DECLARATION(PIP_EXPORT)
PIStringList args, env;
PIString wd;
PIByteArray out;
PIFile f_in, f_out, f_err;
bool g_in, g_out, g_err, t_in, t_out, t_err;
int exit_code;
bool is_exec;
std::atomic_bool exec_start;
std::atomic_bool exec_finished;
};
#endif // MICRO_PIP

View File

@@ -36,3 +36,4 @@ pip_test(core)
pip_test(piobject)
pip_test(client_server pip_client_server)
pip_test(io)
pip_test(system)

View File

@@ -0,0 +1,98 @@
#include "piprocess.h"
#include "pitime.h"
#include "gtest/gtest.h"
class ProcessTest: public ::testing::Test {
protected:
PIProcess launcher;
const PIString command =
#ifdef _WIN32
"C:/Windows/System32/cmd.exe";
#else
"/bin/sh";
#endif
void SetUp() override {
launcher.enableWriteStdIn(false);
launcher.enableReadStdOut();
launcher.enableReadStdErr();
}
void TearDown() override {
if (launcher.isRunning()) {
launcher.waitForFinish();
}
}
};
TEST_F(ProcessTest, Output) {
#ifdef _WIN32
const PIStringList args = {"/c", "echo Hello from stdout && echo Hello from stderr 1>&2"};
#else
const PIStringList args = {"-c", "echo Hello from stdout; echo Hello from stderr 1>&2"};
#endif
launcher.exec(command, args);
ASSERT_TRUE(launcher.isRunning());
ASSERT_TRUE(launcher.waitForFinish());
ASSERT_TRUE(launcher.isExecFinished());
const auto out = PIString::fromAscii(launcher.readOutput());
const auto err = PIString::fromAscii(launcher.readError());
EXPECT_TRUE(out.contains("Hello from stdout"));
EXPECT_TRUE(err.contains("Hello from stderr"));
const int exit_code = launcher.exitCode();
EXPECT_FALSE(launcher.isRunning());
#ifdef _WIN32
EXPECT_EQ(exit_code, 0);
#else
EXPECT_TRUE(WIFEXITED(exit_code));
EXPECT_EQ(WEXITSTATUS(exit_code), 0);
#endif
}
TEST_F(ProcessTest, Input) {
#ifdef _WIN32
const PIStringList args = {"/c", "more"};
#else
const PIStringList args = {"-c", "read input; echo $input"};
#endif
launcher.enableWriteStdIn();
launcher.exec(command, args);
ASSERT_TRUE(launcher.isRunning());
piMSleep(100);
EXPECT_TRUE(launcher.isExecStarted());
EXPECT_TRUE(!launcher.isExecFinished());
PIString test_input = "Test input string\n";
EXPECT_TRUE(launcher.writeInput(test_input.toAscii()));
launcher.closeInput();
ASSERT_TRUE(launcher.waitForFinish());
EXPECT_TRUE(launcher.isExecFinished());
const auto out = PIString::fromAscii(launcher.readOutput());
EXPECT_TRUE(out.contains("Test input string"));
}
TEST_F(ProcessTest, NonexistentCommand) {
const PIString command = {"nonexistent_command_12345"};
launcher.enableWriteStdIn(false);
launcher.enableReadStdOut(false);
launcher.enableReadStdErr(false);
launcher.exec(command);
ASSERT_TRUE(launcher.isRunning());
ASSERT_TRUE(launcher.waitForFinish());
EXPECT_FALSE(launcher.isExecFinished());
}

View File

@@ -0,0 +1,7 @@
list(APPEND PIP_UTILS_LIST "pip_system_calib")
set(PIP_UTILS_LIST ${PIP_UTILS_LIST} PARENT_SCOPE)
add_executable(pip_system_calib "main.cpp")
target_link_libraries(pip_system_calib pip)
if (DEFINED LIB)
install(TARGETS pip_system_calib DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
endif ()

View File

@@ -1,7 +0,0 @@
list(APPEND PIP_UTILS_LIST "pip_system_test")
set(PIP_UTILS_LIST ${PIP_UTILS_LIST} PARENT_SCOPE)
add_executable(pip_system_test "main.cpp")
target_link_libraries(pip_system_test pip)
if (DEFINED LIB)
install(TARGETS pip_system_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
endif ()