common: New function split_fields.
[gnupg.git] / common / t-stringhelp.c
index 6c47237..db0e811 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <assert.h>
 #ifdef HAVE_PWD_H
 # include <pwd.h>
 #endif
 #include <unistd.h>
 #include <sys/types.h>
 
-#include "stringhelp.h"
-
 #include "t-support.h"
+#include "stringhelp.h"
 
 
 static char *home_buffer;
@@ -144,7 +144,7 @@ test_percent_escape (void)
       result = percent_escape (tests[testno].value, tests[testno].extra);
       if (!result)
         fail (testno);
-      if (strcmp (result, tests[testno].expected))
+      else if (strcmp (result, tests[testno].expected))
         fail (testno);
       xfree (result);
     }
@@ -399,13 +399,13 @@ test_make_filename_try (void)
   out = make_filename_try ("~/foo", "bar", NULL);
   if (!out)
     fail (2);
-  if (home)
+  else if (home)
     {
       if (strlen (out) < homelen + 7)
         fail (2);
-      if (strncmp (out, home, homelen))
+      else if (strncmp (out, home, homelen))
         fail (2);
-      if (strcmp (out+homelen, "/foo/bar"))
+      else if (strcmp (out+homelen, "/foo/bar"))
         fail (2);
     }
   else
@@ -418,13 +418,13 @@ test_make_filename_try (void)
   out = make_filename_try ("~", "bar", NULL);
   if (!out)
     fail (2);
-  if (home)
+  else if (home)
     {
       if (strlen (out) < homelen + 3)
         fail (2);
-      if (strncmp (out, home, homelen))
+      else if (strncmp (out, home, homelen))
         fail (2);
-      if (strcmp (out+homelen, "/bar"))
+      else if (strcmp (out+homelen, "/bar"))
         fail (2);
     }
   else
@@ -446,33 +446,33 @@ test_make_absfilename_try (void)
   out = make_absfilename_try ("foo", "bar", NULL);
   if (!out)
     fail (0);
-  if (strlen (out) < cwdlen + 7)
+  else if (strlen (out) < cwdlen + 7)
     fail (0);
-  if (strncmp (out, cwd, cwdlen))
+  else if (strncmp (out, cwd, cwdlen))
     fail (0);
-  if (strcmp (out+cwdlen, "/foo/bar"))
+  else if (strcmp (out+cwdlen, "/foo/bar"))
     fail (0);
   xfree (out);
 
   out = make_absfilename_try ("./foo", NULL);
   if (!out)
     fail (1);
-  if (strlen (out) < cwdlen + 5)
+  else if (strlen (out) < cwdlen + 5)
     fail (1);
-  if (strncmp (out, cwd, cwdlen))
+  else if (strncmp (out, cwd, cwdlen))
     fail (1);
-  if (strcmp (out+cwdlen, "/./foo"))
+  else if (strcmp (out+cwdlen, "/./foo"))
     fail (1);
   xfree (out);
 
   out = make_absfilename_try (".", NULL);
   if (!out)
     fail (2);
-  if (strlen (out) < cwdlen)
+  else if (strlen (out) < cwdlen)
     fail (2);
-  if (strncmp (out, cwd, cwdlen))
+  else if (strncmp (out, cwd, cwdlen))
     fail (2);
-  if (strcmp (out+cwdlen, ""))
+  else if (strcmp (out+cwdlen, ""))
     fail (2);
   xfree (out);
 
@@ -538,6 +538,418 @@ test_strsplit (void)
     }
 }
 
