| 1 | // |
| 2 | // Copyright (c) 2022-2023 Alexander Grund |
| 3 | // |
| 4 | // Distributed under the Boost Software License, Version 1.0. |
| 5 | // https://www.boost.org/LICENSE_1_0.txt |
| 6 | |
| 7 | #include <boost/locale/hold_ptr.hpp> |
| 8 | #include <boost/locale/util.hpp> |
| 9 | #include <boost/locale/util/locale_data.hpp> |
| 10 | #include "boostLocale/test/test_helpers.hpp" |
| 11 | #include "boostLocale/test/tools.hpp" |
| 12 | #include "boostLocale/test/unit_test.hpp" |
| 13 | #include <cstdlib> |
| 14 | #include <stdexcept> |
| 15 | |
| 16 | namespace { |
| 17 | struct Dummy { |
| 18 | int i_; |
| 19 | Dummy(int i) : i_(i) { ++ctr; } |
| 20 | ~Dummy() { --ctr; } |
| 21 | Dummy(const Dummy&) = delete; |
| 22 | Dummy(Dummy&&) = delete; |
| 23 | |
| 24 | int foo() { return i_; } |
| 25 | int foo() const { return -i_; } |
| 26 | |
| 27 | static int ctr; |
| 28 | }; |
| 29 | int Dummy::ctr = 0; |
| 30 | } // namespace |
| 31 | |
| 32 | void test_hold_ptr() |
| 33 | { |
| 34 | { |
| 35 | boost::locale::hold_ptr<Dummy> empty; |
| 36 | TEST(!empty); |
| 37 | auto* raw = new Dummy(42); |
| 38 | boost::locale::hold_ptr<Dummy> ptr(raw); |
| 39 | const boost::locale::hold_ptr<Dummy>& const_ptr = ptr; |
| 40 | TEST_REQUIRE(ptr); |
| 41 | TEST(ptr.get() == raw); |
| 42 | TEST(const_ptr.get() == raw); |
| 43 | // const propagation |
| 44 | TEST_EQ((*ptr).foo(), raw->i_); |
| 45 | TEST_EQ((*const_ptr).foo(), -raw->i_); |
| 46 | TEST_EQ(ptr->foo(), raw->i_); |
| 47 | TEST_EQ(const_ptr->foo(), -raw->i_); |
| 48 | TEST_EQ(ptr.get()->foo(), raw->i_); |
| 49 | TEST_EQ(const_ptr.get()->foo(), -raw->i_); |
| 50 | // move construct |
| 51 | boost::locale::hold_ptr<Dummy> ptr2 = std::move(ptr); |
| 52 | TEST(!ptr); |
| 53 | TEST_REQUIRE(ptr2); |
| 54 | TEST(ptr2.get() == raw); |
| 55 | // move assign |
| 56 | ptr = std::move(ptr2); |
| 57 | TEST(ptr); |
| 58 | TEST_REQUIRE(!ptr2); |
| 59 | TEST(ptr.get() == raw); |
| 60 | // Swap |
| 61 | boost::locale::hold_ptr<Dummy> ptr3(new Dummy(1337)); |
| 62 | ptr.swap(other&: ptr3); |
| 63 | TEST_EQ(ptr->foo(), 1337); |
| 64 | TEST_EQ(ptr3->foo(), 42); |
| 65 | } |
| 66 | TEST_EQ(Dummy::ctr, 0); |
| 67 | auto* raw = new Dummy(42); |
| 68 | { |
| 69 | boost::locale::hold_ptr<Dummy> ptr(new Dummy(1)); |
| 70 | TEST_EQ(Dummy::ctr, 2); |
| 71 | ptr.reset(p: raw); |
| 72 | TEST_EQ(Dummy::ctr, 1); |
| 73 | TEST_EQ(ptr->foo(), 42); |
| 74 | TEST(ptr.release() == raw); |
| 75 | TEST_EQ(Dummy::ctr, 1); |
| 76 | } |
| 77 | TEST_EQ(Dummy::ctr, 1); |
| 78 | delete raw; |
| 79 | } |
| 80 | |
| 81 | void test_get_system_locale() |
| 82 | { |
| 83 | // Clear all -> Default to C |
| 84 | { |
| 85 | using boost::locale::test::unsetenv; |
| 86 | unsetenv(key: "LC_CTYPE" ); |
| 87 | unsetenv(key: "LC_ALL" ); |
| 88 | unsetenv(key: "LANG" ); |
| 89 | } |
| 90 | |
| 91 | using boost::locale::util::get_system_locale; |
| 92 | #if !BOOST_LOCALE_USE_WIN32_API |
| 93 | TEST_EQ(get_system_locale(false), "C" ); |
| 94 | #else |
| 95 | // On Windows the user default name is used, so we can only test the encoding |
| 96 | TEST(get_system_locale(true).find(".UTF-8" ) != std::string::npos); |
| 97 | { |
| 98 | const std::string loc = get_system_locale(false); |
| 99 | const std::string enc = loc.substr(loc.find_last_of('.')); |
| 100 | // encoding should be a windows codepage, but in the error case can be UTF-8 |
| 101 | if(enc.find(".windows-" ) != 0u) |
| 102 | TEST_EQ(enc, ".UTF-8" ); // LCOV_EXCL_LINE |
| 103 | } |
| 104 | #endif |
| 105 | // LC_ALL, LC_CTYPE and LANG variables used in this order |
| 106 | using boost::locale::test::setenv; |
| 107 | setenv(key: "LANG" , value: "mylang.foo" ); |
| 108 | TEST_EQ(get_system_locale(true), "mylang.foo" ); |
| 109 | setenv(key: "LC_CTYPE" , value: "this.lang" ); |
| 110 | TEST_EQ(get_system_locale(true), "this.lang" ); |
| 111 | setenv(key: "LC_ALL" , value: "barlang.bar" ); |
| 112 | TEST_EQ(get_system_locale(true), "barlang.bar" ); |
| 113 | } |
| 114 | |
| 115 | void test_locale_data() |
| 116 | { |
| 117 | boost::locale::util::locale_data data; |
| 118 | // Default is C.US-ASCII |
| 119 | TEST_EQ(data.language(), "C" ); |
| 120 | TEST_EQ(data.country(), "" ); |
| 121 | TEST_EQ(data.encoding(), "US-ASCII" ); |
| 122 | TEST(!data.is_utf8()); |
| 123 | TEST_EQ(data.variant(), "" ); |
| 124 | |
| 125 | TEST(data.parse("en_US.UTF-8" )); |
| 126 | TEST_EQ(data.language(), "en" ); |
| 127 | TEST_EQ(data.country(), "US" ); |
| 128 | TEST_EQ(data.encoding(), "UTF-8" ); |
| 129 | TEST(data.is_utf8()); |
| 130 | TEST_EQ(data.variant(), "" ); |
| 131 | |
| 132 | TEST(data.parse("C" )); |
| 133 | TEST_EQ(data.language(), "C" ); |
| 134 | TEST_EQ(data.country(), "" ); |
| 135 | TEST_EQ(data.encoding(), "US-ASCII" ); |
| 136 | TEST(!data.is_utf8()); |
| 137 | TEST_EQ(data.variant(), "" ); |
| 138 | |
| 139 | TEST(data.parse("ku_TR.UTF-8@sorani" )); |
| 140 | TEST_EQ(data.language(), "ku" ); |
| 141 | TEST_EQ(data.country(), "TR" ); |
| 142 | TEST_EQ(data.encoding(), "UTF-8" ); |
| 143 | TEST(data.is_utf8()); |
| 144 | TEST_EQ(data.variant(), "sorani" ); |
| 145 | |
| 146 | TEST(data.parse("POSIX" )); |
| 147 | TEST_EQ(data.language(), "C" ); |
| 148 | TEST_EQ(data.country(), "" ); |
| 149 | TEST_EQ(data.encoding(), "US-ASCII" ); |
| 150 | TEST(!data.is_utf8()); |
| 151 | TEST_EQ(data.variant(), "" ); |
| 152 | |
| 153 | TEST(data.parse("da_DK.ISO8859-15@euro" )); |
| 154 | TEST_EQ(data.language(), "da" ); |
| 155 | TEST_EQ(data.country(), "DK" ); |
| 156 | TEST_EQ(data.encoding(), "ISO8859-15" ); |
| 157 | TEST(!data.is_utf8()); |
| 158 | TEST_EQ(data.variant(), "euro" ); |
| 159 | |
| 160 | TEST(data.parse("de_DE.ISO8859-1" )); |
| 161 | TEST_EQ(data.language(), "de" ); |
| 162 | TEST_EQ(data.country(), "DE" ); |
| 163 | TEST_EQ(data.encoding(), "ISO8859-1" ); |
| 164 | TEST(!data.is_utf8()); |
| 165 | TEST_EQ(data.variant(), "" ); |
| 166 | |
| 167 | TEST(data.parse("ja_JP.eucJP" )); |
| 168 | TEST_EQ(data.language(), "ja" ); |
| 169 | TEST_EQ(data.country(), "JP" ); |
| 170 | TEST_EQ(data.encoding(), "EUCJP" ); |
| 171 | TEST(!data.is_utf8()); |
| 172 | TEST_EQ(data.variant(), "" ); |
| 173 | |
| 174 | TEST(data.parse("ko_KR.EUC@dict" )); |
| 175 | TEST_EQ(data.language(), "ko" ); |
| 176 | TEST_EQ(data.country(), "KR" ); |
| 177 | TEST_EQ(data.encoding(), "EUC" ); |
| 178 | TEST(!data.is_utf8()); |
| 179 | TEST_EQ(data.variant(), "dict" ); |
| 180 | |
| 181 | TEST(data.parse("th_TH.TIS620" )); |
| 182 | TEST_EQ(data.language(), "th" ); |
| 183 | TEST_EQ(data.country(), "TH" ); |
| 184 | TEST_EQ(data.encoding(), "TIS620" ); |
| 185 | TEST(!data.is_utf8()); |
| 186 | TEST_EQ(data.variant(), "" ); |
| 187 | |
| 188 | TEST(data.parse("zh_TW.UTF-8@radical" )); |
| 189 | TEST_EQ(data.language(), "zh" ); |
| 190 | TEST_EQ(data.country(), "TW" ); |
| 191 | TEST_EQ(data.encoding(), "UTF-8" ); |
| 192 | TEST(data.is_utf8()); |
| 193 | TEST_EQ(data.variant(), "radical" ); |
| 194 | |
| 195 | // Country can be a 3-digit value |
| 196 | TEST(data.parse("en_001.UTF-8" )); |
| 197 | TEST_EQ(data.language(), "en" ); |
| 198 | TEST_EQ(data.country(), "001" ); |
| 199 | TEST_EQ(data.encoding(), "UTF-8" ); |
| 200 | TEST(data.is_utf8()); |
| 201 | TEST_EQ(data.variant(), "" ); |
| 202 | |
| 203 | // to_string yields the input (if format is correct already) |
| 204 | for(const std::string name : {"C" , |
| 205 | "en_US.UTF-8" , |
| 206 | "ku_TR.UTF-8@sorani" , |
| 207 | "da_DK.ISO8859-15@euro" , |
| 208 | "de_DE.ISO8859-1" , |
| 209 | "en_US" , |
| 210 | "ko_KR.EUC@dict" , |
| 211 | "th_TH.TIS620" , |
| 212 | "zh_TW.UTF-8@radical" , |
| 213 | "en_001" , |
| 214 | "en_150.UTF-8" }) |
| 215 | { |
| 216 | TEST(data.parse(name)); |
| 217 | TEST_EQ(data.to_string(), name); |
| 218 | } |
| 219 | // US-ASCII encoding is ignored |
| 220 | TEST(data.parse("da_TR.US-ASCII" )); |
| 221 | TEST_EQ(data.to_string(), "da_TR" ); |
| 222 | TEST(data.parse("da_TR.US-ASCII@dic" )); |
| 223 | TEST_EQ(data.to_string(), "da_TR@dic" ); |
| 224 | |
| 225 | // Unify casing: |
| 226 | // - language: lowercase |
| 227 | // - region: uppercase |
| 228 | // - encoding: uppercase |
| 229 | // - variant: lowercase |
| 230 | TEST(data.parse("EN_us.utf-8@EUro" )); |
| 231 | TEST_EQ(data.language(), "en" ); |
| 232 | TEST_EQ(data.country(), "US" ); |
| 233 | TEST_EQ(data.encoding(), "UTF-8" ); |
| 234 | TEST(data.is_utf8()); |
| 235 | TEST_EQ(data.variant(), "euro" ); |
| 236 | TEST_EQ(data.to_string(), "en_US.UTF-8@euro" ); |
| 237 | TEST(data.parse("lAnGUagE_cOunTRy.eNCo-d123inG@Va-r1_Ant" )); |
| 238 | TEST_EQ(data.to_string(), "language_COUNTRY.ENCO-D123ING@va-r1_ant" ); |
| 239 | |
| 240 | // Dash is allowed in addition to underscore |
| 241 | TEST(data.parse("de-DE.UTF-8" )); |
| 242 | TEST_EQ(data.to_string(), "de_DE.UTF-8" ); |
| 243 | |
| 244 | // C/POSIX is allowed to have an encoding |
| 245 | TEST(data.parse("C.UTF-8" )); |
| 246 | TEST_EQ(data.to_string(), "C.UTF-8" ); |
| 247 | TEST(data.parse("POSIX.UTF-8" )); |
| 248 | TEST_EQ(data.to_string(), "C.UTF-8" ); |
| 249 | |
| 250 | // Special case: en_US_POSIX is an alias for "C" |
| 251 | TEST(data.parse("en_US_POSIX" )); |
| 252 | TEST_EQ(data.to_string(), "C" ); |
| 253 | TEST(data.parse("En_Us_POsix.UTF-8" )); |
| 254 | TEST_EQ(data.to_string(), "C.UTF-8" ); |
| 255 | |
| 256 | // Missing values are defaulted |
| 257 | TEST(data.parse("en" )); |
| 258 | TEST_EQ(data.to_string(), "en" ); |
| 259 | TEST_EQ(data.encoding(), "US-ASCII" ); |
| 260 | TEST(!data.is_utf8()); |
| 261 | TEST(data.parse("en.UTF-8" )); |
| 262 | TEST_EQ(data.to_string(), "en.UTF-8" ); |
| 263 | TEST_EQ(data.encoding(), "UTF-8" ); |
| 264 | TEST(data.is_utf8()); |
| 265 | TEST(data.parse("en@dict" )); |
| 266 | TEST_EQ(data.to_string(), "en@dict" ); |
| 267 | TEST_EQ(data.encoding(), "US-ASCII" ); |
| 268 | TEST_EQ(data.variant(), "dict" ); |
| 269 | TEST(data.parse("en_US@dict" )); |
| 270 | TEST_EQ(data.to_string(), "en_US@dict" ); |
| 271 | TEST_EQ(data.encoding(), "US-ASCII" ); |
| 272 | TEST_EQ(data.variant(), "dict" ); |
| 273 | |
| 274 | // Error cases, default values used starting from error |
| 275 | |
| 276 | // Invalid language (separator at start or not an ASCII letter) |
| 277 | for(const std::string invalidName : |
| 278 | {"_en_US.UTF-8" , "-en_US.UTF-8" , ".en_US.UTF-8" , "@en_US.UTF-8" , "e1_US.UTF-8" , "eö_US.UTF-8" }) |
| 279 | { |
| 280 | TEST(!data.parse(invalidName)); |
| 281 | TEST_EQ(data.to_string(), "C" ); |
| 282 | } |
| 283 | // Invalid country |
| 284 | TEST(!data.parse("en_UÖ.UTF-8" )); |
| 285 | TEST_EQ(data.to_string(), "en" ); |
| 286 | TEST(!data.parse("en_1234.UTF-8" )); // To many digits |
| 287 | TEST_EQ(data.to_string(), "en" ); |
| 288 | TEST(!data.parse("en_US1.UTF-8" )); // digits in text |
| 289 | TEST_EQ(data.to_string(), "en" ); |
| 290 | TEST(!data.parse("en_1US.UTF-8" )); // digits in text |
| 291 | TEST_EQ(data.to_string(), "en" ); |
| 292 | |
| 293 | // Empty parts: |
| 294 | // Language |
| 295 | TEST(!data.parse("_US.UTF-8@variant" )); |
| 296 | TEST_EQ(data.to_string(), "C" ); |
| 297 | // Country |
| 298 | TEST(!data.parse("en_.UTF-8@variant" )); |
| 299 | TEST_EQ(data.to_string(), "en" ); |
| 300 | // Encoding |
| 301 | TEST(!data.parse("en_US.@variant" )); |
| 302 | TEST_EQ(data.to_string(), "en_US" ); |
| 303 | // Variant |
| 304 | TEST(!data.parse("en_US.UTF-8@" )); |
| 305 | TEST_EQ(data.to_string(), "en_US.UTF-8" ); |
| 306 | |
| 307 | // C/POSIX with any other field except the encoding |
| 308 | for(const std::string invalidName : {"C_US" , "C@variant" , "POSIX_US" , "POSIX@variant" }) { |
| 309 | TEST(!data.parse(invalidName)); |
| 310 | TEST_EQ(data.to_string(), "C" ); |
| 311 | } |
| 312 | |
| 313 | // Construct from string |
| 314 | TEST_EQ(boost::locale::util::locale_data("en_US.UTF-8" ).to_string(), "en_US.UTF-8" ); |
| 315 | TEST_THROWS(boost::locale::util::locale_data invalid("en_UÖ.UTF-8" ), std::invalid_argument); |
| 316 | } |
| 317 | |
| 318 | #include "../src/boost/locale/util/numeric.hpp" |
| 319 | #include <limits> |
| 320 | #include <locale> |
| 321 | #include <sstream> |
| 322 | |
| 323 | void test_try_to_int() |
| 324 | { |
| 325 | using boost::locale::util::try_to_int; |
| 326 | |
| 327 | int v = 1337; |
| 328 | TEST(try_to_int("0" , v)); |
| 329 | TEST_EQ(v, 0); |
| 330 | |
| 331 | TEST(try_to_int("42" , v)); |
| 332 | TEST_EQ(v, 42); |
| 333 | |
| 334 | TEST(try_to_int("-1337" , v)); |
| 335 | TEST_EQ(v, -1337); |
| 336 | |
| 337 | std::ostringstream ss; |
| 338 | ss.imbue(loc: std::locale::classic()); |
| 339 | empty_stream(s&: ss) << std::numeric_limits<int>::min(); |
| 340 | TEST(try_to_int(ss.str(), v)); |
| 341 | TEST_EQ(v, std::numeric_limits<int>::min()); |
| 342 | empty_stream(s&: ss) << std::numeric_limits<int>::max(); |
| 343 | TEST(try_to_int(ss.str(), v)); |
| 344 | TEST_EQ(v, std::numeric_limits<int>::max()); |
| 345 | |
| 346 | TEST(!try_to_int("" , v)); |
| 347 | TEST(!try_to_int("a" , v)); |
| 348 | TEST(!try_to_int("1." , v)); |
| 349 | TEST(!try_to_int("1a" , v)); |
| 350 | TEST(!try_to_int("a1" , v)); |
| 351 | static_assert(sizeof(long long) > sizeof(int), "Value below under/overflows!" ); |
| 352 | empty_stream(s&: ss) << static_cast<long long>(std::numeric_limits<int>::min()) - 1; |
| 353 | TEST(!try_to_int(ss.str(), v)); |
| 354 | empty_stream(s&: ss) << static_cast<long long>(std::numeric_limits<int>::max()) + 1; |
| 355 | TEST(!try_to_int(ss.str(), v)); |
| 356 | } |
| 357 | |
| 358 | void test_main(int /*argc*/, char** /*argv*/) |
| 359 | { |
| 360 | test_hold_ptr(); |
| 361 | test_get_system_locale(); |
| 362 | test_locale_data(); |
| 363 | test_try_to_int(); |
| 364 | } |
| 365 | |