#include #include #include #include #include #include #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::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 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 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 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 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; } // 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 cmd; cmd.push_back(childName); size_t bufSize = 164; CodeDweller::Child child(cmd, bufSize); child.run(); // Write. std::string childInput("abc"); child.writer << childInput; child.writer.flush(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Read one character. char ch; child.reader.read(&ch, 1); if (ch != 'A') { RETURN_FALSE(" reader.read() returned incorrect value"); } // Read. char buf[bufSize * 2]; child.reader.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.writer << output; child.writer.flush(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); child.reader.read(&ch, 1); int index = 0; if (ch != expectedInput[index++]) { RETURN_FALSE(" reader.read() returned incorrect value"); } size_t nBytesRead = expectedInput.size() - 1; child.reader.read(buf, nBytesRead); buf[nBytesRead] = '\0'; if (expectedInput.substr(index, nBytesRead) != std::string(buf)) { RETURN_FALSE(" reader.read() failure"); } // Send exit message. child.writer << 'q'; child.writer.flush(); if (!child.writer) { 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 cmd; cmd.push_back(childName); size_t bufSize = 16; CodeDweller::Child child(cmd, bufSize); child.run(); // 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.writer << childInput; child.writer.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.reader.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.reader.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.writer << output; child.writer.flush(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (child.numBytesAvailable() != 1) { RETURN_FALSE(" numBytesAvailable() did not return expected 1"); } child.reader.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.reader.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.reader.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.writer << 'q'; child.writer.flush(); if (!child.writer) { 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::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); RUN_TEST(testBinaryRead); RUN_TEST(testNonBlockingRead); SUMMARY; return 0; }