|                                                                                                                                                                                                                                                                                                                                                                                                                           | 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 | #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;
}
 |