summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rwxr-xr-xindra/llcommon/llapp.cpp4
-rwxr-xr-xindra/llcommon/llapr.cpp8
-rwxr-xr-xindra/llcommon/llavatarname.h2
-rwxr-xr-xindra/llcommon/llcoros.cpp4
-rwxr-xr-xindra/llcommon/llerror.cpp27
-rwxr-xr-xindra/llcommon/llerror.h13
-rwxr-xr-xindra/llcommon/llerrorlegacy.h8
-rwxr-xr-xindra/llcommon/llfasttimer.cpp46
-rwxr-xr-xindra/llcommon/llfile.cpp2
-rwxr-xr-xindra/llcommon/llfile.h4
-rwxr-xr-xindra/llcommon/llsdserialize_xml.cpp4
-rw-r--r--indra/llcommon/lltracerecording.cpp58
-rw-r--r--indra/llcommon/llunit.h17
-rwxr-xr-xindra/llcommon/tests/llprocess_test.cpp5
14 files changed, 119 insertions, 83 deletions
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index c6da205815..67a98d5fb8 100755
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -986,9 +986,9 @@ bool windows_post_minidump_callback(const wchar_t* dump_path,
}
llinfos << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << llendl;
- // *NOTE:Mani - this code is stolen from LLApp, where its never actually used.
+ // *NOTE:Mani - this code is stolen from LLApp, where its never actually used.
//OSMessageBox("Attach Debugger Now", "Error", OSMB_OK);
- // *TODO: Translate the signals/exceptions into cross-platform stuff
+ // *TODO: Translate the signals/exceptions into cross-platform stuff
// Windows implementation
llinfos << "Entering Windows Exception Handler..." << llendl;
diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp
index 47fa70614f..b6adb37eba 100755
--- a/indra/llcommon/llapr.cpp
+++ b/indra/llcommon/llapr.cpp
@@ -56,7 +56,7 @@ void ll_init_apr()
if(!LLAPRFile::sAPRFilePoolp)
{
- LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(FALSE);
+ LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(FALSE) ;
}
LLThreadLocalPointerBase::initAllThreadLocalStorage();
@@ -101,7 +101,7 @@ void ll_cleanup_apr()
}
if (LLAPRFile::sAPRFilePoolp)
{
- delete LLAPRFile::sAPRFilePoolp ;
+ delete LLAPRFile::sAPRFilePoolp ;
LLAPRFile::sAPRFilePoolp = NULL ;
}
apr_terminate();
@@ -243,9 +243,7 @@ void LLVolatileAPRPool::clearVolatileAPRPool()
llassert_always(mNumActiveRef > 0) ;
}
- //paranoia check if the pool is jammed.
- //will remove the check before going to release.
- llassert_always(mNumTotalRef < (FULL_VOLATILE_APR_POOL << 2)) ;
+ llassert(mNumTotalRef < (FULL_VOLATILE_APR_POOL << 2)) ;
}
BOOL LLVolatileAPRPool::isFull()
diff --git a/indra/llcommon/llavatarname.h b/indra/llcommon/llavatarname.h
index 7542a8dece..5d2fccc5ba 100755
--- a/indra/llcommon/llavatarname.h
+++ b/indra/llcommon/llavatarname.h
@@ -63,7 +63,7 @@ public:
// For normal names, returns "James Linden (james.linden)"
// When display names are disabled returns just "James Linden"
std::string getCompleteName() const;
-
+
// Returns "James Linden" or "bobsmith123 Resident" for backwards
// compatibility with systems like voice and muting
// *TODO: Eliminate this in favor of username only
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index a629f71d4b..baaddcaed1 100755
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -60,7 +60,7 @@ bool LLCoros::cleanup(const LLSD&)
// since last tick?
if (mi->second->exited())
{
- LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
+ LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
// The erase() call will invalidate its passed iterator value --
// so increment mi FIRST -- but pass its original value to
// erase(). This is what postincrement is all about.
@@ -94,7 +94,7 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const
{
if (mCoros.find(name) == mCoros.end())
{
- LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
+ LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
return name;
}
}
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index 9b0141eb76..d2af004cde 100755
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -201,10 +201,7 @@ namespace {
virtual void recordMessage(LLError::ELevel level,
const std::string& message)
{
- llutf16string utf16str =
- wstring_to_utf16str(utf8str_to_wstring(message));
- utf16str += '\n';
- OutputDebugString(utf16str.c_str());
+ LL_WINDOWS_OUTPUT_DEBUG(message);
}
};
#endif
@@ -1401,5 +1398,27 @@ namespace LLError
{
sIndex = 0 ;
}
+
+#if LL_WINDOWS
+ void LLOutputDebugUTF8(const std::string& s)
+ {
+ // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C
+ // which works just fine under the windows debugger, but can cause users who
+ // have enabled SEHOP exception chain validation to crash due to interactions
+ // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707
+ //
+ if (IsDebuggerPresent())
+ {
+ // Need UTF16 for Unicode OutputDebugString
+ //
+ if (s.size())
+ {
+ OutputDebugString(utf8str_to_utf16str(s).c_str());
+ OutputDebugString(TEXT("\n"));
+ }
+ }
+ }
+#endif
+
}
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index b65b410153..0b723aeb5d 100755
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -34,7 +34,6 @@
#include "llerrorlegacy.h"
#include "stdtypes.h"
-
/** Error Logging Facility
Information for most users:
@@ -199,8 +198,20 @@ namespace LLError
static void clear() ;
static void end(std::ostringstream* _out) ;
};
+
+#if LL_WINDOWS
+ void LLOutputDebugUTF8(const std::string& s);
+#endif
+
}
+#if LL_WINDOWS
+ // Macro accepting a std::string for display in windows debugging console
+ #define LL_WINDOWS_OUTPUT_DEBUG(a) LLError::LLOutputDebugUTF8(a)
+#else
+ #define LL_WINDOWS_OUTPUT_DEBUG(a)
+#endif
+
//this is cheaper than llcallstacks if no need to output other variables to call stacks.
#define llpushcallstacks LLError::LLCallStacks::push(__FUNCTION__, __LINE__)
#define llcallstacks \
diff --git a/indra/llcommon/llerrorlegacy.h b/indra/llcommon/llerrorlegacy.h
index 097a533b1a..50c95339e4 100755
--- a/indra/llcommon/llerrorlegacy.h
+++ b/indra/llcommon/llerrorlegacy.h
@@ -113,11 +113,11 @@ const int LL_ERR_PRICE_MISMATCH = -23018;
#endif
#ifdef LL_WINDOWS
-#define llstatic_assert(func, msg) static_assert(func, msg)
-#define llstatic_assert_template(type, func, msg) static_assert(func, msg)
+#define LL_STATIC_ASSERT(func, msg) static_assert(func, msg)
+#define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) static_assert(false, msg)
#else
-#define llstatic_assert(func, msg) BOOST_STATIC_ASSERT(func)
-#define llstatic_assert_template(type, func, msg) BOOST_STATIC_ASSERT(sizeof(type) != 0 && func);
+#define LL_STATIC_ASSERT(func, msg) BOOST_STATIC_ASSERT(func)
+#define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) BOOST_STATIC_ASSERT(sizeof(type) != 0 && false);
#endif
// handy compile-time assert - enforce those template parameters!
diff --git a/indra/llcommon/llfasttimer.cpp b/indra/llcommon/llfasttimer.cpp
index becfa9c288..60c451b137 100755
--- a/indra/llcommon/llfasttimer.cpp
+++ b/indra/llcommon/llfasttimer.cpp
@@ -130,9 +130,9 @@ void TimeBlock::pushLog(LLSD log)
}
void TimeBlock::setLogLock(LLMutex* lock)
-{
+ {
sLogLock = lock;
-}
+ }
//static
@@ -182,7 +182,7 @@ void TimeBlock::bootstrapTimerTree()
for (LLInstanceTracker<TimeBlock>::instance_iter begin_it = LLInstanceTracker<TimeBlock>::beginInstances(), end_it = LLInstanceTracker<TimeBlock>::endInstances(), it = begin_it;
it != end_it;
++it)
- {
+ {
TimeBlock& timer = *it;
if (&timer == &TimeBlock::getRootTimeBlock()) continue;
@@ -193,13 +193,13 @@ void TimeBlock::bootstrapTimerTree()
TimeBlockAccumulator* accumulator = timer.getPrimaryAccumulator();
if (accumulator->mLastCaller)
- {
+ {
timer.setParent(accumulator->mLastCaller);
accumulator->mParent = accumulator->mLastCaller;
- }
+ }
// no need to push up tree on first use, flag can be set spuriously
accumulator->mMoveUpTree = false;
- }
+}
}
}
@@ -217,37 +217,37 @@ void TimeBlock::incrementalUpdateTimerTree()
// sort timers by time last called, so call graph makes sense
TimeBlockTreeNode& tree_node = timerp->getTreeNode();
if (tree_node.mNeedsSorting)
- {
+ {
std::sort(tree_node.mChildren.begin(), tree_node.mChildren.end(), SortTimerByName());
- }
+ }
// skip root timer
if (timerp != &TimeBlock::getRootTimeBlock())
- {
+ {
TimeBlockAccumulator* accumulator = timerp->getPrimaryAccumulator();
if (accumulator->mMoveUpTree)
- {
+ {
// since ancestors have already been visited, re-parenting won't affect tree traversal
- //step up tree, bringing our descendants with us
- LL_DEBUGS("FastTimers") << "Moving " << timerp->getName() << " from child of " << timerp->getParent()->getName() <<
- " to child of " << timerp->getParent()->getParent()->getName() << LL_ENDL;
- timerp->setParent(timerp->getParent()->getParent());
+ //step up tree, bringing our descendants with us
+ LL_DEBUGS("FastTimers") << "Moving " << timerp->getName() << " from child of " << timerp->getParent()->getName() <<
+ " to child of " << timerp->getParent()->getParent()->getName() << LL_ENDL;
+ timerp->setParent(timerp->getParent()->getParent());
accumulator->mParent = timerp->getParent();
accumulator->mMoveUpTree = false;
- // don't bubble up any ancestors until descendants are done bubbling up
+ // don't bubble up any ancestors until descendants are done bubbling up
// as ancestors may call this timer only on certain paths, so we want to resolve
// child-most block locations before their parents
- it.skipAncestors();
- }
+ it.skipAncestors();
}
}
+ }
}
void TimeBlock::updateTimes()
-{
+ {
U64 cur_time = getCPUClockCount64();
// walk up stack of active timers and accumulate current time while leaving timing structures active
@@ -257,7 +257,7 @@ void TimeBlock::updateTimes()
while(cur_timer
&& cur_timer->mParentTimerData.mActiveTimer != cur_timer) // root defined by parent pointing to self
- {
+ {
U64 cumulative_time_delta = cur_time - cur_timer->mStartTime;
accumulator->mTotalTimeCounter += cumulative_time_delta
- (accumulator->mTotalTimeCounter
@@ -307,12 +307,12 @@ void TimeBlock::processTimes()
}
std::vector<TimeBlock*>::iterator TimeBlock::beginChildren()
-{
+ {
return getTreeNode().mChildren.begin();
-}
+ }
std::vector<TimeBlock*>::iterator TimeBlock::endChildren()
-{
+ {
return getTreeNode().mChildren.end();
}
@@ -372,7 +372,7 @@ void TimeBlock::logStats()
//static
void TimeBlock::dumpCurTimes()
-{
+ {
LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording();
LLTrace::Recording& last_frame_recording = frame_recording.getLastRecording();
diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp
index 40a57c2ae4..06bc931dea 100755
--- a/indra/llcommon/llfile.cpp
+++ b/indra/llcommon/llfile.cpp
@@ -848,7 +848,7 @@ llifstream::llifstream() : _M_filebuf(),
#endif
// explicit
-llifstream::llifstream(const std::string& _Filename,
+llifstream::llifstream(const std::string& _Filename,
ios_base::openmode _Mode) : _M_filebuf(),
#if LL_WINDOWS
std::istream(&_M_filebuf)
diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h
index 9d70db96ea..d59e68367e 100755
--- a/indra/llcommon/llfile.h
+++ b/indra/llcommon/llfile.h
@@ -35,7 +35,7 @@
* Attempts to mostly mirror the POSIX style IO functions.
*/
-typedef FILE LLFILE;
+typedef FILE LLFILE;
#include <fstream>
#include <sys/stat.h>
@@ -237,7 +237,7 @@ public:
ios_base::openmode _Mode = ios_base::in,
//size_t _Size = static_cast<size_t>(BUFSIZ));
size_t _Size = static_cast<size_t>(1));
-
+
/**
* @brief Create a stream using an open file descriptor.
* @param fd An open file descriptor.
diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp
index cef743a7be..614a2d5636 100755
--- a/indra/llcommon/llsdserialize_xml.cpp
+++ b/indra/llcommon/llsdserialize_xml.cpp
@@ -406,7 +406,7 @@ S32 LLSDXMLParser::Impl::parse(std::istream& input, LLSD& data)
}
if (mEmitErrors)
{
- llinfos << "LLSDXMLParser::Impl::parse: XML_STATUS_ERROR parsing:" << (char*) buffer << llendl;
+ llinfos << "LLSDXMLParser::Impl::parse: XML_STATUS_ERROR parsing:" << (char*) buffer << llendl;
}
data = LLSD();
return LLSDParser::PARSE_FAILURE;
@@ -487,7 +487,7 @@ S32 LLSDXMLParser::Impl::parseLines(std::istream& input, LLSD& data)
{
if (mEmitErrors)
{
- llinfos << "LLSDXMLParser::Impl::parseLines: XML_STATUS_ERROR" << llendl;
+ llinfos << "LLSDXMLParser::Impl::parseLines: XML_STATUS_ERROR" << llendl;
}
return LLSDParser::PARSE_FAILURE;
}
diff --git a/indra/llcommon/lltracerecording.cpp b/indra/llcommon/lltracerecording.cpp
index 52ecb463be..ff1589d12d 100644
--- a/indra/llcommon/lltracerecording.cpp
+++ b/indra/llcommon/lltracerecording.cpp
@@ -429,36 +429,49 @@ void PeriodicRecording::appendPeriodicRecording( PeriodicRecording& other )
getCurRecording().update();
other.getCurRecording().update();
+
+ const U32 other_recording_slots = other.mRecordingPeriods.size();
+ const U32 other_num_recordings = other.getNumRecordedPeriods();
+ const U32 other_current_recording_index = other.mCurPeriod;
+ const U32 other_oldest_recording_index = (other_current_recording_index + other_recording_slots - other_num_recordings + 1) % other_recording_slots;
+
+ // append first recording into our current slot
+ getCurRecording().appendRecording(other.mRecordingPeriods[other_oldest_recording_index]);
+
+ // from now on, add new recordings for everything after the first
+ U32 other_index = (other_oldest_recording_index + 1) % other_recording_slots;
if (mAutoResize)
{
- S32 other_index = (other.mCurPeriod + 1) % other.mRecordingPeriods.size();
- S32 end_index = (other.mCurPeriod) % other.mRecordingPeriods.size();
+ // append first recording into our current slot
+ getCurRecording().appendRecording(other.mRecordingPeriods[other_oldest_recording_index]);
- do
+ // push back recordings for everything in the middle
+ U32 other_index = (other_oldest_recording_index + 1) % other_recording_slots;
+ while (other_index != other_current_recording_index)
{
- if (other.mRecordingPeriods[other_index].getDuration().value())
- {
- mRecordingPeriods.push_back(other.mRecordingPeriods[other_index]);
- }
- other_index = (other_index + 1) % other.mRecordingPeriods.size();
+ mRecordingPeriods.push_back(other.mRecordingPeriods[other_index]);
+ other_index = (other_index + 1) % other_recording_slots;
+ }
+
+ // add final recording, if it wasn't already added as the first
+ if (other_num_recordings > 1)
+ {
+ mRecordingPeriods.push_back(other.mRecordingPeriods[other_current_recording_index]);
}
- while(other_index != end_index);
mCurPeriod = mRecordingPeriods.size() - 1;
mNumPeriods = mRecordingPeriods.size();
}
else
{
- //FIXME: get proper number of recordings from other...might not have used all its slots
- size_t num_to_copy = llmin( mRecordingPeriods.size(), other.getNumRecordedPeriods());
- std::vector<Recording>::iterator src_it = other.mRecordingPeriods.begin()
- + ( (other.mCurPeriod + 1 // oldest period
- + (other.mRecordingPeriods.size() - num_to_copy)) // minus room for copy
- % other.mRecordingPeriods.size());
+ size_t num_to_copy = llmin( mRecordingPeriods.size(), (size_t)other_num_recordings);
+
+ std::vector<Recording>::iterator src_it = other.mRecordingPeriods.begin() + other_index ;
std::vector<Recording>::iterator dest_it = mRecordingPeriods.begin() + mCurPeriod;
- for(size_t i = 0; i < num_to_copy; i++)
+ // already consumed the first recording from other, so start counting at 1
+ for(size_t i = 1; i < num_to_copy; i++)
{
*dest_it = *src_it;
@@ -475,17 +488,14 @@ void PeriodicRecording::appendPeriodicRecording( PeriodicRecording& other )
// want argument to % to be positive, otherwise result could be negative and thus out of bounds
llassert(num_to_copy >= 1);
- // advance to last recording period copied, so we can check if the last period had actually carried any data, in which case we'll advance below
- // using nextPeriod() which retains continuity (mLastValue, etc)
+ // advance to last recording period copied, and make that our current period
mCurPeriod = (mCurPeriod + num_to_copy - 1) % mRecordingPeriods.size();
- mNumPeriods = llmin(mRecordingPeriods.size(), mNumPeriods + num_to_copy);
+ mNumPeriods = llmin(mRecordingPeriods.size(), mNumPeriods + num_to_copy - 1);
}
- if (getCurRecording().getDuration().value())
- {
- //call this to chain last period copied to new active period
- nextPeriod();
- }
+ // end with fresh period, otherwise next appendPeriodicRecording() will merge the first
+ // recording period with the last one appended here
+ nextPeriod();
getCurRecording().setPlayState(getPlayState());
}
diff --git a/indra/llcommon/llunit.h b/indra/llcommon/llunit.h
index 5229fe69d7..2402cdbb95 100644
--- a/indra/llcommon/llunit.h
+++ b/indra/llcommon/llunit.h
@@ -117,7 +117,7 @@ struct LLUnit
void operator *= (LLUnit<OTHER_STORAGE, OTHER_UNIT> multiplicand)
{
// spurious use of dependent type to stop gcc from triggering the static assertion before instantiating the template
- llstatic_assert_template(OTHER_UNIT, false, "Multiplication of unit types not supported.");
+ LL_BAD_TEMPLATE_INSTANTIATION(OTHER_UNIT, "Multiplication of unit types not supported.");
}
void operator /= (storage_t divisor)
@@ -129,7 +129,7 @@ struct LLUnit
void operator /= (LLUnit<OTHER_STORAGE, OTHER_UNIT> divisor)
{
// spurious use of dependent type to stop gcc from triggering the static assertion before instantiating the template
- llstatic_assert_template(OTHER_UNIT, false, "Illegal in-place division of unit types.");
+ LL_BAD_TEMPLATE_INSTANTIATION(OTHER_UNIT, "Illegal in-place division of unit types.");
}
template<typename SOURCE_STORAGE, typename SOURCE_UNITS>
@@ -172,10 +172,11 @@ struct LLUnitImplicit : public LLUnit<STORAGE_TYPE, UNIT_TYPE>
template<typename S1, typename T1, typename S2, typename T2>
LL_FORCE_INLINE void ll_convert_units(LLUnit<S1, T1> in, LLUnit<S2, T2>& out, ...)
{
- static_assert(boost::is_same<T1, T2>::value
- || !boost::is_same<T1, typename T1::base_unit_t>::value
- || !boost::is_same<T2, typename T2::base_unit_t>::value,
- "invalid conversion");
+ typedef boost::integral_constant<bool,
+ boost::is_same<T1, T2>::value
+ || !boost::is_same<T1, typename T1::base_unit_t>::value
+ || !boost::is_same<T2, typename T2::base_unit_t>::value> conversion_valid_t;
+ LL_STATIC_ASSERT(conversion_valid_t::value, "invalid conversion");
if (boost::is_same<T1, typename T1::base_unit_t>::value)
{
@@ -322,7 +323,7 @@ template<typename STORAGE_TYPE1, typename UNIT_TYPE1, typename STORAGE_TYPE2, ty
LLUnit<STORAGE_TYPE1, UNIT_TYPE1> operator * (LLUnit<STORAGE_TYPE1, UNIT_TYPE1>, LLUnit<STORAGE_TYPE2, UNIT_TYPE2>)
{
// spurious use of dependent type to stop gcc from triggering the static assertion before instantiating the template
- llstatic_assert_template(STORAGE_TYPE1, false, "Multiplication of unit types results in new unit type - not supported.");
+ LL_BAD_TEMPLATE_INSTANTIATION(STORAGE_TYPE1, "Multiplication of unit types results in new unit type - not supported.");
return LLUnit<STORAGE_TYPE1, UNIT_TYPE1>();
}
@@ -342,7 +343,7 @@ template<typename STORAGE_TYPE1, typename UNIT_TYPE1, typename STORAGE_TYPE2, ty
LLUnitImplicit<STORAGE_TYPE1, UNIT_TYPE1> operator * (LLUnitImplicit<STORAGE_TYPE1, UNIT_TYPE1>, LLUnitImplicit<STORAGE_TYPE2, UNIT_TYPE2>)
{
// spurious use of dependent type to stop gcc from triggering the static assertion before instantiating the template
- llstatic_assert_template(STORAGE_TYPE1, false, "Multiplication of unit types results in new unit type - not supported.");
+ LL_BAD_TEMPLATE_INSTANTIATION(STORAGE_TYPE1, "Multiplication of unit types results in new unit type - not supported.");
return LLUnitImplicit<STORAGE_TYPE1, UNIT_TYPE1>();
}
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 6f1e7d46b8..f188865eb0 100755
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -969,10 +969,7 @@ namespace tut
childout.getline(), "ok");
// important to get the implicit flush from std::endl
py.mPy->getWritePipe().get_ostream() << "go" << std::endl;
- for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i)
- {
- yield();
- }
+ waitfor(*py.mPy);
ensure("script never replied", childout.contains("\n"));
ensure_equals("child didn't ack", childout.getline(), "ack");
ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED);