summaryrefslogtreecommitdiffstats
path: root/Tests/CMakeLib
diff options
context:
space:
mode:
authorKyle Edwards <kyle.edwards@kitware.com>2019-04-30 15:29:30 (GMT)
committerKyle Edwards <kyle.edwards@kitware.com>2019-05-07 17:40:06 (GMT)
commit26025d6e106ffd4cd777fdc3a4343b33c8554c15 (patch)
treef40c983a734557f9137fa775fd051437079c89e4 /Tests/CMakeLib
parent6b04d1cdc281b9b0dee5f59394a1c41d8b96c4a1 (diff)
downloadCMake-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.txt4
-rw-r--r--Tests/CMakeLib/testUVProcessChain.cxx335
-rw-r--r--Tests/CMakeLib/testUVProcessChainHelper.cxx72
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;
+}