]> Savannah Git Hosting - gnulib.git/commitdiff
execute: Add tests.
authorBruno Haible <bruno@clisp.org>
Sun, 29 Nov 2020 20:27:57 +0000 (21:27 +0100)
committerBruno Haible <bruno@clisp.org>
Sun, 29 Nov 2020 20:34:04 +0000 (21:34 +0100)
* tests/test-execute.sh: New file.
* tests/test-execute-main.c: New file.
* tests/test-execute-child.c: New file.
* modules/execute-tests: New file.

ChangeLog
modules/execute-tests [new file with mode: 0644]
tests/test-execute-child.c [new file with mode: 0644]
tests/test-execute-main.c [new file with mode: 0644]
tests/test-execute.sh [new file with mode: 0755]

index 69d6e49459b702c814c4dd9b6bd4144d83780cf6..5722677272229f19e5562db5c5534fcf56513b20 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2020-11-29  Bruno Haible  <bruno@clisp.org>
+
+       execute: Add tests.
+       * tests/test-execute.sh: New file.
+       * tests/test-execute-main.c: New file.
+       * tests/test-execute-child.c: New file.
+       * modules/execute-tests: New file.
+
 2020-11-29  Bruno Haible  <bruno@clisp.org>
 
        fcntl: Work around NetBSD bug with F_DUPFD_CLOEXEC.