+
+
+static void
+test_strtokenize (void)
+{
+  struct {
+    const char *s;
+    const char *delim;
+    const char *fields_expected[10];
+  } tv[] = {
+    {
+      "", ":",
+      { "", NULL }
+    },
+    {
+      "a", ":",
+      { "a", NULL }
+    },
+    {
+      ":", ":",
+      { "", "", NULL }
+    },
+    {
+      "::", ":",
+      { "", "", "", NULL }
+    },
+    {
+      "a:b:c", ":",
+      { "a", "b", "c", NULL }
+    },
+    {
+      "a:b:", ":",
+      { "a", "b", "", NULL }
+    },
+    {
+      "a:b", ":",
+      { "a", "b", NULL }
+    },
+    {
+      "aa:b:cd", ":",
+      { "aa", "b", "cd", NULL }
+    },
+    {
+      "aa::b:cd", ":",
+      { "aa", "", "b", "cd", NULL }
+    },
+    {
+      "::b:cd", ":",
+      { "", "", "b", "cd", NULL }
+    },
+    {
+      "aa:   : b:cd ", ":",
+      { "aa", "", "b", "cd", NULL }
+    },
+    {
+      "  aa:   : b:  cd ", ":",
+      { "aa", "", "b", "cd", NULL }
+    },
+    {
+      "  ", ":",
+      { "", NULL }
+    },
+    {
+      "  :", ":",
+      { "", "", NULL }
+    },
+    {
+      "  : ", ":",
+      { "", "", NULL }
+    },
+    {
+      ": ", ":",
+      { "", "", NULL }
+    },
+    {
+      ": x ", ":",
+      { "", "x", NULL }
+    },
+    {
+      "a:bc:cde:fghi:jklmn::foo:", ":",
+      { "a", "bc", "cde", "fghi", "jklmn", "", "foo", "", NULL }
+    },
+    {
+      ",a,bc,,def,", ",",
+      { "", "a", "bc", "", "def", "", NULL }
+    },
+    {
+      " a ", " ",
+      { "", "a", "", NULL }
+    },
+    {
+      " ", " ",
+      { "", "", NULL }
+    },
+    {
+      "", " ",
+      { "", NULL }
+    }
+  };
+
+  int tidx;
+
+  for (tidx = 0; tidx < DIM(tv); tidx++)
+    {
+      char **fields;
+      int field_count;
+      int field_count_expected;
+      int i;
+
+      for (field_count_expected = 0;
+           tv[tidx].fields_expected[field_count_expected];
+           field_count_expected ++)
+        ;
+
+      fields = strtokenize (tv[tidx].s, tv[tidx].delim);
+      if (!fields)
+        fail (tidx * 1000);
+      else
+        {
+          for (field_count = 0; fields[field_count]; field_count++)
+            ;
+          if (field_count != field_count_expected)
+            fail (tidx * 1000);
+          else
+            {
+              for (i = 0; i < field_count_expected; i++)
+                if (strcmp (tv[tidx].fields_expected[i], fields[i]))
+                  {
+                    printf ("For field %d, expected '%s', but got '%s'\n",
+                            i, tv[tidx].fields_expected[i], fields[i]);
+                    fail (tidx * 1000 + i + 1);
+                  }
+            }
+          }
+
+      xfree (fields);
+    }
+}
+
+
+static void
+test_split_fields (void)
+{
+  struct {
+    const char *s;
+    int nfields;
+    const char *fields_expected[10];
+  } tv[] = {
+    {
+      "a bc cde fghi jklmn   foo ", 6,
+      { "a", "bc", "cde", "fghi", "jklmn", "foo", NULL }
+    },
+    {
+      " a bc  def ", 2,
+      { "a", "bc", "def", NULL }
+    },
+    {
+      " a bc  def ", 3,
+      { "a", "bc", "def", NULL }
+    },
+    {
+      " a bc  def ", 4,
+      { "a", "bc", "def", NULL }
+    },
+    {
+      "", 0,
+      { NULL }
+    }
+  };
+
+  int tidx;
+  char *fields[10];
+  int field_count_expected, nfields, field_count, i;
+  char *s2;
+
+  for (tidx = 0; tidx < DIM(tv); tidx++)
+    {
+      nfields = tv[tidx].nfields;
+      assert (nfields <= DIM (fields));
+
+      /* Count the fields.  */
+      for (field_count_expected = 0;
+           tv[tidx].fields_expected[field_count_expected];
+           field_count_expected ++)
+        ;
+      if (field_count_expected > nfields)
+        field_count_expected = nfields;
+
+      /* We need to copy s since split_fields modifies in place.  */
+      s2 = xstrdup (tv[tidx].s);
+      field_count = split_fields (s2, fields, nfields);
+
+      if (field_count != field_count_expected)
+        {
+          printf ("%s: tidx %d: expected %d, got %d\n",
+                  __func__, tidx, i, field_count_expected, field_count);
+          fail (tidx * 1000);
+        }
+      else
+        {
+          for (i = 0; i < field_count_expected; i ++)
+            if (strcmp (tv[tidx].fields_expected[i], fields[i]))
+              {
+                printf ("%s: tidx %d, field %d: expected '%s', got '%s'\n",
+                        __func__,
+                        tidx, i, tv[tidx].fields_expected[i], fields[i]);
+                fail (tidx * 1000 + i + 1);
+              }
+        }
+
+      xfree (s2);
+    }
+}
+
+
+static char *
+stresc (char *s)
+{
+  char *p;
+  int l = strlen (s) + 1;
+
+  for (p = s; *p; p ++)
+    if (*p == '\n')
+      l ++;
+
+  p = xmalloc (l);
+  for (l = 0; *s; s ++, l ++)
+    {
+      if (*s == ' ')
+        p[l] = '_';
+      else if (*p == '\n')
+        {
+          p[l ++] = '\\';
+          p[l ++] = 'n';
+          p[l] = '\n';
+        }
+      else
+        p[l] = *s;
+    }
+  p[l] = *s;
+
+  return p;
+}
+
+
+static void
+test_format_text (void)
+{
+  struct test
+  {
+    int target_cols, max_cols;
+    char *input;
+    char *expected;
+  };
+
+  struct test tests[] = {
+    {
+      10, 12,
+      "",
+      "",
+    },
+    {
+      10, 12,
+      " ",
+      "",
+    },
+    {
+      10, 12,
+      "  ",
+      "",
+    },
+    {
+      10, 12,
+      " \n ",
+      " \n",
+    },
+    {
+      10, 12,
+      " \n  \n ",
+      " \n  \n",
+    },
+    {
+      10, 12,
+      "0123456789 0123456789 0",
+      "0123456789\n0123456789\n0",
+    },
+    {
+      10, 12,
+      "   0123456789   0123456789   0  ",
+      "   0123456789\n0123456789\n0",
+    },
+    {
+      10, 12,
+      "01 34 67 90 23 56  89 12 45 67 89 1",
+      "01 34 67\n90 23 56\n89 12 45\n67 89 1"
+    },
+    {
+      10, 12,
+      "01 34 67 90 23 56  89 12 45 67 89 1",
+      "01 34 67\n90 23 56\n89 12 45\n67 89 1"
+    },
+    {
+      72, 80,
+      "Warning: if you think you've seen more than 10 messages "
+      "signed by this key, then this key might be a forgery!  "
+      "Carefully examine the email address for small variations "
+      "(e.g., additional white space).  If the key is suspect, "
+      "then use 'gpg --tofu-policy bad \"FINGERPRINT\"' to mark it as being bad.\n",
+      "Warning: if you think you've seen more than 10 messages signed by this\n"
+      "key, then this key might be a forgery!  Carefully examine the email\n"
+      "address for small variations (e.g., additional white space).  If the key\n"
+      "is suspect, then use 'gpg --tofu-policy bad \"FINGERPRINT\"' to mark it as\n"
+      "being bad.\n"
+
+    },
+    {
+      72, 80,
+      "Normally, there is only a single key associated with an email "
+      "address.  However, people sometimes generate a new key if "
+      "their key is too old or they think it might be compromised.  "
+      "Alternatively, a new key may indicate a man-in-the-middle "
+      "attack!  Before accepting this key, you should talk to or "
+      "call the person to make sure this new key is legitimate.",
+      "Normally, there is only a single key associated with an email "
+      "address.\nHowever, people sometimes generate a new key if "
+      "their key is too old or\nthey think it might be compromised.  "
+      "Alternatively, a new key may indicate\na man-in-the-middle "
+      "attack!  Before accepting this key, you should talk\nto or "
+      "call the person to make sure this new key is legitimate.",
+    }
+  };
+
+  int i;
+  int failed = 0;
+
+  for (i = 0; i < sizeof (tests) / sizeof (tests[0]); i ++)
+    {
+      struct test *test = &tests[i];
+      char *result =
+        format_text (test->input, 0, test->target_cols, test->max_cols);
+      if (strcmp (result, test->expected) != 0)
+        {
+          printf ("%s: Test #%d failed.\nExpected: '%s'\nResult: '%s'\n",
+                  __func__, i + 1, stresc (test->expected), stresc (result));
+          failed ++;
+        }
+      xfree (result);
+    }
+
+  if (failed)
+    fail(0);
+}
+
+
+static void
+test_compare_version_strings (void)
+{
+  struct { const char *a; const char *b; int okay; } tests[] = {
+    { "1.0.0",   "1.0.0", 1 },
+    { "1.0.0-",  "1.0.0", 1 },
+    { "1.0.0-1", "1.0.0", 1 },
+    { "1.0.0.1", "1.0.0", 1 },
+    { "1.0.0",   "1.0.1", 0 },
+    { "1.0.0-",  "1.0.1", 0 },
+    { "1.0.0-1", "1.0.1", 0 },
+    { "1.0.0.1", "1.0.1", 0 },
+    { "1.0.0",   "1.1.0", 0 },
+    { "1.0.0-",  "1.1.0", 0 },
+    { "1.0.0-1", "1.1.0", 0 },
+    { "1.0.0.1", "1.1.0", 0 },
+
+    { "1.0.0",   "1.0.0-", 1 },
+    { "1.0.0",   "1.0.0-1", 1 },
+    { "1.0.0",   "1.0.0.1", 1 },
+    { "1.1.0",   "1.0.0", 1 },
+    { "1.1.1",   "1.1.0", 1 },
+    { "1.1.2",   "1.1.2", 1 },
+    { "1.1.2",   "1.0.2", 1 },
+    { "1.1.2",   "0.0.2", 1 },
+    { "1.1.2",   "1.1.3", 0 },
+
+    { "0.99.1",  "0.9.9", 1 },
+    { "0.9.1",   "0.91.0", 0 },
+
+    { "1.5.3",   "1.5",  1 },
+    { "1.5.0",   "1.5",  1 },
+    { "1.4.99",  "1.5",  0 },
+    { "1.5",     "1.4.99",  1 },
+    { "1.5",     "1.5.0",  1 },
+    { "1.5",     "1.5.1",  0 },
+
+    { "1.5.3-x17",   "1.5-23",  1 },
+
+    { "1.5.3a",   "1.5.3",  1 },
+    { "1.5.3a",   "1.5.3b",  1 },
+
+    { NULL, NULL, 0 }
+  };
+  int idx;
+  int res;
+
+  for (idx=0; idx < DIM(tests); idx++)
+    {
+      res = compare_version_strings (tests[idx].a, tests[idx].b);
+      /* printf ("test %d: '%s'  '%s'  %d  ->  %d\n", */
+      /*         idx, tests[idx].a, tests[idx].b, tests[idx].okay, res); */
+      if (res != tests[idx].okay)
+        fail (idx);
+    }
+}
+
+
 int
 main (int argc, char **argv)
 {
@@ -551,7 +963,11 @@ main (int argc, char **argv)
   test_make_filename_try ();
   test_make_absfilename_try ();
   test_strsplit ();
+  test_strtokenize ();
+  test_split_fields ();
+  test_compare_version_strings ();
+  test_format_text ();
 
   xfree (home_buffer);
-  return 0;
+  return !!errcount;
 }