| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703 | #include <cstdlib>
#include <iostream>
#include <chrono>
#include <thread>
#include <sstream>
#include <algorithm>
#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:  " << 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::ChildStream child;
    // Test exception if called out-of-order.
    try {
      child.isDone();
      NO_EXCEPTION_TERM("isDone() called without run()");
      return false;
    } catch (std::exception &e) {
    }
    // Start the child.
    child.open(childName);
    if (child.isDone()) {
      std::cout << "isDone() failure; returned true." << std::endl;
      return false;
    }
    // Command the child to exit.
    child << 'q';
    child.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 testIsRunning() {
  try {
    CodeDweller::ChildStream child;
    if (child.isRunning()) {
      std::cout << "isRunning() failure; returned true before starting."
                << std::endl;
      return false;
    }
    // Start the child.
    child.open(childName);
    if (!child.isRunning()) {
      std::cout << "isRunning() failure; returned false." << std::endl;
      return false;
    }
    // Command the child to exit.
    child << 'q';
    child.flush();
    // Sleep to let the child exit.
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    child.close();
    if (child.isRunning()) {
      std::cout << "isRunning() failure; returned true after stopping."
                << std::endl;
      return false;
    }
  } catch (std::exception &e) {
    EXCEPTION_TERM("isRunning()");
    return false;
  }
  return true;
}
bool testResult() {
  try {
    std::vector<std::string> cmd;
    cmd.push_back(childName);
    cmd.push_back("quit");
    CodeDweller::ChildStream 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) {
    }
    // 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 testClose() {
  // Test with no waiting.
  try {
      CodeDweller::ChildStream child(childName);
      child.close();
  } catch (std::exception &e) {
    EXCEPTION_TERM("close() with no waiting");
    return false;
  }
  // Test with waiting.
  try {
      CodeDweller::ChildStream child(childName);
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
      child.close();
  } catch (std::exception &e) {
    EXCEPTION_TERM("close() 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::ChildStream child(cmd);
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
      child.close();
  } catch (std::exception &e) {
    EXCEPTION_TERM("close() after child exits");
    return false;
  }
  // Test exception thrown for out-of-order calling.
  try {
      CodeDweller::ChildStream child;
      child.close();
      NO_EXCEPTION_TERM("close() called without run()");
      return false;
  } catch (std::exception &e) {
  }
  return true;
}
bool testReaderWriter() {
  try {
    size_t bufSize = 15;
    CodeDweller::ChildStream child(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.exceptions(std::ostream::failbit | std::ostream::badbit);
      child << bufSize;
      child.flush();
      NO_EXCEPTION_TERM("  writer called without run()");
      return false;
    } catch (std::exception &e) {
    }
    // Clear the writer stream.
    try {
      child.clear();
    } catch (std::exception &e) {
    }
    child.open(childName);
    // 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 << ptr[i];
	if (!child) {
	  RETURN_FALSE("  Failure in testReaderWriter:  writer stream is bad");
	}
      }
      child.flush();
      if (!child) {
	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 >> readChar;
	if (!child) {
	  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 << 'q';
    child.flush();
    if (!child) {
      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 {
    size_t bufSize = 32;
    CodeDweller::ChildStream child(bufSize);
    // Test exception.
    try {
      int temp;
      child.exceptions(std::istream::failbit | std::istream::badbit);
      child >>  temp;
      NO_EXCEPTION_TERM("  reader called without run()");
      return false;
    } catch (std::exception &e) {
    }
    child.clear();
    child.clear();
    std::ostringstream childOutput;
    std::string expectedChildOutput("This is a test");
    char readChar;
    std::vector<std::string> cmd;
    cmd.push_back(childName);
    cmd.push_back("write");
    child.open(cmd);
    child.exceptions(std::istream::badbit);
    child >> std::noskipws;
    if (!child) {
      RETURN_FALSE("  Failure in testReader:  reader stream is bad");
    }
    while (child >> readChar) {
      if (!child) {
	RETURN_FALSE("  Failure in testReader:  reader stream is bad");
      }
      child.putback(readChar);
      if (!child) {
	RETURN_FALSE("  Failure in testReader:  reader stream is bad");
      }
      child >> readChar;
      if (!child) {
	RETURN_FALSE("  Failure in testReader:  reader stream is bad");
      }
      childOutput << readChar;
    }
    if (!child.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;
    }
    // Sleep to let the child exit.
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    if (!child.isDone()) {
      std::cout << "first isDone() failure in testReader." << std::endl;
      return false;
    }
    if (!child.isDone()) {
      std::cout << "second isDone() failure in testReader." << std::endl;
      return false;
    }
  } catch (std::exception &e) {
    EXCEPTION_TERM("reader()");
    return false;
  }
  return true;
}
bool testBinaryRead() {
  try {
    std::vector<std::string> cmd;
    cmd.push_back(childName);
    size_t bufSize = 164;
    CodeDweller::ChildStream child(bufSize);
    child.open(cmd);
    // Write.
    std::string childInput("abc");
    child << childInput;
    child.flush();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // Read one character.
    char ch;
    child.read(&ch, 1);
    if (ch != 'A') {
      RETURN_FALSE("  reader.read() returned incorrect value");
    }
    // Read.
    char buf[bufSize * 2];
    child.read(buf, 2);
    buf[2] = '\0';
    std::string input(buf);
    if (input != "BC") {
      RETURN_FALSE("  reader.read() returned incorrect value");
    }
    // Fill input buffer.
    std::string output("abcdefghijklmnopprstuvwxyz");
    std::string expectedInput(output);
    for (int i = 0; i < output.size(); i++) {
      expectedInput[i] = std::toupper(output[i]);
    }
    child << output;
    child.flush();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    child.read(&ch, 1);
    int index = 0;
    if (ch != expectedInput[index++]) {
      RETURN_FALSE("  reader.read() returned incorrect value");
    }
    size_t nBytesRead = expectedInput.size() - 1;
    child.read(buf, nBytesRead);
    buf[nBytesRead] = '\0';
    if (expectedInput.substr(index, nBytesRead) != std::string(buf)) {
      RETURN_FALSE("  reader.read() failure");
    }
    // Send exit message.
    child << 'q';
    child.flush();
    if (!child) {
      RETURN_FALSE("  Failure in testNonblockingReader:  writer stream is bad");
    }
    // Verify exit.
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    if (!child.isDone()) {
      std::cout << "  Failure in testNonblockingReader:  "
		<< "Child program did not exit."  << std::endl;
      return false;
    }
  } catch (std::exception &e) {
    EXCEPTION_TERM("Binary read test");
    return false;
  }
  return true;
}
bool testNonBlockingRead() {
  try {
    std::vector<std::string> cmd;
    cmd.push_back(childName);
    size_t bufSize = 16;
    CodeDweller::ChildStream child(cmd, bufSize);
    // Check for available input with no input.
    if (child.numBytesAvailable() != 0) {
      RETURN_FALSE("  numBytesAvailable() did not return expected 0");
    }
    // Check for available input with input.
    std::string childInput("abc");
    
    child << childInput;
    child.flush();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // Check that input is available.
    if (child.numBytesAvailable() != 1) {
      RETURN_FALSE("  numBytesAvailable() did not return expected 1");
    }
    // Read one character.
    char ch;
    child.read(&ch, 1);
    if (ch != 'A') {
      RETURN_FALSE("  reader.read() returned incorrect value");
    }
    // Check that input is available.
    if (child.numBytesAvailable() != 2) {
      RETURN_FALSE("  numBytesAvailable() did not return expected 2");
    }
    // Read.
    char buf[bufSize * 2];
    child.read(buf, 2);
    buf[2] = '\0';
    std::string input(buf);
    if (input != "BC") {
      RETURN_FALSE("  reader.read() returned incorrect value");
    }
    // Check that no input is available.
    if (child.numBytesAvailable() != 0) {
      RETURN_FALSE("  numBytesAvailable() did not return expected 0 "
		   "after reading");
    }
    // Fill input buffer.
    std::string output("abcdefghijklmnopprstuvwxyz");
    std::string expectedInput(output);
    for (int i = 0; i < output.size(); i++) {
      expectedInput[i] = std::toupper(output[i]);
    }
    child << output;
    child.flush();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    if (child.numBytesAvailable() != 1) {
      RETURN_FALSE("  numBytesAvailable() did not return expected 1");
    }
    child.read(&ch, 1);
    int index = 0;
    if (ch != expectedInput[index++]) {
      RETURN_FALSE("  reader.read() returned incorrect value");
    }
    size_t nBytesAvailable;
    nBytesAvailable = child.numBytesAvailable();
    if (nBytesAvailable != bufSize) {
      RETURN_FALSE("  numBytesAvailable() did not return expected value");
    }
    std::fill_n(buf, sizeof(buf), 0);
    child.read(buf, nBytesAvailable);
    if (expectedInput.substr(index, nBytesAvailable) != std::string(buf)) {
      RETURN_FALSE("  reader.read() failure");
    }
    index += nBytesAvailable;
    nBytesAvailable = child.numBytesAvailable();
    if (nBytesAvailable != expectedInput.size() - 1 - bufSize) {
      RETURN_FALSE("  numBytesAvailable() did not return expected value");
    }
    std::fill_n(buf, sizeof(buf), 0);
    child.read(buf, nBytesAvailable);
    if (expectedInput.substr(index, nBytesAvailable) != std::string(buf)) {
      RETURN_FALSE("  reader.read() failure");
    }
    index += nBytesAvailable;
    if (expectedInput.size() != index) {
      RETURN_FALSE("  not all data was read by reader.read()");
    }
      
    if (child.numBytesAvailable() != 0) {
      RETURN_FALSE("  numBytesAvailable() did not return expected 0");
    }
    // Send exit message.
    child << 'q';
    child.flush();
    if (!child) {
      RETURN_FALSE("  Failure in testNonblockingReader:  writer stream is bad");
    }
    // Verify exit.
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    if (!child.isDone()) {
      std::cout << "  Failure in testNonblockingReader:  "
		<< "Child program did not exit."  << std::endl;
      return false;
    }
  } catch (std::exception &e) {
    EXCEPTION_TERM("Non-blocking reader test");
    return false;
  }
  return true;
}
////////////////////////////////////////////////////////////////////////////////
// End of tests ////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int main()
{
  std::cout << "CodeDweller::ChildStream unit tests" << std::endl << std::endl;
  CodeDweller::ChildStream child(childName);
  RUN_TEST(testIsDone);
  RUN_TEST(testIsRunning);
  RUN_TEST(testResult);
  RUN_TEST(testClose);
  RUN_TEST(testReader);
  RUN_TEST(testReaderWriter);
  RUN_TEST(testBinaryRead);
  RUN_TEST(testNonBlockingRead);
  SUMMARY;
  return 0;
}
 |