diff --git a/CMakeLists.txt b/CMakeLists.txt index 42f81b28..27a2eaab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -612,7 +612,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") diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index cedd417e..74730ec4 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -17,16 +17,17 @@ along with this program. If not, see . */ -#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 # include +# include # endif # ifdef MAC_OS # include @@ -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(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 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(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; } diff --git a/libs/main/system/piprocess.h b/libs/main/system/piprocess.h index 419776ee..e5841a64 100644 --- a/libs/main/system/piprocess.h +++ b/libs/main/system/piprocess.h @@ -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 . + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ #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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5dae9c3..a5ba75e2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,3 +36,4 @@ pip_test(core) pip_test(piobject) pip_test(client_server pip_client_server) pip_test(io) +pip_test(system) diff --git a/tests/system/process_test.cpp b/tests/system/process_test.cpp new file mode 100644 index 00000000..3a04b53d --- /dev/null +++ b/tests/system/process_test.cpp @@ -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()); +} diff --git a/utils/system_calib/CMakeLists.txt b/utils/system_calib/CMakeLists.txt new file mode 100755 index 00000000..1cc42fce --- /dev/null +++ b/utils/system_calib/CMakeLists.txt @@ -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 () diff --git a/utils/system_test/main.cpp b/utils/system_calib/main.cpp similarity index 100% rename from utils/system_test/main.cpp rename to utils/system_calib/main.cpp diff --git a/utils/system_test/CMakeLists.txt b/utils/system_test/CMakeLists.txt deleted file mode 100755 index e93f6be7..00000000 --- a/utils/system_test/CMakeLists.txt +++ /dev/null @@ -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 ()