| #include <cstring> | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| #include <cmext/algorithm> | |
| #include <cm3p/uv.h> | |
| #include <stdint.h> | |
| #include "cmGetPipes.h" | |
| #include "cmUVHandlePtr.h" | |
| #include "cmUVStream.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 | |
| static bool writeDataToStreamPipe(uv_loop_t& loop, cm::uv_pipe_ptr& inputPipe, | |
| char* outputData, | |
| unsigned int outputDataLength, | |
| char const* /* 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; | |
| } | |
| static bool writeDataToStreamProcess(uv_loop_t& loop, | |
| cm::uv_pipe_ptr& inputPipe, | |
| char* outputData, | |
| unsigned int /* unused */, | |
| char const* cmakeCommand) | |
| { | |
| int err; | |
| inputPipe.init(loop, 0); | |
| std::vector<std::string> arguments = { cmakeCommand, "-E", "echo_append", | |
| outputData }; | |
| std::vector<char const*> 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; | |
| #if UV_VERSION_MAJOR > 1 || !defined(CMAKE_USE_SYSTEM_LIBUV) | |
| options.flags |= UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE; | |
| #endif | |
| 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; | |
| } | |
| static bool testUVStreambufRead( | |
| bool (*cb)(uv_loop_t& loop, cm::uv_pipe_ptr& inputPipe, char* outputData, | |
| unsigned int outputDataLength, char const* cmakeCommand), | |
| char const* 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, //-V1086 Ignore underflow | |
| 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; | |
| } | |
| bool testUVIStream() | |
| { | |
| int pipe[] = { -1, -1 }; | |
| if (cmGetPipes(pipe) < 0) { | |
| std::cout << "cmGetPipes() returned an error" << std::endl; | |
| return false; | |
| } | |
| cm::uv_loop_ptr loop; | |
| loop.init(); | |
| cm::uv_pipe_ptr pipeSink; | |
| pipeSink.init(*loop, 0); | |
| uv_pipe_open(pipeSink, pipe[1]); | |
| std::string str = "Hello world!\n"; | |
| uv_write_t writeReq; | |
| uv_buf_t buf; | |
| buf.base = &str.front(); | |
| buf.len = str.length(); | |
| uv_write(&writeReq, pipeSink, &buf, 1, nullptr); | |
| uv_run(loop, UV_RUN_DEFAULT); | |
| cm::uv_pipe_ptr pipeSource; | |
| pipeSource.init(*loop, 0); | |
| uv_pipe_open(pipeSource, pipe[0]); | |
| cmUVIStream pin(pipeSource); | |
| std::string line; | |
| std::getline(pin, line); | |
| if (line != "Hello world!") { | |
| std::cout << "Line was \"" << line << "\", should be \"Hello world!\"" | |
| << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVStreamRead() | |
| { | |
| int pipe[] = { -1, -1 }; | |
| if (cmGetPipes(pipe) < 0) { | |
| std::cout << "cmGetPipes() returned an error" << std::endl; | |
| return false; | |
| } | |
| cm::uv_loop_ptr loop; | |
| loop.init(); | |
| cm::uv_pipe_ptr pipeSink; | |
| pipeSink.init(*loop, 0); | |
| uv_pipe_open(pipeSink, pipe[1]); | |
| std::string str = "Hello world!"; | |
| uv_write_t writeReq; | |
| uv_buf_t buf; | |
| buf.base = &str.front(); | |
| buf.len = str.length(); | |
| uv_write(&writeReq, pipeSink, &buf, 1, nullptr); | |
| uv_run(loop, UV_RUN_DEFAULT); | |
| pipeSink.reset(); | |
| cm::uv_pipe_ptr pipeSource; | |
| pipeSource.init(*loop, 0); | |
| uv_pipe_open(pipeSource, pipe[0]); | |
| std::string output; | |
| bool finished = false; | |
| auto handle = cmUVStreamRead( | |
| pipeSource, | |
| [&output](std::vector<char> data) { cm::append(output, data); }, | |
| [&output, &finished]() { | |
| if (output != "Hello world!") { | |
| std::cout << "Output was \"" << output | |
| << "\", should be \"Hello world!\"" << std::endl; | |
| return; | |
| } | |
| finished = true; | |
| }); | |
| uv_run(loop, UV_RUN_DEFAULT); | |
| if (!finished) { | |
| std::cout << "finished was not set" << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVStreamReadLeak() | |
| { | |
| int pipe[] = { -1, -1 }; | |
| if (cmGetPipes(pipe) < 0) { | |
| std::cout << "cmGetPipes() returned an error" << std::endl; | |
| return false; | |
| } | |
| cm::uv_loop_ptr loop; | |
| loop.init(); | |
| cm::uv_pipe_ptr pipeSink; | |
| pipeSink.init(*loop, 0); | |
| uv_pipe_open(pipeSink, pipe[1]); | |
| std::string str = "Hello world!"; | |
| uv_write_t writeReq; | |
| uv_buf_t buf; | |
| buf.base = &str.front(); | |
| buf.len = str.length(); | |
| uv_write(&writeReq, pipeSink, &buf, 1, nullptr); | |
| uv_run(loop, UV_RUN_DEFAULT); | |
| pipeSink.reset(); | |
| cm::uv_pipe_ptr pipeSource; | |
| pipeSource.init(*loop, 0); | |
| uv_pipe_open(pipeSource, pipe[0]); | |
| std::string output; | |
| bool finished = false; | |
| auto handle = cmUVStreamRead( | |
| pipeSource, | |
| [&output](std::vector<char> data) { cm::append(output, data); }, | |
| [&output, &finished]() { | |
| if (output != "Hello world!") { | |
| std::cout << "Output was \"" << output | |
| << "\", should be \"Hello world!\"" << std::endl; | |
| return; | |
| } | |
| finished = true; | |
| }); | |
| if (finished) { | |
| std::cout << "finished was set" << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| 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; | |
| } | |
| if (!testUVIStream()) { | |
| std::cout << "While executing testUVIStream().\n"; | |
| return -1; | |
| } | |
| if (!testUVStreamRead()) { | |
| std::cout << "While executing testUVStreamRead().\n"; | |
| return -1; | |
| } | |
| if (!testUVStreamReadLeak()) { | |
| std::cout << "While executing testUVStreamReadLeak().\n"; | |
| return -1; | |
| } | |
| return 0; | |
| } |