From 56d931216e67a3e59199669bba022c65a9617bb5 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Wed, 15 Feb 2012 15:47:03 -0500
Subject: [PATCH] Add LLProcess::ReadPipe::size(), peek(), contains(). Also add
 "len" key to event data on LLProcess::getPump(). If you've used setLimit(),
 event["data"].length() may not reflect the length of the accumulated data in
 the ReadPipe. Add unit test with stdin/stdout handshake with child process.

---
 indra/llcommon/llprocess.cpp            | 31 ++++++++++++++----
 indra/llcommon/llprocess.h              | 25 ++++++++++++++
 indra/llcommon/tests/llprocess_test.cpp | 43 +++++++++++++++++++++++--
 3 files changed, 91 insertions(+), 8 deletions(-)

diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index d7c297b9523..1481bf571fa 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -197,6 +197,25 @@ class ReadPipeImpl: public LLProcess::ReadPipe
 	virtual LLEventPump& getPump() { return mPump; }
 	virtual void setLimit(size_t limit) { mLimit = limit; }
 	virtual size_t getLimit() const { return mLimit; }
+    virtual std::size_t size() { return mStreambuf.size(); }
+
+	virtual std::string peek(std::size_t offset=0,
+							 std::size_t len=(std::numeric_limits<std::size_t>::max)())
+	{
+		// Constrain caller's offset and len to overlap actual buffer content.
+		std::size_t real_offset = (std::min)(mStreambuf.size(), offset);
+		std::size_t real_end	= (std::min)(mStreambuf.size(), real_offset + len);
+		boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
+		return std::string(boost::asio::buffers_begin(cbufs) + real_offset,
+						   boost::asio::buffers_begin(cbufs) + real_end);
+	}
+
+	virtual bool contains(const std::string& seek, std::size_t offset=0)
+	{
+		// There may be a more efficient way to search mStreambuf contents,
+		// but this is far the easiest...
+		return peek(offset).find(seek) != std::string::npos;
+	}
 
 private:
 	bool tick(const LLSD&)
@@ -240,12 +259,13 @@ class ReadPipeImpl: public LLProcess::ReadPipe
 									   << mStreambuf.size() << LL_ENDL;
 
 				// Now that we've received new data, publish it on our
-				// LLEventPump as advertised. Constrain it by mLimit.
+				// LLEventPump as advertised. Constrain it by mLimit. But show
+				// listener the actual accumulated buffer size, regardless of
+				// mLimit.
 				std::size_t datasize((std::min)(mLimit, mStreambuf.size()));
-				boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
-				mPump.post(LLSDMap("data", LLSD::String(
-									   boost::asio::buffers_begin(cbufs),
-									   boost::asio::buffers_begin(cbufs) + datasize)));
+				mPump.post(LLSDMap
+						   ("data", peek(0, datasize))
+						   ("len", LLSD::Integer(mStreambuf.size())));
 			}
 		}
 		return false;
@@ -985,5 +1005,4 @@ void LLProcess::reap(void)
 	}
 }
 |*==========================================================================*/
-
 #endif  // Posix
diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h
index 448a88f4c02..bf0517600d7 100644
--- a/indra/llcommon/llprocess.h
+++ b/indra/llcommon/llprocess.h
@@ -330,6 +330,31 @@ class LL_COMMON_API LLProcess: public boost::noncopyable
 		 */
 		virtual std::istream& get_istream() = 0;
 
+		/**
+		 * Get accumulated buffer length.
+		 * Often we need to refrain from actually reading the std::istream
+		 * returned by get_istream() until we've accumulated enough data to
+		 * make it worthwhile. For instance, if we're expecting a number from
+		 * the child, but the child happens to flush "12" before emitting
+		 * "3\n", get_istream() >> myint could return 12 rather than 123!
+		 */
+		virtual std::size_t size() = 0;
+
+		/**
+		 * Peek at accumulated buffer data without consuming it. Optional
+		 * parameters give you substr() functionality.
+		 *
+		 * @note You can discard buffer data using get_istream().ignore(n).
+		 */
+		virtual std::string peek(std::size_t offset=0,
+								 std::size_t len=(std::numeric_limits<std::size_t>::max)()) = 0;
+
+		/**
+		 * Search accumulated buffer data without retrieving it. Optional
+		 * offset allows you to start at specified position.
+		 */
+		virtual bool contains(const std::string& seek, std::size_t offset=0) = 0;
+
 		/**
 		 * Get LLEventPump& on which to listen for incoming data. The posted
 		 * LLSD::Map event will contain a key "data" whose value is an
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index a901c577d68..2db17cae975 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -919,6 +919,7 @@ namespace tut
                         message, "somename");
     }
 
+    /*-------------- support for "get*Pipe() validation" test --------------*/
 #define TEST_getPipe(PROCESS, GETPIPE, GETOPTPIPE, VALID, NOPIPE, BADPIPE) \
     do                                                                  \
     {                                                                   \
@@ -985,11 +986,49 @@ namespace tut
                      LLProcess::STDIN);  // BADPIPE
     }
 
+    template<> template<>
+    void object::test<16>()
+    {
+        set_test_name("talk to stdin/stdout");
+        PythonProcessLauncher py("stdin/stdout",
+                                 "import sys, time\n"
+                                 "print 'ok'\n"
+                                 "sys.stdout.flush()\n"
+                                 "# wait for 'go' from test program\n"
+                                 "go = sys.stdin.readline()\n"
+                                 "if go != 'go\\n':\n"
+                                 "    sys.exit('expected \"go\", saw %r' % go)\n"
+                                 "print 'ack'\n");
+        py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin
+        py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout
+        py.mPy = LLProcess::create(py.mParams);
+        ensure("couldn't launch stdin/stdout script", py.mPy);
+        LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT));
+        int i, timeout = 60;
+        for (i = 0; i < timeout && py.mPy->isRunning() && childout.size() < 3; ++i)
+        {
+            yield();
+        }
+        ensure("script never started", i < timeout);
+        std::string line;
+        std::getline(childout.get_istream(), line);
+        ensure_equals("bad wakeup from stdin/stdout script", line, "ok");
+        py.mPy->getWritePipe().get_ostream() << "go" << std::endl;
+        for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i)
+        {
+            yield();
+        }
+        ensure("script never replied", childout.contains("\n"));
+        std::getline(childout.get_istream(), line);
+        ensure_equals("child didn't ack", line, "ack");
+        ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED);
+        ensure_equals("bad child exit code",   py.mPy->getStatus().mData,  0);
+    }
+
     // TODO:
-    // test pipe for stdin, stdout (etc.)
-    // test getWritePipe().get_ostream(), getReadPipe().get_istream()
     // test listening on getReadPipe().getPump(), disconnecting
     // test setLimit(), getLimit()
     // test EOF -- check logging
+    // test peek() with substr
 
 } // namespace tut
-- 
GitLab