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