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