[Libguestfs] [PATCH nbdkit 4/4] tests: map: Add a stochastic test of the map filter.

Richard W.M. Jones rjones at redhat.com
Tue Jul 31 19:55:52 UTC 2018


---
 .gitignore                  |   2 +
 tests/Makefile.am           |   5 +
 tests/test-map-stochastic.c | 307 ++++++++++++++++++++++++++++++++++++
 3 files changed, 314 insertions(+)

diff --git a/.gitignore b/.gitignore
index a84ad9a..da8b845 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,7 @@ Makefile.in
 /tests/ext2.img
 /tests/file-data
 /tests/keys.psk
+/tests/mapfile
 /tests/offset-data
 /tests/partition-disk
 /tests/pki
@@ -67,6 +68,7 @@ Makefile.in
 /tests/test-file
 /tests/test-gzip
 /tests/test-lua
+/tests/test-map-stochastic
 /tests/test-memory
 /tests/test-newstyle
 /tests/test-nbd
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2306506..40efd73 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -550,6 +550,11 @@ TESTS += test-log.sh
 
 # map filter test.
 TESTS += test-map-empty.sh
+LIBGUESTFS_TESTS += test-map-stochastic
+
+test_map_stochastic_SOURCES = test-map-stochastic.c test.h
+test_map_stochastic_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS)
+test_map_stochastic_LDADD = libtest.la $(LIBGUESTFS_LIBS)
 
 # nozero filter test.
 TESTS += test-nozero.sh
