diff options
author | Kyle Edwards <kyle.edwards@kitware.com> | 2019-04-30 15:29:30 (GMT) |
---|---|---|
committer | Kyle Edwards <kyle.edwards@kitware.com> | 2019-05-07 17:40:06 (GMT) |
commit | 26025d6e106ffd4cd777fdc3a4343b33c8554c15 (patch) | |
tree | f40c983a734557f9137fa775fd051437079c89e4 /Tests/CMakeLib | |
parent | 6b04d1cdc281b9b0dee5f59394a1c41d8b96c4a1 (diff) | |
download | CMake-26025d6e106ffd4cd777fdc3a4343b33c8554c15.zip CMake-26025d6e106ffd4cd777fdc3a4343b33c8554c15.tar.gz CMake-26025d6e106ffd4cd777fdc3a4343b33c8554c15.tar.bz2 |
cmUVProcessChain: Add cmUVProcessChain
This class is ultimately intended as a replacement for cmsys::Process.
It spawns a series of processes using libuv, piping the output of each
command into the next.
Note: input support has not yet been implemented because write
support has not yet been implemented on cmUVStreambuf.
Diffstat (limited to 'Tests/CMakeLib')
-rw-r--r-- | Tests/CMakeLib/CMakeLists.txt | 4 | ||||
-rw-r--r-- | Tests/CMakeLib/testUVProcessChain.cxx | 335 | ||||
-rw-r--r-- | Tests/CMakeLib/testUVProcessChainHelper.cxx | 72 |
3 files changed, 411 insertions, 0 deletions
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt index e04bba2..a25f25a 100644 --- a/Tests/CMakeLib/CMakeLists.txt +++ b/Tests/CMakeLib/CMakeLists.txt @@ -15,11 +15,15 @@ set(CMakeLib_TESTS testXMLParser.cxx testXMLSafe.cxx testFindPackageCommand.cxx + testUVProcessChain.cxx testUVRAII.cxx testUVStreambuf.cxx ) +add_executable(testUVProcessChainHelper testUVProcessChainHelper.cxx) + set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR}) +set(testUVProcessChain_ARGS $<TARGET_FILE:testUVProcessChainHelper>) set(testUVStreambuf_ARGS $<TARGET_FILE:cmake>) if(WIN32) diff --git a/Tests/CMakeLib/testUVProcessChain.cxx b/Tests/CMakeLib/testUVProcessChain.cxx new file mode 100644 index 0000000..72ae602 --- /dev/null +++ b/Tests/CMakeLib/testUVProcessChain.cxx @@ -0,0 +1,335 @@ +#include "cmUVProcessChain.h" + +#include "cmAlgorithms.h" +#include "cmGetPipes.h" +#include "cmUVHandlePtr.h" +#include "cmUVStreambuf.h" + +#include "cm_uv.h" + +#include <algorithm> +#include <functional> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +#include <csignal> + +struct ExpectedStatus +{ + bool Finished; + bool MatchExitStatus; + bool MatchTermSignal; + cmUVProcessChain::Status Status; +}; + +static const std::vector<ExpectedStatus> status1 = { + { false, false, false, { 0, 0 } }, + { false, false, false, { 0, 0 } }, + { false, false, false, { 0, 0 } }, +}; + +static const std::vector<ExpectedStatus> status2 = { + { true, true, true, { 0, 0 } }, + { false, false, false, { 0, 0 } }, + { false, false, false, { 0, 0 } }, +}; + +static const std::vector<ExpectedStatus> status3 = { + { true, true, true, { 0, 0 } }, + { true, true, true, { 1, 0 } }, +#ifdef _WIN32 + { true, true, true, { 2, 0 } }, +#else + { true, false, true, { 0, SIGABRT } }, +#endif +}; + +bool operator==(const cmUVProcessChain::Status* actual, + const ExpectedStatus& expected) +{ + if (!expected.Finished) { + return !actual; + } else if (!actual) { + return false; + } + if (expected.MatchExitStatus && + expected.Status.ExitStatus != actual->ExitStatus) { + return false; + } + if (expected.MatchTermSignal && + expected.Status.TermSignal != actual->TermSignal) { + return false; + } + return true; +} + +bool resultsMatch(const std::vector<const cmUVProcessChain::Status*>& actual, + const std::vector<ExpectedStatus>& expected) +{ + return actual.size() == expected.size() && + std::equal(actual.begin(), actual.end(), expected.begin()); +} + +std::string getInput(std::istream& input) +{ + char buffer[1024]; + std::ostringstream str; + do { + input.read(buffer, 1024); + str.write(buffer, input.gcount()); + } while (input.gcount() > 0); + return str.str(); +} + +template <typename T> +std::function<std::ostream&(std::ostream&)> printExpected(bool match, + const T& value) +{ + return [match, value](std::ostream& stream) -> std::ostream& { + if (match) { + stream << value; + } else { + stream << "*"; + } + return stream; + }; +} + +std::ostream& operator<<( + std::ostream& stream, + const std::function<std::ostream&(std::ostream&)>& func) +{ + return func(stream); +} + +void printResults(const std::vector<const cmUVProcessChain::Status*>& actual, + const std::vector<ExpectedStatus>& expected) +{ + std::cout << "Expected: " << std::endl; + for (auto const& e : expected) { + if (e.Finished) { + std::cout << " ExitStatus: " + << printExpected(e.MatchExitStatus, e.Status.ExitStatus) + << ", TermSignal: " + << printExpected(e.MatchTermSignal, e.Status.TermSignal) + << std::endl; + } else { + std::cout << " null" << std::endl; + } + } + std::cout << "Actual:" << std::endl; + for (auto const& a : actual) { + if (a) { + std::cout << " ExitStatus: " << a->ExitStatus + << ", TermSignal: " << a->TermSignal << std::endl; + } else { + std::cout << " null" << std::endl; + } + } +} + +bool checkExecution(cmUVProcessChainBuilder& builder, + std::unique_ptr<cmUVProcessChain>& chain) +{ + std::vector<const cmUVProcessChain::Status*> status; + + chain = cm::make_unique<cmUVProcessChain>(builder.Start()); + if (!chain->Valid()) { + std::cout << "Valid() returned false, should be true" << std::endl; + return false; + } + status = chain->GetStatus(); + if (!resultsMatch(status, status1)) { + std::cout << "GetStatus() did not produce expected output" << std::endl; + printResults(status, status1); + return false; + } + + if (chain->Wait(6000)) { + std::cout << "Wait() returned true, should be false" << std::endl; + return false; + } + status = chain->GetStatus(); + if (!resultsMatch(status, status2)) { + std::cout << "GetStatus() did not produce expected output" << std::endl; + printResults(status, status2); + return false; + } + + if (!chain->Wait()) { + std::cout << "Wait() returned false, should be true" << std::endl; + return false; + } + status = chain->GetStatus(); + if (!resultsMatch(status, status3)) { + std::cout << "GetStatus() did not produce expected output" << std::endl; + printResults(status, status3); + return false; + } + + return true; +} + +bool checkOutput(std::istream& outputStream, std::istream& errorStream) +{ + std::string output = getInput(outputStream); + if (output != "HELO WRD!") { + std::cout << "Output was \"" << output << "\", expected \"HELO WRD!\"" + << std::endl; + return false; + } + + std::string error = getInput(errorStream); + if (error.length() != 3 || error.find('1') == std::string::npos || + error.find('2') == std::string::npos || + error.find('3') == std::string::npos) { + std::cout << "Error was \"" << error << "\", expected \"123\"" + << std::endl; + return false; + } + + return true; +} + +bool testUVProcessChainBuiltin(const char* helperCommand) +{ + cmUVProcessChainBuilder builder; + std::unique_ptr<cmUVProcessChain> chain; + builder.AddCommand({ helperCommand, "echo" }) + .AddCommand({ helperCommand, "capitalize" }) + .AddCommand({ helperCommand, "dedup" }) + .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT) + .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR); + + if (!checkExecution(builder, chain)) { + return false; + } + + if (!chain->OutputStream()) { + std::cout << "OutputStream() was null, expecting not null" << std::endl; + return false; + } + if (!chain->ErrorStream()) { + std::cout << "ErrorStream() was null, expecting not null" << std::endl; + return false; + } + + if (!checkOutput(*chain->OutputStream(), *chain->ErrorStream())) { + return false; + } + + return true; +} + +bool testUVProcessChainExternal(const char* helperCommand) +{ + cmUVProcessChainBuilder builder; + std::unique_ptr<cmUVProcessChain> chain; + int outputPipe[2], errorPipe[2]; + cm::uv_pipe_ptr outputInPipe, outputOutPipe, errorInPipe, errorOutPipe; + + if (cmGetPipes(outputPipe) < 0) { + std::cout << "Error creating pipes" << std::endl; + return false; + } + if (cmGetPipes(errorPipe) < 0) { + std::cout << "Error creating pipes" << std::endl; + return false; + } + + builder.AddCommand({ helperCommand, "echo" }) + .AddCommand({ helperCommand, "capitalize" }) + .AddCommand({ helperCommand, "dedup" }) + .SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, outputPipe[1]) + .SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, errorPipe[1]); + + if (!checkExecution(builder, chain)) { + return false; + } + + if (chain->OutputStream()) { + std::cout << "OutputStream() was not null, expecting null" << std::endl; + return false; + } + if (chain->ErrorStream()) { + std::cout << "ErrorStream() was not null, expecting null" << std::endl; + return false; + } + + outputOutPipe.init(chain->GetLoop(), 0); + uv_pipe_open(outputOutPipe, outputPipe[1]); + outputOutPipe.reset(); + + errorOutPipe.init(chain->GetLoop(), 0); + uv_pipe_open(errorOutPipe, errorPipe[1]); + errorOutPipe.reset(); + + outputInPipe.init(chain->GetLoop(), 0); + uv_pipe_open(outputInPipe, outputPipe[0]); + cmUVStreambuf outputBuf; + outputBuf.open(outputInPipe); + std::istream outputStream(&outputBuf); + + errorInPipe.init(chain->GetLoop(), 0); + uv_pipe_open(errorInPipe, errorPipe[0]); + cmUVStreambuf errorBuf; + errorBuf.open(errorInPipe); + std::istream errorStream(&errorBuf); + + if (!checkOutput(outputStream, errorStream)) { + return false; + } + + return true; +} + +bool testUVProcessChainNone(const char* helperCommand) +{ + cmUVProcessChainBuilder builder; + std::unique_ptr<cmUVProcessChain> chain; + builder.AddCommand({ helperCommand, "echo" }) + .AddCommand({ helperCommand, "capitalize" }) + .AddCommand({ helperCommand, "dedup" }); + + if (!checkExecution(builder, chain)) { + return false; + } + + if (chain->OutputStream()) { + std::cout << "OutputStream() was not null, expecting null" << std::endl; + return false; + } + if (chain->ErrorStream()) { + std::cout << "ErrorStream() was not null, expecting null" << std::endl; + return false; + } + + return true; +} + +int testUVProcessChain(int argc, char** const argv) +{ + if (argc < 2) { + std::cout << "Invalid arguments.\n"; + return -1; + } + + if (!testUVProcessChainBuiltin(argv[1])) { + std::cout << "While executing testUVProcessChainBuiltin().\n"; + return -1; + } + + if (!testUVProcessChainExternal(argv[1])) { + std::cout << "While executing testUVProcessChainExternal().\n"; + return -1; + } + + if (!testUVProcessChainNone(argv[1])) { + std::cout << "While executing testUVProcessChainNone().\n"; + return -1; + } + + return 0; +} diff --git a/Tests/CMakeLib/testUVProcessChainHelper.cxx b/Tests/CMakeLib/testUVProcessChainHelper.cxx new file mode 100644 index 0000000..263665d --- /dev/null +++ b/Tests/CMakeLib/testUVProcessChainHelper.cxx @@ -0,0 +1,72 @@ +#include <chrono> +#include <iostream> +#include <set> +#include <sstream> +#include <string> +#include <thread> + +#include <cctype> +#include <cstdlib> + +std::string getStdin() +{ + char buffer[1024]; + std::ostringstream str; + do { + std::cin.read(buffer, 1024); + str.write(buffer, std::cin.gcount()); + } while (std::cin.gcount() > 0); + return str.str(); +} + +int main(int argc, char** argv) +{ + if (argc < 2) { + return -1; + } + + std::string command = argv[1]; + if (command == "echo") { + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); + std::cout << "HELLO world!" << std::flush; + std::cerr << "1" << std::flush; + return 0; + } + if (command == "capitalize") { + std::this_thread::sleep_for(std::chrono::milliseconds(9000)); + std::string input = getStdin(); + for (auto& c : input) { + c = static_cast<char>(std::toupper(c)); + } + std::cout << input << std::flush; + std::cerr << "2" << std::flush; + return 1; + } + if (command == "dedup") { + // Use a nested scope to free all resources before aborting below. + { + std::string input = getStdin(); + std::set<char> seen; + std::string output; + for (auto c : input) { + if (!seen.count(c)) { + seen.insert(c); + output += c; + } + } + std::cout << output << std::flush; + std::cerr << "3" << std::flush; + } + + // On Windows, the exit code of abort() is different between debug and + // release builds, and does not yield a term_signal in libuv in either + // case. For the sake of simplicity, we just return another non-zero code. +#ifdef _WIN32 + return 2; +#else + std::abort(); +#endif + } + + return -1; +} |