diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2012-01-17 19:03:17 -0500 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2012-01-17 19:03:17 -0500 |
commit | 74fbd31813494fe120211fbdad3ed6da9c2d5d8b (patch) | |
tree | 39d79e56791370f5037650d04cc58dd1efac2766 /indra/llcommon | |
parent | c0731c1c05cafe508c91c5f583301234ba3b8403 (diff) |
Add first couple of LLProcessLauncher tests.
Run INTEGRATION_TEST_llprocesslauncher using setpython.py so we can find the
Python interpreter of interest.
Introduce python() function to run a Python script specified using
NamedTempFile conventions.
Introduce a convention by which we can read output from a Python script using
only the limited pre-January-2012 LLProcessLauncher API. Introduce
python_out() function to leverage that convention.
Exercise a couple of LLProcessLauncher methods using all the above.
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/CMakeLists.txt | 3 | ||||
-rw-r--r-- | indra/llcommon/tests/llprocesslauncher_test.cpp | 146 |
2 files changed, 148 insertions, 1 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 334f78cbff..2c376bb016 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -328,7 +328,8 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}") LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llprocesslauncher "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llprocesslauncher "" "${test_libs}" + "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 3935c64a94..aebd280c2e 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -18,10 +18,14 @@ #include <vector> #include <list> // std headers +#include <fstream> // external library headers #include "llapr.h" #include "apr_thread_proc.h" #include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> // other Linden headers #include "../test/lltut.h" #include "../test/manageapr.h" @@ -36,6 +40,8 @@ #include <sys/wait.h> #endif +namespace lambda = boost::lambda; + // static instance of this manages APR init/cleanup static ManageAPR manager; @@ -56,6 +62,116 @@ namespace tut rv, expected); } + /** + * Run a Python script using LLProcessLauncher. + * @param desc Arbitrary description for error messages + * @param script Python script, any form acceptable to NamedTempFile, + * typically either a std::string or an expression of the form + * (lambda::_1 << "script content with " << variable_data) + * @param arg If specified, will be passed to script as its + * sys.argv[1] + * @param tweak "Do something" to LLProcessLauncher object before + * calling its launch() method. This program is to test + * LLProcessLauncher, but many such tests are "just like" this + * python() function but for one or two extra method calls before + * launch(). This avoids us having to clone & edit this function for + * such tests. + */ + template <typename CONTENT> + void python(const std::string& desc, const CONTENT& script, const std::string& arg="", + const boost::function<void (LLProcessLauncher&)> tweak=lambda::_1) + { + const char* PYTHON(getenv("PYTHON")); + ensure("Set $PYTHON to the Python interpreter", PYTHON); + + NamedTempFile scriptfile("py", script); + LLProcessLauncher py; + py.setExecutable(PYTHON); + py.addArgument(scriptfile.getName()); + if (! arg.empty()) + { + py.addArgument(arg); + } + tweak(py); + ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0); + // One of the irritating things about LLProcessLauncher is that + // there's no API to wait for the child to terminate -- but given + // its use in our graphics-intensive interactive viewer, it's + // understandable. + while (py.isRunning()) + { + sleep(1); + } + } + + /** + * Run a Python script using LLProcessLauncher, expecting that it will + * write to the file passed as its sys.argv[1]. Retrieve that output. + * + * Until January 2012, LLProcessLauncher provided distressingly few + * mechanisms for a child process to communicate back to its caller -- + * not even its return code. We've introduced a convention by which we + * create an empty temp file, pass the name of that file to our child + * as sys.argv[1] and expect the script to write its output to that + * file. This function implements the C++ (parent process) side of + * that convention. + * + * @param desc as for python() + * @param script as for python() + * @param tweak as for python() + */ + template <typename CONTENT> + std::string python_out(const std::string& desc, const CONTENT& script, + const boost::function<void (LLProcessLauncher&)> tweak=lambda::_1) + { + NamedTempFile out("out", ""); // placeholder + // pass name of this temporary file to the script + python(desc, script, out.getName(), tweak); + // assuming the script wrote a line to that file, read it + std::string output; + { + std::ifstream inf(out.getName().c_str()); + ensure(STRINGIZE("No output from " << desc << " script"), + std::getline(inf, output)); + std::string more; + while (std::getline(inf, more)) + { + output += '\n' + more; + } + } // important to close inf BEFORE removing NamedTempFile + return output; + } + + class NamedTempDir + { + public: + // Use python() function to create a temp directory: I've found + // nothing in either Boost.Filesystem or APR quite like Python's + // tempfile.mkdtemp(). + // Special extra bonus: on Mac, mkdtemp() reports a pathname + // starting with /var/folders/something, whereas that's really a + // symlink to /private/var/folders/something. Have to use + // realpath() to compare properly. + NamedTempDir(llprocesslauncher_data* ths): + mThis(ths), + mPath(ths->python_out("mkdtemp()", + "import os.path, sys, tempfile\n" + "with open(sys.argv[1], 'w') as f:\n" + " f.write(os.path.realpath(tempfile.mkdtemp()))\n")) + {} + + ~NamedTempDir() + { + mThis->aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); + } + + std::string getName() const { return mPath; } + + private: + llprocesslauncher_data* mThis; + std::string mPath; + }; + LLAPRPool pool; }; typedef test_group<llprocesslauncher_data> llprocesslauncher_group; @@ -349,4 +465,34 @@ namespace tut throw; } } + + template<> template<> + void object::test<2>() + { + set_test_name("set/getExecutable()"); + LLProcessLauncher child; + child.setExecutable("nonsense string"); + ensure_equals("setExecutable() 0", child.getExecutable(), "nonsense string"); + child.setExecutable("python"); + ensure_equals("setExecutable() 1", child.getExecutable(), "python"); + } + + template<> template<> + void object::test<3>() + { + set_test_name("setWorkingDirectory()"); + // We want to test setWorkingDirectory(). But what directory is + // guaranteed to exist on every machine, under every OS? Have to + // create one. + NamedTempDir tempdir(this); + std::string cwd(python_out("getcwd()", + "import os, sys\n" + "with open(sys.argv[1], 'w') as f:\n" + " f.write(os.getcwd())\n", + // Before LLProcessLauncher::launch(), call setWorkingDirectory() + lambda::bind(&LLProcessLauncher::setWorkingDirectory, + lambda::_1, + tempdir.getName()))); + ensure_equals("os.getcwd()", cwd, tempdir.getName()); + } } // namespace tut |