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 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 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 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 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 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