diff --git a/tests/test-map-stochastic.c b/tests/test-map-stochastic.c
new file mode 100644
index 0000000..10b37de
--- /dev/null
+++ b/tests/test-map-stochastic.c
@@ -0,0 +1,307 @@
+/* nbdkit
+ * Copyright (C) 2018 Red Hat Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <guestfs.h>
+
+#include "test.h"
+
+#define PATTERN_SIZE 65536      /* size in bytes of the input pattern */
+#define FILTER_SIZE 131072      /* size in bytes of the output of the filter */
+#define MAPFILE "mapfile"       /* in tests directory */
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+
+static char pattern[PATTERN_SIZE]; /* underlying pattern data */
+static char expected[FILTER_SIZE]; /* after mapping */
+static char actual[FILTER_SIZE]; /* what we read from nbdkit --filter=map */
+
+static void create_pattern (void);
+static void create_map (void);
+static void mismatch_error (size_t output_size) __attribute__((noreturn));
+
+int
+main (int argc, char *argv[])
+{
+  guestfs_h *g;
+  int r;
+  size_t i, output_size;
+
+  srandom (time (NULL));
+
+  create_pattern ();
+
+  /* Create the random mapfile and associated expected data. */
+  create_map ();
+
+  /* We place a truncate filter in front of the map to round up its
+   * size to the next multiple of 512 bytes.  Otherwise qemu chokes on
+   * the non-standard size of most randomly generated maps.
+   */
+  if (test_start_nbdkit ("-r",
+                         "--filter", "truncate",
+                         "--filter", "map",
+                         "pattern", "size=" XSTR(PATTERN_SIZE),
+                         "map=" MAPFILE,
+                         "round-up=512",
+                         NULL) == -1)
+    exit (EXIT_FAILURE);
+
+  g = guestfs_create ();
+  if (g == NULL) {
+    perror ("guestfs_create");
+    exit (EXIT_FAILURE);
+  }
+
+  r = guestfs_add_drive_opts (g, "",
+                              GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
+                              GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+                              GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "nbd",
+                              GUESTFS_ADD_DRIVE_OPTS_SERVER, server,
+                              -1);
+  if (r == -1)
+    exit (EXIT_FAILURE);
+
+  if (guestfs_launch (g) == -1)
+    exit (EXIT_FAILURE);
+
+  /* The size of the final device is not necessarily FILTER_SIZE.  It
+   * might be smaller if there are no mappings that happen to cover
+   * the end.  We can ask nbdkit for the size.
+   */
+  output_size = guestfs_blockdev_getsize64 (g, "/dev/sda");
+  if (output_size == -1)
+    exit (EXIT_FAILURE);
+
+  if (output_size > FILTER_SIZE) {
+    fprintf (stderr,
+             "test-map-stochastic: unexpected size returned for device: "
+             "%zu > %d\n", output_size, FILTER_SIZE);
+    exit (EXIT_FAILURE);
+  }
+
+  i = 0;
+  while (i < output_size) {
+    /* Note that qemu will only issue reads on 512 byte boundaries
+     * so mostly this is pointless.
+     */
+    int n = random () % 2048;
+    size_t read;
+    char *buf;
+
+    if (n <= 0)
+      n = 1;
+    if (n > output_size-i)
+      n = output_size-i;
+
+    buf = guestfs_pread_device (g, "/dev/sda", n, (int64_t) i, &read);
+    if (buf == NULL)
+      exit (EXIT_FAILURE);
+    memcpy (&actual[i], buf, read);
+    free (buf);
+
+    i += read;
+  }
+
+  if (memcmp (actual, expected, output_size) != 0)
+    mismatch_error (output_size);
+
+  unlink (MAPFILE);
+
+  guestfs_close (g);
+  exit (EXIT_SUCCESS);
+}
+
+/* Create the pattern data in the same way as the pattern plugin
+ * works.  See nbdkit-pattern-plugin(1).
+ */
+static void
+create_pattern (void)
+{
+  size_t i;
+  uint64_t d;
+
+  for (i = 0; i < PATTERN_SIZE; i += 8) {
+    d = i;
+    d = htobe64 (d);
+    memcpy (&pattern[i], &d, 8);
+  }
+}
+
+/* Create the random map, write it to the map file, and map
+ * the pattern data accordingly.
+ */
+static const int rows[] = { 0, 1, 3, 5, 7, 11, 13, 17, 19, 23 };
+static const int alignments[] = { 1, 1, 1, 2, 4, 8, 512 };
+static const int lens[] = { 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3,
+                            7, 8, 9,
+                            15, 16, 17,
+                            512, 1024, 2048, 8192,
+                            16384, 16384, 16384*2, 16384*2,
+                            16384*3, 16384*3, 16384*4, 16384*4,
+                            16384*5, 16384*6, 16384*7, 16384*8 };
+
+#define RANDOM_CHOICE(array)                            \
+  array[random () % (sizeof array / sizeof array[0])]
+
+static uint64_t
+random_alignment (uint64_t i)
+{
+  uint64_t alignment = RANDOM_CHOICE (alignments);
+
+  i &= ~(alignment - 1);
+  return i;
+}
+
+static void
+create_map (void)
+{
+  FILE *fp;
+  size_t i, j, nr_rows;
+  uint64_t start, length, to;
+  int end_form, open_end;
+
+  memset (expected, 0, sizeof expected);
+  memset (actual, 0, sizeof actual);
+
+  /* Pick a random number of mappings (rows in the map file).  Can be 0. */
+  nr_rows = RANDOM_CHOICE (rows);
+
+  fp = fopen (MAPFILE, "w");
+  if (fp == NULL) {
+    perror (MAPFILE);
+    exit (EXIT_FAILURE);
+  }
+
+  fprintf (fp, "# %s for testing the map filter\n", MAPFILE);
+  fprintf (fp, "# generated randomly by %s\n", __FILE__);
+  fprintf (fp, "# pattern plugin (input) size: %d\n", PATTERN_SIZE);
+  fprintf (fp, "# map filter (max output) size: %d\n", FILTER_SIZE);
+  fprintf (fp, "# nr_rows: %zu\n", nr_rows);
+  fprintf (fp, "\n");
+
+  for (i = 0; i < nr_rows; ++i) {
+    /* Pick a random start point, length and destination (to). */
+    start = random () % PATTERN_SIZE;
+    start = random_alignment (start);
+    length = RANDOM_CHOICE (lens);
+    to = random () % FILTER_SIZE;
+    to = random_alignment (to);
+
+    /* Don't have a mapping going beyond the end of the output size
+     * we want.
+     */
+    if (to + length >= FILTER_SIZE)
+      length = FILTER_SIZE - to;
+
+    /* Choose randomly whether to use start-end or start,length. */
+    end_form = (random () % 2) == 1;
+
+    /* Randomly pick some open-ended mappings, but don't allow them if
+     * that would go beyond the output size we want.
+     */
+    open_end = 0;
+    if (PATTERN_SIZE - to <= FILTER_SIZE - start)
+      open_end = (random () % 8) == 1;
+
+    if (end_form) {
+      if (open_end)
+        fprintf (fp, "%" PRIu64 "-\t%" PRIu64 "\n", start, to);
+      else
+        fprintf (fp, "%" PRIu64 "-%" PRIu64 "\t%" PRIu64 "\n",
+                 start, start+length-1, to);
+    }
+    else {
+      if (open_end)
+        fprintf (fp, "%" PRIu64 "\t%" PRIu64 "\n", start, to);
+      else
+        fprintf (fp, "%" PRIu64 ",%" PRIu64 "\t%" PRIu64 "\n",
+                 start, length, to);
+    }
+
+    /* Perform the mapping into our expected[] array. */
+    for (j = 0; j < length; ++j) {
+      if (to+j <= FILTER_SIZE) {
+        if (start+j <= PATTERN_SIZE)
+          expected[to+j] = pattern[start+j];
+        else
+          expected[to+j] = 0; /* Bytes outside the source read as 0 */
+      }
+    }
+  }
+
+  fprintf (fp, "\n");
+  fprintf (fp, "# end of %s\n", MAPFILE);
+  fclose (fp);
+}
+
+static void
+dump_array (const char *array, size_t size)
+{
+  FILE *pp;
+
+  pp = popen ("hexdump -C", "w");
+  if (pp == NULL) {
+    perror ("popen: hexdump");
+    return;
+  }
+  fwrite (array, 1, size, pp);
+  pclose (pp);
+}
+
+static void
+mismatch_error (size_t output_size)
+{
+  fprintf (stderr, "test-map-stochastic: "
+           "actual data read back does not match expected data\n");
+  fprintf (stderr, "\n");
+  fprintf (stderr, "map contains:\n");
+  system ("cat " MAPFILE);
+  fprintf (stderr, "\n");
+  fprintf (stderr, "expected data:\n");
+  dump_array (expected, output_size);
+  fprintf (stderr, "\n");
+  fprintf (stderr, "actual data:\n");
+  dump_array (actual, output_size);
+  exit (EXIT_FAILURE);
+}
-- 
2.18.0




More information about the Libguestfs mailing list