]>
Commit | Line | Data |
---|---|---|
688903eb | 1 | /* Copyright (C) 2017-2018 Free Software Foundation, Inc. |
544ce845 ZW |
2 | This file is part of the GNU C Library. |
3 | ||
4 | The GNU C Library is free software; you can redistribute it and/or | |
5 | modify it under the terms of the GNU Lesser General Public | |
6 | License as published by the Free Software Foundation; either | |
7 | version 2.1 of the License, or (at your option) any later version. | |
8 | ||
9 | The GNU C Library is distributed in the hope that it will be useful, | |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | Lesser General Public License for more details. | |
13 | ||
14 | You should have received a copy of the GNU Lesser General Public | |
15 | License along with the GNU C Library; if not, see | |
16 | <http://www.gnu.org/licenses/>. */ | |
17 | ||
18 | /* fprintf is a cancellation point, but getopt is not supposed to be a | |
19 | cancellation point, even when it prints error messages. */ | |
20 | ||
21 | /* Note: getopt.h must be included first in this file, so we get the | |
22 | GNU getopt rather than the POSIX one. */ | |
23 | #include <getopt.h> | |
24 | ||
25 | #include <stdbool.h> | |
26 | #include <stdio.h> | |
27 | #include <stdlib.h> | |
28 | ||
29 | #include <fcntl.h> | |
30 | #include <pthread.h> | |
31 | #include <unistd.h> | |
32 | ||
33 | #include <support/support.h> | |
34 | #include <support/temp_file.h> | |
35 | #include <support/xthread.h> | |
36 | ||
37 | static bool | |
38 | check_stderr (bool expect_errmsg, FILE *stderr_trapped) | |
39 | { | |
40 | static char *lineptr = 0; | |
41 | static size_t linesz = 0; | |
42 | ||
43 | bool got_errmsg = false; | |
44 | rewind (stderr_trapped); | |
45 | while (getline (&lineptr, &linesz, stderr_trapped) > 0) | |
46 | { | |
47 | got_errmsg = true; | |
48 | fputs (lineptr, stdout); | |
49 | } | |
50 | rewind (stderr_trapped); | |
51 | ftruncate (fileno (stderr_trapped), 0); | |
52 | return got_errmsg == expect_errmsg; | |
53 | } | |
54 | ||
55 | struct test_short | |
56 | { | |
57 | const char *label; | |
58 | const char *opts; | |
59 | const char *const argv[8]; | |
60 | int argc; | |
61 | bool expect_errmsg; | |
62 | }; | |
63 | ||
64 | struct test_long | |
65 | { | |
66 | const char *label; | |
67 | const char *opts; | |
68 | const struct option longopts[4]; | |
69 | const char *const argv[8]; | |
70 | int argc; | |
71 | bool expect_errmsg; | |
72 | }; | |
73 | ||
74 | #define DEFINE_TEST_DRIVER(test_type, getopt_call) \ | |
75 | struct test_type##_tdata \ | |
76 | { \ | |
77 | pthread_mutex_t *sync; \ | |
78 | const struct test_type *tcase; \ | |
79 | bool ok; \ | |
80 | }; \ | |
81 | \ | |
82 | static void * \ | |
83 | test_type##_threadproc (void *data) \ | |
84 | { \ | |
85 | struct test_type##_tdata *tdata = data; \ | |
86 | const struct test_type *tc = tdata->tcase; \ | |
87 | \ | |
88 | xpthread_mutex_lock (tdata->sync); \ | |
89 | xpthread_mutex_unlock (tdata->sync); \ | |
90 | \ | |
91 | /* At this point, this thread has a cancellation pending. \ | |
92 | We should still be able to get all the way through a getopt \ | |
93 | loop without being cancelled. \ | |
94 | Setting optind to 0 forces getopt to reinitialize itself. */ \ | |
95 | optind = 0; \ | |
96 | opterr = 1; \ | |
97 | optopt = 0; \ | |
98 | while (getopt_call != -1) \ | |
99 | ; \ | |
100 | tdata->ok = true; \ | |
101 | \ | |
102 | pthread_testcancel(); \ | |
103 | return 0; \ | |
104 | } \ | |
105 | \ | |
106 | static bool \ | |
107 | do_##test_type (const struct test_type *tcase, FILE *stderr_trapped) \ | |
108 | { \ | |
109 | pthread_mutex_t sync; \ | |
110 | struct test_type##_tdata tdata; \ | |
111 | \ | |
112 | printf("begin: %s\n", tcase->label); \ | |
113 | \ | |
114 | xpthread_mutex_init (&sync, 0); \ | |
115 | xpthread_mutex_lock (&sync); \ | |
116 | \ | |
117 | tdata.sync = &sync; \ | |
118 | tdata.tcase = tcase; \ | |
119 | tdata.ok = false; \ | |
120 | \ | |
121 | pthread_t thr = xpthread_create (0, test_type##_threadproc, \ | |
122 | (void *)&tdata); \ | |
123 | xpthread_cancel (thr); \ | |
124 | xpthread_mutex_unlock (&sync); \ | |
125 | void *rv = xpthread_join (thr); \ | |
126 | \ | |
127 | xpthread_mutex_destroy (&sync); \ | |
128 | \ | |
129 | bool ok = true; \ | |
130 | if (!check_stderr (tcase->expect_errmsg, stderr_trapped)) \ | |
131 | { \ | |
132 | ok = false; \ | |
133 | printf("FAIL: %s: stderr not as expected\n", tcase->label); \ | |
134 | } \ | |
135 | if (!tdata.ok) \ | |
136 | { \ | |
137 | ok = false; \ | |
138 | printf("FAIL: %s: did not complete loop\n", tcase->label); \ | |
139 | } \ | |
140 | if (rv != PTHREAD_CANCELED) \ | |
141 | { \ | |
142 | ok = false; \ | |
143 | printf("FAIL: %s: thread was not cancelled\n", tcase->label); \ | |
144 | } \ | |
145 | if (ok) \ | |
146 | printf ("pass: %s\n", tcase->label); \ | |
147 | return ok; \ | |
148 | } | |
149 | ||
150 | DEFINE_TEST_DRIVER (test_short, | |
151 | getopt (tc->argc, (char *const *)tc->argv, tc->opts)) | |
152 | DEFINE_TEST_DRIVER (test_long, | |
153 | getopt_long (tc->argc, (char *const *)tc->argv, | |
154 | tc->opts, tc->longopts, 0)) | |
155 | ||
156 | /* Caution: all option strings must begin with a '+' or '-' so that | |
157 | getopt does not attempt to permute the argument vector (which is in | |
158 | read-only memory). */ | |
159 | const struct test_short tests_short[] = { | |
160 | { "no errors", | |
161 | "+ab:c", { "program", "-ac", "-b", "x", 0 }, 4, false }, | |
162 | { "invalid option", | |
163 | "+ab:c", { "program", "-d", 0 }, 2, true }, | |
164 | { "missing argument", | |
165 | "+ab:c", { "program", "-b", 0 }, 2, true }, | |
166 | { 0 } | |
167 | }; | |
168 | ||
169 | const struct test_long tests_long[] = { | |
170 | { "no errors (long)", | |
171 | "+ab:c", { { "alpha", no_argument, 0, 'a' }, | |
172 | { "bravo", required_argument, 0, 'b' }, | |
173 | { "charlie", no_argument, 0, 'c' }, | |
174 | { 0 } }, | |
175 | { "program", "-a", "--charlie", "--bravo=x", 0 }, 4, false }, | |
176 | ||
177 | { "invalid option (long)", | |
178 | "+ab:c", { { "alpha", no_argument, 0, 'a' }, | |
179 | { "bravo", required_argument, 0, 'b' }, | |
180 | { "charlie", no_argument, 0, 'c' }, | |
181 | { 0 } }, | |
182 | { "program", "-a", "--charlie", "--dingo", 0 }, 4, true }, | |
183 | ||
184 | { "unwanted argument", | |
185 | "+ab:c", { { "alpha", no_argument, 0, 'a' }, | |
186 | { "bravo", required_argument, 0, 'b' }, | |
187 | { "charlie", no_argument, 0, 'c' }, | |
188 | { 0 } }, | |
189 | { "program", "-a", "--charlie=dingo", "--bravo=x", 0 }, 4, true }, | |
190 | ||
191 | { "missing argument", | |
192 | "+ab:c", { { "alpha", no_argument, 0, 'a' }, | |
193 | { "bravo", required_argument, 0, 'b' }, | |
194 | { "charlie", no_argument, 0, 'c' }, | |
195 | { 0 } }, | |
196 | { "program", "-a", "--charlie", "--bravo", 0 }, 4, true }, | |
197 | ||
198 | { "ambiguous options", | |
199 | "+uvw", { { "veni", no_argument, 0, 'u' }, | |
200 | { "vedi", no_argument, 0, 'v' }, | |
201 | { "veci", no_argument, 0, 'w' } }, | |
202 | { "program", "--ve", 0 }, 2, true }, | |
203 | ||
204 | { "no errors (long W)", | |
205 | "+ab:cW;", { { "alpha", no_argument, 0, 'a' }, | |
206 | { "bravo", required_argument, 0, 'b' }, | |
207 | { "charlie", no_argument, 0, 'c' }, | |
208 | { 0 } }, | |
209 | { "program", "-a", "-W", "charlie", "-W", "bravo=x", 0 }, 6, false }, | |
210 | ||
211 | { "missing argument (W itself)", | |
212 | "+ab:cW;", { { "alpha", no_argument, 0, 'a' }, | |
213 | { "bravo", required_argument, 0, 'b' }, | |
214 | { "charlie", no_argument, 0, 'c' }, | |
215 | { 0 } }, | |
216 | { "program", "-a", "-W", "charlie", "-W", 0 }, 5, true }, | |
217 | ||
218 | { "missing argument (W longopt)", | |
219 | "+ab:cW;", { { "alpha", no_argument, 0, 'a' }, | |
220 | { "bravo", required_argument, 0, 'b' }, | |
221 | { "charlie", no_argument, 0, 'c' }, | |
222 | { 0 } }, | |
223 | { "program", "-a", "-W", "charlie", "-W", "bravo", 0 }, 6, true }, | |
224 | ||
225 | { "unwanted argument (W longopt)", | |
226 | "+ab:cW;", { { "alpha", no_argument, 0, 'a' }, | |
227 | { "bravo", required_argument, 0, 'b' }, | |
228 | { "charlie", no_argument, 0, 'c' }, | |
229 | { 0 } }, | |
230 | { "program", "-a", "-W", "charlie=dingo", "-W", "bravo=x", 0 }, 6, true }, | |
231 | ||
232 | { "ambiguous options (W)", | |
233 | "+uvwW;", { { "veni", no_argument, 0, 'u' }, | |
234 | { "vedi", no_argument, 0, 'v' }, | |
235 | { "veci", no_argument, 0, 'w' } }, | |
236 | { "program", "-W", "ve", 0 }, 3, true }, | |
237 | ||
238 | { 0 } | |
239 | }; | |
240 | ||
241 | static int | |
242 | do_test (void) | |
243 | { | |
244 | int stderr_trap = create_temp_file ("stderr", 0); | |
245 | if (stderr_trap < 0) | |
246 | { | |
247 | perror ("create_temp_file"); | |
248 | return 1; | |
249 | } | |
250 | FILE *stderr_trapped = fdopen(stderr_trap, "r+"); | |
251 | if (!stderr_trapped) | |
252 | { | |
253 | perror ("fdopen"); | |
254 | return 1; | |
255 | } | |
256 | int old_stderr = dup (fileno (stderr)); | |
257 | if (old_stderr < 0) | |
258 | { | |
259 | perror ("dup"); | |
260 | return 1; | |
261 | } | |
262 | if (dup2 (stderr_trap, 2) < 0) | |
263 | { | |
264 | perror ("dup2"); | |
265 | return 1; | |
266 | } | |
267 | rewind (stderr); | |
268 | ||
269 | bool success = true; | |
270 | ||
271 | for (const struct test_short *tcase = tests_short; tcase->label; tcase++) | |
272 | success = do_test_short (tcase, stderr_trapped) && success; | |
273 | ||
274 | for (const struct test_long *tcase = tests_long; tcase->label; tcase++) | |
275 | success = do_test_long (tcase, stderr_trapped) && success; | |
276 | ||
277 | dup2 (old_stderr, 2); | |
278 | close (old_stderr); | |
279 | fclose (stderr_trapped); | |
280 | ||
281 | return success ? 0 : 1; | |
282 | } | |
283 | ||
284 | #include <support/test-driver.c> |