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