summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Mayberry <mojofunk@gmail.com>2015-08-03 12:32:31 +1000
committerTim Mayberry <mojofunk@gmail.com>2017-04-16 14:02:41 +1000
commitc634daef6a9de8df7245d4fb6fbbcdf0ad7ac7ff (patch)
treee21e96a8a76133570f756ac31c1fad705c92384a
parent78b82b7ff2e28d53faae176776491404115ce02c (diff)
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<bool> as this seems to be the convention used throughout libardour which will allow using string_to<bool> in those cases. Add accepted bool string values from PBD::string_is_affirmative to PBD::string_to<bool> 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.
-rw-r--r--libs/pbd/pbd/string_convert.h430
-rw-r--r--libs/pbd/string_convert.cc366
-rw-r--r--libs/pbd/test/string_convert_test.cc651
-rw-r--r--libs/pbd/test/string_convert_test.h30
-rw-r--r--libs/pbd/wscript2
5 files changed, 1479 insertions, 0 deletions
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 <string>
+#include <stdint.h>
+
+#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 <class T>
+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 <class T>
+inline bool to_string (bool val, std::string& str)
+{
+ return bool_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (int8_t val, std::string& str)
+{
+ return int16_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (uint8_t val, std::string& str)
+{
+ return uint16_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (int16_t val, std::string& str)
+{
+ return int16_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (uint16_t val, std::string& str)
+{
+ return uint16_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (int32_t val, std::string& str)
+{
+ return int32_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (uint32_t val, std::string& str)
+{
+ return uint32_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (int64_t val, std::string& str)
+{
+ return int64_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (uint64_t val, std::string& str)
+{
+ return uint64_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (float val, std::string& str)
+{
+ return float_to_string (val, str);
+}
+
+template <class T>
+inline bool to_string (double val, std::string& str)
+{
+ return double_to_string (val, str);
+}
+
+template <class T>
+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 <class T>
+inline bool string_to (const std::string& str, bool& val)
+{
+ return string_to_bool (str, val);
+}
+
+template <class T>
+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 <class T>
+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 <class T>
+inline bool string_to (const std::string& str, int16_t& val)
+{
+ return string_to_int16 (str, val);
+}
+
+template <class T>
+inline bool string_to (const std::string& str, uint16_t& val)
+{
+ return string_to_uint16 (str, val);
+}
+
+template <class T>
+inline bool string_to (const std::string& str, int32_t& val)
+{
+ return string_to_int32 (str, val);
+}
+
+template <class T>
+inline bool string_to (const std::string& str, uint32_t& val)
+{
+ return string_to_uint32 (str, val);
+}
+
+template <class T>
+inline bool string_to (const std::string& str, int64_t& val)
+{
+ return string_to_int64 (str, val);
+}
+
+template <class T>
+inline bool string_to (const std::string& str, uint64_t& val)
+{
+ return string_to_uint64 (str, val);
+}
+
+template <class T>
+inline bool string_to (const std::string& str, float& val)
+{
+ return string_to_float (str, val);
+}
+
+template <class T>
+inline bool string_to (const std::string& str, double& val)
+{
+ return string_to_double (str, val);
+}
+
+////////////////////////////////////////////////////////////////
+// Variation that disregards conversion errors
+////////////////////////////////////////////////////////////////
+
+template <class T>
+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 <class T>
+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 <inttypes.h>
+#include <cerrno>
+#include <cstdio>
+#include <limits>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#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 <class FloatType>
+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<FloatType>::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<FloatType>::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 <class FloatType>
+static bool
+_infinity_to_string (FloatType val, std::string& str)
+{
+ if (val == std::numeric_limits<FloatType>::infinity ()) {
+ str = "inf";
+ return true;
+ } else if (val == -std::numeric_limits<FloatType>::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 <stdio.h>
+#include <string.h>
+
+#include <limits>
+#include <cassert>
+
+#include <pthread.h>
+
+#include <glib.h>
+
+#include "pbd/string_convert.h"
+
+using namespace PBD;
+using namespace std;
+
+CPPUNIT_TEST_SUITE_REGISTRATION (StringConvertTest);
+
+static std::vector<std::string> get_test_locales ()
+{
+ std::vector<std::string> 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<std::string> locales = get_test_locales();
+
+ for (std::vector<std::string>::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<int16_t>::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<int16_t>::max (), val);
+
+ CPPUNIT_ASSERT (int16_to_string (numeric_limits<int16_t>::min (), str));
+ CPPUNIT_ASSERT_EQUAL (MIN_INT16_STR, str);
+
+ CPPUNIT_ASSERT (string_to_int16 (str, val));
+ CPPUNIT_ASSERT_EQUAL (numeric_limits<int16_t>::min (), val);
+
+ // test the string_to/to_string templates
+ int16_t max = numeric_limits<int16_t>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<int16_t>(to_string (max)));
+
+ int16_t min = numeric_limits<int16_t>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<int16_t>(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<uint16_t>::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<uint16_t>::max (), val);
+
+ CPPUNIT_ASSERT (uint16_to_string (numeric_limits<uint16_t>::min (), str));
+ CPPUNIT_ASSERT_EQUAL (MIN_UINT16_STR, str);
+
+ CPPUNIT_ASSERT (string_to_uint16 (str, val));
+ CPPUNIT_ASSERT_EQUAL (numeric_limits<uint16_t>::min (), val);
+
+ // test the string_to/to_string templates
+ uint16_t max = numeric_limits<uint16_t>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<uint16_t>(to_string (max)));
+
+ uint16_t min = numeric_limits<uint16_t>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<uint16_t>(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<int32_t>::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<int32_t>::max (), val);
+
+ CPPUNIT_ASSERT (int32_to_string (numeric_limits<int32_t>::min (), str));
+ CPPUNIT_ASSERT_EQUAL (MIN_INT32_STR, str);
+
+ CPPUNIT_ASSERT (string_to_int32 (str, val));
+ CPPUNIT_ASSERT_EQUAL (numeric_limits<int32_t>::min (), val);
+
+ // test the string_to/to_string templates
+ int32_t max = numeric_limits<int32_t>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<int32_t>(to_string (max)));
+
+ int32_t min = numeric_limits<int32_t>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<int32_t>(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<uint32_t>::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<uint32_t>::max (), val);
+
+ CPPUNIT_ASSERT (uint32_to_string (numeric_limits<uint32_t>::min (), str));
+ CPPUNIT_ASSERT_EQUAL (MIN_UINT32_STR, str);
+
+ CPPUNIT_ASSERT (string_to_uint32 (str, val));
+ CPPUNIT_ASSERT_EQUAL (numeric_limits<uint32_t>::min (), val);
+
+ // test the string_to/to_string templates
+ uint32_t max = numeric_limits<uint32_t>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<uint32_t>(to_string (max)));
+
+ uint32_t min = numeric_limits<uint32_t>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<uint32_t>(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<int64_t>::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<int64_t>::max (), val);
+
+ CPPUNIT_ASSERT (int64_to_string (numeric_limits<int64_t>::min (), str));
+ CPPUNIT_ASSERT_EQUAL (MIN_INT64_STR, str);
+
+ CPPUNIT_ASSERT (string_to_int64 (str, val));
+ CPPUNIT_ASSERT_EQUAL (numeric_limits<int64_t>::min (), val);
+
+ // test the string_to/to_string templates
+ int64_t max = numeric_limits<int64_t>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<int64_t>(to_string (max)));
+
+ int64_t min = numeric_limits<int64_t>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<int64_t>(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<uint64_t>::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<uint64_t>::max (), val);
+
+ CPPUNIT_ASSERT (uint64_to_string (numeric_limits<uint64_t>::min (), str));
+ CPPUNIT_ASSERT_EQUAL (MIN_UINT64_STR, str);
+
+ CPPUNIT_ASSERT (string_to_uint64 (str, val));
+ CPPUNIT_ASSERT_EQUAL (numeric_limits<uint64_t>::min (), val);
+
+ // test the string_to/to_string templates
+ uint64_t max = numeric_limits<uint64_t>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<uint64_t>(to_string (max)));
+
+ uint64_t min = numeric_limits<uint64_t>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<uint64_t>(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<std::string>
+_pos_infinity_strings ()
+{
+ std::vector<std::string> 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<std::string>
+_neg_infinity_strings ()
+{
+ std::vector<std::string> 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 <class FloatType>
+void
+_test_infinity_conversion ()
+{
+ const FloatType pos_infinity = numeric_limits<FloatType>::infinity ();
+ const FloatType neg_infinity = -numeric_limits<FloatType>::infinity ();
+
+ // Check float -> string
+ string str;
+ CPPUNIT_ASSERT (to_string<FloatType> (pos_infinity, str));
+ CPPUNIT_ASSERT_EQUAL (POS_INF_STR, str);
+
+ CPPUNIT_ASSERT (to_string<FloatType> (neg_infinity, str));
+ CPPUNIT_ASSERT_EQUAL (NEG_INF_STR, str);
+
+ // Check string -> float for all supported string representations of "INFINITY"
+ std::vector<std::string> pos_inf_strings = _pos_infinity_strings ();
+
+ for (std::vector<std::string>::const_iterator i = pos_inf_strings.begin ();
+ i != pos_inf_strings.end (); ++i) {
+ FloatType pos_infinity_arg;
+ CPPUNIT_ASSERT (string_to<FloatType> (*i, pos_infinity_arg));
+ CPPUNIT_ASSERT_EQUAL (pos_infinity_arg, pos_infinity);
+ }
+
+ // Check string -> float for all supported string representations of "-INFINITY"
+ std::vector<std::string> neg_inf_strings = _neg_infinity_strings ();
+
+ for (std::vector<std::string>::const_iterator i = neg_inf_strings.begin ();
+ i != neg_inf_strings.end (); ++i) {
+ FloatType neg_infinity_arg;
+ CPPUNIT_ASSERT (string_to<FloatType> (*i, neg_infinity_arg));
+ CPPUNIT_ASSERT_EQUAL (neg_infinity_arg, neg_infinity);
+ }
+
+ // Check round-trip equality
+ CPPUNIT_ASSERT_EQUAL (pos_infinity, string_to<FloatType> (to_string<FloatType> (pos_infinity)));
+ CPPUNIT_ASSERT_EQUAL (neg_infinity, string_to<FloatType> (to_string<FloatType> (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<float>::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<float>::max (), val, numeric_limits<float>::epsilon ());
+
+ CPPUNIT_ASSERT (float_to_string (numeric_limits<float>::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<float>::min (), val, numeric_limits<float>::epsilon ());
+
+ // test the string_to/to_string templates
+ float max = numeric_limits<float>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<float>(to_string<float> (max)));
+
+ float min = numeric_limits<float>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<float>(to_string<float> (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<float>::max (), val, numeric_limits<float>::epsilon ());
+
+ CPPUNIT_ASSERT (string_to_float (MIN_FLOAT_STR, val));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL (
+ numeric_limits<float>::min (), val, numeric_limits<float>::epsilon ());
+#else
+ CPPUNIT_ASSERT (string_to_float (MAX_FLOAT_WIN, val));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL (
+ numeric_limits<float>::max (), val, numeric_limits<float>::epsilon ());
+
+ CPPUNIT_ASSERT (string_to_float (MIN_FLOAT_WIN, val));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL (
+ numeric_limits<float>::min (), val, numeric_limits<float>::epsilon ());
+#endif
+
+ _test_infinity_conversion<float>();
+}
+
+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<double>::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<double>::max (), val, numeric_limits<double>::epsilon ());
+
+ CPPUNIT_ASSERT (double_to_string (numeric_limits<double>::min (), str));
+ CPPUNIT_ASSERT_EQUAL (MIN_DOUBLE_STR, str);
+
+ CPPUNIT_ASSERT (string_to_double (str, val));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL (
+ numeric_limits<double>::min (), val, numeric_limits<double>::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<double>::max ();
+ CPPUNIT_ASSERT_EQUAL (max, string_to<double>(to_string<double> (max)));
+
+ double min = numeric_limits<double>::min ();
+ CPPUNIT_ASSERT_EQUAL (min, string_to<double>(to_string<double> (min)));
+
+ _test_infinity_conversion<double>();
+}
+
+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<bool>(to_string (true)));
+
+ CPPUNIT_ASSERT_EQUAL (false, string_to<bool>(to_string (false)));
+}
+
+namespace {
+
+bool
+check_int_convert ()
+{
+ int32_t num = g_random_int ();
+ return (num == string_to<int32_t>(to_string (num)));
+}
+
+bool
+check_float_convert ()
+{
+ float num = (float)g_random_double ();
+ return (num == string_to<float>(to_string<float> (num)));
+}
+
+bool
+check_double_convert ()
+{
+ double num = g_random_double ();
+ return (num == string_to<double>(to_string<double> (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 <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+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