summaryrefslogtreecommitdiff
path: root/test/xdeltatest.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/xdeltatest.c')
-rwxr-xr-xtest/xdeltatest.c681
1 files changed, 681 insertions, 0 deletions
diff --git a/test/xdeltatest.c b/test/xdeltatest.c
new file mode 100755
index 0000000..fc46307
--- /dev/null
+++ b/test/xdeltatest.c
@@ -0,0 +1,681 @@
+/* -*- Mode: C;-*-
+ *
+ * This file is part of XDelta - A binary delta generator.
+ *
+ * Copyright (C) 1997, 1998, 2001 Josh MacDonald
+ *
+ * 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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Josh MacDonald <jmacd@CS.Berkeley.EDU>
+ *
+ * $Id: xdeltatest.c 1.7.1.1 Sat, 27 Jan 2007 17:53:47 -0800 jmacd $
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "config.h"
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <stdio.h>
+#include <zlib.h>
+
+#include "xdelta.h"
+
+//////////////////////////////////////////////////////////////////////
+// Configure these parameters
+// @@@ Of course, a real test wouldn't require this configuration,
+// fix that!
+//////////////////////////////////////////////////////////////////////
+
+const char* cmd_data_source = "/zerostreet2/small-scratch-file";
+guint cmd_size = 1<<20;
+
+guint cmd_warmups = 2;
+guint cmd_reps = 20;
+
+guint cmd_changes = 1000;
+guint cmd_deletion_length = 30;
+guint cmd_insertion_length = 15;
+
+//////////////////////////////////////////////////////////////////////
+//
+//////////////////////////////////////////////////////////////////////
+
+#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0]))
+
+#define TEST_PREFIX "/tmp/xdeltatest"
+
+#define TEST_IS_GZIP 1
+#define TEST_IS_XDELTA 2
+
+typedef struct _File File;
+typedef struct _Patch Patch;
+typedef struct _TestProfile TestProfile;
+typedef struct _Instruction Instruction;
+
+struct _Instruction {
+ guint32 offset;
+ guint32 length;
+ Instruction* next;
+};
+
+struct _File
+{
+ char name[MAXPATHLEN];
+};
+
+struct _Patch
+{
+ guint8 *data;
+ guint32 length;
+};
+
+struct _TestProfile
+{
+ const char *name;
+ const char *progname;
+ int flags;
+};
+
+#undef BUFSIZ
+#define BUFSIZ (1<<24)
+
+guint8 __tmp_buffer[BUFSIZ];
+
+// Note: Of course you should be able to set all this up on the
+// command line.
+
+TestProfile cmd_profiles[] =
+{
+ { "xdelta -qn ", "../xdelta", TEST_IS_XDELTA },
+ { "diff --rcs -a", "/usr/bin/diff", 0 },
+ { "gzip ", "/bin/gzip", TEST_IS_GZIP },
+};
+
+int cmd_slevels[] = { 16, 32, 64 };
+int cmd_zlevels[] = { 0, 1, 3, 6, 9 };
+guint16 cmd_seed[3] = { 47384, 8594, 27489 };
+
+FILE* data_source_handle;
+guint data_source_length;
+File* data_source_file;
+
+long current_to_size;
+long current_from_size;
+
+double __total_time;
+off_t __dsize;
+
+void
+reset_stats ()
+{
+ __total_time = 0;
+ __dsize = -1;
+}
+
+void add_tv (GTimer *timer)
+{
+ __total_time += g_timer_elapsed (timer, NULL);
+}
+
+void
+report (TestProfile *tp, int zlevel, int slevel)
+{
+ static gboolean once = TRUE;
+
+ double t = __total_time / (double) cmd_reps;
+ double s;
+ const char *u;
+ char slevel_str[16];
+
+ if (tp->flags & TEST_IS_XDELTA)
+ {
+ sprintf (slevel_str, "%d", slevel);
+ }
+ else
+ {
+ slevel_str[0] = 0;
+ }
+
+ if (tp->flags & TEST_IS_GZIP)
+ {
+ s = __dsize / (double) current_to_size;
+ u = "total size";
+ }
+ else
+ {
+ s = __dsize / (double) (cmd_changes * cmd_insertion_length);
+ u = "insertion size";
+ }
+
+ if (once)
+ {
+ once = FALSE;
+
+ g_print ("Program\t\tzlevel\tslevel\tSeconds\tNormalized\n\t\t\t\t\tcompression\n");
+ }
+
+ g_print ("%s\t%d\t%s\t%0.3f\t%0.3f (of %s)\n", tp->name, zlevel, slevel_str, t, s, u);
+}
+
+guint32
+random_offset (guint len)
+{
+ return lrand48 () % (data_source_length - len);
+}
+
+void
+fail ()
+{
+ g_warning ("FAILURE\n");
+ abort ();
+}
+
+gboolean starts_with (const char* s, const char *start)
+{
+ return strncmp (s, start, strlen (start)) == 0;
+}
+
+Patch*
+read_patch (File *file, struct stat *sbuf)
+{
+ Patch *p = g_new0 (Patch,1);
+ FILE *f = fopen (file->name, "r");
+
+ p->length = (int)sbuf->st_size;
+
+ p->data = g_malloc (p->length);
+
+ if (! f || fread (p->data, 1, p->length, f) != p->length)
+ {
+ perror ("fread");
+ fail ();
+ }
+
+ fclose (f);
+
+ return p;
+}
+
+File*
+empty_file ()
+{
+ static gint count = 0;
+ File *file = g_new0 (File, 1);
+
+ sprintf (file->name, "%s.%d.%d", TEST_PREFIX, getpid (), count++);
+
+ return file;
+}
+
+void
+compare_files (File* fromfile, File* tofile)
+{
+ gint pos = 0;
+
+ FILE* toh;
+ FILE* fromh;
+
+ guint8 buf1[1024], buf2[1024];
+
+ toh = fopen (tofile->name, "r");
+ fromh = fopen (fromfile->name, "r");
+
+ if (!toh || !fromh)
+ fail ();
+
+ for (;;)
+ {
+ gint readf = fread (buf1, 1, 1024, fromh);
+ gint readt = fread (buf2, 1, 1024, toh);
+ gint i, m = MIN(readf, readt);
+
+ if (readf < 0 || readt < 0)
+ fail ();
+
+ for (i = 0; i < m; i += 1, pos += 1)
+ {
+ if (buf1[i] != buf2[i])
+ fail ();
+ }
+
+ if (m != 1024)
+ {
+ if (readt == readf)
+ {
+ fclose (toh);
+ fclose (fromh);
+ return;
+ }
+
+ fail ();
+ }
+ }
+}
+
+int
+write_file (File* file, Instruction* inst)
+{
+ FILE* h;
+ int ret;
+ int size = 0;
+
+ if (! (h = fopen (file->name, "w"))) {
+ perror (file->name);
+ fail ();
+ }
+
+ for (; inst; inst = inst->next)
+ {
+ g_assert (inst->length <= BUFSIZ);
+
+ if ((ret = fseek (data_source_handle, inst->offset, SEEK_SET))) {
+ perror ("fseek");
+ fail ();
+ }
+
+ if ((ret = fread (__tmp_buffer, 1, inst->length, data_source_handle)) != inst->length) {
+ perror ("fread");
+ fail ();
+ }
+
+ if ((ret = fwrite (__tmp_buffer, 1, inst->length, h)) != inst->length) {
+ perror ("fwrite");
+ fail ();
+ }
+
+ size += inst->length;
+ }
+
+ if ((ret = fclose (h))) {
+ perror ("fclose");
+ fail ();
+ }
+
+ return size;
+}
+
+Instruction*
+perform_change_rec (Instruction* inst, guint32 change_off, guint* total_len)
+{
+ if (change_off < inst->length)
+ {
+ guint32 to_delete = cmd_deletion_length;
+ guint32 avail = inst->length;
+ guint32 this_delete = MIN (to_delete, avail);
+ Instruction* new_inst;
+
+ // One delete
+ inst->length -= this_delete;
+ to_delete -= this_delete;
+
+ while (to_delete > 0 && inst->next->length < to_delete)
+ {
+ to_delete -= inst->next->length;
+ inst->next = inst->next->next;
+ }
+
+ if (to_delete > 0)
+ inst->next->offset += to_delete;
+
+ // One insert
+ new_inst = g_new0 (Instruction, 1);
+
+ new_inst->offset = random_offset (cmd_insertion_length);
+ new_inst->length = cmd_insertion_length;
+ new_inst->next = inst->next;
+ inst->next = new_inst;
+
+ (* total_len) += cmd_insertion_length - cmd_deletion_length;
+
+ return inst;
+ }
+ else
+ {
+ inst->next = perform_change_rec (inst->next, change_off - inst->length, total_len);
+ return inst;
+ }
+}
+
+Instruction*
+perform_change (Instruction* inst, guint* len)
+{
+ return perform_change_rec (inst, lrand48() % ((* len) - cmd_deletion_length), len);
+}
+
+gboolean
+xdelta_verify (TestProfile *tp, int zlevel, int slevel, File* from, File* to, File* out, File *re)
+{
+ int pid, status;
+
+ if ((pid = vfork()) < 0)
+ return FALSE;
+
+ if (pid == 0)
+ {
+ execl (tp->progname,
+ tp->progname,
+ "patch",
+ out->name,
+ from->name,
+ re->name,
+ NULL);
+ perror ("execl failed");
+ _exit (127);
+ }
+
+ if (waitpid (pid, &status, 0) != pid)
+ {
+ perror ("wait failed");
+ fail ();
+ }
+
+ // Note: program is expected to return 0, 1 meaning diff or no diff,
+ // > 1 indicates failure
+ if (! WIFEXITED (status) || WEXITSTATUS (status) > 1)
+ {
+ g_warning ("patch command failed\n");
+ fail ();
+ }
+
+ compare_files (to, re);
+
+ return TRUE;
+}
+
+gboolean
+run_command (TestProfile *tp, int zlevel, int slevel, File* from, File* to, File* out, File *re, gboolean accounting)
+{
+ int pid, status, outfd;
+ struct stat sbuf;
+ char xdelta_args[16];
+ char xdelta_args2[16];
+ char diff_gzargs[16];
+ char gzip_args[16];
+
+ GTimer *timer = g_timer_new ();
+
+ sprintf (xdelta_args, "-qn%d", zlevel);
+ sprintf (xdelta_args2, "-s%d", slevel);
+ sprintf (diff_gzargs, "wb%d", zlevel);
+ sprintf (gzip_args, "-c%d", zlevel);
+
+ unlink (out->name);
+
+ g_timer_start (timer);
+
+ if ((pid = vfork()) < 0)
+ return FALSE;
+
+ if (pid == 0)
+ {
+ if (starts_with (tp->name, "xdelta"))
+ {
+ execl (tp->progname,
+ tp->progname,
+ "delta",
+ xdelta_args,
+ xdelta_args2,
+ from->name,
+ to->name,
+ out->name,
+ NULL);
+ }
+ else
+ {
+ outfd = open (out->name, O_CREAT | O_TRUNC | O_WRONLY, 0777);
+
+ if (outfd < 0)
+ {
+ perror ("open");
+ fail ();
+ }
+
+ dup2(outfd, STDOUT_FILENO);
+
+ if (close (outfd))
+ {
+ perror ("close");
+ fail ();
+ }
+
+ if (starts_with (tp->name, "diff"))
+ execl (tp->progname,
+ tp->progname,
+ "--rcs",
+ "-a",
+ from->name,
+ to->name,
+ NULL);
+ else if (starts_with (tp->name, "gzip"))
+ execl (tp->progname,
+ tp->progname,
+ gzip_args,
+ to->name,
+ NULL);
+ else
+ abort ();
+ }
+
+ perror ("execl failed");
+ _exit (127);
+ }
+
+ if (waitpid (pid, &status, 0) != pid)
+ {
+ perror ("wait failed");
+ fail ();
+ }
+
+ // Note: program is expected to return 0, 1 meaning diff or no diff,
+ // > 1 indicates failure
+ if (! WIFEXITED (status) || WEXITSTATUS (status) > 1)
+ {
+ g_warning ("delta command failed\n");
+ fail ();
+ }
+
+ if (stat (out->name, & sbuf))
+ {
+ perror ("stat");
+ fail ();
+ }
+
+ // Do zlib compression on behalf of diff
+ if (zlevel > 0 && starts_with (tp->name, "diff"))
+ {
+ Patch *patch = read_patch (out, & sbuf);
+ gzFile *rewrite = gzopen (out->name, diff_gzargs);
+
+ if (! rewrite) fail ();
+
+ if (gzwrite (rewrite, patch->data, patch->length) == 0) { perror ("gzwrite"); fail (); }
+ if (gzclose (rewrite) != Z_OK) { perror ("gzclose"); fail (); }
+ if (stat (out->name, & sbuf)) { perror ("stat"); fail (); }
+ }
+
+ g_timer_stop (timer);
+
+ if (accounting)
+ {
+ add_tv (timer);
+ }
+
+ if (__dsize < 0)
+ {
+ __dsize = sbuf.st_size;
+
+ // Verify only once
+
+ if (starts_with (tp->name, "xdelta"))
+ {
+ if (! xdelta_verify (tp, zlevel, slevel, from, to, out, re))
+ {
+ g_warning ("verify failed");
+ fail ();
+ }
+ }
+ }
+ else
+ {
+ if (__dsize != sbuf.st_size)
+ {
+ g_warning ("%s -%d: delta command produced different size deltas: %d and %d",
+ tp->progname, zlevel, (int)__dsize, (int)sbuf.st_size);
+ fail ();
+ }
+ }
+
+ g_timer_destroy (timer);
+
+ return TRUE;
+}
+
+void
+test1 (TestProfile *test_profile,
+ File *from_file,
+ File *to_file,
+ File *out_file,
+ File *re_file)
+{
+ int ret;
+ guint i, change, current_size = cmd_size;
+ guint end_size = (cmd_changes * cmd_insertion_length) + cmd_size;
+ Instruction* inst;
+ struct stat sbuf;
+ int zlevel_i, slevel_i;
+
+ seed48 (cmd_seed);
+
+ if ((ret = stat (cmd_data_source, & sbuf)))
+ {
+ perror (cmd_data_source);
+ fail ();
+ }
+
+ if (! (data_source_handle = fopen (cmd_data_source, "r")))
+ {
+ perror (cmd_data_source);
+ fail ();
+ }
+
+ data_source_length = sbuf.st_size;
+
+ /* arbitrary checks */
+ if (data_source_length < (1.5 * end_size))
+ g_warning ("data source should be longer\n");
+
+ if ((cmd_changes * cmd_deletion_length) > cmd_size)
+ {
+ g_warning ("no copies are expected\n");
+ fail ();
+ }
+
+ inst = g_new0 (Instruction, 1);
+
+ inst->offset = random_offset (cmd_size);
+ inst->length = cmd_size;
+
+ current_from_size = write_file (from_file, inst);
+
+ for (change = 0; change < cmd_changes; change += 1)
+ inst = perform_change (inst, & current_size);
+
+ current_to_size = write_file (to_file, inst);
+
+ for (slevel_i = 0; slevel_i < ARRAY_LENGTH(cmd_slevels); slevel_i += 1)
+ {
+ int slevel = cmd_slevels[slevel_i];
+
+ if ((test_profile->flags & TEST_IS_XDELTA) == 0 && slevel_i != 0)
+ {
+ continue;
+ }
+
+ for (zlevel_i = 0; zlevel_i < ARRAY_LENGTH(cmd_zlevels); zlevel_i += 1)
+ {
+ int zlevel = cmd_zlevels[zlevel_i];
+
+ if (test_profile->flags & TEST_IS_GZIP)
+ {
+ if (zlevel != 1 && zlevel != 9)
+ continue;
+ }
+
+ reset_stats ();
+
+ for (i = 0; i < cmd_warmups + cmd_reps; i += 1)
+ {
+ if (! run_command (test_profile,
+ zlevel,
+ slevel,
+ from_file,
+ to_file,
+ out_file,
+ re_file,
+ (i >= cmd_warmups) /* true => accounting */))
+ {
+ fail ();
+ }
+ }
+
+ report (test_profile, zlevel, slevel);
+ }
+
+ g_print ("\n");
+ }
+}
+
+int
+main (gint argc, gchar** argv)
+{
+ int profile_i;
+
+ File *from_file;
+ File *to_file;
+ File *out_file;
+ File *re_file;
+
+ system ("rm -rf " TEST_PREFIX "*");
+
+ from_file = empty_file ();
+ to_file = empty_file ();
+ out_file = empty_file ();
+ re_file = empty_file ();
+
+ for (profile_i = 0; profile_i < ARRAY_LENGTH(cmd_profiles); profile_i += 1)
+ {
+ test1 (& cmd_profiles[profile_i], from_file, to_file, out_file, re_file);
+
+ system ("rm -rf " TEST_PREFIX "*");
+ }
+
+ return 0;
+}