Blame support/tst-support_capture_subprocess.c

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 Service c137a4
#include <support/temp_file.h>
Packit 6c4009
#include <sys/wait.h>
Packit 6c4009
#include <unistd.h>
Packit Service c137a4
#include <paths.h>
Packit Service c137a4
#include <getopt.h>
Packit Service c137a4
#include <limits.h>
Packit Service c137a4
#include <errno.h>
Packit Service c137a4
#include <array_length.h>
Packit Service c137a4
Packit Service c137a4
/* Nonzero if the program gets called via 'exec'.  */
Packit Service c137a4
static int restart;
Packit Service c137a4
Packit Service c137a4
/* Hold the four initial argument used to respawn the process.  */
Packit Service c137a4
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 Service c137a4
static const char *
Packit Service c137a4
write_mode_to_str (enum write_mode mode)
Packit Service c137a4
{
Packit Service c137a4
  switch (mode)
Packit Service c137a4
    {
Packit Service c137a4
    case out_first:  return "out_first";
Packit Service c137a4
    case err_first:  return "err_first";
Packit Service c137a4
    case interleave: return "interleave";
Packit Service c137a4
    default:         return "write_mode_last";
Packit Service c137a4
    }
Packit Service c137a4
}
Packit Service c137a4
Packit Service c137a4
static enum write_mode
Packit Service c137a4
str_to_write_mode (const char *mode)
Packit Service c137a4
{
Packit Service c137a4
  if (strcmp (mode, "out_first") == 0)
Packit Service c137a4
    return out_first;
Packit Service c137a4
  else if (strcmp (mode, "err_first") == 0)
Packit Service c137a4
    return err_first;
Packit Service c137a4
  else if (strcmp (mode, "interleave") == 0)
Packit Service c137a4
    return interleave;
Packit Service c137a4
  return write_mode_last;
Packit Service c137a4
}
Packit Service c137a4
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 Service c137a4
_Noreturn static void
Packit Service c137a4
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 Service c137a4
static int
Packit Service c137a4
parse_int (const char *str)
Packit Service c137a4
{
Packit Service c137a4
  char *endptr;
Packit Service c137a4
  long int ret = strtol (str, &endptr, 10);
Packit Service c137a4
  TEST_COMPARE (errno, 0);
Packit Service c137a4
  TEST_VERIFY (ret >= 0 && ret <= INT_MAX);
Packit Service c137a4
  return ret;
Packit Service c137a4
}
Packit Service c137a4
Packit Service c137a4
/* For use with support_capture_subprogram.  */
Packit Service c137a4
_Noreturn static void
Packit Service c137a4
handle_restart (char *out, char *err, const char *write_mode,
Packit Service c137a4
               const char *signal, const char *status)
Packit Service c137a4
{
Packit Service c137a4
  struct test test =
Packit Service c137a4
    {
Packit Service c137a4
      out,
Packit Service c137a4
      err,
Packit Service c137a4
      str_to_write_mode (write_mode),
Packit Service c137a4
      parse_int (signal),
Packit Service c137a4
      parse_int (status)
Packit Service c137a4
    };
Packit Service c137a4
  test_common (&test);
Packit Service c137a4
}
Packit Service c137a4
Packit Service c137a4
/* For use with support_capture_subprocess.  */
Packit Service c137a4
_Noreturn static void
Packit Service c137a4
callback (void *closure)
Packit Service c137a4
{
Packit Service c137a4
  const struct test *test = closure;
Packit Service c137a4
  test_common (test);
Packit Service c137a4
}
Packit Service c137a4
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 Service c137a4
static struct support_capture_subprocess
Packit Service c137a4
do_subprocess (struct test *test)
Packit Service c137a4
{
Packit Service c137a4
  return support_capture_subprocess (callback, test);
Packit Service c137a4
}
Packit Service c137a4
Packit Service c137a4
static struct support_capture_subprocess
Packit Service c137a4
do_subprogram (const struct test *test)
Packit Service c137a4
{
Packit Service c137a4
  /* Three digits per byte plus null terminator.  */
Packit Service c137a4
  char signalstr[3 * sizeof(int) + 1];
Packit Service c137a4
  snprintf (signalstr, sizeof (signalstr), "%d", test->signal);
Packit Service c137a4
  char statusstr[3 * sizeof(int) + 1];
Packit Service c137a4
  snprintf (statusstr, sizeof (statusstr), "%d", test->status);
Packit Service c137a4
Packit Service c137a4
  int argc = 0;
Packit Service c137a4
  enum {
Packit Service c137a4
    /* 4 elements from initial_argv (path to ld.so, '--library-path', the
Packit Service c137a4
       path', and application name'), 2 for restart argument ('--direct',
Packit Service c137a4
       '--restart'), 5 arguments plus NULL.  */
Packit Service c137a4
    argv_size = 12
Packit Service c137a4
  };
Packit Service c137a4
  char *args[argv_size];
Packit Service c137a4
Packit Service c137a4
  for (char **arg = initial_argv; *arg != NULL; arg++)
Packit Service c137a4
    args[argc++] = *arg;
Packit Service c137a4
Packit Service c137a4
  args[argc++] = (char*) "--direct";
Packit Service c137a4
  args[argc++] = (char*) "--restart";
Packit Service c137a4
Packit Service c137a4
  args[argc++] = test->out;
Packit Service c137a4
  args[argc++] = test->err;
Packit Service c137a4
  args[argc++] = (char*) write_mode_to_str (test->write_mode);
Packit Service c137a4
  args[argc++] = signalstr;
Packit Service c137a4
  args[argc++] = statusstr;
Packit Service c137a4
  args[argc]   = NULL;
Packit Service c137a4
  TEST_VERIFY (argc < argv_size);
Packit Service c137a4
Packit Service c137a4
  return support_capture_subprogram (args[0], args);
Packit Service c137a4
}
Packit Service c137a4
Packit Service c137a4
enum test_type
Packit Service c137a4
{
Packit Service c137a4
  subprocess,
Packit Service c137a4
  subprogram,
Packit Service c137a4
};
Packit Service c137a4
Packit 6c4009
static int
Packit Service c137a4
do_multiple_tests (enum test_type type)
Packit 6c4009
{
Packit 6c4009
  const int lengths[] = {0, 1, 17, 512, 20000, -1};
Packit 6c4009
Packit Service c137a4
  /* 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 Service c137a4
             struct support_capture_subprocess result
Packit Service c137a4
               = type == subprocess ? do_subprocess (&test)
Packit Service c137a4
                                    : do_subprogram (&test);
Packit Service c137a4
Packit 6c4009
              check_stream ("stdout", &result.out, test.out);
Packit 6c4009
              check_stream ("stderr", &result.err, test.err);
Packit Service 40022c
Packit Service 40022c
              /* Allowed output for support_capture_subprocess_check.  */
Packit Service 40022c
              int check_allow = 0;
Packit Service 40022c
              if (lengths[length_idx_stdout] > 0)
Packit Service 40022c
                check_allow |= sc_allow_stdout;
Packit Service 40022c
              if (lengths[length_idx_stderr] > 0)
Packit Service 40022c
                check_allow |= sc_allow_stderr;
Packit Service 40022c
              if (check_allow == 0)
Packit Service 40022c
                check_allow = sc_allow_none;
Packit Service 40022c
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 Service 40022c
                  support_capture_subprocess_check (&result, "signal",
Packit Service 40022c
                                                    -SIGTERM, check_allow);
Packit 6c4009
                }
