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 | |