Nel codice seguente, sto cercando di implementare un programma che esegue un process e restituisce il suo codice di ritorno, stdout
e stderr
. Ha anche un concetto di timeout dopo il quale il processo figlio viene interrotto. Questa classe verrà utilizzata come parte di unapplicazione multi thread.
#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; }
Di seguito è riportato il codice che sto utilizzando per testarlo
#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 } }
Fornisci i tuoi preziosi commenti e suggerimenti.
Commenti
- Benvenuto in Code Review! Dici che ' stai tentando di implementare il programma. Funziona finora? Hai dubbi sul tuo codice? ' va bene se non ', purché il codice funzioni come previsto.
- @Mast Il codice funziona per i casi per cui lho testato. Ma mi chiedo se ci sono condizioni di gara e altri bug?
- Bene! Potresti includere questi test?
Risposta
Mi sto ancora abituando a Boost::Process
, quindi forse ho” sbagliato “. Tuttavia, hai davvero bisogno del c.wait()
dopo ios.run()
?
Come da loro documentazione , il controllo verrà bloccato in ios.run()
fino a quando io_context
non sarà terminato o, naturalmente, se il tuo deadline_timer
scade.
Risposta
Test mancanti
I non vedere alcun test per i comandi che dovrebbero scadere. Un esempio ovvio potrebbe essere sleep 2
, se lo eseguiamo con un timeout di 1
. Un test più rigoroso sarebbe un comando che ignora tutti i segnali (di quelli che possono essere ignorati): dovremmo assicurarci che se SIGTERM
non funziona, riceverà SIGKILL
eventualmente.
Test basati sui dati
Non mi piace vedere loop allinterno dei casi di test. Boost Test prevede ripetere test con dati diversi? La maggior parte dei framework di test lo fa; sarei sorpreso se fosse m proveniente da Boost.