Browse Source

Merge topic 'cmuvstreambuf'

c74698cb75 cmUVStreambuf: Add std::streambuf implementation for uv_stream_t
8cfd25db71 cmUVHandlePtr: Add cm::uv_loop_ptr
c0e6b22d0a Refactor: Move/rename cmProcessGetPipes() to cmGetPipes()

Acked-by: Kitware Robot <[email protected]>
Merge-request: !3240
Brad King 6 years ago
parent
commit
ea026fb219

+ 3 - 0
Source/CMakeLists.txt

@@ -264,6 +264,8 @@ set(SRCS
   cmGeneratorExpression.h
   cmGeneratorTarget.cxx
   cmGeneratorTarget.h
+  cmGetPipes.cxx
+  cmGetPipes.h
   cmGlobalCommonGenerator.cxx
   cmGlobalCommonGenerator.h
   cmGlobalGenerator.cxx
@@ -386,6 +388,7 @@ set(SRCS
   cmUuid.cxx
   cmUVHandlePtr.cxx
   cmUVHandlePtr.h
+  cmUVStreambuf.h
   cmUVSignalHackRAII.h
   cmVariableWatch.cxx
   cmVariableWatch.h

+ 2 - 44
Source/CTest/cmProcess.cxx

@@ -5,61 +5,19 @@
 #include "cmCTest.h"
 #include "cmCTestRunTest.h"
 #include "cmCTestTestHandler.h"
+#include "cmGetPipes.h"
 #include "cmsys/Process.h"
 
-#include <fcntl.h>
 #include <iostream>
 #include <signal.h>
 #include <string>
 #if defined(_WIN32)
 #  include "cm_kwiml.h"
-#else
-#  include <unistd.h>
 #endif
 #include <utility>
 
 #define CM_PROCESS_BUF_SIZE 65536
 
-#if defined(_WIN32) && !defined(__CYGWIN__)
-#  include <io.h>
-
-static int cmProcessGetPipes(int* fds)
-{
-  SECURITY_ATTRIBUTES attr;
-  HANDLE readh, writeh;
-  attr.nLength = sizeof(attr);
-  attr.lpSecurityDescriptor = nullptr;
-  attr.bInheritHandle = FALSE;
-  if (!CreatePipe(&readh, &writeh, &attr, 0))
-    return uv_translate_sys_error(GetLastError());
-  fds[0] = _open_osfhandle((intptr_t)readh, 0);
-  fds[1] = _open_osfhandle((intptr_t)writeh, 0);
-  if (fds[0] == -1 || fds[1] == -1) {
-    CloseHandle(readh);
-    CloseHandle(writeh);
-    return uv_translate_sys_error(GetLastError());
-  }
-  return 0;
-}
-#else
-#  include <errno.h>
-
-static int cmProcessGetPipes(int* fds)
-{
-  if (pipe(fds) == -1) {
-    return uv_translate_sys_error(errno);
-  }
-
-  if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
-      fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
-    close(fds[0]);
-    close(fds[1]);
-    return uv_translate_sys_error(errno);
-  }
-  return 0;
-}
-#endif
-
 cmProcess::cmProcess(cmCTestRunTest& runner)
   : Runner(runner)
   , Conv(cmProcessOutput::UTF8, CM_PROCESS_BUF_SIZE)
@@ -120,7 +78,7 @@ bool cmProcess::StartProcess(uv_loop_t& loop, std::vector<size_t>* affinity)
   pipe_reader.init(loop, 0, this);
 
   int fds[2] = { -1, -1 };
-  status = cmProcessGetPipes(fds);
+  status = cmGetPipes(fds);
   if (status != 0) {
     cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,
                "Error initializing pipe: " << uv_strerror(status)

+ 48 - 0
Source/cmGetPipes.cxx

@@ -0,0 +1,48 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmGetPipes.h"
+
+#include "cm_uv.h"
+
+#include <fcntl.h>
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#  include <io.h>
+
+int cmGetPipes(int* fds)
+{
+  SECURITY_ATTRIBUTES attr;
+  HANDLE readh, writeh;
+  attr.nLength = sizeof(attr);
+  attr.lpSecurityDescriptor = nullptr;
+  attr.bInheritHandle = FALSE;
+  if (!CreatePipe(&readh, &writeh, &attr, 0))
+    return uv_translate_sys_error(GetLastError());
+  fds[0] = _open_osfhandle((intptr_t)readh, 0);
+  fds[1] = _open_osfhandle((intptr_t)writeh, 0);
+  if (fds[0] == -1 || fds[1] == -1) {
+    CloseHandle(readh);
+    CloseHandle(writeh);
+    return uv_translate_sys_error(GetLastError());
+  }
+  return 0;
+}
+#else
+#  include <errno.h>
+#  include <unistd.h>
+
+int cmGetPipes(int* fds)
+{
+  if (pipe(fds) == -1) {
+    return uv_translate_sys_error(errno);
+  }
+
+  if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+      fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+    close(fds[0]);
+    close(fds[1]);
+    return uv_translate_sys_error(errno);
+  }
+  return 0;
+}
+#endif

+ 8 - 0
Source/cmGetPipes.h

@@ -0,0 +1,8 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmGetPipes_h
+#define cmGetPipes_h
+
+int cmGetPipes(int* fds);
+
+#endif

+ 47 - 7
Source/cmUVHandlePtr.cxx

@@ -11,19 +11,59 @@
 
 namespace cm {
 
-static void close_delete(uv_handle_t* h)
+struct uv_loop_deleter
 {
-  free(h);
+  void operator()(uv_loop_t* loop) const;
+};
+
+void uv_loop_deleter::operator()(uv_loop_t* loop) const
+{
+  uv_run(loop, UV_RUN_DEFAULT);
+  int result = uv_loop_close(loop);
+  (void)result;
+  assert(result >= 0);
+  free(loop);
+}
+
+int uv_loop_ptr::init(void* data)
+{
+  this->reset();
+
+  this->loop.reset(static_cast<uv_loop_t*>(calloc(1, sizeof(uv_loop_t))),
+                   uv_loop_deleter());
+  this->loop->data = data;
+
+  return uv_loop_init(this->loop.get());
+}
+
+void uv_loop_ptr::reset()
+{
+  this->loop.reset();
+}
+
+uv_loop_ptr::operator uv_loop_t*()
+{
+  return this->loop.get();
+}
+
+uv_loop_t* uv_loop_ptr::operator->() const noexcept
+{
+  return this->loop.get();
+}
+
+uv_loop_t* uv_loop_ptr::get() const
+{
+  return this->loop.get();
 }
 
 template <typename T>
-static void default_delete(T* type_handle)
+static void handle_default_delete(T* type_handle)
 {
   auto handle = reinterpret_cast<uv_handle_t*>(type_handle);
   if (handle) {
     assert(!uv_is_closing(handle));
     if (!uv_is_closing(handle)) {
-      uv_close(handle, &close_delete);
+      uv_close(handle, [](uv_handle_t* h) { free(h); });
     }
   }
 }
@@ -34,7 +74,7 @@ static void default_delete(T* type_handle)
 template <typename T>
 struct uv_handle_deleter
 {
-  void operator()(T* type_handle) const { default_delete(type_handle); }
+  void operator()(T* type_handle) const { handle_default_delete(type_handle); }
 };
 
 template <typename T>
@@ -107,7 +147,7 @@ struct uv_handle_deleter<uv_async_t>
   void operator()(uv_async_t* handle)
   {
     std::lock_guard<std::mutex> lock(*handleMutex);
-    default_delete(handle);
+    handle_default_delete(handle);
   }
 };
 
@@ -136,7 +176,7 @@ struct uv_handle_deleter<uv_signal_t>
   {
     if (handle) {
       uv_signal_stop(handle);
-      default_delete(handle);
+      handle_default_delete(handle);
     }
   }
 };

