]>
Commit | Line | Data |
---|---|---|
91b6eb11 | 1 | /* Test capturing output from a subprocess. |
d614a753 | 2 | Copyright (C) 2017-2020 Free Software Foundation, Inc. |
91b6eb11 FW |
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 | |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
91b6eb11 FW |
18 | |
19 | #include <stdbool.h> | |
20 | #include <stdio.h> | |
21 | #include <stdlib.h> | |
22 | #include <string.h> | |
23 | #include <support/capture_subprocess.h> | |
24 | #include <support/check.h> | |
25 | #include <support/support.h> | |
0e169691 | 26 | #include <support/temp_file.h> |
91b6eb11 FW |
27 | #include <sys/wait.h> |
28 | #include <unistd.h> | |
0e169691 AZ |
29 | #include <paths.h> |
30 | #include <getopt.h> | |
31 | #include <limits.h> | |
32 | #include <errno.h> | |
33 | #include <array_length.h> | |
34 | ||
35 | /* Nonzero if the program gets called via 'exec'. */ | |
36 | static int restart; | |
37 | ||
38 | /* Hold the four initial argument used to respawn the process. */ | |
39 | static char *initial_argv[5]; | |
91b6eb11 FW |
40 | |
41 | /* Write one byte at *P to FD and advance *P. Do nothing if *P is | |
42 | '\0'. */ | |
43 | static void | |
44 | transfer (const unsigned char **p, int fd) | |
45 | { | |
46 | if (**p != '\0') | |
47 | { | |
48 | TEST_VERIFY (write (fd, *p, 1) == 1); | |
49 | ++*p; | |
50 | } | |
51 | } | |
52 | ||
53 | /* Determine the order in which stdout and stderr are written. */ | |
54 | enum write_mode { out_first, err_first, interleave, | |
55 | write_mode_last = interleave }; | |
56 | ||
0e169691 AZ |
57 | static const char * |
58 | write_mode_to_str (enum write_mode mode) | |
59 | { | |
60 | switch (mode) | |
61 | { | |
62 | case out_first: return "out_first"; | |
63 | case err_first: return "err_first"; | |
64 | case interleave: return "interleave"; | |
65 | default: return "write_mode_last"; | |
66 | } | |
67 | } | |
68 | ||
69 | static enum write_mode | |
70 | str_to_write_mode (const char *mode) | |
71 | { | |
72 | if (strcmp (mode, "out_first") == 0) | |
73 | return out_first; | |
74 | else if (strcmp (mode, "err_first") == 0) | |
75 | return err_first; | |
76 | else if (strcmp (mode, "interleave") == 0) | |
77 | return interleave; | |
78 | return write_mode_last; | |
79 | } | |
80 | ||
91b6eb11 FW |
81 | /* Describe what to write in the subprocess. */ |
82 | struct test | |
83 | { | |
84 | char *out; | |
85 | char *err; | |
86 | enum write_mode write_mode; | |
87 | int signal; | |
88 | int status; | |
89 | }; | |
90 | ||
0e169691 AZ |
91 | _Noreturn static void |
92 | test_common (const struct test *test) | |
91b6eb11 | 93 | { |
91b6eb11 FW |
94 | bool mode_ok = false; |
95 | switch (test->write_mode) | |
96 | { | |
97 | case out_first: | |
98 | TEST_VERIFY (fputs (test->out, stdout) >= 0); | |
99 | TEST_VERIFY (fflush (stdout) == 0); | |
100 | TEST_VERIFY (fputs (test->err, stderr) >= 0); | |
101 | TEST_VERIFY (fflush (stderr) == 0); | |
102 | mode_ok = true; | |
103 | break; | |
104 | case err_first: | |
105 | TEST_VERIFY (fputs (test->err, stderr) >= 0); | |
106 | TEST_VERIFY (fflush (stderr) == 0); | |
107 | TEST_VERIFY (fputs (test->out, stdout) >= 0); | |
108 | TEST_VERIFY (fflush (stdout) == 0); | |
109 | mode_ok = true; | |
110 | break; | |
111 | case interleave: | |
112 | { | |
113 | const unsigned char *pout = (const unsigned char *) test->out; | |
114 | const unsigned char *perr = (const unsigned char *) test->err; | |
115 | do | |
116 | { | |
117 | transfer (&pout, STDOUT_FILENO); | |
118 | transfer (&perr, STDERR_FILENO); | |
119 | } | |
120 | while (*pout != '\0' || *perr != '\0'); | |
121 | } | |
122 | mode_ok = true; | |
123 | break; | |
124 | } | |
125 | TEST_VERIFY (mode_ok); | |
126 | ||
127 | if (test->signal != 0) | |
128 | raise (test->signal); | |
129 | exit (test->status); | |
130 | } | |
131 | ||
0e169691 AZ |
132 | static int |
133 | parse_int (const char *str) | |
134 | { | |
135 | char *endptr; | |
183083c3 CD |
136 | long int ret; |
137 | errno = 0; | |
138 | ret = strtol (str, &endptr, 10); | |
0e169691 AZ |
139 | TEST_COMPARE (errno, 0); |
140 | TEST_VERIFY (ret >= 0 && ret <= INT_MAX); | |
141 | return ret; | |
142 | } | |
143 | ||
144 | /* For use with support_capture_subprogram. */ | |
145 | _Noreturn static void | |
146 | handle_restart (char *out, char *err, const char *write_mode, | |
147 | const char *signal, const char *status) | |
148 | { | |
149 | struct test test = | |
150 | { | |
151 | out, | |
152 | err, | |
153 | str_to_write_mode (write_mode), | |
154 | parse_int (signal), | |
155 | parse_int (status) | |
156 | }; | |
157 | test_common (&test); | |
158 | } | |
159 | ||
160 | /* For use with support_capture_subprocess. */ | |
161 | _Noreturn static void | |
162 | callback (void *closure) | |
163 | { | |
164 | const struct test *test = closure; | |
165 | test_common (test); | |
166 | } | |
167 | ||
91b6eb11 FW |
168 | /* Create a heap-allocated random string of letters. */ |
169 | static char * | |
170 | random_string (size_t length) | |
171 | { | |
172 | char *result = xmalloc (length + 1); | |
173 | for (size_t i = 0; i < length; ++i) | |
174 | result[i] = 'a' + (rand () % 26); | |
175 | result[length] = '\0'; | |
176 | return result; | |
177 | } | |
178 | ||
179 | /* Check that the specific stream from the captured subprocess matches | |
180 | expectations. */ | |
181 | static void | |
182 | check_stream (const char *what, const struct xmemstream *stream, | |
183 | const char *expected) | |
184 | { | |
185 | if (strcmp (stream->buffer, expected) != 0) | |
186 | { | |
187 | support_record_failure (); | |
188 | printf ("error: captured %s data incorrect\n" | |
189 | " expected: %s\n" | |
190 | " actual: %s\n", | |
191 | what, expected, stream->buffer); | |
192 | } | |
193 | if (stream->length != strlen (expected)) | |
194 | { | |
195 | support_record_failure (); | |
196 | printf ("error: captured %s data length incorrect\n" | |
197 | " expected: %zu\n" | |
198 | " actual: %zu\n", | |
199 | what, strlen (expected), stream->length); | |
200 | } | |
201 | } | |
202 | ||
0e169691 AZ |
203 | static struct support_capture_subprocess |
204 | do_subprocess (struct test *test) | |
205 | { | |
206 | return support_capture_subprocess (callback, test); | |
207 | } | |
208 | ||
209 | static struct support_capture_subprocess | |
210 | do_subprogram (const struct test *test) | |
211 | { | |
212 | /* Three digits per byte plus null terminator. */ | |
213 | char signalstr[3 * sizeof(int) + 1]; | |
214 | snprintf (signalstr, sizeof (signalstr), "%d", test->signal); | |
215 | char statusstr[3 * sizeof(int) + 1]; | |
216 | snprintf (statusstr, sizeof (statusstr), "%d", test->status); | |
217 | ||
218 | int argc = 0; | |
219 | enum { | |
220 | /* 4 elements from initial_argv (path to ld.so, '--library-path', the | |
221 | path', and application name'), 2 for restart argument ('--direct', | |
222 | '--restart'), 5 arguments plus NULL. */ | |
223 | argv_size = 12 | |
224 | }; | |
225 | char *args[argv_size]; | |
226 | ||
227 | for (char **arg = initial_argv; *arg != NULL; arg++) | |
228 | args[argc++] = *arg; | |
229 | ||
230 | args[argc++] = (char*) "--direct"; | |
231 | args[argc++] = (char*) "--restart"; | |
232 | ||
233 | args[argc++] = test->out; | |
234 | args[argc++] = test->err; | |
235 | args[argc++] = (char*) write_mode_to_str (test->write_mode); | |
236 | args[argc++] = signalstr; | |
237 | args[argc++] = statusstr; | |
238 | args[argc] = NULL; | |
239 | TEST_VERIFY (argc < argv_size); | |
240 | ||
241 | return support_capture_subprogram (args[0], args); | |
242 | } | |
243 | ||
244 | enum test_type | |
245 | { | |
246 | subprocess, | |
247 | subprogram, | |
248 | }; | |
249 | ||
91b6eb11 | 250 | static int |
0e169691 | 251 | do_multiple_tests (enum test_type type) |
91b6eb11 FW |
252 | { |
253 | const int lengths[] = {0, 1, 17, 512, 20000, -1}; | |
254 | ||
0e169691 | 255 | /* Test multiple combinations of support_capture_sub{process,program}. |
91b6eb11 FW |
256 | |
257 | length_idx_stdout: Index into the lengths array above, | |
258 | controls how many bytes are written by the subprocess to | |
259 | standard output. | |
260 | length_idx_stderr: Same for standard error. | |
261 | write_mode: How standard output and standard error writes are | |
262 | ordered. | |
263 | signal: Exit with no signal if zero, with SIGTERM if one. | |
264 | status: Process exit status: 0 if zero, 3 if one. */ | |
265 | for (int length_idx_stdout = 0; lengths[length_idx_stdout] >= 0; | |
266 | ++length_idx_stdout) | |
267 | for (int length_idx_stderr = 0; lengths[length_idx_stderr] >= 0; | |
268 | ++length_idx_stderr) | |
269 | for (int write_mode = 0; write_mode < write_mode_last; ++write_mode) | |
270 | for (int signal = 0; signal < 2; ++signal) | |
271 | for (int status = 0; status < 2; ++status) | |
272 | { | |
273 | struct test test = | |
274 | { | |
275 | .out = random_string (lengths[length_idx_stdout]), | |
276 | .err = random_string (lengths[length_idx_stderr]), | |
277 | .write_mode = write_mode, | |
278 | .signal = signal * SIGTERM, /* 0 or SIGTERM. */ | |
279 | .status = status * 3, /* 0 or 3. */ | |
280 | }; | |
281 | TEST_VERIFY (strlen (test.out) == lengths[length_idx_stdout]); | |
282 | TEST_VERIFY (strlen (test.err) == lengths[length_idx_stderr]); | |
283 | ||
0e169691 AZ |
284 | struct support_capture_subprocess result |
285 | = type == subprocess ? do_subprocess (&test) | |
286 | : do_subprogram (&test); | |
287 | ||
91b6eb11 FW |
288 | check_stream ("stdout", &result.out, test.out); |
289 | check_stream ("stderr", &result.err, test.err); | |
96cd0558 FW |
290 | |
291 | /* Allowed output for support_capture_subprocess_check. */ | |
292 | int check_allow = 0; | |
293 | if (lengths[length_idx_stdout] > 0) | |
294 | check_allow |= sc_allow_stdout; | |
295 | if (lengths[length_idx_stderr] > 0) | |
296 | check_allow |= sc_allow_stderr; | |
297 | if (check_allow == 0) | |
298 | check_allow = sc_allow_none; | |
299 | ||
91b6eb11 FW |
300 | if (test.signal != 0) |
301 | { | |
302 | TEST_VERIFY (WIFSIGNALED (result.status)); | |
303 | TEST_VERIFY (WTERMSIG (result.status) == test.signal); | |
96cd0558 FW |
304 | support_capture_subprocess_check (&result, "signal", |
305 | -SIGTERM, check_allow); | |
91b6eb11 FW |
306 | } |
307 | else | |
308 | { | |
309 | TEST_VERIFY (WIFEXITED (result.status)); | |
310 | TEST_VERIFY (WEXITSTATUS (result.status) == test.status); | |
96cd0558 FW |
311 | support_capture_subprocess_check (&result, "exit", |
312 | test.status, check_allow); | |
91b6eb11 FW |
313 | } |
314 | support_capture_subprocess_free (&result); | |
315 | free (test.out); | |
316 | free (test.err); | |
317 | } | |
318 | return 0; | |
319 | } | |
320 | ||
0e169691 AZ |
321 | static int |
322 | do_test (int argc, char *argv[]) | |
323 | { | |
324 | /* We must have either: | |
325 | ||
326 | - one or four parameters if called initially: | |
327 | + argv[1]: path for ld.so optional | |
328 | + argv[2]: "--library-path" optional | |
329 | + argv[3]: the library path optional | |
330 | + argv[4]: the application name | |
331 | ||
332 | - six parameters left if called through re-execution: | |
333 | + argv[1]: the application name | |
334 | + argv[2]: the stdout to print | |
335 | + argv[3]: the stderr to print | |
336 | + argv[4]: the write mode to use | |
337 | + argv[5]: the signal to issue | |
338 | + argv[6]: the exit status code to use | |
339 | ||
340 | * When built with --enable-hardcoded-path-in-tests or issued without | |
341 | using the loader directly. | |
342 | */ | |
343 | ||
344 | if (argc != (restart ? 6 : 5) && argc != (restart ? 6 : 2)) | |
345 | FAIL_EXIT1 ("wrong number of arguments (%d)", argc); | |
346 | ||
347 | if (restart) | |
348 | { | |
349 | handle_restart (argv[1], /* stdout */ | |
350 | argv[2], /* stderr */ | |
351 | argv[3], /* write_mode */ | |
352 | argv[4], /* signal */ | |
353 | argv[5]); /* status */ | |
354 | } | |
355 | ||
356 | initial_argv[0] = argv[1]; /* path for ld.so */ | |
357 | initial_argv[1] = argv[2]; /* "--library-path" */ | |
358 | initial_argv[2] = argv[3]; /* the library path */ | |
359 | initial_argv[3] = argv[4]; /* the application name */ | |
360 | initial_argv[4] = NULL; | |
361 | ||
362 | do_multiple_tests (subprocess); | |
363 | do_multiple_tests (subprogram); | |
364 | ||
365 | return 0; | |
366 | } | |
367 | ||
368 | #define CMDLINE_OPTIONS \ | |
369 | { "restart", no_argument, &restart, 1 }, | |
370 | #define TEST_FUNCTION_ARGV do_test | |
91b6eb11 | 371 | #include <support/test-driver.c> |