|
Packit |
6c4009 |
/* Test capturing output from a subprocess.
|
|
Packit |
6c4009 |
Copyright (C) 2017-2018 Free Software Foundation, Inc.
|
|
Packit |
6c4009 |
This file is part of the GNU C Library.
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
The GNU C Library is free software; you can redistribute it and/or
|
|
Packit |
6c4009 |
modify it under the terms of the GNU Lesser General Public
|
|
Packit |
6c4009 |
License as published by the Free Software Foundation; either
|
|
Packit |
6c4009 |
version 2.1 of the License, or (at your option) any later version.
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
The GNU C Library is distributed in the hope that it will be useful,
|
|
Packit |
6c4009 |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
6c4009 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
6c4009 |
Lesser General Public License for more details.
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
You should have received a copy of the GNU Lesser General Public
|
|
Packit |
6c4009 |
License along with the GNU C Library; if not, see
|
|
Packit |
6c4009 |
<http://www.gnu.org/licenses/>. */
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
#include <stdbool.h>
|
|
Packit |
6c4009 |
#include <stdio.h>
|
|
Packit |
6c4009 |
#include <stdlib.h>
|
|
Packit |
6c4009 |
#include <string.h>
|
|
Packit |
6c4009 |
#include <support/capture_subprocess.h>
|
|
Packit |
6c4009 |
#include <support/check.h>
|
|
Packit |
6c4009 |
#include <support/support.h>
|
|
Packit |
7d4271 |
#include <support/temp_file.h>
|
|
Packit |
6c4009 |
#include <sys/wait.h>
|
|
Packit |
6c4009 |
#include <unistd.h>
|
|
Packit |
7d4271 |
#include <paths.h>
|
|
Packit |
7d4271 |
#include <getopt.h>
|
|
Packit |
7d4271 |
#include <limits.h>
|
|
Packit |
7d4271 |
#include <errno.h>
|
|
Packit |
7d4271 |
#include <array_length.h>
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
/* Nonzero if the program gets called via 'exec'. */
|
|
Packit |
7d4271 |
static int restart;
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
/* Hold the four initial argument used to respawn the process. */
|
|
Packit |
7d4271 |
static char *initial_argv[5];
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
/* Write one byte at *P to FD and advance *P. Do nothing if *P is
|
|
Packit |
6c4009 |
'\0'. */
|
|
Packit |
6c4009 |
static void
|
|
Packit |
6c4009 |
transfer (const unsigned char **p, int fd)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
if (**p != '\0')
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
TEST_VERIFY (write (fd, *p, 1) == 1);
|
|
Packit |
6c4009 |
++*p;
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
/* Determine the order in which stdout and stderr are written. */
|
|
Packit |
6c4009 |
enum write_mode { out_first, err_first, interleave,
|
|
Packit |
6c4009 |
write_mode_last = interleave };
|
|
Packit |
6c4009 |
|
|
Packit |
7d4271 |
static const char *
|
|
Packit |
7d4271 |
write_mode_to_str (enum write_mode mode)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
switch (mode)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
case out_first: return "out_first";
|
|
Packit |
7d4271 |
case err_first: return "err_first";
|
|
Packit |
7d4271 |
case interleave: return "interleave";
|
|
Packit |
7d4271 |
default: return "write_mode_last";
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
static enum write_mode
|
|
Packit |
7d4271 |
str_to_write_mode (const char *mode)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
if (strcmp (mode, "out_first") == 0)
|
|
Packit |
7d4271 |
return out_first;
|
|
Packit |
7d4271 |
else if (strcmp (mode, "err_first") == 0)
|
|
Packit |
7d4271 |
return err_first;
|
|
Packit |
7d4271 |
else if (strcmp (mode, "interleave") == 0)
|
|
Packit |
7d4271 |
return interleave;
|
|
Packit |
7d4271 |
return write_mode_last;
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
6c4009 |
/* Describe what to write in the subprocess. */
|
|
Packit |
6c4009 |
struct test
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
char *out;
|
|
Packit |
6c4009 |
char *err;
|
|
Packit |
6c4009 |
enum write_mode write_mode;
|
|
Packit |
6c4009 |
int signal;
|
|
Packit |
6c4009 |
int status;
|
|
Packit |
6c4009 |
};
|
|
Packit |
6c4009 |
|
|
Packit |
7d4271 |
_Noreturn static void
|
|
Packit |
7d4271 |
test_common (const struct test *test)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
bool mode_ok = false;
|
|
Packit |
6c4009 |
switch (test->write_mode)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
case out_first:
|
|
Packit |
6c4009 |
TEST_VERIFY (fputs (test->out, stdout) >= 0);
|
|
Packit |
6c4009 |
TEST_VERIFY (fflush (stdout) == 0);
|
|
Packit |
6c4009 |
TEST_VERIFY (fputs (test->err, stderr) >= 0);
|
|
Packit |
6c4009 |
TEST_VERIFY (fflush (stderr) == 0);
|
|
Packit |
6c4009 |
mode_ok = true;
|
|
Packit |
6c4009 |
break;
|
|
Packit |
6c4009 |
case err_first:
|
|
Packit |
6c4009 |
TEST_VERIFY (fputs (test->err, stderr) >= 0);
|
|
Packit |
6c4009 |
TEST_VERIFY (fflush (stderr) == 0);
|
|
Packit |
6c4009 |
TEST_VERIFY (fputs (test->out, stdout) >= 0);
|
|
Packit |
6c4009 |
TEST_VERIFY (fflush (stdout) == 0);
|
|
Packit |
6c4009 |
mode_ok = true;
|
|
Packit |
6c4009 |
break;
|
|
Packit |
6c4009 |
case interleave:
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
const unsigned char *pout = (const unsigned char *) test->out;
|
|
Packit |
6c4009 |
const unsigned char *perr = (const unsigned char *) test->err;
|
|
Packit |
6c4009 |
do
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
transfer (&pout, STDOUT_FILENO);
|
|
Packit |
6c4009 |
transfer (&perr, STDERR_FILENO);
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
while (*pout != '\0' || *perr != '\0');
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
mode_ok = true;
|
|
Packit |
6c4009 |
break;
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
TEST_VERIFY (mode_ok);
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
if (test->signal != 0)
|
|
Packit |
6c4009 |
raise (test->signal);
|
|
Packit |
6c4009 |
exit (test->status);
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
|
|
Packit |
7d4271 |
static int
|
|
Packit |
7d4271 |
parse_int (const char *str)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
char *endptr;
|
|
Packit |
7d4271 |
long int ret = strtol (str, &endptr, 10);
|
|
Packit |
7d4271 |
TEST_COMPARE (errno, 0);
|
|
Packit |
7d4271 |
TEST_VERIFY (ret >= 0 && ret <= INT_MAX);
|
|
Packit |
7d4271 |
return ret;
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
/* For use with support_capture_subprogram. */
|
|
Packit |
7d4271 |
_Noreturn static void
|
|
Packit |
7d4271 |
handle_restart (char *out, char *err, const char *write_mode,
|
|
Packit |
7d4271 |
const char *signal, const char *status)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
struct test test =
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
out,
|
|
Packit |
7d4271 |
err,
|
|
Packit |
7d4271 |
str_to_write_mode (write_mode),
|
|
Packit |
7d4271 |
parse_int (signal),
|
|
Packit |
7d4271 |
parse_int (status)
|
|
Packit |
7d4271 |
};
|
|
Packit |
7d4271 |
test_common (&test);
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
/* For use with support_capture_subprocess. */
|
|
Packit |
7d4271 |
_Noreturn static void
|
|
Packit |
7d4271 |
callback (void *closure)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
const struct test *test = closure;
|
|
Packit |
7d4271 |
test_common (test);
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
6c4009 |
/* Create a heap-allocated random string of letters. */
|
|
Packit |
6c4009 |
static char *
|
|
Packit |
6c4009 |
random_string (size_t length)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
char *result = xmalloc (length + 1);
|
|
Packit |
6c4009 |
for (size_t i = 0; i < length; ++i)
|
|
Packit |
6c4009 |
result[i] = 'a' + (rand () % 26);
|
|
Packit |
6c4009 |
result[length] = '\0';
|
|
Packit |
6c4009 |
return result;
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
/* Check that the specific stream from the captured subprocess matches
|
|
Packit |
6c4009 |
expectations. */
|
|
Packit |
6c4009 |
static void
|
|
Packit |
6c4009 |
check_stream (const char *what, const struct xmemstream *stream,
|
|
Packit |
6c4009 |
const char *expected)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
if (strcmp (stream->buffer, expected) != 0)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
support_record_failure ();
|
|
Packit |
6c4009 |
printf ("error: captured %s data incorrect\n"
|
|
Packit |
6c4009 |
" expected: %s\n"
|
|
Packit |
6c4009 |
" actual: %s\n",
|
|
Packit |
6c4009 |
what, expected, stream->buffer);
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
if (stream->length != strlen (expected))
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
support_record_failure ();
|
|
Packit |
6c4009 |
printf ("error: captured %s data length incorrect\n"
|
|
Packit |
6c4009 |
" expected: %zu\n"
|
|
Packit |
6c4009 |
" actual: %zu\n",
|
|
Packit |
6c4009 |
what, strlen (expected), stream->length);
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
|
|
Packit |
7d4271 |
static struct support_capture_subprocess
|
|
Packit |
7d4271 |
do_subprocess (struct test *test)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
return support_capture_subprocess (callback, test);
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
static struct support_capture_subprocess
|
|
Packit |
7d4271 |
do_subprogram (const struct test *test)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
/* Three digits per byte plus null terminator. */
|
|
Packit |
7d4271 |
char signalstr[3 * sizeof(int) + 1];
|
|
Packit |
7d4271 |
snprintf (signalstr, sizeof (signalstr), "%d", test->signal);
|
|
Packit |
7d4271 |
char statusstr[3 * sizeof(int) + 1];
|
|
Packit |
7d4271 |
snprintf (statusstr, sizeof (statusstr), "%d", test->status);
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
int argc = 0;
|
|
Packit |
7d4271 |
enum {
|
|
Packit |
7d4271 |
/* 4 elements from initial_argv (path to ld.so, '--library-path', the
|
|
Packit |
7d4271 |
path', and application name'), 2 for restart argument ('--direct',
|
|
Packit |
7d4271 |
'--restart'), 5 arguments plus NULL. */
|
|
Packit |
7d4271 |
argv_size = 12
|
|
Packit |
7d4271 |
};
|
|
Packit |
7d4271 |
char *args[argv_size];
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
for (char **arg = initial_argv; *arg != NULL; arg++)
|
|
Packit |
7d4271 |
args[argc++] = *arg;
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
args[argc++] = (char*) "--direct";
|
|
Packit |
7d4271 |
args[argc++] = (char*) "--restart";
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
args[argc++] = test->out;
|
|
Packit |
7d4271 |
args[argc++] = test->err;
|
|
Packit |
7d4271 |
args[argc++] = (char*) write_mode_to_str (test->write_mode);
|
|
Packit |
7d4271 |
args[argc++] = signalstr;
|
|
Packit |
7d4271 |
args[argc++] = statusstr;
|
|
Packit |
7d4271 |
args[argc] = NULL;
|
|
Packit |
7d4271 |
TEST_VERIFY (argc < argv_size);
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
return support_capture_subprogram (args[0], args);
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
enum test_type
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
subprocess,
|
|
Packit |
7d4271 |
subprogram,
|
|
Packit |
7d4271 |
};
|
|
Packit |
7d4271 |
|
|
Packit |
6c4009 |
static int
|
|
Packit |
7d4271 |
do_multiple_tests (enum test_type type)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
const int lengths[] = {0, 1, 17, 512, 20000, -1};
|
|
Packit |
6c4009 |
|
|
Packit |
7d4271 |
/* Test multiple combinations of support_capture_sub{process,program}.
|
|
Packit |
6c4009 |
|
|
Packit |
6c4009 |
length_idx_stdout: Index into the lengths array above,
|
|
Packit |
6c4009 |
controls how many bytes are written by the subprocess to
|
|
Packit |
6c4009 |
standard output.
|
|
Packit |
6c4009 |
length_idx_stderr: Same for standard error.
|
|
Packit |
6c4009 |
write_mode: How standard output and standard error writes are
|
|
Packit |
6c4009 |
ordered.
|
|
Packit |
6c4009 |
signal: Exit with no signal if zero, with SIGTERM if one.
|
|
Packit |
6c4009 |
status: Process exit status: 0 if zero, 3 if one. */
|
|
Packit |
6c4009 |
for (int length_idx_stdout = 0; lengths[length_idx_stdout] >= 0;
|
|
Packit |
6c4009 |
++length_idx_stdout)
|
|
Packit |
6c4009 |
for (int length_idx_stderr = 0; lengths[length_idx_stderr] >= 0;
|
|
Packit |
6c4009 |
++length_idx_stderr)
|
|
Packit |
6c4009 |
for (int write_mode = 0; write_mode < write_mode_last; ++write_mode)
|
|
Packit |
6c4009 |
for (int signal = 0; signal < 2; ++signal)
|
|
Packit |
6c4009 |
for (int status = 0; status < 2; ++status)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
struct test test =
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
.out = random_string (lengths[length_idx_stdout]),
|
|
Packit |
6c4009 |
.err = random_string (lengths[length_idx_stderr]),
|
|
Packit |
6c4009 |
.write_mode = write_mode,
|
|
Packit |
6c4009 |
.signal = signal * SIGTERM, /* 0 or SIGTERM. */
|
|
Packit |
6c4009 |
.status = status * 3, /* 0 or 3. */
|
|
Packit |
6c4009 |
};
|
|
Packit |
6c4009 |
TEST_VERIFY (strlen (test.out) == lengths[length_idx_stdout]);
|
|
Packit |
6c4009 |
TEST_VERIFY (strlen (test.err) == lengths[length_idx_stderr]);
|
|
Packit |
6c4009 |
|
|
Packit |
7d4271 |
struct support_capture_subprocess result
|
|
Packit |
7d4271 |
= type == subprocess ? do_subprocess (&test)
|
|
Packit |
7d4271 |
: do_subprogram (&test);
|
|
Packit |
7d4271 |
|
|
Packit |
6c4009 |
check_stream ("stdout", &result.out, test.out);
|
|
Packit |
6c4009 |
check_stream ("stderr", &result.err, test.err);
|
|
Packit |
6c4009 |
if (test.signal != 0)
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
TEST_VERIFY (WIFSIGNALED (result.status));
|
|
Packit |
6c4009 |
TEST_VERIFY (WTERMSIG (result.status) == test.signal);
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
else
|
|
Packit |
6c4009 |
{
|
|
Packit |
6c4009 |
TEST_VERIFY (WIFEXITED (result.status));
|
|
Packit |
6c4009 |
TEST_VERIFY (WEXITSTATUS (result.status) == test.status);
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
support_capture_subprocess_free (&result);
|
|
Packit |
6c4009 |
free (test.out);
|
|
Packit |
6c4009 |
free (test.err);
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
return 0;
|
|
Packit |
6c4009 |
}
|
|
Packit |
6c4009 |
|
|
Packit |
7d4271 |
static int
|
|
Packit |
7d4271 |
do_test (int argc, char *argv[])
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
/* We must have either:
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
- one or four parameters if called initially:
|
|
Packit |
7d4271 |
+ argv[1]: path for ld.so optional
|
|
Packit |
7d4271 |
+ argv[2]: "--library-path" optional
|
|
Packit |
7d4271 |
+ argv[3]: the library path optional
|
|
Packit |
7d4271 |
+ argv[4]: the application name
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
- six parameters left if called through re-execution:
|
|
Packit |
7d4271 |
+ argv[1]: the application name
|
|
Packit |
7d4271 |
+ argv[2]: the stdout to print
|
|
Packit |
7d4271 |
+ argv[3]: the stderr to print
|
|
Packit |
7d4271 |
+ argv[4]: the write mode to use
|
|
Packit |
7d4271 |
+ argv[5]: the signal to issue
|
|
Packit |
7d4271 |
+ argv[6]: the exit status code to use
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
* When built with --enable-hardcoded-path-in-tests or issued without
|
|
Packit |
7d4271 |
using the loader directly.
|
|
Packit |
7d4271 |
*/
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
if (argc != (restart ? 6 : 5) && argc != (restart ? 6 : 2))
|
|
Packit |
7d4271 |
FAIL_EXIT1 ("wrong number of arguments (%d)", argc);
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
if (restart)
|
|
Packit |
7d4271 |
{
|
|
Packit |
7d4271 |
handle_restart (argv[1], /* stdout */
|
|
Packit |
7d4271 |
argv[2], /* stderr */
|
|
Packit |
7d4271 |
argv[3], /* write_mode */
|
|
Packit |
7d4271 |
argv[4], /* signal */
|
|
Packit |
7d4271 |
argv[5]); /* status */
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
initial_argv[0] = argv[1]; /* path for ld.so */
|
|
Packit |
7d4271 |
initial_argv[1] = argv[2]; /* "--library-path" */
|
|
Packit |
7d4271 |
initial_argv[2] = argv[3]; /* the library path */
|
|
Packit |
7d4271 |
initial_argv[3] = argv[4]; /* the application name */
|
|
Packit |
7d4271 |
initial_argv[4] = NULL;
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
do_multiple_tests (subprocess);
|
|
Packit |
7d4271 |
do_multiple_tests (subprogram);
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
return 0;
|
|
Packit |
7d4271 |
}
|
|
Packit |
7d4271 |
|
|
Packit |
7d4271 |
#define CMDLINE_OPTIONS \
|
|
Packit |
7d4271 |
{ "restart", no_argument, &restart, 1 },
|
|
Packit |
7d4271 |
#define TEST_FUNCTION_ARGV do_test
|
|
Packit |
6c4009 |
#include <support/test-driver.c>
|