| #include <algorithm> | |
| #include <csignal> | |
| #include <cstdio> | |
| #include <functional> | |
| #include <iostream> | |
| #include <sstream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <cm/memory> | |
| #include <cm3p/uv.h> | |
| #include "cmGetPipes.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmUVHandlePtr.h" | |
| #include "cmUVProcessChain.h" | |
| #include "cmUVStream.h" | |
| #include "cmUVStreambuf.h" | |
| #ifdef _WIN32 | |
| # include <windows.h> | |
| # include "cmsys/SystemInformation.hxx" | |
| #endif | |
| struct ExpectedStatus | |
| { | |
| bool MatchExitStatus; | |
| bool MatchTermSignal; | |
| cmUVProcessChain::Status Status; | |
| cmUVProcessChain::ExceptionCode ExceptionCode; | |
| std::string ExceptionString; | |
| }; | |
| static char const* ExceptionCodeToString(cmUVProcessChain::ExceptionCode code) | |
| { | |
| switch (code) { | |
| case cmUVProcessChain::ExceptionCode::None: | |
| return "None"; | |
| case cmUVProcessChain::ExceptionCode::Fault: | |
| return "Fault"; | |
| case cmUVProcessChain::ExceptionCode::Illegal: | |
| return "Illegal"; | |
| case cmUVProcessChain::ExceptionCode::Interrupt: | |
| return "Interrupt"; | |
| case cmUVProcessChain::ExceptionCode::Numerical: | |
| return "Numerical"; | |
| case cmUVProcessChain::ExceptionCode::Spawn: | |
| return "Spawn"; | |
| case cmUVProcessChain::ExceptionCode::Other: | |
| return "Other"; | |
| default: | |
| return ""; | |
| } | |
| } | |
| bool operator==(cmUVProcessChain::Status const* actual, | |
| ExpectedStatus const& expected) | |
| { | |
| if (expected.Status.SpawnResult != actual->SpawnResult) { | |
| return false; | |
| } | |
| if (expected.Status.Finished != actual->Finished) { | |
| return false; | |
| } | |
| if (expected.MatchExitStatus && | |
| expected.Status.ExitStatus != actual->ExitStatus) { | |
| return false; | |
| } | |
| if (expected.MatchTermSignal && | |
| expected.Status.TermSignal != actual->TermSignal) { | |
| return false; | |
| } | |
| if (expected.Status.Finished && | |
| std::make_pair(expected.ExceptionCode, expected.ExceptionString) != | |
| actual->GetException()) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool resultsMatch( | |
| std::vector<cmUVProcessChain::Status const*> const& actual, | |
| std::vector<ExpectedStatus> const& expected) | |
| { | |
| return actual.size() == expected.size() && | |
| std::equal(actual.begin(), actual.end(), expected.begin()); | |
| } | |
| static 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, | |
| T const& value) | |
| { | |
| return [match, value](std::ostream& stream) -> std::ostream& { | |
| if (match) { | |
| stream << value; | |
| } else { | |
| stream << "*"; | |
| } | |
| return stream; | |
| }; | |
| } | |
| std::ostream& operator<<( | |
| std::ostream& stream, | |
| std::function<std::ostream&(std::ostream&)> const& func) | |
| { | |
| return func(stream); | |
| } | |
| static void printResults( | |
| std::vector<cmUVProcessChain::Status const*> const& actual, | |
| std::vector<ExpectedStatus> const& expected) | |
| { | |
| std::cout << "Expected: " << std::endl; | |
| for (auto const& e : expected) { | |
| std::cout << " SpawnResult: " << e.Status.SpawnResult | |
| << ", Finished: " << e.Status.Finished << ", ExitStatus: " | |
| << printExpected(e.MatchExitStatus, e.Status.ExitStatus) | |
| << ", TermSignal: " | |
| << printExpected(e.MatchTermSignal, e.Status.TermSignal) | |
| << ", ExceptionCode: " | |
| << printExpected(e.Status.Finished, | |
| ExceptionCodeToString(e.ExceptionCode)) | |
| << ", ExceptionString: \"" | |
| << printExpected(e.Status.Finished, e.ExceptionString) << '"' | |
| << std::endl; | |
| } | |
| std::cout << "Actual:" << std::endl; | |
| for (auto const& a : actual) { | |
| auto exception = a->GetException(); | |
| std::cout << " SpawnResult: " << a->SpawnResult | |
| << ", Finished: " << a->Finished | |
| << ", ExitStatus: " << a->ExitStatus | |
| << ", TermSignal: " << a->TermSignal | |
| << ", ExceptionCode: " << ExceptionCodeToString(exception.first) | |
| << ", ExceptionString: \"" << exception.second << '"' | |
| << std::endl; | |
| } | |
| } | |
| static bool checkExecution(cmUVProcessChainBuilder& builder, | |
| std::unique_ptr<cmUVProcessChain>& chain) | |
| { | |
| static std::vector<ExpectedStatus> const status1 = { | |
| { false, | |
| false, | |
| { 0, false, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| { false, | |
| false, | |
| { 0, false, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| { false, | |
| false, | |
| { 0, false, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| }; | |
| static std::vector<ExpectedStatus> const status2 = { | |
| { true, | |
| true, | |
| { 0, true, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| { false, | |
| false, | |
| { 0, false, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| { false, | |
| false, | |
| { 0, false, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| }; | |
| static std::vector<ExpectedStatus> const status3 = { | |
| { true, | |
| true, | |
| { 0, true, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| { true, | |
| true, | |
| { 0, true, 1, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| #ifdef _WIN32 | |
| { true, | |
| true, | |
| { 0, true, STATUS_ACCESS_VIOLATION, 0 }, | |
| cmUVProcessChain::ExceptionCode::Fault, | |
| "Access violation" }, | |
| #else | |
| { false, | |
| true, | |
| { 0, true, 0, SIGABRT }, | |
| cmUVProcessChain::ExceptionCode::Other, | |
| "Subprocess aborted" }, | |
| #endif | |
| }; | |
| std::vector<cmUVProcessChain::Status const*> 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->Finished()) { | |
| std::cout << "Finished() returned true, should be false" << std::endl; | |
| return false; | |
| } | |
| if (chain->Wait(9000)) { | |
| 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->Finished()) { | |
| std::cout << "Finished() returned true, should be false" << std::endl; | |
| 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; | |
| } | |
| if (!chain->Finished()) { | |
| std::cout << "Finished() returned false, should be true" << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| static 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); | |
| auto qemu_error_pos = error.find("qemu:"); | |
| if (qemu_error_pos != std::string::npos) { | |
| error.resize(qemu_error_pos); | |
| } | |
| 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(char const* 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) | |
| .SetBuiltinLoop(); | |
| if (builder.GetLoop()) { | |
| std::cout << "GetLoop() should return null" << std::endl; | |
| return false; | |
| } | |
| if (!checkExecution(builder, chain)) { | |
| return false; | |
| } | |
| if (!chain->OutputStream()) { | |
| std::cout << "OutputStream() was invalid, expecting valid" << std::endl; | |
| return false; | |
| } | |
| if (!chain->ErrorStream()) { | |
| std::cout << "ErrorStream() was invalid, expecting valid" << std::endl; | |
| return false; | |
| } | |
| cmUVIStream output(chain->OutputStream()); | |
| cmUVIStream error(chain->ErrorStream()); | |
| if (!checkOutput(output, error)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainBuiltinMerged(char const* helperCommand) | |
| { | |
| cmUVProcessChainBuilder builder; | |
| std::unique_ptr<cmUVProcessChain> chain; | |
| builder.AddCommand({ helperCommand, "echo" }) | |
| .AddCommand({ helperCommand, "capitalize" }) | |
| .AddCommand({ helperCommand, "dedup" }) | |
| .SetMergedBuiltinStreams(); | |
| if (!checkExecution(builder, chain)) { | |
| return false; | |
| } | |
| if (!chain->OutputStream()) { | |
| std::cout << "OutputStream() was invalid, expecting valid" << std::endl; | |
| return false; | |
| } | |
| if (chain->ErrorStream()) { | |
| std::cout << "ErrorStream() was valid, expecting invalid" << std::endl; | |
| return false; | |
| } | |
| cmUVIStream mergedStream(chain->OutputStream()); | |
| std::string merged = getInput(mergedStream); | |
| auto qemuErrorPos = merged.find("qemu:"); | |
| if (qemuErrorPos != std::string::npos) { | |
| merged.resize(qemuErrorPos); | |
| } | |
| if (merged.length() != cmStrLen("HELO WRD!123") || | |
| merged.find('1') == std::string::npos || | |
| merged.find('2') == std::string::npos || | |
| merged.find('3') == std::string::npos) { | |
| std::cout << "Expected output to contain '1', '2', and '3', was \"" | |
| << merged << "\"" << std::endl; | |
| return false; | |
| } | |
| std::string output; | |
| for (auto const& c : merged) { | |
| if (c != '1' && c != '2' && c != '3') { | |
| output += c; | |
| } | |
| } | |
| if (output != "HELO WRD!") { | |
| std::cout << "Output was \"" << output << "\", expected \"HELO WRD!\"" | |
| << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainExternal(char const* 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 (builder.GetLoop()) { | |
| std::cout << "GetLoop() should return null" << std::endl; | |
| return false; | |
| } | |
| if (!checkExecution(builder, chain)) { | |
| return false; | |
| } | |
| if (chain->OutputStream()) { | |
| std::cout << "OutputStream() was valid, expecting invalid" << std::endl; | |
| return false; | |
| } | |
| if (chain->ErrorStream()) { | |
| std::cout << "ErrorStream() was valid, expecting invalid" << 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(char const* 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 valid, expecting invalid" << std::endl; | |
| return false; | |
| } | |
| if (chain->ErrorStream()) { | |
| std::cout << "ErrorStream() was valid, expecting invalid" << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainCwdUnchanged(char const* helperCommand) | |
| { | |
| cmUVProcessChainBuilder builder; | |
| builder.AddCommand({ helperCommand, "pwd" }) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR); | |
| auto chain = builder.Start(); | |
| chain.Wait(); | |
| if (chain.GetStatus().front()->ExitStatus != 0) { | |
| std::cout << "Exit status was " << chain.GetStatus().front()->ExitStatus | |
| << ", expecting 0" << std::endl; | |
| return false; | |
| } | |
| cmUVIStream output(chain.OutputStream()); | |
| auto cwd = getInput(output); | |
| if (!cmHasLiteralSuffix(cwd, "/Tests/CMakeLib")) { | |
| std::cout << "Working directory was \"" << cwd | |
| << "\", expected to end in \"/Tests/CMakeLib\"" << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainCwdChanged(char const* helperCommand) | |
| { | |
| cmUVProcessChainBuilder builder; | |
| builder.AddCommand({ helperCommand, "pwd" }) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR) | |
| .SetWorkingDirectory(".."); | |
| auto chain = builder.Start(); | |
| chain.Wait(); | |
| if (chain.GetStatus().front()->ExitStatus != 0) { | |
| std::cout << "Exit status was " << chain.GetStatus().front()->ExitStatus | |
| << ", expecting 0" << std::endl; | |
| return false; | |
| } | |
| cmUVIStream output(chain.OutputStream()); | |
| auto cwd = getInput(output); | |
| if (!cmHasLiteralSuffix(cwd, "/Tests")) { | |
| std::cout << "Working directory was \"" << cwd | |
| << "\", expected to end in \"/Tests\"" << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainSpawnFail(char const* helperCommand) | |
| { | |
| static std::vector<ExpectedStatus> const status1 = { | |
| { false, | |
| false, | |
| { 0, false, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| { false, | |
| false, | |
| { UV_ENOENT, true, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::Spawn, | |
| uv_strerror(UV_ENOENT) }, | |
| #ifdef _WIN32 | |
| { true, | |
| true, | |
| { 0, true, STATUS_ACCESS_VIOLATION, 0 }, | |
| cmUVProcessChain::ExceptionCode::Fault, | |
| "Access violation" }, | |
| #else | |
| { false, | |
| true, | |
| { 0, true, 0, SIGABRT }, | |
| cmUVProcessChain::ExceptionCode::Other, | |
| "Subprocess aborted" }, | |
| #endif | |
| }; | |
| static std::vector<ExpectedStatus> const status2 = { | |
| #ifdef _WIN32 | |
| { true, | |
| true, | |
| { 0, true, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::None, | |
| "" }, | |
| #else | |
| { false, | |
| true, | |
| { 0, true, 0, SIGPIPE }, | |
| cmUVProcessChain::ExceptionCode::Other, | |
| "SIGPIPE" }, | |
| #endif | |
| { false, | |
| false, | |
| { UV_ENOENT, true, 0, 0 }, | |
| cmUVProcessChain::ExceptionCode::Spawn, | |
| uv_strerror(UV_ENOENT) }, | |
| #ifdef _WIN32 | |
| { true, | |
| true, | |
| { 0, true, STATUS_ACCESS_VIOLATION, 0 }, | |
| cmUVProcessChain::ExceptionCode::Fault, | |
| "Access violation" }, | |
| #else | |
| { false, | |
| true, | |
| { 0, true, 0, SIGABRT }, | |
| cmUVProcessChain::ExceptionCode::Other, | |
| "Subprocess aborted" }, | |
| #endif | |
| }; | |
| std::vector<cmUVProcessChain::Status const*> status; | |
| cmUVProcessChainBuilder builder; | |
| builder.AddCommand({ helperCommand, "echo" }) | |
| .AddCommand({ "this_command_is_for_cmake_and_should_never_exist" }) | |
| .AddCommand({ helperCommand, "dedup" }) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR); | |
| auto chain = builder.Start(); | |
| if (!chain.Valid()) { | |
| std::cout << "Valid() returned false, should be true" << std::endl; | |
| return false; | |
| } | |
| // Some platforms, like Solaris 10, take a long time to report a trapped | |
| // subprocess to the parent process (about 1.7 seconds in the case of | |
| // Solaris 10.) Wait 3 seconds to give it enough time. | |
| if (chain.Wait(3000)) { | |
| std::cout << "Wait() did not time out" << 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()) { | |
| std::cout << "Wait() timed out" << 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; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainInputFile(char const* helperCommand) | |
| { | |
| std::unique_ptr<FILE, int (*)(FILE*)> f( | |
| fopen("testUVProcessChainInput.txt", "rb"), fclose); | |
| cmUVProcessChainBuilder builder; | |
| builder.AddCommand({ helperCommand, "dedup" }) | |
| .SetExternalStream(cmUVProcessChainBuilder::Stream_INPUT, f.get()) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT); | |
| auto chain = builder.Start(); | |
| if (!chain.Wait()) { | |
| std::cout << "Wait() timed out" << std::endl; | |
| return false; | |
| } | |
| cmUVIStream stream(chain.OutputStream()); | |
| std::string output = getInput(stream); | |
| if (output != "HELO WRD!") { | |
| std::cout << "Output was \"" << output << "\", expected \"HELO WRD!\"" | |
| << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainWait0(char const* helperCommand) | |
| { | |
| cmUVProcessChainBuilder builder; | |
| builder.AddCommand({ helperCommand, "echo" }); | |
| auto chain = builder.Start(); | |
| if (!chain.Wait(0)) { | |
| std::cout << "Wait(0) returned false, should be true" << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool testUVProcessChainExternalLoop(char const* helperCommand) | |
| { | |
| cm::uv_loop_ptr loop; | |
| loop.init(); | |
| cmUVProcessChainBuilder builder; | |
| builder.AddCommand({ helperCommand, "echo" }) | |
| .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT) | |
| .SetExternalLoop(*loop); | |
| if (builder.GetLoop() != loop) { | |
| std::cout << "GetLoop() should return external loop" << std::endl; | |
| return false; | |
| } | |
| auto chain = builder.Start(); | |
| if (&chain.GetLoop() != loop) { | |
| std::cout << "GetLoop() should return external loop" << std::endl; | |
| return false; | |
| } | |
| if (!chain.Wait()) { | |
| std::cout << "Wait() timed out" << std::endl; | |
| return false; | |
| } | |
| cmUVIStream stream(chain.OutputStream()); | |
| std::string output = getInput(stream); | |
| if (output != "HELLO world!") { | |
| std::cout << "Output was \"" << output << "\", expected \"HELLO world!\"" | |
| << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| #ifdef _WIN32 | |
| bool testUVProcessChainNativeHandle(char const* helperCommand) | |
| { | |
| cmUVProcessChainBuilder builder; | |
| builder.AddCommand({ helperCommand, "pwd" }); | |
| auto chain = builder.Start(); | |
| HANDLE handle = chain.GetNativeProcessHandle(0); | |
| if (!handle || handle == INVALID_HANDLE_VALUE) { | |
| std::cout << "Missing native child process handle\n"; | |
| return false; | |
| } | |
| DWORD const pid = GetProcessId(handle); | |
| if (!pid || pid == GetCurrentProcessId()) { | |
| std::cout << "Native process handle does not identify the child\n"; | |
| return false; | |
| } | |
| DWORD exitCode; | |
| FILETIME creationTime, exitTime, kernelTime, userTime; | |
| if (!chain.Wait() || chain.GetNativeProcessHandle(0) != handle || | |
| GetProcessId(handle) != pid || !GetExitCodeProcess(handle, &exitCode) || | |
| exitCode != 0 || | |
| !GetProcessTimes(handle, &creationTime, &exitTime, &kernelTime, | |
| &userTime)) { | |
| std::cout << "Native child process handle is not usable after exit\n"; | |
| return false; | |
| } | |
| cmUVProcessChainBuilder failingBuilder; | |
| failingBuilder.AddCommand( | |
| { "this_command_is_for_cmake_and_should_never_exist" }); | |
| auto failingChain = failingBuilder.Start(); | |
| if (failingChain.GetNativeProcessHandle(0) != nullptr) { | |
| std::cout << "Failed spawn should not have a native process handle\n"; | |
| return false; | |
| } | |
| cmsys::SystemInformation::ProcessResourceUsage usage{}; | |
| if (cmsys::SystemInformation::GetProcessResourceUsage( | |
| usage, INVALID_HANDLE_VALUE)) { | |
| std::cout | |
| << "Invalid child handle must not report current-process usage\n"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| #endif | |
| int testUVProcessChain(int argc, char** const argv) | |
| { | |
| if (argc < 2) { | |
| std::cout << "Invalid arguments.\n"; | |
| return -1; | |
| } | |
| #ifdef _WIN32 | |
| if (!testUVProcessChainNativeHandle(argv[1])) { | |
| std::cout << "While executing testUVProcessChainNativeHandle().\n"; | |
| return -1; | |
| } | |
| #endif | |
| if (!testUVProcessChainBuiltin(argv[1])) { | |
| std::cout << "While executing testUVProcessChainBuiltin().\n"; | |
| return -1; | |
| } | |
| if (!testUVProcessChainBuiltinMerged(argv[1])) { | |
| std::cout << "While executing testUVProcessChainBuiltinMerged().\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; | |
| } | |
| if (!testUVProcessChainCwdUnchanged(argv[1])) { | |
| std::cout << "While executing testUVProcessChainCwdUnchanged().\n"; | |
| return -1; | |
| } | |
| if (!testUVProcessChainCwdChanged(argv[1])) { | |
| std::cout << "While executing testUVProcessChainCwdChanged().\n"; | |
| return -1; | |
| } | |
| if (!testUVProcessChainSpawnFail(argv[1])) { | |
| std::cout << "While executing testUVProcessChainSpawnFail().\n"; | |
| return -1; | |
| } | |
| if (!testUVProcessChainInputFile(argv[1])) { | |
| std::cout << "While executing testUVProcessChainInputFile().\n"; | |
| return -1; | |
| } | |
| if (!testUVProcessChainWait0(argv[1])) { | |
| std::cout << "While executing testUVProcessChainWait0().\n"; | |
| return -1; | |
| } | |
| if (!testUVProcessChainExternalLoop(argv[1])) { | |
| std::cout << "While executing testUVProcessChainExternalLoop().\n"; | |
| return -1; | |
| } | |
| return 0; | |
| } |