Tests for module 'system-quote'.
authorBruno Haible <bruno@clisp.org>
Wed, 9 May 2012 01:38:34 +0000 (03:38 +0200)
committerBruno Haible <bruno@clisp.org>
Wed, 9 May 2012 01:38:34 +0000 (03:38 +0200)
* modules/system-quote-tests: New file.
* tests/test-system-quote.sh: New file.
* tests/test-system-quote-main.c: New file.
* tests/test-system-quote-child.c: New file.

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

index 164bbe1..d329c3a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2012-05-08  Bruno Haible  <bruno@clisp.org>
 
+       Tests for module 'system-quote'.
+       * modules/system-quote-tests: New file.
+       * tests/test-system-quote.sh: New file.
+       * tests/test-system-quote-main.c: New file.
+       * tests/test-system-quote-child.c: New file.
+
        New module 'system-quote'.
        * lib/system-quote.h: New file.
        * lib/system-quote.c: New file.
diff --git a/modules/system-quote-tests b/modules/system-quote-tests
new file mode 100644 (file)
index 0000000..7b6967b
--- /dev/null
@@ -0,0 +1,26 @@
+Status:
+longrunning-test
+
+Files:
+tests/test-system-quote.sh
+tests/test-system-quote-main.c
+tests/test-system-quote-child.c
+tests/macros.h
+
+Depends-on:
+stdbool
+unistd
+progname
+popen
+pclose
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-system-quote.sh
+check_PROGRAMS += test-system-quote-main test-system-quote-child
+test_system_quote_main_LDADD = $(LDADD) @LIBINTL@
+# The test-system-quote-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_system_quote_child_LDADD =
diff --git a/tests/test-system-quote-child.c b/tests/test-system-quote-child.c
new file mode 100644 (file)
index 0000000..9b63de0
--- /dev/null
@@ -0,0 +1,59 @@
+/* Child program invoked by test-system-quote-main.
+   Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#define EXPECTED_DATA_FILE "t-sq-data.tmp"
+
+int
+main (int argc, char *argv[])
+{
+  const char *arg;
+  char expected_data[1000];
+  size_t expected_data_len;
+
+  if (argc < 2)
+    /* Expected one data argument, received none.  */
+    return 2;
+  if (argc > 2)
+    /* Expected one data argument, received more than one.  */
+    return 3;
+  arg = argv[1];
+
+  /* Read the contents of EXPECTED_DATA_FILE.  */
+  {
+    FILE *fp = fopen (EXPECTED_DATA_FILE, "rb");
+    if (fp == NULL)
+      return 4;
+    expected_data_len = fread (expected_data, 1, sizeof (expected_data), fp);
+    if (fclose (fp))
+      return 5;
+  }
+
+  if (!(strlen (arg) == expected_data_len
+        && memcmp (arg, expected_data, expected_data_len) == 0))
+    {
+      /* arg is not as expected.  */
+      fprintf (stderr, "expected: %.*s\nreceived: %s\n",
+               (int)expected_data_len, expected_data, arg);
+      return 1;
+    }
+  else
+    return 0;
+}
diff --git a/tests/test-system-quote-main.c b/tests/test-system-quote-main.c
new file mode 100644 (file)
index 0000000..10fe823
--- /dev/null
@@ -0,0 +1,344 @@
+/* Test of system-quote module.
+   Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2012.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "system-quote.h"
+
+#if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
+# define WINDOWS_NATIVE
+#endif
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef WINDOWS_NATIVE
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif
+
+#include "progname.h"
+#include "macros.h"
+
+#define EXPECTED_DATA_FILE "t-sq-data.tmp"
+
+static int failed;
+
+static void
+check_one (enum system_command_interpreter interpreter, const char *prog,
+           const char *input)
+{
+  char buf[1000];
+  size_t output_len;
+  char *output;
+  char *bufend;
+
+  output_len = system_quote_length (interpreter, input);
+
+  output = system_quote (interpreter, input);
+  ASSERT (strlen (output) == output_len);
+
+  ASSERT (output_len <= sizeof (buf) - 2);
+  memset (buf, '\0', output_len + 1);
+  buf[output_len + 1] = '%';
+  bufend = system_quote_copy (buf, interpreter, input);
+  ASSERT (bufend == buf + output_len);
+  ASSERT (memcmp (buf, output, output_len + 1) == 0);
+  ASSERT (buf[output_len + 1] == '%');
+
+  /* Store INPUT in EXPECTED_DATA_FILE, for verification by the child
+     process.  */
+  {
+    FILE *fp = fopen (EXPECTED_DATA_FILE, "wb");
+    if (fp == NULL)
+      exit (3);
+    if (fwrite (input, 1, strlen (input), fp) != strlen (input))
+      exit (4);
+    if (fclose (fp))
+      exit (5);
+  }
+
+  /* Invoke the child process through system() and popen().  */
+  {
+    char command[1000];
+
+    sprintf (command, "%s %s", prog, output);
+
+    switch (interpreter)
+      {
+      case SCI_SYSTEM:
+#ifdef WINDOWS_NATIVE
+      case SCI_WINDOWS_CMD:
+#endif
+        {
+          int exitcode = system (command);
+          if (exitcode != 0)
+            {
+              fprintf (stderr, "for input = |%s|: system() command failed with status %d: %s\n",
+                       input, exitcode, command);
+              failed = 1;
+            }
+        }
+        {
+          FILE *fp = popen (command, "r");
+          int exitcode = pclose (fp);
+          if (exitcode != 0)
+            {
+              fprintf (stderr, "for input = |%s|: popen() command failed with status %d: %s\n",
+                       input, exitcode, command);
+              failed = 1;
+            }
+        }
+        break;
+#ifdef WINDOWS_NATIVE
+      case SCI_WINDOWS_CREATEPROCESS:
+        {
+          PROCESS_INFORMATION pinfo;
+          STARTUPINFO sinfo;
+          sinfo.cb = sizeof (STARTUPINFO);
+          sinfo.lpReserved = NULL;
+          sinfo.lpDesktop = NULL;
+          sinfo.lpTitle = NULL;
+          sinfo.cbReserved2 = 0;
+          sinfo.lpReserved2 = NULL;
+          sinfo.dwFlags = STARTF_USESTDHANDLES;
+          sinfo.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
+          sinfo.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
+          sinfo.hStdError = GetStdHandle (STD_ERROR_HANDLE);
+
+          if (CreateProcess (NULL, command, NULL, NULL, TRUE, 0, NULL, NULL,
+                             &sinfo, &pinfo))
+            {
+              DWORD exitcode;
+              CloseHandle (pinfo.hThread);
+              if (WaitForSingleObject (pinfo.hProcess, INFINITE) == WAIT_OBJECT_0)
+                {
+                  if (GetExitCodeProcess (pinfo.hProcess, &exitcode))
+                    {
+                      if (exitcode != 0)
+                        {
+                          fprintf (stderr, "for input = |%s|: CreateProcess() command failed with status %d: %s\n",
+                                   input, exitcode, command);
+                          failed = 1;
+                        }
+                    }
+                  else
+                    {
+                      fprintf (stderr, "for input = |%s|: GetExitCodeProcess failed, GetLastError() = %u\n",
+                               input, GetLastError ());
+                      failed = 1;
+                    }
+                }
+              else
+                {
+                  fprintf (stderr, "for input = |%s|: WaitForSingleObject failed\n",
+                           input);
+                  failed = 1;
+                }
+              CloseHandle (pinfo.hProcess);
+            }
+          else
+            {
+              fprintf (stderr, "for input = |%s|: CreateProcess failed, GetLastError() = %u\n",
+                       input, GetLastError ());
+              failed = 1;
+            }
+        }
+        break;
+#endif
+      default:
+        break;
+      }
+  }
+
+  free (output);
+}
+
+static void
+check_all (enum system_command_interpreter interpreter,
+           bool windows_cmd_limitations,
+           const char *prog)
+{
+  /* Check the system_quote_length, system_quote_copy, system_quote
+     functions.  */
+  {
+    int c;
+
+    /* Empty argument.  */
+    check_one (interpreter, prog, "");
+
+    /* Identifier or number.  */
+    check_one (interpreter, prog, "foo");
+    check_one (interpreter, prog, "phr0ck");
+
+    /* Whitespace would be interpreted as argument separator by the shell.  */
+    check_one (interpreter, prog, "foo\tbar");
+    if (!windows_cmd_limitations)
+      {
+        check_one (interpreter, prog, "foo\nbar");
+        check_one (interpreter, prog, "foo\rbar");
+      }
+    check_one (interpreter, prog, "foo bar");
+
+    /* '!' at the beginning of argv[0] would introduce a negated command.  */
+    check_one (interpreter, prog, "!foo");
+
+    /* '"' would be interpreted as the start of a string.  */
+    check_one (interpreter, prog, "\"foo\"bar");
+
+    /* '#' at the beginning of an argument would be interpreted as the start
+       of a comment.  */
+    check_one (interpreter, prog, "#foo");
+
+    /* '$' at the beginning of an argument would be interpreted as a variable
+       reference.  */
+    check_one (interpreter, prog, "$foo");
+
+    /* '&' at the beginning of an argument would be interpreted as a background
+       task indicator.  */
+    check_one (interpreter, prog, "&");
+
+    /* "'" would be interpreted as the start of a string.  */
+    check_one (interpreter, prog, "'foo'bar");
+
+    /* '(' at the beginning of argv[0] would introduce a subshell command.  */
+    check_one (interpreter, prog, "(");
+
+    /* ')' at the beginning of an argument would be interpreted as the end of
+       the command.  */
+    check_one (interpreter, prog, ")");
+
+    /* '*' would be interpreted as a wildcard character.  */
+    check_one (interpreter, prog, "*");
+    check_one (interpreter, prog, "*foo");
+
+    /* ';' at the beginning of an argument would be interpreted as an empty
+       statement in argv[0] and as the end of the command otherwise.  */
+    check_one (interpreter, prog, ";");
+    check_one (interpreter, prog, "foo;");
+
+    /* '<' would be interpreted as a redirection of stdin.  */
+    check_one (interpreter, prog, "<");
+
+    /* '=' inside argv[0] would be interpreted as an environment variable
+       assignment.  */
+    check_one (interpreter, prog, "foo=bar");
+
+    /* '>' would be interpreted as a redirection of stdout.  */
+    check_one (interpreter, prog, ">");
+
+    /* '?' would be interpreted as a wildcard character.  */
+    check_one (interpreter, prog, "?");
+    check_one (interpreter, prog, "foo?bar");
+
+    /* '^' would be interpreted in old /bin/sh, e.g. SunOS 4.1.4.  */
+    check_one (interpreter, prog, "^");
+
+    /* "[...]" would be interpreted as a wildcard pattern.  */
+    check_one (interpreter, prog, "[");
+    check_one (interpreter, prog, "]");
+
+    /* '\' would be interpreted as an escape character.  */
+    check_one (interpreter, prog, "\\foo");
+
+    /* '`' would be interpreted as the start of a command substitution.  */
+    check_one (interpreter, prog, "`foo");
+
+    /* '{' at the beginning of argv[0] would introduce a complex command.  */
+    check_one (interpreter, prog, "{");
+
+    /* '|' at the beginning of an argument would be interpreted as a pipe
+       between commands.  */
+    check_one (interpreter, prog, "|");
+
+    /* '}' at the beginning of an argument would be interpreted as the end of
+       the command.  */
+    check_one (interpreter, prog, "}");
+
+    /* '~' at the beginning of an argument would be interpreted as a reference
+       to a user's home directory.  */
+    check_one (interpreter, prog, "~");
+    check_one (interpreter, prog, "~foo");
+
+    /* A string that contains both ' and ".  */
+    check_one (interpreter, prog, "foo'bar\"baz");
+
+    /* '%' is used for environment variable references in Windows cmd.exe.  */
+    check_one (interpreter, prog, "%");
+    check_one (interpreter, prog, "%%");
+    check_one (interpreter, prog, "%foo%");
+    check_one (interpreter, prog, "%PATH%");
+
+    /* All other characters don't need quoting.  */
+    for (c = 1; c <= UCHAR_MAX; c++)
+      if (strchr ("\t\n\r !\"#$&'()*;<=>?^[\\]`{|}~", c) == NULL)
+        {
+          char s[5];
+          s[0] = 'a';
+          s[1] = (char) c;
+          s[2] = 'z';
+          s[3] = (char) c;
+          s[4] = '\0';
+
+          check_one (interpreter, prog, s);
+        }
+  }
+}
+
+int
+main (int argc, char *argv[])
+{
+  char *prog;
+
+  set_program_name (argv[0]);
+
+  if (argc != 2)
+    {
+      fprintf (stderr, "%s: need 1 argument\n", argv[0]);
+      return 2;
+    }
+  prog = argv[1];
+
+#ifdef WINDOWS_NATIVE
+  /* Make PROG suitable for native Windows system calls and cmd.exe:
+     Replace '/' with '\\'.  */
+  {
+    char *p;
+    for (p = prog; *p != '\0'; p++)
+      if (*p == '/')
+        *p = '\\';
+  }
+#endif
+
+#ifdef WINDOWS_NATIVE
+  check_all (SCI_SYSTEM, true, prog); /* equivalent to SCI_WINDOWS_CMD */
+  check_all (SCI_WINDOWS_CREATEPROCESS, false, prog);
+  check_all (SCI_WINDOWS_CMD, true, prog);
+#else
+  check_all (SCI_SYSTEM, false, prog); /* equivalent to SCI_POSIX_SH */
+#endif
+
+  /* Clean up.  */
+  unlink (EXPECTED_DATA_FILE);
+
+  return failed;
+}
diff --git a/tests/test-system-quote.sh b/tests/test-system-quote.sh
new file mode 100755 (executable)
index 0000000..d520286
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+./test-system-quote-main${EXEEXT} ./test-system-quote-child${EXEEXT}