1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "openrct2/localisation/Formatting.h"
11 
12 #include <gtest/gtest.h>
13 #include <openrct2/Context.h>
14 #include <openrct2/OpenRCT2.h>
15 #include <openrct2/config/Config.h>
16 #include <openrct2/core/String.hpp>
17 #include <openrct2/localisation/Localisation.h>
18 #include <openrct2/localisation/StringIds.h>
19 #include <sstream>
20 #include <string>
21 
22 using namespace OpenRCT2;
23 
24 class FmtStringTests : public testing::Test
25 {
26 };
27 
TEST_F(FmtStringTests,string_owned)28 TEST_F(FmtStringTests, string_owned)
29 {
30     auto fmt = FmtString(std::string("{BLACK}Guests: {INT32}"));
31     ASSERT_EQ("Guests: ", fmt.WithoutFormatTokens());
32 }
33 
TEST_F(FmtStringTests,iteration)34 TEST_F(FmtStringTests, iteration)
35 {
36     std::string actual;
37 
38     auto fmt = FmtString("{BLACK}Guests: {INT32}");
39     for (const auto& t : fmt)
40     {
41         actual += String::StdFormat("[%d:%s]", t.kind, std::string(t.text).c_str());
42     }
43 
44     ASSERT_EQ("[29:{BLACK}][1:Guests: ][8:{INT32}]", actual);
45 }
46 
TEST_F(FmtStringTests,iteration_escaped)47 TEST_F(FmtStringTests, iteration_escaped)
48 {
49     std::string actual;
50 
51     auto fmt = FmtString("This is an {{ESCAPED}} string.");
52     for (const auto& t : fmt)
53     {
54         actual += String::StdFormat("[%d:%s]", t.kind, std::string(t.text).c_str());
55     }
56 
57     ASSERT_EQ("[1:This is an ][2:{{][1:ESCAPED][2:}}][1: string.]", actual);
58 }
59 
TEST_F(FmtStringTests,without_format_tokens)60 TEST_F(FmtStringTests, without_format_tokens)
61 {
62     auto fmt = FmtString("{BLACK}Guests: {INT32}");
63     ASSERT_EQ("Guests: ", fmt.WithoutFormatTokens());
64 }
65 
66 class FormattingTests : public testing::Test
67 {
68 private:
69     static std::shared_ptr<IContext> _context;
70 
71 protected:
SetUpTestCase()72     static void SetUpTestCase()
73     {
74         gOpenRCT2Headless = true;
75         gOpenRCT2NoGraphics = true;
76         _context = CreateContext();
77         ASSERT_TRUE(_context->Initialise());
78 
79         language_open(LANGUAGE_ENGLISH_UK);
80     }
81 
TearDownTestCase()82     static void TearDownTestCase()
83     {
84         _context = {};
85     }
86 };
87 
88 std::shared_ptr<IContext> FormattingTests::_context;
89 
TEST_F(FormattingTests,no_args)90 TEST_F(FormattingTests, no_args)
91 {
92     auto actual = FormatString("test string");
93     ASSERT_EQ("test string", actual);
94 }
95 
TEST_F(FormattingTests,missing_arg)96 TEST_F(FormattingTests, missing_arg)
97 {
98     auto actual = FormatString("test {STRING} arg");
99     ASSERT_EQ("test  arg", actual);
100 }
101 
TEST_F(FormattingTests,integer)102 TEST_F(FormattingTests, integer)
103 {
104     auto actual = FormatString("Guests: {INT32}", 32);
105     ASSERT_EQ("Guests: 32", actual);
106 }
107 
TEST_F(FormattingTests,integer_integer)108 TEST_F(FormattingTests, integer_integer)
109 {
110     auto actual = FormatString("Guests: {INT32}, Staff: {INT32}", 32, 10);
111     ASSERT_EQ("Guests: 32, Staff: 10", actual);
112 }
113 
TEST_F(FormattingTests,comma)114 TEST_F(FormattingTests, comma)
115 {
116     auto actual = FormatString("Guests: {COMMA16}", 12534);
117     ASSERT_EQ("Guests: 12,534", actual);
118 }
119 
TEST_F(FormattingTests,comma_0)120 TEST_F(FormattingTests, comma_0)
121 {
122     auto actual = FormatString("Guests: {COMMA16}", 0);
123     ASSERT_EQ("Guests: 0", actual);
124 }
125 
TEST_F(FormattingTests,comma_large_negative)126 TEST_F(FormattingTests, comma_large_negative)
127 {
128     auto actual = FormatString("{COMMA16}", std::numeric_limits<int64_t>::min());
129     ASSERT_EQ("-9,223,372,036,854,775,808", actual);
130 }
131 
TEST_F(FormattingTests,comma_large)132 TEST_F(FormattingTests, comma_large)
133 {
134     auto actual = FormatString("{COMMA16}", std::numeric_limits<uint64_t>::max());
135     ASSERT_EQ("18,446,744,073,709,551,615", actual);
136 }
137 
TEST_F(FormattingTests,currency)138 TEST_F(FormattingTests, currency)
139 {
140     gConfigGeneral.currency_format = CurrencyType::Pounds;
141     ASSERT_EQ(u8"-£251", FormatString("{CURRENCY}", -2510));
142     ASSERT_EQ(u8"£1", FormatString("{CURRENCY}", 4));
143     ASSERT_EQ(u8"£1", FormatString("{CURRENCY}", 5));
144     ASSERT_EQ(u8"£1", FormatString("{CURRENCY}", 10));
145     ASSERT_EQ(u8"£2", FormatString("{CURRENCY}", 11));
146     ASSERT_EQ(u8"£112", FormatString("{CURRENCY}", 1111));
147 }
148 
TEST_F(FormattingTests,currency2dp)149 TEST_F(FormattingTests, currency2dp)
150 {
151     gConfigGeneral.currency_format = CurrencyType::Pounds;
152     ASSERT_EQ(u8"-£251.00", FormatString("{CURRENCY2DP}", -2510));
153     ASSERT_EQ(u8"£0.40", FormatString("{CURRENCY2DP}", 4));
154     ASSERT_EQ(u8"£0.50", FormatString("{CURRENCY2DP}", 5));
155     ASSERT_EQ(u8"£1.00", FormatString("{CURRENCY2DP}", 10));
156     ASSERT_EQ(u8"£1.10", FormatString("{CURRENCY2DP}", 11));
157     ASSERT_EQ(u8"£111.10", FormatString("{CURRENCY2DP}", 1111));
158 }
159 
TEST_F(FormattingTests,currency_yen)160 TEST_F(FormattingTests, currency_yen)
161 {
162     gConfigGeneral.currency_format = CurrencyType::Yen;
163     ASSERT_EQ(u8"-¥25,100", FormatString("{CURRENCY}", -2510));
164     ASSERT_EQ(u8"¥40", FormatString("{CURRENCY2DP}", 4));
165     ASSERT_EQ(u8"¥50", FormatString("{CURRENCY2DP}", 5));
166     ASSERT_EQ(u8"¥100", FormatString("{CURRENCY2DP}", 10));
167     ASSERT_EQ(u8"¥110", FormatString("{CURRENCY2DP}", 11));
168     ASSERT_EQ(u8"¥11,110", FormatString("{CURRENCY2DP}", 1111));
169 }
170 
TEST_F(FormattingTests,currency2dp_yen)171 TEST_F(FormattingTests, currency2dp_yen)
172 {
173     gConfigGeneral.currency_format = CurrencyType::Yen;
174     ASSERT_EQ(u8"-¥25,100", FormatString("{CURRENCY2DP}", -2510));
175     ASSERT_EQ(u8"¥40", FormatString("{CURRENCY2DP}", 4));
176     ASSERT_EQ(u8"¥50", FormatString("{CURRENCY2DP}", 5));
177     ASSERT_EQ(u8"¥100", FormatString("{CURRENCY2DP}", 10));
178     ASSERT_EQ(u8"¥110", FormatString("{CURRENCY2DP}", 11));
179     ASSERT_EQ(u8"¥11,110", FormatString("{CURRENCY2DP}", 1111));
180 }
181 
TEST_F(FormattingTests,currency_pts)182 TEST_F(FormattingTests, currency_pts)
183 {
184     gConfigGeneral.currency_format = CurrencyType::Peseta;
185     ASSERT_EQ("-251Pts", FormatString("{CURRENCY}", -2510));
186     ASSERT_EQ("112Pts", FormatString("{CURRENCY}", 1111));
187 }
188 
TEST_F(FormattingTests,currency2dp_pts)189 TEST_F(FormattingTests, currency2dp_pts)
190 {
191     gConfigGeneral.currency_format = CurrencyType::Peseta;
192     ASSERT_EQ("-251.00Pts", FormatString("{CURRENCY2DP}", -2510));
193     ASSERT_EQ("0.40Pts", FormatString("{CURRENCY2DP}", 4));
194     ASSERT_EQ("111.10Pts", FormatString("{CURRENCY2DP}", 1111));
195 }
196 
TEST_F(FormattingTests,string)197 TEST_F(FormattingTests, string)
198 {
199     auto actual = FormatString("{RED}{STRING} has broken down.", "Woodchip");
200     ASSERT_EQ("{RED}Woodchip has broken down.", actual);
201 }
202 
TEST_F(FormattingTests,escaped_braces)203 TEST_F(FormattingTests, escaped_braces)
204 {
205     auto actual = FormatString("--{{ESCAPED}}--", 0);
206     ASSERT_EQ("--{{ESCAPED}}--", actual);
207 }
208 
TEST_F(FormattingTests,velocity_mph)209 TEST_F(FormattingTests, velocity_mph)
210 {
211     gConfigGeneral.measurement_format = MeasurementFormat::Imperial;
212     auto actual = FormatString("Train is going at {VELOCITY}.", 1024);
213     ASSERT_EQ("Train is going at 1,024 mph.", actual);
214 }
215 
TEST_F(FormattingTests,velocity_kph)216 TEST_F(FormattingTests, velocity_kph)
217 {
218     gConfigGeneral.measurement_format = MeasurementFormat::Metric;
219     auto actual = FormatString("Train is going at {VELOCITY}.", 1024);
220     ASSERT_EQ("Train is going at 1,648 km/h.", actual);
221 }
222 
TEST_F(FormattingTests,velocity_mps)223 TEST_F(FormattingTests, velocity_mps)
224 {
225     gConfigGeneral.measurement_format = MeasurementFormat::SI;
226     auto actual = FormatString("Train is going at {VELOCITY}.", 1024);
227     ASSERT_EQ("Train is going at 457.7 m/s.", actual);
228 }
229 
TEST_F(FormattingTests,length_imperial)230 TEST_F(FormattingTests, length_imperial)
231 {
232     gConfigGeneral.measurement_format = MeasurementFormat::Imperial;
233     auto actual = FormatString("Height: {LENGTH}", 1024);
234     ASSERT_EQ("Height: 3,360 ft", actual);
235 }
236 
TEST_F(FormattingTests,length_metric)237 TEST_F(FormattingTests, length_metric)
238 {
239     gConfigGeneral.measurement_format = MeasurementFormat::Metric;
240     auto actual = FormatString("Height: {LENGTH}", 1024);
241     ASSERT_EQ("Height: 1,024 m", actual);
242 }
243 
TEST_F(FormattingTests,length_si)244 TEST_F(FormattingTests, length_si)
245 {
246     gConfigGeneral.measurement_format = MeasurementFormat::SI;
247     auto actual = FormatString("Height: {LENGTH}", 2048);
248     ASSERT_EQ("Height: 2,048 m", actual);
249 }
250 
TEST_F(FormattingTests,minssecs)251 TEST_F(FormattingTests, minssecs)
252 {
253     ASSERT_EQ("0secs", FormatString("{DURATION}", 0));
254     ASSERT_EQ("1sec", FormatString("{DURATION}", 1));
255     ASSERT_EQ("4secs", FormatString("{DURATION}", 4));
256     ASSERT_EQ("1min:0secs", FormatString("{DURATION}", 60));
257     ASSERT_EQ("1min:1sec", FormatString("{DURATION}", 60 + 1));
258     ASSERT_EQ("1min:59secs", FormatString("{DURATION}", 60 + 59));
259     ASSERT_EQ("2mins:0secs", FormatString("{DURATION}", 120));
260     ASSERT_EQ("2mins:1sec", FormatString("{DURATION}", 120 + 1));
261     ASSERT_EQ("2mins:2secs", FormatString("{DURATION}", 120 + 2));
262 }
263 
TEST_F(FormattingTests,hoursmins)264 TEST_F(FormattingTests, hoursmins)
265 {
266     ASSERT_EQ("0mins", FormatString("{REALTIME}", 0));
267     ASSERT_EQ("1min", FormatString("{REALTIME}", 1));
268     ASSERT_EQ("4mins", FormatString("{REALTIME}", 4));
269     ASSERT_EQ("1hour:0mins", FormatString("{REALTIME}", 60));
270     ASSERT_EQ("1hour:1min", FormatString("{REALTIME}", 60 + 1));
271     ASSERT_EQ("1hour:59mins", FormatString("{REALTIME}", 60 + 59));
272     ASSERT_EQ("2hours:0mins", FormatString("{REALTIME}", 120));
273     ASSERT_EQ("2hours:1min", FormatString("{REALTIME}", 120 + 1));
274     ASSERT_EQ("2hours:2mins", FormatString("{REALTIME}", 120 + 2));
275 }
276 
TEST_F(FormattingTests,month)277 TEST_F(FormattingTests, month)
278 {
279     ASSERT_EQ("The month is March", FormatString("The month is {MONTH}", 0));
280     ASSERT_EQ("The month is October", FormatString("The month is {MONTH}", 7));
281     ASSERT_EQ("The month is April", FormatString("The month is {MONTH}", 9));
282 }
283 
TEST_F(FormattingTests,monthyear)284 TEST_F(FormattingTests, monthyear)
285 {
286     ASSERT_EQ("Date: March, Year 1", FormatString("Date: {MONTHYEAR}", 0));
287     ASSERT_EQ("Date: October, Year 1", FormatString("Date: {MONTHYEAR}", 7));
288     ASSERT_EQ("Date: April, Year 2", FormatString("Date: {MONTHYEAR}", 9));
289 }
290 
TEST_F(FormattingTests,two_level_format)291 TEST_F(FormattingTests, two_level_format)
292 {
293     constexpr rct_string_id strDefault = STR_RIDE_NAME_DEFAULT;
294     constexpr rct_string_id strBoatHire = STR_RIDE_NAME_BOAT_HIRE;
295     auto actual = FormatString("Queuing for {STRINGID}", strDefault, strBoatHire, 2);
296     ASSERT_EQ("Queuing for Boat Hire 2", actual);
297 }
298 
TEST_F(FormattingTests,any_string_int_string)299 TEST_F(FormattingTests, any_string_int_string)
300 {
301     auto actual = FormatStringAny(
302         "{RED}{STRING} {INT32} has broken down due to '{STRING}'.", { "Twist", 2, "Mechanical failure" });
303     ASSERT_EQ("{RED}Twist 2 has broken down due to 'Mechanical failure'.", actual);
304 }
305 
TEST_F(FormattingTests,any_two_level_format)306 TEST_F(FormattingTests, any_two_level_format)
307 {
308     constexpr rct_string_id strDefault = STR_RIDE_NAME_DEFAULT;
309     constexpr rct_string_id strBoatHire = STR_RIDE_NAME_BOAT_HIRE;
310     auto actual = FormatStringAny("Queuing for {STRINGID}", { strDefault, strBoatHire, 2 });
311     ASSERT_EQ("Queuing for Boat Hire 2", actual);
312 }
313 
TEST_F(FormattingTests,to_fixed_buffer)314 TEST_F(FormattingTests, to_fixed_buffer)
315 {
316     char buffer[16];
317     std::memset(buffer, '\xFF', sizeof(buffer));
318     auto len = FormatStringId(buffer, 8, STR_GUEST_X, 123);
319     ASSERT_EQ(len, 9U);
320     ASSERT_STREQ("Guest 1", buffer);
321 
322     // Ensure rest of the buffer was not overwritten
323     for (size_t i = 8; i < sizeof(buffer); i++)
324     {
325         ASSERT_EQ('\xFF', buffer[i]);
326     }
327 }
328 
TEST_F(FormattingTests,using_legacy_buffer_args)329 TEST_F(FormattingTests, using_legacy_buffer_args)
330 {
331     auto ft = Formatter();
332     ft.Add<rct_string_id>(STR_RIDE_NAME_DEFAULT);
333     ft.Add<rct_string_id>(STR_RIDE_NAME_BOAT_HIRE);
334     ft.Add<uint16_t>(2);
335 
336     char buffer[32]{};
337     auto len = FormatStringLegacy(buffer, sizeof(buffer), STR_QUEUING_FOR, ft.Data());
338     ASSERT_EQ(len, 23U);
339     ASSERT_STREQ("Queuing for Boat Hire 2", buffer);
340 }
341 
TEST_F(FormattingTests,format_number_basic)342 TEST_F(FormattingTests, format_number_basic)
343 {
344     FormatBuffer ss;
345     // test basic integral conversion
346     FormatArgument<int32_t>(ss, FormatToken::UInt16, 123);
347     ASSERT_STREQ("123", ss.data());
348 }
349 
TEST_F(FormattingTests,format_number_basic_int32)350 TEST_F(FormattingTests, format_number_basic_int32)
351 {
352     FormatBuffer ss;
353     // test that case fallthrough works
354     FormatArgument<int32_t>(ss, FormatToken::Int32, 123);
355     ASSERT_STREQ("123", ss.data());
356 }
357 
TEST_F(FormattingTests,format_number_negative)358 TEST_F(FormattingTests, format_number_negative)
359 {
360     FormatBuffer ss;
361     // test negative conversion
362     FormatArgument<int32_t>(ss, FormatToken::Int32, -123);
363     ASSERT_STREQ("-123", ss.data());
364 }
365 
TEST_F(FormattingTests,format_number_comma16_basic)366 TEST_F(FormattingTests, format_number_comma16_basic)
367 {
368     FormatBuffer ss;
369     // test separator formatter
370     // test base case separator formatter
371     FormatArgument<int32_t>(ss, FormatToken::Comma16, 123);
372     ASSERT_STREQ("123", ss.data());
373 }
374 
TEST_F(FormattingTests,format_number_comma16_negative)375 TEST_F(FormattingTests, format_number_comma16_negative)
376 {
377     FormatBuffer ss;
378     // test separator formatter
379     // test base case separator formatter
380     FormatArgument<int32_t>(ss, FormatToken::Comma16, -123);
381     ASSERT_STREQ("-123", ss.data());
382 }
383 
TEST_F(FormattingTests,format_number_comma16_large)384 TEST_F(FormattingTests, format_number_comma16_large)
385 {
386     FormatBuffer ss;
387     // test larger value for separator formatter
388     FormatArgument<int32_t>(ss, FormatToken::Comma16, 123456789);
389     ASSERT_STREQ("123,456,789", ss.data());
390 }
391 
TEST_F(FormattingTests,format_number_comma16_large_negative)392 TEST_F(FormattingTests, format_number_comma16_large_negative)
393 {
394     FormatBuffer ss;
395     // test larger value for separator formatter with negative
396     FormatArgument<int32_t>(ss, FormatToken::Comma16, -123456789);
397     ASSERT_STREQ("-123,456,789", ss.data());
398 }
399 
TEST_F(FormattingTests,format_number_comma16_uneven)400 TEST_F(FormattingTests, format_number_comma16_uneven)
401 {
402     FormatBuffer ss;
403     // test non-multiple of 3
404     FormatArgument<int32_t>(ss, FormatToken::Comma16, 12345678);
405     ASSERT_STREQ("12,345,678", ss.data());
406 }
407 
TEST_F(FormattingTests,format_number_comma16_uneven_negative)408 TEST_F(FormattingTests, format_number_comma16_uneven_negative)
409 {
410     FormatBuffer ss;
411     // test non-multiple of 3 with negative
412     FormatArgument<int32_t>(ss, FormatToken::Comma16, -12345678);
413     ASSERT_STREQ("-12,345,678", ss.data());
414 }
415 
TEST_F(FormattingTests,format_number_comma16_zero)416 TEST_F(FormattingTests, format_number_comma16_zero)
417 {
418     FormatBuffer ss;
419     // test zero
420     FormatArgument<int32_t>(ss, FormatToken::Comma16, 0);
421     ASSERT_STREQ("0", ss.data());
422 }
423 
TEST_F(FormattingTests,format_number_comma1dp16_zero)424 TEST_F(FormattingTests, format_number_comma1dp16_zero)
425 {
426     FormatBuffer ss;
427     // zero case
428     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, 0);
429     ASSERT_STREQ("0.0", ss.data());
430 }
431 
TEST_F(FormattingTests,format_number_comma1dp16_leading_zero)432 TEST_F(FormattingTests, format_number_comma1dp16_leading_zero)
433 {
434     FormatBuffer ss;
435     // test leading zero
436     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, 5);
437     ASSERT_STREQ("0.5", ss.data());
438 }
439 
TEST_F(FormattingTests,format_number_comma1dp16_leading_zero_negative)440 TEST_F(FormattingTests, format_number_comma1dp16_leading_zero_negative)
441 {
442     FormatBuffer ss;
443     // test leading zero with negative value
444     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, -5);
445     ASSERT_STREQ("-0.5", ss.data());
446 }
447 
TEST_F(FormattingTests,format_number_comma1dp16_small_value)448 TEST_F(FormattingTests, format_number_comma1dp16_small_value)
449 {
450     FormatBuffer ss;
451     // test small value
452     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, 75);
453     ASSERT_STREQ("7.5", ss.data());
454 }
455 
TEST_F(FormattingTests,format_number_comma1dp16_small_value_negative)456 TEST_F(FormattingTests, format_number_comma1dp16_small_value_negative)
457 {
458     FormatBuffer ss;
459     // test small value with negative
460     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, -75);
461     ASSERT_STREQ("-7.5", ss.data());
462 }
463 
TEST_F(FormattingTests,format_number_comma1dp16_trailing_zeros)464 TEST_F(FormattingTests, format_number_comma1dp16_trailing_zeros)
465 {
466     FormatBuffer ss;
467     // test value with trailing zero, no commas
468     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, 1000);
469     ASSERT_STREQ("100.0", ss.data());
470 }
471 
TEST_F(FormattingTests,format_number_comma1dp16_trailing_zeros_negative)472 TEST_F(FormattingTests, format_number_comma1dp16_trailing_zeros_negative)
473 {
474     FormatBuffer ss;
475     // test value with trailing zero, no commas
476     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, -1000);
477     ASSERT_STREQ("-100.0", ss.data());
478 }
479 
TEST_F(FormattingTests,format_number_comma1dp16_large_trailing_zeros)480 TEST_F(FormattingTests, format_number_comma1dp16_large_trailing_zeros)
481 {
482     FormatBuffer ss;
483     // test value with commas and trailing zeros
484     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, 10000000);
485     ASSERT_STREQ("1,000,000.0", ss.data());
486 }
487 
TEST_F(FormattingTests,format_number_comma1dp16_large_trailing_zeros_negative)488 TEST_F(FormattingTests, format_number_comma1dp16_large_trailing_zeros_negative)
489 {
490     FormatBuffer ss;
491     // test value with commas and trailing zeros
492     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, -10000000);
493     ASSERT_STREQ("-1,000,000.0", ss.data());
494 }
495 
TEST_F(FormattingTests,format_number_comma1dp16_large_value)496 TEST_F(FormattingTests, format_number_comma1dp16_large_value)
497 {
498     FormatBuffer ss;
499     // test large value
500     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, 123456789);
501     ASSERT_STREQ("12,345,678.9", ss.data());
502 }
503 
TEST_F(FormattingTests,format_number_comma1dp16_large_value_negative)504 TEST_F(FormattingTests, format_number_comma1dp16_large_value_negative)
505 {
506     FormatBuffer ss;
507     // test large value
508     FormatArgument<int32_t>(ss, FormatToken::Comma1dp16, -123456789);
509     ASSERT_STREQ("-12,345,678.9", ss.data());
510 }
511 
TEST_F(FormattingTests,format_number_comma2dp32_zero)512 TEST_F(FormattingTests, format_number_comma2dp32_zero)
513 {
514     FormatBuffer ss;
515     // zero case
516     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, 0);
517     ASSERT_STREQ("0.00", ss.data());
518 }
519 
TEST_F(FormattingTests,format_number_comma2dp32_less_sig_figs)520 TEST_F(FormattingTests, format_number_comma2dp32_less_sig_figs)
521 {
522     FormatBuffer ss;
523     // test leading zero
524     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, 5);
525     ASSERT_STREQ("0.05", ss.data());
526 }
527 
TEST_F(FormattingTests,format_number_comma2dp32_less_sig_figs_negative)528 TEST_F(FormattingTests, format_number_comma2dp32_less_sig_figs_negative)
529 {
530     FormatBuffer ss;
531     // test leading zero
532     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, -5);
533     ASSERT_STREQ("-0.05", ss.data());
534 }
535 
TEST_F(FormattingTests,format_number_comma2dp32_leading_zero)536 TEST_F(FormattingTests, format_number_comma2dp32_leading_zero)
537 {
538     FormatBuffer ss;
539     // test small value
540     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, 75);
541     ASSERT_STREQ("0.75", ss.data());
542 }
543 
TEST_F(FormattingTests,format_number_comma2dp32_leading_zero_negative)544 TEST_F(FormattingTests, format_number_comma2dp32_leading_zero_negative)
545 {
546     FormatBuffer ss;
547     // test small value
548     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, -75);
549     ASSERT_STREQ("-0.75", ss.data());
550 }
551 
TEST_F(FormattingTests,format_number_comma2dp32_trailing_zeros)552 TEST_F(FormattingTests, format_number_comma2dp32_trailing_zeros)
553 {
554     FormatBuffer ss;
555     // test value with trailing zero, no commas
556     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, 1000);
557     ASSERT_STREQ("10.00", ss.data());
558 }
559 
TEST_F(FormattingTests,format_number_comma2dp32_trailing_zeros_negative)560 TEST_F(FormattingTests, format_number_comma2dp32_trailing_zeros_negative)
561 {
562     FormatBuffer ss;
563     // test value with trailing zero, no commas
564     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, -1000);
565     ASSERT_STREQ("-10.00", ss.data());
566 }
567 
TEST_F(FormattingTests,format_number_comma2dp32_large_trailing_zeros)568 TEST_F(FormattingTests, format_number_comma2dp32_large_trailing_zeros)
569 {
570     FormatBuffer ss;
571     // test value with commas and trailing zeros
572     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, 10000000);
573     ASSERT_STREQ("100,000.00", ss.data());
574 }
575 
TEST_F(FormattingTests,format_number_comma2dp32_large_trailing_zeros_negative)576 TEST_F(FormattingTests, format_number_comma2dp32_large_trailing_zeros_negative)
577 {
578     FormatBuffer ss;
579     // test value with commas and trailing zeros
580     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, -10000000);
581     ASSERT_STREQ("-100,000.00", ss.data());
582 }
583 
TEST_F(FormattingTests,format_number_comma2dp32_large_value)584 TEST_F(FormattingTests, format_number_comma2dp32_large_value)
585 {
586     FormatBuffer ss;
587     // test large value
588     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, 123456789);
589     ASSERT_STREQ("1,234,567.89", ss.data());
590 }
591 
TEST_F(FormattingTests,format_number_comma2dp32_large_value_negative)592 TEST_F(FormattingTests, format_number_comma2dp32_large_value_negative)
593 {
594     FormatBuffer ss;
595     // test large value
596     FormatArgument<int32_t>(ss, FormatToken::Comma2dp32, -123456789);
597     ASSERT_STREQ("-1,234,567.89", ss.data());
598 
599     // extra note:
600     // for some reason the FormatArgument function contains constexpr
601     // checks for std::is_floating_point<T>, even though a template
602     // specialization for which that would ever be the case is never
603     // declared. As such the following line won't be able to find
604     // the necessary symbol to link with.
605     // FormatArgument<double>(ss, FormatToken::Comma1dp16, 12.372);
606 }
607 
TEST_F(FormattingTests,buffer_storage_swap)608 TEST_F(FormattingTests, buffer_storage_swap)
609 {
610     FormatBufferBase<char, 16> ss;
611     ss << "Hello World";
612     ASSERT_STREQ(ss.data(), "Hello World");
613     ss << ", Exceeding local storage";
614     ASSERT_STREQ(ss.data(), "Hello World, Exceeding local storage");
615     ss << ", extended";
616     ASSERT_STREQ(ss.data(), "Hello World, Exceeding local storage, extended");
617 }
618