#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 bool doReadWrite(size_t bufSize, size_t nominalAboveMin_ms, size_t delta_ms, size_t maxChar) { try { std::vector cmd; cmd.push_back(childName); CodeDweller::Child child(cmd, bufSize, nominalAboveMin_ms, delta_ms); // Characters for testing. char randomLetter[] = "abcdefghijklmnop#rstuvwxyz"; // Calculate how often to output a "." to indicate progress. int maxChunks = maxChar / bufSize; int nChunks = 0; int nDots = 50; int outputEveryChunk = maxChunks / nDots / 2; if (outputEveryChunk == 0) { outputEveryChunk = 2; } // Send and receive random-sized chunks of data. size_t nChar = 0; while (nChar < maxChar) { std::uniform_int_distribution randomChunk(1, bufSize * 4); std::default_random_engine generator(4828943); nChunks++; if (nChunks % outputEveryChunk == 0) { std::cout << "."; std::cout.flush(); } int chunkSize; std::string sentChunk, expectedChunk; // Get a random chunk size for sending. chunkSize = randomChunk(generator); if (chunkSize + nChar > maxChar) { chunkSize = maxChar - nChar; } // Generate output to child. sentChunk.clear(); for (size_t i = 0; i < chunkSize; i++) { sentChunk.push_back(randomLetter[std::rand() % 26]); } // Generated expected input from child. expectedChunk = sentChunk; for (int i = 0; i < expectedChunk.size(); i++) { expectedChunk[i] = std::toupper(expectedChunk[i]); } // Send the data. while (!sentChunk.empty()) { if (child.writeAndShrink(sentChunk) == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } // Read and compare while sending data. std::string chunkFromChild; size_t chunkFromChildSize; (void) child.read(chunkFromChild); chunkFromChildSize = chunkFromChild.size(); if (!chunkFromChild.empty()) { if (chunkFromChild == expectedChunk.substr(0, chunkFromChildSize)) { expectedChunk.erase(0, chunkFromChildSize); } else { std::cout << "Expected: \"" << expectedChunk.substr(0, chunkFromChildSize) << "\", received \"" << chunkFromChild << "\"." << std::endl; std::cout << "Remaining chunk: \"" << expectedChunk << "\"" << std::endl; child.write("q"); std::this_thread::sleep_for(std::chrono::milliseconds(100)); RETURN_FALSE(" comparison failure"); } } } // Read and compare after all data was sent. while (!expectedChunk.empty()) { std::string chunkFromChild; size_t chunkFromChildSize; (void) child.read(chunkFromChild); chunkFromChildSize = chunkFromChild.size(); if (0 == chunkFromChildSize) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } else { if (chunkFromChild == expectedChunk.substr(0, chunkFromChildSize)) { expectedChunk.erase(0, chunkFromChildSize); } else { std::cout << "Expected: \"" << expectedChunk.substr(0, chunkFromChildSize) << "\", received \"" << chunkFromChild << "\"." << std::endl; std::cout << "Remaining chunk: \"" << expectedChunk << "\"" << std::endl; child.write("q"); std::this_thread::sleep_for(std::chrono::milliseconds(100)); RETURN_FALSE(" comparison failure"); } } } nChar += chunkSize; } // Send exit message. if (!child.write("q")) { RETURN_FALSE(" write() failure"); } // Verify exit. std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (!child.isDone()) { std::cout << " Failure in testChildNonblockingReadWrite2: " << "Child program did not exit." << std::endl; return false; } std::string errorDescription; if (child.errorOccurred(errorDescription)) { std::ostringstream temp; temp << " Failure in: testChildNonBlockingReadWrite2: " << errorDescription; RETURN_FALSE(temp.str()); } child.close(); } catch (std::exception &e) { EXCEPTION_TERM("Non-blocking reader test"); return false; } return true; } //////////////////////////////////////////////////////////////////////////////// // ChildStream Tests /////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// bool testChildStreamIsDone() { 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 testChildStreamIsRunning() { 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("testChildStreamIsRunning"); return false; } return true; } bool testChildStreamResult() { try { std::vector cmd; cmd.push_back(childName); cmd.push_back("quit"); // Child sleeps for 50 ms and then exits. 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 testChildStreamClose() { // 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 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 testChildStreamReaderWriter() { try { size_t bufSize = 15; CodeDweller::ChildStream child(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.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 testChildStreamReader() { 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 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 testChildStreamBinaryRead() { try { std::vector 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 testChildStreamNonBlockingRead() { try { std::vector 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; } //////////////////////////////////////////////////////////////////////////////// // Child Tests /////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// bool testChildIsDone() { try { CodeDweller::Child 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. if (!child.write("q")) { RETURN_FALSE(" Failure in testChildIsDone: write() failure"); } // 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; } child.close(); } catch (std::exception &e) { EXCEPTION_TERM("isDone()"); return false; } return true; } bool testChildIsRunning() { try { CodeDweller::Child 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. if (!child.write("q")) { RETURN_FALSE(" write() failure"); } // 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 testChildResult() { 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) { } // 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 testChildClose() { // Test with no child process. try { CodeDweller::Child child; child.close(); NO_EXCEPTION_TERM("close() with no child process"); return false; } catch (std::exception &e) { } // Test with no waiting. try { CodeDweller::Child child(childName); child.close(); } catch (std::exception &e) { EXCEPTION_TERM("close() with no waiting"); return false; } // Test with waiting. try { CodeDweller::Child 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 cmd; cmd.push_back(childName); cmd.push_back("quit"); try { CodeDweller::Child child(cmd); if (!child.write("q")) { RETURN_FALSE(" write() failure"); } 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::Child child; child.close(); NO_EXCEPTION_TERM("close() called without open()"); return false; } catch (std::exception &e) { } return true; } bool testChildStdInClose() { std::string errorDescription; // Test with no child process. try { CodeDweller::Child child; child.closeStdIn(); NO_EXCEPTION_TERM("closeStdIn() with no child process"); return false; } catch (std::exception &e) { } // Test with no waiting. try { CodeDweller::Child child(childName); child.closeStdIn(); child.close(); } catch (std::exception &e) { EXCEPTION_TERM("closeStdIn() with no waiting"); return false; } // Run child that waits for stdin to close. std::vector cmd; cmd.push_back(childName); cmd.push_back("checkStdinEOF"); try { CodeDweller::Child child(cmd); child.closeStdIn(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); int32_t result = child.result(); if (15 != result) { std::cout << "closeStdIn() failure; returned " << result << " instead of 15." << std::endl; return false; } child.close(); if (child.errorOccurred(errorDescription)) { std::ostringstream temp; temp << " Failure in: testChildStdInClose: " << errorDescription; RETURN_FALSE(temp.str()); } } catch (std::exception &e) { EXCEPTION_TERM("closeStdIn() or close()"); return false; } // Test after the child exits. cmd.clear(); cmd.push_back(childName); cmd.push_back("quit"); try { CodeDweller::Child child(cmd); if (!child.write("q")) { RETURN_FALSE(" write() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(100)); child.closeStdIn(); } catch (std::exception &e) { EXCEPTION_TERM("closeStdIn() after child exits"); return false; } // Test exception thrown for out-of-order calling. try { CodeDweller::Child child; child.closeStdIn(); NO_EXCEPTION_TERM("closeStdIn() called without open()"); return false; } catch (std::exception &e) { } return true; } bool testChildOpen() { // Test with no waiting. try { CodeDweller::Child child(childName); child.close(); child.open(childName); child.close(); } catch (std::exception &e) { EXCEPTION_TERM("close()/open() with no waiting"); return false; } // Test with waiting. try { CodeDweller::Child child(childName); std::this_thread::sleep_for(std::chrono::milliseconds(100)); child.close(); child.open(childName); std::this_thread::sleep_for(std::chrono::milliseconds(100)); child.close(); } catch (std::exception &e) { EXCEPTION_TERM("close()/open() with 100 ms waiting"); return false; } // Test exception thrown for out-of-order calling. try { CodeDweller::Child child; child.close(); NO_EXCEPTION_TERM("close() called without open()"); return false; } catch (std::exception &e) { } return true; } bool testChildReadWrite() { size_t bufSize = 15; int nChar = bufSize - 1; // Size of each packet. try { CodeDweller::Child child(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.write("Should Throw"); NO_EXCEPTION_TERM(" writer called without run()"); return false; } catch (std::exception &e) { } child.open(childName); // Write, read test. int nLine = 0; for (std::string &line : expectedChildOutput) { nLine++; // Try to queue. bool done; int nTries = 100; for (int i = 0; i < nTries; i++) { done = child.write(line); if (done) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } if (!done) { std::ostringstream temp; temp << " Failure from write(std::string const &) on line " << nLine; RETURN_FALSE(temp.str()); } // Wait for transmission. for (int i = 0; i < nTries; i++) { done = child.isFinishedWriting(); if (done) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } if (!done) { std::ostringstream temp; temp << " Failure from isFinishedWriting() on line " << nLine; RETURN_FALSE(temp.str()); } std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Read one line. std::string readLine, temp; int nCharToRead, nCharRead; readLine.clear(); nCharToRead = line.size(); for (int i = 0; i < nTries; i++) { nCharRead = child.read(temp, nCharToRead); readLine += temp; nCharToRead -= nCharRead; if (nCharToRead <= 0) { break; } } if (0 != nCharToRead) { std::ostringstream temp; temp << " Failure from () on line " << nLine << ": nCharRead: " << nCharRead; RETURN_FALSE(temp.str()); } // Convert to upper case. std::string expectedLine; expectedLine = line; for (auto &c : expectedLine) { c = toupper(c); } // Compare. if (expectedLine != readLine) { std::cout << " Failure in testReadWrite." << std::endl; std::cout << " Expected: '" << expectedLine << "'\n Received: '" << readLine << "'" << std::endl; return false; } } // Send exit message. if (!child.write("q")) { RETURN_FALSE(" Failure in testChildIsDone: write() failure"); } // Verify exit. std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (!child.isDone()) { std::cout << " Failure in testReadWrite: Child program did not exit." << std::endl; return false; } } catch (std::exception &e) { EXCEPTION_TERM("read()/write()/isFinishedWriting()"); return false; } return true; } bool testChildIsFinishedWriting() { std::vector cmd; cmd.push_back(childName); size_t bufSize = 16; std::uint16_t nominalAboveMin_ms = 100, delta_ms = 0; try { CodeDweller::Child child(cmd, bufSize, nominalAboveMin_ms, delta_ms); if (!child.isFinishedWriting()) { RETURN_FALSE(" isFinishedWriting() failure"); } if (!child.write("0123456789")) { RETURN_FALSE(" write() failure"); } // Busy wait until the data is written. while (!child.isFinishedWriting()) ; std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Writer thread should be pausing now. if (!child.write("0123456789")) { RETURN_FALSE(" write() failure"); } if (child.isFinishedWriting()) { RETURN_FALSE(" isFinishedWriting() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(20)); if (child.isFinishedWriting()) { RETURN_FALSE(" isFinishedWriting() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(80)); if (!child.isFinishedWriting()) { RETURN_FALSE(" isFinishedWriting() failure"); } // Send exit message. if (!child.write("q")) { RETURN_FALSE(" write() failure"); } // Verify exit. std::this_thread::sleep_for(std::chrono::milliseconds(500)); if (!child.isDone()) { std::cout << " Failure in testIsFinishedWriting: " << "Child program did not exit." << std::endl; return false; } } catch (std::exception &e) { EXCEPTION_TERM("write()/isFinishedWriting()"); return false; } return true; } bool testChildRead() { try { size_t bufSize = 32; CodeDweller::Child child(bufSize); // Test exception. try { std::string temp; (void) child.read(temp); NO_EXCEPTION_TERM(" read() called without open()"); return false; } catch (std::exception &e) { } std::ostringstream childOutput; std::string expectedChildOutput("This is a test"); std::string readBuf; std::vector cmd; cmd.push_back(childName); cmd.push_back("write"); child.open(cmd); int nTries = 100; size_t nRead = 0; for (int i = 0; i < nTries; i++) { nRead += child.read(readBuf); childOutput << readBuf; if (expectedChildOutput.size() == nRead) { break; } if (child.errorOccurred(readBuf)) { std::ostringstream temp; temp << " Failure in testReader on try " << i << ": " << readBuf; RETURN_FALSE(temp.str()); } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } // Check. if (childOutput.str() != expectedChildOutput) { std::cout << " read() failure in testChildRead." << 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; } child.close(); } catch (std::exception &e) { EXCEPTION_TERM("reader()"); return false; } return true; } bool testChildNonBlockingReadWrite1() { try { std::vector cmd; cmd.push_back(childName); size_t bufSize = 16; std::uint16_t nominalAboveMin_ms = 15, delta_ms = 0; CodeDweller::Child child(cmd, bufSize, nominalAboveMin_ms, delta_ms); // Check for available input with input. std::string childInput("abc"); if (!child.write(childInput)) { RETURN_FALSE(" first write() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Read one character. std::string readBuf; if (child.read(readBuf, 1) != 1) { RETURN_FALSE(" read() failure"); } if ("A" != readBuf) { RETURN_FALSE(" read() failure"); } // Read. if (child.read(readBuf, 2) != 2) { RETURN_FALSE(" read() failure"); } if ("BC" != readBuf) { RETURN_FALSE(" read() failure"); } // Check that no input is available. if (child.read(readBuf) != 0) { RETURN_FALSE(" read() failure"); } if (!readBuf.empty()) { RETURN_FALSE(" read() failure"); } // Fill input buffer. std::string output("abcdefghijklmnopprstuvwxyz"); std::string expectedInput(output); std::string expectedOutputAfter1(output.substr(bufSize)); for (int i = 0; i < output.size(); i++) { expectedInput[i] = std::toupper(output[i]); } if (child.writeAndShrink(output) != bufSize) { RETURN_FALSE(" writeAndShrink() failure"); } if (output != expectedOutputAfter1) { RETURN_FALSE(" writeAndShrink() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(25)); int expectedLeftOver = output.size(); if (child.writeAndShrink(output) != expectedLeftOver) { RETURN_FALSE(" writeAndShrink() failure"); } if (!output.empty()) { RETURN_FALSE(" writeAndShrink() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(25)); size_t nRead = child.read(readBuf); if (nRead != bufSize) { RETURN_FALSE(" read() failure"); } if (readBuf != expectedInput.substr(0, bufSize)) { RETURN_FALSE(" read() failure)"); } // Wait for reader thread to fill input buffer. std::this_thread::sleep_for(std::chrono::milliseconds(25)); if (child.read(readBuf) != expectedLeftOver) { RETURN_FALSE(" read() failure"); } if (expectedInput.substr(bufSize) != readBuf) { RETURN_FALSE(" read() failure"); } // Send exit message. if (!child.write("q")) { RETURN_FALSE(" write() failure"); } // Verify exit. std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (!child.isDone()) { std::cout << " Failure in testChildNonblockingReadWrite1: " << "Child program did not exit." << std::endl; return false; } std::string errorDescription; if (child.errorOccurred(errorDescription)) { std::ostringstream temp; temp << " Failure in: testChildNonBlockingReadWrite1: " << errorDescription; RETURN_FALSE(temp.str()); } child.close(); } catch (std::exception &e) { EXCEPTION_TERM("Non-blocking reader test"); return false; } return true; } bool testChildNonBlockingReadWrite2() { size_t bufSize, maxChar; std::uint16_t nominalAboveMin_ms = 0, delta_ms = 0; bufSize = 16; maxChar = 1000 * 100; std::cout << "\n many small buffers"; std::cout.flush(); if (!doReadWrite(bufSize, nominalAboveMin_ms, delta_ms, maxChar)) { RETURN_FALSE(" read()/write() failure"); } bufSize = 128 * 1024; maxChar = 1000 * 10; std::cout << "\n one big buffer"; std::cout.flush(); if (!doReadWrite(bufSize, nominalAboveMin_ms, delta_ms, maxChar)) { RETURN_FALSE(" read()/write() failure"); } bufSize = 128 * 1024; maxChar = 1000 * 1000 * 10; std::cout << "\n many big buffers"; std::cout.flush(); if (!doReadWrite(bufSize, nominalAboveMin_ms, delta_ms, maxChar)) { RETURN_FALSE(" read()/write() failure"); } return true; } bool testChildVectorReadWrite() { try { std::vector cmd; cmd.push_back(childName); size_t bufSize = 16; std::uint16_t nominalAboveMin_ms = 15, delta_ms = 0; CodeDweller::Child child(cmd, bufSize, nominalAboveMin_ms, delta_ms); // Check that write fails. std::vector badVector(bufSize + 1); if (child.write(badVector)) { RETURN_FALSE(" write() failure"); } // Check write(). std::vector childInput = {'a', 'b', 'c'}; if (!child.write(childInput)) { RETURN_FALSE(" write() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Read one character. std::vector readBuf; if (child.read(readBuf, 1) != 1) { RETURN_FALSE(" read() failure"); } if ('A' != readBuf[0]) { RETURN_FALSE(" read() failure"); } // Read. if (child.read(readBuf, 2) != 2) { RETURN_FALSE(" read() failure"); } if (std::vector({'B', 'C'}) != readBuf) { RETURN_FALSE(" read() failure"); } // Check that no input is available. if (child.read(readBuf) != 0) { RETURN_FALSE(" read() failure"); } if (!readBuf.empty()) { RETURN_FALSE(" read() failure"); } // Fill input buffer. std::vector output = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'p', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; std::vector expectedInput(output); for (int i = 0; i < output.size(); i++) { expectedInput[i] = std::toupper(output[i]); } if (child.write(output, bufSize) != bufSize) { RETURN_FALSE(" write() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); size_t nRequested = bufSize / 2; // Check partial read. size_t nRead = child.read(readBuf, nRequested); if (nRead != nRequested) { RETURN_FALSE(" read() failure"); } if (readBuf != std::vector(expectedInput.begin(), expectedInput.begin() + nRead)) { RETURN_FALSE(" read() failure)"); } // Check remainder. readBuf.resize(bufSize * 2); nRead = child.read(readBuf); if ( (bufSize - nRequested) != nRead) { RETURN_FALSE(" read() failure"); } if (readBuf != std::vector(expectedInput.begin() + nRequested, expectedInput.begin() + bufSize)) { RETURN_FALSE(" read() failure)"); } // Write remainder. output.erase(output.begin(), output.begin() + bufSize); expectedInput.erase(expectedInput.begin(), expectedInput.begin() + bufSize); size_t nExpectedBytes = output.size(); if (child.writeAndShrink(output) != nExpectedBytes) { RETURN_FALSE(" writeAndShrink() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (child.read(readBuf) != nExpectedBytes) { RETURN_FALSE(" read() failure"); } if (readBuf != expectedInput) { RETURN_FALSE(" read() failure"); } // Send exit message. if (!child.write("q")) { RETURN_FALSE(" write() failure"); } // Verify exit. std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (!child.isDone()) { std::cout << " Failure in testChildVectorReadWrite: " << "Child program did not exit." << std::endl; return false; } std::string errorDescription; if (child.errorOccurred(errorDescription)) { std::ostringstream temp; temp << " Failure in: testChildVectorReadWrite: " << errorDescription; RETURN_FALSE(temp.str()); } child.close(); } catch (std::exception &e) { EXCEPTION_TERM("testChildVectorReadWrite"); return false; } return true; } bool testChildReadDelimited() { try { std::vector cmd; cmd.push_back(childName); size_t bufSize = 16; std::uint16_t nominalAboveMin_ms = 15, delta_ms = 0; CodeDweller::Child child(cmd, bufSize, nominalAboveMin_ms, delta_ms); // Check delimiter at beginning of buffer. std::vector childInput = {'a', 'b', 'c', 'd', 'e'}; if (!child.write(childInput)) { RETURN_FALSE(" write() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Check std::vector readVec; if (!child.readDelimited(readVec, "AB")) { RETURN_FALSE(" readDelimited() failure"); } if (!readVec.empty()) { RETURN_FALSE(" readDelimited() failure"); } if (child.readDelimited(readVec, "n")) { RETURN_FALSE(" readDelimited() failure"); } if (child.readDelimited(readVec, "nlelk;lqm;foim")) { RETURN_FALSE(" readDelimited() failure"); } // Empty buffer. std::string temp; (void) child.read(temp); if ("CDE" != temp) { RETURN_FALSE(" readDelimited() failure"); } // Check delimiter at middle of buffer. if (!child.write(childInput)) { RETURN_FALSE(" write() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Check std::string readStr; std::string delim = "CD"; if (!child.readDelimited(readStr, delim)) { RETURN_FALSE(" readDelimited() failure"); } if ("AB" != readStr) { RETURN_FALSE(" readDelimited() failure"); } delim = "n"; if (child.readDelimited(readStr, delim)) { RETURN_FALSE(" readDelimited() failure"); } delim = "fimsoim3m4"; if (child.readDelimited(readStr, delim)) { RETURN_FALSE(" readDelimited() failure"); } readStr = "orirw"; delim = ""; if (!child.readDelimited(readStr, delim)) { RETURN_FALSE(" readDelimited() failure"); } if (!readStr.empty()) { RETURN_FALSE(" readDelimited() failure"); } // Empty buffer. (void) child.read(temp); if ("E" != temp) { RETURN_FALSE(" readDelimited() failure"); } // Check delimiter at end of buffer. if (!child.write(childInput)) { RETURN_FALSE(" write() failure"); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Check std::vector delimVec = {'E'}; if (!child.readDelimited(readStr, delimVec)) { RETURN_FALSE(" readDelimited() failure"); } if ("ABCD" != readStr) { RETURN_FALSE(" readDelimited() failure"); } delimVec[0] = 'n'; if (child.readDelimited(readStr, delimVec)) { RETURN_FALSE(" readDelimited() failure"); } delimVec.push_back('4'); delimVec.push_back(','); delimVec.push_back('m'); delimVec.push_back('y'); delimVec.push_back('t'); delimVec.push_back('2'); delimVec.push_back('e'); delimVec.push_back('t'); if (child.readDelimited(readStr, delimVec)) { RETURN_FALSE(" readDelimited() failure"); } // Empty buffer. (void) child.read(temp); if ("" != temp) { RETURN_FALSE(" readDelimited() failure"); } // Send exit message. if (!child.write("q")) { RETURN_FALSE(" write() failure"); } // Verify exit. std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (!child.isDone()) { std::cout << " Failure in testChildReadDelimited: " << "Child program did not exit." << std::endl; return false; } std::string errorDescription; if (child.errorOccurred(errorDescription)) { std::ostringstream temp; temp << " Failure in: testChildReadDelimited: " << errorDescription; RETURN_FALSE(temp.str()); } child.close(); } catch (std::exception &e) { EXCEPTION_TERM("testChildReadDelimited"); return false; } return true; } //////////////////////////////////////////////////////////////////////////////// // End of tests //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// int main() { // ChildStream is not supported. #if 0 std::cout << "\nCodeDweller::ChildStream unit tests\n" << std::endl; RUN_TEST(testChildStreamIsDone); RUN_TEST(testChildStreamIsRunning); RUN_TEST(testChildStreamResult); RUN_TEST(testChildStreamClose); RUN_TEST(testChildStreamReader); RUN_TEST(testChildStreamReaderWriter); RUN_TEST(testChildStreamBinaryRead); RUN_TEST(testChildStreamNonBlockingRead); #endif std::cout << "\nCodeDweller::Child unit tests\n" << std::endl; RUN_TEST(testChildIsDone); RUN_TEST(testChildIsRunning); RUN_TEST(testChildResult); RUN_TEST(testChildClose); RUN_TEST(testChildStdInClose); RUN_TEST(testChildOpen); RUN_TEST(testChildIsFinishedWriting); RUN_TEST(testChildRead); RUN_TEST(testChildReadWrite); RUN_TEST(testChildNonBlockingReadWrite1); RUN_TEST(testChildNonBlockingReadWrite2); RUN_TEST(testChildVectorReadWrite); RUN_TEST(testChildReadDelimited); SUMMARY; return 0; }