ardour/libs/pbd/test/string_convert_test.cc
Tim Mayberry c634daef6a 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.
2017-04-16 14:02:41 +10:00

651 lines
18 KiB
C++

#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);
}