| #include <cstring> |
| #include <iostream> |
| #include <string> |
| #include <vector> |
| |
| #include <stdint.h> |
| |
| #include "cm_uv.h" |
| |
| #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<WriteCallbackData*>(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<std::string> arguments = { cmakeCommand, "-E", "echo_append", |
| outputData }; |
| std::vector<const char*> processArgs; |
| for (auto const& arg : arguments) { |
| processArgs.push_back(arg.c_str()); |
| } |
| processArgs.push_back(nullptr); |
| std::vector<uv_stdio_container_t> stdio(3); |
| stdio[0].flags = UV_IGNORE; |
| stdio[0].data.stream = nullptr; |
| stdio[1].flags = |
| static_cast<uv_stdio_flags>(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<char**>(processArgs.data()); |
| options.flags = UV_PROCESS_WINDOWS_HIDE; |
| options.stdio = stdio.data(); |
| options.stdio_count = static_cast<int>(stdio.size()); |
| options.exit_cb = [](uv_process_t* handle, int64_t exitStatus, |
| int termSignal) { |
| auto data = static_cast<ProcessExitData*>(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<char> 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<cmUVStreambuf*>(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; |
| } |