From c634daef6a9de8df7245d4fb6fbbcdf0ad7ac7ff Mon Sep 17 00:00:00 2001 From: Tim Mayberry Date: Mon, 3 Aug 2015 12:32:31 +1000 Subject: [PATCH] Add locale independent and thread safe string conversion API with tests All conversions are performed as if in the "C" locale but without actually changing locale. This is a wrapper around printf/sscanf for int types which aren't affected by locale and uses glib functions g_ascii_strtod and g_ascii_dtostr for float/double types. My first attempt at this used std::stringstream and ios::imbue(std::locale::classic()) as it should be thread safe, but testing shows it is not for gcc/mingw-w64 on Windows, and possibly also some versions of macOS/OS X. Use "yes" and "no" when converting a boolean in PBD::string_to as this seems to be the convention used throughout libardour which will allow using string_to in those cases. Add accepted bool string values from PBD::string_is_affirmative to PBD::string_to Mark strings in pbd/string_convert.cc as not for translation Add u/int16_t string conversions to pbd/string_convert.h and tests Add DEBUG_TRACE output on conversion errors Add int8_t/uint8_t conversions(using int16/uint16 types) to string_convert.h Add support for converting an infinity expression to/from string Follows the C99/C11 standard for strtof/strtod where subject sequence is an optional plus or minus sign then INF or INFINITY, ignoring case. --- libs/pbd/pbd/string_convert.h | 430 ++++++++++++++++++ libs/pbd/string_convert.cc | 366 +++++++++++++++ libs/pbd/test/string_convert_test.cc | 651 +++++++++++++++++++++++++++ libs/pbd/test/string_convert_test.h | 30 ++ libs/pbd/wscript | 2 + 5 files changed, 1479 insertions(+) create mode 100644 libs/pbd/pbd/string_convert.h create mode 100644 libs/pbd/string_convert.cc create mode 100644 libs/pbd/test/string_convert_test.cc create mode 100644 libs/pbd/test/string_convert_test.h diff --git a/libs/pbd/pbd/string_convert.h b/libs/pbd/pbd/string_convert.h new file mode 100644 index 0000000000..3194c48863 --- /dev/null +++ b/libs/pbd/pbd/string_convert.h @@ -0,0 +1,430 @@ +/* + Copyright (C) 2015 Tim Mayberry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef PBD_STRING_CONVERT_H +#define PBD_STRING_CONVERT_H + +#include +#include + +#include "pbd/libpbd_visibility.h" + +/** + * Locale independent and thread-safe string conversion utility functions. + * + * All conversions are done as if they were performed in the C locale without + * actually changing the current locale. + */ +namespace PBD { + +LIBPBD_API bool bool_to_string (bool val, std::string& str); + +LIBPBD_API bool int16_to_string (int16_t val, std::string& str); + +LIBPBD_API bool uint16_to_string (uint16_t val, std::string& str); + +LIBPBD_API bool int32_to_string (int32_t val, std::string& str); + +LIBPBD_API bool uint32_to_string (uint32_t val, std::string& str); + +LIBPBD_API bool int64_to_string (int64_t val, std::string& str); + +LIBPBD_API bool uint64_to_string (uint64_t val, std::string& str); + +LIBPBD_API bool float_to_string (float val, std::string& str); + +LIBPBD_API bool double_to_string (double val, std::string& str); + +LIBPBD_API bool string_to_bool (const std::string& str, bool& val); + +LIBPBD_API bool string_to_int16 (const std::string& str, int16_t& val); + +LIBPBD_API bool string_to_uint16 (const std::string& str, uint16_t& val); + +LIBPBD_API bool string_to_int32 (const std::string& str, int32_t& val); + +LIBPBD_API bool string_to_uint32 (const std::string& str, uint32_t& val); + +LIBPBD_API bool string_to_int64 (const std::string& str, int64_t& val); + +LIBPBD_API bool string_to_uint64 (const std::string& str, uint64_t& val); + +LIBPBD_API bool string_to_float (const std::string& str, float& val); + +LIBPBD_API bool string_to_double (const std::string& str, double& val); + +template +inline bool to_string (T val, std::string& str) +{ + // This will cause a compile time error if this function is ever + // instantiated, which is useful to catch unintended conversions + typename T::TO_STRING_TEMPLATE_NOT_DEFINED_FOR_THIS_TYPE invalid_type; + return false; +} + +template +inline bool to_string (bool val, std::string& str) +{ + return bool_to_string (val, str); +} + +template +inline bool to_string (int8_t val, std::string& str) +{ + return int16_to_string (val, str); +} + +template +inline bool to_string (uint8_t val, std::string& str) +{ + return uint16_to_string (val, str); +} + +template +inline bool to_string (int16_t val, std::string& str) +{ + return int16_to_string (val, str); +} + +template +inline bool to_string (uint16_t val, std::string& str) +{ + return uint16_to_string (val, str); +} + +template +inline bool to_string (int32_t val, std::string& str) +{ + return int32_to_string (val, str); +} + +template +inline bool to_string (uint32_t val, std::string& str) +{ + return uint32_to_string (val, str); +} + +template +inline bool to_string (int64_t val, std::string& str) +{ + return int64_to_string (val, str); +} + +template +inline bool to_string (uint64_t val, std::string& str) +{ + return uint64_to_string (val, str); +} + +template +inline bool to_string (float val, std::string& str) +{ + return float_to_string (val, str); +} + +template +inline bool to_string (double val, std::string& str) +{ + return double_to_string (val, str); +} + +template +inline bool string_to (const std::string& str, T& val) +{ + // This will cause a compile time error if this function is ever + // instantiated, which is useful to catch unintended conversions + typename T::TO_STRING_TEMPLATE_NOT_DEFINED_FOR_THIS_TYPE invalid_type; + return false; +} + +template +inline bool string_to (const std::string& str, bool& val) +{ + return string_to_bool (str, val); +} + +template +inline bool string_to (const std::string& str, int8_t& val) +{ + int16_t tmp = val; + bool success = string_to_int16 (str, tmp); + if (!success) return false; + val = tmp; + return true; +} + +template +inline bool string_to (const std::string& str, uint8_t& val) +{ + uint16_t tmp = val; + bool success = string_to_uint16 (str, tmp); + if (!success) return false; + val = tmp; + return true; +} + +template +inline bool string_to (const std::string& str, int16_t& val) +{ + return string_to_int16 (str, val); +} + +template +inline bool string_to (const std::string& str, uint16_t& val) +{ + return string_to_uint16 (str, val); +} + +template +inline bool string_to (const std::string& str, int32_t& val) +{ + return string_to_int32 (str, val); +} + +template +inline bool string_to (const std::string& str, uint32_t& val) +{ + return string_to_uint32 (str, val); +} + +template +inline bool string_to (const std::string& str, int64_t& val) +{ + return string_to_int64 (str, val); +} + +template +inline bool string_to (const std::string& str, uint64_t& val) +{ + return string_to_uint64 (str, val); +} + +template +inline bool string_to (const std::string& str, float& val) +{ + return string_to_float (str, val); +} + +template +inline bool string_to (const std::string& str, double& val) +{ + return string_to_double (str, val); +} + +//////////////////////////////////////////////////////////////// +// Variation that disregards conversion errors +//////////////////////////////////////////////////////////////// + +template +inline std::string to_string (T val) +{ + // This will cause a compile time error if this function is ever + // instantiated, which is useful to catch unintended conversions + typename T::TO_STRING_TEMPLATE_NOT_DEFINED_FOR_THIS_TYPE invalid_type; + return std::string(); +} + +template <> +inline std::string to_string (bool val) +{ + std::string tmp; + bool_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (int8_t val) +{ + std::string tmp; + int16_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (uint8_t val) +{ + std::string tmp; + uint16_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (int16_t val) +{ + std::string tmp; + int16_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (uint16_t val) +{ + std::string tmp; + uint16_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (int32_t val) +{ + std::string tmp; + int32_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (uint32_t val) +{ + std::string tmp; + uint32_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (int64_t val) +{ + std::string tmp; + int64_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (uint64_t val) +{ + std::string tmp; + uint64_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (float val) +{ + std::string tmp; + float_to_string (val, tmp); + return tmp; +} + +template <> +inline std::string to_string (double val) +{ + std::string tmp; + double_to_string (val, tmp); + return tmp; +} + +template +inline T string_to (const std::string& str) +{ + // This will cause a compile time error if this function is ever + // instantiated, which is useful to catch unintended conversions + typename T::STRING_TO_TEMPLATE_NOT_DEFINED_FOR_THIS_TYPE invalid_type; + return T(); +} + +template <> +inline bool string_to (const std::string& str) +{ + bool tmp; + string_to_bool (str, tmp); + return tmp; +} + +template <> +inline int8_t string_to (const std::string& str) +{ + int16_t tmp; + string_to_int16 (str, tmp); + return tmp; +} + +template <> +inline uint8_t string_to (const std::string& str) +{ + uint16_t tmp; + string_to_uint16 (str, tmp); + return tmp; +} + +template <> +inline int16_t string_to (const std::string& str) +{ + int16_t tmp; + string_to_int16 (str, tmp); + return tmp; +} + +template <> +inline uint16_t string_to (const std::string& str) +{ + uint16_t tmp; + string_to_uint16 (str, tmp); + return tmp; +} + +template <> +inline int32_t string_to (const std::string& str) +{ + int32_t tmp; + string_to_int32 (str, tmp); + return tmp; +} + +template <> +inline uint32_t string_to (const std::string& str) +{ + uint32_t tmp; + string_to_uint32 (str, tmp); + return tmp; +} + +template <> +inline int64_t string_to (const std::string& str) +{ + int64_t tmp; + string_to_int64 (str, tmp); + return tmp; +} + +template <> +inline uint64_t string_to (const std::string& str) +{ + uint64_t tmp; + string_to_uint64 (str, tmp); + return tmp; +} + +template <> +inline float string_to (const std::string& str) +{ + float tmp; + string_to_float (str, tmp); + return tmp; +} + +template <> +inline double string_to (const std::string& str) +{ + double tmp; + string_to_double (str, tmp); + return tmp; +} + +} // namespace PBD + +#endif // PBD_STRING_CONVERT_H diff --git a/libs/pbd/string_convert.cc b/libs/pbd/string_convert.cc new file mode 100644 index 0000000000..3a46b8a682 --- /dev/null +++ b/libs/pbd/string_convert.cc @@ -0,0 +1,366 @@ +/* + Copyright (C) 2015 Tim Mayberry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "pbd/string_convert.h" + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#include +#include +#include + +#include +#include + +#include "pbd/compose.h" +#include "pbd/debug.h" +#include "pbd/i18n.h" + +#define DEBUG_SCONVERT(msg) DEBUG_TRACE (PBD::DEBUG::StringConvert, string_compose ("%1: %2\n", __LINE__, msg)); + +#define CONVERT_BUF_SIZE 32 + +namespace PBD { + +bool string_to_bool (const std::string& str, bool& val) +{ + if (str.empty ()) { + return false; + + } else if (str == X_("1")) { + val = true; + return true; + + } else if (str == X_("0")) { + val = false; + return true; + + } else if (str == X_("y")) { + val = true; + return true; + + } else if (str == X_("n")) { + val = false; + return true; + + } else if (g_ascii_strncasecmp (str.c_str(), X_("yes"), str.length()) == 0) { + val = true; + return true; + + } else if (g_ascii_strncasecmp (str.c_str(), X_("no"), str.length()) == 0) { + val = false; + return true; + + } else if (g_ascii_strncasecmp (str.c_str(), X_("true"), str.length()) == 0) { + val = true; + return true; + + } else if (g_ascii_strncasecmp (str.c_str(), X_("false"), str.length()) == 0) { + val = false; + return true; + } + + DEBUG_SCONVERT ( + string_compose ("string_to_bool conversion failed for %1", str)); + + return false; +} + +bool string_to_int16 (const std::string& str, int16_t& val) +{ + if (sscanf (str.c_str (), "%" SCNi16, &val) != 1) { + DEBUG_SCONVERT ( + string_compose ("string_to_int16 conversion failed for %1", str)); + return false; + } + return true; +} + +bool string_to_uint16 (const std::string& str, uint16_t& val) +{ + if (sscanf (str.c_str (), "%" SCNu16, &val) != 1) { + DEBUG_SCONVERT ( + string_compose ("string_to_uint16 conversion failed for %1", str)); + return false; + } + return true; +} + +bool string_to_int32 (const std::string& str, int32_t& val) +{ + if (sscanf (str.c_str (), "%" SCNi32, &val) != 1) { + DEBUG_SCONVERT ( + string_compose ("string_to_int32 conversion failed for %1", str)); + return false; + } + return true; +} + +bool string_to_uint32 (const std::string& str, uint32_t& val) +{ + if (sscanf (str.c_str (), "%" SCNu32, &val) != 1) { + DEBUG_SCONVERT ( + string_compose ("string_to_uint32 conversion failed for %1", str)); + return false; + } + return true; +} + +bool string_to_int64 (const std::string& str, int64_t& val) +{ + if (sscanf (str.c_str (), "%" SCNi64, &val) != 1) { + DEBUG_SCONVERT ( + string_compose ("string_to_int64 conversion failed for %1", str)); + return false; + } + return true; +} + +bool string_to_uint64 (const std::string& str, uint64_t& val) +{ + if (sscanf (str.c_str (), "%" SCNu64, &val) != 1) { + DEBUG_SCONVERT ( + string_compose ("string_to_uint64 conversion failed for %1", str)); + return false; + } + return true; +} + +template +static bool +_string_to_infinity (const std::string& str, FloatType& val) +{ + if (!g_ascii_strncasecmp (str.c_str (), X_ ("inf"), str.length ()) || + !g_ascii_strncasecmp (str.c_str (), X_ ("+inf"), str.length ()) || + !g_ascii_strncasecmp (str.c_str (), X_ ("INFINITY"), str.length ()) || + !g_ascii_strncasecmp (str.c_str (), X_ ("+INFINITY"), str.length ())) { + val = std::numeric_limits::infinity (); + return true; + } else if (!g_ascii_strncasecmp (str.c_str (), X_ ("-inf"), str.length ()) || + !g_ascii_strncasecmp (str.c_str (), X_ ("-INFINITY"), str.length ())) { + val = -std::numeric_limits::infinity (); + return true; + } + return false; +} + +bool +_string_to_double (const std::string& str, double& val) +{ + val = g_ascii_strtod (str.c_str (), NULL); + + // It is possible that the conversion was successful and another thread + // has set errno meanwhile but as most conversions are currently not + // checked for error conditions this is better than nothing. + if (errno == ERANGE) { + DEBUG_SCONVERT (string_compose ("string_to_double possible conversion failure for %1", str)); + // There should not be any conversion failures as we control the string + // contents so returning false here should not have any impact... + return false; + } + return true; +} + +bool string_to_float (const std::string& str, float& val) +{ + double tmp; + if (_string_to_double (str, tmp)) { + val = (float)tmp; + return true; + } + + if (_string_to_infinity (str, val)) { + return true; + } + + return false; +} + +bool string_to_double (const std::string& str, double& val) +{ + if (_string_to_double (str, val)) { + return true; + } + + if (_string_to_infinity (str, val)) { + return true; + } + + return false; +} + +bool bool_to_string (bool val, std::string& str) +{ + if (val) { + str = X_("yes"); + } else { + str = X_("no"); + } + return true; +} + +bool int16_to_string (int16_t val, std::string& str) +{ + char buffer[CONVERT_BUF_SIZE]; + + int retval = g_snprintf (buffer, sizeof(buffer), "%" PRIi16, val); + + if (retval <= 0 || retval >= (int)sizeof(buffer)) { + DEBUG_SCONVERT ( + string_compose ("int16_to_string conversion failure for %1", val)); + return false; + } + str = buffer; + return true; +} + +bool uint16_to_string (uint16_t val, std::string& str) +{ + char buffer[CONVERT_BUF_SIZE]; + + int retval = g_snprintf (buffer, sizeof(buffer), "%" PRIu16, val); + + if (retval <= 0 || retval >= (int)sizeof(buffer)) { + DEBUG_SCONVERT ( + string_compose ("uint16_to_string conversion failure for %1", val)); + return false; + } + str = buffer; + return true; +} + +bool int32_to_string (int32_t val, std::string& str) +{ + char buffer[CONVERT_BUF_SIZE]; + + int retval = g_snprintf (buffer, sizeof(buffer), "%" PRIi32, val); + + if (retval <= 0 || retval >= (int)sizeof(buffer)) { + DEBUG_SCONVERT ( + string_compose ("int32_to_string conversion failure for %1", val)); + return false; + } + str = buffer; + return true; +} + +bool uint32_to_string (uint32_t val, std::string& str) +{ + char buffer[CONVERT_BUF_SIZE]; + + int retval = g_snprintf (buffer, sizeof(buffer), "%" PRIu32, val); + + if (retval <= 0 || retval >= (int)sizeof(buffer)) { + DEBUG_SCONVERT ( + string_compose ("uint32_to_string conversion failure for %1", val)); + return false; + } + str = buffer; + return true; +} + +bool int64_to_string (int64_t val, std::string& str) +{ + char buffer[CONVERT_BUF_SIZE]; + + int retval = g_snprintf (buffer, sizeof(buffer), "%" PRIi64, val); + + if (retval <= 0 || retval >= (int)sizeof(buffer)) { + DEBUG_SCONVERT ( + string_compose ("int64_to_string conversion failure for %1", val)); + return false; + } + str = buffer; + return true; +} + +bool uint64_to_string (uint64_t val, std::string& str) +{ + char buffer[CONVERT_BUF_SIZE]; + + int retval = g_snprintf (buffer, sizeof(buffer), "%" PRIu64, val); + + if (retval <= 0 || retval >= (int)sizeof(buffer)) { + DEBUG_SCONVERT ( + string_compose ("uint64_to_string conversion failure for %1", val)); + return false; + } + str = buffer; + return true; +} + +template +static bool +_infinity_to_string (FloatType val, std::string& str) +{ + if (val == std::numeric_limits::infinity ()) { + str = "inf"; + return true; + } else if (val == -std::numeric_limits::infinity ()) { + str = "-inf"; + return true; + } + return false; +} + +static bool +_double_to_string (double val, std::string& str) +{ + char buffer[G_ASCII_DTOSTR_BUF_SIZE]; + + char* d_cstr = g_ascii_dtostr (buffer, sizeof(buffer), val); + + if (d_cstr == NULL) { + return false; + } + str = d_cstr; + return true; +} + +bool float_to_string (float val, std::string& str) +{ + if (_infinity_to_string (val, str)) { + return true; + } + + if (_double_to_string (val, str)) { + return true; + } + + DEBUG_SCONVERT (string_compose ("float_to_string conversion failure for %1", val)); + return false; +} + +bool double_to_string (double val, std::string& str) +{ + if (_infinity_to_string (val, str)) { + return true; + } + + if (_double_to_string (val, str)) { + return true; + } + + DEBUG_SCONVERT (string_compose ("double_to_string conversion failure for %1", val)); + return false; +} + +} // namespace PBD diff --git a/libs/pbd/test/string_convert_test.cc b/libs/pbd/test/string_convert_test.cc new file mode 100644 index 0000000000..f383aed8a2 --- /dev/null +++ b/libs/pbd/test/string_convert_test.cc @@ -0,0 +1,651 @@ +#include "string_convert_test.h" + +#include +#include + +#include +#include + +#include + +#include + +#include "pbd/string_convert.h" + +using namespace PBD; +using namespace std; + +CPPUNIT_TEST_SUITE_REGISTRATION (StringConvertTest); + +static std::vector get_test_locales () +{ + std::vector locales; + +#ifdef PLATFORM_WINDOWS + locales.push_back("French_France.1252"); // must be first + locales.push_back("Dutch_Netherlands.1252"); + locales.push_back("Italian_Italy.1252"); + locales.push_back("Farsi_Iran.1256"); + locales.push_back("Chinese_China.936"); + locales.push_back("Czech_Czech Republic.1250"); +#else + locales.push_back("fr_FR"); // French France // must be first + locales.push_back("nl_NL"); // Dutch - Netherlands + locales.push_back("it_IT"); // Italian + locales.push_back("fa_IR"); // Farsi Iran + locales.push_back("zh_CN"); // Chinese + locales.push_back("cs_CZ"); // Czech +#endif + return locales; +} + + +namespace { + +class LocaleGuard { +public: + // RAII class that sets the global C locale and then resets it to its + // previous setting when going out of scope + LocaleGuard (const std::string& locale) + { + m_previous_locale = setlocale (LC_ALL, NULL); + + CPPUNIT_ASSERT (m_previous_locale != NULL); + + const char* new_locale = setlocale (LC_ALL, locale.c_str ()); + + CPPUNIT_ASSERT (new_locale != NULL); + + CPPUNIT_ASSERT (locale == new_locale); + } + + ~LocaleGuard () + { + CPPUNIT_ASSERT (setlocale (LC_ALL, m_previous_locale) != NULL); + } + +private: + const char* m_previous_locale; +}; + +} // anon namespace + +static const std::string MAX_INT16_STR ("32767"); +static const std::string MIN_INT16_STR ("-32768"); + +typedef void (*TestFunctionType)(void); + +void +test_function_for_locales (TestFunctionType test_func) +{ + const std::vector locales = get_test_locales(); + + for (std::vector::const_iterator ci = locales.begin (); + ci != locales.end (); + ++ci) { + LocaleGuard guard (*ci); + test_func (); + } +} + +void +_test_int16_conversion () +{ + string str; + CPPUNIT_ASSERT (int16_to_string (numeric_limits::max (), str)); + CPPUNIT_ASSERT_EQUAL (MAX_INT16_STR, str); + + int16_t val = 0; + CPPUNIT_ASSERT (string_to_int16 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::max (), val); + + CPPUNIT_ASSERT (int16_to_string (numeric_limits::min (), str)); + CPPUNIT_ASSERT_EQUAL (MIN_INT16_STR, str); + + CPPUNIT_ASSERT (string_to_int16 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::min (), val); + + // test the string_to/to_string templates + int16_t max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + int16_t min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); +} + +void +StringConvertTest::test_int16_conversion () +{ + test_function_for_locales(&_test_int16_conversion); +} + +static const std::string MAX_UINT16_STR("65535"); +static const std::string MIN_UINT16_STR("0"); + +void +_test_uint16_conversion () +{ + string str; + CPPUNIT_ASSERT (uint16_to_string (numeric_limits::max (), str)); + CPPUNIT_ASSERT_EQUAL (MAX_UINT16_STR, str); + + uint16_t val = 0; + CPPUNIT_ASSERT (string_to_uint16 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::max (), val); + + CPPUNIT_ASSERT (uint16_to_string (numeric_limits::min (), str)); + CPPUNIT_ASSERT_EQUAL (MIN_UINT16_STR, str); + + CPPUNIT_ASSERT (string_to_uint16 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::min (), val); + + // test the string_to/to_string templates + uint16_t max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + uint16_t min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); +} + +void +StringConvertTest::test_uint16_conversion () +{ + test_function_for_locales(&_test_uint16_conversion); +} + +static const std::string MAX_INT32_STR ("2147483647"); +static const std::string MIN_INT32_STR ("-2147483648"); + +void +_test_int32_conversion () +{ + string str; + CPPUNIT_ASSERT (int32_to_string (numeric_limits::max (), str)); + CPPUNIT_ASSERT_EQUAL (MAX_INT32_STR, str); + + int32_t val = 0; + CPPUNIT_ASSERT (string_to_int32 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::max (), val); + + CPPUNIT_ASSERT (int32_to_string (numeric_limits::min (), str)); + CPPUNIT_ASSERT_EQUAL (MIN_INT32_STR, str); + + CPPUNIT_ASSERT (string_to_int32 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::min (), val); + + // test the string_to/to_string templates + int32_t max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + int32_t min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); +} + +void +StringConvertTest::test_int32_conversion () +{ + test_function_for_locales(&_test_int32_conversion); +} + +static const std::string MAX_UINT32_STR("4294967295"); +static const std::string MIN_UINT32_STR("0"); + +void +_test_uint32_conversion () +{ + string str; + CPPUNIT_ASSERT (uint32_to_string (numeric_limits::max (), str)); + CPPUNIT_ASSERT_EQUAL (MAX_UINT32_STR, str); + + uint32_t val = 0; + CPPUNIT_ASSERT (string_to_uint32 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::max (), val); + + CPPUNIT_ASSERT (uint32_to_string (numeric_limits::min (), str)); + CPPUNIT_ASSERT_EQUAL (MIN_UINT32_STR, str); + + CPPUNIT_ASSERT (string_to_uint32 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::min (), val); + + // test the string_to/to_string templates + uint32_t max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + uint32_t min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); +} + +void +StringConvertTest::test_uint32_conversion () +{ + test_function_for_locales(&_test_uint32_conversion); +} + +static const std::string MAX_INT64_STR ("9223372036854775807"); +static const std::string MIN_INT64_STR ("-9223372036854775808"); + +void +_test_int64_conversion () +{ + string str; + CPPUNIT_ASSERT (int64_to_string (numeric_limits::max (), str)); + CPPUNIT_ASSERT_EQUAL (MAX_INT64_STR, str); + + int64_t val = 0; + CPPUNIT_ASSERT (string_to_int64 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::max (), val); + + CPPUNIT_ASSERT (int64_to_string (numeric_limits::min (), str)); + CPPUNIT_ASSERT_EQUAL (MIN_INT64_STR, str); + + CPPUNIT_ASSERT (string_to_int64 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::min (), val); + + // test the string_to/to_string templates + int64_t max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + int64_t min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); +} + +void +StringConvertTest::test_int64_conversion () +{ + test_function_for_locales(&_test_int64_conversion); +} + +static const std::string MAX_UINT64_STR ("18446744073709551615"); +static const std::string MIN_UINT64_STR ("0"); + +void +_test_uint64_conversion () +{ + string str; + CPPUNIT_ASSERT (uint64_to_string (numeric_limits::max (), str)); + CPPUNIT_ASSERT_EQUAL (MAX_UINT64_STR, str); + + uint64_t val = 0; + CPPUNIT_ASSERT (string_to_uint64 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::max (), val); + + CPPUNIT_ASSERT (uint64_to_string (numeric_limits::min (), str)); + CPPUNIT_ASSERT_EQUAL (MIN_UINT64_STR, str); + + CPPUNIT_ASSERT (string_to_uint64 (str, val)); + CPPUNIT_ASSERT_EQUAL (numeric_limits::min (), val); + + // test the string_to/to_string templates + uint64_t max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + uint64_t min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); +} + +void +StringConvertTest::test_uint64_conversion () +{ + test_function_for_locales(&_test_uint64_conversion); +} + +static const std::string POS_INFINITY_STR ("infinity"); +static const std::string NEG_INFINITY_STR ("-infinity"); +static const std::string POS_INFINITY_CAPS_STR ("INFINITY"); +static const std::string NEG_INFINITY_CAPS_STR ("-INFINITY"); +static const std::string POS_INF_STR ("inf"); +static const std::string NEG_INF_STR ("-inf"); +static const std::string POS_INF_CAPS_STR ("INF"); +static const std::string NEG_INF_CAPS_STR ("-INF"); + +static +std::vector +_pos_infinity_strings () +{ + std::vector vec; + vec.push_back (POS_INFINITY_STR); + vec.push_back (POS_INFINITY_CAPS_STR); + vec.push_back (POS_INF_STR); + vec.push_back (POS_INF_CAPS_STR); + return vec; +} + +static +std::vector +_neg_infinity_strings () +{ + std::vector vec; + vec.push_back (NEG_INFINITY_STR); + vec.push_back (NEG_INFINITY_CAPS_STR); + vec.push_back (NEG_INF_STR); + vec.push_back (NEG_INF_CAPS_STR); + return vec; +} + +template +void +_test_infinity_conversion () +{ + const FloatType pos_infinity = numeric_limits::infinity (); + const FloatType neg_infinity = -numeric_limits::infinity (); + + // Check float -> string + string str; + CPPUNIT_ASSERT (to_string (pos_infinity, str)); + CPPUNIT_ASSERT_EQUAL (POS_INF_STR, str); + + CPPUNIT_ASSERT (to_string (neg_infinity, str)); + CPPUNIT_ASSERT_EQUAL (NEG_INF_STR, str); + + // Check string -> float for all supported string representations of "INFINITY" + std::vector pos_inf_strings = _pos_infinity_strings (); + + for (std::vector::const_iterator i = pos_inf_strings.begin (); + i != pos_inf_strings.end (); ++i) { + FloatType pos_infinity_arg; + CPPUNIT_ASSERT (string_to (*i, pos_infinity_arg)); + CPPUNIT_ASSERT_EQUAL (pos_infinity_arg, pos_infinity); + } + + // Check string -> float for all supported string representations of "-INFINITY" + std::vector neg_inf_strings = _neg_infinity_strings (); + + for (std::vector::const_iterator i = neg_inf_strings.begin (); + i != neg_inf_strings.end (); ++i) { + FloatType neg_infinity_arg; + CPPUNIT_ASSERT (string_to (*i, neg_infinity_arg)); + CPPUNIT_ASSERT_EQUAL (neg_infinity_arg, neg_infinity); + } + + // Check round-trip equality + CPPUNIT_ASSERT_EQUAL (pos_infinity, string_to (to_string (pos_infinity))); + CPPUNIT_ASSERT_EQUAL (neg_infinity, string_to (to_string (neg_infinity))); +} + +static const std::string MAX_FLOAT_WIN ("3.4028234663852886e+038"); +static const std::string MIN_FLOAT_WIN ("1.1754943508222875e-038"); +static const std::string MAX_FLOAT_STR ("3.4028234663852886e+38"); +static const std::string MIN_FLOAT_STR ("1.1754943508222875e-38"); + +void +_test_float_conversion () +{ + // check float to string and back again for min and max float values + string str; + CPPUNIT_ASSERT (float_to_string (numeric_limits::max (), str)); +#ifdef PLATFORM_WINDOWS + CPPUNIT_ASSERT_EQUAL (MAX_FLOAT_WIN, str); +#else + CPPUNIT_ASSERT_EQUAL (MAX_FLOAT_STR, str); +#endif + + float val = 0.0f; + CPPUNIT_ASSERT (string_to_float (str, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::max (), val, numeric_limits::epsilon ()); + + CPPUNIT_ASSERT (float_to_string (numeric_limits::min (), str)); +#ifdef PLATFORM_WINDOWS + CPPUNIT_ASSERT_EQUAL (MIN_FLOAT_WIN, str); +#else + CPPUNIT_ASSERT_EQUAL (MIN_FLOAT_STR, str); +#endif + + CPPUNIT_ASSERT (string_to_float (str, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::min (), val, numeric_limits::epsilon ()); + + // test the string_to/to_string templates + float max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + float min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); + +// check that parsing the windows float string representation with the +// difference in the exponent part parses correctly on other platforms +// and vice versa +#ifdef PLATFORM_WINDOWS + CPPUNIT_ASSERT (string_to_float (MAX_FLOAT_STR, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::max (), val, numeric_limits::epsilon ()); + + CPPUNIT_ASSERT (string_to_float (MIN_FLOAT_STR, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::min (), val, numeric_limits::epsilon ()); +#else + CPPUNIT_ASSERT (string_to_float (MAX_FLOAT_WIN, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::max (), val, numeric_limits::epsilon ()); + + CPPUNIT_ASSERT (string_to_float (MIN_FLOAT_WIN, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::min (), val, numeric_limits::epsilon ()); +#endif + + _test_infinity_conversion(); +} + +void +StringConvertTest::test_float_conversion () +{ + test_function_for_locales(&_test_float_conversion); +} + +static const std::string MAX_DOUBLE_STR ("1.7976931348623157e+308"); +static const std::string MIN_DOUBLE_STR ("2.2250738585072014e-308"); + +void +_test_double_conversion () +{ + string str; + CPPUNIT_ASSERT (double_to_string (numeric_limits::max (), str)); + CPPUNIT_ASSERT_EQUAL (MAX_DOUBLE_STR, str); + + double val = 0.0; + CPPUNIT_ASSERT (string_to_double (str, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::max (), val, numeric_limits::epsilon ()); + + CPPUNIT_ASSERT (double_to_string (numeric_limits::min (), str)); + CPPUNIT_ASSERT_EQUAL (MIN_DOUBLE_STR, str); + + CPPUNIT_ASSERT (string_to_double (str, val)); + CPPUNIT_ASSERT_DOUBLES_EQUAL ( + numeric_limits::min (), val, numeric_limits::epsilon ()); + + // test that overflow fails + CPPUNIT_ASSERT (!string_to_double ("1.8e+308", val)); + // test that underflow fails + CPPUNIT_ASSERT (!string_to_double ("2.4e-310", val)); + + // test the string_to/to_string templates + double max = numeric_limits::max (); + CPPUNIT_ASSERT_EQUAL (max, string_to(to_string (max))); + + double min = numeric_limits::min (); + CPPUNIT_ASSERT_EQUAL (min, string_to(to_string (min))); + + _test_infinity_conversion(); +} + +void +StringConvertTest::test_double_conversion () +{ + test_function_for_locales(&_test_double_conversion); +} + +// we have to use these as CPPUNIT_ASSERT_EQUAL won't accept char arrays +static const std::string BOOL_TRUE_STR ("yes"); +static const std::string BOOL_FALSE_STR ("no"); + +void +StringConvertTest::test_bool_conversion () +{ + string str; + + // check the normal case for true/false + CPPUNIT_ASSERT (bool_to_string (true, str)); + CPPUNIT_ASSERT_EQUAL (BOOL_TRUE_STR, str); + + bool val = false; + CPPUNIT_ASSERT (string_to_bool (str, val)); + CPPUNIT_ASSERT_EQUAL (val, true); + + CPPUNIT_ASSERT (bool_to_string (false, str)); + CPPUNIT_ASSERT_EQUAL (BOOL_FALSE_STR, str); + + val = true; + CPPUNIT_ASSERT (string_to_bool (str, val)); + CPPUNIT_ASSERT_EQUAL (val, false); + + // now check the other accepted values for true and false + // when converting from a string to bool + + val = false; + CPPUNIT_ASSERT (string_to_bool ("1", val)); + CPPUNIT_ASSERT_EQUAL (val, true); + + val = true; + CPPUNIT_ASSERT (string_to_bool ("0", val)); + CPPUNIT_ASSERT_EQUAL (val, false); + + val = false; + CPPUNIT_ASSERT (string_to_bool ("Y", val)); + CPPUNIT_ASSERT_EQUAL (val, true); + + val = true; + CPPUNIT_ASSERT (string_to_bool ("N", val)); + CPPUNIT_ASSERT_EQUAL (val, false); + + val = false; + CPPUNIT_ASSERT (string_to_bool ("y", val)); + CPPUNIT_ASSERT_EQUAL (val, true); + + val = true; + CPPUNIT_ASSERT (string_to_bool ("n", val)); + CPPUNIT_ASSERT_EQUAL (val, false); + + // test some junk + CPPUNIT_ASSERT (!string_to_bool ("01234someYNtrueyesno junk0123", val)); + + // test the string_to/to_string templates + CPPUNIT_ASSERT_EQUAL (true, string_to(to_string (true))); + + CPPUNIT_ASSERT_EQUAL (false, string_to(to_string (false))); +} + +namespace { + +bool +check_int_convert () +{ + int32_t num = g_random_int (); + return (num == string_to(to_string (num))); +} + +bool +check_float_convert () +{ + float num = (float)g_random_double (); + return (num == string_to(to_string (num))); +} + +bool +check_double_convert () +{ + double num = g_random_double (); + return (num == string_to(to_string (num))); +} + +static const int s_iter_count = 500000; + +void* +check_int_convert_thread(void*) +{ + for (int n = 0; n < s_iter_count; n++) { + assert (check_int_convert ()); + } + return NULL; +} + +void* +check_float_convert_thread(void*) +{ + for (int n = 0; n < s_iter_count; n++) { + assert (check_float_convert ()); + } + return NULL; +} + +void* +check_double_convert_thread(void*) +{ + for (int n = 0; n < s_iter_count; n++) { + assert (check_double_convert ()); + } + return NULL; +} + +static const double s_test_double = 31459.265359; + +bool +check_fr_printf () +{ + char buf[32]; + snprintf (buf, sizeof(buf), "%.12g", s_test_double); + bool found = (strchr (buf, ',') != NULL); + return found; +} + +void* +check_fr_printf_thread (void*) +{ + for (int n = 0; n < s_iter_count; n++) { + assert (check_fr_printf ()); + } + + return NULL; +} + +} // anon namespace + +// Perform the test in the French locale as the format for decimals is +// different and a comma is used as a decimal point. Test that this has no +// impact on the string conversions which are expected to be the same as in the +// C locale. +void +StringConvertTest::test_convert_thread_safety () +{ + LocaleGuard guard (get_test_locales().front()); + + CPPUNIT_ASSERT (check_int_convert ()); + CPPUNIT_ASSERT (check_float_convert ()); + CPPUNIT_ASSERT (check_double_convert ()); + CPPUNIT_ASSERT (check_fr_printf ()); + + pthread_t convert_int_thread; + pthread_t convert_float_thread; + pthread_t convert_double_thread; + pthread_t fr_printf_thread; + + CPPUNIT_ASSERT ( + pthread_create ( + &convert_int_thread, NULL, check_int_convert_thread, NULL) == 0); + CPPUNIT_ASSERT ( + pthread_create ( + &convert_float_thread, NULL, check_float_convert_thread, NULL) == 0); + CPPUNIT_ASSERT ( + pthread_create ( + &convert_double_thread, NULL, check_double_convert_thread, NULL) == 0); + CPPUNIT_ASSERT ( + pthread_create (&fr_printf_thread, NULL, check_fr_printf_thread, NULL) == + 0); + + void* return_value; + + CPPUNIT_ASSERT (pthread_join (convert_int_thread, &return_value) == 0); + CPPUNIT_ASSERT (pthread_join (convert_float_thread, &return_value) == 0); + CPPUNIT_ASSERT (pthread_join (convert_double_thread, &return_value) == 0); + CPPUNIT_ASSERT (pthread_join (fr_printf_thread, &return_value) == 0); +} diff --git a/libs/pbd/test/string_convert_test.h b/libs/pbd/test/string_convert_test.h new file mode 100644 index 0000000000..82ccb26af3 --- /dev/null +++ b/libs/pbd/test/string_convert_test.h @@ -0,0 +1,30 @@ +#include +#include + +class StringConvertTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE (StringConvertTest); + CPPUNIT_TEST (test_int16_conversion); + CPPUNIT_TEST (test_uint16_conversion); + CPPUNIT_TEST (test_int32_conversion); + CPPUNIT_TEST (test_uint32_conversion); + CPPUNIT_TEST (test_int64_conversion); + CPPUNIT_TEST (test_uint64_conversion); + CPPUNIT_TEST (test_float_conversion); + CPPUNIT_TEST (test_double_conversion); + CPPUNIT_TEST (test_bool_conversion); + CPPUNIT_TEST (test_convert_thread_safety); + CPPUNIT_TEST_SUITE_END (); + +public: + void test_int16_conversion (); + void test_uint16_conversion (); + void test_int32_conversion (); + void test_uint32_conversion (); + void test_int64_conversion (); + void test_uint64_conversion (); + void test_float_conversion (); + void test_double_conversion (); + void test_bool_conversion (); + void test_convert_thread_safety (); +}; diff --git a/libs/pbd/wscript b/libs/pbd/wscript index 05c2deb1ae..29d82edcb4 100644 --- a/libs/pbd/wscript +++ b/libs/pbd/wscript @@ -73,6 +73,7 @@ libpbd_sources = [ 'stacktrace.cc', 'stateful_diff_command.cc', 'stateful.cc', + 'string_convert.cc', 'strreplace.cc', 'strsplit.cc', 'system_exec.cc', @@ -191,6 +192,7 @@ def build(bld): test/mutex_test.cc test/scalar_properties.cc test/signals_test.cc + test/string_convert_test.cc test/convert_test.cc test/filesystem_test.cc test/natsort_test.cc -- 2.30.2