This is an automated email from the ASF dual-hosted git repository. isapego pushed a commit to branch ignite-17424 in repository https://gitbox.apache.org/repos/asf/ignite-3.git
commit 61c64082a870d209db6b3648961b143eb61befff Author: Igor Sapego <[email protected]> AuthorDate: Mon Aug 29 23:02:00 2022 +0400 IGNITE-17424 Start and stop Ignite node --- .../cpp/client-test/src/ignite_client_test.cpp | 16 +- modules/platforms/cpp/test-common/CMakeLists.txt | 1 + .../cpp/test-common/include/ignite_node.h | 52 ++++--- .../include/{ignite_node.h => process.h} | 46 +++--- .../platforms/cpp/test-common/include/test_utils.h | 19 --- .../platforms/cpp/test-common/src/ignite_node.cpp | 51 ++++-- modules/platforms/cpp/test-common/src/process.cpp | 173 +++++++++++++++++++++ .../platforms/cpp/test-common/src/test_utils.cpp | 18 --- 8 files changed, 263 insertions(+), 113 deletions(-) diff --git a/modules/platforms/cpp/client-test/src/ignite_client_test.cpp b/modules/platforms/cpp/client-test/src/ignite_client_test.cpp index 2413b1839b..7fc40ed724 100644 --- a/modules/platforms/cpp/client-test/src/ignite_client_test.cpp +++ b/modules/platforms/cpp/client-test/src/ignite_client_test.cpp @@ -42,25 +42,11 @@ protected: TEST_F(ClientTest, TestTest) { - std::cout << "Hello" << std::endl; - ignite::IgniteNode node; node.start(); - for (int i = 0; i < 20; ++i) - { - std::cout << node.getOutput(); - - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + std::this_thread::sleep_for(std::chrono::seconds(20)); node.stop(); - - for (int i = 0; i < 2; ++i) - { - std::cout << node.getOutput(); - - std::this_thread::sleep_for(std::chrono::seconds(1)); - } } \ No newline at end of file diff --git a/modules/platforms/cpp/test-common/CMakeLists.txt b/modules/platforms/cpp/test-common/CMakeLists.txt index 396ee4ed72..a157d5ed98 100644 --- a/modules/platforms/cpp/test-common/CMakeLists.txt +++ b/modules/platforms/cpp/test-common/CMakeLists.txt @@ -24,6 +24,7 @@ include_directories(include) set(SOURCES src/ignite_node.cpp + src/process.cpp src/test_utils.cpp ) diff --git a/modules/platforms/cpp/test-common/include/ignite_node.h b/modules/platforms/cpp/test-common/include/ignite_node.h index 224aa3e124..490a7985e0 100644 --- a/modules/platforms/cpp/test-common/include/ignite_node.h +++ b/modules/platforms/cpp/test-common/include/ignite_node.h @@ -15,46 +15,54 @@ * limitations under the License. */ -#ifndef TEST_COMMON_IGNITE_NODE -#define TEST_COMMON_IGNITE_NODE +#pragma once -//#include <cstdio> +#include "process.h" namespace ignite { class IgniteNode { public: -// /** -// * Constructor. -// */ -// IgniteNode() = default; + /** + * Constructor. + */ + IgniteNode() = default; -// /** -// * Destructor. -// */ -// ~IgniteNode() = default; + /** + * Destructor. + */ + ~IgniteNode() = default; /** * Start node. + * + * @param dryRun Perform a dry run. Mostly used to ensure that code is compiled and all artifacts are downloaded. */ - void start(); + void start(bool dryRun = false); /** * Stop node. */ void stop(); - /** - * Get current node output. - * - * @param max Max bytes to get. - * @return Output. - */ - std::string getOutput(int max = 1024); +// /** +// * Check whether node is still running. +// * +// * @return @c true if the node is running. +// */ +// bool isRunning(); +// +// /** +// * Get current node output. +// * +// * @param max Max bytes to get. +// * @return Output. +// */ +// std::string getOutput(int max = 1024); + private: - FILE* stream; + /** Underlying process. */ + std::unique_ptr<Process> process; }; } // namespace ignite - -#endif // TEST_COMMON_IGNITE_NODE diff --git a/modules/platforms/cpp/test-common/include/ignite_node.h b/modules/platforms/cpp/test-common/include/process.h similarity index 62% copy from modules/platforms/cpp/test-common/include/ignite_node.h copy to modules/platforms/cpp/test-common/include/process.h index 224aa3e124..b2d6d50121 100644 --- a/modules/platforms/cpp/test-common/include/ignite_node.h +++ b/modules/platforms/cpp/test-common/include/process.h @@ -15,46 +15,44 @@ * limitations under the License. */ -#ifndef TEST_COMMON_IGNITE_NODE -#define TEST_COMMON_IGNITE_NODE +#pragma once -//#include <cstdio> +#include <string> namespace ignite { - class IgniteNode + class Process { public: -// /** -// * Constructor. -// */ -// IgniteNode() = default; + /** + * Destructor. + */ + virtual ~Process() = default; -// /** -// * Destructor. -// */ -// ~IgniteNode() = default; + /** + * Make new process instance. + * + * @param command Command. + * @param workDir Working directory. + * @return Process. + */ + static std::unique_ptr<Process> make(std::string command, std::string workDir); /** - * Start node. + * Start process. */ - void start(); + virtual bool start() = 0; /** - * Stop node. + * Stop process. */ - void stop(); + virtual void stop() = 0; + protected: /** - * Get current node output. - * - * @param max Max bytes to get. - * @return Output. + * Constructor. */ - std::string getOutput(int max = 1024); - private: - FILE* stream; + Process() = default; }; } // namespace ignite -#endif // TEST_COMMON_IGNITE_NODE diff --git a/modules/platforms/cpp/test-common/include/test_utils.h b/modules/platforms/cpp/test-common/include/test_utils.h index ca0d00a51f..01626fd703 100644 --- a/modules/platforms/cpp/test-common/include/test_utils.h +++ b/modules/platforms/cpp/test-common/include/test_utils.h @@ -40,23 +40,4 @@ namespace ignite * Get path to maven executable. */ std::string getMavenPath(); - - /** - * Open process. - * - * @param command System shell command line instruction. - * @param type Mode of the returned process output stream. Can be one of the following: - * "r" - The calling process can read the spawned command's standard output using the returned stream. - * "w" - The calling process can write to the spawned command's standard input using the returned stream. - * @return File stream for the process. - */ - FILE* processOpen(const char *command, const char *type); - - /** - * Waits for the associated process to terminate and returns the exit status of the command. - * - * @param stream Return value from the previous call to processOpen(). - * @return Returns the exit status of the terminating command processor, or -1 if an error occurs. - */ - int processClose(FILE* stream); } // namespace ignite \ No newline at end of file diff --git a/modules/platforms/cpp/test-common/src/ignite_node.cpp b/modules/platforms/cpp/test-common/src/ignite_node.cpp index 1fb4bfb6eb..47807989a8 100644 --- a/modules/platforms/cpp/test-common/src/ignite_node.cpp +++ b/modules/platforms/cpp/test-common/src/ignite_node.cpp @@ -15,17 +15,18 @@ * limitations under the License. */ -#include <iostream> +#include <filesystem> #include <stdexcept> #include <vector> #include <utility> +#include <iostream> #include "ignite_node.h" #include "test_utils.h" namespace ignite { - void IgniteNode::start() + void IgniteNode::start(bool dryRun) { std::string home = resolveIgniteHome(); if (home.empty()) @@ -39,24 +40,44 @@ namespace ignite "/bin/bash -c "; #endif - command += getMavenPath() + " " + "exec:java@platform-test-node-runner"; + command += getMavenPath() + " exec:java@platform-test-node-runner"; + + if (dryRun) + command += " -Dexec.args=dry-run"; + + auto workDir = std::filesystem::path(home) / "modules" / "runner"; + + std::cout << "IGNITE_HOME=" << home << std::endl; + std::cout << "working dir=" << workDir << std::endl; + std::cout << "command=" << command << std::endl; - stream = processOpen(command.c_str(), "r"); + process = Process::make(command, workDir.string()); + if (!process->start()) + { + throw std::runtime_error("Failed to invoke Ignite command: '" + command + "'"); + + process.reset(); + } } void IgniteNode::stop() { - if (stream) - processClose(stream); + if (process) + process->stop(); } - std::string IgniteNode::getOutput(int max) - { - std::string buffer(max, 0); - - size_t actual = std::fread(buffer.data(), 1, max, stream); - buffer.resize(actual); - - return buffer; - } +// bool IgniteNode::isRunning() +// { +// return std::feof(stream) == 0 && std::ferror(stream) == 0; +// } +// +// std::string IgniteNode::getOutput(int max) +// { +// std::string buffer(max, 0); +// +// size_t actual = std::fread(buffer.data(), 1, max, stream); +// buffer.resize(actual); +// +// return buffer; +// } } // namespace ignite \ No newline at end of file diff --git a/modules/platforms/cpp/test-common/src/process.cpp b/modules/platforms/cpp/test-common/src/process.cpp new file mode 100644 index 0000000000..4620b53aec --- /dev/null +++ b/modules/platforms/cpp/test-common/src/process.cpp @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WIN32 +# include <windows.h> +# include <tlhelp32.h> +#endif // WIN32 + +#include <filesystem> +#include <utility> +#include <vector> + +#include "process.h" + +namespace +{ +#ifdef WIN32 + /** + * Get process tree. + * @param processId ID of the parent process. + * @return Process tree. + */ + std::vector<DWORD> getProcessTree(DWORD processId) + { + std::vector<DWORD> children; + PROCESSENTRY32 pe; + + memset(&pe, 0, sizeof(PROCESSENTRY32)); + pe.dwSize = sizeof(PROCESSENTRY32); + + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + + if (Process32First(hSnap, &pe)) + { + BOOL bContinue = TRUE; + + while (bContinue) + { + if (pe.th32ParentProcessID == processId) + children.push_back(pe.th32ProcessID); + + bContinue = Process32Next(hSnap, &pe); + } + } + + std::vector<DWORD> tree(children); + for (auto procId : children) + { + std::vector<DWORD> childTree = getProcessTree(procId); + tree.insert(tree.end(), childTree.begin(), childTree.end()); + } + + return tree; + } + + /** + * Implementation of Process for Windows. + */ + class WinProcess : public ignite::Process + { + public: + /** + * Constructor. + * + * @param command Command. + * @param workDir Working directory. + */ + WinProcess(std::string command, std::string workDir) : + running(false), + command(std::move(command)), + workDir(std::move(workDir)), + info{} + { } + + /** + * Destructor. + */ + ~WinProcess() override = default; + + + /** + * Start process. + */ + bool start() override + { + if (running) + return false; + + STARTUPINFO si; + + std::memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + std::memset(&info, 0, sizeof(info)); + + std::vector<char> cmd(command.begin(), command.end()); + cmd.push_back(0); + + BOOL success = CreateProcess( + NULL, cmd.data(), NULL, NULL, + FALSE, 0, NULL, workDir.c_str(), + &si, &info); + + running = success == TRUE; + + return running; + } + + /** + * Stop process. + */ + void stop() override + { + std::vector<DWORD> processTree = getProcessTree(info.dwProcessId); + for (auto procId : processTree) + { + HANDLE hChildProc = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, procId); + if (hChildProc) + { + TerminateProcess(hChildProc, 1); + CloseHandle(hChildProc); + } + } + + TerminateProcess(info.hProcess, 1); + + WaitForSingleObject( info.hProcess, INFINITE ); + + CloseHandle( info.hProcess ); + CloseHandle( info.hThread ); + } + + private: + /** Running flag. */ + bool running; + + /** Command. */ + const std::string command; + + /** Working directory. */ + const std::string workDir; + + /** Process information. */ + PROCESS_INFORMATION info; + }; + +#else +#endif +} + +namespace ignite +{ + std::unique_ptr<Process> Process::make(std::string command, std::string workDir) + { +#ifdef WIN32 + return std::unique_ptr<Process>(new WinProcess(std::move(command), std::move(workDir))); +#else +#endif + } +} // namespace ignite \ No newline at end of file diff --git a/modules/platforms/cpp/test-common/src/test_utils.cpp b/modules/platforms/cpp/test-common/src/test_utils.cpp index 65fe5b8975..f6b0a46386 100644 --- a/modules/platforms/cpp/test-common/src/test_utils.cpp +++ b/modules/platforms/cpp/test-common/src/test_utils.cpp @@ -91,22 +91,4 @@ namespace ignite // Currently, we only support systems with "mvn" command in PATH return "mvn"; } - - FILE *processOpen(const char *command, const char *type) - { -#ifdef WIN32 - return _popen(command, type); -#else - return popen(command, type); -#endif - } - - int processClose(FILE *stream) - { -#ifdef WIN32 - return _pclose(stream); -#else - return pclose(stream); -#endif - } } // namespace ignite \ No newline at end of file
