xref: /illumos-gate/usr/src/test/libc-tests/tests/getlocname.c (revision e8d3efa1c56e5f2b5368600a2baeb7b1d54a07f8)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2025 Oxide Computer Company
14  * Copyright 2026 Bill Sommerfeld <sommerfeld@hamachi.org>
15  */
16 
17 /*
18  * Tests for getlocalename_l(3C). The libc tests depends on locale/ar,
19  * locale/de, locale/en, and locale/ja. We limit ourselves to these locales,
20  * plus C.UTF-8. We use a combination of global locales set via setlocale(),
21  * locales set with uselocale(), and composite locale.
22  */
23 
24 #include <err.h>
25 #include <stdlib.h>
26 #include <locale.h>
27 #include <sys/sysmacros.h>
28 #include <sys/debug.h>
29 #include <stdbool.h>
30 #include <string.h>
31 
32 typedef struct {
33 	int lc_cat;
34 	const char *lc_name;
35 } lc_cat_t;
36 
37 static const lc_cat_t lc_cats[] = {
38 	{ LC_CTYPE, "LC_CTYPE" },
39 	{ LC_NUMERIC, "LC_NUMERIC" },
40 	{ LC_TIME, "LC_TIME" },
41 	{ LC_COLLATE, "LC_COLLATE" },
42 	{ LC_MONETARY, "LC_MONETARY" },
43 	{ LC_MESSAGES, "LC_MESSAGES" },
44 	{ LC_ALL, "LC_ALL" }
45 };
46 CTASSERT(ARRAY_SIZE(lc_cats) == LC_ALL + 1);
47 
48 typedef struct {
49 	int lc_cat;
50 	int lc_mask;
51 	const char *lc_loc;
52 } lc_comp_t;
53 
54 static const lc_comp_t composite[] = {
55 	{ LC_CTYPE, LC_CTYPE_MASK, "en_US.UTF-8" },
56 	{ LC_NUMERIC, LC_NUMERIC_MASK, "en_GB.UTF-8" },
57 	{ LC_TIME, LC_TIME_MASK, "de_DE.UTF-8" },
58 	{ LC_COLLATE, LC_COLLATE_MASK, "ar_EG.UTF-8" },
59 	{ LC_MONETARY, LC_MONETARY_MASK, "ja_JP.UTF-8" },
60 	{ LC_MESSAGES, LC_MESSAGES_MASK, "C.UTF-8" }
61 };
62 
63 static const char *locales[] = { "C.UTF-8", "ja_JP.UTF-8", "de_DE.UTF-8",
64 	"en_US.UTF-8", "en_GB.UTF-8" };
65 
66 /*
67  * Check each category of a locale. These are ordered in exp by their LC_ type
68  * category.
69  */
70 static bool
71 locname_check(const char *desc, locale_t loc, const char **exp_names)
72 {
73 	bool ret = true;
74 
75 	for (size_t i = 0; i < ARRAY_SIZE(lc_cats); i++) {
76 		const char *catname = lc_cats[i].lc_name;
77 		const char *exp = exp_names[lc_cats[i].lc_cat];
78 		const char *name = getlocalename_l(lc_cats[i].lc_cat, loc);
79 		if (name == NULL) {
80 			warnx("TEST FAILED: %s: failed to get locale name for "
81 			    "category %s: expected %s", desc, catname, exp);
82 			ret = false;
83 			continue;
84 		}
85 
86 		if (strcmp(name, exp) != 0) {
87 			warnx("TEST FAILED: %s: got incorrect value for "
88 			    "category %s: expected %s, but got %s", desc,
89 			    catname, exp, name);
90 			ret = false;
91 		} else {
92 			(void) printf("TEST PASSED: %s: category %s\n", desc,
93 			    catname);
94 		}
95 	}
96 
97 	return (ret);
98 }
99 
100 /*
101  * A small wrapper for names that are uniform.
102  */
103 static bool
104 locname_check_all(const char *desc, locale_t loc, const char *exp)
105 {
106 	const char *names[LC_ALL + 1] = { exp, exp, exp, exp, exp, exp, exp };
107 
108 	return (locname_check(desc, loc, names));
109 }
110 
111 /*
112  * Change each locale category one at a time and ensure that we get the expected
113  * locale. Then set it as the global locale and ensure that this is what we
114  * expect. We start from C. We track the names based on an array indexed by the
115  * different categories and use the fact that they're ordered to know what to
116  * check against.
117  *
118  * We also test setting the global locale to the result of this to ensure that
119  * the results of composite locales are usable.
120  */
121 static bool
122 locname_composite(void)
123 {
124 	bool ret = true;
125 	const char *names[LC_ALL + 1] = { "C", "C", "C", "C", "C", "C", NULL };
126 	locale_t loc = newlocale(LC_ALL_MASK, "C", NULL);
127 
128 	if (loc == NULL) {
129 		errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to create "
130 		    "base C locale for composite tests");
131 	}
132 
133 	for (size_t i = 0; i < ARRAY_SIZE(composite); i++) {
134 		char cname[1024], desc[1024];
135 
136 		names[composite[i].lc_cat] = composite[i].lc_loc;
137 		(void) snprintf(cname, sizeof (cname), "%s/%s/%s/%s/%s/%s",
138 		    names[0], names[1], names[2], names[3], names[4], names[5]);
139 		names[LC_ALL] = cname;
140 
141 		loc = newlocale(composite[i].lc_mask, composite[i].lc_loc,
142 		    loc);
143 		if (loc == NULL) {
144 			err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
145 			    "evolve composite locale to %s", cname);
146 		}
147 
148 		(void) snprintf(desc, sizeof (desc), "composite %s (%d)",
149 		    composite[i].lc_loc, composite[i].lc_cat);
150 		if (!locname_check(desc, loc, names))
151 			ret = false;
152 
153 		locale_t loc2 = duplocale(loc);
154 
155 		if (loc2 == NULL) {
156 			errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
157 			    "duplicate composite locale");
158 		}
159 		(void) snprintf(desc, sizeof (desc), "dup composite %s (%d)",
160 		    composite[i].lc_loc, composite[i].lc_cat);
161 		if (!locname_check(desc, loc2, names))
162 			ret = false;
163 
164 		freelocale(loc2);
165 
166 		if (setlocale(LC_ALL, getlocalename_l(LC_ALL, loc)) == NULL) {
167 			err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
168 			    "set global locale to composite %s", cname);
169 		}
170 
171 		(void) snprintf(desc, sizeof (desc), "global composite %s (%d)",
172 		    composite[i].lc_loc, composite[i].lc_cat);
173 		if (!locname_check(desc, loc, names))
174 			ret = false;
175 	}
176 	freelocale(loc);
177 
178 	return (ret);
179 }
180 
181 static bool
182 locname_invalid(void)
183 {
184 	bool ret = true;
185 
186 	if (getlocalename_l(LC_ALL, NULL) != NULL) {
187 		ret = false;
188 		warnx("TEST FAILED: passing invalid locale: string incorrectly "
189 		    "returned");
190 	} else {
191 		(void) printf("TEST PASSED: invalid locale\n");
192 	}
193 
194 	if (getlocalename_l(12345, LC_GLOBAL_LOCALE) != NULL) {
195 		ret = false;
196 		warnx("TEST FAILED: passing invalid category (1): string "
197 		    "incorrectly returned");
198 	} else {
199 		(void) printf("TEST PASSED: invalid category (1)\n");
200 	}
201 
202 	if (getlocalename_l(0x7777, uselocale(NULL)) != NULL) {
203 		ret = false;
204 		warnx("TEST FAILED: passing invalid category (2): string "
205 		    "incorrectly returned");
206 	} else {
207 		(void) printf("TEST PASSED: invalid category (2)\n");
208 	}
209 
210 	return (ret);
211 }
212 
213 int
214 main(void)
215 {
216 	int ret = EXIT_SUCCESS;
217 
218 	/*
219 	 * The default locale should be C.
220 	 */
221 	if (!locname_check_all("global locale: default", LC_GLOBAL_LOCALE, "C"))
222 		ret = EXIT_FAILURE;
223 
224 	/*
225 	 * Test non-composite locales. We always do the thread-specific locale
226 	 * first so that way we can catch an erroneous locale.
227 	 */
228 	for (size_t i = 0; i < ARRAY_SIZE(locales); i++) {
229 		char desc[1024];
230 		locale_t loc, loc2;
231 
232 		loc = newlocale(LC_ALL_MASK, locales[i], NULL);
233 		if (loc == NULL) {
234 			err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
235 			    "construct locale %s", locales[i]);
236 		}
237 
238 		(void) snprintf(desc, sizeof (desc),
239 		    "%s: newlocale", locales[i]);
240 		if (!locname_check_all(desc, loc, locales[i]))
241 			ret = EXIT_FAILURE;
242 
243 		loc2 = duplocale(loc);
244 		if (loc2 == NULL) {
245 			err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
246 			    "duplicate locale %s", locales[i]);
247 		}
248 
249 		(void) snprintf(desc, sizeof (desc),
250 		    "%s: duplocale", locales[i]);
251 		if (!locname_check_all(desc, loc2, locales[i]))
252 			ret = EXIT_FAILURE;
253 		freelocale(loc2);
254 
255 		(void) snprintf(desc, sizeof (desc),
256 		    "%s: thread locale", locales[i]);
257 		if (uselocale(loc) == NULL) {
258 			err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
259 			    "transition thread to thread-specific locale %s, "
260 			    "cannot continue", locales[i]);
261 		}
262 		if (!locname_check_all(desc, uselocale(NULL), locales[i]))
263 			ret = EXIT_FAILURE;
264 		if (uselocale(LC_GLOBAL_LOCALE) == NULL) {
265 			err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
266 			    "transition thread back to global locale, cannot "
267 			    "continue");
268 		}
269 		freelocale(loc);
270 
271 		(void) snprintf(desc, sizeof (desc),
272 		    "%s: global locale", locales[i]);
273 		if (setlocale(LC_ALL, locales[i]) == NULL) {
274 			errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
275 			    "set global locale to %s", locales[i]);
276 		}
277 		if (!locname_check_all(desc, LC_GLOBAL_LOCALE, locales[i]))
278 			ret = EXIT_FAILURE;
279 	}
280 
281 	if (!locname_composite())
282 		ret = EXIT_FAILURE;
283 
284 	if (!locname_invalid())
285 		ret = EXIT_FAILURE;
286 
287 	if (ret == EXIT_SUCCESS) {
288 		(void) printf("All tests completed successfully\n");
289 	}
290 
291 	return (ret);
292 }
293