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
16namespace {
17struct 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};
29int Dummy::ctr = 0;
30} // namespace
31
32void 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
81void 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
115void 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
323void 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
358void 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

source code of boost/libs/locale/test/test_util.cpp