#include #include #include #include #include #include #include "cmGetPipes.h" #include "cmUVHandlePtr.h" #include "cmUVStreambuf.h" #define TEST_STR_LINE_1 "This string must be exactly 128 characters long so" #define TEST_STR_LINE_2 "that we can test CMake's std::streambuf integration" #define TEST_STR_LINE_3 "with libuv's uv_stream_t." #define TEST_STR TEST_STR_LINE_1 "\n" TEST_STR_LINE_2 "\n" TEST_STR_LINE_3 bool writeDataToStreamPipe(uv_loop_t& loop, cm::uv_pipe_ptr& inputPipe, char* outputData, unsigned int outputDataLength, const char* /* unused */) { int err; // Create the pipe int pipeHandles[2]; if (cmGetPipes(pipeHandles) < 0) { std::cout << "Could not open pipe" << std::endl; return false; } cm::uv_pipe_ptr outputPipe; inputPipe.init(loop, 0); outputPipe.init(loop, 0); uv_pipe_open(inputPipe, pipeHandles[0]); uv_pipe_open(outputPipe, pipeHandles[1]); // Write data for reading uv_write_t writeReq; struct WriteCallbackData { bool Finished = false; int Status; } writeData; writeReq.data = &writeData; uv_buf_t outputBuf; outputBuf.base = outputData; outputBuf.len = outputDataLength; if ((err = uv_write(&writeReq, outputPipe, &outputBuf, 1, [](uv_write_t* req, int status) { auto data = static_cast(req->data); data->Finished = true; data->Status = status; })) < 0) { std::cout << "Could not write to pipe: " << uv_strerror(err) << std::endl; return false; } while (!writeData.Finished) { uv_run(&loop, UV_RUN_ONCE); } if (writeData.Status < 0) { std::cout << "Status is " << uv_strerror(writeData.Status) << ", should be 0" << std::endl; return false; } return true; } bool writeDataToStreamProcess(uv_loop_t& loop, cm::uv_pipe_ptr& inputPipe, char* outputData, unsigned int /* unused */, const char* cmakeCommand) { int err; inputPipe.init(loop, 0); std::vector arguments = { cmakeCommand, "-E", "echo_append", outputData }; std::vector processArgs; for (auto const& arg : arguments) { processArgs.push_back(arg.c_str()); } processArgs.push_back(nullptr); std::vector stdio(3); stdio[0].flags = UV_IGNORE; stdio[0].data.stream = nullptr; stdio[1].flags = static_cast(UV_CREATE_PIPE | UV_WRITABLE_PIPE); stdio[1].data.stream = inputPipe; stdio[2].flags = UV_IGNORE; stdio[2].data.stream = nullptr; struct ProcessExitData { bool Finished = false; int64_t ExitStatus; int TermSignal; } exitData; cm::uv_process_ptr process; auto options = uv_process_options_t(); options.file = cmakeCommand; options.args = const_cast(processArgs.data()); options.flags = UV_PROCESS_WINDOWS_HIDE; options.stdio = stdio.data(); options.stdio_count = static_cast(stdio.size()); options.exit_cb = [](uv_process_t* handle, int64_t exitStatus, int termSignal) { auto data = static_cast(handle->data); data->Finished = true; data->ExitStatus = exitStatus; data->TermSignal = termSignal; }; if ((err = process.spawn(loop, options, &exitData)) < 0) { std::cout << "Could not spawn process: " << uv_strerror(err) << std::endl; return false; } while (!exitData.Finished) { uv_run(&loop, UV_RUN_ONCE); } if (exitData.ExitStatus != 0) { std::cout << "Process exit status is " << exitData.ExitStatus << ", should be 0" << std::endl; return false; } if (exitData.TermSignal != 0) { std::cout << "Process term signal is " << exitData.TermSignal << ", should be 0" << std::endl; return false; } return true; } bool testUVStreambufRead( bool (*cb)(uv_loop_t& loop, cm::uv_pipe_ptr& inputPipe, char* outputData, unsigned int outputDataLength, const char* cmakeCommand), const char* cmakeCommand) { char outputData[] = TEST_STR; bool success = false; cm::uv_loop_ptr loop; loop.init(); cm::uv_pipe_ptr inputPipe; std::vector inputData(128); std::streamsize readLen; std::string line; cm::uv_timer_ptr timer; // Create the streambuf cmUVStreambuf inputBuf(64); std::istream inputStream(&inputBuf); if (inputBuf.is_open()) { std::cout << "is_open() is true, should be false" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } // Perform first read test - read all the data if (!cb(*loop, inputPipe, outputData, 128, cmakeCommand)) { goto end; } inputBuf.open(inputPipe); if (!inputBuf.is_open()) { std::cout << "is_open() is false, should be true" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 0) { std::cout << "in_avail() returned " << readLen << ", should be 0" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 128) { std::cout << "sgetn() returned " << readLen << ", should be 128" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 0) { std::cout << "in_avail() returned " << readLen << ", should be 0" << std::endl; goto end; } if (std::memcmp(inputData.data(), outputData, 128)) { std::cout << "Read data does not match write data" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } inputData.assign(128, char{}); inputBuf.close(); if (inputBuf.is_open()) { std::cout << "is_open() is true, should be false" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } // Perform second read test - read some data and then close if (!cb(*loop, inputPipe, outputData, 128, cmakeCommand)) { goto end; } inputBuf.open(inputPipe); if (!inputBuf.is_open()) { std::cout << "is_open() is false, should be true" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 0) { std::cout << "in_avail() returned " << readLen << ", should be 0" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 64)) != 64) { std::cout << "sgetn() returned " << readLen << ", should be 64" << std::endl; goto end; } if (std::memcmp(inputData.data(), outputData, 64)) { std::cout << "Read data does not match write data" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 8) { std::cout << "in_avail() returned " << readLen << ", should be 8" << std::endl; goto end; } inputData.assign(128, char{}); inputBuf.close(); if (inputBuf.is_open()) { std::cout << "is_open() is true, should be false" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } // Perform third read test - read line by line if (!cb(*loop, inputPipe, outputData, 128, cmakeCommand)) { goto end; } inputBuf.open(inputPipe); if (!inputBuf.is_open()) { std::cout << "is_open() is false, should be true" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 0) { std::cout << "in_avail() returned " << readLen << ", should be 0" << std::endl; goto end; } if (!std::getline(inputStream, line)) { std::cout << "getline returned false, should be true" << std::endl; goto end; } if (line != TEST_STR_LINE_1) { std::cout << "Line 1 is \"" << line << "\", should be \"" TEST_STR_LINE_1 "\"" << std::endl; goto end; } if (!std::getline(inputStream, line)) { std::cout << "getline returned false, should be true" << std::endl; goto end; } if (line != TEST_STR_LINE_2) { std::cout << "Line 2 is \"" << line << "\", should be \"" TEST_STR_LINE_2 "\"" << std::endl; goto end; } if (!std::getline(inputStream, line)) { std::cout << "getline returned false, should be true" << std::endl; goto end; } if (line != TEST_STR_LINE_3) { std::cout << "Line 3 is \"" << line << "\", should be \"" TEST_STR_LINE_3 "\"" << std::endl; goto end; } if (std::getline(inputStream, line)) { std::cout << "getline returned true, should be false" << std::endl; goto end; } inputBuf.close(); if (inputBuf.is_open()) { std::cout << "is_open() is true, should be false" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } // Perform fourth read test - run the event loop outside of underflow() if (!cb(*loop, inputPipe, outputData, 128, cmakeCommand)) { goto end; } inputBuf.open(inputPipe); if (!inputBuf.is_open()) { std::cout << "is_open() is false, should be true" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 0) { std::cout << "in_avail() returned " << readLen << ", should be 0" << std::endl; goto end; } uv_run(loop, UV_RUN_DEFAULT); if ((readLen = inputBuf.in_avail()) != 72) { std::cout << "in_avail() returned " << readLen << ", should be 72" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 128) { std::cout << "sgetn() returned " << readLen << ", should be 128" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 0) { std::cout << "in_avail() returned " << readLen << ", should be 0" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 128" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } inputBuf.close(); if (inputBuf.is_open()) { std::cout << "is_open() is true, should be false" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } // Perform fifth read test - close the streambuf in the middle of a read timer.init(*loop, &inputBuf); if (!cb(*loop, inputPipe, outputData, 128, cmakeCommand)) { goto end; } inputBuf.open(inputPipe); if (!inputBuf.is_open()) { std::cout << "is_open() is false, should be true" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != 0) { std::cout << "in_avail() returned " << readLen << ", should be 0" << std::endl; goto end; } uv_timer_start(timer, [](uv_timer_t* handle) { auto buf = static_cast(handle->data); buf->close(); }, 0, 0); if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } if (inputBuf.is_open()) { std::cout << "is_open() is true, should be false" << std::endl; goto end; } if ((readLen = inputBuf.in_avail()) != -1) { std::cout << "in_avail() returned " << readLen << ", should be -1" << std::endl; goto end; } if ((readLen = inputBuf.sgetn(inputData.data(), 128)) != 0) { std::cout << "sgetn() returned " << readLen << ", should be 0" << std::endl; goto end; } success = true; end: return success; } int testUVStreambuf(int argc, char** const argv) { if (argc < 2) { std::cout << "Invalid arguments.\n"; return -1; } if (!testUVStreambufRead(writeDataToStreamPipe, argv[1])) { std::cout << "While executing testUVStreambufRead() with pipe.\n"; return -1; } if (!testUVStreambufRead(writeDataToStreamProcess, argv[1])) { std::cout << "While executing testUVStreambufRead() with process.\n"; return -1; } return 0; }