Implement and test multi-matching on strings.
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 15 May 2014 18:29:50 +0000 (19:29 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 15 May 2014 18:36:22 +0000 (19:36 +0100)
If the subprocess prints multiple matching strings, and you call
mexp_expect repeatedly, the code now does the right thing.

.gitignore
Makefile.am
miniexpect.c
miniexpect.h
miniexpect.pod
test-multi-match.c [new file with mode: 0644]

index 959fb48..959e32a 100644 (file)
@@ -31,4 +31,5 @@
 /stamp-h1
 /test-driver
 /test-ls-version
 /stamp-h1
 /test-driver
 /test-ls-version
+/test-multi-match
 /test-spawn
 /test-spawn
index 4d3f91f..98d21eb 100644 (file)
@@ -41,7 +41,8 @@ example_sshpass_LDADD = libminiexpect.la
 TESTS = $(check_PROGRAMS)
 check_PROGRAMS = \
        test-spawn \
 TESTS = $(check_PROGRAMS)
 check_PROGRAMS = \
        test-spawn \
-       test-ls-version
+       test-ls-version \
+       test-multi-match
 
 test_spawn_SOURCES = test-spawn.c tests.h miniexpect.h
 test_spawn_CFLAGS = $(PCRE_CFLAGS) -Wall
 
 test_spawn_SOURCES = test-spawn.c tests.h miniexpect.h
 test_spawn_CFLAGS = $(PCRE_CFLAGS) -Wall
@@ -51,6 +52,10 @@ test_ls_version_SOURCES = test-ls-version.c tests.h miniexpect.h
 test_ls_version_CFLAGS = $(PCRE_CFLAGS) -Wall
 test_ls_version_LDADD = libminiexpect.la
 
 test_ls_version_CFLAGS = $(PCRE_CFLAGS) -Wall
 test_ls_version_LDADD = libminiexpect.la
 
+test_multi_match_SOURCES = test-multi-match.c tests.h miniexpect.h
+test_multi_match_CFLAGS = $(PCRE_CFLAGS) -Wall
+test_multi_match_LDADD = libminiexpect.la
+
 # parallel-tests breaks the ability to put 'valgrind' into
 # TESTS_ENVIRONMENT.  Hence we have to work around it:
 check-valgrind:
 # parallel-tests breaks the ability to put 'valgrind' into
 # TESTS_ENVIRONMENT.  Hence we have to work around it:
 check-valgrind:
index 7f02584..9e9cc0e 100644 (file)
@@ -21,6 +21,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <string.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <poll.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <poll.h>
@@ -53,6 +54,7 @@ create_handle (void)
   h->pcre_error = 0;
   h->buffer = NULL;
   h->len = h->alloc = 0;
   h->pcre_error = 0;
   h->buffer = NULL;
   h->len = h->alloc = 0;
+  h->next_match = -1;
   h->user1 = h->user2 = h->user3 = NULL;
 
   return h;
   h->user1 = h->user2 = h->user3 = NULL;
 
   return h;
@@ -64,6 +66,7 @@ clear_buffer (mexp_h *h)
   free (h->buffer);
   h->buffer = NULL;
   h->alloc = h->len = 0;
   free (h->buffer);
   h->buffer = NULL;
   h->alloc = h->len = 0;
+  h->next_match = -1;
 }
 
 int
 }
 
 int
@@ -209,12 +212,19 @@ mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize)
 
   time (&start_t);
 
 
   time (&start_t);
 
