/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* GMime * Copyright (C) 2000-2007 Jeffrey Stedfast * * 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 2 of the License, 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, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "testsuite.h" /* Note: this test suite assumes StreamFs and StreamMem are correct */ extern int verbose; #define d(x) #define v(x) if (verbose > 3) x; static GMimeStream * random_whole_stream (const char *datadir, char **filename) { size_t nwritten, buflen, total = 0, size, i; GMimeStream *stream; char buf[4096]; ssize_t n; int fd; /* read between 4k and 14k bytes */ size = 4096 + (size_t) (10240.0 * (rand () / (RAND_MAX + 1.0))); v(fprintf (stdout, "Generating " SIZE_T " bytes of random data... ", size)); v(fflush (stdout)); g_mkdir_with_parents (datadir, 0755); snprintf (buf, sizeof (buf), "%s%cstream.%u", datadir, G_DIR_SEPARATOR, getpid ()); if ((fd = open (buf, O_CREAT | O_TRUNC | O_RDWR, 0666)) == -1) { fprintf (stderr, "Error: Cannot create `%s': %s\n", buf, strerror (errno)); exit (EXIT_FAILURE); } *filename = g_strdup (buf); stream = g_mime_stream_fs_new (fd); while (total < size) { buflen = size - total > sizeof (buf) ? sizeof (buf) : size - total; for (i = 0; i < buflen; i++) buf[i] = (char) (255 * (rand () / (RAND_MAX + 1.0))); nwritten = 0; do { if ((n = g_mime_stream_write (stream, buf + nwritten, buflen - nwritten)) <= 0) break; nwritten += n; total += n; } while (nwritten < buflen); if (nwritten < buflen) break; } g_mime_stream_flush (stream); g_mime_stream_reset (stream); v(fputs ("done\n", stdout)); return stream; } struct _StreamPart { struct _StreamPart *next; off_t pstart, pend; /* start/end offsets of the part stream */ off_t wstart, wend; /* corresponding start/end offsets of the whole stream */ char filename[256]; }; static int check_streams_match (GMimeStream *orig, GMimeStream *dup, const char *filename, int check_overflow) { char buf[4096], dbuf[4096], errstr[1024]; size_t totalsize, totalread = 0; size_t nread, size; ssize_t n; v(fprintf (stdout, "Matching original stream (" OFF_T " -> " OFF_T ") with %s (" OFF_T ", " OFF_T ")... ", orig->position, orig->bound_end, filename, dup->position, dup->bound_end)); if (orig->bound_end != -1) { totalsize = orig->bound_end - orig->position; } else if ((n = g_mime_stream_length (orig)) == -1) { sprintf (errstr, "Error: Unable to get length of original stream\n"); goto fail; } else if (n < (orig->position - orig->bound_start)) { sprintf (errstr, "Error: Overflow on original stream?\n"); goto fail; } else { totalsize = n - (orig->position - orig->bound_start); } while (totalread < totalsize) { if ((n = g_mime_stream_read (orig, buf, sizeof (buf))) <= 0) break; size = n; nread = 0; totalread += n; d(fprintf (stderr, "read " SIZE_T " bytes from original stream\n", size)); do { if ((n = g_mime_stream_read (dup, dbuf + nread, size - nread)) <= 0) { d(fprintf (stderr, "dup read() returned " SSIZE_T ", EOF\n", n)); break; } d(fprintf (stderr, "read " SSIZE_T " bytes from dup stream\n", n)); nread += n; } while (nread < size); if (nread < size) { sprintf (errstr, "Error: `%s' appears to be truncated, short %u+ bytes\n", filename, size - nread); goto fail; } if (memcmp (buf, dbuf, size) != 0) { sprintf (errstr, "Error: `%s': content does not match\n", filename); goto fail; } else { d(fprintf (stderr, SIZE_T " bytes identical\n", size)); } } if (totalread < totalsize) { sprintf (errstr, "Error: expected more data from original stream\n"); goto fail; } if (check_overflow && (n = g_mime_stream_read (dup, buf, sizeof (buf))) > 0) { sprintf (errstr, "Error: `%s' appears to contain extra content\n", filename); goto fail; } v(fputs ("passed\n", stdout)); return 0; fail: v(fputs ("failed\n", stdout)); v(fputs (errstr, stderr)); return -1; } static void test_cat_write (GMimeStream *whole, struct _StreamPart *parts, int bounded) { struct _StreamPart *part = parts; GMimeStream *stream, *sub, *cat; Exception *ex; int fd; cat = g_mime_stream_cat_new (); while (part != NULL) { d(fprintf (stderr, "adding %s start=" OFF_T ", end=" OFF_T "...\n", part->filename, part->pstart, part->pend)); if ((fd = open (part->filename, O_CREAT | O_TRUNC | O_WRONLY, 0666)) == -1) { ex = exception_new ("could not create `%s': %s", part->filename, strerror (errno)); throw (ex); } stream = g_mime_stream_fs_new_with_bounds (fd, part->pstart, part->pend); g_mime_stream_cat_add_source ((GMimeStreamCat *) cat, stream); g_object_unref (stream); part = part->next; } g_mime_stream_reset (whole); if (g_mime_stream_write_to_stream (whole, (GMimeStream *) cat) == -1) { ex = exception_new ("%s", strerror (errno)); g_object_unref (cat); throw (ex); } g_object_unref (cat); /* now lets check that the content matches */ d(fprintf (stderr, "checking all part streams have correct data...\n")); part = parts; while (part != NULL) { d(fprintf (stderr, "checking substream %s\n", part->filename)); if ((fd = open (part->filename, O_RDONLY)) == -1) { ex = exception_new ("could not open `%s': %s", part->filename, strerror (errno)); throw (ex); } if (!(sub = g_mime_stream_substream (whole, part->wstart, part->wend))) { ex = exception_new ("could not substream original stream"); close (fd); throw (ex); } if (!(stream = g_mime_stream_fs_new_with_bounds (fd, part->pstart, -1))) { ex = exception_new ("could not instantiate stream for `%s'", part->filename); close (fd); throw (ex); } d(fprintf (stderr, "checking substream %s matches...\n", part->filename)); if (check_streams_match (sub, stream, part->filename, TRUE) == -1) { ex = exception_new ("streams did not match"); g_object_unref (stream); g_object_unref (sub); throw (ex); } g_object_unref (stream); g_object_unref (sub); part = part->next; } } static void test_cat_read (GMimeStream *whole, struct _StreamPart *parts, int bounded) { struct _StreamPart *part = parts; GMimeStream *stream, *cat; Exception *ex; int fd; cat = g_mime_stream_cat_new (); while (part != NULL) { d(fprintf (stderr, "adding %s start=" OFF_T ", end=" OFF_T "...\n", part->filename, part->pstart, part->pend)); if ((fd = open (part->filename, O_RDONLY)) == -1) { ex = exception_new ("could not open `%s': %s", part->filename, strerror (errno)); g_object_unref (cat); throw (ex); } stream = g_mime_stream_fs_new_with_bounds (fd, part->pstart, bounded ? part->pend : -1); g_mime_stream_cat_add_source ((GMimeStreamCat *) cat, stream); g_object_unref (stream); part = part->next; } g_mime_stream_reset (whole); if (check_streams_match (whole, cat, "stream.part*", TRUE) == -1) { ex = exception_new ("streams do not match"); g_object_unref (cat); throw (ex); } } static void test_cat_seek (GMimeStream *whole, struct _StreamPart *parts, int bounded) { struct _StreamPart *part = parts; GMimeStream *stream, *cat; Exception *ex; off_t offset; size_t size; ssize_t n; int fd; if (whole->bound_end != -1) { size = whole->bound_end - whole->bound_start; } else if ((n = g_mime_stream_length (whole)) == -1) { ex = exception_new ("unable to get original stream length"); throw (ex); } else { size = n; } cat = g_mime_stream_cat_new (); while (part != NULL) { d(fprintf (stderr, "adding %s start=" OFF_T ", end=" OFF_T "...\n", part->filename, part->pstart, part->pend)); if ((fd = open (part->filename, O_RDONLY)) == -1) { ex = exception_new ("could not open `%s': %s", part->filename, strerror (errno)); g_object_unref (cat); throw (ex); } stream = g_mime_stream_fs_new_with_bounds (fd, part->pstart, bounded ? part->pend : -1); g_mime_stream_cat_add_source ((GMimeStreamCat *) cat, stream); g_object_unref (stream); part = part->next; } /* calculate a random seek offset to compare at */ offset = (off_t) (size * (rand () / (RAND_MAX + 1.0))); if (g_mime_stream_seek (whole, offset, GMIME_STREAM_SEEK_SET) == -1) { ex = exception_new ("could not seek to " OFF_T " in original stream: %s", offset, strerror (errno)); throw (ex); } if (g_mime_stream_seek (cat, offset, GMIME_STREAM_SEEK_SET) == -1) { ex = exception_new ("could not seek to " OFF_T ": %s", offset, strerror (errno)); throw (ex); } if (check_streams_match (whole, cat, "stream.part*", TRUE) == -1) { ex = exception_new ("streams did not match"); g_object_unref (cat); throw (ex); } } static void test_cat_substream (GMimeStream *whole, struct _StreamPart *parts, int bounded) { GMimeStream *stream, *cat, *sub1, *sub2; struct _StreamPart *part = parts; off_t start, end; Exception *ex; size_t size; ssize_t n; int fd; if (whole->bound_end != -1) { size = whole->bound_end - whole->bound_start; } else if ((n = g_mime_stream_length (whole)) == -1) { ex = exception_new ("unable to get original stream length"); throw (ex); } else { size = n; } cat = g_mime_stream_cat_new (); while (part != NULL) { d(fprintf (stderr, "adding %s start=" OFF_T ", end=" OFF_T "...\n", part->filename, part->pstart, part->pend)); if ((fd = open (part->filename, O_RDONLY)) == -1) { ex = exception_new ("could not open `%s': %s", part->filename, strerror (errno)); g_object_unref (cat); throw (ex); } stream = g_mime_stream_fs_new_with_bounds (fd, part->pstart, bounded ? part->pend : -1); g_mime_stream_cat_add_source ((GMimeStreamCat *) cat, stream); g_object_unref (stream); part = part->next; } /* calculate a random start/end offsets */ start = (off_t) (size * (rand () / (RAND_MAX + 1.0))); if (rand () % 2) end = start + (off_t) ((size - start) * (rand () / (RAND_MAX + 1.0))); else end = -1; if (!(sub1 = g_mime_stream_substream (whole, start, end))) { ex = exception_new ("could not substream the original stream: %s", strerror (errno)); g_object_unref (cat); throw (ex); } if (!(sub2 = g_mime_stream_substream (cat, start, end))) { ex = exception_new ("%s", strerror (errno)); g_object_unref (sub1); g_object_unref (cat); throw (ex); } g_object_unref (cat); if (check_streams_match (sub1, sub2, "stream.part*", TRUE) == -1) { ex = exception_new ("streams did not match"); g_object_unref (sub1); g_object_unref (sub2); throw (ex); } g_object_unref (sub1); g_object_unref (sub2); } typedef void (* checkFunc) (GMimeStream *stream, struct _StreamPart *parts, int bounded); struct { const char *what; checkFunc check; int bounded; } checks[] = { { "GMimeStreamCat::write()", test_cat_write, FALSE }, { "GMimeStreamCat::read(bound)", test_cat_read, TRUE }, { "GMimeStreamCat::read(unbound)", test_cat_read, FALSE }, { "GMimeStreamCat::seek(bound)", test_cat_seek, TRUE }, { "GMimeStreamCat::seek(unbound)", test_cat_seek, FALSE }, { "GMimeStreamCat::substream(bound)", test_cat_substream, TRUE }, { "GMimeStreamCat::substream(unbound)", test_cat_substream, FALSE }, }; int main (int argc, char **argv) { const char *datadir = "data/cat"; struct _StreamPart *list, *tail, *n; gboolean failed = FALSE; ssize_t wholelen, left; GMimeStream *whole; guint32 part = 0; off_t start = 0; char *filename; struct stat st; size_t len; int fd, i; srand (time (NULL)); g_mime_init (0); testsuite_init (argc, argv); for (i = 1; i < argc; i++) { if (argv[i][0] != '-') { datadir = argv[i]; break; } } if (i < argc) { if (stat (datadir, &st) == -1) { if (errno == ENOENT) { g_mkdir_with_parents (datadir, 0755); if (stat (datadir, &st) == -1) return EXIT_FAILURE; } else return EXIT_FAILURE; } if (S_ISREG (st.st_mode)) { /* test a particular input file */ if ((fd = open (argv[i], O_RDONLY)) == -1) return EXIT_FAILURE; filename = g_strdup (argv[i]); whole = g_mime_stream_fs_new (fd); } else if (S_ISDIR (st.st_mode)) { /* use path as test suite data dir */ whole = random_whole_stream (argv[i], &filename); } else { return EXIT_FAILURE; } } else { whole = random_whole_stream (datadir, &filename); } if ((wholelen = g_mime_stream_length (whole)) == -1) { fprintf (stderr, "Error: length of test stream unknown\n"); g_object_unref (whole); return EXIT_FAILURE; } else if (wholelen == 64) { fprintf (stderr, "Error: length of test stream is unsuitable for testing\n"); g_object_unref (whole); return EXIT_FAILURE; } list = NULL; tail = (struct _StreamPart *) &list; left = wholelen; while (left > 0) { len = 1 + (size_t) (left * (rand() / (RAND_MAX + 1.0))); n = g_new (struct _StreamPart, 1); sprintf (n->filename, "%s.%u", filename, part++); n->pstart = (off_t) 0; /* FIXME: we could make this a random offset */ n->pend = n->pstart + len; n->wend = start + len; n->wstart = start; tail->next = n; tail = n; start += len; left -= len; } tail->next = NULL; testsuite_start ("GMimeStreamCat"); for (i = 0; i < G_N_ELEMENTS (checks) && !failed; i++) { testsuite_check (checks[i].what); try { checks[i].check (whole, list, checks[i].bounded); testsuite_check_passed (); } catch (ex) { testsuite_check_failed ("%s failed: %s", checks[i].what, ex->message); failed = TRUE; } finally; } testsuite_end (); while (list != NULL) { n = list->next; if (!failed) unlink (list->filename); g_free (list); list = n; } g_object_unref (whole); if (!failed) unlink (filename); g_free (filename); g_mime_shutdown (); return testsuite_exit (); }