diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2012-02-15 15:47:03 -0500 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2012-02-15 15:47:03 -0500 |
commit | 56d931216e67a3e59199669bba022c65a9617bb5 (patch) | |
tree | e5efffdd49b3d94b69ec27c38c597b5d05d3edbc | |
parent | 9b02f483ffe6f313ec86af3f29fa858fa0cb22e4 (diff) |
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.
-rw-r--r-- | indra/llcommon/llprocess.cpp | 31 | ||||
-rw-r--r-- | indra/llcommon/llprocess.h | 25 | ||||
-rw-r--r-- | 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 d7c297b952..1481bf571f 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -197,6 +197,25 @@ public: 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 @@ private: << 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 448a88f4c0..bf0517600d 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -331,6 +331,31 @@ public: 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 * LLSD::String containing (part of) the data accumulated in the diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index a901c577d6..2db17cae97 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 |