-  /* Clear the read buffer. */
-  /* XXX This is possibly incorrect because it throws away inputs that
-   * may not have been matched yet.  A better idea is to record the
-   * end of the previous match and only throw that away.
-   */
-  clear_buffer (h);
+  if (h->next_match == -1) {
+    /* Fully clear the buffer, then read. */
+    clear_buffer (h);
+  } else {
+    /* See the comment in the manual about h->next_match.  We have
+     * some data remaining in the buffer, so begin by matching that.
+     */
+    memmove (&h->buffer[0], &h->buffer[h->next_match], h->len - h->next_match);
+    h->len -= h->next_match;
+    h->buffer[h->len] = '\0';
+    h->next_match = -1;
+    goto try_match;
+  }
 
   for (;;) {
     /* If we've got a timeout then work out how many seconds are left.
 
   for (;;) {
     /* If we've got a timeout then work out how many seconds are left.
@@ -279,6 +289,7 @@ mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize)
     fprintf (stderr, "DEBUG: buffer content: %s\n", h->buffer);
 #endif
 
     fprintf (stderr, "DEBUG: buffer content: %s\n", h->buffer);
 #endif
 
+  try_match:
     /* See if there is a full or partial match against any regexp. */
     if (regexps) {
       size_t i;
     /* See if there is a full or partial match against any regexp. */
     if (regexps) {
       size_t i;
@@ -297,6 +308,10 @@ mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize)
 
         if (r >= 0) {
           /* A full match. */
 
         if (r >= 0) {
           /* A full match. */
+          if (ovector != NULL && ovecsize >= 1 && ovector[1] >= 0)
+            h->next_match = ovector[1];
+          else
+            h->next_match = -1;
           return regexps[i].r;
         }
 
           return regexps[i].r;
         }
 
index 9a374b7..f987655 100644 (file)
@@ -41,6 +41,7 @@ struct mexp_h {
   char *buffer;
   size_t len;
   size_t alloc;
   char *buffer;
   size_t len;
   size_t alloc;
+  ssize_t next_match;
   size_t read_size;
   int pcre_error;
   void *user1;
   size_t read_size;
   int pcre_error;
   void *user1;
index 0ab4a4a..66ba8c2 100644 (file)
@@ -114,6 +114,19 @@ the process, but it will contain at least the part matched by the
 regular expression (and maybe some more).  C<buffer> is the read
 buffer and C<len> is the number of bytes of data in the buffer.
 
 regular expression (and maybe some more).  C<buffer> is the read
 buffer and C<len> is the number of bytes of data in the buffer.
 
+   ssize_t next_match;
+
+If C<mexp_expect> returns a match, then C<next_match> points to the
+first byte in the buffer I<after> the fully matched expression.  (It
+may be C<-1> which means it is invalid).  The next time that
+C<mexp_expect> is called, it will start by consuming the data
+C<buffer[next_match...len-1]>.  Callers may also need to read from
+that point in the buffer before calling L<read(2)> on the file
+descriptor.  Callers may also set this, for example setting it to
+C<-1> in order to ignore the remainder of the buffer.  In most cases
+callers can ignore this field, and C<mexp_expect> will just do the
+right thing when called repeatedly.
+
    size_t read_size;
 
 Callers may set this to the natural size (in bytes) for reads from the
    size_t read_size;
 
 Callers may set this to the natural size (in bytes) for reads from the
diff --git a/test-multi-match.c b/test-multi-match.c
new file mode 100644 (file)
index 0000000..7237202
--- /dev/null
@@ -0,0 +1,97 @@
+/* miniexpect test suite
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "miniexpect.h"
+#include "tests.h"
+
+int
+main (int argc, char *argv[])
+{
+  mexp_h *h;
+  int status;
+  int r;
+  int rv[5];
+  size_t i;
+  pcre *multi_re = test_compile_re ("multi");
+  pcre *match_re = test_compile_re ("match");
+  pcre *ing_re = test_compile_re ("ing");
+  pcre *str_re = test_compile_re ("str");
+  pcre *s_re = test_compile_re ("s");
+  const int ovecsize = 12;
+  int ovector[ovecsize];
+
+  /* If the subprocess prints multiple things, we should be able to
+   * repeatedly call mexp_expect to match on each one.  This didn't
+   * work correctly in earlier versions of the library.
+   */
+  h = mexp_spawnl ("echo", "echo", "multimatchingstrs", NULL);
+  assert (h != NULL);
+
+  for (i = 0; i < 5; ++i) {
+    r = mexp_expect (h,
+                     (mexp_regexp[]) {
+                       { 100, multi_re },
+                       { 101, match_re },
+                       { 102, ing_re },
+                       { 103, str_re },
+                       { 104, s_re },
+                       { 0 },
+                     }, ovector, ovecsize);
+    switch (r) {
+    case 100: case 101: case 102: case 103: case 104:
+      printf ("iteration %zu: matched %d\n", i, r);
+      rv[i] = r;
+      break;
+    case MEXP_EOF:
+      fprintf (stderr, "error: unexpected EOF in iteration %zu\n", i);
+      exit (EXIT_FAILURE);
+    case MEXP_TIMEOUT:
+      fprintf (stderr, "error: unexpected timeout in iteration %zu\n", i);
+      exit (EXIT_FAILURE);
+    case MEXP_ERROR:
+        perror ("mexp_expect");
+      exit (EXIT_FAILURE);
+    case MEXP_PCRE_ERROR:
+      fprintf (stderr, "error: PCRE error: %d\n", h->pcre_error);
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  assert (rv[0] == 100); /* multi */
+  assert (rv[1] == 101); /* match */
+  assert (rv[2] == 102); /* ing */
+  assert (rv[3] == 103); /* str */
+  assert (rv[4] == 104); /* s */
+
+  status = mexp_close (h);
+  if (status != 0 && !test_is_sighup (status)) {
+    fprintf (stderr, "%s: non-zero exit status from subcommand: ", argv[0]);
+    test_diagnose (status);
+    fprintf (stderr, "\n");
+    exit (EXIT_FAILURE);
+  }
+
+  exit (EXIT_SUCCESS);
+}