Im folgenden Code versuche ich, ein Programm zu implementieren, das a ausführt verarbeitet und gibt den Rückkehrcode stdout
und stderr
zurück. Es gibt auch ein Konzept für eine Zeitüberschreitung, nach der der untergeordnete Prozess beendet wird. Diese Klasse wird als Teil einer Multithread-Anwendung verwendet.
#ifndef PROCESS_H_ #define PROCESS_H_ //Header #include <chrono> #include <thread> #include <iostream> #include <boost/process.hpp> #include <boost/asio.hpp namespace bp = boost::process; class Proces { public: Process(const std::string& cmd, const int timeout); void run(); int getReturnStatus(); //Must be only called after run() exits std::string getStdOut(); //Must be only called after run() exits std::string getStdErr(); //Must be only called after run() exits bool wasKilled(); //Must be only called after run() exits private: void initLog(); void timeoutHandler(const boost::system::error_code& ec); void kill(); const std::string command; const int timeout; int returnStatus; std::string stdOut; std::string stdErr; bool killed; bool stopped; boost::process::group group; boost::asio::io_context ioc; boost::asio::deadline_timer deadline_timer; }; #endif //Source #include "Process.h" Process::Process(const std::string& cmd, const int timeout): command(cmd), timeout(timeout), returnStatus(0), stdOut(""), stdErr(""), killed(false), stopped(false), ioc(), deadline_timer(ioc) { } void Process::timeoutHandler(const boost::system::error_code& ec) { if (stopped || ec == boost::asio::error::operation_aborted) { return; } std::cout << "Time Up!"<< std::endl; kill(); deadline_timer.expires_at(boost::posix_time::pos_infin); } void Process::run() { std::future<std::string> dataOut; std::future<std::string> dataErr; std::cout << "Running command: " << command << std::endl; bp::child c(command, bp::std_in.close(), bp::std_out > dataOut, bp::std_err > dataErr, ioc, group, bp::on_exit([=](int e, const std::error_code& ec) { std::cout << "on_exit: " << ec.message() << " -> " << e << std::endl; deadline_timer.cancel(); returnStatus = e; })); deadline_timer.expires_from_now(boost::posix_time::seconds(timeout)); deadline_timer.async_wait(std::bind(&Process::timeoutHandler, this, std::placeholders::_1)); ioc.run(); c.wait(); stdOut = dataOut.get(); stdErr = dataErr.get(); } //Must be only called after run() exits int Process::getReturnStatus() { return returnStatus; } //Must be only called after run() exits std::string Process::getStdOut() { return stdOut; } //Must be only called after run() exits std::string Process::getStdErr() { return stdErr; } void Process::kill() { std::error_code ec; group.terminate(ec); if(ec) { std::cout << "Error occurred while trying to kill the process: " << ec.message(); throw std::runtime_error(ec.message()); } std::cout << "Killed the process and all its descendants" << std::endl; killed = true; stopped = true; } //Must be only called after run() exits bool Process::wasKilled() { return killed; }
Nachfolgend finden Sie den Code, den ich zum Testen verwende
#define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE ProcessTest #include <boost/test/unit_test.hpp> #include "../src/Process.h" BOOST_AUTO_TEST_CASE( ProcessTest ) { const std::vector<std::string> commands = { "ls" , "pwd" , "uname -a" , "cat /proc/cpuinfo" , "wget https://dl.google.com/dl/earth/client/current/google-earth-pro-stable-current.x86_64.rpm"}; for(const auto& cmd: commands) { Process p(cmd, 3600); p.run(); BOOST_CHECK( p.getReturnStatus() == 0); // #1 continues on error } const std::vector<std::string> commandsThatShouldFail = { "ls doesnotexit" , "cat /proc/doesnotexist" , "wget https://dl.google.com/dl/earth/client/current/doesnotxist.rpm"}; for(const auto& cmd: commandsThatShouldFail) { Process p(cmd, 3600); p.run(); BOOST_CHECK( p.getReturnStatus() != 0); // #1 continues on error } }
Bitte geben Sie Ihre wertvollen Kommentare und Vorschläge an.
Kommentare
- Willkommen bei Code Review! Sie sagen, Sie ' versuchen, das Programm zu implementieren. Funktioniert es soweit? Haben Sie Bedenken bezüglich Ihres Codes? ' ist in Ordnung, wenn Sie ' t nicht verwenden, solange der Code wie beabsichtigt funktioniert.
- @Mast Der Code funktioniert für die Fälle, für die ich ihn getestet habe. Aber ich frage mich, ob es Rennbedingungen und andere Fehler gibt?
- Gut! Könnten Sie diese Tests einschließen?
Antwort
Ich gewöhne mich immer noch an Boost::Process
, also habe ich das vielleicht falsch verstanden. Benötigen Sie jedoch wirklich die c.wait()
nach ios.run()
?
Gemäß ihrer Dokumentation wird die Steuerung bei ios.run()
blockiert, bis io_context
beendet ist oder natürlich, wenn Ihr deadline_timer
läuft ab.
Antwort
Fehlende Tests
I. Es werden keine Tests für Befehle angezeigt, bei denen eine Zeitüberschreitung auftreten sollte. Ein offensichtliches Beispiel könnte sleep 2
sein, wenn wir es mit einer Zeitüberschreitung von 1
. Ein strengerer Test wäre ein Befehl, der alle Signale (von denen, die ignoriert werden können) ignoriert. Wir sollten sicherstellen, dass SIGTERM
nicht funktioniert SIGKILL
eventuell.
Datengesteuerte Tests
Ich möchte keine Schleifen in Testfällen sehen. Hat Boost Test Vorkehrungen für Wiederholen von Tests mit unterschiedlichen Daten? Die meisten Test-Frameworks tun dies. Ich wäre überrascht, wenn das m wäre Ausgabe von Boost.