This patch adds tests for the 'execute' module.
2020-11-29 Bruno Haible <br...@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. ============================ tests/test-execute.sh ============================ #!/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 ========================== tests/test-execute-main.c ========================== /* 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; } ========================== tests/test-execute-child.c ========================== /* 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 (); } } ============================ modules/execute-tests ============================ 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 =