From d43e61a65dcba77cbfddb96b19847daced7ef426 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Sun, 29 Nov 2020 21:27:57 +0100 Subject: [PATCH] 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. --- ChangeLog | 8 + modules/execute-tests | 24 +++ tests/test-execute-child.c | 176 ++++++++++++++++++++ tests/test-execute-main.c | 317 +++++++++++++++++++++++++++++++++++++ tests/test-execute.sh | 8 + 5 files changed, 533 insertions(+) create mode 100644 modules/execute-tests create mode 100644 tests/test-execute-child.c create mode 100644 tests/test-execute-main.c create mode 100755 tests/test-execute.sh diff --git a/ChangeLog b/ChangeLog index 69d6e49459..5722677272 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2020-11-29 Bruno Haible + + 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 fcntl: Work around NetBSD bug with F_DUPFD_CLOEXEC. diff --git a/modules/execute-tests b/modules/execute-tests new file mode 100644 index 0000000000..854b1ad8f6 --- /dev/null +++ b/modules/execute-tests @@ -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 index 0000000000..2994476bb7 --- /dev/null +++ b/tests/test-execute-child.c @@ -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 . */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined _WIN32 && ! defined __CYGWIN__ +/* Get declarations of the native Windows API functions. */ +# define WIN32_LEAN_AND_MEAN +# include +/* Get _get_osfhandle. */ +# include +#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 index 0000000000..d0993d8c69 --- /dev/null +++ b/tests/test-execute-main.c @@ -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 . */ + +#include + +#include "execute.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000000..c5f7d236a8 --- /dev/null +++ b/tests/test-execute.sh @@ -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 -- 2.39.5