xref: /freebsd/lib/libc/tests/stdlib/strfmon_test.c (revision cf85e7034ad5640b18a3b68d6b291b7bf89bfc80)
1 /*
2  * Copyright (c) 2018 Conrad Meyer <cem@FreeBSD.org>
3  * All rights reserved.
4  * Copyright (c) 2022-2025 Jose Luis Duran <jlduran@FreeBSD.org>
5  *
6  * SPDX-License-Identifier: BSD-2-Clause
7  */
8 
9 #include <sys/param.h>
10 
11 #include <errno.h>
12 #include <locale.h>
13 #include <monetary.h>
14 #include <stdio.h>
15 
16 #include <atf-c.h>
17 
18 ATF_TC_WITHOUT_HEAD(strfmon_locale_thousands);
ATF_TC_BODY(strfmon_locale_thousands,tc)19 ATF_TC_BODY(strfmon_locale_thousands, tc)
20 {
21 	char actual[40], expected[40];
22 	struct lconv *lc;
23 	const char *ts;
24 	double n;
25 
26 	setlocale(LC_MONETARY, "sv_SE.UTF-8");
27 
28 	lc = localeconv();
29 
30 	ts = lc->mon_thousands_sep;
31 	if (strlen(ts) == 0)
32 		ts = lc->thousands_sep;
33 
34 	if (strlen(ts) < 2)
35 		atf_tc_skip("multi-byte thousands-separator not found");
36 
37 	n = 1234.56;
38 	strfmon(actual, sizeof(actual) - 1, "%i", n);
39 
40 	strcpy(expected, "1");
41 	strlcat(expected, ts, sizeof(expected));
42 	strlcat(expected, "234", sizeof(expected));
43 
44 	/* We're just testing the thousands separator, not all of strfmon. */
45 	actual[strlen(expected)] = '\0';
46 	ATF_CHECK_STREQ(expected, actual);
47 }
48 
49 ATF_TC_WITHOUT_HEAD(strfmon_examples);
ATF_TC_BODY(strfmon_examples,tc)50 ATF_TC_BODY(strfmon_examples, tc)
51 {
52 	const struct {
53 		const char *format;
54 		const char *expected;
55 	} tests[] = {
56 	    { "%n", "[$123.45] [-$123.45] [$3,456.78]" },
57 	    { "%11n", "[    $123.45] [   -$123.45] [  $3,456.78]" },
58 	    { "%#5n", "[ $   123.45] [-$   123.45] [ $ 3,456.78]" },
59 	    { "%=*#5n", "[ $***123.45] [-$***123.45] [ $*3,456.78]" },
60 	    { "%=0#5n", "[ $000123.45] [-$000123.45] [ $03,456.78]" },
61 	    { "%^#5n", "[ $  123.45] [-$  123.45] [ $ 3456.78]" },
62 	    { "%^#5.0n", "[ $  123] [-$  123] [ $ 3457]" },
63 	    { "%^#5.4n", "[ $  123.4500] [-$  123.4500] [ $ 3456.7810]" },
64 	    { "%(#5n", "[ $   123.45 ] [($   123.45)] [ $ 3,456.78 ]" },
65 	    { "%!(#5n", "[    123.45 ] [(   123.45)] [  3,456.78 ]" },
66 	    { "%-14#5.4n", "[ $   123.4500 ] [-$   123.4500 ] [ $ 3,456.7810 ]" },
67 	    { "%14#5.4n", "[  $   123.4500] [ -$   123.4500] [  $ 3,456.7810]" },
68 	};
69 	size_t i;
70 	char actual[100], format[50];
71 
72 	if (setlocale(LC_MONETARY, "en_US.UTF-8") == NULL)
73 		atf_tc_skip("unable to setlocale()");
74 
75 	for (i = 0; i < nitems(tests); ++i) {
76 		snprintf(format, sizeof(format), "[%s] [%s] [%s]",
77 		    tests[i].format, tests[i].format, tests[i].format);
78 		strfmon(actual, sizeof(actual) - 1, format,
79 		    123.45, -123.45, 3456.781);
80 		ATF_CHECK_STREQ_MSG(tests[i].expected, actual,
81 		    "[%s]", tests[i].format);
82 	}
83 }
84 
85 ATF_TC(strfmon_cs_precedes_0);
ATF_TC_HEAD(strfmon_cs_precedes_0,tc)86 ATF_TC_HEAD(strfmon_cs_precedes_0, tc)
87 {
88 	atf_tc_set_md_var(tc, "descr",
89 	    "sep_by_space x sign_posn when cs_precedes = 0");
90 }
ATF_TC_BODY(strfmon_cs_precedes_0,tc)91 ATF_TC_BODY(strfmon_cs_precedes_0, tc)
92 {
93 	const struct {
94 		const char *expected;
95 	} tests[] = {
96 	    /* sep_by_space x sign_posn */
97 	    { "[(123.00$)] [-123.00$] [123.00$-] [123.00-$] [123.00$-]" },
98 	    { "[(123.00 $)] [-123.00 $] [123.00 $-] [123.00 -$] [123.00 $-]" },
99 	    { "[(123.00$)] [- 123.00$] [123.00$ -] [123.00- $] [123.00$ -]" },
100 	};
101 	size_t i, j;
102 	struct lconv *lc;
103 	char actual[100], buf[100];
104 
105 	if (setlocale(LC_MONETARY, "en_US.UTF-8") == NULL)
106 		atf_tc_skip("unable to setlocale()");
107 
108 	lc = localeconv();
109 	lc->n_cs_precedes = 0;
110 
111 	for (i = 0; i < nitems(tests); ++i) {
112 		actual[0] = '\0';
113 		lc->n_sep_by_space = i;
114 
115 		for (j = 0; j < 5; ++j) {
116 			lc->n_sign_posn = j;
117 
118 			strfmon(buf, sizeof(buf) - 1, "[%n] ", -123.0);
119 			strlcat(actual, buf, sizeof(actual));
120 		}
121 
122 		actual[strlen(actual) - 1] = '\0';
123 		ATF_CHECK_STREQ_MSG(tests[i].expected, actual,
124 		    "sep_by_space = %zu", i);
125 	}
126 }
127 
128 ATF_TC(strfmon_cs_precedes_1);
ATF_TC_HEAD(strfmon_cs_precedes_1,tc)129 ATF_TC_HEAD(strfmon_cs_precedes_1, tc)
130 {
131 	atf_tc_set_md_var(tc, "descr",
132 	    "sep_by_space x sign_posn when cs_precedes = 1");
133 }
ATF_TC_BODY(strfmon_cs_precedes_1,tc)134 ATF_TC_BODY(strfmon_cs_precedes_1, tc)
135 {
136 	const struct {
137 		const char *expected;
138 	} tests[] = {
139 	    /* sep_by_space x sign_posn */
140 	    { "[($123.00)] [-$123.00] [$123.00-] [-$123.00] [$-123.00]" },
141 	    { "[($ 123.00)] [-$ 123.00] [$ 123.00-] [-$ 123.00] [$- 123.00]" },
142 	    { "[($123.00)] [- $123.00] [$123.00 -] [- $123.00] [$ -123.00]" },
143 	};
144 	size_t i, j;
145 	struct lconv *lc;
146 	char actual[100], buf[100];
147 
148 	if (setlocale(LC_MONETARY, "en_US.UTF-8") == NULL)
149 		atf_tc_skip("unable to setlocale()");
150 
151 	lc = localeconv();
152 	lc->n_cs_precedes = 1;
153 
154 	for (i = 0; i < nitems(tests); ++i) {
155 		actual[0] = '\0';
156 		lc->n_sep_by_space = i;
157 
158 		for (j = 0; j < 5; ++j) {
159 			lc->n_sign_posn = j;
160 
161 			strfmon(buf, sizeof(buf) - 1, "[%n] ", -123.0);
162 			strlcat(actual, buf, sizeof(actual));
163 		}
164 
165 		actual[strlen(actual) - 1] = '\0';
166 		ATF_CHECK_STREQ_MSG(tests[i].expected, actual,
167 		    "sep_by_space = %zu", i);
168 	}
169 }
170 
171 ATF_TC_WITHOUT_HEAD(strfmon_international_currency_code);
ATF_TC_BODY(strfmon_international_currency_code,tc)172 ATF_TC_BODY(strfmon_international_currency_code, tc)
173 {
174 	const struct {
175 		const char *locale;
176 		const char *expected;
177 	} tests[] = {
178 	    { "en_US.UTF-8", "[USD123.45]" },
179 	    { "de_DE.UTF-8", "[123,45 EUR]" },
180 	    { "C", "[123.45]" },
181 	};
182 	size_t i;
183 	char actual[100];
184 
185 	for (i = 0; i < nitems(tests); ++i) {
186 		if (setlocale(LC_MONETARY, tests[i].locale) == NULL)
187 			atf_tc_skip("unable to setlocale()");
188 
189 		strfmon(actual, sizeof(actual) - 1, "[%i]", 123.45);
190 		ATF_CHECK_STREQ(tests[i].expected, actual);
191 	}
192 }
193 
194 ATF_TC_WITHOUT_HEAD(strfmon_plus_or_parenthesis);
ATF_TC_BODY(strfmon_plus_or_parenthesis,tc)195 ATF_TC_BODY(strfmon_plus_or_parenthesis, tc)
196 {
197 	const struct {
198 		const char *format;
199 		const char *locale;
200 		const char *expected;
201 	} tests[] = {
202 	    { "%+n", "en_US.UTF-8", "[$123.45] [-$123.45]" },
203 	    { "%+i", "en_US.UTF-8", "[USD123.45] [-USD123.45]" },
204 	    { "%(n", "C", "[123.45] [(123.45)]" },
205 	    { "%(i", "C", "[123.45] [(123.45)]" },
206 	    { "%(n", "en_US.UTF-8", "[$123.45] [($123.45)]" },
207 	    { "%(i", "en_US.UTF-8", "[USD123.45] [(USD123.45)]" },
208 	    { "%n", "C", "[123.45] [-123.45]" },
209 	    { "%i", "C", "[123.45] [-123.45]" },
210 	    { "%n", "en_US.UTF-8", "[$123.45] [-$123.45]" },
211 	    { "%i", "en_US.UTF-8", "[USD123.45] [-USD123.45]" },
212 	};
213 	size_t i;
214 	char actual[100], format[50];
215 
216 	for (i = 0; i < nitems(tests); ++i) {
217 		if (setlocale(LC_MONETARY, tests[i].locale) == NULL)
218 			atf_tc_skip("unable to setlocale(): %s",
219 			    tests[i].locale);
220 
221 		snprintf(format, sizeof(format), "[%s] [%s]",
222 		    tests[i].format, tests[i].format);
223 		strfmon(actual, sizeof(actual) - 1, format,
224 		    123.45, -123.45);
225 		ATF_CHECK_STREQ_MSG(tests[i].expected, actual,
226 		    "[%s] %s", tests[i].format, tests[i].locale);
227 	}
228 
229 	/*
230 	 * The '+' flag was included in a conversion specification and
231 	 * the locale's positive_sign and negative_sign values would
232 	 * both be returned by localeconv() as empty strings.
233 	 */
234 	if (setlocale(LC_MONETARY, "C") == NULL)
235 		atf_tc_skip("unable to setlocale(): %s", tests[i].locale);
236 
237 	ATF_CHECK_ERRNO(EINVAL, strfmon(actual, sizeof(actual) - 1,
238 	    "[%+n] [%+n]", 123.45, -123.45));
239 
240 	ATF_CHECK_ERRNO(EINVAL, strfmon(actual, sizeof(actual) - 1,
241 	    "[%+i] [%+i]", 123.45, -123.45));
242 }
243 
244 ATF_TC(strfmon_l);
ATF_TC_HEAD(strfmon_l,tc)245 ATF_TC_HEAD(strfmon_l, tc)
246 {
247 	atf_tc_set_md_var(tc, "descr",
248 	    "checks strfmon_l under different locales");
249 }
ATF_TC_BODY(strfmon_l,tc)250 ATF_TC_BODY(strfmon_l, tc)
251 {
252 	const struct {
253 		const char *locale;
254 		const char *expected;
255 	} tests[] = {
256 	    { "C", "[ **1234.57] [ **1234.57]" },
257 	    { "de_DE.UTF-8", "[ **1234,57 €] [ **1.234,57 EUR]" },
258 	    { "en_GB.UTF-8", "[ £**1234.57] [ GBP**1,234.57]" },
259 	};
260 	locale_t loc;
261 	size_t i;
262 	char buf[100];
263 
264 	for (i = 0; i < nitems(tests); ++i) {
265 		loc = newlocale(LC_MONETARY_MASK, tests[i].locale, NULL);
266 		ATF_REQUIRE(loc != NULL);
267 
268 		strfmon_l(buf, sizeof(buf) - 1, loc, "[%^=*#6n] [%=*#6i]",
269 		    1234.567, 1234.567);
270 		ATF_REQUIRE_STREQ(tests[i].expected, buf);
271 
272 		freelocale(loc);
273 	}
274 }
275 
ATF_TP_ADD_TCS(tp)276 ATF_TP_ADD_TCS(tp)
277 {
278 	ATF_TP_ADD_TC(tp, strfmon_locale_thousands);
279 	ATF_TP_ADD_TC(tp, strfmon_examples);
280 	ATF_TP_ADD_TC(tp, strfmon_cs_precedes_0);
281 	ATF_TP_ADD_TC(tp, strfmon_cs_precedes_1);
282 	ATF_TP_ADD_TC(tp, strfmon_international_currency_code);
283 	ATF_TP_ADD_TC(tp, strfmon_plus_or_parenthesis);
284 	ATF_TP_ADD_TC(tp, strfmon_l);
285 	return (atf_no_error());
286 }
287