Browse Source

Completed unit tests under Ubuntu for CodeDweller::Child.



git-svn-id: https://svn.microneil.com/svn/CodeDweller/branches/adeniz_1@33 d34b734f-a00e-4b39-a726-e4eeb87269ab
adeniz_1
adeniz 10 years ago
parent
commit
ade5789945
2 changed files with 380 additions and 130 deletions
  1. 334
    129
      child.cpp
  2. 46
    1
      child.hpp

+ 334
- 129
child.cpp View File

// Place, Suite 330, Boston, MA 02111-1307 USA // Place, Suite 330, Boston, MA 02111-1307 USA
//============================================================================== //==============================================================================
#ifndef WIN32
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <cstring>
#include <cerrno>
#endif
#include <iostream> // Temporary. #include <iostream> // Temporary.
#include <stdexcept> #include <stdexcept>
readStreambuf(bufSize), readStreambuf(bufSize),
writeStreambuf(bufSize), writeStreambuf(bufSize),
reader(&readStreambuf), reader(&readStreambuf),
writer(&writeStreambuf) {
writer(&writeStreambuf),
cmdArgs(args) {
init(); init();
if (args.size() == 0) {
//
} else if (args.size() == 1) {
cmdline = args[0];
return;
}
// Append all but last command-line arguments.
for (size_t i = 0; i < args.size() - 1; i++) {
cmdline += args[i] + " ";
}
cmdline += args.back(); // Append last command-line argument.
} }
Child::Child(std::string childpath, size_t bufSize) : Child::Child(std::string childpath, size_t bufSize) :
reader(&readStreambuf), reader(&readStreambuf),
writer(&writeStreambuf), writer(&writeStreambuf),
cmdline(childpath) { cmdline(childpath) {
cmdArgs.push_back(childpath);
init(); init();
} }
void void
Child::init() { Child::init() {
if (cmdArgs.empty()) {
throw std::invalid_argument("A child executable must be specified.");
}
reader.exceptions(std::istream::failbit | std::istream::badbit); reader.exceptions(std::istream::failbit | std::istream::badbit);
writer.exceptions(std::ostream::failbit | std::ostream::badbit); writer.exceptions(std::ostream::failbit | std::ostream::badbit);
childStarted = false; childStarted = false;
childExited = false;
exitCodeObtainedFlag = false; exitCodeObtainedFlag = false;
exitCode = 0; exitCode = 0;
} }
"run() was called"); "run() was called");
} }
#ifdef WIN32
// Set the bInheritHandle flag so pipe handles are inherited. // Set the bInheritHandle flag so pipe handles are inherited.
SECURITY_ATTRIBUTES securityAttributes; SECURITY_ATTRIBUTES securityAttributes;
startInfo.hStdInput = childStdInAtChild; startInfo.hStdInput = childStdInAtChild;
startInfo.dwFlags |= STARTF_USESTDHANDLES; startInfo.dwFlags |= STARTF_USESTDHANDLES;
// Assemble the command line.
std::string cmdline;
if (cmdArgs.size() == 1) {
cmdline = cmdArgs[0];
} else {
// Append all but last command-line arguments.
for (size_t i = 0; i < cmdArgs.size() - 1; i++) {
cmdline += cmdArgs[i] + " ";
}
cmdline += cmdArgs.back(); // Append last command-line argument.
}
// Create the child process. // Create the child process.
bool status; bool status;
status = CreateProcess(NULL, status = CreateProcess(NULL,
(char *) cmdline.c_str(), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
true, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&startInfo, // STARTUPINFO pointer
&processInfo); // receives PROCESS_INFORMATION
(char *) cmdline.c_str(),
NULL, // process security attributes
NULL, // primary thread security attributes
true, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&startInfo, // STARTUPINFO pointer
&processInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application. // If an error occurs, exit the application.
if (!status ) { if (!status ) {
childProcess = processInfo.hProcess; childProcess = processInfo.hProcess;
childThread = processInfo.hThread; childThread = processInfo.hThread;
childStarted = true;
// Close the child's end of the pipes. // Close the child's end of the pipes.
if (!CloseHandle(childStdOutAtChild)) { if (!CloseHandle(childStdOutAtChild)) {
throw std::runtime_error("Error closing the child process stdout handle: " +
getErrorText());
throw std::runtime_error("Error closing the child process "
"stdout handle: " + getErrorText());
} }
if (!CloseHandle(childStdInAtChild)) { if (!CloseHandle(childStdInAtChild)) {
throw std::runtime_error("Error closing the child process stdin handle: " +
throw std::runtime_error("Error closing the child process "
"stdin handle: " + getErrorText());
}
#else
// Create the pipes for the stdin and stdout.
int childStdInPipe[2];
int childStdOutPipe[2];
if (pipe(childStdInPipe) != 0) {
throw std::runtime_error("Error creating pipe for stdin: " +
getErrorText());
}
if (pipe(childStdOutPipe) != 0) {
close(childStdInPipe[0]);
close(childStdInPipe[1]);
throw std::runtime_error("Error creating pipe for stdout: " +
getErrorText());
}
// Create the child process.
childPid = fork();
if (-1 == childPid) {
for (int i = 0; i < 2; i++) {
close(childStdInPipe[i]);
close(childStdOutPipe[i]);
}
throw std::runtime_error("Error creating child process: " +
getErrorText()); getErrorText());
} }
if (0 == childPid) {
// The child executes this. Redirect stdin.
if (dup2(childStdInPipe[0], STDIN_FILENO) == -1) {
std::string errMsg;
// Send message to parent.
errMsg = "Error redirecting stdin in the child: " + getErrorText();
write(childStdOutPipe[1], errMsg.data(), errMsg.size());
exit(-1);
}
// Redirect stdout.
if (dup2(childStdOutPipe[1], STDOUT_FILENO) == -1) {
std::string errMsg;
// Send message to parent.
errMsg = "Error redirecting stdout in the child: " + getErrorText();
write(childStdOutPipe[1], errMsg.data(), errMsg.size());
exit(-1);
}
// Close pipes.
if ( (close(childStdInPipe[0]) != 0) ||
(close(childStdInPipe[1]) != 0) ||
(close(childStdOutPipe[0]) != 0) ||
(close(childStdOutPipe[1]) != 0) ) {
std::string errMsg;
// Send message to parent.
errMsg = "Error closing the pipes in the child: " + getErrorText();
write(STDOUT_FILENO, errMsg.data(), errMsg.size());
exit(-1);
}
// Prepare the arguments.
std::vector<const char *> execvArgv;
for (auto &arg : cmdArgs) {
execvArgv.push_back(arg.c_str());
}
execvArgv.push_back((char *) NULL);
// Run the child process image.
(void) execv(execvArgv[0], (char * const *) &(execvArgv[0]));
// Error from exec.
std::string errMsg;
// Send message to parent.
errMsg = "Error from exec: " + getErrorText();
write(STDOUT_FILENO, errMsg.data(), errMsg.size());
exit(-1);
}
// std::cout << "Child pid: " << childPid << std::endl; // DEBUG.
// Provide the stream buffers with the file descriptors for
// communicating with the child process.
readStreambuf.setInputFileDescriptor(childStdOutPipe[0]);
writeStreambuf.setOutputFileDescriptor(childStdInPipe[1]);
// Close the child's end of the pipes.
if ( (close(childStdInPipe[0]) != 0) ||
(close(childStdOutPipe[1]) != 0) ) {
std::string errMsg;
throw std::runtime_error("Error closing child's end of pipes in "
"the parent: " + getErrorText());
}
#endif
childStarted = true;
} }
} }
#ifdef WIN32
if (!TerminateProcess(childProcess, terminateExitCode)) { if (!TerminateProcess(childProcess, terminateExitCode)) {
throw std::runtime_error("Error terminating the child process: " +
getErrorText());
}
#else
if (kill(childPid, SIGTERM) != 0) {
#endif
throw std::runtime_error("Error terminating the child process: " +
getErrorText());
}
} }
bool
Child::isDone() {
bool
Child::isDone() {
if (exitCodeObtainedFlag) {
if (childExited) {
return true;
return true;
}
}
if (!childStarted) { if (!childStarted) {
throw std::logic_error("Child process was not started "
"when isDone() was called");
}
throw std::logic_error("Child process was not started "
"when isDone() was called");
}
int result; int result;
#ifdef WIN32
if (!GetExitCodeProcess(childProcess, (LPDWORD) &result)) { if (!GetExitCodeProcess(childProcess, (LPDWORD) &result)) {
throw std::runtime_error("Error checking status of child process: " +
getErrorText());
}
throw std::runtime_error("Error checking status of child process: " +
getErrorText());
}
if (STILL_ACTIVE == result) { if (STILL_ACTIVE == result) {
return false;
return false;
}
}
// Child process has exited. Save the exit code. // Child process has exited. Save the exit code.
exitCode = result; exitCode = result;
exitCodeObtainedFlag = true; exitCodeObtainedFlag = true;
#else
int status = 0;
result = waitpid(childPid, &status, WNOHANG);
// std::cout << "isDone(). waitpid(" << childPid << ",...) returned " << result << std::endl; // DEBUG
if (-1 == result) {
throw std::runtime_error("Error checking status of child process: " +
getErrorText());
} else if (0 == result) {
// Child is still running.
// std::cout << "isDone(). Child is still running..." << std::endl; // DEBUG.
return false;
}
// std::cout << "isDone(). Child exited." << std::endl; // DEBUG.
if (WIFEXITED(status)) {
// Child exited normally.
exitCode = WEXITSTATUS(status);
exitCodeObtainedFlag = true;
//std::cout << "isDone(). Child exited normally. Exit code: " << exitCode << std::endl; // DEBUG.
}
#endif
childExited = true;
return true; return true;
} }
int32_t
Child::result() {
int32_t
Child::result() {
if (exitCodeObtainedFlag) { if (exitCodeObtainedFlag) {
return exitCode;
return exitCode;
}
}
if (!childStarted) {
throw std::logic_error("Child process was not started "
"when result() was called");
}
// Check whether the process is running, and get the exit code.
if (!isDone()) {
throw std::logic_error("Child process was still running"
"when result() was called");
}
int result;
// Child process has exited.
if (!exitCodeObtainedFlag) {
if (!GetExitCodeProcess(childProcess, (LPDWORD) &result)) {
throw std::runtime_error("Error getting child process exit code: " +
getErrorText());
}
// Exit code is not available.
throw std::runtime_error("Child process has exited but the exit "
"code is not available");
// Error if the process has not exited.
if (STILL_ACTIVE == result) {
throw std::logic_error("Child process was active when "
"result() was called");
}
}
// Child process has exited. Save the exit code.
exitCode = result;
exitCodeObtainedFlag = true;
return exitCode;
return result;
} }
std::string
Child::getErrorText() {
std::string
Child::getErrorText() {
#ifdef WIN32
LPVOID winMsgBuf; LPVOID winMsgBuf;
DWORD lastError = GetLastError(); DWORD lastError = GetLastError();
FormatMessage( FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
lastError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *) &winMsgBuf,
0, NULL );
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
lastError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *) &winMsgBuf,
0, NULL );
std::string errMsg((char *) winMsgBuf); std::string errMsg((char *) winMsgBuf);
LocalFree(winMsgBuf); LocalFree(winMsgBuf);
return errMsg; return errMsg;
#else
return strerror(errno);
#endif
} }
Child::ReadStreambuf::ReadStreambuf(std::size_t bufferSize) :
inputHandle(0),
buffer(bufferSize + 1) {
Child::ReadStreambuf::ReadStreambuf(std::size_t bufferSize) :
#ifdef WIN32
inputHandle(0),
#else
inputFileDescriptor(-1),
#endif
buffer(bufferSize + 1) {
char *end = &(buffer.front()) + buffer.size(); char *end = &(buffer.front()) + buffer.size();
} }
void
Child::ReadStreambuf::setInputHandle(HANDLE inHandle) {
#ifdef WIN32
void
Child::ReadStreambuf::setInputHandle(HANDLE inHandle) {
inputHandle = inHandle; inputHandle = inHandle;
} }
#else
void
Child::ReadStreambuf::setInputFileDescriptor(int inFd) {
inputFileDescriptor = inFd;
}
#endif
std::streambuf::int_type
Child::ReadStreambuf::underflow() {
std::streambuf::int_type
Child::ReadStreambuf::underflow() {
// Check for empty buffer. // Check for empty buffer.
if (gptr() < egptr()) { if (gptr() < egptr()) {
// Not empty.
return traits_type::to_int_type(*gptr());
// Not empty.
return traits_type::to_int_type(*gptr());
}
}
// Need to fill the buffer. // Need to fill the buffer.
char *base = &(buffer.front()); char *base = &(buffer.front());
// Check whether this is the first fill. // Check whether this is the first fill.
if (eback() == base) { if (eback() == base) {
// Not the first fill. Copy one putback character.
*(eback()) = *(egptr() - 1);
start++;
}
// Not the first fill. Copy one putback character.
*(eback()) = *(egptr() - 1);
start++;
}
// start points to the start of the buffer. Fill buffer. // start points to the start of the buffer. Fill buffer.
#ifdef WIN32
DWORD nBytesRead; DWORD nBytesRead;
if (!ReadFile(inputHandle, if (!ReadFile(inputHandle,
start,
buffer.size() - (start - base),
&nBytesRead,
NULL)) {
return traits_type::eof();
}
start,
buffer.size() - (start - base),
&nBytesRead,
NULL)) {
return traits_type::eof();
}
#else
size_t nBytesRead;
nBytesRead = read(inputFileDescriptor,
start,
buffer.size() - (start - base));
if (-1 == nBytesRead) {
return traits_type::eof();
}
#endif
// Check for EOF. // Check for EOF.
if (0 == nBytesRead) { if (0 == nBytesRead) {
return traits_type::eof();
}
return traits_type::eof();
}
// Update buffer pointers. // Update buffer pointers.
setg(base, start, start + nBytesRead); setg(base, start, start + nBytesRead);
} }
Child::WriteStreambuf::WriteStreambuf(std::size_t bufferSize) :
outputHandle(0),
buffer(bufferSize + 1) {
Child::WriteStreambuf::WriteStreambuf(std::size_t bufferSize) :
#ifdef WIN32
outputHandle(0),
#else
outputFileDescriptor(-1),
#endif
buffer(bufferSize + 1) {
char *base = &(buffer.front()); char *base = &(buffer.front());
} }
void
Child::WriteStreambuf::setOutputHandle(HANDLE outHandle) {
#ifdef WIN32
void
Child::WriteStreambuf::setOutputHandle(HANDLE outHandle) {
outputHandle = outHandle; outputHandle = outHandle;
} }
#else
void
Child::WriteStreambuf::setOutputFileDescriptor(int outFd) {
outputFileDescriptor = outFd;
}
#endif
void
Child::WriteStreambuf::flushBuffer() {
void
Child::WriteStreambuf::flushBuffer() {
// Write. // Write.
std::ptrdiff_t nBytes = pptr() - pbase(); std::ptrdiff_t nBytes = pptr() - pbase();
#ifdef WIN32
DWORD nBytesWritten; DWORD nBytesWritten;
if (!WriteFile(outputHandle, if (!WriteFile(outputHandle,
pbase(),
nBytes,
&nBytesWritten,
NULL)) {
pbump(epptr() - pptr()); // Indicate failure.
throw std::runtime_error("Error writing to child process: " +
getErrorText());
}
if (nBytes != nBytesWritten) {
pbump(epptr() - pptr()); // Indicate failure.
throw std::runtime_error("Not all data was written to to child process: " +
getErrorText());
}
pbase(),
nBytes,
&nBytesWritten,
NULL)) {
// Clear the output buffer.
pbump(-nBytes);
throw std::runtime_error("Error writing to child process: " +
getErrorText());
}
#else
size_t nBytesWritten;
nBytesWritten = write(outputFileDescriptor, pbase(), nBytes);
#endif
// Clear the output buffer.
pbump(-nBytes); pbump(-nBytes);
if (nBytes != nBytesWritten) {
throw std::runtime_error("Not all data was written to to child "
"process: " + getErrorText());
}
return; return;
} }
std::streambuf::int_type
Child::WriteStreambuf::overflow(int_type ch) {
std::streambuf::int_type
Child::WriteStreambuf::overflow(int_type ch) {
// Check whether we're writing EOF. // Check whether we're writing EOF.
if (traits_type::eof() != ch) { if (traits_type::eof() != ch) {
// Not writing EOF.
*(pptr()) = ch;
pbump(1);
// Not writing EOF.
*(pptr()) = ch;
pbump(1);
// Write.
flushBuffer();
// Write.
flushBuffer();
// Success.
return ch;
// Success.
return ch;
}
}
return traits_type::eof(); return traits_type::eof();
} }
int
Child::WriteStreambuf::sync() {
int
Child::WriteStreambuf::sync() {
flushBuffer(); // Throws exception on failure. flushBuffer(); // Throws exception on failure.

+ 46
- 1
child.hpp View File

#ifndef CHILD_HPP #ifndef CHILD_HPP
#define CHILD_HPP #define CHILD_HPP


#ifdef WIN32
#include <windows.h> #include <windows.h>
#endif


#include <cstdint> #include <cstdint>
#include <streambuf> #include <streambuf>
// //
explicit ReadStreambuf(std::size_t bufferSize = 4096); explicit ReadStreambuf(std::size_t bufferSize = 4096);


#ifdef WIN32
/// Set the handle to read the standard output of the child /// Set the handle to read the standard output of the child
/// process. /// process.
// //
// output of the child process. // output of the child process.
// //
void setInputHandle(HANDLE inHandle); void setInputHandle(HANDLE inHandle);
#else
/// Set the file descriptor to read the standard output of the
/// child process.
//
// \param[in] inFd is the input file descriptor for the standard
// output of the child process.
//
void setInputFileDescriptor(int inFd);
#endif


private: private:


ReadStreambuf &operator=(const ReadStreambuf &); ReadStreambuf &operator=(const ReadStreambuf &);


/// Input handle. /// Input handle.
#ifdef WIN32
HANDLE inputHandle; HANDLE inputHandle;
#else
int inputFileDescriptor;
#endif


/// Read buffer. /// Read buffer.
std::vector<char> buffer; std::vector<char> buffer;


/// Streambuf class for writing to the standard input of the child /// Streambuf class for writing to the standard input of the child
/// process. /// process.
//
// Note: If an error occurs when writing the output from the
// parent process, the output buffer is cleared.
//
class WriteStreambuf : public std::streambuf { class WriteStreambuf : public std::streambuf {


public: public:


/// Writeer streambuf constructor.
/// Writer streambuf constructor.
// //
// \param[in] bufferSize is the size in bytes of the input // \param[in] bufferSize is the size in bytes of the input
// buffer. // buffer.
// //
explicit WriteStreambuf(std::size_t bufferSize = 4096); explicit WriteStreambuf(std::size_t bufferSize = 4096);


#ifdef WIN32
/// Set the handle to write the standard input of the child /// Set the handle to write the standard input of the child
/// process. /// process.
// //
// input of the child process. // input of the child process.
// //
void setOutputHandle(HANDLE outHandle); void setOutputHandle(HANDLE outHandle);
#else
/// Set the file descriptor to write the standard input of the
/// child process.
//
// \param[in] outFd is the output file descriptor for the
// standard input of the child process.
//
void setOutputFileDescriptor(int outFd);
#endif


private: private:


WriteStreambuf &operator=(const WriteStreambuf &); WriteStreambuf &operator=(const WriteStreambuf &);


/// Output handle. /// Output handle.
#ifdef WIN32
HANDLE outputHandle; HANDLE outputHandle;
#else
int outputFileDescriptor;
#endif


/// Write buffer. /// Write buffer.
std::vector<char> buffer; std::vector<char> buffer;
/// True if the child process was successfully started. /// True if the child process was successfully started.
bool childStarted; bool childStarted;


/// True if the child process has exited.
bool childExited;

/// Initialize data members. /// Initialize data members.
void init(); void init();


/// Child executable path and command-line parameters.
std::vector<std::string> cmdArgs;

/// Child executable path and command-line parameters. /// Child executable path and command-line parameters.
std::string cmdline; std::string cmdline;


#ifdef WIN32
/// Child's process handle. /// Child's process handle.
HANDLE childProcess; HANDLE childProcess;


/// Child's thread handle. /// Child's thread handle.
HANDLE childThread; HANDLE childThread;
#else
/// Child process ID.
pid_t childPid;
#endif


/// Exit value of the process. /// Exit value of the process.
int32_t exitCode; int32_t exitCode;

Loading…
Cancel
Save