2017-06-02 09:59:28 +00:00
|
|
|
/* Test capturing output from a subprocess.
|
2019-01-01 00:11:28 +00:00
|
|
|
Copyright (C) 2017-2019 Free Software Foundation, Inc.
|
2017-06-02 09:59:28 +00:00
|
|
|
This file is part of the GNU C Library.
|
|
|
|
|
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with the GNU C Library; if not, see
|
|
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <support/capture_subprocess.h>
|
|
|
|
#include <support/check.h>
|
|
|
|
#include <support/support.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
/* Write one byte at *P to FD and advance *P. Do nothing if *P is
|
|
|
|
'\0'. */
|
|
|
|
static void
|
|
|
|
transfer (const unsigned char **p, int fd)
|
|
|
|
{
|
|
|
|
if (**p != '\0')
|
|
|
|
{
|
|
|
|
TEST_VERIFY (write (fd, *p, 1) == 1);
|
|
|
|
++*p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Determine the order in which stdout and stderr are written. */
|
|
|
|
enum write_mode { out_first, err_first, interleave,
|
|
|
|
write_mode_last = interleave };
|
|
|
|
|
|
|
|
/* Describe what to write in the subprocess. */
|
|
|
|
struct test
|
|
|
|
{
|
|
|
|
char *out;
|
|
|
|
char *err;
|
|
|
|
enum write_mode write_mode;
|
|
|
|
int signal;
|
|
|
|
int status;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* For use with support_capture_subprocess. */
|
|
|
|
static void
|
|
|
|
callback (void *closure)
|
|
|
|
{
|
|
|
|
const struct test *test = closure;
|
|
|
|
bool mode_ok = false;
|
|
|
|
switch (test->write_mode)
|
|
|
|
{
|
|
|
|
case out_first:
|
|
|
|
TEST_VERIFY (fputs (test->out, stdout) >= 0);
|
|
|
|
TEST_VERIFY (fflush (stdout) == 0);
|
|
|
|
TEST_VERIFY (fputs (test->err, stderr) >= 0);
|
|
|
|
TEST_VERIFY (fflush (stderr) == 0);
|
|
|
|
mode_ok = true;
|
|
|
|
break;
|
|
|
|
case err_first:
|
|
|
|
TEST_VERIFY (fputs (test->err, stderr) >= 0);
|
|
|
|
TEST_VERIFY (fflush (stderr) == 0);
|
|
|
|
TEST_VERIFY (fputs (test->out, stdout) >= 0);
|
|
|
|
TEST_VERIFY (fflush (stdout) == 0);
|
|
|
|
mode_ok = true;
|
|
|
|
break;
|
|
|
|
case interleave:
|
|
|
|
{
|
|
|
|
const unsigned char *pout = (const unsigned char *) test->out;
|
|
|
|
const unsigned char *perr = (const unsigned char *) test->err;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
transfer (&pout, STDOUT_FILENO);
|
|
|
|
transfer (&perr, STDERR_FILENO);
|
|
|
|
}
|
|
|
|
while (*pout != '\0' || *perr != '\0');
|
|
|
|
}
|
|
|
|
mode_ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
TEST_VERIFY (mode_ok);
|
|
|
|
|
|
|
|
if (test->signal != 0)
|
|
|
|
raise (test->signal);
|
|
|
|
exit (test->status);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create a heap-allocated random string of letters. */
|
|
|
|
static char *
|
|
|
|
random_string (size_t length)
|
|
|
|
{
|
|
|
|
char *result = xmalloc (length + 1);
|
|
|
|
for (size_t i = 0; i < length; ++i)
|
|
|
|
result[i] = 'a' + (rand () % 26);
|
|
|
|
result[length] = '\0';
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check that the specific stream from the captured subprocess matches
|
|
|
|
expectations. */
|
|
|
|
static void
|
|
|
|
check_stream (const char *what, const struct xmemstream *stream,
|
|
|
|
const char *expected)
|
|
|
|
{
|
|
|
|
if (strcmp (stream->buffer, expected) != 0)
|
|
|
|
{
|
|
|
|
support_record_failure ();
|
|
|
|
printf ("error: captured %s data incorrect\n"
|
|
|
|
" expected: %s\n"
|
|
|
|
" actual: %s\n",
|
|
|
|
what, expected, stream->buffer);
|
|
|
|
}
|
|
|
|
if (stream->length != strlen (expected))
|
|
|
|
{
|
|
|
|
support_record_failure ();
|
|
|
|
printf ("error: captured %s data length incorrect\n"
|
|
|
|
" expected: %zu\n"
|
|
|
|
" actual: %zu\n",
|
|
|
|
what, strlen (expected), stream->length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_test (void)
|
|
|
|
{
|
|
|
|
const int lengths[] = {0, 1, 17, 512, 20000, -1};
|
|
|
|
|
|
|
|
/* Test multiple combinations of support_capture_subprocess.
|
|
|
|
|
|
|
|
length_idx_stdout: Index into the lengths array above,
|
|
|
|
controls how many bytes are written by the subprocess to
|
|
|
|
standard output.
|
|
|
|
length_idx_stderr: Same for standard error.
|
|
|
|
write_mode: How standard output and standard error writes are
|
|
|
|
ordered.
|
|
|
|
signal: Exit with no signal if zero, with SIGTERM if one.
|
|
|
|
status: Process exit status: 0 if zero, 3 if one. */
|
|
|
|
for (int length_idx_stdout = 0; lengths[length_idx_stdout] >= 0;
|
|
|
|
++length_idx_stdout)
|
|
|
|
for (int length_idx_stderr = 0; lengths[length_idx_stderr] >= 0;
|
|
|
|
++length_idx_stderr)
|
|
|
|
for (int write_mode = 0; write_mode < write_mode_last; ++write_mode)
|
|
|
|
for (int signal = 0; signal < 2; ++signal)
|
|
|
|
for (int status = 0; status < 2; ++status)
|
|
|
|
{
|
|
|
|
struct test test =
|
|
|
|
{
|
|
|
|
.out = random_string (lengths[length_idx_stdout]),
|
|
|
|
.err = random_string (lengths[length_idx_stderr]),
|
|
|
|
.write_mode = write_mode,
|
|
|
|
.signal = signal * SIGTERM, /* 0 or SIGTERM. */
|
|
|
|
.status = status * 3, /* 0 or 3. */
|
|
|
|
};
|
|
|
|
TEST_VERIFY (strlen (test.out) == lengths[length_idx_stdout]);
|
|
|
|
TEST_VERIFY (strlen (test.err) == lengths[length_idx_stderr]);
|
|
|
|
|
|
|
|
struct support_capture_subprocess result
|
|
|
|
= support_capture_subprocess (callback, &test);
|
|
|
|
check_stream ("stdout", &result.out, test.out);
|
|
|
|
check_stream ("stderr", &result.err, test.err);
|
2018-11-28 18:59:45 +00:00
|
|
|
|
|
|
|
/* Allowed output for support_capture_subprocess_check. */
|
|
|
|
int check_allow = 0;
|
|
|
|
if (lengths[length_idx_stdout] > 0)
|
|
|
|
check_allow |= sc_allow_stdout;
|
|
|
|
if (lengths[length_idx_stderr] > 0)
|
|
|
|
check_allow |= sc_allow_stderr;
|
|
|
|
if (check_allow == 0)
|
|
|
|
check_allow = sc_allow_none;
|
|
|
|
|
2017-06-02 09:59:28 +00:00
|
|
|
if (test.signal != 0)
|
|
|
|
{
|
|
|
|
TEST_VERIFY (WIFSIGNALED (result.status));
|
|
|
|
TEST_VERIFY (WTERMSIG (result.status) == test.signal);
|
2018-11-28 18:59:45 +00:00
|
|
|
support_capture_subprocess_check (&result, "signal",
|
|
|
|
-SIGTERM, check_allow);
|
2017-06-02 09:59:28 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TEST_VERIFY (WIFEXITED (result.status));
|
|
|
|
TEST_VERIFY (WEXITSTATUS (result.status) == test.status);
|
2018-11-28 18:59:45 +00:00
|
|
|
support_capture_subprocess_check (&result, "exit",
|
|
|
|
test.status, check_allow);
|
2017-06-02 09:59:28 +00:00
|
|
|
}
|
|
|
|
support_capture_subprocess_free (&result);
|
|
|
|
free (test.out);
|
|
|
|
free (test.err);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#include <support/test-driver.c>
|