1 | /* Test for localedef path name handling and normalization. |
2 | Copyright (C) 2020-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* The test runs localedef with various named paths to test for expected |
20 | behaviours dealing with codeset name normalization. That is to say that use |
21 | of UTF-8, and it's variations, are normalized to utf8. Likewise that values |
22 | after the @ are not normalized and left as-is. The test needs to run |
23 | localedef with known input values and then check that the generated path |
24 | matches the expected value after normalization. */ |
25 | |
26 | /* Note: In some cases adding -v (verbose) to localedef changes the exit |
27 | status to a non-zero value because some warnings are only enabled in verbose |
28 | mode. This should probably be changed so warnings are either present or not |
29 | present, regardless of verbosity. POSIX requires that any warnings cause the |
30 | exit status to be non-zero. */ |
31 | |
32 | #include <array_length.h> |
33 | #include <sys/types.h> |
34 | #include <sys/stat.h> |
35 | #include <unistd.h> |
36 | |
37 | #include <support/capture_subprocess.h> |
38 | #include <support/check.h> |
39 | #include <support/support.h> |
40 | #include <support/xunistd.h> |
41 | #include <support/xthread.h> |
42 | |
43 | /* Full path to localedef. */ |
44 | static const char *prog; |
45 | |
46 | /* Execute localedef in a subprocess. */ |
47 | static void |
48 | execv_wrapper (void *args) |
49 | { |
50 | char **argv = args; |
51 | |
52 | execv (path: prog, argv: argv); |
53 | FAIL_EXIT1 ("execv: %m" ); |
54 | } |
55 | |
56 | struct test_closure |
57 | { |
58 | /* Arguments for running localedef. */ |
59 | const char *const argv[16]; |
60 | /* Expected directory name for compiled locale. */ |
61 | const char *exp; |
62 | /* Expected path to compiled locale. */ |
63 | const char *complocaledir; |
64 | }; |
65 | |
66 | /* Run localedef with DATA.ARGV arguments (NULL terminated), and expect path to |
67 | the compiled locale is "DATA.COMPLOCALEDIR/DATA.EXP". */ |
68 | static void * |
69 | run_test (void *closure) |
70 | { |
71 | struct test_closure *data = closure; |
72 | const char * const *args = data->argv; |
73 | const char *exp = data->exp; |
74 | const char *complocaledir = data->complocaledir; |
75 | struct stat64 fs; |
76 | |
77 | /* Expected output path. */ |
78 | const char *path = xasprintf (format: "%s/%s" , complocaledir, exp); |
79 | |
80 | /* Run test. */ |
81 | struct support_capture_subprocess result; |
82 | result = support_capture_subprocess (callback: execv_wrapper, closure: (void *)args); |
83 | support_capture_subprocess_check (&result, context: "execv" , status_or_signal: 0, allowed: sc_allow_none); |
84 | support_capture_subprocess_free (&result); |
85 | |
86 | /* Verify path is present and is a directory. */ |
87 | xstat (path, &fs); |
88 | if (!S_ISDIR (fs.st_mode)) |
89 | { |
90 | support_record_failure (); |
91 | printf (format: "error: Directory '%s' does not exist.\n" , path); |
92 | return (void *) -1ULL; |
93 | } |
94 | |
95 | return NULL; |
96 | } |
97 | |
98 | static int |
99 | do_test (void) |
100 | { |
101 | /* We are running as root inside the container. */ |
102 | prog = xasprintf (format: "%s/localedef" , support_bindir_prefix); |
103 | |
104 | /* We need an arbitrary absolute path for localedef output. |
105 | Writing to a non-default absolute path disables any kind |
106 | of path normalization since we expect the user wants the path |
107 | exactly as they specified it. */ |
108 | #define ABSDIR "/output" |
109 | xmkdirp (ABSDIR, 0777); |
110 | |
111 | /* It takes ~10 seconds to serially execute 9 localedef test. We run the |
112 | compilations in parallel to reduce test time. We don't want to split |
113 | this out into distinct tests because it would require multiple chroots. |
114 | Batching the same localedef tests saves disk space during testing. */ |
115 | |
116 | struct test_closure tests[] = |
117 | { |
118 | /* Test 1: Expected normalization. |
119 | Run localedef and expect output in $(complocaledir)/en_US1.utf8, |
120 | with normalization changing UTF-8 to utf8. */ |
121 | { |
122 | .argv = { (const char *const) prog, |
123 | "--no-archive" , |
124 | "-i" , "en_US" , |
125 | "-f" , "UTF-8" , |
126 | "en_US1.UTF-8" , NULL }, |
127 | .exp = "en_US1.utf8" , |
128 | .complocaledir = support_complocaledir_prefix |
129 | }, |
130 | /* Test 2: No normalization past '@'. |
131 | Run localedef and expect output in $(complocaledir)/en_US2.utf8@tEsT, |
132 | with normalization changing UTF-8@tEsT to utf8@tEsT (everything after |
133 | @ is untouched). */ |
134 | { |
135 | .argv = { prog, |
136 | "--no-archive" , |
137 | "-i" , "en_US" , |
138 | "-f" , "UTF-8" , |
139 | "en_US2.UTF-8@tEsT" , NULL }, |
140 | .exp = "en_US2.utf8@tEsT" , |
141 | .complocaledir = support_complocaledir_prefix |
142 | }, |
143 | /* Test 3: No normalization past '@' despite period. |
144 | Run localedef and expect output in $(complocaledir)/en_US3@tEsT.UTF-8, |
145 | with normalization changing nothing (everything after @ is untouched) |
146 | despite there being a period near the end. */ |
147 | { |
148 | .argv = { prog, |
149 | "--no-archive" , |
150 | "-i" , "en_US" , |
151 | "-f" , "UTF-8" , |
152 | "en_US3@tEsT.UTF-8" , NULL }, |
153 | .exp = "en_US3@tEsT.UTF-8" , |
154 | .complocaledir = support_complocaledir_prefix |
155 | }, |
156 | /* Test 4: Normalize numeric codeset by adding 'iso' prefix. |
157 | Run localedef and expect output in $(complocaledir)/en_US4.88591, |
158 | with normalization changing 88591 to iso88591. */ |
159 | { |
160 | .argv = { prog, |
161 | "--no-archive" , |
162 | "-i" , "en_US" , |
163 | "-f" , "UTF-8" , |
164 | "en_US4.88591" , NULL }, |
165 | .exp = "en_US4.iso88591" , |
166 | .complocaledir = support_complocaledir_prefix |
167 | }, |
168 | /* Test 5: Don't add 'iso' prefix if first char is alpha. |
169 | Run localedef and expect output in $(complocaledir)/en_US5.a88591, |
170 | with normalization changing nothing. */ |
171 | { |
172 | .argv = { prog, |
173 | "--no-archive" , |
174 | "-i" , "en_US" , |
175 | "-f" , "UTF-8" , |
176 | "en_US5.a88591" , NULL }, |
177 | .exp = "en_US5.a88591" , |
178 | .complocaledir = support_complocaledir_prefix |
179 | }, |
180 | /* Test 6: Don't add 'iso' prefix if last char is alpha. |
181 | Run localedef and expect output in $(complocaledir)/en_US6.88591a, |
182 | with normalization changing nothing. */ |
183 | { |
184 | .argv = { prog, |
185 | "--no-archive" , |
186 | "-i" , "en_US" , |
187 | "-f" , "UTF-8" , |
188 | "en_US6.88591a" , NULL }, |
189 | .exp = "en_US6.88591a" , |
190 | .complocaledir = support_complocaledir_prefix |
191 | }, |
192 | /* Test 7: Don't normalize anything with an absolute path. |
193 | Run localedef and expect output in ABSDIR/en_US7.UTF-8, |
194 | with normalization changing nothing. */ |
195 | { |
196 | .argv = { prog, |
197 | "--no-archive" , |
198 | "-i" , "en_US" , |
199 | "-f" , "UTF-8" , |
200 | ABSDIR "/en_US7.UTF-8" , NULL }, |
201 | .exp = "en_US7.UTF-8" , |
202 | .complocaledir = ABSDIR |
203 | }, |
204 | /* Test 8: Don't normalize anything with an absolute path. |
205 | Run localedef and expect output in ABSDIR/en_US8.UTF-8@tEsT, |
206 | with normalization changing nothing. */ |
207 | { |
208 | .argv = { prog, |
209 | "--no-archive" , |
210 | "-i" , "en_US" , |
211 | "-f" , "UTF-8" , |
212 | ABSDIR "/en_US8.UTF-8@tEsT" , NULL }, |
213 | .exp = "en_US8.UTF-8@tEsT" , |
214 | .complocaledir = ABSDIR |
215 | }, |
216 | /* Test 9: Don't normalize anything with an absolute path. |
217 | Run localedef and expect output in ABSDIR/en_US9@tEsT.UTF-8, |
218 | with normalization changing nothing. */ |
219 | { |
220 | .argv = { prog, |
221 | "--no-archive" , |
222 | "-i" , "en_US" , |
223 | "-f" , "UTF-8" , |
224 | ABSDIR "/en_US9@tEsT.UTF-8" , NULL }, |
225 | .exp = "en_US9@tEsT.UTF-8" , |
226 | .complocaledir = ABSDIR |
227 | } |
228 | }; |
229 | |
230 | /* Do not run more threads than the maximum of online CPUs. */ |
231 | size_t ntests = array_length (tests); |
232 | long int cpus = sysconf (_SC_NPROCESSORS_ONLN); |
233 | cpus = cpus == -1 ? 1 : cpus; |
234 | printf (format: "info: cpus=%ld ntests=%zu\n" , cpus, ntests); |
235 | |
236 | pthread_t thr[ntests]; |
237 | |
238 | for (int i = 0; i < ntests; i += cpus) |
239 | { |
240 | int max = i + cpus; |
241 | if (max > ntests) |
242 | max = ntests; |
243 | |
244 | for (int j = i; j < max; j++) |
245 | thr[j] = xpthread_create (NULL, thread_func: run_test, closure: &tests[j]); |
246 | |
247 | for (int j = i; j < max; j++) |
248 | TEST_VERIFY (xpthread_join (thr[j]) == NULL); |
249 | } |
250 | |
251 | return 0; |
252 | } |
253 | |
254 | #define TIMEOUT 30 |
255 | #include <support/test-driver.c> |
256 | |