From 0f665666201146069647d1686e2ff565d469097b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Jul 2011 17:36:59 -0400 Subject: Introduce support for C++ integration tests running Python scripts. This is in its infancy; tested on Mac; needs to be ironed out on Windows and Linux. Goal is to test at least some cross-language LLSD serialization. --- indra/llcommon/CMakeLists.txt | 3 +- indra/llcommon/tests/llsdserialize_test.cpp | 97 ++++++++++++++++++++++++----- indra/llcommon/tests/setpython.py | 19 ++++++ 3 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 indra/llcommon/tests/setpython.py (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 9910281b64..c755020a64 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -317,7 +317,8 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}" + "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}") diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 7b4c7d6a48..75b79467b7 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -25,33 +25,26 @@ * $/LicenseInfo$ */ -#if !LL_WINDOWS -#include -#endif - #include "linden_common.h" #include "../llsd.h" #include "../llsdserialize.h" #include "../llformat.h" #include "../test/lltut.h" - +#include "llprocesslauncher.h" +#include "stringize.h" #if LL_WINDOWS #include typedef U32 uint32_t; +#else +#include +#include #endif -std::vector string_to_vector(std::string str) +std::vector string_to_vector(const std::string& str) { - // bc LLSD can't... - size_t len = (size_t)str.length(); - std::vector v(len); - for (size_t i = 0; i < len ; i++) - { - v[i] = str[i]; - } - return v; + return std::vector(str.begin(), str.end()); } namespace tut @@ -1494,5 +1487,79 @@ namespace tut ensureBinaryAndNotation("map", test); ensureBinaryAndXML("map", test); } -} + struct TestPythonCompatible + { + TestPythonCompatible() {} + ~TestPythonCompatible() {} + + void python(const std::string& desc, const std::string& script, F32 timeout=5) + { + const char* PYTHON(getenv("PYTHON")); + ensure("Set $PYTHON to the Python interpreter", PYTHON); + LLProcessLauncher py; + py.setExecutable(PYTHON); + py.addArgument("-c"); + py.addArgument(script); + ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0); + +#if LL_WINDOWS + ensure_equals(STRINGIZE(desc << " script ran beyond " + << std::fixed << std::setprecision(1) + << timeout << " seconds"), + WaitForSingleObject(py.getProcessHandle(), DWORD(timeout * 1000)), + WAIT_OBJECT_0); + DWORD rc(0); + GetExitCodeProcess(py.getProcessHandle(), &rc); + ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, 0); +#else + // Implementing timeout would mean messing with alarm() and + // catching SIGALRM... later maybe... + int status(0); + if (waitpid(py.getProcessID(), &status, 0) == -1) + { + int waitpid_errno(errno); + ensure_equals(STRINGIZE("Couldn't retrieve rc from " << desc << " script: " + "waitpid() errno " << waitpid_errno), + waitpid_errno, ECHILD); + } + else + { + if (WIFEXITED(status)) + { + int rc(WEXITSTATUS(status)); + ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, 0); + } + else if (WIFSIGNALED(status)) + { + ensure(STRINGIZE(desc << " script terminated by signal " << WTERMSIG(status)), + false); + } + else + { + ensure(STRINGIZE(desc << " script produced impossible status " << status), + false); + } + } +#endif + } + }; + + typedef tut::test_group TestPythonCompatibleGroup; + typedef TestPythonCompatibleGroup::object TestPythonCompatibleObject; + TestPythonCompatibleGroup pycompat("LLSD serialize Python compatibility"); + + template<> template<> + void TestPythonCompatibleObject::test<1>() + { + python("hello", "print 'Hello, world!'"); + } + + template<> template<> + void TestPythonCompatibleObject::test<2>() + { + python("platform", +"import sys\n" +"print 'Running on', sys.platform"); + } +} diff --git a/indra/llcommon/tests/setpython.py b/indra/llcommon/tests/setpython.py new file mode 100644 index 0000000000..df7b90428e --- /dev/null +++ b/indra/llcommon/tests/setpython.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +"""\ +@file setpython.py +@author Nat Goodspeed +@date 2011-07-13 +@brief Set PYTHON environment variable for tests that care. + +$LicenseInfo:firstyear=2011&license=viewerlgpl$ +Copyright (c) 2011, Linden Research, Inc. +$/LicenseInfo$ +""" + +import os +import sys +import subprocess + +if __name__ == "__main__": + os.environ["PYTHON"] = sys.executable + sys.exit(subprocess.call(sys.argv[1:])) -- cgit v1.2.3 From beea7cdc2d003d815ef2ac32978f841b81288494 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Jul 2011 19:03:28 -0400 Subject: Attempt to fix confusing header-file-order problems on Windows. --- indra/llcommon/tests/llsdserialize_test.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 75b79467b7..6b96c0e234 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -25,6 +25,12 @@ * $/LicenseInfo$ */ +#if !LL_WINDOWS +#include +#include +#include +#endif + #include "linden_common.h" #include "../llsd.h" #include "../llsdserialize.h" @@ -37,9 +43,6 @@ #if LL_WINDOWS #include typedef U32 uint32_t; -#else -#include -#include #endif std::vector string_to_vector(const std::string& str) -- cgit v1.2.3 From ec780a733f46e2fc436ca5d492f42ab4e3e8b516 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Jul 2011 19:41:23 -0400 Subject: Still trying to fix Windows header-file-order problem. --- indra/llcommon/tests/llsdserialize_test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 6b96c0e234..ff0d8d5f46 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -25,13 +25,18 @@ * $/LicenseInfo$ */ -#if !LL_WINDOWS + +#include "linden_common.h" + +#if LL_WINDOWS +#include +typedef U32 uint32_t; +#else #include #include #include #endif -#include "linden_common.h" #include "../llsd.h" #include "../llsdserialize.h" #include "../llformat.h" @@ -40,11 +45,6 @@ #include "llprocesslauncher.h" #include "stringize.h" -#if LL_WINDOWS -#include -typedef U32 uint32_t; -#endif - std::vector string_to_vector(const std::string& str) { return std::vector(str.begin(), str.end()); -- cgit v1.2.3 From 0ab0efc3270f44da3d8b3a9db2845eeddde44dc6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 14:00:12 -0400 Subject: Work around broken Windows command-line processing. It's wonderful that the Python interpreter will accept a whole multi-line script as a composite -c argument... but because Windows command-line processing is fundamentally flawed, we simply can't count on it for Windows. Instead, accept script text, write a temporary script file in a system- dependent temp directory, ask Python to run that script and delete the file. Also, on Windows, use _spawnl(), much simpler than adding bizarre Windows wait logic to LLProcessLauncher. Use LLProcessLauncher only on Mac & Linux, with waitpid() to capture rc. --- indra/llcommon/tests/llsdserialize_test.cpp | 93 ++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 15 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index ff0d8d5f46..4e09a4fe3c 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -31,18 +31,28 @@ #if LL_WINDOWS #include typedef U32 uint32_t; +#include #else #include #include #include +#include "llprocesslauncher.h" #endif +// As we're not trying to preserve compatibility with old Boost.Filesystem +// code, but rather writing brand-new code, use the newest available +// Filesystem API. +#define BOOST_FILESYSTEM_VERSION 3 +#include "boost/filesystem.hpp" +#include "boost/filesystem/v3/fstream.hpp" +#include "boost/range.hpp" +#include "boost/foreach.hpp" + #include "../llsd.h" #include "../llsdserialize.h" #include "../llformat.h" #include "../test/lltut.h" -#include "llprocesslauncher.h" #include "stringize.h" std::vector string_to_vector(const std::string& str) @@ -50,6 +60,51 @@ std::vector string_to_vector(const std::string& str) return std::vector(str.begin(), str.end()); } +// boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-( +// Switch to that one as soon as we update to a Boost that contains it. +boost::filesystem::path temp_directory_path() +{ +#if LL_WINDOWS + char buffer[PATH_MAX]; + GetTempPath(sizeof(buffer), buffer); + return boost::filesystem::path(buffer); + +#else // LL_DARWIN, LL_LINUX + static const char* vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" }; + BOOST_FOREACH(const char* var, vars) + { + const char* found = getenv(var); + if (found) + return boost::filesystem::path(found); + } + return boost::filesystem::path("/tmp"); +#endif // LL_DARWIN, LL_LINUX +} + +// Create a Python script file with specified content "somewhere in the +// filesystem," cleaning up when it goes out of scope. +class NamedTempScript +{ +public: + NamedTempScript(const std::string& content): + mPath(/*boost::filesystem*/::temp_directory_path() / + boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%.py")) + { + boost::filesystem::ofstream file(mPath); + file << content << '\n'; + } + + ~NamedTempScript() + { + boost::filesystem::remove(mPath); + } + + std::string getName() const { return mPath.native(); } + +private: + boost::filesystem::path mPath; +}; + namespace tut { struct sd_xml_data @@ -1496,26 +1551,34 @@ namespace tut TestPythonCompatible() {} ~TestPythonCompatible() {} - void python(const std::string& desc, const std::string& script, F32 timeout=5) + void python(const std::string& desc, const std::string& script /*, F32 timeout=5 */) { const char* PYTHON(getenv("PYTHON")); ensure("Set $PYTHON to the Python interpreter", PYTHON); + + NamedTempScript scriptfile(script); + +#if LL_WINDOWS + std::string q("\""); + std::string qPYTHON(q + PYTHON + q); + std::string qscript(q + scriptfile.getName() + q); + int rc(_spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL)); + if (rc == -1) + { + char buffer[256]; + strerror_s(buffer, errno); // C++ can infer the buffer size! :-O + ensure(STRINGIZE("Couldn't run Python " << desc << "script: " << buffer), false); + } + else + { + ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, 0); + } + +#else // LL_DARWIN, LL_LINUX LLProcessLauncher py; py.setExecutable(PYTHON); - py.addArgument("-c"); - py.addArgument(script); + py.addArgument(scriptfile.getName()); ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0); - -#if LL_WINDOWS - ensure_equals(STRINGIZE(desc << " script ran beyond " - << std::fixed << std::setprecision(1) - << timeout << " seconds"), - WaitForSingleObject(py.getProcessHandle(), DWORD(timeout * 1000)), - WAIT_OBJECT_0); - DWORD rc(0); - GetExitCodeProcess(py.getProcessHandle(), &rc); - ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, 0); -#else // Implementing timeout would mean messing with alarm() and // catching SIGALRM... later maybe... int status(0); -- cgit v1.2.3 From 3ddaf95c5c0dc35f0efa91860f9642d4cdf26559 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 14:01:45 -0400 Subject: New llsdserialize_test logic needs Boost.Filesystem library. That, in turn, needs Boost.System library. --- indra/llcommon/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index c755020a64..09a05689f4 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -317,7 +317,8 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}" + LL_ADD_INTEGRATION_TEST(llsdserialize "" + "${test_libs};${BOOST_FILESYSTEM_LIBRARY};${BOOST_SYSTEM_LIBRARY}" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") -- cgit v1.2.3 From 4c465f496f602860f5044bded01fde8883083e70 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 15:08:25 -0400 Subject: Eliminate use of PATH_MAX, which is bogus anyway. --- indra/llcommon/tests/llsdserialize_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 4e09a4fe3c..687c64dfeb 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -65,7 +65,7 @@ std::vector string_to_vector(const std::string& str) boost::filesystem::path temp_directory_path() { #if LL_WINDOWS - char buffer[PATH_MAX]; + char buffer[4096]; GetTempPath(sizeof(buffer), buffer); return boost::filesystem::path(buffer); -- cgit v1.2.3 From 24508cc924938d2a8752496b9752b7c4d934dd77 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 15:27:36 -0400 Subject: Attempt to fix minor build errors on Windows. --- indra/llcommon/tests/llsdserialize_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 687c64dfeb..e0a7835550 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -66,7 +66,7 @@ boost::filesystem::path temp_directory_path() { #if LL_WINDOWS char buffer[4096]; - GetTempPath(sizeof(buffer), buffer); + GetTempPathA(sizeof(buffer), buffer); return boost::filesystem::path(buffer); #else // LL_DARWIN, LL_LINUX @@ -99,7 +99,7 @@ public: boost::filesystem::remove(mPath); } - std::string getName() const { return mPath.native(); } + std::string getName() const { return mPath.string(); } private: boost::filesystem::path mPath; -- cgit v1.2.3 From 624c3f1a8e503a3a577b81e06b0ae3344e8ede17 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 16:24:31 -0400 Subject: Use Linden wstring-to-string conversion, not boost::filesystem's. On Windows, calling boost::filesystem::path::string() implicitly requests code conversion between std::wstring (the boost::filesystem::path::string_type selected on Windows) and std::string. At least for integration-test program, that produces link errors. Use Linden's wstring_to_utf8str() instead. --- indra/llcommon/tests/llsdserialize_test.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index e0a7835550..93261fa306 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -54,6 +54,7 @@ typedef U32 uint32_t; #include "../test/lltut.h" #include "stringize.h" +#include "llstring.h" std::vector string_to_vector(const std::string& str) { @@ -81,6 +82,28 @@ boost::filesystem::path temp_directory_path() #endif // LL_DARWIN, LL_LINUX } +// We want a std::string from a boost::filesystem::path::native() call. While +// there is a boost::filesystem::path::string() call as well, invoking that +// (at least in this test-program context) produces unresolved externals for +// boost::filesystem::path conversion machinery even though we can resolve +// other boost::filesystem symbols. Possibly those conversion routines aren't +// being built for Linden's Boost package. But that's okay, llstring.h +// provides conversion machinery we can use instead. +// On Posix platforms, boost::filesystem::path::native() returns std::string. +inline +std::string native_to_string(const std::string& in) +{ + return in; +} + +// On Windows, though, boost::filesystem::path::native() returns std::wstring. +// Make sure the right conversion happens. +inline +std::string native_to_string(const std::wstring& in) +{ + return wstring_to_utf8str(in); +} + // Create a Python script file with specified content "somewhere in the // filesystem," cleaning up when it goes out of scope. class NamedTempScript @@ -99,7 +122,7 @@ public: boost::filesystem::remove(mPath); } - std::string getName() const { return mPath.string(); } + std::string getName() const { return native_to_string(mPath.native()); } private: boost::filesystem::path mPath; -- cgit v1.2.3 From 8ca0f872f2f3cd026788c3ea28c3a00f5d407033 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 17:12:02 -0400 Subject: wstring_to_utf8str() accepts LLWString rather than std::wstring. --- indra/llcommon/tests/llsdserialize_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 93261fa306..e61e6b9460 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -101,7 +101,8 @@ std::string native_to_string(const std::string& in) inline std::string native_to_string(const std::wstring& in) { - return wstring_to_utf8str(in); + // So actually, wstring_to_utf8str() accepts LLWString rather than std::wstring... + return wstring_to_utf8str(LLWString(in.begin(), in.end())); } // Create a Python script file with specified content "somewhere in the -- cgit v1.2.3 From 5f37ec3c712221765bbb42e3428975e9b1402c9c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 19:07:13 -0400 Subject: Avoid Boost.Filesystem: Boost package improperly built on Windows? Seems Linden's Boost package and the viewer build might use different settings of the /Zc:wchar_t switch. Anyway, this implementation using open(O_CREAT | O_EXCL) should be more robust. I'm surprised Boost.Filesystem doesn't seem to offer "create a unique file"; all I found was "generate a random filename fairly likely to be unique." --- indra/llcommon/tests/llsdserialize_test.cpp | 112 +++++++++++++++++++--------- 1 file changed, 77 insertions(+), 35 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index e61e6b9460..ec0cacfe90 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -33,18 +33,34 @@ typedef U32 uint32_t; #include #else +#include #include #include +#include #include +#include #include "llprocesslauncher.h" #endif +/*==========================================================================*| +// Whoops, seems Linden's Boost package and the viewer are built with +// different settings of VC's /Zc:wchar_t switch! Using Boost.Filesystem +// pathname operations produces Windows link errors: +// unresolved external symbol "private: static class std::codecvt const * & __cdecl boost::filesystem3::path::wchar_t_codecvt_facet()" +// unresolved external symbol "void __cdecl boost::filesystem3::path_traits::convert()" +// See: +// http://boost.2283326.n4.nabble.com/filesystem-v3-unicode-and-std-codecvt-linker-error-td3455549.html +// which points to: +// http://msdn.microsoft.com/en-us/library/dh8che7s%28v=VS.100%29.aspx + // As we're not trying to preserve compatibility with old Boost.Filesystem // code, but rather writing brand-new code, use the newest available // Filesystem API. #define BOOST_FILESYSTEM_VERSION 3 #include "boost/filesystem.hpp" #include "boost/filesystem/v3/fstream.hpp" +|*==========================================================================*/ #include "boost/range.hpp" #include "boost/foreach.hpp" @@ -54,7 +70,6 @@ typedef U32 uint32_t; #include "../test/lltut.h" #include "stringize.h" -#include "llstring.h" std::vector string_to_vector(const std::string& str) { @@ -62,13 +77,12 @@ std::vector string_to_vector(const std::string& str) } // boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-( -// Switch to that one as soon as we update to a Boost that contains it. -boost::filesystem::path temp_directory_path() +std::string temp_directory_path() { #if LL_WINDOWS char buffer[4096]; GetTempPathA(sizeof(buffer), buffer); - return boost::filesystem::path(buffer); + return buffer; #else // LL_DARWIN, LL_LINUX static const char* vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" }; @@ -76,34 +90,28 @@ boost::filesystem::path temp_directory_path() { const char* found = getenv(var); if (found) - return boost::filesystem::path(found); + return found; } - return boost::filesystem::path("/tmp"); + return "/tmp"; #endif // LL_DARWIN, LL_LINUX } -// We want a std::string from a boost::filesystem::path::native() call. While -// there is a boost::filesystem::path::string() call as well, invoking that -// (at least in this test-program context) produces unresolved externals for -// boost::filesystem::path conversion machinery even though we can resolve -// other boost::filesystem symbols. Possibly those conversion routines aren't -// being built for Linden's Boost package. But that's okay, llstring.h -// provides conversion machinery we can use instead. -// On Posix platforms, boost::filesystem::path::native() returns std::string. -inline -std::string native_to_string(const std::string& in) -{ - return in; -} - -// On Windows, though, boost::filesystem::path::native() returns std::wstring. -// Make sure the right conversion happens. -inline -std::string native_to_string(const std::wstring& in) -{ - // So actually, wstring_to_utf8str() accepts LLWString rather than std::wstring... - return wstring_to_utf8str(LLWString(in.begin(), in.end())); -} +// Windows presents a kinda sorta compatibility layer. Code to the yucky +// Windows names because they're less likely than the Posix names to collide +// with any other names in this source. +#if LL_WINDOWS +#define _remove DeleteFile +#else // ! LL_WINDOWS +#define _open open +#define _write write +#define _close close +#define _remove remove +#define _O_WRONLY O_WRONLY +#define _O_CREAT O_CREAT +#define _O_EXCL O_EXCL +#define _S_IWRITE S_IWUSR +#define _S_IREAD S_IRUSR +#endif // ! LL_WINDOWS // Create a Python script file with specified content "somewhere in the // filesystem," cleaning up when it goes out of scope. @@ -111,22 +119,56 @@ class NamedTempScript { public: NamedTempScript(const std::string& content): - mPath(/*boost::filesystem*/::temp_directory_path() / - boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%.py")) + mPath(temp_directory_path()) { - boost::filesystem::ofstream file(mPath); - file << content << '\n'; + // Make sure mPath ends with a directory separator, if it doesn't already. + if (mPath.empty() || + ! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/')) + { + mPath.append("/"); + } + + // Open a file with a unique name in the mPath directory. + int fd(-1); + for (int i(0);; ++i) + { + // Append an integer name to mPath. It need not be zero-filled, + // but I think it's neater that way. + std::string testname(STRINGIZE(mPath + << std::setw(8) << std::setfill('0') << i + << ".py")); + // The key to this whole loop is the _O_CREAT | _O_EXCL bitmask, + // which requests an error if the file already exists. A proper + // implementation will check atomically, ensuring that racing + // processes will end up with two different filenames. + fd = _open(testname.c_str(), + _O_WRONLY | _O_CREAT | _O_EXCL, + _S_IREAD | _S_IWRITE); + if (fd != -1) // managed to open the file + { + mPath = testname; // remember its actual name + break; + } + // it's a problem if the open() failed for any other reason but + // the existence of a file by the same name + assert(errno == EEXIST); + // loop back to try another filename + } + // File is open, its name is in mPath: write it and close it. + _write(fd, content.c_str(), content.length()); + _write(fd, "\n", 1); + _close(fd); } ~NamedTempScript() { - boost::filesystem::remove(mPath); + _remove(mPath.c_str()); } - std::string getName() const { return native_to_string(mPath.native()); } + std::string getName() const { return mPath; } private: - boost::filesystem::path mPath; + std::string mPath; }; namespace tut -- cgit v1.2.3 From 9f66409b88481ca4ded5b9bb9d81e5977a43a5af Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 19:39:32 -0400 Subject: #include correct headers for Windows _open() et al. Also mollify Linux build, which gets alarmed when you implicitly ignore write()'s return value. Ignore it explicitly. --- indra/llcommon/tests/llsdserialize_test.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index ec0cacfe90..025870c915 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -32,16 +32,18 @@ #include typedef U32 uint32_t; #include +#include #else #include #include #include -#include #include -#include #include "llprocesslauncher.h" #endif +#include +#include + /*==========================================================================*| // Whoops, seems Linden's Boost package and the viewer are built with // different settings of VC's /Zc:wchar_t switch! Using Boost.Filesystem @@ -100,7 +102,7 @@ std::string temp_directory_path() // Windows names because they're less likely than the Posix names to collide // with any other names in this source. #if LL_WINDOWS -#define _remove DeleteFile +#define _remove DeleteFileA #else // ! LL_WINDOWS #define _open open #define _write write @@ -155,8 +157,8 @@ public: // loop back to try another filename } // File is open, its name is in mPath: write it and close it. - _write(fd, content.c_str(), content.length()); - _write(fd, "\n", 1); + (void)_write(fd, content.c_str(), content.length()); + (void)_write(fd, "\n", 1); _close(fd); } -- cgit v1.2.3 From 1f58cd688f44fed6da91af5cac0d48166c2647d0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 14 Jul 2011 20:20:35 -0400 Subject: Pacify Linux gcc more thoroughly. --- indra/llcommon/tests/llsdserialize_test.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 025870c915..41d2fcc696 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -157,8 +157,12 @@ public: // loop back to try another filename } // File is open, its name is in mPath: write it and close it. - (void)_write(fd, content.c_str(), content.length()); - (void)_write(fd, "\n", 1); + // Truthfully, we'd just as soon ignore the return value from + // _write(), but Linux gcc generates fatal warnings if we do. + bool ok(true); + ok = ok && (content.length() == _write(fd, content.c_str(), content.length())); + ok = ok && (1 == _write(fd, "\n", 1)); + assert(ok); _close(fd); } -- cgit v1.2.3 From e3b5d9fc5c638beff4eed25a366dc5cc1c0478b1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 10:48:46 -0400 Subject: Change NamedTempScript to NamedTempFile; allow streaming to it. The only thing about NamedTempScript that was specific to script files was the hardcoded ".py" extension. Renaming it to NamedTempFile with an explicit extension argument addresses that. Allow constructing NamedTempFile with either a std::string, as before, or an expression of the form (lambda::_1 << some << stuff). If Linden's Boost package included the Boost.Iostreams lib, we could even stream such an expression directly to an ostream constructed around the fd. But oh well. --- indra/llcommon/tests/llsdserialize_test.cpp | 102 ++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 27 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 41d2fcc696..8b59e99b24 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -43,6 +43,7 @@ typedef U32 uint32_t; #include #include +#include /*==========================================================================*| // Whoops, seems Linden's Boost package and the viewer are built with @@ -65,6 +66,14 @@ typedef U32 uint32_t; |*==========================================================================*/ #include "boost/range.hpp" #include "boost/foreach.hpp" +#include "boost/function.hpp" +#include "boost/lambda/lambda.hpp" +namespace lambda = boost::lambda; +/*==========================================================================*| +// Aaaarrgh, Linden's Boost package doesn't even include Boost.Iostreams! +#include "boost/iostreams/stream.hpp" +#include "boost/iostreams/device/file_descriptor.hpp" +|*==========================================================================*/ #include "../llsd.h" #include "../llsdserialize.h" @@ -115,13 +124,37 @@ std::string temp_directory_path() #define _S_IREAD S_IRUSR #endif // ! LL_WINDOWS -// Create a Python script file with specified content "somewhere in the +// Create a text file with specified content "somewhere in the // filesystem," cleaning up when it goes out of scope. -class NamedTempScript +class NamedTempFile { public: - NamedTempScript(const std::string& content): + // Function that accepts an ostream ref and (presumably) writes stuff to + // it, e.g.: + // (lambda::_1 << "the value is " << 17 << '\n') + typedef boost::function Streamer; + + NamedTempFile(const std::string& ext, const std::string& content): + mPath(temp_directory_path()) + { + createFile(ext, lambda::_1 << content); + } + + NamedTempFile(const std::string& ext, const Streamer& func): mPath(temp_directory_path()) + { + createFile(ext, func); + } + + ~NamedTempFile() + { + _remove(mPath.c_str()); + } + + std::string getName() const { return mPath; } + +private: + void createFile(const std::string& ext, const Streamer& func) { // Make sure mPath ends with a directory separator, if it doesn't already. if (mPath.empty() || @@ -138,11 +171,11 @@ public: // but I think it's neater that way. std::string testname(STRINGIZE(mPath << std::setw(8) << std::setfill('0') << i - << ".py")); + << ext)); // The key to this whole loop is the _O_CREAT | _O_EXCL bitmask, - // which requests an error if the file already exists. A proper - // implementation will check atomically, ensuring that racing - // processes will end up with two different filenames. + // which requests error EEXIST if the file already exists. A + // proper implementation will check atomically, ensuring that + // racing processes will end up with two different filenames. fd = _open(testname.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL, _S_IREAD | _S_IWRITE); @@ -151,29 +184,40 @@ public: mPath = testname; // remember its actual name break; } - // it's a problem if the open() failed for any other reason but - // the existence of a file by the same name - assert(errno == EEXIST); + // This loop is specifically coded to handle EEXIST. Any other + // error is a problem. + llassert_always(errno == EEXIST); // loop back to try another filename } - // File is open, its name is in mPath: write it and close it. - // Truthfully, we'd just as soon ignore the return value from - // _write(), but Linux gcc generates fatal warnings if we do. - bool ok(true); - ok = ok && (content.length() == _write(fd, content.c_str(), content.length())); - ok = ok && (1 == _write(fd, "\n", 1)); - assert(ok); - _close(fd); + // fd is open, its name is in mPath: write it and close it. + +/*==========================================================================*| + // Define an ostream on the open fd. Tell it to close fd on destruction. + boost::iostreams::stream + out(fd, boost::iostreams::close_handle); +|*==========================================================================*/ + std::ostringstream out; + // Stream stuff to it. + func(out); + // toss in a final newline for good measure + out << '\n'; + + std::string data(out.str()); + int written(_write(fd, data.c_str(), data.length())); + int closed(_close(fd)); + llassert_always(written == data.length() && closed == 0); } - ~NamedTempScript() + void peep() { - _remove(mPath.c_str()); + std::cout << "File '" << mPath << "' contains:\n"; + std::ifstream reader(mPath.c_str()); + std::string line; + while (std::getline(reader, line)) + std::cout << line << '\n'; + std::cout << "---\n"; } - std::string getName() const { return mPath; } - -private: std::string mPath; }; @@ -1628,7 +1672,7 @@ namespace tut const char* PYTHON(getenv("PYTHON")); ensure("Set $PYTHON to the Python interpreter", PYTHON); - NamedTempScript scriptfile(script); + NamedTempFile scriptfile(".py", script); #if LL_WINDOWS std::string q("\""); @@ -1690,14 +1734,18 @@ namespace tut template<> template<> void TestPythonCompatibleObject::test<1>() { - python("hello", "print 'Hello, world!'"); + set_test_name("verify python()"); + python("hello", + "import sys\n" + "sys.exit(0)"); } template<> template<> void TestPythonCompatibleObject::test<2>() { + set_test_name("verify NamedTempFile"); python("platform", -"import sys\n" -"print 'Running on', sys.platform"); + "import sys\n" + "print 'Running on', sys.platform"); } } -- cgit v1.2.3 From c33cf379f25e9a1a3780a73805749616fa07de66 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 11:53:05 -0400 Subject: Add test to verify C++-to-Python LLSD notation sequence. Write a sequence of LLSDSerialize::toNotation() calls separated by newlines to a data file, then read lines and parse using llbase.llsd.parse(). Verify that this produces expected data even when one item is a string containing newlines. Generalize python() helper function to allow using any of the NamedTempFile constructor forms. Allow specifying expected Python rc (default 0) and use this to verify an intentional sys.exit(17). This is better than previous sys.exit(0) test because when, at one point, NamedTempFile failed to write file data, running Python on an empty script file still terminates with rc 0. A nonzero rc verifies that we've written the file, that Python is running it and that we're retrieving its rc. --- indra/llcommon/tests/llsdserialize_test.cpp | 65 +++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 8b59e99b24..aaf3740b07 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -68,6 +68,7 @@ typedef U32 uint32_t; #include "boost/foreach.hpp" #include "boost/function.hpp" #include "boost/lambda/lambda.hpp" +#include "boost/lambda/bind.hpp" namespace lambda = boost::lambda; /*==========================================================================*| // Aaaarrgh, Linden's Boost package doesn't even include Boost.Iostreams! @@ -77,6 +78,7 @@ namespace lambda = boost::lambda; #include "../llsd.h" #include "../llsdserialize.h" +#include "llsdutil.h" #include "../llformat.h" #include "../test/lltut.h" @@ -140,6 +142,13 @@ public: createFile(ext, lambda::_1 << content); } + // Disambiguate when passing string literal + NamedTempFile(const std::string& ext, const char* content): + mPath(temp_directory_path()) + { + createFile(ext, lambda::_1 << content); + } + NamedTempFile(const std::string& ext, const Streamer& func): mPath(temp_directory_path()) { @@ -1667,7 +1676,8 @@ namespace tut TestPythonCompatible() {} ~TestPythonCompatible() {} - void python(const std::string& desc, const std::string& script /*, F32 timeout=5 */) + template + void python(const std::string& desc, const CONTENT& script, int expect=0) { const char* PYTHON(getenv("PYTHON")); ensure("Set $PYTHON to the Python interpreter", PYTHON); @@ -1687,7 +1697,7 @@ namespace tut } else { - ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, 0); + ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, expect); } #else // LL_DARWIN, LL_LINUX @@ -1710,7 +1720,8 @@ namespace tut if (WIFEXITED(status)) { int rc(WEXITSTATUS(status)); - ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, 0); + ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), + rc, expect); } else if (WIFSIGNALED(status)) { @@ -1737,7 +1748,8 @@ namespace tut set_test_name("verify python()"); python("hello", "import sys\n" - "sys.exit(0)"); + "sys.exit(17)", + 17); // expect nonzero rc } template<> template<> @@ -1748,4 +1760,49 @@ namespace tut "import sys\n" "print 'Running on', sys.platform"); } + + template<> template<> + void TestPythonCompatibleObject::test<3>() + { + set_test_name("verify sequence to Python"); + + LLSD cdata(LLSDArray(17)(3.14) + ("This string\n" + "has several\n" + "lines.")); + + const char pydata[] = + "def verify(iterable):\n" + " it = iter(iterable)\n" + " assert it.next() == 17\n" + " assert abs(it.next() - 3.14) < 0.01\n" + " assert it.next() == '''\\\n" + "This string\n" + "has several\n" + "lines.'''\n" + " try:\n" + " it.next()\n" + " except StopIteration:\n" + " pass\n" + " else:\n" + " assert False, 'Too many data items'\n"; + + // Create a something.llsd file containing 'data' serialized to notation. + // Avoid final newline because NamedTempFile implicitly adds one. + NamedTempFile file(".llsd", + (lambda::bind(LLSDSerialize::toNotation, cdata[0], lambda::_1), + lambda::_1 << '\n', + lambda::bind(LLSDSerialize::toNotation, cdata[1], lambda::_1), + lambda::_1 << '\n', + lambda::bind(LLSDSerialize::toNotation, cdata[2], lambda::_1))); + + python("read C++ notation", + lambda::_1 << + "from llbase import llsd\n" + "def parse_each(iterable):\n" + " for item in iterable:\n" + " yield llsd.parse(item)\n" << + pydata << + "verify(parse_each(open('" << file.getName() << "')))\n"); + } } -- cgit v1.2.3 From 7341e01ce2c833a190e6361bd97cf64bc1947efc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 12:42:49 -0400 Subject: Add test to verify Python-to-C++ LLSD notation sequence. Verify that an LLSD::String containing newlines works; verify that newlines between items are accepted. --- indra/llcommon/tests/llsdserialize_test.cpp | 60 ++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index aaf3740b07..0b9f1b53d2 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1787,8 +1787,12 @@ namespace tut " else:\n" " assert False, 'Too many data items'\n"; - // Create a something.llsd file containing 'data' serialized to notation. - // Avoid final newline because NamedTempFile implicitly adds one. + // Create a something.llsd file containing 'data' serialized to + // notation. It's important to separate with newlines because Python's + // llsd module doesn't support parsing from a file stream, only from a + // string, so we have to know how much of the file to read into a + // string. Avoid final newline because NamedTempFile implicitly adds + // one. NamedTempFile file(".llsd", (lambda::bind(LLSDSerialize::toNotation, cdata[0], lambda::_1), lambda::_1 << '\n', @@ -1805,4 +1809,56 @@ namespace tut pydata << "verify(parse_each(open('" << file.getName() << "')))\n"); } + + template<> template<> + void TestPythonCompatibleObject::test<4>() + { + set_test_name("verify sequence from Python"); + + // Create an empty data file. This is just a placeholder for our + // script to write into. Create it to establish a unique name that + // we know. + NamedTempFile file(".llsd", ""); + + python("write Python notation", + lambda::_1 << + "from __future__ import with_statement\n" + "from llbase import llsd\n" + "DATA = [\n" + " 17,\n" + " 3.14,\n" + " '''\\\n" + "This string\n" + "has several\n" + "lines.''',\n" + "]\n" + // N.B. Using 'print' implicitly adds newlines. + "with open('" << file.getName() << "', 'w') as f:\n" + " for item in DATA:\n" + " print >>f, llsd.format_notation(item)\n"); + + std::ifstream inf(file.getName().c_str()); + LLSD item; + // Notice that we're not doing anything special to parse out the + // newlines: LLSDSerialize::fromNotation ignores them. While it would + // seem they're not strictly necessary, going in this direction, we + // want to ensure that notation-separated-by-newlines works in both + // directions -- since in practice, a given file might be read by + // either language. + ensure_equals("Failed to read LLSD::Integer from Python", + LLSDSerialize::fromNotation(item, inf, LLSDSerialize::SIZE_UNLIMITED), + 1); + ensure_equals(item.asInteger(), 17); + ensure_equals("Failed to read LLSD::Real from Python", + LLSDSerialize::fromNotation(item, inf, LLSDSerialize::SIZE_UNLIMITED), + 1); + ensure_approximately_equals(item.asReal(), 3.14, 7); // 7 bits ~= 0.01 + ensure_equals("Failed to read LLSD::String from Python", + LLSDSerialize::fromNotation(item, inf, LLSDSerialize::SIZE_UNLIMITED), + 1); + ensure_equals(item.asString(), + "This string\n" + "has several\n" + "lines."); + } } -- cgit v1.2.3 From 4b21954729163069e9d8f78c22624a2ecbc17b38 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 14:02:45 -0400 Subject: Muzzle VS warning --- indra/llcommon/tests/llsdserialize_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 0b9f1b53d2..e6a0deaa8b 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1852,7 +1852,7 @@ namespace tut ensure_equals("Failed to read LLSD::Real from Python", LLSDSerialize::fromNotation(item, inf, LLSDSerialize::SIZE_UNLIMITED), 1); - ensure_approximately_equals(item.asReal(), 3.14, 7); // 7 bits ~= 0.01 + ensure_approximately_equals(F32(item.asReal()), 3.14, 7); // 7 bits ~= 0.01 ensure_equals("Failed to read LLSD::String from Python", LLSDSerialize::fromNotation(item, inf, LLSDSerialize::SIZE_UNLIMITED), 1); -- cgit v1.2.3 From fee07bb597a64489b8e4bca8513ebd2afd9e7b6e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 14:24:37 -0400 Subject: Try again to pacify VS fatal warning. --- indra/llcommon/tests/llsdserialize_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index e6a0deaa8b..b0c0df192c 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1852,7 +1852,8 @@ namespace tut ensure_equals("Failed to read LLSD::Real from Python", LLSDSerialize::fromNotation(item, inf, LLSDSerialize::SIZE_UNLIMITED), 1); - ensure_approximately_equals(F32(item.asReal()), 3.14, 7); // 7 bits ~= 0.01 + ensure_approximately_equals("Bad LLSD::Real value from Python", + item.asReal(), 3.14, 7); // 7 bits ~= 0.01 ensure_equals("Failed to read LLSD::String from Python", LLSDSerialize::fromNotation(item, inf, LLSDSerialize::SIZE_UNLIMITED), 1); -- cgit v1.2.3 From e41c4c90f0d8c935fa8e4de6a8439d00283c3206 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 16:01:43 -0400 Subject: Not all TC agents have llbase.llsd, fall back to indra.base.llsd --- indra/llcommon/tests/llsdserialize_test.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index b0c0df192c..30cc2bbc8a 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1802,7 +1802,10 @@ namespace tut python("read C++ notation", lambda::_1 << - "from llbase import llsd\n" + "try:\n" + " from llbase import llsd\n" + "except ImportError:\n" + " from indra.base import llsd\n" "def parse_each(iterable):\n" " for item in iterable:\n" " yield llsd.parse(item)\n" << @@ -1823,7 +1826,10 @@ namespace tut python("write Python notation", lambda::_1 << "from __future__ import with_statement\n" - "from llbase import llsd\n" + "try:\n" + " from llbase import llsd\n" + "except ImportError:\n" + " from indra.base import llsd\n" "DATA = [\n" " 17,\n" " 3.14,\n" -- cgit v1.2.3 From 15c36ee9b39624a29303b6e0cf434c9758657ded Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 17:20:27 -0400 Subject: If we're going to need indra.base.llsd, have to munge sys.path. And at that point, the Python logic needed to bring in the llsd module is big enough to warrant capturing it in a separate string variable common to multiple tests. --- indra/llcommon/tests/llsdserialize_test.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 30cc2bbc8a..a54d861680 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1673,9 +1673,21 @@ namespace tut struct TestPythonCompatible { - TestPythonCompatible() {} + TestPythonCompatible(): + import_llsd("import os.path\n" + "import sys\n" + "sys.path.insert(0,\n" + " os.path.join(os.path.dirname(__file__),\n" + " os.pardir, os.pardir, 'lib', 'python'))\n" + "try:\n" + " from llbase import llsd\n" + "except ImportError:\n" + " from indra.base import llsd\n") + {} ~TestPythonCompatible() {} + std::string import_llsd; + template void python(const std::string& desc, const CONTENT& script, int expect=0) { @@ -1802,10 +1814,7 @@ namespace tut python("read C++ notation", lambda::_1 << - "try:\n" - " from llbase import llsd\n" - "except ImportError:\n" - " from indra.base import llsd\n" + import_llsd << "def parse_each(iterable):\n" " for item in iterable:\n" " yield llsd.parse(item)\n" << @@ -1825,11 +1834,8 @@ namespace tut python("write Python notation", lambda::_1 << - "from __future__ import with_statement\n" - "try:\n" - " from llbase import llsd\n" - "except ImportError:\n" - " from indra.base import llsd\n" + "from __future__ import with_statement\n" << + import_llsd << "DATA = [\n" " 17,\n" " 3.14,\n" -- cgit v1.2.3 From 2b509383ccb22a1a4258e1d56710cbb998d6c6af Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 15 Jul 2011 22:32:06 -0400 Subject: Use C++ __FILE__ rather than Python __file__ to find indra work area. In this case, the Python code in question is being written from a C++ string literal to a temp script file in a platform-dependent temp directory -- so the Python __file__ value tells you nothing about the location of the repository checkout. Embedding __FILE__ from the containing C++ source file works better. --- indra/llcommon/tests/llsdserialize_test.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index a54d861680..c65a1d3ca0 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1674,10 +1674,16 @@ namespace tut struct TestPythonCompatible { TestPythonCompatible(): + // Note the peculiar insertion of __FILE__ into this string. + // Normally I like to make a Python script navigate relative to + // its own placement in the repo directory tree (__file__) -- but + // in this case, the script is being written into a platform- + // dependent temp directory! So locate indra/lib/python relative + // to this C++ source file rather than the Python module. import_llsd("import os.path\n" "import sys\n" "sys.path.insert(0,\n" - " os.path.join(os.path.dirname(__file__),\n" + " os.path.join(os.path.dirname('" __FILE__ "'),\n" " os.pardir, os.pardir, 'lib', 'python'))\n" "try:\n" " from llbase import llsd\n" -- cgit v1.2.3 From 81dc4401288f0a3cb95ce53d4ede79daa0f0f3d0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 16 Jul 2011 10:20:41 -0400 Subject: Use raw-string syntax for Python string containing Windows pathname. Consider this pathname for llsdserialize_test.cpp: C:\nats\indra\llcommon\tests\llsdserialize_test.cpp Embed that in a Python string literal: 'C:\nats\indra\llcommon\tests\llsdserialize_test.cpp' and you get a string containing: C: ats\indra\llcommon ests\llsdserialize_test.cpp where the \n became a newline and the \t became a tab character. Hopefully Python raw-string syntax r'C:\etc\etc' works better. --- indra/llcommon/tests/llsdserialize_test.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index c65a1d3ca0..81de64930f 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1680,10 +1680,12 @@ namespace tut // in this case, the script is being written into a platform- // dependent temp directory! So locate indra/lib/python relative // to this C++ source file rather than the Python module. + // Use Python raw-string syntax so Windows pathname backslashes + // won't mislead Python's string scanner. import_llsd("import os.path\n" "import sys\n" "sys.path.insert(0,\n" - " os.path.join(os.path.dirname('" __FILE__ "'),\n" + " os.path.join(os.path.dirname(r'" __FILE__ "'),\n" " os.pardir, os.pardir, 'lib', 'python'))\n" "try:\n" " from llbase import llsd\n" -- cgit v1.2.3 From 790032d2316989eb5b36af2569408ce1e1296015 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 16 Jul 2011 22:24:31 -0400 Subject: Use raw-string syntax for other Windows pathnames inserted to Python. --- indra/llcommon/tests/llsdserialize_test.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 81de64930f..7ce7ada29e 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1827,7 +1827,8 @@ namespace tut " for item in iterable:\n" " yield llsd.parse(item)\n" << pydata << - "verify(parse_each(open('" << file.getName() << "')))\n"); + // Don't forget raw-string syntax for Windows pathnames. + "verify(parse_each(open(r'" << file.getName() << "')))\n"); } template<> template<> @@ -1852,8 +1853,9 @@ namespace tut "has several\n" "lines.''',\n" "]\n" + // Don't forget raw-string syntax for Windows pathnames. // N.B. Using 'print' implicitly adds newlines. - "with open('" << file.getName() << "', 'w') as f:\n" + "with open(r'" << file.getName() << "', 'w') as f:\n" " for item in DATA:\n" " print >>f, llsd.format_notation(item)\n"); -- cgit v1.2.3 From 9077f7722b3ab2b6dda7e5c13cee3fd59d7dbf53 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 17 Jul 2011 09:51:29 -0400 Subject: Decided against using Boost.Filesystem, remove from link --- indra/llcommon/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 09a05689f4..c755020a64 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -317,8 +317,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llsdserialize "" - "${test_libs};${BOOST_FILESYSTEM_LIBRARY};${BOOST_SYSTEM_LIBRARY}" + LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") -- cgit v1.2.3 From 677609b7224b2cd1e02e5866218f2e0d1fce57ba Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 19 Jul 2011 09:05:54 -0400 Subject: Per Josh's comments in http://codereview.lindenlab.com/6510035/ Instead of low-level open(O_CREAT | O_EXCL) loop on all platforms, use GetTempFileName() on Windows and mkstemp() elsewhere. Don't append a final newline to NamedTempFile: use caller's data literally. Tweak a couple comments. --- indra/llcommon/tests/llsdserialize_test.cpp | 179 +++++++++++++++++++++------- 1 file changed, 133 insertions(+), 46 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 7ce7ada29e..1fe3dc13c0 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -37,12 +37,12 @@ typedef U32 uint32_t; #include #include #include +#include +#include #include #include "llprocesslauncher.h" #endif -#include -#include #include /*==========================================================================*| @@ -89,6 +89,45 @@ std::vector string_to_vector(const std::string& str) return std::vector(str.begin(), str.end()); } +#if ! LL_WINDOWS +// We want to call strerror_r(), but alarmingly, there are two different +// variants. The one that returns int always populates the passed buffer +// (except in case of error), whereas the other one always returns a valid +// char* but might or might not populate the passed buffer. How do we know +// which one we're getting? Define adapters for each and let the compiler +// select the applicable adapter. + +// strerror_r() returns char* +std::string message_from(int /*orig_errno*/, const char* /*buffer*/, const char* strerror_ret) +{ + return strerror_ret; +} + +// strerror_r() returns int +std::string message_from(int orig_errno, const char* buffer, int strerror_ret) +{ + if (strerror_ret == 0) + { + return buffer; + } + // Here strerror_r() has set errno. Since strerror_r() has already failed, + // seems like a poor bet to call it again to diagnose its own error... + int stre_errno = errno; + if (stre_errno == ERANGE) + { + return STRINGIZE("strerror_r() can't explain errno " << orig_errno + << " (buffer too small)"); + } + if (stre_errno == EINVAL) + { + return STRINGIZE("unknown errno " << orig_errno); + } + // Here we don't even understand the errno from strerror_r()! + return STRINGIZE("strerror_r() can't explain errno " << orig_errno + << " (error " << stre_errno << ')'); +} +#endif // ! LL_WINDOWS + // boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-( std::string temp_directory_path() { @@ -119,11 +158,6 @@ std::string temp_directory_path() #define _write write #define _close close #define _remove remove -#define _O_WRONLY O_WRONLY -#define _O_CREAT O_CREAT -#define _O_EXCL O_EXCL -#define _S_IWRITE S_IWUSR -#define _S_IREAD S_IRUSR #endif // ! LL_WINDOWS // Create a text file with specified content "somewhere in the @@ -165,6 +199,11 @@ public: private: void createFile(const std::string& ext, const Streamer& func) { + // Silly maybe, but use 'ext' as the name prefix. Strip off a leading + // '.' if present. + int pfx_offset = ((! ext.empty()) && ext[0] == '.')? 1 : 0; + +#if ! LL_WINDOWS // Make sure mPath ends with a directory separator, if it doesn't already. if (mPath.empty() || ! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/')) @@ -172,49 +211,92 @@ private: mPath.append("/"); } - // Open a file with a unique name in the mPath directory. - int fd(-1); - for (int i(0);; ++i) + // mkstemp() accepts and modifies a char* template string. Generate + // the template string, then copy to modifiable storage. + // mkstemp() requires its template string to end in six X's. + mPath += ext.substr(pfx_offset) + "XXXXXX"; + // Copy to vector + std::vector pathtemplate(mPath.begin(), mPath.end()); + // append a nul byte for classic-C semantics + pathtemplate.push_back('\0'); + // std::vector promises that a pointer to the 0th element is the same + // as a pointer to a contiguous classic-C array + int fd(mkstemp(&pathtemplate[0])); + if (fd == -1) { - // Append an integer name to mPath. It need not be zero-filled, - // but I think it's neater that way. - std::string testname(STRINGIZE(mPath - << std::setw(8) << std::setfill('0') << i - << ext)); - // The key to this whole loop is the _O_CREAT | _O_EXCL bitmask, - // which requests error EEXIST if the file already exists. A - // proper implementation will check atomically, ensuring that - // racing processes will end up with two different filenames. - fd = _open(testname.c_str(), - _O_WRONLY | _O_CREAT | _O_EXCL, - _S_IREAD | _S_IWRITE); - if (fd != -1) // managed to open the file + // The documented errno values (http://linux.die.net/man/3/mkstemp) + // are used in a somewhat unusual way, so provide context-specific + // errors. + if (errno == EEXIST) + { + LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath + << "\") could not create unique file " << LL_ENDL; + } + if (errno == EINVAL) { - mPath = testname; // remember its actual name - break; + LL_ERRS("NamedTempFile") << "bad mkstemp() file path template '" + << mPath << "'" << LL_ENDL; } - // This loop is specifically coded to handle EEXIST. Any other - // error is a problem. - llassert_always(errno == EEXIST); - // loop back to try another filename + // Shrug, something else + int mkst_errno = errno; + char buffer[256]; + LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath << "\") failed: " + << message_from(mkst_errno, buffer, + strerror_r(mkst_errno, buffer, sizeof(buffer))) + << LL_ENDL; } - // fd is open, its name is in mPath: write it and close it. + // mkstemp() seems to have worked! Capture the modified filename. + // Avoid the nul byte we appended. + mPath.assign(pathtemplate.begin(), (pathtemplate.end()-1)); /*==========================================================================*| // Define an ostream on the open fd. Tell it to close fd on destruction. boost::iostreams::stream out(fd, boost::iostreams::close_handle); |*==========================================================================*/ + + // Write desired content. std::ostringstream out; // Stream stuff to it. func(out); - // toss in a final newline for good measure - out << '\n'; std::string data(out.str()); int written(_write(fd, data.c_str(), data.length())); int closed(_close(fd)); llassert_always(written == data.length() && closed == 0); + +#else // LL_WINDOWS + // GetTempFileName() is documented to require a MAX_PATH buffer. + char tempname[MAX_PATH]; + // Use 'ext' as filename prefix, but skip leading '.' if any. + // The 0 param is very important: requests iterating until we get a + // unique name. + if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname)) + { + // I always have to look up this call... :-P + LPVOID msgptr; + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + LPTSTR(&lpMsgBuf), + 0, NULL ); + LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \"" + << (ext.c_str() + pfx_offset) << "\") failed: " + << msgptr << LL_ENDL; + LocalFree(msgptr); + } + // GetTempFileName() appears to have worked! Capture the actual + // filename. + mPath = tempname; + // Open the file and stream content to it. Destructor will close. + std::ofstream out(tempname); + func(out); + +#endif // LL_WINDOWS } void peep() @@ -1674,14 +1756,13 @@ namespace tut struct TestPythonCompatible { TestPythonCompatible(): - // Note the peculiar insertion of __FILE__ into this string. - // Normally I like to make a Python script navigate relative to - // its own placement in the repo directory tree (__file__) -- but - // in this case, the script is being written into a platform- - // dependent temp directory! So locate indra/lib/python relative - // to this C++ source file rather than the Python module. - // Use Python raw-string syntax so Windows pathname backslashes - // won't mislead Python's string scanner. + // Note the peculiar insertion of __FILE__ into this string. Since + // this script is being written into a platform-dependent temp + // directory, we can't locate indra/lib/python relative to + // Python's __file__. Use __FILE__ instead, navigating relative + // to this C++ source file. Use Python raw-string syntax so + // Windows pathname backslashes won't mislead Python's string + // scanner. import_llsd("import os.path\n" "import sys\n" "sys.path.insert(0,\n" @@ -1708,7 +1789,7 @@ namespace tut std::string q("\""); std::string qPYTHON(q + PYTHON + q); std::string qscript(q + scriptfile.getName() + q); - int rc(_spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL)); + int rc = _spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL); if (rc == -1) { char buffer[256]; @@ -1768,7 +1849,7 @@ namespace tut set_test_name("verify python()"); python("hello", "import sys\n" - "sys.exit(17)", + "sys.exit(17)\n", 17); // expect nonzero rc } @@ -1778,7 +1859,7 @@ namespace tut set_test_name("verify NamedTempFile"); python("platform", "import sys\n" - "print 'Running on', sys.platform"); + "print 'Running on', sys.platform\n"); } template<> template<> @@ -1811,14 +1892,20 @@ namespace tut // notation. It's important to separate with newlines because Python's // llsd module doesn't support parsing from a file stream, only from a // string, so we have to know how much of the file to read into a - // string. Avoid final newline because NamedTempFile implicitly adds - // one. + // string. NamedTempFile file(".llsd", + // NamedTempFile's boost::function constructor + // takes a callable. To this callable it passes the + // std::ostream with which it's writing the + // NamedTempFile. This lambda-based expression + // first calls LLSD::Serialize() with that ostream, + // then streams a newline to it, etc. (lambda::bind(LLSDSerialize::toNotation, cdata[0], lambda::_1), lambda::_1 << '\n', lambda::bind(LLSDSerialize::toNotation, cdata[1], lambda::_1), lambda::_1 << '\n', - lambda::bind(LLSDSerialize::toNotation, cdata[2], lambda::_1))); + lambda::bind(LLSDSerialize::toNotation, cdata[2], lambda::_1), + lambda::_1 << '\n')); python("read C++ notation", lambda::_1 << -- cgit v1.2.3 From 5379467ccb2861d2dbcbc8a13f860d9448bd2fb0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 19 Jul 2011 10:18:12 -0400 Subject: Fix copy/paste error in swiped FormatMessage() example code. --- indra/llcommon/tests/llsdserialize_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 1fe3dc13c0..f2a7530f10 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -282,7 +282,7 @@ private: NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - LPTSTR(&lpMsgBuf), + LPTSTR(&msgptr), 0, NULL ); LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \"" << (ext.c_str() + pfx_offset) << "\") failed: " -- cgit v1.2.3 From 25ababd7a6e7a1ab7222b760b7bcc8dde3e6b829 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 19 Jul 2011 10:40:02 -0400 Subject: More FormatMessage compile errors, try again to fix --- indra/llcommon/tests/llsdserialize_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index f2a7530f10..72322c3b72 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -274,7 +274,7 @@ private: if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname)) { // I always have to look up this call... :-P - LPVOID msgptr; + LPSTR msgptr; FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | @@ -282,7 +282,7 @@ private: NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - LPTSTR(&msgptr), + LPSTR(&msgptr), // have to cast (char**) to (char*) 0, NULL ); LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \"" << (ext.c_str() + pfx_offset) << "\") failed: " -- cgit v1.2.3