Procházet zdrojové kódy

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 před 10 roky
rodič
revize
ade5789945
2 změnil soubory, kde provedl 380 přidání a 130 odebrání
  1. 334
    129
      child.cpp
  2. 46
    1
      child.hpp

+ 334
- 129
child.cpp Zobrazit soubor

@@ -19,6 +19,16 @@
// 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 <stdexcept>
@@ -30,22 +40,11 @@ namespace CodeDweller {
readStreambuf(bufSize),
writeStreambuf(bufSize),
reader(&readStreambuf),
writer(&writeStreambuf) {
writer(&writeStreambuf),
cmdArgs(args) {
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) :
@@ -54,6 +53,7 @@ namespace CodeDweller {
reader(&readStreambuf),
writer(&writeStreambuf),
cmdline(childpath) {
cmdArgs.push_back(childpath);
init();
}
@@ -63,9 +63,15 @@ namespace CodeDweller {
void
Child::init() {
if (cmdArgs.empty()) {
throw std::invalid_argument("A child executable must be specified.");
}
reader.exceptions(std::istream::failbit | std::istream::badbit);
writer.exceptions(std::ostream::failbit | std::ostream::badbit);
childStarted = false;
childExited = false;
exitCodeObtainedFlag = false;
exitCode = 0;
}
@@ -78,6 +84,7 @@ namespace CodeDweller {
"run() was called");
}
#ifdef WIN32
// Set the bInheritHandle flag so pipe handles are inherited.
SECURITY_ATTRIBUTES securityAttributes;
@@ -146,19 +153,33 @@ namespace CodeDweller {
startInfo.hStdInput = childStdInAtChild;
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.
bool status;
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 (!status ) {
@@ -176,18 +197,121 @@ namespace CodeDweller {
childProcess = processInfo.hProcess;
childThread = processInfo.hThread;
childStarted = true;
// Close the child's end of the pipes.
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)) {
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());
}
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;
}
@@ -200,107 +324,144 @@ namespace CodeDweller {
}
#ifdef WIN32
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) {
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;
#ifdef WIN32
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) {
return false;
return false;
}
}
// Child process has exited. Save the exit code.
exitCode = result;
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;
}
int32_t
Child::result() {
int32_t
Child::result() {
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;
DWORD lastError = GetLastError();
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);
LocalFree(winMsgBuf);
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();
@@ -309,23 +470,32 @@ namespace CodeDweller {
}
void
Child::ReadStreambuf::setInputHandle(HANDLE inHandle) {
#ifdef WIN32
void
Child::ReadStreambuf::setInputHandle(HANDLE 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.
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.
char *base = &(buffer.front());
@@ -334,26 +504,37 @@ namespace CodeDweller {
// Check whether this is the first fill.
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.
#ifdef WIN32
DWORD nBytesRead;
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.
if (0 == nBytesRead) {
return traits_type::eof();
}
return traits_type::eof();
}
// Update buffer pointers.
setg(base, start, start + nBytesRead);
@@ -362,9 +543,13 @@ namespace CodeDweller {
}
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());
@@ -373,64 +558,84 @@ namespace CodeDweller {
}
void
Child::WriteStreambuf::setOutputHandle(HANDLE outHandle) {
#ifdef WIN32
void
Child::WriteStreambuf::setOutputHandle(HANDLE outHandle) {
outputHandle = outHandle;
}
#else
void
Child::WriteStreambuf::setOutputFileDescriptor(int outFd) {
outputFileDescriptor = outFd;
}
#endif
void
Child::WriteStreambuf::flushBuffer() {
void
Child::WriteStreambuf::flushBuffer() {
// Write.
std::ptrdiff_t nBytes = pptr() - pbase();
#ifdef WIN32
DWORD nBytesWritten;
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);
if (nBytes != nBytesWritten) {
throw std::runtime_error("Not all data was written to to child "
"process: " + getErrorText());
}
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.
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();
}
int
Child::WriteStreambuf::sync() {
int
Child::WriteStreambuf::sync() {
flushBuffer(); // Throws exception on failure.

+ 46
- 1
child.hpp Zobrazit soubor

@@ -28,7 +28,9 @@
#ifndef CHILD_HPP
#define CHILD_HPP

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

#include <cstdint>
#include <streambuf>
@@ -72,6 +74,7 @@ namespace CodeDweller {
//
explicit ReadStreambuf(std::size_t bufferSize = 4096);

#ifdef WIN32
/// Set the handle to read the standard output of the child
/// process.
//
@@ -79,6 +82,15 @@ namespace CodeDweller {
// output of the child process.
//
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:

@@ -92,7 +104,11 @@ namespace CodeDweller {
ReadStreambuf &operator=(const ReadStreambuf &);

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

/// Read buffer.
std::vector<char> buffer;
@@ -101,17 +117,22 @@ namespace CodeDweller {

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

public:

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

#ifdef WIN32
/// Set the handle to write the standard input of the child
/// process.
//
@@ -119,6 +140,15 @@ namespace CodeDweller {
// input of the child process.
//
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:

@@ -138,7 +168,11 @@ namespace CodeDweller {
WriteStreambuf &operator=(const WriteStreambuf &);

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

/// Write buffer.
std::vector<char> buffer;
@@ -241,17 +275,28 @@ namespace CodeDweller {
/// True if the child process was successfully started.
bool childStarted;

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

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

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

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

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

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

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

Načítá se…
Zrušit
Uložit