summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2012-02-15 15:47:03 -0500
committerNat Goodspeed <nat@lindenlab.com>2012-02-15 15:47:03 -0500
commit56d931216e67a3e59199669bba022c65a9617bb5 (patch)
treee5efffdd49b3d94b69ec27c38c597b5d05d3edbc
parent9b02f483ffe6f313ec86af3f29fa858fa0cb22e4 (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.cpp31
-rw-r--r--indra/llcommon/llprocess.h25
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp43
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