diff options
Diffstat (limited to 'indra/llcommon/tests')
| -rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 171 | 
1 files changed, 163 insertions, 8 deletions
| diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 8c21be196b..d4e9977e63 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -34,6 +34,7 @@  #include "stringize.h"  #include "llsdutil.h"  #include "llevents.h" +#include "llerrorcontrol.h"  #if defined(LL_WINDOWS)  #define sleep(secs) _sleep((secs) * 1000) @@ -92,12 +93,18 @@ static std::string readfile(const std::string& pathname, const std::string& desc  /// Looping on LLProcess::isRunning() must now be accompanied by pumping  /// "mainloop" -- otherwise the status won't update and you get an infinite  /// loop. +void yield(int seconds=1) +{ +    // This function simulates waiting for another viewer frame +    sleep(seconds); +    LLEventPumps::instance().obtain("mainloop").post(LLSD()); +} +  void waitfor(LLProcess& proc)  {      while (proc.isRunning())      { -        sleep(1); -        LLEventPumps::instance().obtain("mainloop").post(LLSD()); +        yield();      }  } @@ -105,8 +112,7 @@ void waitfor(LLProcess::handle h, const std::string& desc)  {      while (LLProcess::isRunning(h, desc))      { -        sleep(1); -        LLEventPumps::instance().obtain("mainloop").post(LLSD()); +        yield();      }  } @@ -219,6 +225,68 @@ private:      std::string mPath;  }; +// statically reference the function in test.cpp... it's short, we could +// replicate, but better to reuse +extern void wouldHaveCrashed(const std::string& message); + +/** + * Capture log messages. This is adapted (simplified) from the one in + * llerror_test.cpp. Sigh, should've broken that out into a separate header + * file, but time for this project is short... + */ +class TestRecorder : public LLError::Recorder +{ +public: +    TestRecorder(): +        // Mostly what we're trying to accomplish by saving and resetting +        // LLError::Settings is to bypass the default RecordToStderr and +        // RecordToWinDebug Recorders. As these are visible only inside +        // llerror.cpp, we can't just call LLError::removeRecorder() with +        // each. For certain tests we need to produce, capture and examine +        // DEBUG log messages -- but we don't want to spam the user's console +        // with that output. If it turns out that saveAndResetSettings() has +        // some bad effect, give up and just let the DEBUG level log messages +        // display. +        mOldSettings(LLError::saveAndResetSettings()) +    { +        LLError::setFatalFunction(wouldHaveCrashed); +        LLError::setDefaultLevel(LLError::LEVEL_DEBUG); +        LLError::addRecorder(this); +    } + +    ~TestRecorder() +    { +        LLError::removeRecorder(this); +        LLError::restoreSettings(mOldSettings); +    } + +    void recordMessage(LLError::ELevel level, +                       const std::string& message) +    { +        mMessages.push_back(message); +    } + +    /// Don't assume the message we want is necessarily the LAST log message +    /// emitted by the underlying code; search backwards through all messages +    /// for the sought string. +    std::string messageWith(const std::string& search) +    { +        for (std::list<std::string>::const_reverse_iterator rmi(mMessages.rbegin()), +                 rmend(mMessages.rend()); +             rmi != rmend; ++rmi) +        { +            if (rmi->find(search) != std::string::npos) +                return *rmi; +        } +        // failed to find any such message +        return std::string(); +    } + +    typedef std::list<std::string> MessageList; +    MessageList mMessages; +    LLError::Settings* mOldSettings; +}; +  /*****************************************************************************  *   TUT  *****************************************************************************/ @@ -602,9 +670,19 @@ namespace tut          set_test_name("syntax_error:");          PythonProcessLauncher py("syntax_error:",                                   "syntax_error:\n"); +        py.mParams.files.add(LLProcess::FileParam()); // inherit stdin +        py.mParams.files.add(LLProcess::FileParam()); // inherit stdout +        py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr          py.run();          ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED);          ensure_equals("Status.mData",  py.mPy->getStatus().mData,  1); +        std::istream& rpipe(py.mPy->getReadPipe(LLProcess::STDERR).get_istream()); +        std::vector<char> buffer(4096); +        rpipe.read(&buffer[0], buffer.size()); +        std::streamsize got(rpipe.gcount()); +        ensure("Nothing read from stderr pipe", got); +        std::string data(&buffer[0], got); +        ensure("Didn't find 'SyntaxError:'", data.find("\nSyntaxError:") != std::string::npos);      }      template<> template<> @@ -629,7 +707,7 @@ namespace tut          int i = 0, timeout = 60;          for ( ; i < timeout; ++i)          { -            sleep(1); +            yield();              if (readfile(out.getName(), "from kill() script") == "ok")                  break;          } @@ -678,7 +756,7 @@ namespace tut              int i = 0, timeout = 60;              for ( ; i < timeout; ++i)              { -                sleep(1); +                yield();                  if (readfile(out.getName(), "from kill() script") == "ok")                      break;              } @@ -733,7 +811,7 @@ namespace tut              int i = 0, timeout = 60;              for ( ; i < timeout; ++i)              { -                sleep(1); +                yield();                  if (readfile(from.getName(), "from autokill script") == "ok")                      break;              } @@ -742,7 +820,7 @@ namespace tut              // Now destroy the LLProcess, which should NOT kill the child!          }          // If the destructor killed the child anyway, give it time to die -        sleep(2); +        yield(2);          // How do we know it's not terminated? By making it respond to          // a specific stimulus in a specific way.          { @@ -755,4 +833,81 @@ namespace tut          // script could not have written 'ack' as we expect.          ensure_equals("autokill script output", readfile(from.getName()), "ack");      } + +    template<> template<> +    void object::test<10>() +    { +        set_test_name("'bogus' test"); +        TestRecorder recorder; +        PythonProcessLauncher py("'bogus' test", +                                 "print 'Hello world'\n"); +        py.mParams.files.add(LLProcess::FileParam("bogus")); +        py.mPy = LLProcess::create(py.mParams); +        ensure("should have rejected 'bogus'", ! py.mPy); +        std::string message(recorder.messageWith("bogus")); +        ensure("did not log 'bogus' type", ! message.empty()); +        ensure_contains("did not name 'stdin'", message, "stdin"); +    } + +    template<> template<> +    void object::test<11>() +    { +        set_test_name("'file' test"); +        // Replace this test with one or more real 'file' tests when we +        // implement 'file' support +        PythonProcessLauncher py("'file' test", +                                 "print 'Hello world'\n"); +        py.mParams.files.add(LLProcess::FileParam()); +        py.mParams.files.add(LLProcess::FileParam("file")); +        py.mPy = LLProcess::create(py.mParams); +        ensure("should have rejected 'file'", ! py.mPy); +    } + +    template<> template<> +    void object::test<12>() +    { +        set_test_name("'tpipe' test"); +        // Replace this test with one or more real 'tpipe' tests when we +        // implement 'tpipe' support +        TestRecorder recorder; +        PythonProcessLauncher py("'tpipe' test", +                                 "print 'Hello world'\n"); +        py.mParams.files.add(LLProcess::FileParam()); +        py.mParams.files.add(LLProcess::FileParam("tpipe")); +        py.mPy = LLProcess::create(py.mParams); +        ensure("should have rejected 'tpipe'", ! py.mPy); +        std::string message(recorder.messageWith("tpipe")); +        ensure("did not log 'tpipe' type", ! message.empty()); +        ensure_contains("did not name 'stdout'", message, "stdout"); +    } + +    template<> template<> +    void object::test<13>() +    { +        set_test_name("'npipe' test"); +        // Replace this test with one or more real 'npipe' tests when we +        // implement 'npipe' support +        TestRecorder recorder; +        PythonProcessLauncher py("'npipe' test", +                                 "print 'Hello world'\n"); +        py.mParams.files.add(LLProcess::FileParam()); +        py.mParams.files.add(LLProcess::FileParam()); +        py.mParams.files.add(LLProcess::FileParam("npipe")); +        py.mPy = LLProcess::create(py.mParams); +        ensure("should have rejected 'npipe'", ! py.mPy); +        std::string message(recorder.messageWith("npipe")); +        ensure("did not log 'npipe' type", ! message.empty()); +        ensure_contains("did not name 'stderr'", message, "stderr"); +    } + +    // TODO: +    // test "pipe" with nonempty name (should log & continue) +    // test pipe for just stderr (tests for get[Opt]ReadPipe(), get[Opt]WritePipe()) +    // 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 get(Read|Write)Pipe(3), unmonitored slot, getReadPipe(1), getWritePipe(0) +  } // namespace tut | 