+ 39 - 1
Source/cmUVHandlePtr.h

@@ -30,7 +30,45 @@
 namespace cm {
 
 /***
- * RAII class to simplify and insure the safe usage of uv_*_t types. This
+ * RAII class to simplify and ensure the safe usage of uv_loop_t. This includes
+ * making sure resources are properly freed.
+ */
+class uv_loop_ptr
+{
+protected:
+  std::shared_ptr<uv_loop_t> loop;
+
+public:
+  uv_loop_ptr(uv_loop_ptr const&) = delete;
+  uv_loop_ptr& operator=(uv_loop_ptr const&) = delete;
+  uv_loop_ptr(uv_loop_ptr&&) noexcept;
+  uv_loop_ptr& operator=(uv_loop_ptr&&) noexcept;
+
+  // Dtor and ctor need to be inline defined like this for default ctors and
+  // dtors to work.  Some compilers do not like '= default' here.
+  uv_loop_ptr() {} // NOLINT(modernize-use-equals-default)
+  uv_loop_ptr(std::nullptr_t) {}
+  ~uv_loop_ptr() { this->reset(); }
+
+  int init(void* data = nullptr);
+
+  /**
+   * Properly close the handle if needed and sets the inner handle to nullptr
+   */
+  void reset();
+
+  /**
+   * Allow less verbose calling of uv_loop_* functions
+   * @return reinterpreted handle
+   */
+  operator uv_loop_t*();
+
+  uv_loop_t* get() const;
+  uv_loop_t* operator->() const noexcept;
+};
+
+/***
+ * RAII class to simplify and ensure the safe usage of uv_*_t types. This
  * includes making sure resources are properly freed and contains casting
  * operators which allow for passing into relevant uv_* functions.
  *

+ 219 - 0
Source/cmUVStreambuf.h

@@ -0,0 +1,219 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmUVStreambuf_h
+#define cmUVStreambuf_h
+
+#include "cmUVHandlePtr.h"
+
+#include "cm_uv.h"
+
+#include <algorithm>
+#include <cstring>
+#include <streambuf>
+#include <vector>
+
+/*
+ * This file is based on example code from:
+ *
+ * http://www.voidcn.com/article/p-vjnlygmc-gy.html
+ *
+ * The example code was distributed under the following license:
+ *
+ * Copyright 2007 Edd Dawson.
+ * Distributed under the Boost Software License, Version 1.0.
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+template <typename CharT, typename Traits = std::char_traits<CharT>>
+class cmBasicUVStreambuf : public std::basic_streambuf<CharT, Traits>
+{
+public:
+  cmBasicUVStreambuf(std::size_t bufSize = 256, std::size_t putBack = 8);
+  ~cmBasicUVStreambuf() override;
+
+  bool is_open() const;
+
+  cmBasicUVStreambuf* open(uv_stream_t* stream);
+
+  cmBasicUVStreambuf* close();
+
+protected:
+  typename cmBasicUVStreambuf::int_type underflow() override;
+  std::streamsize showmanyc() override;
+
+  // FIXME: Add write support
+
+private:
+  uv_stream_t* Stream = nullptr;
+  void* OldStreamData;
+  const std::size_t PutBack;
+  std::vector<CharT> InputBuffer;
+  bool EndOfFile;
+
+  void StreamReadStartStop();
+
+  void StreamRead(ssize_t nread);
+  void HandleAlloc(uv_buf_t* buf);
+};
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>::cmBasicUVStreambuf(std::size_t bufSize,
+                                                      std::size_t putBack)
+  : PutBack(std::max<std::size_t>(putBack, 1))
+  , InputBuffer(std::max<std::size_t>(this->PutBack, bufSize) + this->PutBack)
+{
+  this->close();
+}
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>::~cmBasicUVStreambuf()
+{
+  this->close();
+}
+
+template <typename CharT, typename Traits>
+bool cmBasicUVStreambuf<CharT, Traits>::is_open() const
+{
+  return this->Stream != nullptr;
+}
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>* cmBasicUVStreambuf<CharT, Traits>::open(
+  uv_stream_t* stream)
+{
+  this->close();
+  this->Stream = stream;
+  this->EndOfFile = false;
+  if (this->Stream) {
+    this->OldStreamData = this->Stream->data;
+    this->Stream->data = this;
+  }
+  this->StreamReadStartStop();
+  return this;
+}
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>* cmBasicUVStreambuf<CharT, Traits>::close()
+{
+  if (this->Stream) {
+    uv_read_stop(this->Stream);
+    this->Stream->data = this->OldStreamData;
+  }
+  this->Stream = nullptr;
+  CharT* readEnd = this->InputBuffer.data() + this->InputBuffer.size();
+  this->setg(readEnd, readEnd, readEnd);
+  return this;
+}
+
+template <typename CharT, typename Traits>
+typename cmBasicUVStreambuf<CharT, Traits>::int_type
+cmBasicUVStreambuf<CharT, Traits>::underflow()
+{
+  if (!this->is_open()) {
+    return Traits::eof();
+  }
+
+  if (this->gptr() < this->egptr()) {
+    return Traits::to_int_type(*this->gptr());
+  }
+
+  this->StreamReadStartStop();
+  while (this->in_avail() == 0) {
+    uv_run(this->Stream->loop, UV_RUN_ONCE);
+  }
+  if (this->in_avail() == -1) {
+    return Traits::eof();
+  }
+  return Traits::to_int_type(*this->gptr());
+}
+
+template <typename CharT, typename Traits>
+std::streamsize cmBasicUVStreambuf<CharT, Traits>::showmanyc()
+{
+  if (!this->is_open() || this->EndOfFile) {
+    return -1;
+  }
+  return 0;
+}
+
+template <typename CharT, typename Traits>
+void cmBasicUVStreambuf<CharT, Traits>::StreamReadStartStop()
+{
+  if (this->Stream) {
+    uv_read_stop(this->Stream);
+    if (this->gptr() >= this->egptr()) {
+      uv_read_start(
+        this->Stream,
+        [](uv_handle_t* handle, size_t /* unused */, uv_buf_t* buf) {
+          auto streambuf =
+            static_cast<cmBasicUVStreambuf<CharT, Traits>*>(handle->data);
+          streambuf->HandleAlloc(buf);
+        },
+        [](uv_stream_t* stream2, ssize_t nread, const uv_buf_t* /* unused */) {
+          auto streambuf =
+            static_cast<cmBasicUVStreambuf<CharT, Traits>*>(stream2->data);
+          streambuf->StreamRead(nread);
+        });
+    }
+  }
+}
+
+template <typename CharT, typename Traits>
+void cmBasicUVStreambuf<CharT, Traits>::HandleAlloc(uv_buf_t* buf)
+{
+  auto size = this->egptr() - this->gptr();
+  std::memmove(this->InputBuffer.data(), this->gptr(),
+               this->egptr() - this->gptr());
+  this->setg(this->InputBuffer.data(), this->InputBuffer.data(),
+             this->InputBuffer.data() + size);
+  buf->base = this->egptr();
+#ifdef _WIN32
+#  define BUF_LEN_TYPE ULONG
+#else
+#  define BUF_LEN_TYPE size_t
+#endif
+  buf->len = BUF_LEN_TYPE(
+    (this->InputBuffer.data() + this->InputBuffer.size() - this->egptr()) *
+    sizeof(CharT));
+#undef BUF_LEN_TYPE
+}
+
+template <typename CharT, typename Traits>
+void cmBasicUVStreambuf<CharT, Traits>::StreamRead(ssize_t nread)
+{
+  if (nread > 0) {
+    this->setg(this->eback(), this->gptr(),
+               this->egptr() + nread / sizeof(CharT));
+    uv_read_stop(this->Stream);
+  } else if (nread < 0 || nread == UV_EOF) {
+    this->EndOfFile = true;
+    uv_read_stop(this->Stream);
+  }
+}
+
+using cmUVStreambuf = cmBasicUVStreambuf<char>;
+
+#endif

+ 3 - 1
Tests/CMakeLib/CMakeLists.txt

@@ -16,9 +16,11 @@ set(CMakeLib_TESTS
   testXMLSafe.cxx
   testFindPackageCommand.cxx
   testUVRAII.cxx
+  testUVStreambuf.cxx
   )
 
 set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
+set(testUVStreambuf_ARGS $<TARGET_FILE:cmake>)
 
 if(WIN32)
   list(APPEND CMakeLib_TESTS
@@ -43,7 +45,7 @@ target_link_libraries(testEncoding cmsys)
 
 foreach(testfile ${CMakeLib_TESTS})
   get_filename_component(test "${testfile}" NAME_WE)
-  add_test(CMakeLib.${test} CMakeLibTests ${test} ${${test}_ARGS})
+  add_test(NAME CMakeLib.${test} COMMAND CMakeLibTests ${test} ${${test}_ARGS})
 endforeach()
 
 if(TEST_CompileCommandOutput)

+ 49 - 1
Tests/CMakeLib/testUVRAII.cxx

@@ -171,11 +171,59 @@ static bool testAllMoves()
   return true;
 };
 
+static bool testLoopReset()
+{
+  bool closed = false;
+  cm::uv_loop_ptr loop;
+  loop.init();
+
+  uv_timer_t timer;
+  uv_timer_init(loop, &timer);
+  timer.data = &closed;
+  uv_close(reinterpret_cast<uv_handle_t*>(&timer), [](uv_handle_t* handle) {
+    auto closedPtr = static_cast<bool*>(handle->data);
+    *closedPtr = true;
+  });
+
+  loop.reset();
+  if (!closed) {
+    std::cerr << "uv_loop_ptr did not finish" << std::endl;
+    return false;
+  }
+
+  return true;
+};
+
+static bool testLoopDestructor()
+{
+  bool closed = false;
+
+  uv_timer_t timer;
+  {
+    cm::uv_loop_ptr loop;
+    loop.init();
+
+    uv_timer_init(loop, &timer);
+    timer.data = &closed;
+    uv_close(reinterpret_cast<uv_handle_t*>(&timer), [](uv_handle_t* handle) {
+      auto closedPtr = static_cast<bool*>(handle->data);
+      *closedPtr = true;
+    });
+  }
+
+  if (!closed) {
+    std::cerr << "uv_loop_ptr did not finish" << std::endl;
+    return false;
+  }
+
+  return true;
+};
+
 int testUVRAII(int, char** const)
 {
   if ((testAsyncShutdown() &&
        testAsyncDtor() & testAsyncMove() & testCrossAssignment() &
-         testAllMoves()) == 0) {
+         testAllMoves() & testLoopReset() & testLoopDestructor()) == 0) {
     return -1;
   }
   return 0;

+ 457 - 0
Tests/CMakeLib/testUVStreambuf.cxx

@@ -0,0 +1,457 @@
+#include "cmUVStreambuf.h"
+
+#include "cmGetPipes.h"
+#include "cmUVHandlePtr.h"
+
+#include "cm_uv.h"
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <cstring>
+
+#include <stdint.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;
+}