Skip to content
Snippets Groups Projects
Commit 2ae9f921 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

Refactor llprocesslauncher_test.cpp for better code reuse.

Instead of free python() and python_out() functions containing a local
temporary LLProcessLauncher instance, with a 'tweak' callback param to
"do stuff" to that inaccessible object, change to a PythonProcessLauncher
class that sets up a (public) LLProcessLauncher member, then allows you to
run() or run() and then readfile() the output. Now you can construct an
instance and tweak to your heart's content -- without funky callback syntax --
before running the script.
Move all such helpers from TUT fixture struct to namespace scope. While
fixture-struct methods can freely call one another, introducing a nested class
gets awkward: constructor must explicitly require and bind a fixture-struct
pointer or reference. Namespace scope solves this.
(Truthfully, I only put them in the fixture struct originally because I
thought it necessary for calling ensure() et al. But ensure() and friends are
free functions; need only qualify them with tut:: namespace.)
parent 51b26cab
No related branches found
No related tags found
No related merge requests found
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
// Precompiled header // Precompiled header
#include "linden_common.h" #include "linden_common.h"
// associated header // associated header
#define WIN32_LEAN_AND_MEAN
#include "llprocesslauncher.h" #include "llprocesslauncher.h"
// STL headers // STL headers
#include <vector> #include <vector>
...@@ -24,8 +23,8 @@ ...@@ -24,8 +23,8 @@
#include "apr_thread_proc.h" #include "apr_thread_proc.h"
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/function.hpp> #include <boost/function.hpp>
#include <boost/lambda/lambda.hpp> //#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp> //#include <boost/lambda/bind.hpp>
// other Linden headers // other Linden headers
#include "../test/lltut.h" #include "../test/lltut.h"
#include "../test/manageapr.h" #include "../test/manageapr.h"
...@@ -40,138 +39,169 @@ ...@@ -40,138 +39,169 @@
#include <sys/wait.h> #include <sys/wait.h>
#endif #endif
namespace lambda = boost::lambda; //namespace lambda = boost::lambda;
// static instance of this manages APR init/cleanup // static instance of this manages APR init/cleanup
static ManageAPR manager; static ManageAPR manager;
/*****************************************************************************
* Helpers
*****************************************************************************/
#define ensure_equals_(left, right) \ #define ensure_equals_(left, right) \
ensure_equals(STRINGIZE(#left << " != " << #right), (left), (right)) ensure_equals(STRINGIZE(#left << " != " << #right), (left), (right))
#define aprchk(expr) aprchk_(#expr, (expr)) #define aprchk(expr) aprchk_(#expr, (expr))
static void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS)
{
tut::ensure_equals(STRINGIZE(call << " => " << rv << ": " << manager.strerror(rv)),
rv, expected);
}
/***************************************************************************** /**
* TUT * Read specified file using std::getline(). It is assumed to be an error if
*****************************************************************************/ * the file is empty: don't use this function if that's an acceptable case.
namespace tut * Last line will not end with '\n'; this is to facilitate the usual case of
* string compares with a single line of output.
* @param pathname The file to read.
* @param desc Optional description of the file for error message;
* defaults to "in <pathname>"
*/
static std::string readfile(const std::string& pathname, const std::string& desc="")
{ {
struct llprocesslauncher_data std::string use_desc(desc);
if (use_desc.empty())
{ {
void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) use_desc = STRINGIZE("in " << pathname);
{ }
ensure_equals(STRINGIZE(call << " => " << rv << ": " << manager.strerror(rv)), std::ifstream inf(pathname.c_str());
rv, expected); std::string output;
} tut::ensure(STRINGIZE("No output " << use_desc), std::getline(inf, output));
std::string more;
while (std::getline(inf, more))
{
output += '\n' + more;
}
return output;
}
/** /**
* Run a Python script using LLProcessLauncher. * Construct an LLProcessLauncher to run a Python script.
* @param desc Arbitrary description for error messages */
* @param script Python script, any form acceptable to NamedTempFile, struct PythonProcessLauncher
* 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 * @param desc Arbitrary description for error messages
* sys.argv[1] * @param script Python script, any form acceptable to NamedTempFile,
* @param tweak "Do something" to LLProcessLauncher object before * typically either a std::string or an expression of the form
* calling its launch() method. This program is to test * (lambda::_1 << "script content with " << variable_data)
* LLProcessLauncher, but many such tests are "just like" this */
* python() function but for one or two extra method calls before template <typename CONTENT>
* launch(). This avoids us having to clone & edit this function for PythonProcessLauncher(const std::string& desc, const CONTENT& script):
* such tests. mDesc(desc),
*/ mScript("py", script)
template <typename CONTENT> {
void python(const std::string& desc, const CONTENT& script, const std::string& arg="", const char* PYTHON(getenv("PYTHON"));
const boost::function<void (LLProcessLauncher&)> tweak=lambda::_1) tut::ensure("Set $PYTHON to the Python interpreter", PYTHON);
{
const char* PYTHON(getenv("PYTHON")); mPy.setExecutable(PYTHON);
ensure("Set $PYTHON to the Python interpreter", PYTHON); mPy.addArgument(mScript.getName());
}
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 Python script and wait for it to complete.
* Run a Python script using LLProcessLauncher, expecting that it will void run()
* write to the file passed as its sys.argv[1]. Retrieve that output. {
* tut::ensure_equals(STRINGIZE("Couldn't launch " << mDesc << " script"),
* Until January 2012, LLProcessLauncher provided distressingly few mPy.launch(), 0);
* mechanisms for a child process to communicate back to its caller -- // One of the irritating things about LLProcessLauncher is that
* not even its return code. We've introduced a convention by which we // there's no API to wait for the child to terminate -- but given
* create an empty temp file, pass the name of that file to our child // its use in our graphics-intensive interactive viewer, it's
* as sys.argv[1] and expect the script to write its output to that // understandable.
* file. This function implements the C++ (parent process) side of while (mPy.isRunning())
* 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 sleep(1);
// 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 /**
{ * Run a Python script using LLProcessLauncher, expecting that it will
public: * write to the file passed as its sys.argv[1]. Retrieve that output.
// Use python() function to create a temp directory: I've found *
// nothing in either Boost.Filesystem or APR quite like Python's * Until January 2012, LLProcessLauncher provided distressingly few
// tempfile.mkdtemp(). * mechanisms for a child process to communicate back to its caller --
// Special extra bonus: on Mac, mkdtemp() reports a pathname * not even its return code. We've introduced a convention by which we
// starting with /var/folders/something, whereas that's really a * create an empty temp file, pass the name of that file to our child
// symlink to /private/var/folders/something. Have to use * as sys.argv[1] and expect the script to write its output to that
// realpath() to compare properly. * file. This function implements the C++ (parent process) side of
NamedTempDir(llprocesslauncher_data* ths): * that convention.
mThis(ths), */
mPath(ths->python_out("mkdtemp()", std::string run_read()
"import os.path, sys, tempfile\n" {
"with open(sys.argv[1], 'w') as f:\n" NamedTempFile out("out", ""); // placeholder
" f.write(os.path.realpath(tempfile.mkdtemp()))\n")) // pass name of this temporary file to the script
{} mPy.addArgument(out.getName());
run();
~NamedTempDir() // assuming the script wrote to that file, read it
{ return readfile(out.getName(), STRINGIZE("from " << mDesc << " script"));
mThis->aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); }
}
LLProcessLauncher mPy;
std::string mDesc;
NamedTempFile mScript;
};
/// convenience function for PythonProcessLauncher::run()
template <typename CONTENT>
static void python(const std::string& desc, const CONTENT& script)
{
PythonProcessLauncher py(desc, script);
py.run();
}
/// convenience function for PythonProcessLauncher::run_read()
template <typename CONTENT>
static std::string python_out(const std::string& desc, const CONTENT& script)
{
PythonProcessLauncher py(desc, script);
return py.run_read();
}
/// Create a temporary directory and clean it up later.
class NamedTempDir: public boost::noncopyable
{
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():
mPath(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()
{
aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp));
}
std::string getName() const { return mPath; } std::string getName() const { return mPath; }
private: private:
llprocesslauncher_data* mThis; std::string mPath;
std::string mPath; };
};
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
struct llprocesslauncher_data
{
LLAPRPool pool; LLAPRPool pool;
}; };
typedef test_group<llprocesslauncher_data> llprocesslauncher_group; typedef test_group<llprocesslauncher_data> llprocesslauncher_group;
...@@ -484,15 +514,13 @@ namespace tut ...@@ -484,15 +514,13 @@ namespace tut
// We want to test setWorkingDirectory(). But what directory is // We want to test setWorkingDirectory(). But what directory is
// guaranteed to exist on every machine, under every OS? Have to // guaranteed to exist on every machine, under every OS? Have to
// create one. // create one.
NamedTempDir tempdir(this); NamedTempDir tempdir;
std::string cwd(python_out("getcwd()", PythonProcessLauncher py("getcwd()",
"import os, sys\n" "import os, sys\n"
"with open(sys.argv[1], 'w') as f:\n" "with open(sys.argv[1], 'w') as f:\n"
" f.write(os.getcwd())\n", " f.write(os.getcwd())\n");
// Before LLProcessLauncher::launch(), call setWorkingDirectory() // Before running, call setWorkingDirectory()
lambda::bind(&LLProcessLauncher::setWorkingDirectory, py.mPy.setWorkingDirectory(tempdir.getName());
lambda::_1, ensure_equals("os.getcwd()", py.run_read(), tempdir.getName());
tempdir.getName())));
ensure_equals("os.getcwd()", cwd, tempdir.getName());
} }
} // namespace tut } // namespace tut
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment