|
|
@@ -0,0 +1,411 @@ |
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <chrono>
|
|
|
|
#include <thread>
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
#include "CodeDweller/child.hpp"
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Configuration ///////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/// Child program name.
|
|
|
|
const std::string childName("./childProgram");
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// End of configuration ////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
int nTotalTests = 0;
|
|
|
|
int nPass = 0;
|
|
|
|
int nFail = 0;
|
|
|
|
|
|
|
|
bool result;
|
|
|
|
|
|
|
|
#define NO_EXCEPTION_TERM(msg) \
|
|
|
|
std::cout \
|
|
|
|
<< msg << " failed to throw exception at line " \
|
|
|
|
<< __LINE__ << "." << std::endl
|
|
|
|
|
|
|
|
#define EXCEPTION_TERM(msg) \
|
|
|
|
std::cout \
|
|
|
|
<< msg << " threw unexpected exception at line " \
|
|
|
|
<< __LINE__ << ": " << e.what() << std::endl
|
|
|
|
|
|
|
|
#define RETURN_FALSE(msg) \
|
|
|
|
std::cout \
|
|
|
|
<< msg << " at line " << __LINE__ << std::endl; \
|
|
|
|
return false;
|
|
|
|
|
|
|
|
#define RUN_TEST(test) \
|
|
|
|
std::cout << " " #test ": "; \
|
|
|
|
std::cout.flush(); \
|
|
|
|
result = test(); \
|
|
|
|
std::cout << (result ? "ok" : "fail") << std::endl; \
|
|
|
|
nTotalTests++; \
|
|
|
|
if (result) nPass++; else nFail++;
|
|
|
|
|
|
|
|
#define SUMMARY \
|
|
|
|
std::cout \
|
|
|
|
<< "\nPass: " << nPass \
|
|
|
|
<< ", Fail: " << nFail \
|
|
|
|
<< ", Total: " << nTotalTests << std::endl
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Tests ///////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool
|
|
|
|
testIsDone() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
CodeDweller::Child child(childName);
|
|
|
|
|
|
|
|
// Test exception if called out-of-order.
|
|
|
|
try {
|
|
|
|
child.isDone();
|
|
|
|
NO_EXCEPTION_TERM("isDone() called without run()");
|
|
|
|
return false;
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
child.run();
|
|
|
|
if (child.isDone()) {
|
|
|
|
std::cout << "isDone() failure; returned true." << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Command the child to exit.
|
|
|
|
child.writer << 'q';
|
|
|
|
child.writer.flush();
|
|
|
|
|
|
|
|
// Sleep to let the child exit.
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
|
|
|
if (!child.isDone()) {
|
|
|
|
std::cout << "isDone() failure; returned false." << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
EXCEPTION_TERM("isDone()");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
testResult() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
std::vector<std::string> cmd;
|
|
|
|
|
|
|
|
cmd.push_back(childName);
|
|
|
|
cmd.push_back("quit");
|
|
|
|
|
|
|
|
CodeDweller::Child child(cmd);
|
|
|
|
|
|
|
|
// Test exception if called out-of-order.
|
|
|
|
try {
|
|
|
|
(void) child.result();
|
|
|
|
NO_EXCEPTION_TERM(" result() called without run()");
|
|
|
|
return false;
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
child.run();
|
|
|
|
|
|
|
|
// Test exception if called while child is running.
|
|
|
|
try {
|
|
|
|
(void) child.result();
|
|
|
|
NO_EXCEPTION_TERM(" result() called before child exited");
|
|
|
|
return false;
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the child to exit.
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
|
|
|
int32_t result = child.result();
|
|
|
|
if (25 != result) {
|
|
|
|
std::cout << "result() failure; returned " << result
|
|
|
|
<< " instead of 25." << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
EXCEPTION_TERM("result()");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
testTerminate() {
|
|
|
|
|
|
|
|
// Test with no waiting.
|
|
|
|
try {
|
|
|
|
CodeDweller::Child child(childName);
|
|
|
|
child.run();
|
|
|
|
child.terminate();
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
EXCEPTION_TERM("terminate() with no waiting");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with waiting.
|
|
|
|
try {
|
|
|
|
CodeDweller::Child child(childName);
|
|
|
|
child.run();
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
child.terminate();
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
EXCEPTION_TERM("terminate() with 100 ms waiting");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test after the child exits.
|
|
|
|
std::vector<std::string> cmd;
|
|
|
|
|
|
|
|
cmd.push_back(childName);
|
|
|
|
cmd.push_back("quit");
|
|
|
|
|
|
|
|
try {
|
|
|
|
CodeDweller::Child child(cmd);
|
|
|
|
child.run();
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
child.terminate();
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
EXCEPTION_TERM("terminate() after child exits");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test exception thrown for out-of-order calling.
|
|
|
|
try {
|
|
|
|
CodeDweller::Child child(cmd);
|
|
|
|
child.terminate();
|
|
|
|
NO_EXCEPTION_TERM("terminate() called without run()");
|
|
|
|
return false;
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
testReaderWriter() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
size_t bufSize = 15;
|
|
|
|
CodeDweller::Child child(childName, bufSize);
|
|
|
|
std::ostringstream childOutput;
|
|
|
|
std::vector<std::string> expectedChildOutput;
|
|
|
|
char readChar;
|
|
|
|
|
|
|
|
// Generate input.
|
|
|
|
char randomLetter[] = "abcdefghijklmnop#rstuvwxyz";
|
|
|
|
int nChar = bufSize - 1;
|
|
|
|
int nLines = 2;
|
|
|
|
for (int iLine = 0; iLine < nLines; iLine++) {
|
|
|
|
std::string line;
|
|
|
|
line.erase();
|
|
|
|
for (int iChar = 0; iChar < nChar; iChar++) {
|
|
|
|
line.push_back(randomLetter[std::rand() % 26]);
|
|
|
|
}
|
|
|
|
expectedChildOutput.push_back(line);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test exception.
|
|
|
|
try {
|
|
|
|
child.writer << bufSize;
|
|
|
|
child.writer.flush();
|
|
|
|
NO_EXCEPTION_TERM(" writer called without run()");
|
|
|
|
return false;
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the writer stream.
|
|
|
|
try {
|
|
|
|
child.writer.clear();
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
child.run();
|
|
|
|
|
|
|
|
// Write, read, put back, and reread each character.
|
|
|
|
for (std::string line : expectedChildOutput) {
|
|
|
|
|
|
|
|
// Write one line.
|
|
|
|
const char *ptr;
|
|
|
|
|
|
|
|
ptr = line.data();
|
|
|
|
for (std::string::size_type i = 0; i < line.length(); i++) {
|
|
|
|
child.writer << ptr[i];
|
|
|
|
if (!child.writer) {
|
|
|
|
RETURN_FALSE(" Failure in testReaderWriter: writer stream is bad");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
child.writer.flush();
|
|
|
|
if (!child.writer) {
|
|
|
|
RETURN_FALSE(" Failure in testReaderWriter: writer stream is bad");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read one line.
|
|
|
|
std::string readLine;
|
|
|
|
|
|
|
|
readLine.erase();
|
|
|
|
for (std::string::size_type i = 0; i < line.length(); i++) {
|
|
|
|
child.reader >> readChar;
|
|
|
|
if (!child.reader) {
|
|
|
|
RETURN_FALSE(" Failure in testReaderWriter: reader stream is bad");
|
|
|
|
}
|
|
|
|
readLine.push_back(readChar);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to upper case.
|
|
|
|
std::string expectedLine;
|
|
|
|
|
|
|
|
expectedLine = line;
|
|
|
|
for (auto &c : expectedLine) {
|
|
|
|
|
|
|
|
c = toupper(c);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare.
|
|
|
|
if (expectedLine != readLine) {
|
|
|
|
std::cout << " Failure in testReaderWriter." << std::endl;
|
|
|
|
std::cout << " Expected: '" << expectedLine
|
|
|
|
<< "'\n Received: '" << readLine << "'" << std::endl;
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send exit message.
|
|
|
|
child.writer << 'q';
|
|
|
|
child.writer.flush();
|
|
|
|
if (!child.writer) {
|
|
|
|
RETURN_FALSE(" Failure in testReaderWriter: writer stream is bad");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify exit.
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
|
|
|
if (!child.isDone()) {
|
|
|
|
std::cout << " Failure in testReaderWriter: "
|
|
|
|
<< "Child program did not exit." << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
EXCEPTION_TERM("reader()/writer()");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
testReader() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
std::vector<std::string> cmd;
|
|
|
|
|
|
|
|
cmd.push_back(childName);
|
|
|
|
cmd.push_back("write");
|
|
|
|
|
|
|
|
size_t bufSize = 32;
|
|
|
|
CodeDweller::Child child(cmd, bufSize);
|
|
|
|
|
|
|
|
// Test exception.
|
|
|
|
try {
|
|
|
|
int temp;
|
|
|
|
child.reader >> temp;
|
|
|
|
NO_EXCEPTION_TERM(" reader called without run()");
|
|
|
|
return false;
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
child.reader.clear();
|
|
|
|
child.writer.clear();
|
|
|
|
std::ostringstream childOutput;
|
|
|
|
std::string expectedChildOutput("This is a test");
|
|
|
|
char readChar;
|
|
|
|
|
|
|
|
child.run();
|
|
|
|
child.reader.exceptions(std::istream::badbit);
|
|
|
|
child.reader >> std::noskipws;
|
|
|
|
if (!child.reader) {
|
|
|
|
RETURN_FALSE(" Failure in testReader: reader stream is bad");
|
|
|
|
}
|
|
|
|
|
|
|
|
while (child.reader >> readChar) {
|
|
|
|
|
|
|
|
if (!child.reader) {
|
|
|
|
RETURN_FALSE(" Failure in testReader: reader stream is bad");
|
|
|
|
}
|
|
|
|
child.reader.putback(readChar);
|
|
|
|
if (!child.reader) {
|
|
|
|
RETURN_FALSE(" Failure in testReader: reader stream is bad");
|
|
|
|
}
|
|
|
|
child.reader >> readChar;
|
|
|
|
if (!child.reader) {
|
|
|
|
RETURN_FALSE(" Failure in testReader: reader stream is bad");
|
|
|
|
}
|
|
|
|
childOutput << readChar;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!child.reader.eof()) {
|
|
|
|
RETURN_FALSE(" Failure in testReader: Error occured before "
|
|
|
|
"EOF was reached while reading");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check.
|
|
|
|
if (childOutput.str() != expectedChildOutput) {
|
|
|
|
std::cout << " reader() failure in testReader." << std::endl;
|
|
|
|
std::cout << " Expected: '" << expectedChildOutput
|
|
|
|
<< "'\n Received: '" << childOutput.str() << "'"
|
|
|
|
<< std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!child.isDone()) {
|
|
|
|
std::cout << "isDone() failure in testReader." << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
EXCEPTION_TERM("reader()");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// End of tests ////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
int main()
|
|
|
|
{
|
|
|
|
std::cout << "CodeDweller::Child unit tests" << std::endl << std::endl;
|
|
|
|
|
|
|
|
CodeDweller::Child child(childName);
|
|
|
|
|
|
|
|
RUN_TEST(testIsDone);
|
|
|
|
RUN_TEST(testResult);
|
|
|
|
RUN_TEST(testTerminate);
|
|
|
|
RUN_TEST(testReader);
|
|
|
|
RUN_TEST(testReaderWriter);
|
|
|
|
|
|
|
|
SUMMARY;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|