/* -*- 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 "testsuite.h" extern int verbose; #define d(x) #define v(x) if (verbose > 3) x; static gboolean streams_match (GMimeStream **streams, const char *filename) { 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 ")... ", streams[0]->position, streams[0]->bound_end, filename, streams[1]->position, streams[1]->bound_end)); if (streams[0]->bound_end != -1) { totalsize = streams[0]->bound_end - streams[0]->position; } else if ((n = g_mime_stream_length (streams[0])) == -1) { sprintf (errstr, "Error: Unable to get length of original stream\n"); goto fail; } else if (n < (streams[0]->position - streams[0]->bound_start)) { sprintf (errstr, "Error: Overflow on original stream?\n"); goto fail; } else { totalsize = n - (streams[0]->position - streams[0]->bound_start); } while (totalread < totalsize) { if ((n = g_mime_stream_read (streams[0], buf, sizeof (buf))) <= 0) break; size = n; nread = 0; totalread += n; d(fprintf (stderr, "read " SIZE_T " bytes from stream[0]\n", size)); do { if ((n = g_mime_stream_read (streams[1], dbuf + nread, size - nread)) <= 0) { d(fprintf (stderr, "stream[1] read() returned " SSIZE_T ", EOF\n", n)); break; } d(fprintf (stderr, "read " SSIZE_T " bytes from stream[1]\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, "%u bytes identical\n", size)); } } if (totalread < totalsize) { sprintf (errstr, "Error: expected more data from stream[0]\n"); goto fail; } if ((n = g_mime_stream_read (streams[1], buf, sizeof (buf))) > 0) { sprintf (errstr, "Error: `%s' appears to contain extra content\n", filename); goto fail; } v(fputs ("passed\n", stdout)); return TRUE; fail: v(fputs ("failed\n", stdout)); v(fputs (errstr, stderr)); return FALSE; } static void test_stream_gets (GMimeStream *stream, const char *filename) { char sbuf[100], rbuf[100]; ssize_t slen; FILE *fp; if (!(fp = fopen (filename, "r+"))) throw (exception_new ("could not open `%s': %s", filename, strerror (errno))); while (!g_mime_stream_eos (stream)) { rbuf[0] = '\0'; slen = g_mime_stream_buffer_gets (stream, sbuf, sizeof (sbuf)); fgets (rbuf, sizeof (rbuf), fp); if (strcmp (sbuf, rbuf) != 0) break; } fclose (fp); if (strcmp (sbuf, rbuf) != 0) { v(fprintf (stderr, "\tstream: \"%s\" (" SIZE_T ")\n", sbuf, strlen (sbuf))); v(fprintf (stderr, "\treal: \"%s\" (" SIZE_T ")\n", rbuf, strlen (rbuf))); throw (exception_new ("streams did not match")); } } static void test_stream_buffer_gets (const char *filename) { GMimeStream *stream, *buffered; int fd; if ((fd = open (filename, O_RDONLY)) == -1) { v(fprintf (stderr, "failed to open %s", filename)); return; } stream = g_mime_stream_fs_new (fd); testsuite_check ("GMimeStreamBuffer::block gets"); try { g_mime_stream_reset (stream); buffered = g_mime_stream_buffer_new (stream, GMIME_STREAM_BUFFER_BLOCK_READ); test_stream_gets (buffered, filename); testsuite_check_passed (); } catch (ex) { testsuite_check_failed ("GMimeStreamBuffer::block gets() failed: %s", ex->message); } finally { g_mime_stream_unref (buffered); } testsuite_check ("GMimeStreamBuffer::cache gets"); try { g_mime_stream_reset (stream); buffered = g_mime_stream_buffer_new (stream, GMIME_STREAM_BUFFER_CACHE_READ); test_stream_gets (buffered, filename); testsuite_check_passed (); } catch (ex) { testsuite_check_failed ("GMimeStreamBuffer::block gets() failed: %s", ex->message); } finally { g_mime_stream_unref (buffered); } g_mime_stream_unref (stream); } #if 0 static void test_stream_mem (const char *filename) { /* Note: this also tests g_mime_stream_write_to_stream */ GMimeStream *stream, *fstream; int fd; if ((fd = open (filename, O_RDONLY)) == -1) { v(fprintf (stderr, "failed to open %s", filename)); return; } testsuite_start ("GMimeStreamMem"); fstream = g_mime_stream_fs_new (fd); stream = g_mime_stream_mem_new (); testsuite_check ("GMimeStreamMem::read()"); try { if (g_mime_stream_write_to_stream (fstream, stream) == -1) throw (exception_new ("g_mime_stream_write_to_stream() failed")); if (g_mime_stream_length (stream) != g_mime_stream_length (fstream)) throw (exception_new ("stream lengths didn't match")); test_stream_read (stream, filename); testsuite_check_passed (); } catch (ex) { testsuite_check_failed ("GMimeStreamMem::read() failed: %s", ex->message); } finally; g_mime_stream_unref (fstream); g_mime_stream_unref (stream); testsuite_end (); } #endif static gboolean check_stream_fs (const char *input, const char *output, const char *filename, off_t start, off_t end) { GMimeStream *streams[2], *stream; Exception *ex = NULL; int fd[2]; if ((fd[0] = open (input, O_RDONLY)) == -1) return FALSE; if ((fd[1] = open (output, O_RDONLY)) == -1) { close (fd[0]); return FALSE; } stream = g_mime_stream_fs_new (fd[0]); streams[0] = g_mime_stream_substream (stream, start, end); g_object_unref (stream); streams[1] = g_mime_stream_fs_new (fd[1]); if (!streams_match (streams, filename)) ex = exception_new ("GMimeStreamFs streams did not match for `%s'", filename); g_object_unref (streams[0]); g_object_unref (streams[1]); if (ex != NULL) throw (ex); return TRUE; } static gboolean check_stream_file (const char *input, const char *output, const char *filename, off_t start, off_t end) { GMimeStream *streams[2], *stream; Exception *ex = NULL; FILE *fp[2]; if (!(fp[0] = fopen (input, "r"))) return FALSE; if (!(fp[1] = fopen (output, "r"))) { fclose (fp[0]); return FALSE; } stream = g_mime_stream_file_new (fp[0]); streams[0] = g_mime_stream_substream (stream, start, end); g_object_unref (stream); streams[1] = g_mime_stream_file_new (fp[1]); if (!streams_match (streams, filename)) ex = exception_new ("GMimeStreamFile streams did not match for `%s'", filename); g_object_unref (streams[0]); g_object_unref (streams[1]); if (ex != NULL) throw (ex); return TRUE; } static gboolean check_stream_mmap (const char *input, const char *output, const char *filename, off_t start, off_t end) { GMimeStream *streams[2], *stream; Exception *ex = NULL; int fd[2]; if ((fd[0] = open (input, O_RDONLY)) == -1) return FALSE; if ((fd[1] = open (output, O_RDONLY)) == -1) { close (fd[0]); return FALSE; } stream = g_mime_stream_mmap_new (fd[0], PROT_READ, MAP_PRIVATE); streams[0] = g_mime_stream_substream (stream, start, end); g_object_unref (stream); streams[1] = g_mime_stream_mmap_new (fd[1], PROT_READ, MAP_PRIVATE); if (!streams_match (streams, filename)) ex = exception_new ("streams did not match"); g_object_unref (streams[0]); g_object_unref (streams[1]); if (ex != NULL) throw (ex); return TRUE; } static gboolean check_stream_buffer_block (const char *input, const char *output, const char *filename, off_t start, off_t end) { GMimeStream *streams[2], *stream; Exception *ex = NULL; int fd[2]; if ((fd[0] = open (input, O_RDONLY)) == -1) return FALSE; if ((fd[1] = open (output, O_RDONLY)) == -1) { close (fd[0]); return FALSE; } streams[0] = g_mime_stream_fs_new (fd[0]); stream = g_mime_stream_buffer_new (streams[0], GMIME_STREAM_BUFFER_BLOCK_READ); g_object_unref (streams[0]); streams[0] = g_mime_stream_substream (stream, start, end); g_object_unref (stream); streams[1] = g_mime_stream_fs_new (fd[1]); if (!streams_match (streams, filename)) ex = exception_new ("GMimeStreamBuffer (Block Mode) streams did not match for `%s'", filename); g_object_unref (streams[0]); g_object_unref (streams[1]); if (ex != NULL) throw (ex); return TRUE; } static gboolean check_stream_buffer_cache (const char *input, const char *output, const char *filename, off_t start, off_t end) { GMimeStream *streams[2], *stream; Exception *ex = NULL; int fd[2]; if ((fd[0] = open (input, O_RDONLY)) == -1) return FALSE; if ((fd[1] = open (output, O_RDONLY)) == -1) { close (fd[0]); return FALSE; } streams[0] = g_mime_stream_fs_new (fd[0]); stream = g_mime_stream_buffer_new (streams[0], GMIME_STREAM_BUFFER_CACHE_READ); g_object_unref (streams[0]); streams[0] = g_mime_stream_substream (stream, start, end); g_object_unref (stream); streams[1] = g_mime_stream_fs_new (fd[1]); if (!streams_match (streams, filename)) ex = exception_new ("GMimeStreamBuffer (Cache Mode) streams did not match for `%s'", filename); g_object_unref (streams[0]); g_object_unref (streams[1]); if (ex != NULL) throw (ex); return TRUE; } typedef gboolean (* checkFunc) (const char *, const char *, const char *, off_t, off_t); static struct { const char *what; checkFunc check; } checks[] = { { "GMimeStreamFs", check_stream_fs }, { "GMimeStreamFile", check_stream_file }, { "GMimeStreamMmap", check_stream_mmap }, { "GMimeStreamBuffer (block mode)", check_stream_buffer_block }, { "GMimeStreamBuffer (cache mode)", check_stream_buffer_cache }, }; static void test_streams (DIR *dir, const char *datadir, const char *filename) { char inpath[256], outpath[256], *p, *q, *o; struct dirent *dent; off_t start, end; size_t n; guint i; p = g_stpcpy (inpath, datadir); *p++ = G_DIR_SEPARATOR; p = g_stpcpy (p, "input"); *p++ = G_DIR_SEPARATOR; strcpy (p, filename); q = g_stpcpy (outpath, datadir); *q++ = G_DIR_SEPARATOR; q = g_stpcpy (q, "output"); *q++ = G_DIR_SEPARATOR; *q = '\0'; n = strlen (filename); while ((dent = readdir (dir))) { if (strncmp (dent->d_name, filename, n) != 0 || dent->d_name[n] != ':') continue; p = dent->d_name + n + 1; if ((start = strtol (p, &o, 10)) < 0 || *o != ',') continue; p = o + 1; if ((((end = strtol (p, &o, 10)) < start) && end != -1) || *o != '\0') continue; strcpy (q, dent->d_name); for (i = 0; i < G_N_ELEMENTS (checks); i++) { testsuite_check ("%s on `%s'", checks[i].what, dent->d_name); try { if (!checks[i].check (inpath, outpath, dent->d_name, start, end)) { testsuite_check_warn ("%s could not open `%s'", checks[i].what, dent->d_name); } else { testsuite_check_passed (); } } catch (ex) { testsuite_check_failed ("%s on `%s' failed: %s", checks[i].what, dent->d_name, ex->message); } finally; } } rewinddir (dir); } static void gen_random_stream (GMimeStream *stream) { size_t nwritten, buflen, total = 0, size, i; char buf[4096]; ssize_t n; /* 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)); 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)); } static int gen_test_data (const char *datadir) { GMimeStream *istream, *ostream, *stream; char input[256], output[256], *name, *p; off_t start, end; struct stat st; size_t len; int fd, i; srand (time (NULL)); name = g_stpcpy (input, datadir); *name++ = G_DIR_SEPARATOR; name = g_stpcpy (name, "input"); p = g_stpcpy (output, datadir); *p++ = G_DIR_SEPARATOR; p = g_stpcpy (p, "output"); g_mkdir_with_parents (input, 0755); g_mkdir_with_parents (output, 0755); *name++ = G_DIR_SEPARATOR; strcpy (name, "streamXXXXXX"); if ((fd = mkstemp (input)) == -1) return -1; *p++ = G_DIR_SEPARATOR; p = g_stpcpy (p, name); *p++ = ':'; istream = g_mime_stream_fs_new (fd); gen_random_stream (istream); if (stat (input, &st) == -1 || !S_ISREG (st.st_mode)) { g_object_unref (istream); unlink (input); return -1; } for (i = 0; i < 64; i++) { retry: start = (off_t) (st.st_size * (rand () / (RAND_MAX + 1.0))); len = (size_t) (st.st_size * (rand () / (RAND_MAX + 1.0))); if (start + len > st.st_size) { len = st.st_size - start; end = -1; } else { end = start + len; } sprintf (p, OFF_T "," OFF_T, start, end); if ((fd = open (output, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0666)) == -1) goto retry; ostream = g_mime_stream_fs_new (fd); stream = g_mime_stream_substream (istream, start, end); g_mime_stream_write_to_stream (stream, ostream); g_mime_stream_flush (ostream); g_object_unref (ostream); g_object_unref (stream); } return 0; } int main (int argc, char **argv) { const char *datadir = "data/streams"; gboolean gen_data = TRUE; struct dirent *dent; char path[256], *p; DIR *dir, *outdir; int i; g_mime_init (0); testsuite_init (argc, argv); for (i = 1; i < argc; i++) { if (argv[i][0] != '-') { datadir = argv[i]; break; } } testsuite_start ("Stream tests"); p = g_stpcpy (path, datadir); *p++ = G_DIR_SEPARATOR; strcpy (p, "output"); if (!(outdir = opendir (path))) { if (gen_test_data (datadir) == -1 || !(outdir = opendir (path))) goto exit; gen_data = FALSE; } p = g_stpcpy (p, "input"); if (!(dir = opendir (path))) { if (!gen_data || gen_test_data (datadir) == -1 || !(dir = opendir (path))) { closedir (outdir); goto exit; } gen_data = FALSE; } if (gen_data) { while ((dent = readdir (dir))) { if (dent->d_name[0] == '.' || !strcmp (dent->d_name, "README")) continue; gen_data = FALSE; break; } rewinddir (dir); if (gen_data && gen_test_data (datadir) == -1) goto exit; } *p++ = G_DIR_SEPARATOR; *p = '\0'; while ((dent = readdir (dir))) { if (dent->d_name[0] == '.' || !strcmp (dent->d_name, "README")) continue; test_streams (outdir, datadir, dent->d_name); strcpy (p, dent->d_name); test_stream_buffer_gets (path); } closedir (outdir); closedir (dir); exit: testsuite_end (); g_mime_shutdown (); return testsuite_exit (); }