diff --git a/modules/execute-tests b/modules/execute-tests
new file mode 100644 (file)
index 0000000..854b1ad
--- /dev/null
@@ -0,0 +1,24 @@
+Files:
+tests/test-execute.sh
+tests/test-execute-main.c
+tests/test-execute-child.c
+tests/macros.h
+
+Depends-on:
+dup2
+fcntl
+msvc-inval
+read-file
+stdint
+unistd
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-execute.sh
+check_PROGRAMS += test-execute-main test-execute-child
+test_execute_main_LDADD = $(LDADD) @LIBINTL@ $(LIBTHREAD)
+# The test-execute-child program must be a real executable, not a libtool
+# wrapper script, and should link against as few libraries as possible.
+# Therefore don't link it against any libraries other than -lc.
+test_execute_child_LDADD =
diff --git a/tests/test-execute-child.c b/tests/test-execute-child.c
new file mode 100644 (file)
index 0000000..2994476
--- /dev/null
@@ -0,0 +1,176 @@
+/* Child program invoked by test-execute-main.
+   Copyright (C) 2009-2020 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#if defined _WIN32 && ! defined __CYGWIN__
+/* Get declarations of the native Windows API functions.  */
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+/* Get _get_osfhandle.  */
+# include <io.h>
+#endif
+
+/* In this file, we use only system functions, no overrides from gnulib.  */
+#undef atoi
+#undef fcntl
+#undef fflush
+#undef fgetc
+#undef fprintf
+#undef fputs
+#undef fstat
+#undef raise
+#undef sprintf
+#undef stat
+#undef strcmp
+#undef strlen
+
+#if HAVE_MSVC_INVALID_PARAMETER_HANDLER
+static void __cdecl
+gl_msvc_invalid_parameter_handler (const wchar_t *expression,
+                                   const wchar_t *function,
+                                   const wchar_t *file,
+                                   unsigned int line,
+                                   uintptr_t dummy)
+{
+}
+#endif
+
+/* Return non-zero if FD is open.  */
+static int
+is_open (int fd)
+{
+#if defined _WIN32 && ! defined __CYGWIN__
+  /* On native Windows, the initial state of unassigned standard file
+     descriptors is that they are open but point to an
+     INVALID_HANDLE_VALUE, and there is no fcntl.  */
+  return (HANDLE) _get_osfhandle (fd) != INVALID_HANDLE_VALUE;
+#else
+# ifndef F_GETFL
+#  error Please port fcntl to your platform
+# endif
+  return 0 <= fcntl (fd, F_GETFL);
+#endif
+}
+
+int
+main (int argc, char *argv[])
+{
+  if (argc == 1)
+    /* Check an invocation without arguments.  Check the exit code.  */
+    return 40;
+
+  int test = atoi (argv[1]);
+  switch (test)
+    {
+    case 2:
+      /* Check argument passing.  */
+      return !(argc == 12
+               && strcmp (argv[2], "abc def") == 0
+               && strcmp (argv[3], "abc\"def\"ghi") == 0
+               && strcmp (argv[4], "xyz\"") == 0
+               && strcmp (argv[5], "abc\\def\\ghi") == 0
+               && strcmp (argv[6], "xyz\\") == 0
+               && strcmp (argv[7], "???") == 0
+               && strcmp (argv[8], "***") == 0
+               && strcmp (argv[9], "") == 0
+               && strcmp (argv[10], "foo") == 0
+               && strcmp (argv[11], "") == 0);
+    #if !(defined _WIN32 && !defined __CYGWIN__)
+    case 3:
+      /* Check SIGPIPE handling with ignore_sigpipe = false.  */
+    case 4:
+      /* Check SIGPIPE handling with ignore_sigpipe = true.  */
+      raise (SIGPIPE);
+      return 71;
+    #endif
+    case 5:
+      /* Check other signal.  */
+      raise (SIGINT);
+      return 71;
+    case 6:
+      /* Check stdin is inherited.  */
+      return !(fgetc (stdin) == 'F' && fgetc (stdin) == 'o');
+    case 7:
+      /* Check null_stdin = true.  */
+      return !(fgetc (stdin) == EOF);
+    case 8:
+      /* Check stdout is inherited, part 1 (regular file).  */
+      return !(fputs ("bar", stdout) != EOF && fflush (stdout) == 0);
+    case 9:
+      /* Check stdout is inherited, part 2 (device).  */
+    case 10:
+      /* Check null_stdout = true.  */
+      {
+        struct stat st;
+        return !(fstat (STDOUT_FILENO, &st) >= 0 && !S_ISREG (st.st_mode));
+      }
+    case 11:
+      /* Check stderr is inherited, part 1 (regular file).  */
+      return !(fputs ("bar", stderr) != EOF && fflush (stderr) == 0);
+    case 12:
+      /* Check stderr is inherited, part 2 (device).  */
+    case 13:
+      /* Check null_stderr = true.  */
+      {
+        struct stat st;
+        return !(fstat (STDERR_FILENO, &st) >= 0 && !S_ISREG (st.st_mode));
+      }
+    case 14:
+    case 15:
+      /* Check file descriptors >= 3 can be inherited.  */
+    case 16:
+      /* Check file descriptors >= 3 with O_CLOEXEC bit are not inherited.  */
+      #if HAVE_MSVC_INVALID_PARAMETER_HANDLER
+      /* Avoid exceptions from within _get_osfhandle.  */
+      _set_invalid_parameter_handler (gl_msvc_invalid_parameter_handler);
+      #endif
+      {
+        char buf[300];
+        buf[0] = '\0';
+        char *p = buf;
+        int fd;
+        for (fd = 0; fd < 20; fd++)
+          #ifdef __NetBSD__
+          if (fd != 3)
+          #endif
+            if (is_open (fd))
+              {
+                sprintf (p, "%d ", fd);
+                p += strlen (p);
+              }
+        const char *expected = (test < 16 ? "0 1 2 10 " : "0 1 2 ");
+        if (strcmp (buf, expected) == 0)
+          return 0;
+        else
+          {
+            fprintf (stderr, "Test case %d: %s\n", test, buf); fflush (stderr);
+            return 1;
+          }
+      }
+    default:
+      abort ();
+    }
+}
diff --git a/tests/test-execute-main.c b/tests/test-execute-main.c
new file mode 100644 (file)
index 0000000..d0993d8
--- /dev/null
@@ -0,0 +1,317 @@
+/* Test of execute.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "execute.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "read-file.h"
+#include "macros.h"
+
+/* The name of the "always silent" device.  */
+#if defined _WIN32 && ! defined __CYGWIN__
+/* Native Windows API.  */
+# define DEV_NULL "NUL"
+#else
+/* Unix API.  */
+# define DEV_NULL "/dev/null"
+#endif
+
+#define BASE "test-execute"
+
+int
+main (int argc, char *argv[])
+{
+  if (argc != 3)
+    {
+      fprintf (stderr, "%s: need 2 arguments\n", argv[0]);
+      return 2;
+    }
+  char *prog_path = argv[1];
+  const char *progname = "test-execute-child";
+  int test = atoi (argv[2]);
+  switch (test)
+    {
+    case 0:
+      {
+        /* Check an invocation without arguments.  Check the exit code.  */
+        char *prog_argv[2] = { prog_path, NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 40);
+      }
+      break;
+    case 1:
+      {
+        /* Check an invocation of a non-existent program.  */
+        char *prog_argv[3] = { "./non-existent", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 127);
+      }
+      break;
+    case 2:
+      {
+        /* Check argument passing.  */
+        char *prog_argv[13] =
+          {
+            prog_path,
+            (char *) "2",
+            (char *) "abc def",
+            (char *) "abc\"def\"ghi",
+            (char *) "xyz\"",
+            (char *) "abc\\def\\ghi",
+            (char *) "xyz\\",
+            (char *) "???",
+            (char *) "***",
+            (char *) "",
+            (char *) "foo",
+            (char *) "",
+            NULL
+          };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+      }
+      break;
+    case 3:
+      #if !(defined _WIN32 && !defined __CYGWIN__)
+      {
+        /* Check SIGPIPE handling with ignore_sigpipe = false.  */
+        char *prog_argv[3] = { prog_path, (char *) "3", NULL };
+        int termsig = 0xDEADBEEF;
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, &termsig);
+        ASSERT (ret == 127);
+        ASSERT (termsig == SIGPIPE);
+      }
+      #endif
+      break;
+    case 4:
+      #if !(defined _WIN32 && !defined __CYGWIN__)
+      {
+        /* Check SIGPIPE handling with ignore_sigpipe = true.  */
+        char *prog_argv[3] = { prog_path, (char *) "4", NULL };
+        int termsig = 0xDEADBEEF;
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           true, false, false, false, true, false, &termsig);
+        ASSERT (ret == 0);
+        ASSERT (termsig == SIGPIPE);
+      }
+      #endif
+      break;
+    case 5:
+      {
+        /* Check other signal.  */
+        char *prog_argv[3] = { prog_path, (char *) "5", NULL };
+        int termsig = 0xDEADBEEF;
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, &termsig);
+        #if defined _WIN32 && !defined __CYGWIN__
+        ASSERT (ret == 3);
+        ASSERT (termsig == 0);
+        #else
+        ASSERT (ret == 127);
+        ASSERT (termsig == SIGINT);
+        #endif
+      }
+      break;
+    case 6:
+      {
+        /* Check stdin is inherited.  */
+        FILE *fp = fopen (BASE ".tmp", "w");
+        fputs ("Foo", fp);
+        ASSERT (fclose (fp) == 0);
+
+        fp = freopen (BASE ".tmp", "r", stdin);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "6", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+
+        ASSERT (fclose (stdin) == 0);
+        ASSERT (remove (BASE ".tmp") == 0);
+      }
+      break;
+    case 7:
+      {
+        /* Check null_stdin = true.  */
+        FILE *fp = fopen (BASE ".tmp", "w");
+        fputs ("Foo", fp);
+        ASSERT (fclose (fp) == 0);
+
+        fp = freopen (BASE ".tmp", "r", stdin);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "7", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, true, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+
+        ASSERT (fclose (stdin) == 0);
+        ASSERT (remove (BASE ".tmp") == 0);
+      }
+      break;
+    case 8:
+      {
+        /* Check stdout is inherited, part 1 (regular file).  */
+        FILE *fp = freopen (BASE ".tmp", "w", stdout);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "8", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+
+        ASSERT (fclose (stdout) == 0);
+
+        size_t length;
+        char *contents = read_file (BASE ".tmp", 0, &length);
+        ASSERT (length == 3 && memcmp (contents, "bar", 3) == 0);
+
+        ASSERT (remove (BASE ".tmp") == 0);
+      }
+      break;
+    case 9:
+      {
+        /* Check stdout is inherited, part 2 (device).  */
+        FILE *fp = freopen (DEV_NULL, "w", stdout);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "9", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+      }
+      break;
+    case 10:
+      {
+        /* Check null_stdout = true.  */
+        FILE *fp = freopen (BASE ".tmp", "w", stdout);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "10", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, true, false, true, false, NULL);
+        ASSERT (ret == 0);
+
+        ASSERT (fclose (stdout) == 0);
+
+        size_t length;
+        (void) read_file (BASE ".tmp", 0, &length);
+        ASSERT (length == 0);
+
+        ASSERT (remove (BASE ".tmp") == 0);
+      }
+      break;
+    case 11:
+      {
+        /* Check stderr is inherited, part 1 (regular file).  */
+        FILE *fp = freopen (BASE ".tmp", "w", stderr);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "11", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+
+        ASSERT (fclose (stderr) == 0);
+
+        size_t length;
+        char *contents = read_file (BASE ".tmp", 0, &length);
+        ASSERT (length == 3 && memcmp (contents, "bar", 3) == 0);
+
+        ASSERT (remove (BASE ".tmp") == 0);
+      }
+      break;
+    case 12:
+      {
+        /* Check stderr is inherited, part 2 (device).  */
+        FILE *fp = freopen (DEV_NULL, "w", stderr);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "12", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+      }
+      break;
+    case 13:
+      {
+        /* Check null_stderr = true.  */
+        FILE *fp = freopen (BASE ".tmp", "w", stderr);
+        ASSERT (fp != NULL);
+
+        char *prog_argv[3] = { prog_path, (char *) "13", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           false, false, false, true, true, false, NULL);
+        ASSERT (ret == 0);
+
+        ASSERT (fclose (stderr) == 0);
+
+        size_t length;
+        (void) read_file (BASE ".tmp", 0, &length);
+        ASSERT (length == 0);
+
+        ASSERT (remove (BASE ".tmp") == 0);
+      }
+      break;
+    case 14:
+      {
+        /* Check file descriptors >= 3 can be inherited.  */
+        ASSERT (dup2 (STDOUT_FILENO, 10) >= 0);
+        char *prog_argv[3] = { prog_path, (char *) "14", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           true, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+      }
+      break;
+    case 15:
+      {
+        /* Check file descriptors >= 3 can be inherited.  */
+        ASSERT (fcntl (STDOUT_FILENO, F_DUPFD, 10) >= 0);
+        char *prog_argv[3] = { prog_path, (char *) "15", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           true, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+      }
+      break;
+    case 16:
+      {
+        /* Check file descriptors >= 3 with O_CLOEXEC bit are not inherited.  */
+        ASSERT (fcntl (STDOUT_FILENO, F_DUPFD_CLOEXEC, 10) >= 0);
+        char *prog_argv[3] = { prog_path, (char *) "16", NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv,
+                           true, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+      }
+      break;
+    default:
+      ASSERT (false);
+    }
+  return 0;
+}
diff --git a/tests/test-execute.sh b/tests/test-execute.sh
new file mode 100755 (executable)
index 0000000..c5f7d23
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+st=0
+for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ; do
+  ${CHECKER} ./test-execute-main${EXEEXT} ./test-execute-child${EXEEXT} $i \
+    || { echo test-execute.sh: test case $i failed >&2; st=1; }
+done
+exit $st