Packit 6c4009
              else
Packit 6c4009
                {
Packit 6c4009
                  TEST_VERIFY (WIFEXITED (result.status));
Packit 6c4009
                  TEST_VERIFY (WEXITSTATUS (result.status) == test.status);
Packit Service 40022c
                  support_capture_subprocess_check (&result, "exit",
Packit Service 40022c
                                                    test.status, check_allow);
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 Service c137a4
static int
Packit Service c137a4
do_test (int argc, char *argv[])
Packit Service c137a4
{
Packit Service c137a4
  /* We must have either:
Packit Service c137a4
Packit Service c137a4
     - one or four parameters if called initially:
Packit Service c137a4
       + argv[1]: path for ld.so        optional
Packit Service c137a4
       + argv[2]: "--library-path"      optional
Packit Service c137a4
       + argv[3]: the library path      optional
Packit Service c137a4
       + argv[4]: the application name
Packit Service c137a4
Packit Service c137a4
     - six parameters left if called through re-execution:
Packit Service c137a4
       + argv[1]: the application name
Packit Service c137a4
       + argv[2]: the stdout to print
Packit Service c137a4
       + argv[3]: the stderr to print
Packit Service c137a4
       + argv[4]: the write mode to use
Packit Service c137a4
       + argv[5]: the signal to issue
Packit Service c137a4
       + argv[6]: the exit status code to use
Packit Service c137a4
Packit Service c137a4
     * When built with --enable-hardcoded-path-in-tests or issued without
Packit Service c137a4
       using the loader directly.
Packit Service c137a4
  */
Packit Service c137a4
Packit Service c137a4
  if (argc != (restart ? 6 : 5) && argc != (restart ? 6 : 2))
Packit Service c137a4
    FAIL_EXIT1 ("wrong number of arguments (%d)", argc);
Packit Service c137a4
Packit Service c137a4
  if (restart)
Packit Service c137a4
    {
Packit Service c137a4
      handle_restart (argv[1],  /* stdout  */
Packit Service c137a4
                     argv[2],  /* stderr  */
Packit Service c137a4
                     argv[3],  /* write_mode  */
Packit Service c137a4
                     argv[4],  /* signal  */
Packit Service c137a4
                     argv[5]); /* status  */
Packit Service c137a4
    }
Packit Service c137a4
Packit Service c137a4
  initial_argv[0] = argv[1]; /* path for ld.so  */
Packit Service c137a4
  initial_argv[1] = argv[2]; /* "--library-path"  */
Packit Service c137a4
  initial_argv[2] = argv[3]; /* the library path  */
Packit Service c137a4
  initial_argv[3] = argv[4]; /* the application name  */
Packit Service c137a4
  initial_argv[4] = NULL;
Packit Service c137a4
Packit Service c137a4
  do_multiple_tests (subprocess);
Packit Service c137a4
  do_multiple_tests (subprogram);
Packit Service c137a4
Packit Service c137a4
  return 0;
Packit Service c137a4
}
Packit Service c137a4
Packit Service c137a4
#define CMDLINE_OPTIONS \
Packit Service c137a4
  { "restart", no_argument, &restart, 1 },
Packit Service c137a4
#define TEST_FUNCTION_ARGV do_test
Packit 6c4009
#include <support/test-driver.c>