summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2012-01-17 19:03:17 -0500
committerNat Goodspeed <nat@lindenlab.com>2012-01-17 19:03:17 -0500
commit74fbd31813494fe120211fbdad3ed6da9c2d5d8b (patch)
tree39d79e56791370f5037650d04cc58dd1efac2766 /indra/llcommon
parentc0731c1c05cafe508c91c5f583301234ba3b8403 (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.txt3
-rw-r--r--indra/llcommon/tests/llprocesslauncher_test.cpp146
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