/* -*- 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <gmime/gmime.h>
#include <gmime/gmime-stream.h>
#include <gmime/gmime-stream-fs.h>
#include <gmime/gmime-stream-cat.h>
#include <gmime/gmime-stream-mem.h>
#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 ();
}
syntax highlighted by Code2HTML, v. 0.9.1